From 431f85d8750d3e33c72fe1823ee6f7b8309eabab Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Thu, 4 Jul 2024 10:18:06 +0200 Subject: [PATCH] front: implement macro editor OP import --- .../components/MacroEditor/import.ts | 130 ++++++++++++++++++ .../components/MacroEditor/types.ts | 28 ++++ .../views/v2/ScenarioV2.tsx | 8 ++ 3 files changed, 166 insertions(+) create mode 100644 front/src/applications/operationalStudies/components/MacroEditor/import.ts create mode 100644 front/src/applications/operationalStudies/components/MacroEditor/types.ts diff --git a/front/src/applications/operationalStudies/components/MacroEditor/import.ts b/front/src/applications/operationalStudies/components/MacroEditor/import.ts new file mode 100644 index 00000000000..4a8023964d6 --- /dev/null +++ b/front/src/applications/operationalStudies/components/MacroEditor/import.ts @@ -0,0 +1,130 @@ +import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import type { SearchResultItemOperationalPoint, SearchPayload } from 'common/api/osrdEditoastApi'; +import type { AppDispatch } from 'store'; + +import type { Node } from './types'; + +const convertGeoCoords = (nodes: Node[]) => { + const xCoords = nodes.map((node) => node.positionX); + const yCoords = nodes.map((node) => node.positionY); + const minX = Math.min(...xCoords); + const minY = Math.min(...yCoords); + const maxX = Math.max(...xCoords); + const maxY = Math.max(...yCoords); + const width = maxX - minX; + const height = maxY - minY; + // TODO: grab NGE component size + const scaleX = 800; + const scaleY = 500; + const padding = 0.1; + + return nodes.map((node) => { + const normalizedX = (node.positionX - minX) / width; + const normalizedY = (node.positionY - minY) / height; + const paddedX = normalizedX * (1 - 2 * padding) + padding; + const paddedY = normalizedY * (1 - 2 * padding) + padding; + return { + ...node, + positionX: scaleX * paddedX, + positionY: scaleY * paddedY, + }; + }); +}; + +const importTimetable = async (infraId: number, timetableId: number, dispatch: AppDispatch) => { + const timetablePromise = dispatch( + osrdEditoastApi.endpoints.getV2TimetableById.initiate({ id: timetableId }) + ); + const { train_ids } = await timetablePromise.unwrap(); + + const trainSchedulesPromise = dispatch( + osrdEditoastApi.endpoints.postV2TrainSchedule.initiate({ + body: { ids: train_ids }, + }) + ); + const trainSchedules = await trainSchedulesPromise.unwrap(); + + // Collect UICs, trigrams and operational point IDs + const pathItems = trainSchedules.map((schedule) => schedule.path).flat(); + const uics = new Set(); + const trigrams = new Set(); + const opIds = new Set(); + // eslint-disable-next-line no-restricted-syntax + for (const item of pathItems) { + if ('uic' in item) { + uics.add(item.uic); + } else if ('trigram' in item) { + trigrams.add(item.trigram); + } else if ('operational_point' in item) { + opIds.add(item.operational_point); + } + } + + if (uics.size === 0 && trigrams.size === 0 && opIds.size === 0) { + return []; + } + + const filterChs = ['BV', '00']; // we're only interested in stations + const searchPayload: SearchPayload = { + object: 'operationalpoint', + query: [ + 'and', + ['=', ['infra_id'], infraId], + ['or', ...filterChs.map((ch) => ['=', ['ch'], ch])], + [ + 'or', + ...[...uics].map((uic) => ['=', ['uic'], uic]), + ...[...trigrams].map((trigram) => ['=', ['trigram'], trigram]), + ...[...opIds].map((id) => ['=', ['obj_id'], id]), + ], + ], + }; + + const searchResults = []; + const pageSize = 100; + let done = false; + for (let page = 1; !done; page += 1) { + const searchPromise = dispatch( + osrdEditoastApi.endpoints.postSearch.initiate({ + page, + pageSize, + searchPayload, + }) + ); + // eslint-disable-next-line no-await-in-loop + const results = await searchPromise.unwrap(); + searchResults.push(...results); + done = results.length < pageSize; + } + + let nodes: Node[] = searchResults.map((searchResult) => { + const op = searchResult as SearchResultItemOperationalPoint; + return { + id: op.obj_id, + betriebspunktName: op.trigram, + fullName: op.name, + positionX: op.geographic.coordinates[0], + positionY: op.geographic.coordinates[1], + ports: [], + transitions: [], + connections: [], + resourceId: 1, + perronkanten: 10, + connectionTime: 0, + trainrunCategoryHaltezeiten: {}, + symmetryAxis: 0, + warnings: [], + labelIds: [], + }; + }); + + nodes = convertGeoCoords(nodes); + + // TODO: remove + // eslint-disable-next-line + console.log(nodes); + + return nodes; +}; + +export default importTimetable; diff --git a/front/src/applications/operationalStudies/components/MacroEditor/types.ts b/front/src/applications/operationalStudies/components/MacroEditor/types.ts new file mode 100644 index 00000000000..e2f6540a279 --- /dev/null +++ b/front/src/applications/operationalStudies/components/MacroEditor/types.ts @@ -0,0 +1,28 @@ +// NGE DTO types, see: +// https://github.com/SchweizerischeBundesbahnen/netzgrafik-editor-frontend/blob/main/src/app/data-structures/business.data.structures.ts + +export type Haltezeit = { + haltezeit: number; + no_halt: boolean; +}; + +export type Node = { + id: string; // TODO: in NGE this is a number + /** Trigram */ + betriebspunktName: string; + fullName: string; + positionX: number; + positionY: number; + ports: unknown[]; + transitions: unknown[]; + connections: unknown[]; + resourceId: number; + /** Number of tracks where train can stop */ + perronkanten: number; + /** Time needed to change train in minutes */ + connectionTime: number; + trainrunCategoryHaltezeiten: { [category: string]: Haltezeit }; + symmetryAxis: number; + warnings: unknown[]; + labelIds: number[]; +}; diff --git a/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx b/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx index 959fe2a8f1c..ea1a55e3581 100644 --- a/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx +++ b/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx @@ -29,6 +29,7 @@ import { getSpaceTimeChartData, selectProjectionV2 } from './getSimulationResult import ImportTrainScheduleV2 from './ImportTrainScheduleV2'; import ManageTrainScheduleV2 from './ManageTrainScheduleV2'; import SimulationResultsV2 from './SimulationResultsV2'; +import importTimetableToNGE from '../../components/MacroEditor/import'; type SimulationParams = { projectId: string; @@ -146,6 +147,13 @@ const ScenarioV2 = () => { [] ); + // TODO: drop this + useEffect(() => { + if (infraId && timetableId) { + importTimetableToNGE(infraId, timetableId, dispatch); + } + }, [infraId, timetableId]); + if (!scenario || !infraId || !timetableId || !timetable) return null; return (