Skip to content

Commit

Permalink
front: fix space time chart when first or last waypoint are hidden
Browse files Browse the repository at this point in the history
Filter data from projected path (path and occupancy blocks) and conflicts when the first or last waypoint is hidden.
Allow the space time chart to look as if it started/ended with the first/last displayed waypoint.

Signed-off-by: SharglutDev <p.filimon75@gmail.com>
  • Loading branch information
SharglutDev committed Nov 7, 2024
1 parent d371907 commit cd4cdfb
Show file tree
Hide file tree
Showing 4 changed files with 233 additions and 14 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useRef, useState } from 'react';
import { useMemo, useRef, useState } from 'react';

import { KebabHorizontal } from '@osrd-project/ui-icons';
import { Manchette } from '@osrd-project/ui-manchette';
Expand All @@ -11,12 +11,18 @@ import {
OccupancyBlockLayer,
} from '@osrd-project/ui-spacetimechart';
import type { Conflict } from '@osrd-project/ui-spacetimechart';
import { compact } from 'lodash';

import type { OperationalPoint, TrainSpaceTimeData } from 'applications/operationalStudies/types';
import upward from 'assets/pictures/workSchedules/ScheduledMaintenanceUp.svg';
import type { PostWorkSchedulesProjectPathApiResponse } from 'common/api/osrdEditoastApi';
import cutSpaceTimeRect from 'modules/simulationResult/components/SpaceTimeChart/helpers/utils';
import { ASPECT_LABELS_COLORS } from 'modules/simulationResult/consts';
import type { AspectLabel, WaypointsPanelData } from 'modules/simulationResult/types';
import type {
AspectLabel,
LayerRangeData,
WaypointsPanelData,
} from 'modules/simulationResult/types';

import SettingsPanel from './SettingsPanel';
import ManchetteMenuButton from '../SpaceTimeChart/ManchetteMenuButton';
Expand Down Expand Up @@ -45,9 +51,91 @@ const ManchetteWithSpaceTimeChartWrapper = ({

const [waypointsPanelIsOpen, setWaypointsPanelIsOpen] = useState(false);

// Cut the space time chart curves if the first or last waypoints are hidden
const spaceTimeChartLayersData = useMemo(() => {
let filteredProjectPathTrainResult = projectPathTrainResult;
let filteredConflicts = conflicts;

if (!waypointsPanelData || waypointsPanelData.filteredWaypoints.length < 2)
return { filteredProjectPathTrainResult, filteredConflicts };

const { filteredWaypoints } = waypointsPanelData;
const firstPosition = filteredWaypoints.at(0)!.position;
const lastPosition = filteredWaypoints.at(-1)!.position;

if (firstPosition !== 0 || lastPosition !== operationalPoints.at(-1)!.position) {
filteredProjectPathTrainResult = projectPathTrainResult.map((train) => ({
...train,
space_time_curves: train.space_time_curves.map(({ positions, times }) => {
const cutPositions: number[] = [];
const cutTimes: number[] = [];

for (let i = 1; i < positions.length; i += 1) {
const currentRange: LayerRangeData = {
spaceStart: positions[i - 1],
spaceEnd: positions[i],
timeStart: times[i - 1],
timeEnd: times[i],
};

const interpolatedRange = cutSpaceTimeRect(currentRange, firstPosition, lastPosition);

// TODO : remove reformatting the datas when https://github.com/OpenRailAssociation/osrd-ui/issues/694 is merged
if (!interpolatedRange) continue;

if (i === 1 || cutPositions.length === 0) {
cutPositions.push(interpolatedRange.spaceStart);
cutTimes.push(interpolatedRange.timeStart);
}
cutPositions.push(interpolatedRange.spaceEnd);
cutTimes.push(interpolatedRange.timeEnd);
}

return {
positions: cutPositions,
times: cutTimes,
};
}),
signal_updates: compact(
train.signal_updates.map((signal) => {
const updatedSignalRange = cutSpaceTimeRect(
{
spaceStart: signal.position_start,
spaceEnd: signal.position_end,
timeStart: signal.time_start,
timeEnd: signal.time_end,
},
firstPosition,
lastPosition
);

if (!updatedSignalRange) return null;

// TODO : remove reformatting the datas when https://github.com/OpenRailAssociation/osrd-ui/issues/694 is merged
return {
...signal,
position_start: updatedSignalRange.spaceStart,
position_end: updatedSignalRange.spaceEnd,
time_start: updatedSignalRange.timeStart,
time_end: updatedSignalRange.timeEnd,
};
})
),
}));

filteredConflicts = compact(
conflicts.map((conflict) => cutSpaceTimeRect(conflict, firstPosition, lastPosition))
);

return { filteredProjectPathTrainResult, filteredConflicts };
}

return { filteredProjectPathTrainResult, filteredConflicts };
}, [waypointsPanelData?.filteredWaypoints, projectPathTrainResult, conflicts]);

const { manchetteProps, spaceTimeChartProps, handleScroll } = useManchettesWithSpaceTimeChart(
waypointsPanelData?.filteredWaypoints ?? operationalPoints,
projectPathTrainResult,
spaceTimeChartLayersData.filteredProjectPathTrainResult,
manchetteWithSpaceTimeChartRef,
selectedTrainScheduleId
);
Expand All @@ -58,17 +146,19 @@ const ManchetteWithSpaceTimeChartWrapper = ({
showSignalsStates: false,
});

const occupancyBlocks = projectPathTrainResult.flatMap((train) => {
const departureTime = new Date(train.departure_time).getTime();
const occupancyBlocks = spaceTimeChartLayersData.filteredProjectPathTrainResult.flatMap(
(train) => {
const departureTime = new Date(train.departure_time).getTime();

return train.signal_updates.map((block) => ({
timeStart: departureTime + block.time_start,
timeEnd: departureTime + block.time_end,
spaceStart: block.position_start,
spaceEnd: block.position_end,
color: ASPECT_LABELS_COLORS[block.aspect_label as AspectLabel],
}));
});
return train.signal_updates.map((block) => ({
timeStart: departureTime + block.time_start,
timeEnd: departureTime + block.time_end,
spaceStart: block.position_start,
spaceEnd: block.position_end,
color: ASPECT_LABELS_COLORS[block.aspect_label as AspectLabel],
}));
}
);

return (
<div className="manchette-space-time-chart-wrapper">
Expand Down Expand Up @@ -134,7 +224,9 @@ const ManchetteWithSpaceTimeChartWrapper = ({
imageUrl={upward}
/>
)}
{settings.showConflicts && <ConflictLayer conflicts={conflicts} />}
{settings.showConflicts && (
<ConflictLayer conflicts={spaceTimeChartLayersData.filteredConflicts} />
)}
{settings.showSignalsStates && (
<OccupancyBlockLayer occupancyBlocks={occupancyBlocks} />
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { describe, it, expect } from 'vitest';

import cutSpaceTimeRect from '../utils';

describe('interpolateRange', () => {
it('should return null if the interpolated range ends before the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 1, 3);
expect(interpolatedRange).toBeNull();
});

it('should return null if the interpolated range starts after the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 5, 7);
expect(interpolatedRange).toBeNull();
});

it('should return the same range if its ranges are inside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 2, 7);
expect(interpolatedRange).toEqual(range);
});

it('should return the interpolated range when the start position is outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 200,
};
const interpolatedRange = cutSpaceTimeRect(range, 4, 5);
expect(interpolatedRange).toEqual({
spaceStart: 4,
spaceEnd: 5,
timeStart: 150,
timeEnd: 200,
});
});

it('should return the interpolated range when the end position is is outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 6,
timeStart: 100,
timeEnd: 160,
};
const interpolatedRange = cutSpaceTimeRect(range, 3, 5);
expect(interpolatedRange).toEqual({
spaceStart: 3,
spaceEnd: 5,
timeStart: 100,
timeEnd: 140,
});
});

it('should return the interpolated range when both positions are outside the cut space', () => {
const range = {
spaceStart: 3,
spaceEnd: 6,
timeStart: 100,
timeEnd: 160,
};
const interpolatedRange = cutSpaceTimeRect(range, 4, 5);
expect(interpolatedRange).toEqual({
spaceStart: 4,
spaceEnd: 5,
timeStart: 120,
timeEnd: 140,
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { LayerRangeData } from '../../../types';

const cutSpaceTimeRect = (
range: LayerRangeData,
minSpace: number,
maxSpace: number
): LayerRangeData | null => {
let { timeStart, timeEnd, spaceStart, spaceEnd } = range;

if (spaceEnd <= minSpace || spaceStart >= maxSpace) {
return null;
}

if (spaceStart < minSpace) {
const interpolationFactor = (minSpace - spaceStart) / (spaceEnd - spaceStart);
spaceStart = minSpace;
timeStart += (timeEnd - timeStart) * interpolationFactor;
}

if (spaceEnd > maxSpace) {
const interpolationFactor = (spaceEnd - maxSpace) / (spaceEnd - spaceStart);
spaceEnd = maxSpace;
timeEnd -= (timeEnd - timeStart) * interpolationFactor;
}

return {
spaceStart,
spaceEnd,
timeStart,
timeEnd,
};
};

export default cutSpaceTimeRect;
7 changes: 7 additions & 0 deletions front/src/modules/simulationResult/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ export type WaypointsPanelData = {
projectionPath: TrainScheduleBase['path'];
};

export type LayerRangeData = {
spaceStart: number;
spaceEnd: number;
timeStart: number;
timeEnd: number;
};

export type AspectLabel =
| 'VL'
| '300VL'
Expand Down

0 comments on commit cd4cdfb

Please sign in to comment.