Skip to content

Commit

Permalink
fix: warn users when having unsymmetric times (#359)
Browse files Browse the repository at this point in the history
* fix: warn users when having unsymmetric times

* fix: refactored - only trainrun section elements (text) with warnings will have an <svg:title>....</svg:title> tag. This improves rendering performance.

* Update src/app/view/editor-tools-view-component/editor-tools-view.component.ts

Thanks

Co-authored-by: Serge Croisé <SergeCroise@users.noreply.github.com>

* fix: message - symmetry broken a + b = sum

* fix: rounding / (machine epsilon) issue fixed

* fix: message output text and refactored (simplified, improved readability) for trainrun section validation

* fix: epsilon issue fixed (caused by % 60 operation)

* code formatting

* fix: test added

* Update src/assets/i18n/fr.json

Co-authored-by: Serge Croisé <SergeCroise@users.noreply.github.com>

* fix: Serge comments

---------

Co-authored-by: Serge Croisé <SergeCroise@users.noreply.github.com>
Co-authored-by: Louis Greiner <greiner.louis@gmail.com>
  • Loading branch information
3 people authored Nov 22, 2024
1 parent 338b69f commit 9923567
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 48 deletions.
24 changes: 22 additions & 2 deletions src/app/models/trainrunsection.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -639,7 +659,7 @@ export class TrainrunSection {
resourceId: this.resourceId,

specificTrainrunSectionFrequencyId:
this.specificTrainrunSectionFrequencyId,
this.specificTrainrunSectionFrequencyId,
path: this.path,
warnings: this.warnings,
};
Expand Down
39 changes: 31 additions & 8 deletions src/app/services/util/trainrunsection.validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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`,
Expand All @@ -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`,
Expand All @@ -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(
Expand Down
61 changes: 36 additions & 25 deletions src/app/services/util/transition.validator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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));
});
});
64 changes: 58 additions & 6 deletions src/app/view/editor-main-view/data-views/trainrunsections.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -1221,7 +1246,7 @@ export class TrainrunSectionsView {
connectedTrainIds: any,
atSource: boolean,
) {
if (!this.editorView.trainrunSectionPreviewLineView.getVariantIsWritable()){
if (!this.editorView.trainrunSectionPreviewLineView.getVariantIsWritable()) {
return;
}
groupEnter
Expand Down Expand Up @@ -1314,29 +1339,30 @@ 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) &&
this.filterTimeTrainrunsectionNonStop(
d.trainrunSection,
atSource,
isArrival,
),
) &&
TrainrunSectionsView.hasWarning(d.trainrunSection, textElement) === hasWarning
)
.append(StaticDomTags.EDGE_LINE_TEXT_SVG)
.attr("class", (d: TrainrunSectionViewObject) =>
Expand Down Expand Up @@ -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 <svg:title>warning_msg</svg:title>
this.createInternTrainrunSectionElementFilteringWarningElements(
groupEnter, selectedTrainrun, connectedTrainIds, textElement, enableEvents, true
);
}

createTrainrunSectionGotoInfoElement(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
}
},
"trainrunsection-validator": {
"broken-symmetry": "Symmetrie gebrochen",
"target-arrival-not-reacheable": {
"title": "Ziel Ankunft Warnung",
"description": "Zielankunftszeit kann nicht erreicht werden"
Expand Down
1 change: 1 addition & 0 deletions src/assets/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
Loading

0 comments on commit 9923567

Please sign in to comment.