diff --git a/src/app/sample-netzgrafik/netzgrafik.default.spec.ts b/src/app/sample-netzgrafik/netzgrafik.default.spec.ts index 9c28bb936..deca1e7e0 100644 --- a/src/app/sample-netzgrafik/netzgrafik.default.spec.ts +++ b/src/app/sample-netzgrafik/netzgrafik.default.spec.ts @@ -1,5 +1,6 @@ import netzgrafikDefaultJson from "./netzgrafik_default.json"; import {NetzgrafikDefault} from "./netzgrafik.default"; +import {NetzgrafikTestData} from "./test-data/netzgrafik"; import {DataService} from "../services/data/data.service"; import {LogPublishersService} from "../logger/log.publishers.service"; import {LogService} from "../logger/log.service"; @@ -63,21 +64,116 @@ describe("NetzgrafikDefault", () => { ); }); - it("should load and serialize default netzgrafikDto (no trainruns) without changes", () => { - const inputDto = NetzgrafikDefault.getDefaultNetzgrafik(); + // TODO: broken until the numberOfStops migration is completely done + // need to be replaced with tests importing a graph without any numberOfStops + describe("normal case", () => { + // needs to be updated to new format (no longer numberOfStops but the complete graph nodes + links) + it("should load and serialize default netzgrafikDto (no trainruns) without changes", () => { + const inputDto = NetzgrafikDefault.getDefaultNetzgrafik(); - dataService.loadNetzgrafikDto(inputDto); + dataService.loadNetzgrafikDto(inputDto); - const outputJson = dataService.getNetzgrafikDto(); - expect(JSON.stringify(inputDto, null, 2)).toEqual(JSON.stringify(outputJson, null, 2)); + const outputJson = dataService.getNetzgrafikDto(); + expect(JSON.stringify(inputDto, null, 2)).toEqual(JSON.stringify(outputJson, null, 2)); + }); + + it("should load and serialize demo netzgrafikDto (complete variant) without changes", () => { + const inputDto = NetzgrafikDefault.getNetzgrafikDemoStandaloneGithub(); + + dataService.loadNetzgrafikDto(inputDto); + + const outputJson = dataService.getNetzgrafikDto(); + expect(JSON.stringify(inputDto, null, 2)).toEqual(JSON.stringify(outputJson, null, 2)); + }); }); - it("should load and serialize demo netzgrafikDto (complete variant) without changes", () => { - const inputDto = NetzgrafikDefault.getNetzgrafikDemoStandaloneGithub(); + describe("handling old files with numberOfStops", () => { + describe("2 nodes", () => { + // A -> B has 1 stop + it("should replace the numberOfStops: 1 with an equivalent graph portion", () => { + const inputDto = NetzgrafikTestData.getLegacyNtezgrafik2Nodes(); + + dataService.loadNetzgrafikDto(inputDto); + + const outputJson = dataService.getNetzgrafikDto(); + + // we expect to have a new node between A and B + // the existing trainrunSection A -> B to now be A -> NEW + // and a new trainrunSection NEW -> B + const newNode = outputJson.nodes[2]; + expect(newNode.betriebspunktName).toBe("NEW"); + expect(newNode.positionX).toBe(1488); + expect(newNode.positionY).toBe(512); + expect(outputJson.trainrunSections[0].sourceNodeId).toBe(11); + expect(outputJson.trainrunSections[0].targetNodeId).toBe(newNode.id); + expect(outputJson.trainrunSections[1].sourceNodeId).toBe(newNode.id); + expect(outputJson.trainrunSections[1].targetNodeId).toBe(12); + expect(outputJson.trainrunSections[0].numberOfStops).toBe(0); + expect(outputJson.trainrunSections[1].numberOfStops).toBe(0); + }); + it("should replace the numberOfStops: 3 with an equivalent graph portion", () => { + const inputDto = NetzgrafikTestData.getLegacyNtezgrafik2Nodes(); + inputDto.trainrunSections[0].numberOfStops = 3; + + dataService.loadNetzgrafikDto(inputDto); + // before + // A ------ B + // after + // A ------ C ------ D ------ E ------ B + const outputJson = dataService.getNetzgrafikDto(); + const newNode1 = outputJson.nodes[2]; + expect(newNode1.betriebspunktName).toBe("NEW"); + expect(newNode1.positionX).toBe(1488); + expect(newNode1.positionY).toBe(512); + const newNode2 = outputJson.nodes[3]; + expect(newNode2.betriebspunktName).toBe("NEW"); + expect(newNode2.positionX).toBe(1576); + expect(newNode2.positionY).toBe(512); + const newNode3 = outputJson.nodes[4]; + expect(newNode3.betriebspunktName).toBe("NEW"); + expect(newNode3.positionX).toBe(1620); + expect(newNode3.positionY).toBe(512); + expect(outputJson.trainrunSections[0].sourceNodeId).toBe(11); + expect(outputJson.trainrunSections[0].targetNodeId).toBe(newNode1.id); + expect(outputJson.trainrunSections[1].sourceNodeId).toBe(newNode1.id); + expect(outputJson.trainrunSections[1].targetNodeId).toBe(newNode2.id); + expect(outputJson.trainrunSections[2].sourceNodeId).toBe(newNode2.id); + expect(outputJson.trainrunSections[2].targetNodeId).toBe(newNode3.id); + expect(outputJson.trainrunSections[3].sourceNodeId).toBe(newNode3.id); + expect(outputJson.trainrunSections[3].targetNodeId).toBe(12); + expect(outputJson.trainrunSections[0].numberOfStops).toBe(0); + expect(outputJson.trainrunSections[1].numberOfStops).toBe(0); + expect(outputJson.trainrunSections[2].numberOfStops).toBe(0); + expect(outputJson.trainrunSections[3].numberOfStops).toBe(0); + }); + }); + describe("3 nodes", () => { + // A -> B -> C + // each trainrunSection with 1 stop + it("should replace the numberOfStops: 1 with an equivalent graph portion for each section", () => { + const inputDto = NetzgrafikTestData.getLegacyNtezgrafik3Nodes(); + + dataService.loadNetzgrafikDto(inputDto); - dataService.loadNetzgrafikDto(inputDto); + const outputJson = dataService.getNetzgrafikDto(); - const outputJson = dataService.getNetzgrafikDto(); - expect(JSON.stringify(inputDto, null, 2)).toEqual(JSON.stringify(outputJson, null, 2)); + const newNode1 = outputJson.nodes[3]; + expect(newNode1.betriebspunktName).toBe("NEW"); + expect(newNode1.positionX).toBe(1488); + expect(newNode1.positionY).toBe(512); + const newNode2 = outputJson.nodes[4]; + expect(newNode2.betriebspunktName).toBe("NEW"); + expect(newNode2.positionX).toBe(1808); + expect(newNode2.positionY).toBe(544); + expect(outputJson.trainrunSections[0].sourceNodeId).toBe(11); + expect(outputJson.trainrunSections[0].targetNodeId).toBe(newNode1.id); + expect(outputJson.trainrunSections[2].sourceNodeId).toBe(newNode1.id); + expect(outputJson.trainrunSections[2].targetNodeId).toBe(12); + expect(outputJson.trainrunSections[1].sourceNodeId).toBe(12); + expect(outputJson.trainrunSections[1].targetNodeId).toBe(newNode2.id); + expect(outputJson.trainrunSections[3].sourceNodeId).toBe(newNode2.id); + expect(outputJson.trainrunSections[3].targetNodeId).toBe(13); + }); + }); }); }); diff --git a/src/app/sample-netzgrafik/test-data/2-nodes-legacy.json b/src/app/sample-netzgrafik/test-data/2-nodes-legacy.json new file mode 100644 index 000000000..9b46b529c --- /dev/null +++ b/src/app/sample-netzgrafik/test-data/2-nodes-legacy.json @@ -0,0 +1,330 @@ +{ + "nodes": [ + { + "id": 11, + "betriebspunktName": "A", + "fullName": "", + "positionX": 1312, + "positionY": 512, + "ports": [{"id": 1, "trainrunSectionId": 1, "positionIndex": 0, "positionAlignment": 3}], + "transitions": [], + "connections": [], + "resourceId": 12, + "perronkanten": 5, + "connectionTime": 3, + "trainrunCategoryHaltezeiten": { + "HaltezeitIPV": {"haltezeit": 3, "no_halt": false}, + "HaltezeitA": {"haltezeit": 2, "no_halt": false}, + "HaltezeitB": {"haltezeit": 2, "no_halt": false}, + "HaltezeitC": {"haltezeit": 1, "no_halt": false}, + "HaltezeitD": {"haltezeit": 1, "no_halt": false}, + "HaltezeitUncategorized": {"haltezeit": 0, "no_halt": true} + }, + "symmetryAxis": null, + "warnings": null, + "labelIds": [], + "isCollapsed": false + }, + { + "id": 12, + "betriebspunktName": "B", + "fullName": "", + "positionX": 1664, + "positionY": 512, + "ports": [{"id": 2, "trainrunSectionId": 1, "positionIndex": 0, "positionAlignment": 2}], + "transitions": [], + "connections": [], + "resourceId": 13, + "perronkanten": 5, + "connectionTime": 3, + "trainrunCategoryHaltezeiten": { + "HaltezeitIPV": {"haltezeit": 3, "no_halt": false}, + "HaltezeitA": {"haltezeit": 2, "no_halt": false}, + "HaltezeitB": {"haltezeit": 2, "no_halt": false}, + "HaltezeitC": {"haltezeit": 1, "no_halt": false}, + "HaltezeitD": {"haltezeit": 1, "no_halt": false}, + "HaltezeitUncategorized": {"haltezeit": 0, "no_halt": true} + }, + "symmetryAxis": null, + "warnings": null, + "labelIds": [], + "isCollapsed": false + } + ], + "trainrunSections": [ + { + "id": 1, + "sourceNodeId": 11, + "sourcePortId": 1, + "targetNodeId": 12, + "targetPortId": 2, + "travelTime": { + "time": 1, + "consecutiveTime": 1, + "lock": true, + "warning": null, + "timeFormatter": null + }, + "sourceDeparture": { + "time": 0, + "consecutiveTime": 0, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "sourceArrival": { + "time": 0, + "consecutiveTime": 60, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetDeparture": { + "time": 59, + "consecutiveTime": 59, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetArrival": { + "time": 1, + "consecutiveTime": 1, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "numberOfStops": 1, + "trainrunId": 1, + "resourceId": 0, + "specificTrainrunSectionFrequencyId": null, + "path": { + "path": [ + {"x": 1410, "y": 528}, + {"x": 1474, "y": 528}, + {"x": 1598, "y": 528}, + {"x": 1662, "y": 528} + ], + "textPositions": { + "0": {"x": 1428, "y": 540}, + "1": {"x": 1456, "y": 516}, + "2": {"x": 1644, "y": 516}, + "3": {"x": 1616, "y": 540}, + "4": {"x": 1536, "y": 516}, + "5": {"x": 1536, "y": 516}, + "6": {"x": 1536, "y": 540} + } + }, + "warnings": null + } + ], + "trainruns": [ + { + "id": 1, + "name": "X", + "categoryId": 1, + "frequencyId": 3, + "trainrunTimeCategoryId": 0, + "labelIds": [], + "direction": "round_trip" + } + ], + "resources": [ + {"id": 12, "capacity": 2}, + {"id": 13, "capacity": 2} + ], + "metadata": { + "analyticsSettings": {"originDestinationSettings": {"connectionPenalty": 5}}, + "trainrunCategories": [ + { + "id": 0, + "order": 0, + "shortName": "EC", + "name": "International", + "colorRef": "EC", + "fachCategory": "HaltezeitIPV", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 1, + "order": 1, + "shortName": "IC", + "name": "InterCity", + "colorRef": "IC", + "fachCategory": "HaltezeitA", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 2, + "order": 2, + "shortName": "IR", + "name": "InterRegio", + "colorRef": "IR", + "fachCategory": "HaltezeitB", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 3, + "order": 3, + "shortName": "RE", + "name": "RegioExpress", + "colorRef": "RE", + "fachCategory": "HaltezeitC", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 4, + "order": 4, + "shortName": "S", + "name": "RegioUndSBahnverkehr", + "colorRef": "S", + "fachCategory": "HaltezeitD", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 5, + "order": 5, + "shortName": "GEX", + "name": "GüterExpress", + "colorRef": "GEX", + "fachCategory": "HaltezeitUncategorized", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 3, + "nodeHeadwayNonStop": 3, + "sectionHeadway": 3 + }, + { + "id": 6, + "order": 6, + "shortName": "G", + "name": "Güterverkehr", + "colorRef": "G", + "fachCategory": "HaltezeitUncategorized", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 3, + "nodeHeadwayNonStop": 3, + "sectionHeadway": 3 + } + ], + "trainrunFrequencies": [ + { + "id": 0, + "order": 0, + "frequency": 15, + "offset": 0, + "shortName": "15", + "name": "verkehrt viertelstündlich", + "linePatternRef": "15" + }, + { + "id": 1, + "order": 0, + "frequency": 20, + "offset": 0, + "shortName": "20", + "name": "verkehrt im 20 Minuten Takt", + "linePatternRef": "20" + }, + { + "id": 2, + "order": 0, + "frequency": 30, + "offset": 0, + "shortName": "30", + "name": "verkehrt halbstündlich", + "linePatternRef": "30" + }, + { + "id": 3, + "order": 0, + "frequency": 60, + "offset": 0, + "shortName": "60", + "name": "verkehrt stündlich", + "linePatternRef": "60" + }, + { + "id": 4, + "order": 0, + "frequency": 120, + "offset": 0, + "shortName": "120", + "name": "verkehrt zweistündlich (gerade)", + "linePatternRef": "120" + }, + { + "id": 5, + "order": 0, + "frequency": 120, + "offset": 60, + "shortName": "120+", + "name": "verkehrt zweistündlich (ungerade)", + "linePatternRef": "120" + } + ], + "trainrunTimeCategories": [ + { + "id": 0, + "order": 0, + "shortName": "7/24", + "name": "verkehrt uneingeschränkt", + "dayTimeInterval": [], + "weekday": [1, 2, 3, 4, 5, 6, 7], + "linePatternRef": "7/24" + }, + { + "id": 1, + "order": 0, + "shortName": "HVZ", + "name": "verkehrt zur Hauptverkehrszeit", + "dayTimeInterval": [ + {"from": 360, "to": 420}, + {"from": 960, "to": 1140} + ], + "weekday": [1, 2, 3, 4, 5, 6, 7], + "linePatternRef": "HVZ" + }, + { + "id": 2, + "order": 0, + "shortName": "zeitweise", + "name": "verkehrt zeitweise", + "dayTimeInterval": [], + "weekday": [], + "linePatternRef": "ZEITWEISE" + } + ], + "netzgrafikColors": [ + { + "id": 2, + "colorRef": "NORMAL", + "color": "#767676", + "colorFocus": "#000000", + "colorMuted": "#DCDCDC", + "colorRelated": "#767676", + "colorDarkMode": "#767676", + "colorDarkModeFocus": "#DCDCDC", + "colorDarkModeMuted": "#000000", + "colorDarkModeRelated": "#767676" + } + ] + }, + "freeFloatingTexts": [], + "labels": [], + "labelGroups": [], + "filterData": {"filterSettings": []} +} diff --git a/src/app/sample-netzgrafik/test-data/3-nodes-legacy.json b/src/app/sample-netzgrafik/test-data/3-nodes-legacy.json new file mode 100644 index 000000000..5543bd679 --- /dev/null +++ b/src/app/sample-netzgrafik/test-data/3-nodes-legacy.json @@ -0,0 +1,432 @@ +{ + "nodes": [ + { + "id": 11, + "betriebspunktName": "A", + "fullName": "", + "positionX": 1312, + "positionY": 512, + "ports": [{"id": 1, "trainrunSectionId": 1, "positionIndex": 0, "positionAlignment": 3}], + "transitions": [], + "connections": [], + "resourceId": 12, + "perronkanten": 5, + "connectionTime": 3, + "trainrunCategoryHaltezeiten": { + "HaltezeitIPV": {"haltezeit": 3, "no_halt": false}, + "HaltezeitA": {"haltezeit": 2, "no_halt": false}, + "HaltezeitB": {"haltezeit": 2, "no_halt": false}, + "HaltezeitC": {"haltezeit": 1, "no_halt": false}, + "HaltezeitD": {"haltezeit": 1, "no_halt": false}, + "HaltezeitUncategorized": {"haltezeit": 0, "no_halt": true} + }, + "symmetryAxis": null, + "warnings": null, + "labelIds": [], + "isCollapsed": false + }, + { + "id": 12, + "betriebspunktName": "B", + "fullName": "", + "positionX": 1664, + "positionY": 512, + "ports": [ + {"id": 2, "trainrunSectionId": 1, "positionIndex": 0, "positionAlignment": 2}, + {"id": 3, "trainrunSectionId": 2, "positionIndex": 0, "positionAlignment": 3} + ], + "transitions": [], + "connections": [], + "resourceId": 13, + "perronkanten": 5, + "connectionTime": 3, + "trainrunCategoryHaltezeiten": { + "HaltezeitIPV": {"haltezeit": 3, "no_halt": false}, + "HaltezeitA": {"haltezeit": 2, "no_halt": false}, + "HaltezeitB": {"haltezeit": 2, "no_halt": false}, + "HaltezeitC": {"haltezeit": 1, "no_halt": false}, + "HaltezeitD": {"haltezeit": 1, "no_halt": false}, + "HaltezeitUncategorized": {"haltezeit": 0, "no_halt": true} + }, + "symmetryAxis": null, + "warnings": null, + "labelIds": [], + "isCollapsed": false + }, + { + "id": 13, + "betriebspunktName": "C", + "fullName": "", + "positionX": 1952, + "positionY": 576, + "ports": [{"id": 4, "trainrunSectionId": 2, "positionIndex": 0, "positionAlignment": 2}], + "transitions": [], + "connections": [], + "resourceId": 14, + "perronkanten": 5, + "connectionTime": 3, + "trainrunCategoryHaltezeiten": { + "HaltezeitIPV": {"haltezeit": 3, "no_halt": false}, + "HaltezeitA": {"haltezeit": 2, "no_halt": false}, + "HaltezeitB": {"haltezeit": 2, "no_halt": false}, + "HaltezeitC": {"haltezeit": 1, "no_halt": false}, + "HaltezeitD": {"haltezeit": 1, "no_halt": false}, + "HaltezeitUncategorized": {"haltezeit": 0, "no_halt": true} + }, + "symmetryAxis": null, + "warnings": null, + "labelIds": [], + "isCollapsed": false + } + ], + "trainrunSections": [ + { + "id": 1, + "sourceNodeId": 11, + "sourcePortId": 1, + "targetNodeId": 12, + "targetPortId": 2, + "travelTime": { + "time": 1, + "consecutiveTime": 1, + "lock": true, + "warning": null, + "timeFormatter": null + }, + "sourceDeparture": { + "time": 0, + "consecutiveTime": 0, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "sourceArrival": { + "time": 0, + "consecutiveTime": 60, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetDeparture": { + "time": 59, + "consecutiveTime": 59, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetArrival": { + "time": 1, + "consecutiveTime": 1, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "numberOfStops": 1, + "trainrunId": 1, + "resourceId": 0, + "specificTrainrunSectionFrequencyId": null, + "path": { + "path": [ + {"x": 1410, "y": 528}, + {"x": 1474, "y": 528}, + {"x": 1598, "y": 528}, + {"x": 1662, "y": 528} + ], + "textPositions": { + "0": {"x": 1428, "y": 540}, + "1": {"x": 1456, "y": 516}, + "2": {"x": 1644, "y": 516}, + "3": {"x": 1616, "y": 540}, + "4": {"x": 1536, "y": 516}, + "5": {"x": 1536, "y": 516}, + "6": {"x": 1536, "y": 540} + } + }, + "warnings": null + }, + { + "id": 2, + "sourceNodeId": 12, + "sourcePortId": 3, + "targetNodeId": 13, + "targetPortId": 4, + "travelTime": { + "time": 1, + "consecutiveTime": 1, + "lock": true, + "warning": null, + "timeFormatter": null + }, + "sourceDeparture": { + "time": 0, + "consecutiveTime": 0, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "sourceArrival": { + "time": 0, + "consecutiveTime": 60, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetDeparture": { + "time": 59, + "consecutiveTime": 59, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "targetArrival": { + "time": 1, + "consecutiveTime": 1, + "lock": false, + "warning": null, + "timeFormatter": null + }, + "numberOfStops": 1, + "trainrunId": 2, + "resourceId": 0, + "specificTrainrunSectionFrequencyId": null, + "path": { + "path": [ + {"x": 1762, "y": 528}, + {"x": 1826, "y": 528}, + {"x": 1886, "y": 592}, + {"x": 1950, "y": 592} + ], + "textPositions": { + "0": {"x": 1780, "y": 540}, + "1": {"x": 1808, "y": 516}, + "2": {"x": 1932, "y": 580}, + "3": {"x": 1904, "y": 604}, + "4": {"x": 1856, "y": 548}, + "5": {"x": 1856, "y": 548}, + "6": {"x": 1856, "y": 572} + } + }, + "warnings": null + } + ], + "trainruns": [ + { + "id": 1, + "name": "X", + "categoryId": 1, + "frequencyId": 3, + "trainrunTimeCategoryId": 0, + "labelIds": [], + "direction": "round_trip" + }, + { + "id": 2, + "name": "X", + "categoryId": 1, + "frequencyId": 3, + "trainrunTimeCategoryId": 0, + "labelIds": [], + "direction": "round_trip" + } + ], + "resources": [ + {"id": 12, "capacity": 2}, + {"id": 13, "capacity": 2}, + {"id": 14, "capacity": 2} + ], + "metadata": { + "analyticsSettings": {"originDestinationSettings": {"connectionPenalty": 5}}, + "trainrunCategories": [ + { + "id": 0, + "order": 0, + "shortName": "EC", + "name": "International", + "colorRef": "EC", + "fachCategory": "HaltezeitIPV", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 1, + "order": 1, + "shortName": "IC", + "name": "InterCity", + "colorRef": "IC", + "fachCategory": "HaltezeitA", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 2, + "order": 2, + "shortName": "IR", + "name": "InterRegio", + "colorRef": "IR", + "fachCategory": "HaltezeitB", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 3, + "order": 3, + "shortName": "RE", + "name": "RegioExpress", + "colorRef": "RE", + "fachCategory": "HaltezeitC", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 4, + "order": 4, + "shortName": "S", + "name": "RegioUndSBahnverkehr", + "colorRef": "S", + "fachCategory": "HaltezeitD", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 2, + "nodeHeadwayNonStop": 2, + "sectionHeadway": 2 + }, + { + "id": 5, + "order": 5, + "shortName": "GEX", + "name": "GüterExpress", + "colorRef": "GEX", + "fachCategory": "HaltezeitUncategorized", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 3, + "nodeHeadwayNonStop": 3, + "sectionHeadway": 3 + }, + { + "id": 6, + "order": 6, + "shortName": "G", + "name": "Güterverkehr", + "colorRef": "G", + "fachCategory": "HaltezeitUncategorized", + "minimalTurnaroundTime": 4, + "nodeHeadwayStop": 3, + "nodeHeadwayNonStop": 3, + "sectionHeadway": 3 + } + ], + "trainrunFrequencies": [ + { + "id": 0, + "order": 0, + "frequency": 15, + "offset": 0, + "shortName": "15", + "name": "verkehrt viertelstündlich", + "linePatternRef": "15" + }, + { + "id": 1, + "order": 0, + "frequency": 20, + "offset": 0, + "shortName": "20", + "name": "verkehrt im 20 Minuten Takt", + "linePatternRef": "20" + }, + { + "id": 2, + "order": 0, + "frequency": 30, + "offset": 0, + "shortName": "30", + "name": "verkehrt halbstündlich", + "linePatternRef": "30" + }, + { + "id": 3, + "order": 0, + "frequency": 60, + "offset": 0, + "shortName": "60", + "name": "verkehrt stündlich", + "linePatternRef": "60" + }, + { + "id": 4, + "order": 0, + "frequency": 120, + "offset": 0, + "shortName": "120", + "name": "verkehrt zweistündlich (gerade)", + "linePatternRef": "120" + }, + { + "id": 5, + "order": 0, + "frequency": 120, + "offset": 60, + "shortName": "120+", + "name": "verkehrt zweistündlich (ungerade)", + "linePatternRef": "120" + } + ], + "trainrunTimeCategories": [ + { + "id": 0, + "order": 0, + "shortName": "7/24", + "name": "verkehrt uneingeschränkt", + "dayTimeInterval": [], + "weekday": [1, 2, 3, 4, 5, 6, 7], + "linePatternRef": "7/24" + }, + { + "id": 1, + "order": 0, + "shortName": "HVZ", + "name": "verkehrt zur Hauptverkehrszeit", + "dayTimeInterval": [ + {"from": 360, "to": 420}, + {"from": 960, "to": 1140} + ], + "weekday": [1, 2, 3, 4, 5, 6, 7], + "linePatternRef": "HVZ" + }, + { + "id": 2, + "order": 0, + "shortName": "zeitweise", + "name": "verkehrt zeitweise", + "dayTimeInterval": [], + "weekday": [], + "linePatternRef": "ZEITWEISE" + } + ], + "netzgrafikColors": [ + { + "id": 2, + "colorRef": "NORMAL", + "color": "#767676", + "colorFocus": "#000000", + "colorMuted": "#DCDCDC", + "colorRelated": "#767676", + "colorDarkMode": "#767676", + "colorDarkModeFocus": "#DCDCDC", + "colorDarkModeMuted": "#000000", + "colorDarkModeRelated": "#767676" + } + ] + }, + "freeFloatingTexts": [], + "labels": [], + "labelGroups": [], + "filterData": {"filterSettings": []} +} diff --git a/src/app/sample-netzgrafik/test-data/netzgrafik.ts b/src/app/sample-netzgrafik/test-data/netzgrafik.ts new file mode 100644 index 000000000..8ba7e743c --- /dev/null +++ b/src/app/sample-netzgrafik/test-data/netzgrafik.ts @@ -0,0 +1,13 @@ +import {NetzgrafikDto} from "../../data-structures/business.data.structures"; +import netzgrafikLegacy2NodesJson from "./2-nodes-legacy.json"; +import netzgrafikLegacy3NodesJson from "./3-nodes-legacy.json"; + +export class NetzgrafikTestData { + // old exported json data, still using numberOfStops trainrunSections format + static getLegacyNtezgrafik2Nodes(): NetzgrafikDto { + return JSON.parse(JSON.stringify(netzgrafikLegacy2NodesJson)) as NetzgrafikDto; + } + static getLegacyNtezgrafik3Nodes(): NetzgrafikDto { + return JSON.parse(JSON.stringify(netzgrafikLegacy3NodesJson)) as NetzgrafikDto; + } +} diff --git a/src/app/services/data/data.service.ts b/src/app/services/data/data.service.ts index ff6d2df45..d7c756a35 100644 --- a/src/app/services/data/data.service.ts +++ b/src/app/services/data/data.service.ts @@ -22,6 +22,7 @@ import {DataMigration} from "../../utils/data-migration"; import {FilterService} from "../ui/filter.service"; import {NetzgrafikColoringService} from "./netzgrafikColoring.service"; import {Trainrun} from "src/app/models/trainrun.model"; +import {Vec2D} from "src/app/utils/vec2D"; export class NetzgrafikLoadedInfo { constructor( @@ -30,9 +31,7 @@ export class NetzgrafikLoadedInfo { ) {} } -@Injectable({ - providedIn: "root", -}) +@Injectable({providedIn: "root"}) export class DataService implements OnDestroy { private netzgrafikDtoStore: {netzgrafikDto: NetzgrafikDto} = { netzgrafikDto: NetzgrafikDefault.getDefaultNetzgrafik(), @@ -99,6 +98,8 @@ export class DataService implements OnDestroy { this.trainrunSectionService.enforceConsistentSectionDirection(trainrun.getId()); }); + this.replaceLegacyNumberOfStopsWithRealNodes(netzgrafikDto); + // This must be done due of the bug fix - ensure that each resource object // is used in the Netzgrafik // https://github.com/OpenRailAssociation/netzgrafik-editor-frontend/issues/522 @@ -107,6 +108,29 @@ export class DataService implements OnDestroy { this.netzgrafikLoadedInfoSubject.next(new NetzgrafikLoadedInfo(false, preview)); } + replaceLegacyNumberOfStopsWithRealNodes(netzgrafikDto: NetzgrafikDto) { + for (const trainrunSection of netzgrafikDto.trainrunSections) { + let currentSection = trainrunSection; + for (let i = 0; i < trainrunSection.numberOfStops; i++) { + const newNode = this.nodeService.addEmptyNode(); + const {existingTrainRunSection, newTrainRunSection} = + this.trainrunSectionService.replaceIntermediateStopWithNode( + currentSection.id, + 0, + newNode.getId(), + ); + const sourceNode = existingTrainRunSection.getSourceNode(); + const targetNode = newTrainRunSection.getTargetNode(); + const interpolatedPosition = new Vec2D( + sourceNode.getPositionX() + (targetNode.getPositionX() - sourceNode.getPositionX()) * 0.5, + sourceNode.getPositionY() + (targetNode.getPositionY() - sourceNode.getPositionY()) * 0.5, + ); + newNode.setPosition(interpolatedPosition.getX(), interpolatedPosition.getY()); + currentSection = newTrainRunSection.getDto(); + } + } + } + insertCopyNetzgrafikDto(netzgrafikDto: NetzgrafikDto, enforceUpdate = true) { this.nodeService.unselectAllNodes(); const nodeMap = this.nodeService.mergeNodes(netzgrafikDto.nodes); diff --git a/src/app/services/data/node.service.ts b/src/app/services/data/node.service.ts index 484d77b83..28c3e3ac9 100644 --- a/src/app/services/data/node.service.ts +++ b/src/app/services/data/node.service.ts @@ -38,9 +38,7 @@ import { TrainrunOperation, } from "../../models/operation.model"; -@Injectable({ - providedIn: "root", -}) +@Injectable({providedIn: "root"}) export class NodeService implements OnDestroy { // Description of observable data service: https://coryrylan.com/blog/angular-observable-data-services nodesSubject = new BehaviorSubject([]); @@ -256,6 +254,16 @@ export class NodeService implements OnDestroy { return node; } + addEmptyNode(): Node { + const node: Node = new Node(); + node.setIsCollapsed(true); + const resource: Resource = this.resourceService.createAndGetResource(); + node.setResourceId(resource.getId()); + this.nodesStore.nodes.push(node); + this.operation.emit(new NodeOperation(OperationType.create, node)); + return node; + } + deleteNode(nodeId: number, enforceUpdate = true) { const node = this.getNodeFromId(nodeId); const deletetLabelIds = this.labelService.clearLabel( diff --git a/src/app/services/data/trainrunsection.service.ts b/src/app/services/data/trainrunsection.service.ts index 3ed3a2453..a1ccc9813 100644 --- a/src/app/services/data/trainrunsection.service.ts +++ b/src/app/services/data/trainrunsection.service.ts @@ -21,6 +21,7 @@ import {takeUntil} from "rxjs/operators"; import {FilterService} from "../ui/filter.service"; import {TrainrunSectionNodePair} from "../util/trainrun.iterator"; import {Operation, OperationType, TrainrunOperation} from "../../models/operation.model"; +import {Vec2D} from "../../utils/vec2D"; interface DepartureAndArrivalTimes { nodeFromDepartureTime: number; @@ -34,16 +35,12 @@ export interface InformSelectedTrainrunClick { open: boolean; } -@Injectable({ - providedIn: "root", -}) +@Injectable({providedIn: "root"}) export class TrainrunSectionService implements OnDestroy { // Description of observable data service: https://coryrylan.com/blog/angular-observable-data-services trainrunSectionsSubject = new BehaviorSubject([]); readonly trainrunSections = this.trainrunSectionsSubject.asObservable(); - trainrunSectionsStore: {trainrunSections: TrainrunSection[]} = { - trainrunSections: [], - }; // store the data in memory + trainrunSectionsStore: {trainrunSections: TrainrunSection[]} = {trainrunSections: []}; // store the data in memory readonly operation = new EventEmitter(); @@ -936,13 +933,14 @@ export class TrainrunSectionService implements OnDestroy { }); } + // this function is no longer used for its original purpose (drag a node that only existed inside numberOfStops and create it inside the real graph) replaceIntermediateStopWithNode(trainrunSectionId: number, stopIndex: number, nodeId: number) { const trainrunSection1 = this.getTrainrunSectionFromId(trainrunSectionId); if ( trainrunSection1.getSourceNodeId() === nodeId || trainrunSection1.getTargetNodeId() === nodeId ) { - return; + return {}; } const origTravelTime = trainrunSection1.getTravelTime(); const trainrunSection2 = this.copyTrainrunSection( @@ -1048,6 +1046,10 @@ export class TrainrunSectionService implements OnDestroy { this.nodeService.connectionsUpdated(); this.nodeService.nodesUpdated(); this.trainrunSectionsUpdated(); + return { + existingTrainRunSection: trainrunSection1, + newTrainRunSection: trainrunSection2, + }; } setWarningOnNode(