Added single page for signal system.
This commit is contained in:
parent
cbbf74ee4a
commit
6edb2e4912
8
pom.xml
8
pom.xml
|
@ -25,6 +25,14 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-devtools</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-websocket</artifactId>
|
||||
|
|
|
@ -24,13 +24,17 @@ public class Signal {
|
|||
@OneToMany(mappedBy = "signal", orphanRemoval = true, cascade = CascadeType.ALL)
|
||||
private Set<SignalBranchConnection> branchConnections;
|
||||
|
||||
@Embedded
|
||||
private Position position;
|
||||
|
||||
@Column(nullable = false)
|
||||
@Setter
|
||||
private boolean online = false;
|
||||
|
||||
public Signal(RailSystem railSystem, String name, Set<SignalBranchConnection> branchConnections) {
|
||||
public Signal(RailSystem railSystem, String name, Position position, Set<SignalBranchConnection> branchConnections) {
|
||||
this.railSystem = railSystem;
|
||||
this.name = name;
|
||||
this.position = position;
|
||||
this.branchConnections = branchConnections;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@ import lombok.Getter;
|
|||
import lombok.NoArgsConstructor;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@Entity
|
||||
@NoArgsConstructor
|
||||
|
@ -22,9 +24,13 @@ public class SignalBranchConnection {
|
|||
@Enumerated(EnumType.STRING)
|
||||
private Direction direction;
|
||||
|
||||
@ManyToMany
|
||||
private Set<SignalBranchConnection> reachableSignalConnections;
|
||||
|
||||
public SignalBranchConnection(Signal signal, Branch branch, Direction direction) {
|
||||
this.signal = signal;
|
||||
this.branch = branch;
|
||||
this.direction = direction;
|
||||
reachableSignalConnections = new HashSet<>();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
package nl.andrewl.railsignalapi.page;
|
||||
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
||||
@Controller
|
||||
@RequestMapping(path = "/")
|
||||
public class IndexPageController {
|
||||
@GetMapping
|
||||
public String getIndex() {
|
||||
return "index";
|
||||
}
|
||||
}
|
|
@ -2,10 +2,15 @@ package nl.andrewl.railsignalapi.rest;
|
|||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
public class WebConfig implements WebMvcConfigurer {
|
||||
|
||||
@Override
|
||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||
registry.addResourceHandler("/static/**")
|
||||
.addResourceLocations("classpath:/static/");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.Position;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public record SignalCreationPayload(
|
||||
String name,
|
||||
Position position,
|
||||
List<BranchData> branchConnections
|
||||
) {
|
||||
public static record BranchData(String direction, String name, Long id) {}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package nl.andrewl.railsignalapi.rest.dto;
|
||||
|
||||
import nl.andrewl.railsignalapi.model.Position;
|
||||
import nl.andrewl.railsignalapi.model.Signal;
|
||||
import nl.andrewl.railsignalapi.model.SignalBranchConnection;
|
||||
|
||||
|
@ -9,6 +10,7 @@ import java.util.List;
|
|||
public record SignalResponse(
|
||||
long id,
|
||||
String name,
|
||||
Position position,
|
||||
List<ConnectionData> branchConnections,
|
||||
boolean online
|
||||
) {
|
||||
|
@ -16,6 +18,7 @@ public record SignalResponse(
|
|||
this(
|
||||
signal.getId(),
|
||||
signal.getName(),
|
||||
signal.getPosition(),
|
||||
signal.getBranchConnections().stream()
|
||||
.sorted(Comparator.comparing(SignalBranchConnection::getDirection))
|
||||
.map(ConnectionData::new)
|
||||
|
@ -25,11 +28,32 @@ public record SignalResponse(
|
|||
}
|
||||
|
||||
public static record ConnectionData(
|
||||
long id,
|
||||
String direction,
|
||||
BranchResponse branch
|
||||
BranchResponse branch,
|
||||
List<ReachableConnectionData> reachableSignalConnections
|
||||
) {
|
||||
public ConnectionData(SignalBranchConnection c) {
|
||||
this(c.getDirection().name(), new BranchResponse(c.getBranch()));
|
||||
this(
|
||||
c.getId(),
|
||||
c.getDirection().name(),
|
||||
new BranchResponse(c.getBranch()),
|
||||
c.getReachableSignalConnections().stream()
|
||||
.map(cc -> new ReachableConnectionData(
|
||||
cc.getId(),
|
||||
cc.getSignal().getId(),
|
||||
cc.getSignal().getName(),
|
||||
cc.getSignal().getPosition()
|
||||
))
|
||||
.toList()
|
||||
);
|
||||
}
|
||||
|
||||
public static record ReachableConnectionData(
|
||||
long connectionId,
|
||||
long signalId,
|
||||
String signalName,
|
||||
Position signalPosition
|
||||
) {}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,7 @@ public class SignalService {
|
|||
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "Signal " + payload.name() + " already exists.");
|
||||
}
|
||||
Set<SignalBranchConnection> branchConnections = new HashSet<>();
|
||||
Signal signal = new Signal(rs, payload.name(), branchConnections);
|
||||
Signal signal = new Signal(rs, payload.name(), payload.position(), branchConnections);
|
||||
for (var branchData : payload.branchConnections()) {
|
||||
Branch branch;
|
||||
if (branchData.id() != null) {
|
||||
|
@ -61,7 +61,7 @@ public class SignalService {
|
|||
return new SignalResponse(signal);
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
@Transactional
|
||||
public void registerSignalWebSocketSession(Set<Long> signalIds, WebSocketSession session) {
|
||||
this.signalWebSocketSessions.put(session, signalIds);
|
||||
// Instantly send a data packet so that the signals are up-to-date.
|
||||
|
|
|
@ -0,0 +1,124 @@
|
|||
function worldTransform() {
|
||||
const canvasRect = railMapCanvas[0].getBoundingClientRect();
|
||||
const scale = SCALE_VALUES[canvasScaleIndex];
|
||||
let tx = new DOMMatrix();
|
||||
tx.translateSelf(canvasRect.width / 2, canvasRect.height / 2, 0);
|
||||
tx.scaleSelf(scale, scale, scale);
|
||||
tx.translateSelf(canvasTranslation.x, canvasTranslation.y, 0);
|
||||
if (canvasDragOrigin !== null && canvasDragTranslation !== null) {
|
||||
tx.translateSelf(canvasDragTranslation.x, canvasDragTranslation.y, 0);
|
||||
}
|
||||
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 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 === undefined || direction === null || direction === "") {
|
||||
direction = "NORTH";
|
||||
}
|
||||
let angle = 0;
|
||||
if (direction === "NORTH" || direction === "SOUTH") {
|
||||
angle = 90;
|
||||
} else if (direction === "NORTH_WEST" || direction === "SOUTH_EAST") {
|
||||
angle = 45;
|
||||
} else if (direction === "NORTH_EAST" || direction === "SOUTH_WEST") {
|
||||
angle = 135;
|
||||
}
|
||||
tx.rotateSelf(0, 0, angle);
|
||||
return tx;
|
||||
}
|
||||
|
||||
function drawRailSystem() {
|
||||
let ctx = railMapCanvas[0].getContext("2d");
|
||||
ctx.resetTransform();
|
||||
ctx.clearRect(0, 0, railMapCanvas.width(), railMapCanvas.height());
|
||||
const worldTx = worldTransform();
|
||||
ctx.setTransform(worldTx);
|
||||
railSystem.signals.forEach(signal => {
|
||||
drawReachableConnections(ctx, signal);
|
||||
});
|
||||
railSystem.signals.forEach(signal => {
|
||||
ctx.setTransform(signalTransform(worldTx, signal));
|
||||
drawSignal(ctx, signal);
|
||||
});
|
||||
ctx.resetTransform();
|
||||
ctx.fillStyle = 'black';
|
||||
ctx.strokeStyle = 'black';
|
||||
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;
|
||||
});
|
||||
}
|
||||
|
||||
function drawSignal(ctx, signal) {
|
||||
if (signal.online) {
|
||||
ctx.fillStyle = 'black';
|
||||
} else {
|
||||
ctx.fillStyle = 'gray';
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
ctx.fillStyle = getSignalColor(signal, firstCon.branch.status);
|
||||
ctx.fillRect(-0.75, -0.4, 0.3, 0.8);
|
||||
ctx.fillStyle = getSignalColor(signal, secondCon.branch.status);
|
||||
ctx.fillRect(0.45, -0.4, 0.3, 0.8);
|
||||
}
|
||||
|
||||
function getSignalColor(signal, branchStatus) {
|
||||
if (!signal.online) return 'rgb(0, 0, 255)';
|
||||
if (branchStatus === "FREE") {
|
||||
return 'rgb(0, 255, 0)';
|
||||
} else if (branchStatus === "OCCUPIED") {
|
||||
return 'rgb(255, 0, 0)';
|
||||
} else {
|
||||
return 'rgb(0, 0, 255)';
|
||||
}
|
||||
}
|
||||
|
||||
function drawReachableConnections(ctx, signal) {
|
||||
ctx.strokeStyle = 'black';
|
||||
ctx.lineWidth = 0.25;
|
||||
signal.branchConnections.forEach(connection => {
|
||||
connection.reachableSignalConnections.forEach(reachableCon => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(signal.position.x, signal.position.z);
|
||||
ctx.lineTo(reachableCon.signalPosition.x, reachableCon.signalPosition.z);
|
||||
ctx.stroke();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getRailSystemBoundingBox() {
|
||||
let min = {x: Number.MAX_SAFE_INTEGER, z: Number.MAX_SAFE_INTEGER};
|
||||
let max = {x: Number.MIN_SAFE_INTEGER, z: Number.MIN_SAFE_INTEGER};
|
||||
railSystem.signals.forEach(signal => {
|
||||
let p = signal.position;
|
||||
if (p.x < min.x) min.x = p.x;
|
||||
if (p.z < min.z) min.z = p.z;
|
||||
if (p.x > max.x) max.x = p.x;
|
||||
if (p.z > max.z) max.z = p.z;
|
||||
});
|
||||
return {x: min.x, y: min.z, width: Math.abs(max.x - min.x), height: Math.abs(max.z - min.z)};
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
const $ = jQuery;
|
||||
|
||||
let railSystemSelect;
|
||||
let railMapCanvas;
|
||||
let railSystem = null;
|
||||
|
||||
let canvasTranslation = {x: 0, y: 0};
|
||||
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;
|
||||
let canvasScaleIndex = SCALE_INDEX_NORMAL;
|
||||
|
||||
$(document).ready(() => {
|
||||
railSystemSelect = $('#railSystemSelect');
|
||||
railSystemSelect.change(railSystemChanged);
|
||||
|
||||
railMapCanvas = $('#railMapCanvas');
|
||||
railMapCanvas.on('wheel', onMouseWheel);
|
||||
railMapCanvas.mousedown(onMouseDown);
|
||||
railMapCanvas.mouseup(onMouseUp);
|
||||
railMapCanvas.mousemove(onMouseMove);
|
||||
|
||||
$.get("/api/railSystems")
|
||||
.done(railSystems => {
|
||||
railSystems.forEach(railSystem => {
|
||||
let option = $('<option value="' + railSystem.id + '">' + railSystem.name + '</option>')
|
||||
railSystemSelect.append(option);
|
||||
});
|
||||
railSystemSelect.val(railSystems[0].id);
|
||||
railSystemSelect.change();
|
||||
});
|
||||
});
|
||||
|
||||
function onMouseWheel(event) {
|
||||
let s = event.originalEvent.deltaY;
|
||||
if (s > 0) {
|
||||
canvasScaleIndex = Math.max(0, canvasScaleIndex - 1);
|
||||
} else if (s < 0) {
|
||||
canvasScaleIndex = Math.min(SCALE_VALUES.length - 1, canvasScaleIndex + 1);
|
||||
}
|
||||
drawRailSystem();
|
||||
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};
|
||||
}
|
||||
|
||||
function onMouseUp(event) {
|
||||
if (canvasDragTranslation !== null) {
|
||||
canvasTranslation.x += canvasDragTranslation.x;
|
||||
canvasTranslation.y += canvasDragTranslation.y;
|
||||
} else {
|
||||
const p = mousePointToWorld(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) {
|
||||
console.log(signal);
|
||||
$('#testingText').val(JSON.stringify(signal, null, 2));
|
||||
}
|
||||
});
|
||||
}
|
||||
canvasDragOrigin = null;
|
||||
canvasDragTranslation = null;
|
||||
}
|
||||
|
||||
function onMouseMove(event) {
|
||||
const rect = railMapCanvas[0].getBoundingClientRect();
|
||||
const x = event.clientX - rect.left;
|
||||
const y = event.clientY - rect.top;
|
||||
if (canvasDragOrigin !== null) {
|
||||
const scale = SCALE_VALUES[canvasScaleIndex];
|
||||
const dx = x - canvasDragOrigin.x;
|
||||
const dy = y - canvasDragOrigin.y;
|
||||
canvasDragTranslation = {x: dx / scale, y: dy / scale};
|
||||
drawRailSystem();
|
||||
} else {
|
||||
hoveredElements = [];
|
||||
const p = mousePointToWorld(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) {
|
||||
hoveredElements.push(signal);
|
||||
}
|
||||
});
|
||||
drawRailSystem();
|
||||
}
|
||||
}
|
||||
|
||||
function railSystemChanged() {
|
||||
railSystem = {};
|
||||
railSystem.id = railSystemSelect.val();
|
||||
$.get("/api/railSystems/" + railSystem.id + "/signals")
|
||||
.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;
|
||||
drawRailSystem();
|
||||
window.setTimeout(railSystemChanged, 1000);
|
||||
});
|
||||
$.get("/api/railSystems/" + railSystem.id + "/branches")
|
||||
.done(branches => {
|
||||
railSystem.branches = branches;
|
||||
});
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>RailSignal</title>
|
||||
|
||||
<link href="/static/style/main.css" rel="stylesheet"/>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
|
||||
<div class="container">
|
||||
<h1>RailSignal</h1>
|
||||
<p class="lead">Stay in control of your rails.</p>
|
||||
|
||||
<div class="row align-items-center">
|
||||
<div class="col-auto">
|
||||
<label for="railSystemSelect">Select a rail system:</label>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<select id="railSystemSelect" class="form-select">
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" placeholder="Or add a new system" id="addRailSystemInput"/>
|
||||
<button class="btn btn-success" type="button" id="addRailSystemButton" disabled>Add</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<button class="btn btn-danger" type="button" id="removeRailSystemButton" disabled>Remove Selected System</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="my-4"/>
|
||||
|
||||
<div class="row">
|
||||
<div id="railMapCanvasParent" class="col border p-0">
|
||||
<canvas id="railMapCanvas" width="1000" height="500"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<textarea id="testingText"></textarea>
|
||||
</div>
|
||||
</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="/static/js/drawing.js"></script>
|
||||
<script src="/static/js/main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue