From 1d89ece357ce5844289ebe03c46504cf932e5235 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Henriot?= <142150521+shenriotpro@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:36:25 +0200 Subject: [PATCH] style: format O/D Matrix source using prettier (#318) --- src/app/view/util/origin-destination-graph.ts | 594 +++-- .../netzgrafik.unit.testing.od.matrix.ts | 2178 ++++++++--------- .../origin.destination.csv.test.spec.ts | 97 +- 3 files changed, 1501 insertions(+), 1368 deletions(-) diff --git a/src/app/view/util/origin-destination-graph.ts b/src/app/view/util/origin-destination-graph.ts index 1420ba34..29f26f52 100644 --- a/src/app/view/util/origin-destination-graph.ts +++ b/src/app/view/util/origin-destination-graph.ts @@ -5,298 +5,400 @@ import {Node} from "src/app/models/node.model"; // A vertex indicates a "state": e.g. arriving at a node at a certain time and from a given trainrun. export class Vertex { - constructor( - public nodeId: number, - // Indicates if we depart or arrive at the node. - public isDeparture: boolean, - // Optional fields are undefined for "convenience" vertices. - // Absolute time (duration from the start of the schedule) in minutes. - public time?: number, - // Negative trainrun ids are used for reverse directions. - public trainrunId?: number - ){} + constructor( + public nodeId: number, + // Indicates if we depart or arrive at the node. + public isDeparture: boolean, + // Optional fields are undefined for "convenience" vertices. + // Absolute time (duration from the start of the schedule) in minutes. + public time?: number, + // Negative trainrun ids are used for reverse directions. + public trainrunId?: number, + ) {} } export class Edge { - constructor( - public v1: Vertex, - public v2: Vertex, - // The weight represents the cost of the edge, it is similar to a duration in minutes - // but it may include a connection penalty cost. - public weight: number - ){} + constructor( + public v1: Vertex, + public v2: Vertex, + // The weight represents the cost of the edge, it is similar to a duration in minutes + // but it may include a connection penalty cost. + public weight: number, + ) {} } -export const buildEdges = (nodes: Node[], odNodes: Node[], trainruns: Trainrun[], connectionPenalty: number, - trainrunService: TrainrunService, timeLimit: number +export const buildEdges = ( + nodes: Node[], + odNodes: Node[], + trainruns: Trainrun[], + connectionPenalty: number, + trainrunService: TrainrunService, + timeLimit: number, ): Edge[] => { - let edges = buildSectionEdges(trainruns, trainrunService, timeLimit); + let edges = buildSectionEdges(trainruns, trainrunService, timeLimit); - const verticesDepartureByTrainrunByNode = new Map>(); - const verticesArrivalByTrainrunByNode = new Map>(); - edges.forEach((edge) => { - const src = edge.v1; - const tgt = edge.v2; - if (src.isDeparture !== true) { - console.log("src is not a departure: ", src); - } - if (tgt.isDeparture !== false) { - console.log("tgt is not an arrival: ", tgt); - } - const departuresByTrainrun = verticesDepartureByTrainrunByNode.get(src.nodeId); - if (departuresByTrainrun === undefined) { - verticesDepartureByTrainrunByNode.set(src.nodeId, new Map([[src.trainrunId, [src]]])); + const verticesDepartureByTrainrunByNode = new Map< + number, + Map + >(); + const verticesArrivalByTrainrunByNode = new Map< + number, + Map + >(); + edges.forEach((edge) => { + const src = edge.v1; + const tgt = edge.v2; + if (src.isDeparture !== true) { + console.log("src is not a departure: ", src); + } + if (tgt.isDeparture !== false) { + console.log("tgt is not an arrival: ", tgt); + } + const departuresByTrainrun = verticesDepartureByTrainrunByNode.get( + src.nodeId, + ); + if (departuresByTrainrun === undefined) { + verticesDepartureByTrainrunByNode.set( + src.nodeId, + new Map([[src.trainrunId, [src]]]), + ); + } else { + const departures = departuresByTrainrun.get(src.trainrunId); + if (departures === undefined) { + departuresByTrainrun.set(src.trainrunId, [src]); } else { - const departures = departuresByTrainrun.get(src.trainrunId); - if (departures === undefined) { - departuresByTrainrun.set(src.trainrunId, [src]); - } else { - departures.push(src); - } + departures.push(src); } - const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(tgt.nodeId); - if (arrivalsByTrainrun === undefined) { - verticesArrivalByTrainrunByNode.set(tgt.nodeId, new Map([[tgt.trainrunId, [tgt]]])); + } + const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(tgt.nodeId); + if (arrivalsByTrainrun === undefined) { + verticesArrivalByTrainrunByNode.set( + tgt.nodeId, + new Map([[tgt.trainrunId, [tgt]]]), + ); + } else { + const arrivals = arrivalsByTrainrun.get(tgt.trainrunId); + if (arrivals === undefined) { + arrivalsByTrainrun.set(tgt.trainrunId, [tgt]); } else { - const arrivals = arrivalsByTrainrun.get(tgt.trainrunId); - if (arrivals === undefined) { - arrivalsByTrainrun.set(tgt.trainrunId, [tgt]); - } else { - arrivals.push(tgt); - } + arrivals.push(tgt); } - }); + } + }); - // Sorting is useful to find relevant connections later. - verticesDepartureByTrainrunByNode.forEach((verticesDepartureByTrainrun) => { - verticesDepartureByTrainrun.forEach((departures, trainrunId) => { - departures.sort((a, b) => a.time - b.time); - }); + // Sorting is useful to find relevant connections later. + verticesDepartureByTrainrunByNode.forEach((verticesDepartureByTrainrun) => { + verticesDepartureByTrainrun.forEach((departures, trainrunId) => { + departures.sort((a, b) => a.time - b.time); }); - verticesArrivalByTrainrunByNode.forEach((verticesArrivalByTrainrun) => { - verticesArrivalByTrainrun.forEach((arrivals, trainrunId) => { - arrivals.sort((a, b) => a.time - b.time); - }); + }); + verticesArrivalByTrainrunByNode.forEach((verticesArrivalByTrainrun) => { + verticesArrivalByTrainrun.forEach((arrivals, trainrunId) => { + arrivals.sort((a, b) => a.time - b.time); }); - - // Note: pushing too many elements at once does not work well. - edges = [...edges, ...buildConvenienceEdges(odNodes, verticesDepartureByTrainrunByNode, verticesArrivalByTrainrunByNode)]; - edges = [...edges, - ...buildConnectionEdges(nodes, verticesDepartureByTrainrunByNode, verticesArrivalByTrainrunByNode, connectionPenalty) - ]; + }); - return edges; + // Note: pushing too many elements at once does not work well. + edges = [ + ...edges, + ...buildConvenienceEdges( + odNodes, + verticesDepartureByTrainrunByNode, + verticesArrivalByTrainrunByNode, + ), + ]; + edges = [ + ...edges, + ...buildConnectionEdges( + nodes, + verticesDepartureByTrainrunByNode, + verticesArrivalByTrainrunByNode, + connectionPenalty, + ), + ]; + + return edges; }; // Given edges, return the neighbors (with weights) for each vertex, if any (outgoing adjacency list). -export const computeNeighbors = (edges: Edge[]): Map => { - const neighbors = new Map(); - edges.forEach((edge) => { - const v1 = JSON.stringify(edge.v1); - const v1Neighbors = neighbors.get(v1); - if (v1Neighbors === undefined) { - neighbors.set(v1, [[edge.v2, edge.weight]]); - } else { - v1Neighbors.push([edge.v2, edge.weight]); - } - }); - return neighbors; +export const computeNeighbors = ( + edges: Edge[], +): Map => { + const neighbors = new Map(); + edges.forEach((edge) => { + const v1 = JSON.stringify(edge.v1); + const v1Neighbors = neighbors.get(v1); + if (v1Neighbors === undefined) { + neighbors.set(v1, [[edge.v2, edge.weight]]); + } else { + v1Neighbors.push([edge.v2, edge.weight]); + } + }); + return neighbors; }; // Given a graph (adjacency list), return the vertices in topological order. // Note: sorting vertices by time would be enough for our use case. export const topoSort = (graph: Map): Vertex[] => { - const res = []; - const visited = new Set(); - for (const node of graph.keys()) { - if (!visited.has(node)) { - depthFirstSearch(graph, JSON.parse(node) as Vertex, visited, res); - } + const res = []; + const visited = new Set(); + for (const node of graph.keys()) { + if (!visited.has(node)) { + depthFirstSearch(graph, JSON.parse(node) as Vertex, visited, res); } - return res.reverse(); + } + return res.reverse(); }; // Given a graph (adjacency list), and vertices in topological order, return the shortest paths (and connections) // from a given node to other nodes. -export const computeShortestPaths = (from: number, neighbors: Map, vertices: Vertex[]): -Map => { - const res = new Map(); - const dist = new Map(); - let started = false; - vertices.forEach((vertex) => { - const key = JSON.stringify(vertex); - // First, look for our start node. - if (!started) { - if (from === vertex.nodeId && vertex.isDeparture === true && vertex.time === undefined) { - started = true; - dist.set(key, [0, 0]); - } else { - return; - } - } - // We found an end node. - if (vertex.isDeparture === false && vertex.time === undefined && dist.get(key) !== undefined - && vertex.nodeId !== from) { - res.set(vertex.nodeId, dist.get(key)); - } - const neighs = neighbors.get(key); - if (neighs === undefined || dist.get(key) === undefined) { - return; +export const computeShortestPaths = ( + from: number, + neighbors: Map, + vertices: Vertex[], +): Map => { + const res = new Map(); + const dist = new Map(); + let started = false; + vertices.forEach((vertex) => { + const key = JSON.stringify(vertex); + // First, look for our start node. + if (!started) { + if ( + from === vertex.nodeId && + vertex.isDeparture === true && + vertex.time === undefined + ) { + started = true; + dist.set(key, [0, 0]); + } else { + return; + } + } + // We found an end node. + if ( + vertex.isDeparture === false && + vertex.time === undefined && + dist.get(key) !== undefined && + vertex.nodeId !== from + ) { + res.set(vertex.nodeId, dist.get(key)); + } + const neighs = neighbors.get(key); + if (neighs === undefined || dist.get(key) === undefined) { + return; + } + // The shortest path from the start node to this vertex is a shortest path from the start node to a neighbor + // plus the weight of the edge connecting the neighbor to this vertex. + neighs.forEach(([neighbor, weight]) => { + const alt = dist.get(key)[0] + weight; + const neighborKey = JSON.stringify(neighbor); + if ( + dist.get(neighborKey) === undefined || + alt < dist.get(neighborKey)[0] + ) { + let connection = 0; + if ( + vertex.trainrunId !== undefined && + neighbor.trainrunId !== undefined && + vertex.trainrunId !== neighbor.trainrunId + ) { + connection = 1; } - // The shortest path from the start node to this vertex is a shortest path from the start node to a neighbor - // plus the weight of the edge connecting the neighbor to this vertex. - neighs.forEach(([neighbor, weight]) => { - const alt = dist.get(key)[0] + weight; - const neighborKey = JSON.stringify(neighbor); - if (dist.get(neighborKey) === undefined || alt < dist.get(neighborKey)[0]) { - let connection = 0; - if (vertex.trainrunId !== undefined && neighbor.trainrunId !== undefined - && vertex.trainrunId !== neighbor.trainrunId) { - connection = 1; - } - dist.set(neighborKey, [alt, dist.get(key)[1] + connection]); - } - }); + dist.set(neighborKey, [alt, dist.get(key)[1] + connection]); + } }); - return res; + }); + return res; }; -const buildSectionEdges = (trainruns: Trainrun[], trainrunService: TrainrunService, timeLimit: number): Edge[] => { - const edges = []; - const its = trainrunService.getRootIterators(); - trainruns.forEach((trainrun) => { - const tsIterators = its.get(trainrun.getId()); - if (tsIterators === undefined) { - console.log("Ignoring trainrun (no root found): ", trainrun.getId()); - return; - } - tsIterators.forEach((tsIterator) => { - edges.push(...buildSectionEdgesFromIterator(tsIterator, false, timeLimit)); - // Don't forget the reverse direction. - const ts = tsIterator.current().trainrunSection; - const nextIterator = trainrunService.getIterator(ts.getTargetNode(), ts); - edges.push(...buildSectionEdgesFromIterator(nextIterator, true, timeLimit)); - }); +const buildSectionEdges = ( + trainruns: Trainrun[], + trainrunService: TrainrunService, + timeLimit: number, +): Edge[] => { + const edges = []; + const its = trainrunService.getRootIterators(); + trainruns.forEach((trainrun) => { + const tsIterators = its.get(trainrun.getId()); + if (tsIterators === undefined) { + console.log("Ignoring trainrun (no root found): ", trainrun.getId()); + return; + } + tsIterators.forEach((tsIterator) => { + edges.push( + ...buildSectionEdgesFromIterator(tsIterator, false, timeLimit), + ); + // Don't forget the reverse direction. + const ts = tsIterator.current().trainrunSection; + const nextIterator = trainrunService.getIterator(ts.getTargetNode(), ts); + edges.push( + ...buildSectionEdgesFromIterator(nextIterator, true, timeLimit), + ); }); - return edges; + }); + return edges; }; -const buildSectionEdgesFromIterator = (tsIterator: TrainrunIterator, reverse: boolean, timeLimit: number): Edge[] => { - const edges = []; - let nonStopV1Time = -1; - let nonStopV1Node = -1; - while (tsIterator.hasNext()) { - tsIterator.next(); - const ts = tsIterator.current().trainrunSection; - const trainrunId = reverse ? -ts.getTrainrunId() : ts.getTrainrunId(); - const v1Time = reverse ? ts.getTargetDepartureDto().consecutiveTime : ts.getSourceDepartureDto().consecutiveTime; - const v1Node = reverse ? ts.getTargetNodeId() : ts.getSourceNodeId(); - // If we don't stop here, we need to remember where we started. - if (reverse ? ts.getSourceNode().isNonStop(ts) : ts.getTargetNode().isNonStop(ts)) { - if (nonStopV1Time === -1) { - nonStopV1Time = v1Time; - nonStopV1Node = v1Node; - } - continue; - } - let v1 = new Vertex(v1Node, true, v1Time, trainrunId); - let nonStop = false; - if (nonStopV1Time !== -1) { - v1 = new Vertex(nonStopV1Node, true, nonStopV1Time, trainrunId); - nonStopV1Time = -1; - nonStop = true; +const buildSectionEdgesFromIterator = ( + tsIterator: TrainrunIterator, + reverse: boolean, + timeLimit: number, +): Edge[] => { + const edges = []; + let nonStopV1Time = -1; + let nonStopV1Node = -1; + while (tsIterator.hasNext()) { + tsIterator.next(); + const ts = tsIterator.current().trainrunSection; + const trainrunId = reverse ? -ts.getTrainrunId() : ts.getTrainrunId(); + const v1Time = reverse + ? ts.getTargetDepartureDto().consecutiveTime + : ts.getSourceDepartureDto().consecutiveTime; + const v1Node = reverse ? ts.getTargetNodeId() : ts.getSourceNodeId(); + // If we don't stop here, we need to remember where we started. + if ( + reverse + ? ts.getSourceNode().isNonStop(ts) + : ts.getTargetNode().isNonStop(ts) + ) { + if (nonStopV1Time === -1) { + nonStopV1Time = v1Time; + nonStopV1Node = v1Node; } - const v2Time = reverse ? ts.getSourceArrivalDto().consecutiveTime : ts.getTargetArrivalDto().consecutiveTime; - const v2Node = reverse ? ts.getSourceNodeId() : ts.getTargetNodeId(); - const v2 = new Vertex(v2Node, false, v2Time, trainrunId); + continue; + } + let v1 = new Vertex(v1Node, true, v1Time, trainrunId); + let nonStop = false; + if (nonStopV1Time !== -1) { + v1 = new Vertex(nonStopV1Node, true, nonStopV1Time, trainrunId); + nonStopV1Time = -1; + nonStop = true; + } + const v2Time = reverse + ? ts.getSourceArrivalDto().consecutiveTime + : ts.getTargetArrivalDto().consecutiveTime; + const v2Node = reverse ? ts.getSourceNodeId() : ts.getTargetNodeId(); + const v2 = new Vertex(v2Node, false, v2Time, trainrunId); - for (let i = 0; i*ts.getTrainrun().getFrequency() < timeLimit; i++) { - const newV1 = new Vertex(v1.nodeId, v1.isDeparture, v1.time+i*ts.getTrainrun().getFrequency(), v1.trainrunId); - const newV2 = new Vertex(v2.nodeId, v2.isDeparture, v2.time+i*ts.getTrainrun().getFrequency(), v2.trainrunId); - const edge = new Edge(newV1, newV2, newV2.time - newV1.time); - edges.push(edge); - } + for (let i = 0; i * ts.getTrainrun().getFrequency() < timeLimit; i++) { + const newV1 = new Vertex( + v1.nodeId, + v1.isDeparture, + v1.time + i * ts.getTrainrun().getFrequency(), + v1.trainrunId, + ); + const newV2 = new Vertex( + v2.nodeId, + v2.isDeparture, + v2.time + i * ts.getTrainrun().getFrequency(), + v2.trainrunId, + ); + const edge = new Edge(newV1, newV2, newV2.time - newV1.time); + edges.push(edge); } - return edges; + } + return edges; }; -const buildConvenienceEdges = (nodes: Node[], verticesDepartureByTrainrunByNode: Map>, - verticesArrivalByTrainrunByNode: Map>): Edge[] => { - const edges = []; - nodes.forEach((node) => { - const nodeId = node.getId(); - // We add a single start and end vertex for each node, so we can compute shortest paths more easily. - const srcVertex = new Vertex(nodeId, true); - const tgtVertex = new Vertex(nodeId, false); - // Going from one node to itself is free. - const edge = new Edge(srcVertex, tgtVertex, 0); - edges.push(edge); - const departuresByTrainrun = verticesDepartureByTrainrunByNode.get(nodeId); - if (departuresByTrainrun !== undefined) { - departuresByTrainrun.forEach((departures, trainrunId) => { - departures.forEach((departure) => { - const edge = new Edge(srcVertex, departure, 0); - edges.push(edge); - }); +const buildConvenienceEdges = ( + nodes: Node[], + verticesDepartureByTrainrunByNode: Map>, + verticesArrivalByTrainrunByNode: Map>, +): Edge[] => { + const edges = []; + nodes.forEach((node) => { + const nodeId = node.getId(); + // We add a single start and end vertex for each node, so we can compute shortest paths more easily. + const srcVertex = new Vertex(nodeId, true); + const tgtVertex = new Vertex(nodeId, false); + // Going from one node to itself is free. + const edge = new Edge(srcVertex, tgtVertex, 0); + edges.push(edge); + const departuresByTrainrun = verticesDepartureByTrainrunByNode.get(nodeId); + if (departuresByTrainrun !== undefined) { + departuresByTrainrun.forEach((departures, trainrunId) => { + departures.forEach((departure) => { + const edge = new Edge(srcVertex, departure, 0); + edges.push(edge); }); - } - const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(nodeId); - if (arrivalsByTrainrun !== undefined) { - arrivalsByTrainrun.forEach((arrivals, trainrunId) => { - arrivals.forEach((arrival) => { - const edge = new Edge(arrival, tgtVertex, 0); - edges.push(edge); - }); + }); + } + const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(nodeId); + if (arrivalsByTrainrun !== undefined) { + arrivalsByTrainrun.forEach((arrivals, trainrunId) => { + arrivals.forEach((arrival) => { + const edge = new Edge(arrival, tgtVertex, 0); + edges.push(edge); }); - } - }); - return edges; + }); + } + }); + return edges; }; -const buildConnectionEdges = (nodes: Node[], verticesDepartureByTrainrunByNode: Map>, - verticesArrivalByTrainrunByNode: Map>, connectionPenalty: number): Edge[] => { - const edges = []; - nodes.forEach((node) => { - const departuresByTrainrun = verticesDepartureByTrainrunByNode.get(node.getId()); - const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get(node.getId()); - if (departuresByTrainrun !== undefined && arrivalsByTrainrun !== undefined) { - arrivalsByTrainrun.forEach((arrivals, arrivalTrainrunId) => { - arrivals.forEach((arrival) => { - departuresByTrainrun.forEach((departures, departureTrainrunId) => { - let minDepartureTime = arrival.time; - if (arrivalTrainrunId !== departureTrainrunId) { - minDepartureTime += node.getConnectionTime(); - } - // For each arrival and for each trainrun available, we only want to consider the first departure. - // This could be a binary search but it does not seem to be worth it. - const departure = departures.find((departure) => {return departure.time >= minDepartureTime;}); - if (departure !== undefined) { - let cost = departure.time - arrival.time; - if (arrivalTrainrunId !== departureTrainrunId) { - cost += connectionPenalty; - } - const edge = new Edge(arrival, departure, cost); - edges.push(edge); - } - }); +const buildConnectionEdges = ( + nodes: Node[], + verticesDepartureByTrainrunByNode: Map>, + verticesArrivalByTrainrunByNode: Map>, + connectionPenalty: number, +): Edge[] => { + const edges = []; + nodes.forEach((node) => { + const departuresByTrainrun = verticesDepartureByTrainrunByNode.get( + node.getId(), + ); + const arrivalsByTrainrun = verticesArrivalByTrainrunByNode.get( + node.getId(), + ); + if ( + departuresByTrainrun !== undefined && + arrivalsByTrainrun !== undefined + ) { + arrivalsByTrainrun.forEach((arrivals, arrivalTrainrunId) => { + arrivals.forEach((arrival) => { + departuresByTrainrun.forEach((departures, departureTrainrunId) => { + let minDepartureTime = arrival.time; + if (arrivalTrainrunId !== departureTrainrunId) { + minDepartureTime += node.getConnectionTime(); + } + // For each arrival and for each trainrun available, we only want to consider the first departure. + // This could be a binary search but it does not seem to be worth it. + const departure = departures.find((departure) => { + return departure.time >= minDepartureTime; }); + if (departure !== undefined) { + let cost = departure.time - arrival.time; + if (arrivalTrainrunId !== departureTrainrunId) { + cost += connectionPenalty; + } + const edge = new Edge(arrival, departure, cost); + edges.push(edge); + } }); - } - }); - return edges; -}; - -const depthFirstSearch = (graph: Map, root: Vertex, visited: Set, res: Vertex[]): void => { - const key = JSON.stringify(root); - visited.add(key); - const neighbors = graph.get(key); - if (neighbors !== undefined) { - neighbors.forEach(([neighbor, weight]) => { - if (!visited.has(JSON.stringify(neighbor))) { - depthFirstSearch(graph, neighbor, visited, res); - } + }); }); } - // Note that the order is important for topological sorting. - res.push(root); + }); + return edges; +}; + +const depthFirstSearch = ( + graph: Map, + root: Vertex, + visited: Set, + res: Vertex[], +): void => { + const key = JSON.stringify(root); + visited.add(key); + const neighbors = graph.get(key); + if (neighbors !== undefined) { + neighbors.forEach(([neighbor, weight]) => { + if (!visited.has(JSON.stringify(neighbor))) { + depthFirstSearch(graph, neighbor, visited, res); + } + }); + } + // Note that the order is important for topological sorting. + res.push(root); }; diff --git a/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts b/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts index b8a3f98c..f46158cb 100644 --- a/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts +++ b/src/integration-testing/netzgrafik.unit.testing.od.matrix.ts @@ -1,1164 +1,1146 @@ import { HaltezeitFachCategories, - LabelRef, LinePatternRefs, NetzgrafikDto, } from "../app/data-structures/business.data.structures"; -import {TrainrunSectionText} from "../app/data-structures/technical.data.structures"; export class NetzgrafikUnitTestingOdMatrix { static getUnitTestNetzgrafik(): NetzgrafikDto { return { - "nodes": [ - { - "id": 11, - "betriebspunktName": "C", - "fullName": "C", - "positionX": 1536, - "positionY": 864, - "ports": [ - { - "id": 14, - "trainrunSectionId": 7, - "positionIndex": 0, - "positionAlignment": 2 - }, - { - "id": 20, - "trainrunSectionId": 10, - "positionIndex": 1, - "positionAlignment": 2 - }, - { - "id": 15, - "trainrunSectionId": 8, - "positionIndex": 2, - "positionAlignment": 2 - }, - { - "id": 21, - "trainrunSectionId": 11, - "positionIndex": 3, - "positionAlignment": 2 - } - ], - "transitions": [ - { - "id": 4, - "port1Id": 20, - "port2Id": 21, - "isNonStopTransit": true - } - ], - "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": [] + nodes: [ + { + id: 11, + betriebspunktName: "C", + fullName: "C", + positionX: 1536, + positionY: 864, + ports: [ + { + id: 14, + trainrunSectionId: 7, + positionIndex: 0, + positionAlignment: 2, + }, + { + id: 20, + trainrunSectionId: 10, + positionIndex: 1, + positionAlignment: 2, + }, + { + id: 15, + trainrunSectionId: 8, + positionIndex: 2, + positionAlignment: 2, + }, + { + id: 21, + trainrunSectionId: 11, + positionIndex: 3, + positionAlignment: 2, + }, + ], + transitions: [ + { + id: 4, + port1Id: 20, + port2Id: 21, + isNonStopTransit: true, + }, + ], + 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, + }, }, - { - "id": 12, - "betriebspunktName": "A", - "fullName": "A", - "positionX": 1120, - "positionY": 672, - "ports": [ - { - "id": 17, - "trainrunSectionId": 9, - "positionIndex": 0, - "positionAlignment": 1 - }, - { - "id": 12, - "trainrunSectionId": 6, - "positionIndex": 0, - "positionAlignment": 2 - }, - { - "id": 13, - "trainrunSectionId": 7, - "positionIndex": 0, - "positionAlignment": 3 - }, - { - "id": 19, - "trainrunSectionId": 10, - "positionIndex": 1, - "positionAlignment": 3 - } - ], - "transitions": [ - { - "id": 3, - "port1Id": 12, - "port2Id": 13, - "isNonStopTransit": false - } - ], - "connections": [], - "resourceId": 13, - "perronkanten": 5, - "connectionTime": 8, - "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": [] + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 12, + betriebspunktName: "A", + fullName: "A", + positionX: 1120, + positionY: 672, + ports: [ + { + id: 17, + trainrunSectionId: 9, + positionIndex: 0, + positionAlignment: 1, + }, + { + id: 12, + trainrunSectionId: 6, + positionIndex: 0, + positionAlignment: 2, + }, + { + id: 13, + trainrunSectionId: 7, + positionIndex: 0, + positionAlignment: 3, + }, + { + id: 19, + trainrunSectionId: 10, + positionIndex: 1, + positionAlignment: 3, + }, + ], + transitions: [ + { + id: 3, + port1Id: 12, + port2Id: 13, + isNonStopTransit: false, + }, + ], + connections: [], + resourceId: 13, + perronkanten: 5, + connectionTime: 8, + 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, + }, }, - { - "id": 13, - "betriebspunktName": "D", - "fullName": "D", - "positionX": 1088, - "positionY": 1088, - "ports": [ - { - "id": 18, - "trainrunSectionId": 9, - "positionIndex": 0, - "positionAlignment": 0 - }, - { - "id": 22, - "trainrunSectionId": 11, - "positionIndex": 0, - "positionAlignment": 3 - } - ], - "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": [] + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 13, + betriebspunktName: "D", + fullName: "D", + positionX: 1088, + positionY: 1088, + ports: [ + { + id: 18, + trainrunSectionId: 9, + positionIndex: 0, + positionAlignment: 0, + }, + { + id: 22, + trainrunSectionId: 11, + positionIndex: 0, + positionAlignment: 3, + }, + ], + 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, + }, }, - { - "id": 14, - "betriebspunktName": "B", - "fullName": "B", - "positionX": 800, - "positionY": 864, - "ports": [ - { - "id": 11, - "trainrunSectionId": 6, - "positionIndex": 0, - "positionAlignment": 3 - }, - { - "id": 16, - "trainrunSectionId": 8, - "positionIndex": 1, - "positionAlignment": 3 - } - ], - "transitions": [], - "connections": [], - "resourceId": 15, - "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": [] - } + symmetryAxis: null, + warnings: null, + labelIds: [], + }, + { + id: 14, + betriebspunktName: "B", + fullName: "B", + positionX: 800, + positionY: 864, + ports: [ + { + id: 11, + trainrunSectionId: 6, + positionIndex: 0, + positionAlignment: 3, + }, + { + id: 16, + trainrunSectionId: 8, + positionIndex: 1, + positionAlignment: 3, + }, + ], + transitions: [], + connections: [], + resourceId: 15, + 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: [], + }, ], - "trainrunSections": [ - { - "id": 6, - "sourceNodeId": 14, - "sourcePortId": 11, - "targetNodeId": 12, - "targetPortId": 12, - "travelTime": { - "time": 2, - "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": 58, - "consecutiveTime": 58, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 2, - "consecutiveTime": 2, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 4, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 898, - "y": 880 - }, - { - "x": 962, - "y": 880 - }, - { - "x": 1054, - "y": 688 - }, - { - "x": 1118, - "y": 688 - } - ], - "textPositions": { - "0": { - "x": 916, - "y": 892 - }, - "1": { - "x": 944, - "y": 868 - }, - "2": { - "x": 1100, - "y": 676 - }, - "3": { - "x": 1072, - "y": 700 - }, - "4": { - "x": 1008, - "y": 772 - }, - "5": { - "x": 1008, - "y": 772 - }, - "6": { - "x": 1008, - "y": 796 - } - } - }, - "warnings": null + trainrunSections: [ + { + id: 6, + sourceNodeId: 14, + sourcePortId: 11, + targetNodeId: 12, + targetPortId: 12, + travelTime: { + time: 2, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, }, - { - "id": 7, - "sourceNodeId": 12, - "sourcePortId": 13, - "targetNodeId": 11, - "targetPortId": 14, - "travelTime": { - "time": 4, - "consecutiveTime": 1, - "lock": true, - "warning": null, - "timeFormatter": null - }, - "sourceDeparture": { - "time": 3, - "consecutiveTime": 3, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "sourceArrival": { - "time": 57, - "consecutiveTime": 57, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetDeparture": { - "time": 53, - "consecutiveTime": 53, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 7, - "consecutiveTime": 7, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 4, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 1218, - "y": 688 - }, - { - "x": 1282, - "y": 688 - }, - { - "x": 1470, - "y": 880 - }, - { - "x": 1534, - "y": 880 - } - ], - "textPositions": { - "0": { - "x": 1236, - "y": 700 - }, - "1": { - "x": 1264, - "y": 676 - }, - "2": { - "x": 1516, - "y": 868 - }, - "3": { - "x": 1488, - "y": 892 - }, - "4": { - "x": 1376, - "y": 772 - }, - "5": { - "x": 1376, - "y": 772 - }, - "6": { - "x": 1376, - "y": 796 - } - } - }, - "warnings": null + sourceDeparture: { + time: 0, + consecutiveTime: 0, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 8, - "sourceNodeId": 11, - "sourcePortId": 15, - "targetNodeId": 14, - "targetPortId": 16, - "travelTime": { - "time": 6, - "consecutiveTime": 1, - "lock": true, - "warning": null, - "timeFormatter": null - }, - "sourceDeparture": { - "time": 0, - "consecutiveTime": 60, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "sourceArrival": { - "time": 0, - "consecutiveTime": 60, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetDeparture": { - "time": 54, - "consecutiveTime": 54, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 6, - "consecutiveTime": 66, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 5, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 1534, - "y": 944 - }, - { - "x": 1470, - "y": 944 - }, - { - "x": 962, - "y": 912 - }, - { - "x": 898, - "y": 912 - } - ], - "textPositions": { - "0": { - "x": 1516, - "y": 932 - }, - "1": { - "x": 1488, - "y": 956 - }, - "2": { - "x": 916, - "y": 924 - }, - "3": { - "x": 944, - "y": 900 - }, - "4": { - "x": 1216, - "y": 916 - }, - "5": { - "x": 1216, - "y": 916 - }, - "6": { - "x": 1216, - "y": 940 - } - } - }, - "warnings": null + sourceArrival: { + time: 0, + consecutiveTime: 60, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 9, - "sourceNodeId": 12, - "sourcePortId": 17, - "targetNodeId": 13, - "targetPortId": 18, - "travelTime": { - "time": 2, - "consecutiveTime": 1, - "lock": true, - "warning": null, - "timeFormatter": null - }, - "sourceDeparture": { - "time": 8, - "consecutiveTime": 8, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "sourceArrival": { - "time": 52, - "consecutiveTime": 52, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetDeparture": { - "time": 50, - "consecutiveTime": 50, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 10, - "consecutiveTime": 10, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 6, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 1136, - "y": 766 - }, - { - "x": 1136, - "y": 830 - }, - { - "x": 1104, - "y": 1022 - }, - { - "x": 1104, - "y": 1086 - } - ], - "textPositions": { - "0": { - "x": 1124, - "y": 784 - }, - "1": { - "x": 1148, - "y": 812 - }, - "2": { - "x": 1116, - "y": 1068 - }, - "3": { - "x": 1092, - "y": 1040 - }, - "4": { - "x": 1108, - "y": 926 - }, - "5": { - "x": 1108, - "y": 926 - }, - "6": { - "x": 1132, - "y": 926 - } - } - }, - "warnings": null + targetDeparture: { + time: 58, + consecutiveTime: 58, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 10, - "sourceNodeId": 12, - "sourcePortId": 19, - "targetNodeId": 11, - "targetPortId": 20, - "travelTime": { - "time": 5, - "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": 55, - "consecutiveTime": 55, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 5, - "consecutiveTime": 5, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 7, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 1218, - "y": 720 - }, - { - "x": 1282, - "y": 720 - }, - { - "x": 1470, - "y": 912 - }, - { - "x": 1534, - "y": 912 - } - ], - "textPositions": { - "0": { - "x": 1236, - "y": 732 - }, - "1": { - "x": 1264, - "y": 708 - }, - "2": { - "x": 1516, - "y": 900 - }, - "3": { - "x": 1488, - "y": 924 - }, - "4": { - "x": 1376, - "y": 804 - }, - "5": { - "x": 1376, - "y": 804 - }, - "6": { - "x": 1376, - "y": 828 - } - } - }, - "warnings": null + targetArrival: { + time: 2, + consecutiveTime: 2, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 11, - "sourceNodeId": 11, - "sourcePortId": 21, - "targetNodeId": 13, - "targetPortId": 22, - "travelTime": { - "time": 4, - "consecutiveTime": 1, - "lock": true, - "warning": null, - "timeFormatter": null - }, - "sourceDeparture": { - "time": 5, - "consecutiveTime": 5, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "sourceArrival": { - "time": 55, - "consecutiveTime": 55, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetDeparture": { - "time": 51, - "consecutiveTime": 51, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "targetArrival": { - "time": 9, - "consecutiveTime": 9, - "lock": false, - "warning": null, - "timeFormatter": null - }, - "numberOfStops": 0, - "trainrunId": 7, - "resourceId": 0, - "specificTrainrunSectionFrequencyId": null, - "path": { - "path": [ - { - "x": 1534, - "y": 976 - }, - { - "x": 1470, - "y": 976 - }, - { - "x": 1250, - "y": 1104 - }, - { - "x": 1186, - "y": 1104 - } - ], - "textPositions": { - "0": { - "x": 1516, - "y": 964 - }, - "1": { - "x": 1488, - "y": 988 - }, - "2": { - "x": 1204, - "y": 1116 - }, - "3": { - "x": 1232, - "y": 1092 - }, - "4": { - "x": 1360, - "y": 1028 - }, - "5": { - "x": 1360, - "y": 1028 - }, - "6": { - "x": 1360, - "y": 1052 - } - } - }, - "warnings": null - } - ], - "trainruns": [ - { - "id": 4, - "name": "1", - "categoryId": 1, - "frequencyId": 3, - "trainrunTimeCategoryId": 0, - "labelIds": [] + numberOfStops: 0, + trainrunId: 4, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 898, + y: 880, + }, + { + x: 962, + y: 880, + }, + { + x: 1054, + y: 688, + }, + { + x: 1118, + y: 688, + }, + ], + textPositions: { + "0": { + x: 916, + y: 892, + }, + "1": { + x: 944, + y: 868, + }, + "2": { + x: 1100, + y: 676, + }, + "3": { + x: 1072, + y: 700, + }, + "4": { + x: 1008, + y: 772, + }, + "5": { + x: 1008, + y: 772, + }, + "6": { + x: 1008, + y: 796, + }, + }, }, - { - "id": 5, - "name": "2", - "categoryId": 3, - "frequencyId": 2, - "trainrunTimeCategoryId": 0, - "labelIds": [] + warnings: null, + }, + { + id: 7, + sourceNodeId: 12, + sourcePortId: 13, + targetNodeId: 11, + targetPortId: 14, + travelTime: { + time: 4, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, }, - { - "id": 6, - "name": "4", - "categoryId": 6, - "frequencyId": 3, - "trainrunTimeCategoryId": 0, - "labelIds": [] + sourceDeparture: { + time: 3, + consecutiveTime: 3, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 7, - "name": "3", - "categoryId": 5, - "frequencyId": 0, - "trainrunTimeCategoryId": 0, - "labelIds": [] - } - ], - "resources": [ - { - "id": 1, - "capacity": 2 + sourceArrival: { + time: 57, + consecutiveTime: 57, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 2, - "capacity": 2 + targetDeparture: { + time: 53, + consecutiveTime: 53, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 3, - "capacity": 2 + targetArrival: { + time: 7, + consecutiveTime: 7, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 4, - "capacity": 2 + numberOfStops: 0, + trainrunId: 4, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 1218, + y: 688, + }, + { + x: 1282, + y: 688, + }, + { + x: 1470, + y: 880, + }, + { + x: 1534, + y: 880, + }, + ], + textPositions: { + "0": { + x: 1236, + y: 700, + }, + "1": { + x: 1264, + y: 676, + }, + "2": { + x: 1516, + y: 868, + }, + "3": { + x: 1488, + y: 892, + }, + "4": { + x: 1376, + y: 772, + }, + "5": { + x: 1376, + y: 772, + }, + "6": { + x: 1376, + y: 796, + }, + }, }, - { - "id": 5, - "capacity": 2 + warnings: null, + }, + { + id: 8, + sourceNodeId: 11, + sourcePortId: 15, + targetNodeId: 14, + targetPortId: 16, + travelTime: { + time: 6, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, }, - { - "id": 6, - "capacity": 2 + sourceDeparture: { + time: 0, + consecutiveTime: 60, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 7, - "capacity": 2 + sourceArrival: { + time: 0, + consecutiveTime: 60, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 8, - "capacity": 2 + targetDeparture: { + time: 54, + consecutiveTime: 54, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 9, - "capacity": 2 + targetArrival: { + time: 6, + consecutiveTime: 66, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 10, - "capacity": 2 + numberOfStops: 0, + trainrunId: 5, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ + { + x: 1534, + y: 944, + }, + { + x: 1470, + y: 944, + }, + { + x: 962, + y: 912, + }, + { + x: 898, + y: 912, + }, + ], + textPositions: { + "0": { + x: 1516, + y: 932, + }, + "1": { + x: 1488, + y: 956, + }, + "2": { + x: 916, + y: 924, + }, + "3": { + x: 944, + y: 900, + }, + "4": { + x: 1216, + y: 916, + }, + "5": { + x: 1216, + y: 916, + }, + "6": { + x: 1216, + y: 940, + }, + }, }, - { - "id": 11, - "capacity": 2 + warnings: null, + }, + { + id: 9, + sourceNodeId: 12, + sourcePortId: 17, + targetNodeId: 13, + targetPortId: 18, + travelTime: { + time: 2, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, }, - { - "id": 12, - "capacity": 2 + sourceDeparture: { + time: 8, + consecutiveTime: 8, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 13, - "capacity": 2 + sourceArrival: { + time: 52, + consecutiveTime: 52, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 14, - "capacity": 2 + targetDeparture: { + time: 50, + consecutiveTime: 50, + lock: false, + warning: null, + timeFormatter: null, }, - { - "id": 15, - "capacity": 2 - } - ], - "metadata": { - "analyticsSettings": { - "originDestinationSettings": { - "connectionPenalty": 5, - } + targetArrival: { + time: 10, + consecutiveTime: 10, + lock: false, + warning: null, + timeFormatter: null, }, - "trainrunCategories": [ + numberOfStops: 0, + trainrunId: 6, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ { - "id": 0, - "order": 0, - "shortName": "EC", - "name": "International", - "colorRef": "EC", - "fachCategory": HaltezeitFachCategories.IPV, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 2, - "nodeHeadwayNonStop": 2, - "sectionHeadway": 2 + x: 1136, + y: 766, }, { - "id": 1, - "order": 1, - "shortName": "IC", - "name": "InterCity", - "colorRef": "IC", - "fachCategory": HaltezeitFachCategories.A, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 2, - "nodeHeadwayNonStop": 2, - "sectionHeadway": 2 + x: 1136, + y: 830, }, { - "id": 2, - "order": 2, - "shortName": "IR", - "name": "InterRegio", - "colorRef": "IR", - "fachCategory": HaltezeitFachCategories.B, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 2, - "nodeHeadwayNonStop": 2, - "sectionHeadway": 2 + x: 1104, + y: 1022, }, { - "id": 3, - "order": 3, - "shortName": "RE", - "name": "RegioExpress", - "colorRef": "RE", - "fachCategory": HaltezeitFachCategories.C, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 2, - "nodeHeadwayNonStop": 2, - "sectionHeadway": 2 + x: 1104, + y: 1086, }, - { - "id": 4, - "order": 4, - "shortName": "S", - "name": "RegioUndSBahnverkehr", - "colorRef": "S", - "fachCategory": HaltezeitFachCategories.D, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 2, - "nodeHeadwayNonStop": 2, - "sectionHeadway": 2 + ], + textPositions: { + "0": { + x: 1124, + y: 784, }, - { - "id": 5, - "order": 5, - "shortName": "GEX", - "name": "GüterExpress", - "colorRef": "GEX", - "fachCategory": HaltezeitFachCategories.Uncategorized, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 3, - "nodeHeadwayNonStop": 3, - "sectionHeadway": 3 + "1": { + x: 1148, + y: 812, + }, + "2": { + x: 1116, + y: 1068, + }, + "3": { + x: 1092, + y: 1040, + }, + "4": { + x: 1108, + y: 926, + }, + "5": { + x: 1108, + y: 926, + }, + "6": { + x: 1132, + y: 926, }, + }, + }, + warnings: null, + }, + { + id: 10, + sourceNodeId: 12, + sourcePortId: 19, + targetNodeId: 11, + targetPortId: 20, + travelTime: { + time: 5, + 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: 55, + consecutiveTime: 55, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 5, + consecutiveTime: 5, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 7, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ { - "id": 6, - "order": 6, - "shortName": "G", - "name": "Güterverkehr", - "colorRef": "G", - "fachCategory": HaltezeitFachCategories.Uncategorized, - "minimalTurnaroundTime": 4, - "nodeHeadwayStop": 3, - "nodeHeadwayNonStop": 3, - "sectionHeadway": 3 - } - ], - "trainrunFrequencies": [ + x: 1218, + y: 720, + }, { - "id": 0, - "order": 0, - "frequency": 15, - "offset": 0, - "shortName": "15", - "name": "verkehrt viertelstündlich", - "linePatternRef": LinePatternRefs.Freq15 + x: 1282, + y: 720, }, { - "id": 1, - "order": 0, - "frequency": 20, - "offset": 0, - "shortName": "20", - "name": "verkehrt im 20 Minuten Takt", - "linePatternRef": LinePatternRefs.Freq20 + x: 1470, + y: 912, }, { - "id": 2, - "order": 0, - "frequency": 30, - "offset": 0, - "shortName": "30", - "name": "verkehrt halbstündlich", - "linePatternRef": LinePatternRefs.Freq30 + x: 1534, + y: 912, + }, + ], + textPositions: { + "0": { + x: 1236, + y: 732, + }, + "1": { + x: 1264, + y: 708, }, + "2": { + x: 1516, + y: 900, + }, + "3": { + x: 1488, + y: 924, + }, + "4": { + x: 1376, + y: 804, + }, + "5": { + x: 1376, + y: 804, + }, + "6": { + x: 1376, + y: 828, + }, + }, + }, + warnings: null, + }, + { + id: 11, + sourceNodeId: 11, + sourcePortId: 21, + targetNodeId: 13, + targetPortId: 22, + travelTime: { + time: 4, + consecutiveTime: 1, + lock: true, + warning: null, + timeFormatter: null, + }, + sourceDeparture: { + time: 5, + consecutiveTime: 5, + lock: false, + warning: null, + timeFormatter: null, + }, + sourceArrival: { + time: 55, + consecutiveTime: 55, + lock: false, + warning: null, + timeFormatter: null, + }, + targetDeparture: { + time: 51, + consecutiveTime: 51, + lock: false, + warning: null, + timeFormatter: null, + }, + targetArrival: { + time: 9, + consecutiveTime: 9, + lock: false, + warning: null, + timeFormatter: null, + }, + numberOfStops: 0, + trainrunId: 7, + resourceId: 0, + specificTrainrunSectionFrequencyId: null, + path: { + path: [ { - "id": 3, - "order": 0, - "frequency": 60, - "offset": 0, - "shortName": "60", - "name": "verkehrt stündlich", - "linePatternRef": LinePatternRefs.Freq60 + x: 1534, + y: 976, }, { - "id": 4, - "order": 0, - "frequency": 120, - "offset": 0, - "shortName": "120", - "name": "verkehrt zweistündlich (gerade)", - "linePatternRef": LinePatternRefs.Freq120 + x: 1470, + y: 976, }, { - "id": 5, - "order": 0, - "frequency": 120, - "offset": 60, - "shortName": "120+", - "name": "verkehrt zweistündlich (ungerade)", - "linePatternRef": LinePatternRefs.Freq120 - } - ], - "trainrunTimeCategories": [ + x: 1250, + y: 1104, + }, { - "id": 0, - "order": 0, - "shortName": "7/24", - "name": "verkehrt uneingeschränkt", - "dayTimeInterval": [], - "weekday": [ - 1, - 2, - 3, - 4, - 5, - 6, - 7 - ], - "linePatternRef": LinePatternRefs.TimeCat7_24 + x: 1186, + y: 1104, + }, + ], + textPositions: { + "0": { + x: 1516, + y: 964, + }, + "1": { + x: 1488, + y: 988, + }, + "2": { + x: 1204, + y: 1116, + }, + "3": { + x: 1232, + y: 1092, + }, + "4": { + x: 1360, + y: 1028, + }, + "5": { + x: 1360, + y: 1028, + }, + "6": { + x: 1360, + y: 1052, }, + }, + }, + warnings: null, + }, + ], + trainruns: [ + { + id: 4, + name: "1", + categoryId: 1, + frequencyId: 3, + trainrunTimeCategoryId: 0, + labelIds: [], + }, + { + id: 5, + name: "2", + categoryId: 3, + frequencyId: 2, + trainrunTimeCategoryId: 0, + labelIds: [], + }, + { + id: 6, + name: "4", + categoryId: 6, + frequencyId: 3, + trainrunTimeCategoryId: 0, + labelIds: [], + }, + { + id: 7, + name: "3", + categoryId: 5, + frequencyId: 0, + trainrunTimeCategoryId: 0, + labelIds: [], + }, + ], + resources: [ + { + id: 1, + capacity: 2, + }, + { + id: 2, + capacity: 2, + }, + { + id: 3, + capacity: 2, + }, + { + id: 4, + capacity: 2, + }, + { + id: 5, + capacity: 2, + }, + { + id: 6, + capacity: 2, + }, + { + id: 7, + capacity: 2, + }, + { + id: 8, + capacity: 2, + }, + { + id: 9, + capacity: 2, + }, + { + id: 10, + capacity: 2, + }, + { + id: 11, + capacity: 2, + }, + { + id: 12, + capacity: 2, + }, + { + id: 13, + capacity: 2, + }, + { + id: 14, + capacity: 2, + }, + { + id: 15, + capacity: 2, + }, + ], + metadata: { + analyticsSettings: { + originDestinationSettings: { + connectionPenalty: 5, + }, + }, + trainrunCategories: [ + { + id: 0, + order: 0, + shortName: "EC", + name: "International", + colorRef: "EC", + fachCategory: HaltezeitFachCategories.IPV, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 2, + nodeHeadwayNonStop: 2, + sectionHeadway: 2, + }, + { + id: 1, + order: 1, + shortName: "IC", + name: "InterCity", + colorRef: "IC", + fachCategory: HaltezeitFachCategories.A, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 2, + nodeHeadwayNonStop: 2, + sectionHeadway: 2, + }, + { + id: 2, + order: 2, + shortName: "IR", + name: "InterRegio", + colorRef: "IR", + fachCategory: HaltezeitFachCategories.B, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 2, + nodeHeadwayNonStop: 2, + sectionHeadway: 2, + }, + { + id: 3, + order: 3, + shortName: "RE", + name: "RegioExpress", + colorRef: "RE", + fachCategory: HaltezeitFachCategories.C, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 2, + nodeHeadwayNonStop: 2, + sectionHeadway: 2, + }, + { + id: 4, + order: 4, + shortName: "S", + name: "RegioUndSBahnverkehr", + colorRef: "S", + fachCategory: HaltezeitFachCategories.D, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 2, + nodeHeadwayNonStop: 2, + sectionHeadway: 2, + }, + { + id: 5, + order: 5, + shortName: "GEX", + name: "GüterExpress", + colorRef: "GEX", + fachCategory: HaltezeitFachCategories.Uncategorized, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 3, + nodeHeadwayNonStop: 3, + sectionHeadway: 3, + }, + { + id: 6, + order: 6, + shortName: "G", + name: "Güterverkehr", + colorRef: "G", + fachCategory: HaltezeitFachCategories.Uncategorized, + minimalTurnaroundTime: 4, + nodeHeadwayStop: 3, + nodeHeadwayNonStop: 3, + sectionHeadway: 3, + }, + ], + trainrunFrequencies: [ + { + id: 0, + order: 0, + frequency: 15, + offset: 0, + shortName: "15", + name: "verkehrt viertelstündlich", + linePatternRef: LinePatternRefs.Freq15, + }, + { + id: 1, + order: 0, + frequency: 20, + offset: 0, + shortName: "20", + name: "verkehrt im 20 Minuten Takt", + linePatternRef: LinePatternRefs.Freq20, + }, + { + id: 2, + order: 0, + frequency: 30, + offset: 0, + shortName: "30", + name: "verkehrt halbstündlich", + linePatternRef: LinePatternRefs.Freq30, + }, + { + id: 3, + order: 0, + frequency: 60, + offset: 0, + shortName: "60", + name: "verkehrt stündlich", + linePatternRef: LinePatternRefs.Freq60, + }, + { + id: 4, + order: 0, + frequency: 120, + offset: 0, + shortName: "120", + name: "verkehrt zweistündlich (gerade)", + linePatternRef: LinePatternRefs.Freq120, + }, + { + id: 5, + order: 0, + frequency: 120, + offset: 60, + shortName: "120+", + name: "verkehrt zweistündlich (ungerade)", + linePatternRef: LinePatternRefs.Freq120, + }, + ], + trainrunTimeCategories: [ + { + id: 0, + order: 0, + shortName: "7/24", + name: "verkehrt uneingeschränkt", + dayTimeInterval: [], + weekday: [1, 2, 3, 4, 5, 6, 7], + linePatternRef: LinePatternRefs.TimeCat7_24, + }, + { + id: 1, + order: 0, + shortName: "HVZ", + name: "verkehrt zur Hauptverkehrszeit", + dayTimeInterval: [ { - "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": LinePatternRefs.TimeCatHVZ + from: 360, + to: 420, }, { - "id": 2, - "order": 0, - "shortName": "zeitweise", - "name": "verkehrt zeitweise", - "dayTimeInterval": [], - "weekday": [], - "linePatternRef": LinePatternRefs.TimeZeitweise - } - ], - "netzgrafikColors": [] + from: 960, + to: 1140, + }, + ], + weekday: [1, 2, 3, 4, 5, 6, 7], + linePatternRef: LinePatternRefs.TimeCatHVZ, + }, + { + id: 2, + order: 0, + shortName: "zeitweise", + name: "verkehrt zeitweise", + dayTimeInterval: [], + weekday: [], + linePatternRef: LinePatternRefs.TimeZeitweise, + }, + ], + netzgrafikColors: [], + }, + freeFloatingTexts: [], + labels: [], + labelGroups: [], + filterData: { + filterSettings: [], }, - "freeFloatingTexts": [], - "labels": [], - "labelGroups": [], - "filterData": { - "filterSettings": [] - } - }; + }; } } diff --git a/src/integration-testing/origin.destination.csv.test.spec.ts b/src/integration-testing/origin.destination.csv.test.spec.ts index d859c4f7..b3f3d467 100644 --- a/src/integration-testing/origin.destination.csv.test.spec.ts +++ b/src/integration-testing/origin.destination.csv.test.spec.ts @@ -11,7 +11,14 @@ import {StammdatenService} from "src/app/services/data/stammdaten.service"; import {TrainrunService} from "src/app/services/data/trainrun.service"; import {TrainrunSectionService} from "src/app/services/data/trainrunsection.service"; import {FilterService} from "src/app/services/ui/filter.service"; -import {buildEdges, computeNeighbors, computeShortestPaths, Edge, topoSort, Vertex} from "src/app/view/util/origin-destination-graph"; +import { + buildEdges, + computeNeighbors, + computeShortestPaths, + Edge, + topoSort, + Vertex, +} from "src/app/view/util/origin-destination-graph"; import {NetzgrafikUnitTestingOdMatrix} from "./netzgrafik.unit.testing.od.matrix"; describe("Origin Destination CSV Test", () => { @@ -28,7 +35,7 @@ describe("Origin Destination CSV Test", () => { let stammdatenService: StammdatenService = null; let noteService: NoteService = null; let netzgrafikColoringService: NetzgrafikColoringService = null; - + beforeEach(() => { resourceService = new ResourceService(); logPublishersService = new LogPublishersService(); @@ -78,29 +85,49 @@ describe("Origin Destination CSV Test", () => { const nodes = nodeService.getNodes(); const trainruns = trainrunService.getTrainruns(); const connectionPenalty = 5; - const timeLimit = 60*10; + const timeLimit = 60 * 10; const start = new Date().getTime(); - const edges = buildEdges(nodes, nodes, trainruns, connectionPenalty, trainrunService, timeLimit); + const edges = buildEdges( + nodes, + nodes, + trainruns, + connectionPenalty, + trainrunService, + timeLimit, + ); const neighbors = computeNeighbors(edges); const vertices = topoSort(neighbors); const res = new Map(); nodes.forEach((origin) => { - computeShortestPaths(origin.getId(), neighbors, vertices).forEach((value, key) => { - res.set([origin.getId(), key].join(","), value); - }); + computeShortestPaths(origin.getId(), neighbors, vertices).forEach( + (value, key) => { + res.set([origin.getId(), key].join(","), value); + }, + ); }); const end = new Date().getTime(); // Note: there may be some other equivalent solutions, depending on connections. // See https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/issues/199 - expect(res).toEqual(new Map([ - ["11,13", [22, 1]], ["11,14", [6, 0]], ["11,12", [4, 0]], ["12,13", [2, 0]], ["12,14", [2, 0]], - ["12,11", [4, 0]], ["13,14", [29, 1]], ["13,11", [22, 1]], ["13,12", [2, 0]], ["14,13", [29, 1]], - ["14,11", [6, 0]], ["14,12", [2, 0]] - ])); + expect(res).toEqual( + new Map([ + ["11,13", [22, 1]], + ["11,14", [6, 0]], + ["11,12", [4, 0]], + ["12,13", [2, 0]], + ["12,14", [2, 0]], + ["12,11", [4, 0]], + ["13,14", [29, 1]], + ["13,11", [22, 1]], + ["13,12", [2, 0]], + ["14,13", [29, 1]], + ["14,11", [6, 0]], + ["14,12", [2, 0]], + ]), + ); // This should be reasonably fast, likely less than 10ms. expect(end - start).toBeLessThan(100); }); @@ -115,23 +142,37 @@ describe("Origin Destination CSV Test", () => { const odNodes = nodeService.getSelectedNodes(); const trainruns = trainrunService.getTrainruns(); const connectionPenalty = 5; - const timeLimit = 60*10; + const timeLimit = 60 * 10; - const edges = buildEdges(nodes, odNodes, trainruns, connectionPenalty, trainrunService, timeLimit); + const edges = buildEdges( + nodes, + odNodes, + trainruns, + connectionPenalty, + trainrunService, + timeLimit, + ); const neighbors = computeNeighbors(edges); const vertices = topoSort(neighbors); const res = new Map(); nodes.forEach((origin) => { - computeShortestPaths(origin.getId(), neighbors, vertices).forEach((value, key) => { - res.set([origin.getId(), key].join(","), value); - }); + computeShortestPaths(origin.getId(), neighbors, vertices).forEach( + (value, key) => { + res.set([origin.getId(), key].join(","), value); + }, + ); }); // Note: there may be some other equivalent solutions, depending on connections. // See https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/issues/199 - expect(res).toEqual(new Map([["13,14", [29, 1]], ["14,13", [29, 1]]])); + expect(res).toEqual( + new Map([ + ["13,14", [29, 1]], + ["14,13", [29, 1]], + ]), + ); }); it("simple path unit test", () => { @@ -159,8 +200,12 @@ describe("Origin Destination CSV Test", () => { expect(topoVertices).toHaveSize(4); edges.forEach((edge) => { - const v1Index = topoVertices.findIndex((value, index, obj) => {return value === edge.v1;}); - const v2Index = topoVertices.findIndex((value, index, obj) => {return value === edge.v2;}); + const v1Index = topoVertices.findIndex((value, index, obj) => { + return value === edge.v1; + }); + const v2Index = topoVertices.findIndex((value, index, obj) => { + return value === edge.v2; + }); expect(v1Index).toBeLessThan(v2Index); }); @@ -190,7 +235,7 @@ describe("Origin Destination CSV Test", () => { const e6 = new Edge(v7, v8, 0); const e7 = new Edge(v8, v9, 10); // connection - const e8 = new Edge(v9, v4, 6+5); + const e8 = new Edge(v9, v4, 6 + 5); // convenience const v10 = new Vertex(1, false); const e9 = new Edge(v3, v10, 0); @@ -214,8 +259,12 @@ describe("Origin Destination CSV Test", () => { const topoVertices = topoSort(neighbors); expect(topoVertices).toHaveSize(11); edges.forEach((edge) => { - const v1Index = topoVertices.findIndex((value, index, obj) => {return value === edge.v1;}); - const v2Index = topoVertices.findIndex((value, index, obj) => {return value === edge.v2;}); + const v1Index = topoVertices.findIndex((value, index, obj) => { + return value === edge.v1; + }); + const v2Index = topoVertices.findIndex((value, index, obj) => { + return value === edge.v2; + }); expect(v1Index).toBeLessThan(v2Index); }); @@ -232,6 +281,6 @@ describe("Origin Destination CSV Test", () => { expect(distances3).toHaveSize(2); expect(distances3.get(1)).toEqual([10, 0]); // connection - expect(distances3.get(2)).toEqual([30+5, 1]); + expect(distances3.get(2)).toEqual([30 + 5, 1]); }); });