Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ const SimulationResults = ({
timetableId,
pathfinding: projectionData?.pathfinding,
projectedOperationalPoints: projectionData?.operationalPoints,
operationalPointReferences: projectionData?.operationalPointReferences,
});

const {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,42 @@ import { useSelector } from 'react-redux';
import { upsertMapWaypointsInOperationalPoints } from 'applications/operationalStudies/helpers/upsertMapWaypointsInOperationalPoints';
import type {
CorePathfindingResultSuccess,
OperationalPointReference,
TrainScheduleResponse,
} from 'common/api/osrdEditoastApi';
import { isStation } from 'modules/pathfinding/utils';
import type { PathOperationalPoint, ProjectionData } from 'modules/simulationResult/types';
import { getProjectionType } from 'reducers/simulationResults/selectors';

import { getWaypointsLocalStorageKey } from './helpers/utils';

function buildOpRefByPosition(
path: TrainScheduleResponse['path'],
pathfinding: CorePathfindingResultSuccess
): Map<number, OperationalPointReference> {
const mapOpRef = new Map<number, OperationalPointReference>();
path.forEach((step, i) => {
if (step.location.type === 'operational_point_part_reference') {
mapOpRef.set(pathfinding.path_item_positions[i], step.location.operational_point);
}
});
return mapOpRef;
}

const useGetProjectedTrainOperationalPoints = ({
infraId,
timetableId,
path,
pathfinding,
projectedOperationalPoints,
operationalPointReferences,
}: {
infraId: number;
timetableId: number | undefined;
path?: TrainScheduleResponse['path'];
pathfinding?: CorePathfindingResultSuccess;
projectedOperationalPoints?: ProjectionData['operationalPoints'];
operationalPointReferences?: OperationalPointReference[];
}) => {
const { t } = useTranslation('operational-studies');
const projectionType = useSelector(getProjectionType);
Expand All @@ -35,54 +52,68 @@ const useGetProjectedTrainOperationalPoints = ({
useState<PathOperationalPoint[]>(operationalPoints);

useEffect(() => {
const getOperationalPoints = async () => {
let operationalPointsWithUniqueIds: PathOperationalPoint[] =
projectedOperationalPoints?.map((op, i) => ({
...omit(op, 'id'),
waypointId: `${op.id}-${op.position}-${i}`,
opId: op.id,
})) || [];

operationalPointsWithUniqueIds =
projectionType === 'trackProjection' && path && pathfinding
? upsertMapWaypointsInOperationalPoints(
'PathOperationalPoint',
path,
pathfinding.path_item_positions,
operationalPointsWithUniqueIds,
t
)
: operationalPointsWithUniqueIds;

setOperationalPoints(operationalPointsWithUniqueIds);

const stringifiedSavedWaypoints = localStorage.getItem(
getWaypointsLocalStorageKey(timetableId, path)
// Successful pathfinding: position → original OP reference for path steps.
// Failed pathfinding: pathfinding is undefined, map stays null (index-based fallback below).
const opRefByPosition = path && pathfinding ? buildOpRefByPosition(path, pathfinding) : null;

let allOps: PathOperationalPoint[] =
projectedOperationalPoints?.map((op, i) => ({
...omit(op, 'id'),
waypointId: `${op.id}-${op.position}-${i}`,
opId: op.id,
// Successful pathfinding: match by position against path steps.
// Failed pathfinding: operationalPointReferences[i] is the original opRef for normalizedOps[i].
opRef: opRefByPosition ? opRefByPosition.get(op.position) : operationalPointReferences?.[i],
})) || [];

if (projectionType === 'trackProjection' && path && pathfinding) {
allOps = upsertMapWaypointsInOperationalPoints(
'PathOperationalPoint',
path,
pathfinding.path_item_positions,
allOps,
t
);
}

setOperationalPoints(allOps);

const opRefByWaypointId = new Map(allOps.map((op) => [op.waypointId, op.opRef]));

const stringifiedSavedWaypoints = localStorage.getItem(
getWaypointsLocalStorageKey(timetableId, path)
);

let filteredOps: PathOperationalPoint[];
if (stringifiedSavedWaypoints) {
filteredOps = (JSON.parse(stringifiedSavedWaypoints) as PathOperationalPoint[]).map((op) => ({
...op,
opRef: opRefByWaypointId.get(op.waypointId),
}));
} else {
// If the manchette hasn't been saved, we want to display by default only
// the waypoints with CH BV/00/'' and the path steps (origin, destination, vias)
const lastIndex = allOps.length - 1;
filteredOps = allOps.filter((op, i) => {
if (i === 0 || i === lastIndex) return true;
// handle waypoints added from the map
if (!op.extensions?.sncf) return true;
// handle waypoints added from the pathfinding or operational points on path
return isStation(op.extensions.sncf.ch) || op.weight === 100;
});
}

if (stringifiedSavedWaypoints) {
operationalPointsWithUniqueIds = JSON.parse(
stringifiedSavedWaypoints
) as PathOperationalPoint[];
} else {
// If the manchette hasn't been saved, we want to display by default only
// the waypoints with CH BV/00/'' and the path steps (origin, destination, vias)

const lastIndex = operationalPointsWithUniqueIds.length - 1;
operationalPointsWithUniqueIds = operationalPointsWithUniqueIds.filter((op, i) => {
if (i === 0 || i === lastIndex) return true;
// handle waypoints added from the map
if (!op.extensions?.sncf) return true;
// handle waypoints added from the pathfinding or operational points on path
return isStation(op.extensions.sncf.ch) || op.weight === 100;
});
}

setFilteredOperationalPoints(operationalPointsWithUniqueIds);
};

getOperationalPoints();
}, [path, pathfinding, infraId, projectedOperationalPoints, timetableId, t, projectionType]);
setFilteredOperationalPoints(filteredOps);
}, [
path,
pathfinding,
infraId,
projectedOperationalPoints,
operationalPointReferences,
timetableId,
t,
projectionType,
]);

return { operationalPoints, filteredOperationalPoints, setFilteredOperationalPoints };
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ function getOperationalPointReference(
op: PathOperationalPoint | undefined
): OperationalPointReference | undefined {
if (!op) return undefined;
// Prefer the original reference from the projection train's path step when available.
if (op.opRef) return op.opRef;
// Only use the opId when it refers to a real infra OP. Virtual OPs (unrecognised, created
// by usePathProjection when pathfinding fails) have a synthetic id like "virtual_op_Zürich"
// and an empty part.track — they must be matched by trigram/uic instead.
Expand Down
2 changes: 2 additions & 0 deletions front/src/modules/simulationResult/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import type {
import type {
PacedTrainException,
CoreSignalUpdate,
OperationalPointReference,
PathProperties,
RollingStockWithLiveries,
SimulationResponseSuccess,
Expand All @@ -30,6 +31,7 @@ export type EditoastPathOperationalPoint = NonNullable<
export type PathOperationalPoint = Omit<EditoastPathOperationalPoint, 'id'> & {
waypointId: string;
opId: string | null;
opRef?: OperationalPointReference;
};

// Space Time Chart
Expand Down
Loading