diff --git a/src/app/models/trainrunsection.model.ts b/src/app/models/trainrunsection.model.ts index 0460aee7..12d67a48 100644 --- a/src/app/models/trainrunsection.model.ts +++ b/src/app/models/trainrunsection.model.ts @@ -201,7 +201,7 @@ export class TrainrunSection { return formattedText; } - shiftAllTimes(translateMinutes: number, mirrorSourceDeparture : boolean) { + shiftAllTimes(translateMinutes: number, mirrorSourceDeparture: boolean) { this.sourceDeparture.time += translateMinutes; this.targetArrival.time += translateMinutes; this.sourceArrival.time += translateMinutes; @@ -503,6 +503,26 @@ export class TrainrunSection { }; } + getTargetArrivalWarning() { + return this.targetArrival.warning; + } + + getSourceArrivalWarning() { + return this.sourceArrival.warning; + } + + getTargetDepartureWarning() { + return this.targetDeparture.warning; + } + + getSourceDepartureWarning() { + return this.sourceDeparture.warning; + } + + getTravelTimeWarning() { + return this.travelTime.warning; + } + resetTargetArrivalWarning() { this.targetArrival.warning = null; } @@ -639,7 +659,7 @@ export class TrainrunSection { resourceId: this.resourceId, specificTrainrunSectionFrequencyId: - this.specificTrainrunSectionFrequencyId, + this.specificTrainrunSectionFrequencyId, path: this.path, warnings: this.warnings, }; diff --git a/src/app/services/util/trainrunsection.validator.ts b/src/app/services/util/trainrunsection.validator.ts index bc533598..3fd32912 100644 --- a/src/app/services/util/trainrunsection.validator.ts +++ b/src/app/services/util/trainrunsection.validator.ts @@ -6,11 +6,14 @@ export class TrainrunsectionValidator { trainrunSection.resetSourceDepartureWarning(); trainrunSection.resetTargetDepartureWarning(); + TrainrunsectionValidator.validateTravelTimeOneSection(trainrunSection); + TrainrunsectionValidator.validateUnsymmetricTimesOneSection(trainrunSection); + } + + static validateTravelTimeOneSection(trainrunSection: TrainrunSection) { const calculatedTargetArrivalTime = - (trainrunSection.getSourceDeparture() + trainrunSection.getTravelTime()) % - 60; - if (Math.abs(calculatedTargetArrivalTime - trainrunSection.getTargetArrival()) - > 1 / 60) { + (trainrunSection.getSourceDeparture() + trainrunSection.getTravelTime()) % 60; + if (Math.abs(calculatedTargetArrivalTime - trainrunSection.getTargetArrival()) > 1 / 60) { trainrunSection.setTargetArrivalWarning( $localize`:@@app.services.util.trainrunsection-validator.target-arrival-not-reacheable.title:Target Arrival Warning`, $localize`:@@app.services.util.trainrunsection-validator.target-arrival-not-reacheable.description:Target arrival time cannot be reached`, @@ -20,10 +23,8 @@ export class TrainrunsectionValidator { } const calculatedSourceArrivalTime = - (trainrunSection.getTargetDeparture() + trainrunSection.getTravelTime()) % - 60; - if (Math.abs(calculatedSourceArrivalTime - trainrunSection.getSourceArrival()) - > 1 / 60) { + (trainrunSection.getTargetDeparture() + trainrunSection.getTravelTime()) % 60; + if (Math.abs(calculatedSourceArrivalTime - trainrunSection.getSourceArrival()) > 1 / 60) { trainrunSection.setSourceArrivalWarning( $localize`:@@app.services.util.trainrunsection-validator.source-arrival-not-reacheable.title:Source Arrival Warning`, $localize`:@@app.services.util.trainrunsection-validator.source-arrival-not-reacheable.description:Source arrival time cannot be reached`, @@ -33,6 +34,28 @@ export class TrainrunsectionValidator { } } + static validateUnsymmetricTimesOneSection(trainrunSection: TrainrunSection) { + // check for broken symmetry (times) + trainrunSection.resetSourceDepartureWarning(); + trainrunSection.resetTargetDepartureWarning(); + const sourceSum = MathUtils.round(trainrunSection.getSourceArrival() + trainrunSection.getSourceDeparture(), 4); + const sourceSymmetricCheck = Math.abs(sourceSum % 60) < 1 / 60; + if (!sourceSymmetricCheck) { + trainrunSection.setSourceArrivalWarning($localize`:@@app.services.util.trainrunsection-validator.broken-symmetry:Broken symmetry`, + "" + (trainrunSection.getSourceArrival() + " + " + trainrunSection.getSourceDeparture()) + " = " + sourceSum); + trainrunSection.setSourceDepartureWarning($localize`:@@app.services.util.trainrunsection-validator.broken-symmetry:Broken symmetry`, + "" + (trainrunSection.getSourceArrival() + " + " + trainrunSection.getSourceDeparture()) + " = " + sourceSum); + } + const targetSum = MathUtils.round(trainrunSection.getTargetArrival() + trainrunSection.getTargetDeparture(), 4); + const targetSymmetricCheck = Math.abs(targetSum % 60) < 1 / 60; + if (!targetSymmetricCheck) { + trainrunSection.setTargetArrivalWarning($localize`:@@app.services.util.trainrunsection-validator.broken-symmetry:Broken symmetry`, + "" + (trainrunSection.getTargetArrival() + " + " + trainrunSection.getTargetDeparture()) + " = " + targetSum); + trainrunSection.setTargetDepartureWarning($localize`:@@app.services.util.trainrunsection-validator.broken-symmetry:Broken symmetry`, + "" + (trainrunSection.getTargetArrival() + " + " + trainrunSection.getTargetDeparture()) + " = " + targetSum); + } + } + static validateTravelTime(trainrunSection: TrainrunSection) { if (trainrunSection.getTravelTime() < 1) { trainrunSection.setTravelTimeWarning( diff --git a/src/app/services/util/transition.validator.spec.ts b/src/app/services/util/transition.validator.spec.ts index 1b26244b..a90efb83 100644 --- a/src/app/services/util/transition.validator.spec.ts +++ b/src/app/services/util/transition.validator.spec.ts @@ -184,16 +184,16 @@ describe("TransitionValidator", () => { nodeHaltezeiten[ trainrunSections.trainrunSection1.getTrainrun().getTrainrunCategory() .fachCategory - ].haltezeit; + ].haltezeit; trainrunSections.trainrunSection2.setSourceDeparture( trainrunSections.trainrunSection1.getSourceArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection2.setTargetDeparture( trainrunSections.trainrunSection1.getTargetArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.resetTargetArrivalWarning(); @@ -276,16 +276,16 @@ describe("TransitionValidator", () => { nodeHaltezeiten[ trainrunSections.trainrunSection2.getTrainrun().getTrainrunCategory() .fachCategory - ].haltezeit; + ].haltezeit; trainrunSections.trainrunSection1.setSourceDeparture( trainrunSections.trainrunSection2.getSourceArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.setTargetDeparture( trainrunSections.trainrunSection2.getTargetArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.resetTargetArrivalWarning(); @@ -386,16 +386,16 @@ describe("TransitionValidator", () => { nodeHaltezeiten[ trainrunSections.trainrunSection2.getTrainrun().getTrainrunCategory() .fachCategory - ].haltezeit; + ].haltezeit; trainrunSections.trainrunSection1.setSourceDeparture( trainrunSections.trainrunSection2.getSourceArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.setTargetDeparture( trainrunSections.trainrunSection2.getTargetArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.resetTargetArrivalWarning(); @@ -496,16 +496,16 @@ describe("TransitionValidator", () => { nodeHaltezeiten[ trainrunSections.trainrunSection2.getTrainrun().getTrainrunCategory() .fachCategory - ].haltezeit; + ].haltezeit; trainrunSections.trainrunSection1.setSourceDeparture( trainrunSections.trainrunSection2.getSourceArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.setTargetDeparture( trainrunSections.trainrunSection2.getTargetArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.resetTargetArrivalWarning(); @@ -606,16 +606,16 @@ describe("TransitionValidator", () => { nodeHaltezeiten[ trainrunSections.trainrunSection2.getTrainrun().getTrainrunCategory() .fachCategory - ].haltezeit; + ].haltezeit; trainrunSections.trainrunSection1.setSourceDeparture( trainrunSections.trainrunSection2.getSourceArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection2.setTargetDeparture( trainrunSections.trainrunSection1.getTargetArrival() - - trainrunHaltezeit - - 1, + trainrunHaltezeit - + 1, ); trainrunSections.trainrunSection1.resetTargetArrivalWarning(); @@ -682,4 +682,15 @@ describe("TransitionValidator", () => { false, ); }); + + it("Validate Test - 007", () => { + dataService.loadNetzgrafikDto( + NetzgrafikUnitTesting.getUnitTestNetzgrafik(), + ); + const ts1 = trainrunSectionService.getTrainrunSectionFromId(4); + ts1.setSourceDeparture((ts1.getSourceDeparture() + 1) % 60); + const a = ts1.getSourceArrival(); + const b = ts1.getSourceDeparture(); + expect(ts1.getSourceArrivalWarning().description).toBe("" + a + " + " + b + " = " + (a + b)); + }); }); diff --git a/src/app/view/editor-main-view/data-views/trainrunsections.view.ts b/src/app/view/editor-main-view/data-views/trainrunsections.view.ts index 0232ec33..087a05b3 100644 --- a/src/app/view/editor-main-view/data-views/trainrunsections.view.ts +++ b/src/app/view/editor-main-view/data-views/trainrunsections.view.ts @@ -178,6 +178,31 @@ export class TrainrunSectionsView { } } + static getWarning( + trainrunSection: TrainrunSection, + textElement: TrainrunSectionText, + ): string { + if (!TrainrunSectionsView.hasWarning(trainrunSection, textElement)) { + return ""; + } + switch (textElement) { + case TrainrunSectionText.SourceDeparture: + return trainrunSection.getSourceDepartureWarning().title + ": " + trainrunSection.getSourceDepartureWarning().description; + case TrainrunSectionText.SourceArrival: + return trainrunSection.getSourceArrivalWarning().title + ": " + trainrunSection.getSourceArrivalWarning().description; + case TrainrunSectionText.TargetDeparture: + return trainrunSection.getTargetDepartureWarning().title + ": " + trainrunSection.getTargetDepartureWarning().description; + case TrainrunSectionText.TargetArrival: + return trainrunSection.getTargetArrivalWarning().title + ": " + trainrunSection.getTargetArrivalWarning().description; + case TrainrunSectionText.TrainrunSectionTravelTime: + return trainrunSection.getTravelTimeWarning().title + ": " + trainrunSection.getTravelTimeWarning().description; + case TrainrunSectionText.TrainrunSectionName: + default: + return ""; + } + return ""; + } + static getTime( trainrunSection: TrainrunSection, textElement: TrainrunSectionText, @@ -1221,7 +1246,7 @@ export class TrainrunSectionsView { connectedTrainIds: any, atSource: boolean, ) { - if (!this.editorView.trainrunSectionPreviewLineView.getVariantIsWritable()){ + if (!this.editorView.trainrunSectionPreviewLineView.getVariantIsWritable()) { return; } groupEnter @@ -1314,21 +1339,21 @@ export class TrainrunSectionsView { ); } - createTrainrunSectionElement( + private createInternTrainrunSectionElementFilteringWarningElements( groupEnter: d3.Selector, selectedTrainrun: Trainrun, connectedTrainIds: any, textElement: TrainrunSectionText, - enableEvents = true + enableEvents = true, + hasWarning = true ) { - const atSource = textElement === TrainrunSectionText.SourceArrival || textElement === TrainrunSectionText.SourceDeparture; const isArrival = textElement === TrainrunSectionText.SourceArrival || textElement === TrainrunSectionText.TargetArrival; - groupEnter + const renderingObjects = groupEnter .filter( (d: TrainrunSectionViewObject) => this.filterTrainrunsectionAtNode(d.trainrunSection, atSource) && @@ -1336,7 +1361,8 @@ export class TrainrunSectionsView { d.trainrunSection, atSource, isArrival, - ), + ) && + TrainrunSectionsView.hasWarning(d.trainrunSection, textElement) === hasWarning ) .append(StaticDomTags.EDGE_LINE_TEXT_SVG) .attr("class", (d: TrainrunSectionViewObject) => @@ -1417,6 +1443,32 @@ export class TrainrunSectionsView { ); } }); + + if (hasWarning) { + renderingObjects + .append("svg:title") + .text((d: TrainrunSectionViewObject) => { + return TrainrunSectionsView.getWarning(d.trainrunSection, textElement); + }); + } + } + + createTrainrunSectionElement( + groupEnter: d3.Selector, + selectedTrainrun: Trainrun, + connectedTrainIds: any, + textElement: TrainrunSectionText, + enableEvents = true + ) { + // pass(1) : render all elements without warnings + this.createInternTrainrunSectionElementFilteringWarningElements( + groupEnter, selectedTrainrun, connectedTrainIds, textElement, enableEvents, false + ); + // pass(2) : render all elements with warnings + // especially warning_msg + this.createInternTrainrunSectionElementFilteringWarningElements( + groupEnter, selectedTrainrun, connectedTrainIds, textElement, enableEvents, true + ); } createTrainrunSectionGotoInfoElement( diff --git a/src/app/view/editor-tools-view-component/editor-tools-view.component.ts b/src/app/view/editor-tools-view-component/editor-tools-view.component.ts index 647708dc..a4bba36d 100644 --- a/src/app/view/editor-tools-view-component/editor-tools-view.component.ts +++ b/src/app/view/editor-tools-view-component/editor-tools-view.component.ts @@ -29,6 +29,7 @@ import { computeShortestPaths, topoSort } from "../util/origin-destination-graph"; +import {TrainrunsectionValidator} from "../../services/util/trainrunsection.validator"; @Component({ selector: "sbb-editor-tools-view-component", @@ -627,8 +628,14 @@ export class EditorToolsViewComponent { }); }); - // step(4) Recalc/propagte consecutive times + // step(4) Recalc/propagate consecutive times this.trainrunService.propagateInitialConsecutiveTimes(); + + // step(5) Validate all trainrun sections + this.trainrunSectionService.getTrainrunSections().forEach((ts) => { + TrainrunsectionValidator.validateOneSection(ts); + TrainrunsectionValidator.validateTravelTime(ts); + }); } private processNetzgrafikJSON(netzgrafikDto: NetzgrafikDto) { diff --git a/src/assets/i18n/de.json b/src/assets/i18n/de.json index b281c335..2722128e 100644 --- a/src/assets/i18n/de.json +++ b/src/assets/i18n/de.json @@ -86,6 +86,7 @@ } }, "trainrunsection-validator": { + "broken-symmetry": "Symmetrie gebrochen", "target-arrival-not-reacheable": { "title": "Ziel Ankunft Warnung", "description": "Zielankunftszeit kann nicht erreicht werden" diff --git a/src/assets/i18n/en.json b/src/assets/i18n/en.json index 82afa7fd..5b448180 100644 --- a/src/assets/i18n/en.json +++ b/src/assets/i18n/en.json @@ -86,6 +86,7 @@ } }, "trainrunsection-validator": { + "broken-symmetry": "Broken symmetry", "target-arrival-not-reacheable": { "title": "Target Arrival Warning", "description": "Target arrival time cannot be reached" diff --git a/src/assets/i18n/fr.json b/src/assets/i18n/fr.json index b7f92d9b..d729aa90 100644 --- a/src/assets/i18n/fr.json +++ b/src/assets/i18n/fr.json @@ -86,9 +86,10 @@ } }, "trainrunsection-validator": { + "broken-symmetry": "Symétrie brisée", "target-arrival-not-reacheable": { - "title": "Avertissement d'arrivée à la destination", - "description": "L'heure d'arrivée à la destination ne peut être atteinte" + "title": "Avertissement d'arrivée à destination", + "description": "L'heure d'arrivée à destination ne peut être atteinte" }, "source-arrival-not-reacheable": { "title": "Avertissement d'arrivée à l'origine", @@ -109,12 +110,12 @@ "description": "L'heure de départ à l'origine ne peut être atteinte" }, "target-arrival-not-reacheable": { - "title": "Avertissement d'arrivée à la destination", - "description": "L'heure d'arrivée à la destination ne peut être atteinte" + "title": "Avertissement d'arrivée à destination", + "description": "L'heure d'arrivée à destination ne peut être atteinte" }, "target-departure-not-reacheable": { - "title": "Avertissement de départ à la destination", - "description": "L'heure de départ à la destination ne peut être atteinte" + "title": "Avertissement de départ à destination", + "description": "L'heure de départ à destination ne peut être atteinte" } } } diff --git a/src/assets/i18n/it.json b/src/assets/i18n/it.json index b0ca7ba5..1dd450fd 100644 --- a/src/assets/i18n/it.json +++ b/src/assets/i18n/it.json @@ -86,6 +86,7 @@ } }, "trainrunsection-validator": { + "broken-symmetry": "Broken symmetry", "target-arrival-not-reacheable": { "title": "Target Arrival Warning", "description": "Target arrival time cannot be reached"