diff --git a/pom.xml b/pom.xml index 0a08909..231d602 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ nl.andrewl rail-signal-api - 2.1.0 + 2.2.0 rail-signal-api A simple API for tracking rail traffic in signalled blocks. diff --git a/quasar-app/src/components/rs/component_views/BaseComponentView.vue b/quasar-app/src/components/rs/component_views/BaseComponentView.vue index cf878d7..a0c44ff 100644 --- a/quasar-app/src/components/rs/component_views/BaseComponentView.vue +++ b/quasar-app/src/components/rs/component_views/BaseComponentView.vue @@ -70,7 +70,7 @@ export default { message: "Are you sure you want to remove this component? This cannot be undone.", cancel: true }).onOk(() => { - removeComponent(this.railSystem, component.id) + removeComponent(this.rsStore.selectedRailSystem, component.id) .then(() => { this.quasar.notify({ color: "positive", diff --git a/quasar-app/src/components/rs/component_views/SwitchComponentView.vue b/quasar-app/src/components/rs/component_views/SwitchComponentView.vue index 25013f2..8667c42 100644 --- a/quasar-app/src/components/rs/component_views/SwitchComponentView.vue +++ b/quasar-app/src/components/rs/component_views/SwitchComponentView.vue @@ -127,7 +127,19 @@ export default { }, methods: { setActiveSwitchConfig(configId) { - updateSwitchConfiguration(this.railSystem, this.sw, configId); + updateSwitchConfiguration(this.rsStore.selectedRailSystem, this.sw, configId) + .then(() => { + this.quasar.notify({ + color: "positive", + message: "Sent switch configuration update request." + }); + }) + .catch(error => { + this.quasar.notify({ + color: "negative", + message: "An error occurred: " + error.response.data.message + }); + }); }, isConfigActive(config) { return this.sw.activeConfiguration !== null && this.sw.activeConfiguration.id === config.id; diff --git a/quasar-app/src/render/canvasUtils.js b/quasar-app/src/render/canvasUtils.js index fbad3a5..f0c9d0e 100644 --- a/quasar-app/src/render/canvasUtils.js +++ b/quasar-app/src/render/canvasUtils.js @@ -23,3 +23,28 @@ export function mulberry32(a) { return ((t ^ t >>> 14) >>> 0) / 4294967296; } } + +export function sortPoints(points) { + points = points.splice(0); + const p0 = {}; + p0.y = Math.min.apply(null, points.map(p=>p.y)); + p0.x = Math.max.apply(null, points.filter(p=>p.y === p0.y).map(p=>p.x)); + points.sort((a,b)=>angleCompare(p0, a, b)); + return points; +} + +function angleCompare(p0, a, b) { + const left = isLeft(p0, a, b); + if (left === 0) return distCompare(p0, a, b); + return left; +} + +function isLeft(p0, a, b) { + return (a.x-p0.x)*(b.y-p0.y) - (b.x-p0.x)*(a.y-p0.y); +} + +function distCompare(p0, a, b) { + const distA = (p0.x-a.x)*(p0.x-a.x) + (p0.y-a.y)*(p0.y-a.y); + const distB = (p0.x-b.x)*(p0.x-b.x) + (p0.y-b.y)*(p0.y-b.y); + return distA - distB; +} diff --git a/quasar-app/src/render/drawing.js b/quasar-app/src/render/drawing.js index c722f20..db6119c 100644 --- a/quasar-app/src/render/drawing.js +++ b/quasar-app/src/render/drawing.js @@ -3,7 +3,7 @@ Helper functions to actually perform rendering of different components. */ import { getScaleFactor, getWorldTransform, isComponentHovered, isComponentSelected } from "./mapRenderer"; -import { circle, roundedRect } from "./canvasUtils"; +import { circle, roundedRect, sortPoints } from "./canvasUtils"; import randomColor from "randomcolor"; export function drawMap(ctx, rs) { @@ -16,6 +16,7 @@ export function drawMap(ctx, rs) { function drawSegments(ctx, rs) { const segmentPoints = new Map(); + // Gather for each segment a set of points representing its bounds. rs.segments.forEach(segment => segmentPoints.set(segment.id, [])); for (let i = 0; i < rs.components.length; i++) { const c = rs.components[i]; @@ -25,6 +26,11 @@ function drawSegments(ctx, rs) { } } } + // Sort the points to make regular convex polygons. + for (let i = 0; i < rs.segments.length; i++) { + const unsortedPoints = segmentPoints.get(rs.segments[i].id); + segmentPoints.set(rs.segments[i].id, sortPoints(unsortedPoints)); + } for (let i = 0; i < rs.segments.length; i++) { const color = randomColor({ luminosity: 'light', format: 'rgb', seed: rs.segments[i].id }); diff --git a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SwitchPayload.java b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SwitchPayload.java index 5e4fc43..38d9f5c 100644 --- a/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SwitchPayload.java +++ b/src/main/java/nl/andrewl/railsignalapi/rest/dto/component/in/SwitchPayload.java @@ -1,14 +1,15 @@ package nl.andrewl.railsignalapi.rest.dto.component.in; import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; public class SwitchPayload extends ComponentPayload { - @NotEmpty @Size(min = 2, max = 10) + @NotNull @Size(max = 10) public SwitchConfigurationPayload[] possibleConfigurations; public static class SwitchConfigurationPayload { - @NotEmpty @Size(min = 2, max = 10) + @NotEmpty @Size(min = 2, max = 2) public NodePayload[] nodes; public static class NodePayload { diff --git a/src/main/java/nl/andrewl/railsignalapi/service/ComponentCreationService.java b/src/main/java/nl/andrewl/railsignalapi/service/ComponentCreationService.java index 31a518e..baa6a47 100644 --- a/src/main/java/nl/andrewl/railsignalapi/service/ComponentCreationService.java +++ b/src/main/java/nl/andrewl/railsignalapi/service/ComponentCreationService.java @@ -87,9 +87,6 @@ public class ComponentCreationService { } s.getPossibleConfigurations().add(new SwitchConfiguration(s, pathNodes)); } - if (s.getPossibleConfigurations().size() < 2) { - throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "At least two switch configurations are needed."); - } return s; } diff --git a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java index 5218095..47be561 100644 --- a/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java +++ b/src/main/java/nl/andrewl/railsignalapi/service/SegmentService.java @@ -6,6 +6,7 @@ import nl.andrewl.railsignalapi.dao.ComponentRepository; import nl.andrewl.railsignalapi.dao.RailSystemRepository; import nl.andrewl.railsignalapi.dao.SegmentRepository; import nl.andrewl.railsignalapi.live.ComponentDownlinkService; +import nl.andrewl.railsignalapi.live.dto.ErrorMessage; import nl.andrewl.railsignalapi.live.dto.SegmentBoundaryUpdateMessage; import nl.andrewl.railsignalapi.live.dto.SegmentStatusMessage; import nl.andrewl.railsignalapi.live.websocket.AppUpdateService; @@ -83,8 +84,21 @@ public class SegmentService { public void onBoundaryUpdate(SegmentBoundaryUpdateMessage msg) { var segmentBoundary = segmentBoundaryRepository.findById(msg.cId) .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); - switch (msg.eventType) { + segmentRepository.findByIdAndRailSystemId(msg.toSegmentId, segmentBoundary.getRailSystem().getId()) + .ifPresentOrElse( + segment -> handleSegmentBoundaryMessage(msg.eventType, segmentBoundary, segment), + () -> downlinkService.sendMessage(new ErrorMessage(msg.cId, "Invalid toSegmentId.")) + ); + } + + private void handleSegmentBoundaryMessage( + SegmentBoundaryUpdateMessage.Type type, + SegmentBoundaryNode segmentBoundary, + Segment toSegment + ) { + switch (type) { case ENTERING -> { + log.info("Train entering segment {} in rail system {}.", toSegment.getName(), segmentBoundary.getRailSystem().getName()); for (var segment : segmentBoundary.getSegments()) { if (!segment.isOccupied()) { segment.setOccupied(true); @@ -94,20 +108,17 @@ public class SegmentService { } } case ENTERED -> { + log.info("Train has entered segment {} in rail system {}.", toSegment.getName(), segmentBoundary.getRailSystem().getName()); List otherSegments = new ArrayList<>(segmentBoundary.getSegments()); // Set the "to" segment as occupied. - segmentRepository.findById(msg.toSegmentId).ifPresent(segment -> { - segment.setOccupied(true); - segmentRepository.save(segment); - sendSegmentOccupiedStatus(segment); - otherSegments.remove(segment); - }); + toSegment.setOccupied(true); + segmentRepository.save(toSegment); + otherSegments.remove(toSegment); // And all others as no longer occupied. for (var segment : otherSegments) { - if (segment.isOccupied()) { - segment.setOccupied(false); - segmentRepository.save(segment); - } + log.info("Train has left segment {} in rail system {}.", segment.getName(), segmentBoundary.getRailSystem().getName()); + segment.setOccupied(false); + segmentRepository.save(segment); sendSegmentOccupiedStatus(segment); } }