Added improved rendering and signal detail panel.
This commit is contained in:
parent
6edb2e4912
commit
4546993f0f
|
@ -0,0 +1,9 @@
|
|||
package nl.andrewl.railsignalapi.dao;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.SignalBranchConnection;
|
||||
import org.springframework.data.jpa.repository.JpaRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface SignalBranchConnectionRepository extends JpaRepository<SignalBranchConnection, Long> {
|
||||
}
|
|
@ -18,7 +18,8 @@ public interface SignalRepository extends JpaRepository<Signal, Long> {
|
|||
|
||||
@Query("SELECT DISTINCT s FROM Signal s " +
|
||||
"LEFT JOIN s.branchConnections bc " +
|
||||
"WHERE bc.branch = :branch")
|
||||
"WHERE bc.branch = :branch " +
|
||||
"ORDER BY s.name")
|
||||
List<Signal> findAllConnectedToBranch(Branch branch);
|
||||
|
||||
List<Signal> findAllByRailSystemOrderByName(RailSystem railSystem);
|
||||
|
|
|
@ -8,5 +8,41 @@ public enum Direction {
|
|||
NORTH_WEST,
|
||||
NORTH_EAST,
|
||||
SOUTH_WEST,
|
||||
SOUTH_EAST
|
||||
SOUTH_EAST;
|
||||
|
||||
public Direction opposite() {
|
||||
return switch (this) {
|
||||
case NORTH -> SOUTH;
|
||||
case SOUTH -> NORTH;
|
||||
case EAST -> WEST;
|
||||
case WEST -> EAST;
|
||||
case NORTH_EAST -> SOUTH_WEST;
|
||||
case NORTH_WEST -> SOUTH_EAST;
|
||||
case SOUTH_EAST -> NORTH_WEST;
|
||||
case SOUTH_WEST -> NORTH_EAST;
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isOpposite(Direction other) {
|
||||
return this.opposite().equals(other);
|
||||
}
|
||||
|
||||
public static Direction parse(String s) {
|
||||
s = s.trim().toUpperCase();
|
||||
try {
|
||||
return Direction.valueOf(s);
|
||||
} catch (IllegalArgumentException e) {
|
||||
return switch (s) {
|
||||
case "N" -> NORTH;
|
||||
case "S" -> SOUTH;
|
||||
case "E" -> EAST;
|
||||
case "W" -> WEST;
|
||||
case "NW" -> NORTH_WEST;
|
||||
case "NE" -> NORTH_EAST;
|
||||
case "SW" -> SOUTH_WEST;
|
||||
case "SE" -> SOUTH_EAST;
|
||||
default -> throw new IllegalArgumentException("Invalid direction: " + s);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,13 @@ import lombok.NoArgsConstructor;
|
|||
|
||||
import javax.persistence.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
public class SignalBranchConnection {
|
||||
public class SignalBranchConnection implements Comparable<SignalBranchConnection> {
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
|
@ -33,4 +34,24 @@ public class SignalBranchConnection {
|
|||
this.direction = direction;
|
||||
reachableSignalConnections = new HashSet<>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
return this.id != null &&
|
||||
o instanceof SignalBranchConnection sbc && sbc.getId() != null &&
|
||||
this.id.equals(sbc.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hashCode(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(SignalBranchConnection o) {
|
||||
int c = Long.compare(this.getSignal().getId(), o.getSignal().getId());
|
||||
if (c != 0) return c;
|
||||
return this.direction.compareTo(o.getDirection());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest;
|
|||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.rest.dto.BranchResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
|
||||
import nl.andrewl.railsignalapi.service.BranchService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
@ -19,6 +20,16 @@ public class BranchesController {
|
|||
return branchService.getAllBranches(rsId);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{branchId}")
|
||||
public BranchResponse getBranch(@PathVariable long rsId, @PathVariable long branchId) {
|
||||
return branchService.getBranch(rsId, branchId);
|
||||
}
|
||||
|
||||
@GetMapping(path = "/{branchId}/signals")
|
||||
public List<SignalResponse> getBranchSignals(@PathVariable long rsId, @PathVariable long branchId) {
|
||||
return branchService.getConnectedSignals(rsId, branchId);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{branchId}")
|
||||
public ResponseEntity<?> deleteBranch(@PathVariable long rsId, @PathVariable long branchId) {
|
||||
branchService.deleteBranch(rsId, branchId);
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package nl.andrewl.railsignalapi.rest;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalConnectionsUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
|
||||
import nl.andrewl.railsignalapi.service.SignalService;
|
||||
|
@ -30,6 +31,11 @@ public class SignalsApiController {
|
|||
return signalService.getSignal(rsId, sigId);
|
||||
}
|
||||
|
||||
@PostMapping(path = "/{sigId}/signalConnections")
|
||||
public SignalResponse updateSignalConnections(@PathVariable long rsId, @PathVariable long sigId, @RequestBody SignalConnectionsUpdatePayload payload) {
|
||||
return signalService.updateSignalBranchConnections(rsId, sigId, payload);
|
||||
}
|
||||
|
||||
@DeleteMapping(path = "/{sigId}")
|
||||
public ResponseEntity<?> deleteSignal(@PathVariable long rsId, @PathVariable long sigId) {
|
||||
signalService.deleteSignal(rsId, sigId);
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record SignalConnectionsUpdatePayload(
|
||||
List<ConnectionData> connections
|
||||
) {
|
||||
public static record ConnectionData(long from, long to) {}
|
||||
}
|
|
@ -41,6 +41,7 @@ public record SignalResponse(
|
|||
c.getReachableSignalConnections().stream()
|
||||
.map(cc -> new ReachableConnectionData(
|
||||
cc.getId(),
|
||||
cc.getDirection().name(),
|
||||
cc.getSignal().getId(),
|
||||
cc.getSignal().getName(),
|
||||
cc.getSignal().getPosition()
|
||||
|
@ -51,6 +52,7 @@ public record SignalResponse(
|
|||
|
||||
public static record ReachableConnectionData(
|
||||
long connectionId,
|
||||
String direction,
|
||||
long signalId,
|
||||
String signalName,
|
||||
Position signalPosition
|
||||
|
|
|
@ -3,7 +3,9 @@ package nl.andrewl.railsignalapi.service;
|
|||
import lombok.RequiredArgsConstructor;
|
||||
import nl.andrewl.railsignalapi.dao.BranchRepository;
|
||||
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
||||
import nl.andrewl.railsignalapi.dao.SignalRepository;
|
||||
import nl.andrewl.railsignalapi.rest.dto.BranchResponse;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
@ -16,6 +18,7 @@ import java.util.List;
|
|||
public class BranchService {
|
||||
private final BranchRepository branchRepository;
|
||||
private final RailSystemRepository railSystemRepository;
|
||||
private final SignalRepository signalRepository;
|
||||
|
||||
@Transactional
|
||||
public void deleteBranch(long rsId, long branchId) {
|
||||
|
@ -35,4 +38,20 @@ public class BranchService {
|
|||
.map(BranchResponse::new)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public BranchResponse getBranch(long rsId, long branchId) {
|
||||
var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return new BranchResponse(branch);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<SignalResponse> getConnectedSignals(long rsId, long branchId) {
|
||||
var branch = branchRepository.findByIdAndRailSystemId(branchId, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
return signalRepository.findAllConnectedToBranch(branch).stream()
|
||||
.map(SignalResponse::new)
|
||||
.toList();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,8 +6,10 @@ import lombok.RequiredArgsConstructor;
|
|||
import lombok.extern.slf4j.Slf4j;
|
||||
import nl.andrewl.railsignalapi.dao.BranchRepository;
|
||||
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
|
||||
import nl.andrewl.railsignalapi.dao.SignalBranchConnectionRepository;
|
||||
import nl.andrewl.railsignalapi.dao.SignalRepository;
|
||||
import nl.andrewl.railsignalapi.model.*;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalConnectionsUpdatePayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalCreationPayload;
|
||||
import nl.andrewl.railsignalapi.rest.dto.SignalResponse;
|
||||
import nl.andrewl.railsignalapi.websocket.BranchUpdateMessage;
|
||||
|
@ -32,6 +34,7 @@ public class SignalService {
|
|||
private final RailSystemRepository railSystemRepository;
|
||||
private final SignalRepository signalRepository;
|
||||
private final BranchRepository branchRepository;
|
||||
private final SignalBranchConnectionRepository signalBranchConnectionRepository;
|
||||
|
||||
private final ObjectMapper mapper = new ObjectMapper();
|
||||
private final Map<WebSocketSession, Set<Long>> signalWebSocketSessions = new ConcurrentHashMap<>();
|
||||
|
@ -43,6 +46,14 @@ public class SignalService {
|
|||
if (signalRepository.existsByNameAndRailSystem(payload.name(), rs)) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal " + payload.name() + " already exists.");
|
||||
}
|
||||
if (payload.branchConnections().size() != 2) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Exactly two branch connections must be provided.");
|
||||
}
|
||||
// Ensure that the directions of the connections are opposite each other.
|
||||
Direction dir1 = Direction.parse(payload.branchConnections().get(0).direction());
|
||||
Direction dir2 = Direction.parse(payload.branchConnections().get(1).direction());
|
||||
if (!dir1.isOpposite(dir2)) throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Branch connections must be opposite each other.");
|
||||
|
||||
Set<SignalBranchConnection> branchConnections = new HashSet<>();
|
||||
Signal signal = new Signal(rs, payload.name(), payload.position(), branchConnections);
|
||||
for (var branchData : payload.branchConnections()) {
|
||||
|
@ -61,6 +72,39 @@ public class SignalService {
|
|||
return new SignalResponse(signal);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public SignalResponse updateSignalBranchConnections(long rsId, long sigId, SignalConnectionsUpdatePayload payload) {
|
||||
var signal = signalRepository.findByIdAndRailSystemId(sigId, rsId)
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
|
||||
for (var c : payload.connections()) {
|
||||
var fromConnection = signalBranchConnectionRepository.findById(c.from())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not find signal branch connection: " + c.from()));
|
||||
if (!fromConnection.getSignal().getId().equals(signal.getId())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Can only update signal branch connections originating from the specified signal.");
|
||||
}
|
||||
var toConnection = signalBranchConnectionRepository.findById(c.to())
|
||||
.orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST, "Could not find signal branch connection: " + c.to()));
|
||||
if (!fromConnection.getBranch().getId().equals(toConnection.getBranch().getId())) {
|
||||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal branch connections can only path via a mutual branch.");
|
||||
}
|
||||
fromConnection.getReachableSignalConnections().add(toConnection);
|
||||
signalBranchConnectionRepository.save(fromConnection);
|
||||
}
|
||||
for (var con : signal.getBranchConnections()) {
|
||||
Set<SignalBranchConnection> connectionsToRemove = new HashSet<>();
|
||||
for (var reachableCon : con.getReachableSignalConnections()) {
|
||||
if (!payload.connections().contains(new SignalConnectionsUpdatePayload.ConnectionData(con.getId(), reachableCon.getId()))) {
|
||||
connectionsToRemove.add(reachableCon);
|
||||
}
|
||||
}
|
||||
con.getReachableSignalConnections().removeAll(connectionsToRemove);
|
||||
signalBranchConnectionRepository.save(con);
|
||||
}
|
||||
// Reload the signal.
|
||||
signal = signalRepository.findById(signal.getId()).orElseThrow();
|
||||
return new SignalResponse(signal);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void registerSignalWebSocketSession(Set<Long> signalIds, WebSocketSession session) {
|
||||
this.signalWebSocketSessions.put(session, signalIds);
|
||||
|
|
|
@ -11,26 +11,30 @@ function worldTransform() {
|
|||
return tx;
|
||||
}
|
||||
|
||||
function mousePointToWorld(event) {
|
||||
const rect = railMapCanvas[0].getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
return worldTransform().invertSelf().transformPoint(new DOMPoint(x, y, 0, 1));
|
||||
function canvasPointToWorld(p) {
|
||||
return worldTransform().invertSelf().transformPoint(p);
|
||||
}
|
||||
|
||||
function worldPointToCanvas(p) {
|
||||
return worldTransform().transformPoint(p);
|
||||
}
|
||||
|
||||
function signalTransform(worldTx, signal) {
|
||||
let tx = DOMMatrix.fromMatrix(worldTx);
|
||||
tx.translateSelf(signal.position.x, signal.position.z, 0);
|
||||
let direction = signal.branchConnections[0].direction;
|
||||
if (direction === "EAST" || direction === "SOUTH" || direction === "SOUTH_EAST" || direction === "SOUTH_WEST") {
|
||||
direction = signal.branchConnections[1].direction;
|
||||
}
|
||||
if (direction === undefined || direction === null || direction === "") {
|
||||
direction = "NORTH";
|
||||
}
|
||||
let angle = 0;
|
||||
if (direction === "NORTH" || direction === "SOUTH") {
|
||||
if (direction === "NORTH") {
|
||||
angle = 90;
|
||||
} else if (direction === "NORTH_WEST" || direction === "SOUTH_EAST") {
|
||||
} else if (direction === "NORTH_WEST") {
|
||||
angle = 45;
|
||||
} else if (direction === "NORTH_EAST" || direction === "SOUTH_WEST") {
|
||||
} else if (direction === "NORTH_EAST") {
|
||||
angle = 135;
|
||||
}
|
||||
tx.rotateSelf(0, 0, angle);
|
||||
|
@ -56,8 +60,6 @@ function drawRailSystem() {
|
|||
ctx.font = '24px Serif';
|
||||
let textLine = 0;
|
||||
hoveredElements.forEach(element => {
|
||||
console.log('printing!')
|
||||
console.log(element);
|
||||
ctx.strokeText(element.name, 10, 20 + textLine * 20);
|
||||
ctx.fillText(element.name, 10, 20 + textLine * 20);
|
||||
textLine += 1;
|
||||
|
@ -72,17 +74,17 @@ function drawSignal(ctx, signal) {
|
|||
}
|
||||
ctx.scale(2, 2);
|
||||
ctx.fillRect(-0.5, -0.5, 1, 1);
|
||||
let firstCon = signal.branchConnections[0];
|
||||
let secondCon = signal.branchConnections[1];
|
||||
if (firstCon === "EAST" || firstCon === "SOUTH" || firstCon === "SOUTH_WEST" || firstCon === "SOUTH_EAST") {
|
||||
let tmp = firstCon;
|
||||
firstCon = secondCon;
|
||||
secondCon = tmp;
|
||||
let northWesterlyCon = signal.branchConnections[0];
|
||||
let southEasterlyCon = signal.branchConnections[1];
|
||||
if (northWesterlyCon.direction === "EAST" || northWesterlyCon.direction === "SOUTH" || northWesterlyCon.direction === "SOUTH_WEST" || northWesterlyCon.direction === "SOUTH_EAST") {
|
||||
let tmp = northWesterlyCon;
|
||||
northWesterlyCon = southEasterlyCon;
|
||||
southEasterlyCon = tmp;
|
||||
}
|
||||
|
||||
ctx.fillStyle = getSignalColor(signal, firstCon.branch.status);
|
||||
ctx.fillStyle = getSignalColor(signal, southEasterlyCon.branch.status);
|
||||
ctx.fillRect(-0.75, -0.4, 0.3, 0.8);
|
||||
ctx.fillStyle = getSignalColor(signal, secondCon.branch.status);
|
||||
ctx.fillStyle = getSignalColor(signal, northWesterlyCon.branch.status);
|
||||
ctx.fillRect(0.45, -0.4, 0.3, 0.8);
|
||||
}
|
||||
|
||||
|
@ -97,15 +99,33 @@ function getSignalColor(signal, branchStatus) {
|
|||
}
|
||||
}
|
||||
|
||||
// Draws lines indicating reachable paths between this signal and others, with arrows for directionality.
|
||||
function drawReachableConnections(ctx, signal) {
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.lineWidth = 0.25;
|
||||
signal.branchConnections.forEach(connection => {
|
||||
ctx.resetTransform();
|
||||
connection.reachableSignalConnections.forEach(reachableCon => {
|
||||
const dx = reachableCon.signalPosition.x - signal.position.x;
|
||||
const dy = reachableCon.signalPosition.z - signal.position.z;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
let tx = worldTransform();
|
||||
tx.translateSelf(signal.position.x, signal.position.z, 0);
|
||||
const angle = Math.atan2(dy, dx) * 180 / Math.PI - 90;
|
||||
tx.rotateSelf(0, 0, angle);
|
||||
ctx.setTransform(tx);
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(signal.position.x, signal.position.z);
|
||||
ctx.lineTo(reachableCon.signalPosition.x, reachableCon.signalPosition.z);
|
||||
ctx.moveTo(0, 0);
|
||||
ctx.lineTo(0, dist);
|
||||
const arrowEnd = 5;
|
||||
const arrowWidth = 0.5;
|
||||
const arrowLength = 1;
|
||||
ctx.lineTo(0, arrowEnd);
|
||||
ctx.lineTo(arrowWidth, arrowEnd - arrowLength);
|
||||
ctx.lineTo(-arrowWidth, arrowEnd - arrowLength);
|
||||
ctx.lineTo(0, arrowEnd);
|
||||
ctx.stroke();
|
||||
ctx.fill();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -9,8 +9,8 @@ let canvasDragOrigin = null;
|
|||
let canvasDragTranslation = null;
|
||||
let hoveredElements = [];
|
||||
|
||||
const SCALE_VALUES = [0.01, 0.1, 1.0, 1.25, 1.5, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
||||
const SCALE_INDEX_NORMAL = 5;
|
||||
const SCALE_VALUES = [0.01, 0.1, 0.25, 0.5, 1.0, 1.25, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 10.0, 12.0, 16.0, 20.0, 30.0, 45.0, 60.0, 80.0, 100.0];
|
||||
const SCALE_INDEX_NORMAL = 7;
|
||||
let canvasScaleIndex = SCALE_INDEX_NORMAL;
|
||||
|
||||
$(document).ready(() => {
|
||||
|
@ -18,10 +18,10 @@ $(document).ready(() => {
|
|||
railSystemSelect.change(railSystemChanged);
|
||||
|
||||
railMapCanvas = $('#railMapCanvas');
|
||||
railMapCanvas.on('wheel', onMouseWheel);
|
||||
railMapCanvas.mousedown(onMouseDown);
|
||||
railMapCanvas.mouseup(onMouseUp);
|
||||
railMapCanvas.mousemove(onMouseMove);
|
||||
railMapCanvas.on('wheel', onCanvasMouseWheel);
|
||||
railMapCanvas.mousedown(onCanvasMouseDown);
|
||||
railMapCanvas.mouseup(onCanvasMouseUp);
|
||||
railMapCanvas.mousemove(onCanvasMouseMove);
|
||||
|
||||
$.get("/api/railSystems")
|
||||
.done(railSystems => {
|
||||
|
@ -34,7 +34,8 @@ $(document).ready(() => {
|
|||
});
|
||||
});
|
||||
|
||||
function onMouseWheel(event) {
|
||||
// Handle mouse scrolling within the context of the canvas.
|
||||
function onCanvasMouseWheel(event) {
|
||||
let s = event.originalEvent.deltaY;
|
||||
if (s > 0) {
|
||||
canvasScaleIndex = Math.max(0, canvasScaleIndex - 1);
|
||||
|
@ -45,33 +46,40 @@ function onMouseWheel(event) {
|
|||
event.stopPropagation();
|
||||
}
|
||||
|
||||
function onMouseDown(event) {
|
||||
const rect = railMapCanvas[0].getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
canvasDragOrigin = {x: x, y: y};
|
||||
// Handle mouse clicks on the canvas.
|
||||
function onCanvasMouseDown(event) {
|
||||
const p = getMousePoint(event);
|
||||
canvasDragOrigin = {x: p.x, y: p.y};
|
||||
}
|
||||
|
||||
function onMouseUp(event) {
|
||||
// Handle mouse release on the canvas, which stops dragging or indicates that the user may have clicked on something.
|
||||
function onCanvasMouseUp(event) {
|
||||
if (canvasDragTranslation !== null) {
|
||||
canvasTranslation.x += canvasDragTranslation.x;
|
||||
canvasTranslation.y += canvasDragTranslation.y;
|
||||
} else {
|
||||
const p = mousePointToWorld(event);
|
||||
const p = getMousePoint(event);
|
||||
let signalClicked = false;
|
||||
railSystem.signals.forEach(signal => {
|
||||
const sp = new DOMPoint(signal.position.x, signal.position.z, 0, 1);
|
||||
const dist = Math.sqrt(Math.pow(p.x - sp.x, 2) + Math.pow(p.y - sp.y, 2));
|
||||
if (dist < 1) {
|
||||
const canvasSp = worldPointToCanvas(sp);
|
||||
const dist = Math.sqrt(Math.pow(p.x - canvasSp.x, 2) + Math.pow(p.y - canvasSp.y, 2));
|
||||
if (dist < 5) {
|
||||
console.log(signal);
|
||||
$('#testingText').val(JSON.stringify(signal, null, 2));
|
||||
onSignalSelected(signal);
|
||||
signalClicked = true;
|
||||
}
|
||||
});
|
||||
if (!signalClicked) {
|
||||
onSignalSelected(null);
|
||||
}
|
||||
}
|
||||
canvasDragOrigin = null;
|
||||
canvasDragTranslation = null;
|
||||
}
|
||||
|
||||
function onMouseMove(event) {
|
||||
// Handle mouse motion over the canvas. This is for dragging and hovering over items.
|
||||
function onCanvasMouseMove(event) {
|
||||
const rect = railMapCanvas[0].getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
|
@ -83,11 +91,12 @@ function onMouseMove(event) {
|
|||
drawRailSystem();
|
||||
} else {
|
||||
hoveredElements = [];
|
||||
const p = mousePointToWorld(event);
|
||||
const p = getMousePoint(event);
|
||||
railSystem.signals.forEach(signal => {
|
||||
const sp = new DOMPoint(signal.position.x, signal.position.z, 0, 1);
|
||||
const dist = Math.sqrt(Math.pow(p.x - sp.x, 2) + Math.pow(p.y - sp.y, 2));
|
||||
if (dist < 1) {
|
||||
const canvasSp = worldPointToCanvas(sp);
|
||||
const dist = Math.sqrt(Math.pow(p.x - canvasSp.x, 2) + Math.pow(p.y - canvasSp.y, 2));
|
||||
if (dist < 5) {
|
||||
hoveredElements.push(signal);
|
||||
}
|
||||
});
|
||||
|
@ -95,6 +104,13 @@ function onMouseMove(event) {
|
|||
}
|
||||
}
|
||||
|
||||
function getMousePoint(event) {
|
||||
const rect = railMapCanvas[0].getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
return new DOMPoint(x, y, 0, 1);
|
||||
}
|
||||
|
||||
function railSystemChanged() {
|
||||
railSystem = {};
|
||||
railSystem.id = railSystemSelect.val();
|
||||
|
@ -102,14 +118,30 @@ function railSystemChanged() {
|
|||
.done(signals => {
|
||||
railSystem.signals = signals;
|
||||
let bb = getRailSystemBoundingBox();
|
||||
// canvasTranslation.x = -1 * (bb.x + (bb.width / 2));
|
||||
// canvasTranslation.y = -1 * (bb.y + (bb.height / 2));
|
||||
// canvasScaleIndex = SCALE_INDEX_NORMAL;
|
||||
canvasTranslation.x = -1 * (bb.x + (bb.width / 2));
|
||||
canvasTranslation.y = -1 * (bb.y + (bb.height / 2));
|
||||
canvasScaleIndex = SCALE_INDEX_NORMAL;
|
||||
drawRailSystem();
|
||||
window.setTimeout(railSystemChanged, 1000);
|
||||
});
|
||||
$.get("/api/railSystems/" + railSystem.id + "/branches")
|
||||
.done(branches => {
|
||||
railSystem.branches = branches;
|
||||
});
|
||||
}
|
||||
|
||||
function selectSignalById(id) {
|
||||
railSystem.signals.forEach(signal => {
|
||||
if (signal.id === id) {
|
||||
onSignalSelected(signal);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function onSignalSelected(signal) {
|
||||
const dp = $('#railMapDetailPanel');
|
||||
dp.empty();
|
||||
if (signal !== null) {
|
||||
const tpl = Handlebars.compile($('#signalTemplate').html());
|
||||
dp.html(tpl(signal));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,21 +37,52 @@
|
|||
<hr class="my-4"/>
|
||||
|
||||
<div class="row">
|
||||
<div id="railMapCanvasParent" class="col border p-0">
|
||||
<canvas id="railMapCanvas" width="1000" height="500"></canvas>
|
||||
<div id="railMapCanvasParent" class="col">
|
||||
<canvas id="railMapCanvas" width="800" height="500" class="border"></canvas>
|
||||
</div>
|
||||
<div id="railMapDetailPanel" class="col p-2 border">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<textarea id="testingText"></textarea>
|
||||
</div>
|
||||
<div style="display: none;">
|
||||
<script id="signalTemplate" type="text/x-handlebars-template">
|
||||
<h3>{{name}}</h3>
|
||||
<hr>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">ID</dt>
|
||||
<dd class="col-sm-9">{{id}}</dd>
|
||||
<dt class="col-sm-3">Position</dt>
|
||||
<dd class="col-sm-9">{{position.x}}, {{position.y}}, {{position.z}}</dd>
|
||||
<dt class="col-sm-3">Online</dt>
|
||||
<dd class="col-sm-9">{{online}}</dd>
|
||||
</dl>
|
||||
<h4>Connections</h4>
|
||||
<hr>
|
||||
|
||||
{{#each branchConnections}}
|
||||
<h5>{{this.direction}} <small class="text-muted">{{this.id}}</small></h5>
|
||||
<dl class="row">
|
||||
<dt class="col-sm-3">Branch</dt>
|
||||
<dd class="col-sm-9">{{this.branch.name}} <small class="text-muted">{{this.branch.id}}</small></dd>
|
||||
{{#if this.reachableSignalConnections.length}}
|
||||
<dt class="col-sm-3">Reachable Signals</dt>
|
||||
<dd class="col-sm-9">
|
||||
{{#each this.reachableSignalConnections}}
|
||||
<p><a onclick="selectSignalById({{this.signalId}})" href="#">{{this.signalName}}</a> <small class="text-muted">{{this.signalId}}</small> via {{this.direction}} connection {{this.connectionId}}</p>
|
||||
{{/each}}
|
||||
</dd>
|
||||
{{/if}}
|
||||
</dl>
|
||||
{{/each}}
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js" integrity="sha256-/xUj+3OJU5yExlq6GSYGSHk7tPXikynS7ogEvDej/m4=" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/handlebars.js/4.7.7/handlebars.min.js" integrity="sha512-RNLkV3d+aLtfcpEyFG8jRbnWHxUqVZozacROI4J2F1sTaDqo1dPQYs01OMi1t1w9Y2FdbSCDSQ2ZVdAC8bzgAg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
<script src="/static/js/drawing.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue