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..b2c643ff904 --- /dev/null +++ b/front/src/applications/operationalStudies/components/MacroEditor/import.ts @@ -0,0 +1,101 @@ +import type { AppDispatch } from 'store'; +import { osrdEditoastApi } from 'common/api/osrdEditoastApi'; +import type { TrainScheduleResult, SearchResultItemOperationalPoint, SearchQuery } from 'common/api/osrdEditoastApi'; +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), minY = Math.min(...yCoords); + const maxX = Math.max(...xCoords), maxY = Math.max(...yCoords); + const width = maxX - minX, height = maxY - minY; + const scaleX = 800, scaleY = 500; // TODO: grab NGE component size + 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, + }; + }); +}; + +export 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(); + //console.log(trainSchedules); + + // 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(); + 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); + } + } + + const filterChs = ['BV', '00']; // we're only interested in stations + const searchPromise = dispatch(osrdEditoastApi.endpoints.postSearch.initiate({ + 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 = await searchPromise.unwrap(); + //console.log(searchResults); + + 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); + + console.log(nodes); + return nodes; +}; 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 25206ab4e6a..101c0df6661 100644 --- a/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx +++ b/front/src/applications/operationalStudies/views/v2/ScenarioV2.tsx @@ -28,6 +28,7 @@ import { getSpaceTimeChartData, selectProjectionV2 } from './getSimulationResult import ImportTrainScheduleV2 from './ImportTrainScheduleV2'; import ManageTrainScheduleV2 from './ManageTrainScheduleV2'; import SimulationResultsV2 from './SimulationResultsV2'; +import { importTimetable as importTimetableToNGE } from '../../components/MacroEditor/import'; type SimulationParams = { projectId: string; @@ -183,6 +184,13 @@ const ScenarioV2 = () => { [] ); + // TODO: drop this + useEffect(() => { + if (infraId && timetableId) { + importTimetableToNGE(infraId, timetableId, dispatch); + } + }, [infraId, timetableId]); + if (!scenario || !infraId || !timetableId || !timetable) return null; return (