+
+
@@ -33,6 +37,7 @@ import { useRailSystemsStore } from "stores/railSystemsStore";
import MapView from "components/rs/MapView.vue";
import SegmentsView from "components/rs/SegmentsView.vue";
import SettingsView from "components/rs/SettingsView.vue";
+import { loadData, unloadData } from "src/api/railSystems";
export default {
name: "RailSystemPage",
@@ -41,23 +46,45 @@ export default {
return {
panel: "map",
railSystem: null,
-
- linkTokens: []
+ loading: false
}
},
- beforeRouteEnter(to, from, next) {
- const id = parseInt(to.params.id);
+ setup() {
const rsStore = useRailSystemsStore();
- rsStore.selectRailSystem(id).then(() => {
- next(vm => vm.railSystem = rsStore.selectedRailSystem);
- });
+ return {rsStore};
},
- beforeRouteUpdate(to, from) {
- const id = parseInt(to.params.id);
- const rsStore = useRailSystemsStore();
- rsStore.selectRailSystem(id).then(() => {
- this.railSystem = rsStore.selectedRailSystem;
- });
+ mounted() {
+ this.updateRailSystem();
+ },
+ created() {
+ this.$watch(
+ () => this.$route.params,
+ this.updateRailSystem,
+ {
+ immediate: true
+ }
+ )
+ },
+ methods: {
+ async updateRailSystem() {
+ if (this.loading) return;
+ this.loading = true;
+ console.log(">>>> updating rail system.")
+ if (this.railSystem) {
+ this.rsStore.selectedRailSystem = null;
+ await unloadData(this.railSystem);
+ }
+ if (this.$route.params.id) {
+ const newRsId = parseInt(this.$route.params.id);
+ const rs = this.rsStore.railSystems.find(r => r.id === newRsId);
+ if (rs) {
+ this.railSystem = rs;
+ this.rsStore.selectedRailSystem = rs;
+ await loadData(rs);
+ }
+ }
+ this.loading = false;
+ }
}
};
diff --git a/quasar-app/src/render/canvasUtils.js b/quasar-app/src/render/canvasUtils.js
index 726b394..fbad3a5 100644
--- a/quasar-app/src/render/canvasUtils.js
+++ b/quasar-app/src/render/canvasUtils.js
@@ -13,4 +13,13 @@ export function roundedRect(ctx, x, y, w, h, r) {
export function circle(ctx, x, y, r) {
ctx.beginPath();
ctx.arc(x, y, r, 0, Math.PI * 2);
-}
\ No newline at end of file
+}
+
+export function mulberry32(a) {
+ return function() {
+ let t = a += 0x6D2B79F5;
+ t = Math.imul(t ^ t >>> 15, t | 1);
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
+ }
+}
diff --git a/quasar-app/src/render/drawing.js b/quasar-app/src/render/drawing.js
index 81d8f59..c722f20 100644
--- a/quasar-app/src/render/drawing.js
+++ b/quasar-app/src/render/drawing.js
@@ -2,10 +2,89 @@
Helper functions to actually perform rendering of different components.
*/
-import { getScaleFactor, isComponentHovered, isComponentSelected } from "./mapRenderer";
+import { getScaleFactor, getWorldTransform, isComponentHovered, isComponentSelected } from "./mapRenderer";
import { circle, roundedRect } from "./canvasUtils";
+import randomColor from "randomcolor";
-export function drawComponent(ctx, worldTx, component) {
+export function drawMap(ctx, rs) {
+ const worldTx = getWorldTransform();
+ ctx.setTransform(worldTx);
+ drawSegments(ctx, rs);
+ drawNodeConnections(ctx, rs, worldTx);
+ drawComponents(ctx, rs, worldTx);
+}
+
+function drawSegments(ctx, rs) {
+ const segmentPoints = new Map();
+ rs.segments.forEach(segment => segmentPoints.set(segment.id, []));
+ for (let i = 0; i < rs.components.length; i++) {
+ const c = rs.components[i];
+ if (c.type === "SEGMENT_BOUNDARY") {
+ for (let j = 0; j < c.segments.length; j++) {
+ segmentPoints.get(c.segments[j].id).push({x: c.position.x, y: c.position.z});
+ }
+ }
+ }
+
+ for (let i = 0; i < rs.segments.length; i++) {
+ const color = randomColor({ luminosity: 'light', format: 'rgb', seed: rs.segments[i].id });
+ ctx.fillStyle = color;
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 5;
+ ctx.lineCap = "round";
+ ctx.lineJoin = "round";
+ ctx.font = "3px Sans-Serif";
+
+ const points = segmentPoints.get(rs.segments[i].id);
+ if (points.length === 0) continue;
+ const avgPoint = {x: points[0].x, y: points[0].y};
+ if (points.length === 1) {
+ circle(ctx, points[0].x, points[0].y, 5);
+ ctx.fill();
+ } else {
+ ctx.beginPath();
+ ctx.moveTo(points[0].x, points[0].y);
+ for (let j = 1; j < points.length; j++) {
+ ctx.lineTo(points[j].x, points[j].y);
+ avgPoint.x += points[j].x;
+ avgPoint.y += points[j].y;
+ }
+ avgPoint.x /= points.length;
+ avgPoint.y /= points.length;
+ ctx.lineTo(points[0].x, points[0].y);
+ ctx.fill();
+ ctx.stroke();
+ }
+
+ // Draw the segment name.
+ ctx.fillStyle = randomColor({luminosity: 'dark', format: 'rgb', seed: rs.segments[i].id});
+ ctx.fillText(rs.segments[i].name, avgPoint.x, avgPoint.y);
+ }
+}
+
+function drawNodeConnections(ctx, rs, worldTx) {
+ for (let i = 0; i < rs.components.length; i++) {
+ const c = rs.components[i];
+ if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
+ drawConnectedNodes(ctx, worldTx, c);
+ }
+ }
+}
+
+function drawComponents(ctx, rs, worldTx) {
+ // Draw switch configurations first
+ for (let i = 0; i < rs.components.length; i++) {
+ if (rs.components[i].type === "SWITCH") {
+ drawSwitchConfigurations(ctx, worldTx, rs.components[i]);
+ }
+ }
+
+ for (let i = 0; i < rs.components.length; i++) {
+ drawComponent(ctx, worldTx, rs.components[i]);
+ }
+}
+
+function drawComponent(ctx, worldTx, component) {
const tx = DOMMatrix.fromMatrix(worldTx);
tx.translateSelf(component.position.x, component.position.z, 0);
const s = getScaleFactor();
@@ -67,48 +146,60 @@ function drawSegmentBoundary(ctx) {
ctx.fill();
}
-function drawSwitch(ctx, sw) {
- const colors = [
- `rgba(61, 148, 66, 0.25)`,
- `rgba(59, 22, 135, 0.25)`,
- `rgba(145, 17, 90, 0.25)`,
- `rgba(191, 49, 10, 0.25)`
- ];
- ctx.lineWidth = 1;
- for (let i = 0; i < sw.possibleConfigurations.length; i++) {
- const config = sw.possibleConfigurations[i];
- ctx.strokeStyle = colors[i];
- for (let j = 0; j < config.nodes.length; j++) {
- const node = config.nodes[j];
- const diff = {
- x: sw.position.x - node.position.x,
- y: sw.position.z - node.position.z,
- };
- const mag = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2));
- diff.x = 2 * -diff.x / mag;
- diff.y = 2 * -diff.y / mag;
- ctx.beginPath();
- ctx.moveTo(0, 0);
- ctx.lineTo(diff.x, diff.y);
- ctx.stroke();
- }
+function drawSwitchConfigurations(ctx, worldTx, sw) {
+ const tx = DOMMatrix.fromMatrix(worldTx);
+ tx.translateSelf(sw.position.x, sw.position.z, 0);
+ const s = getScaleFactor();
+ tx.scaleSelf(1/s, 1/s, 1/s);
+ tx.scaleSelf(20, 20, 20);
+ ctx.setTransform(tx);
+
+ for (let i = 0; i < sw.possibleConfigurations.length; i++) {
+ const config = sw.possibleConfigurations[i];
+ ctx.strokeStyle = randomColor({
+ seed: config.id,
+ format: 'rgb',
+ luminosity: 'bright'
+ });
+ if (sw.activeConfiguration !== null && sw.activeConfiguration.id === config.id) {
+ ctx.lineWidth = 0.6;
+ } else {
+ ctx.lineWidth = 0.3;
}
- ctx.fillStyle = `rgb(245, 188, 66)`;
- ctx.strokeStyle = `rgb(245, 188, 66)`;
- ctx.lineWidth = 0.2;
- circle(ctx, 0, 0.3, 0.2);
- ctx.fill();
- circle(ctx, -0.3, -0.3, 0.2);
- ctx.fill();
- circle(ctx, 0.3, -0.3, 0.2);
- ctx.fill();
- ctx.beginPath();
- ctx.moveTo(0, 0.3);
- ctx.lineTo(0, 0);
- ctx.lineTo(0.3, -0.3);
- ctx.moveTo(0, 0);
- ctx.lineTo(-0.3, -0.3);
- ctx.stroke();
+ for (let j = 0; j < config.nodes.length; j++) {
+ const node = config.nodes[j];
+ const diff = {
+ x: sw.position.x - node.position.x,
+ y: sw.position.z - node.position.z,
+ };
+ const mag = Math.sqrt(Math.pow(diff.x, 2) + Math.pow(diff.y, 2));
+ diff.x = 3 * -diff.x / mag;
+ diff.y = 3 * -diff.y / mag;
+ ctx.beginPath();
+ ctx.moveTo(0, 0);
+ ctx.lineTo(diff.x, diff.y);
+ ctx.stroke();
+ }
+ }
+}
+
+function drawSwitch(ctx, sw) {
+ ctx.fillStyle = `rgb(245, 188, 66)`;
+ ctx.strokeStyle = `rgb(245, 188, 66)`;
+ ctx.lineWidth = 0.2;
+ circle(ctx, 0, 0.3, 0.2);
+ ctx.fill();
+ circle(ctx, -0.3, -0.3, 0.2);
+ ctx.fill();
+ circle(ctx, 0.3, -0.3, 0.2);
+ ctx.fill();
+ ctx.beginPath();
+ ctx.moveTo(0, 0.3);
+ ctx.lineTo(0, 0);
+ ctx.lineTo(0.3, -0.3);
+ ctx.moveTo(0, 0);
+ ctx.lineTo(-0.3, -0.3);
+ ctx.stroke();
}
function drawLabel(ctx, lbl) {
diff --git a/quasar-app/src/render/mapRenderer.js b/quasar-app/src/render/mapRenderer.js
index 57e666e..450334b 100644
--- a/quasar-app/src/render/mapRenderer.js
+++ b/quasar-app/src/render/mapRenderer.js
@@ -3,7 +3,7 @@ This component is responsible for the rendering of a RailSystem in a 2d map
view.
*/
-import {drawComponent, drawConnectedNodes} from "./drawing";
+import { drawMap } from "./drawing";
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;
@@ -56,73 +56,26 @@ export function draw() {
ctx.resetTransform();
ctx.fillStyle = `rgb(240, 240, 240)`;
ctx.fillRect(0, 0, width, height);
- const worldTx = getWorldTransform();
- ctx.setTransform(worldTx);
- // Draw segments!
- const segmentPoints = new Map();
- railSystem.segments.forEach(segment => segmentPoints.set(segment.id, []));
- for (let i = 0; i < railSystem.components.length; i++) {
- const c = railSystem.components[i];
- if (c.type === "SEGMENT_BOUNDARY") {
- for (let j = 0; j < c.segments.length; j++) {
- segmentPoints.get(c.segments[j].id).push({x: c.position.x, y: c.position.z});
- }
- }
- }
- railSystem.segments.forEach(segment => {
- const points = segmentPoints.get(segment.id);
- const avgPoint = {x: 0, y: 0};
- points.forEach(point => {
- avgPoint.x += point.x;
- avgPoint.y += point.y;
- });
- avgPoint.x /= points.length;
- avgPoint.y /= points.length;
- let r = 5;
- points.forEach(point => {
- const dist2 = Math.pow(avgPoint.x - point.x, 2) + Math.pow(avgPoint.y - point.y, 2);
- if (dist2 > r * r) {
- r = Math.sqrt(dist2);
- }
- });
- ctx.fillStyle = `rgba(200, 200, 200, 0.25)`;
- const p = worldPointToMap(new DOMPoint(avgPoint.x, avgPoint.y, 0, 0));
- const s = getScaleFactor();
- ctx.beginPath();
- ctx.arc(p.x / s, p.y / s, r, 0, Math.PI * 2);
- ctx.fill();
- ctx.fillStyle = "rgba(0, 0, 0, 0.5)";
- ctx.font = "3px Sans-Serif";
- ctx.fillText(`${segment.name}`, p.x / s, p.y / s);
- });
+ drawMap(ctx, railSystem);
+ drawDebugInfo(ctx);
+}
- for (let i = 0; i < railSystem.components.length; i++) {
- const c = railSystem.components[i];
- if (c.connectedNodes !== undefined && c.connectedNodes !== null) {
- drawConnectedNodes(ctx, worldTx, c);
- }
- }
-
- for (let i = 0; i < railSystem.components.length; i++) {
- drawComponent(ctx, worldTx, railSystem.components[i]);
- }
-
- // Draw debug info.
- ctx.resetTransform();
- ctx.fillStyle = "black";
- ctx.strokeStyle = "black";
- ctx.font = "10px Sans-Serif";
- const lastWorldPoint = mapPointToWorld(lastMousePoint);
- const lines = [
- "Scale factor: " + getScaleFactor(),
- `(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
- `Components: ${railSystem.components.length}`,
- `Hovered elements: ${hoveredElements.length}`
- ]
- for (let i = 0; i < lines.length; i++) {
- ctx.fillText(lines[i], 10, 20 + (i * 15));
- }
+function drawDebugInfo(ctx) {
+ ctx.resetTransform();
+ ctx.fillStyle = "black";
+ ctx.strokeStyle = "black";
+ ctx.font = "10px Sans-Serif";
+ const lastWorldPoint = mapPointToWorld(lastMousePoint);
+ const lines = [
+ "Scale factor: " + getScaleFactor(),
+ `(x = ${lastWorldPoint.x.toFixed(2)}, y = ${lastWorldPoint.y.toFixed(2)}, z = ${lastWorldPoint.z.toFixed(2)})`,
+ `Components: ${railSystem.components.length}`,
+ `Hovered elements: ${hoveredElements.length}`
+ ]
+ for (let i = 0; i < lines.length; i++) {
+ ctx.fillText(lines[i], 10, 20 + (i * 15));
+ }
}
export function getScaleFactor() {
@@ -133,7 +86,7 @@ export function getScaleFactor() {
* Gets a matrix that transforms world coordinates to canvas.
* @returns {DOMMatrix}
*/
-function getWorldTransform() {
+export function getWorldTransform() {
const canvasRect = mapCanvas.getBoundingClientRect();
const scale = getScaleFactor();
const tx = new DOMMatrix();
@@ -159,7 +112,7 @@ export function isComponentSelected(component) {
* @param {DOMPoint} p
* @returns {DOMPoint}
*/
-function mapPointToWorld(p) {
+export function mapPointToWorld(p) {
return getWorldTransform().invertSelf().transformPoint(p);
}
@@ -168,7 +121,7 @@ function mapPointToWorld(p) {
* @param {DOMPoint} p
* @returns {DOMPoint}
*/
-function worldPointToMap(p) {
+export function worldPointToMap(p) {
return getWorldTransform().transformPoint(p);
}
diff --git a/quasar-app/src/stores/railSystemsStore.js b/quasar-app/src/stores/railSystemsStore.js
index d86195b..31488df 100644
--- a/quasar-app/src/stores/railSystemsStore.js
+++ b/quasar-app/src/stores/railSystemsStore.js
@@ -20,20 +20,22 @@ export const useRailSystemsStore = defineStore('RailSystemsStore', {
actions: {
/**
* Updates the selected rail system.
- * @param rsId {Number} The new rail system id.
+ * @param rsId {Number | null} The new rail system id.
* @returns {Promise} A promise that resolves when the new rail system is
* fully loaded and ready.
*/
selectRailSystem(rsId) {
// Close any existing websocket connections prior to refreshing.
const wsClosePromises = [];
- if (this.selectedRailSystem) {
+ if (this.selectedRailSystem !== null) {
wsClosePromises.push(closeWebsocketConnection(this.selectedRailSystem));
}
+ if (rsId === null) return Promise.all(wsClosePromises);
return new Promise(resolve => {
Promise.all(wsClosePromises).then(() => {
refreshRailSystems(this).then(() => {
const rs = this.railSystems.find(r => r.id === rsId);
+ console.log(rs);
const updatePromises = [];
updatePromises.push(refreshSegments(rs));
updatePromises.push(refreshComponents(rs));
diff --git a/src/main/java/nl/andrewl/railsignalapi/page/IndexPageController.java b/src/main/java/nl/andrewl/railsignalapi/page/IndexPageController.java
index 02dfce2..e8db1f0 100644
--- a/src/main/java/nl/andrewl/railsignalapi/page/IndexPageController.java
+++ b/src/main/java/nl/andrewl/railsignalapi/page/IndexPageController.java
@@ -9,7 +9,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
* web app's index page.
*/
@Controller
-@RequestMapping(path = {"/", "/app", "/app/about", "/home", "/index.html", "/index"})
+@RequestMapping(path = {"/", "/app", "/app/about", "/app/rail-systems/*", "/home", "/index.html", "/index"})
public class IndexPageController {
@GetMapping
public String getIndex() {
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
index 3d2bfee..503d4c2 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/PathNodeResponse.java
@@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.PathNode;
+import java.util.Comparator;
import java.util.List;
public abstract class PathNodeResponse extends ComponentResponse {
@@ -9,6 +10,9 @@ public abstract class PathNodeResponse extends ComponentResponse {
public PathNodeResponse(PathNode p) {
super(p);
- this.connectedNodes = p.getConnectedNodes().stream().map(SimpleComponentResponse::new).toList();
+ this.connectedNodes = p.getConnectedNodes().stream()
+ .sorted(Comparator.comparing(PathNode::getName))
+ .map(SimpleComponentResponse::new)
+ .toList();
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
index 1af60e4..2d84737 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SegmentBoundaryNodeResponse.java
@@ -3,6 +3,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.SegmentBoundaryNode;
import nl.andrewl.railsignalapi.rest.dto.SegmentResponse;
+import java.util.Comparator;
import java.util.List;
public class SegmentBoundaryNodeResponse extends PathNodeResponse {
@@ -10,6 +11,9 @@ public class SegmentBoundaryNodeResponse extends PathNodeResponse {
public SegmentBoundaryNodeResponse(SegmentBoundaryNode n) {
super(n);
- this.segments = n.getSegments().stream().map(SegmentResponse::new).toList();
+ this.segments = n.getSegments().stream()
+ .map(SegmentResponse::new)
+ .sorted(Comparator.comparing(sr -> sr.name))
+ .toList();
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
index eab8df2..1e2e58d 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchConfigurationResponse.java
@@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.SwitchConfiguration;
+import java.util.Comparator;
import java.util.List;
public record SwitchConfigurationResponse (
@@ -11,7 +12,10 @@ public record SwitchConfigurationResponse (
public SwitchConfigurationResponse(SwitchConfiguration sc) {
this(
sc.getId(),
- sc.getNodes().stream().map(SimpleComponentResponse::new).toList()
+ sc.getNodes().stream()
+ .map(SimpleComponentResponse::new)
+ .sorted(Comparator.comparing(SimpleComponentResponse::name))
+ .toList()
);
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
index 33ad8a9..81fce8f 100644
--- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
+++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/out/SwitchResponse.java
@@ -2,6 +2,7 @@ package nl.andrewl.railsignalapi.rest.dto.component.out;
import nl.andrewl.railsignalapi.model.component.Switch;
+import java.util.Comparator;
import java.util.List;
public class SwitchResponse extends PathNodeResponse {
@@ -10,7 +11,10 @@ public class SwitchResponse extends PathNodeResponse {
public SwitchResponse(Switch s) {
super(s);
- this.possibleConfigurations = s.getPossibleConfigurations().stream().map(SwitchConfigurationResponse::new).toList();
+ this.possibleConfigurations = s.getPossibleConfigurations().stream()
+ .map(SwitchConfigurationResponse::new)
+ .sorted(Comparator.comparing(SwitchConfigurationResponse::id))
+ .toList();
this.activeConfiguration = s.getActiveConfiguration() == null ? null : new SwitchConfigurationResponse(s.getActiveConfiguration());
}
}
diff --git a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
index 8c56af0..5218095 100644
--- a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
+++ b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java
@@ -1,6 +1,7 @@
package nl.andrewl.railsignalapi.service;
import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import nl.andrewl.railsignalapi.dao.ComponentRepository;
import nl.andrewl.railsignalapi.dao.RailSystemRepository;
import nl.andrewl.railsignalapi.dao.SegmentRepository;
@@ -24,6 +25,7 @@ import java.util.List;
@Service
@RequiredArgsConstructor
+@Slf4j
public class SegmentService {
private final SegmentRepository segmentRepository;
private final RailSystemRepository railSystemRepository;