Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

front: move markers computation in simulation results map #10726

Merged
merged 1 commit into from
Feb 13, 2025
Merged
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 @@ -2,15 +2,12 @@ import { useEffect, useState, useMemo } from 'react';

import { ChevronLeft, ChevronRight } from '@osrd-project/ui-icons';
import cx from 'classnames';
import type { Position } from 'geojson';
import { useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';

import { type Conflict, type PathfindingResultSuccess } from 'common/api/osrdEditoastApi';
import { type Conflict } from 'common/api/osrdEditoastApi';
import SimulationWarpedMap from 'common/Map/WarpedMap/SimulationWarpedMap';
import ResizableSection from 'common/ResizableSection';
import getPointOnPathCoordinates from 'modules/pathfinding/helpers/getPointOnPathCoordinates';
import getTrackLengthCumulativeSums from 'modules/pathfinding/helpers/getTrackLengthCumulativeSums';
import ManchetteWithSpaceTimeChartWrapper, {
MANCHETTE_WITH_SPACE_TIME_CHART_DEFAULT_HEIGHT,
} from 'modules/simulationResult/components/ManchetteWithSpaceTimeChart/ManchetteWithSpaceTimeChart';
Expand All @@ -29,7 +26,6 @@ import { updateSelectedTrainId } from 'reducers/simulationResults';
import { getTrainIdUsedForProjection } from 'reducers/simulationResults/selectors';
import { useAppDispatch } from 'store';

import { useScenarioContext } from '../hooks/useScenarioContext';
import useSimulationResults from '../hooks/useSimulationResults';
import type { TrainSpaceTimeData } from '../types';

Expand Down Expand Up @@ -59,8 +55,6 @@ const SimulationResults = ({
const { t } = useTranslation('simulation');
const dispatch = useAppDispatch();

const { getTrackSectionsByIds } = useScenarioContext();

const {
selectedTrainSchedule,
selectedTrainRollingStock,
Expand All @@ -73,7 +67,6 @@ const SimulationResults = ({
const trainIdUsedForProjection = useSelector(getTrainIdUsedForProjection);

const [showWarpedMap, setShowWarpedMap] = useState(false);
const [pathItemsCoordinates, setPathItemsCoordinates] = useState<Position[]>();

const [manchetteWithSpaceTimeChartHeight, setManchetteWithSpaceTimeChartHeight] = useState(
MANCHETTE_WITH_SPACE_TIME_CHART_DEFAULT_HEIGHT
Expand All @@ -97,32 +90,6 @@ const SimulationResults = ({
}
}, [projectionData]);

// Compute path items coordinates in order to place them on the map
useEffect(() => {
const getPathItemsCoordinates = async (pathfindingResult: PathfindingResultSuccess) => {
const trackIds = pathfindingResult.track_section_ranges.map((range) => range.track_section);
const tracks = await getTrackSectionsByIds(trackIds);
const tracksLengthCumulativeSums = getTrackLengthCumulativeSums(
pathfindingResult.track_section_ranges
);

const waypointsCoordinates = pathfindingResult.path_item_positions.map((position) =>
getPointOnPathCoordinates(
tracks,
pathfindingResult.track_section_ranges,
tracksLengthCumulativeSums,
position
)
);

setPathItemsCoordinates(waypointsCoordinates);
};

if (path) {
getPathItemsCoordinates(path);
}
}, [path]);

const {
operationalPoints: projectedOperationalPoints,
filteredOperationalPoints,
Expand Down Expand Up @@ -276,8 +243,8 @@ const SimulationResults = ({
trainId: selectedTrainSchedule.id,
startTime: selectedTrainSchedule.start_time,
}}
pathItemsCoordinates={pathItemsCoordinates}
setMapCanvas={setMapCanvas}
pathfindingResult={path}
/>
</div>

Expand Down
52 changes: 52 additions & 0 deletions front/src/common/Map/components/MapMarkers.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import cx from 'classnames';
import type { Position } from 'geojson';
import { Marker } from 'react-map-gl/maplibre';

import destinationIcon from 'assets/pictures/mapMarkers/destination.svg';
import viaIcon from 'assets/pictures/mapMarkers/intermediate-point.svg';
import originIcon from 'assets/pictures/mapMarkers/start.svg';
import { MARKER_TYPE } from 'modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/ItineraryMarkers';

const MARKER_OFFSET: [number, number] = [0, 8];

export type MapMarker = {
coordinates: Position;
pointType: MARKER_TYPE;
};

type MapMarkersProps = {
markers: MapMarker[];
};

const MapMarkers = ({ markers }: MapMarkersProps) =>
markers.map(({ coordinates, pointType }, index) => {
const viaNumber = markers[0].pointType === MARKER_TYPE.VIA ? index + 1 : index;
let imgSrc = viaIcon;
let imgAlt = `via ${viaNumber}`;

if (pointType === MARKER_TYPE.ORIGIN) {
imgSrc = originIcon;
imgAlt = 'origin';
} else if (pointType === MARKER_TYPE.DESTINATION) {
imgSrc = destinationIcon;
imgAlt = 'destination';
}

return (
<Marker
longitude={coordinates[0]}
latitude={coordinates[1]}
anchor="bottom"
offset={MARKER_OFFSET}
>
<img src={imgSrc} alt={imgAlt} />
{pointType === MARKER_TYPE.VIA && (
<span className={cx('map-pathfinding-marker', 'via-number', 'stdcm-via')}>
{viaNumber}
</span>
)}
</Marker>
);
});

export default MapMarkers;
Original file line number Diff line number Diff line change
@@ -1,23 +1,14 @@
import cx from 'classnames';
import type { Feature, LineString, Position } from 'geojson';
import { Marker, Source } from 'react-map-gl/maplibre';
import type { Feature, LineString } from 'geojson';
import { Source } from 'react-map-gl/maplibre';

import destinationIcon from 'assets/pictures/mapMarkers/destination.svg';
import viaIcon from 'assets/pictures/mapMarkers/intermediate-point.svg';
import originIcon from 'assets/pictures/mapMarkers/start.svg';
import OrderedLayer from 'common/Map/Layers/OrderedLayer';

interface RenderItineraryProps {
type ItineraryProps = {
geojsonPath: Feature<LineString>;
pathItemsCoordinates?: Position[];
layerOrder: number;
}
};

export default function RenderItinerary({
geojsonPath,
pathItemsCoordinates,
layerOrder,
}: RenderItineraryProps) {
const Itinerary = ({ geojsonPath, layerOrder }: ItineraryProps) => {
const paintBackgroundLine = {
'line-width': 4,
'line-color': '#EDF9FF',
Expand All @@ -28,48 +19,8 @@ export default function RenderItinerary({
'line-color': '#158DCF',
};

const markerOffset: [number, number] = [0, 8];

if (!pathItemsCoordinates || pathItemsCoordinates.length < 2) {
return null;
}

const [originLongitude, originLatitude] = pathItemsCoordinates.at(0)!;
const [destinationLongitude, destinationLatitude] = pathItemsCoordinates.at(-1)!;
const vias = pathItemsCoordinates.slice(1, -1);

return (
<Source type="geojson" data={geojsonPath}>
<Marker
longitude={originLongitude}
latitude={originLatitude}
anchor="bottom"
offset={markerOffset}
>
<img src={originIcon} alt="origin" />
</Marker>
{vias.map(([longitude, latitude], index) => (
<Marker
key={`via-${index}`}
longitude={longitude}
latitude={latitude}
anchor="bottom"
offset={markerOffset}
>
<img src={viaIcon} alt={`via ${index + 1}`} />
<span className={cx('map-pathfinding-marker', 'via-number', 'stdcm-via')}>
{index + 1}
</span>
</Marker>
))}
<Marker
longitude={destinationLongitude}
latitude={destinationLatitude}
anchor="bottom"
offset={markerOffset}
>
<img src={destinationIcon} alt="destination" />
</Marker>
<OrderedLayer
id="geojsonPathBackgroundLine"
type="line"
Expand All @@ -80,4 +31,6 @@ export default function RenderItinerary({
<OrderedLayer id="geojsonPathLine" type="line" paint={paintLine} layerOrder={layerOrder} />
</Source>
);
}
};

export default Itinerary;
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,30 @@ import bbox from '@turf/bbox';
import { lineString, point } from '@turf/helpers';
import lineLength from '@turf/length';
import lineSlice from '@turf/line-slice';
import type { Position } from 'geojson';
import type { MapLayerMouseEvent } from 'maplibre-gl';
import type { MapRef } from 'react-map-gl/maplibre';
import { useSelector } from 'react-redux';

import captureMap from 'applications/operationalStudies/helpers/captureMap';
import { useScenarioContext } from 'applications/operationalStudies/hooks/useScenarioContext';
import type {
PathPropertiesFormatted,
SimulationResponseSuccess,
} from 'applications/operationalStudies/types';
import type { PathfindingResultSuccess } from 'common/api/osrdEditoastApi';
import BaseMap from 'common/Map/BaseMap';
import MapButtons from 'common/Map/Buttons/MapButtons';
import MapMarkers, { type MapMarker } from 'common/Map/components/MapMarkers';
import TrainOnMap, { type TrainCurrentInfo } from 'common/Map/components/TrainOnMap/TrainOnMap';
import { removeSearchItemMarkersOnMap } from 'common/Map/utils';
import { computeBBoxViewport } from 'common/Map/WarpedMap/core/helpers';
import { useInfraID } from 'common/osrdContext';
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder';
import getPointOnPathCoordinates from 'modules/pathfinding/helpers/getPointOnPathCoordinates';
import getTrackLengthCumulativeSums from 'modules/pathfinding/helpers/getTrackLengthCumulativeSums';
import { MARKER_TYPE } from 'modules/trainschedule/components/ManageTrainSchedule/ManageTrainScheduleMap/ItineraryMarkers';
import { updateViewport, type Viewport } from 'reducers/map';
import { getMap, getTerrain3DExaggeration } from 'reducers/map/selectors';
import { getMap } from 'reducers/map/selectors';
import { getIsPlaying } from 'reducers/simulationResults/selectors';
import { useAppDispatch } from 'store';
import { isoDateWithTimezoneToSec } from 'utils/date';
Expand All @@ -31,35 +36,71 @@ import { kmToM, mmToM, msToKmh } from 'utils/physics';
import getSelectedTrainHoverPositions from './getSelectedTrainHoverPositions';
import { interpolateOnPosition } from '../ChartHelpers/ChartHelpers';
import { useChartSynchronizer } from '../ChartSynchronizer';
import RenderItinerary from './RenderItinerary';
import Itinerary from './RenderItinerary';

const MAP_ID = 'simulation-result-map';

type SimulationResultMapProps = {
pathfindingResult?: PathfindingResultSuccess;
geometry?: PathPropertiesFormatted['geometry'];
trainSimulation?: SimulationResponseSuccess & { trainId: number; startTime: string };
pathItemsCoordinates?: Position[];
setMapCanvas?: (mapCanvas: string) => void;
};

const SimulationResultMap = ({
pathfindingResult,
geometry,
trainSimulation,
pathItemsCoordinates,
setMapCanvas,
}: SimulationResultMapProps) => {
const dispatch = useAppDispatch();

const infraID = useInfraID();
const { viewport, mapSearchMarker, mapStyle, showOSM } = useSelector(getMap);
const { getTrackSectionsByIds } = useScenarioContext();
const { viewport, mapSearchMarker, mapStyle, showOSM, terrain3DExaggeration } =
useSelector(getMap);
const isPlaying = useSelector(getIsPlaying);
const terrain3DExaggeration = useSelector(getTerrain3DExaggeration);

const mapRef = React.useRef<MapRef>(null);
const [selectedTrainHoverPosition, setSelectedTrainHoverPosition] = useState<TrainCurrentInfo>();

const geojsonPath = useMemo(() => geometry && lineString(geometry.coordinates), [geometry]);

const [mapMarkers, setMapMarkers] = useState<MapMarker[]>([]);

// Compute path items coordinates in order to place them on the map
useEffect(() => {
const getPathItemsCoordinates = async (path: PathfindingResultSuccess) => {
const trackIds = path.track_section_ranges.map((range) => range.track_section);
const tracks = await getTrackSectionsByIds(trackIds);
const tracksLengthCumulativeSums = getTrackLengthCumulativeSums(path.track_section_ranges);

const markers = path.path_item_positions.map((position, index) => {
let pointType = MARKER_TYPE.VIA;
if (index === 0) {
pointType = MARKER_TYPE.ORIGIN;
} else if (index === path.path_item_positions.length - 1) {
pointType = MARKER_TYPE.DESTINATION;
}
return {
coordinates: getPointOnPathCoordinates(
tracks,
path.track_section_ranges,
tracksLengthCumulativeSums,
position
),
pointType,
};
});

setMapMarkers(markers);
};

if (pathfindingResult) {
getPathItemsCoordinates(pathfindingResult);
}
}, [pathfindingResult]);

const interactiveLayerIds = useMemo(
() => (geojsonPath ? ['geojsonPath', 'main-train-path'] : []),
[geojsonPath]
Expand Down Expand Up @@ -160,13 +201,14 @@ const SimulationResultMap = ({
terrain3DExaggeration={terrain3DExaggeration}
>
{geojsonPath && (
<RenderItinerary
<Itinerary
geojsonPath={geojsonPath}
layerOrder={LAYER_GROUPS_ORDER[LAYERS.ITINERARY.GROUP]}
pathItemsCoordinates={pathItemsCoordinates}
/>
)}

<MapMarkers markers={mapMarkers} />

{geojsonPath && selectedTrainHoverPosition && trainSimulation && (
<TrainOnMap
trainInfo={selectedTrainHoverPosition}
Expand Down
Loading