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: adds warped map in simulation #4995

Merged
merged 15 commits into from
Sep 15, 2023
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
2 changes: 2 additions & 0 deletions front/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"@turf/boolean-point-in-polygon": "^6.3.0",
"@turf/center": "^6.5.0",
"@turf/combine": "^6.5.0",
"@turf/destination": "^6.5.0",
"@turf/distance": "^6.3.0",
"@turf/explode": "^6.5.0",
"@turf/helpers": "^6.3.0",
Expand All @@ -38,6 +39,7 @@
"@turf/line-split": "^6.5.0",
"@turf/nearest-point": "^6.3.0",
"@turf/nearest-point-on-line": "^6.3.0",
"@turf/simplify": "^6.5.0",
"@turf/transform-translate": "^6.5.0",
"@vitejs/plugin-react-swc": "^3.1.0",
"ajv": "^8.12.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ const defineChart = (
drawZone,
originalScaleX,
originalScaleY,
rotate,
};
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import React, { FC, useCallback, useEffect, useState } from 'react';
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useParams } from 'react-router-dom';
import { MapLayerMouseEvent } from 'maplibre-gl';
import WebMercatorViewport from 'viewport-mercator-project';
import ReactMapGL, { AttributionControl, ScaleControl, MapRef } from 'react-map-gl/maplibre';
import ReactMapGL, { AttributionControl, MapRef, ScaleControl } from 'react-map-gl/maplibre';
import { Feature, LineString } from 'geojson';
import { lineString, point, BBox } from '@turf/helpers';
import along from '@turf/along';
import { BBox, lineString, point } from '@turf/helpers';
import bbox from '@turf/bbox';
import lineLength from '@turf/length';
import lineSlice from '@turf/line-slice';
import { last } from 'lodash';
import { keyBy } from 'lodash';

import { updateTimePositionValues } from 'reducers/osrdsimulation/actions';
import { getPresentSimulation, getSelectedTrain } from 'reducers/osrdsimulation/selectors';
import {
AllowancesSettings,
PositionValues,
PositionSpeedTime,
Train,
} from 'reducers/osrdsimulation/types';
import { PositionSpeedTime, Train } from 'reducers/osrdsimulation/types';
import { updateViewport, Viewport } from 'reducers/map';
import { RootState } from 'reducers';
import { TrainPosition } from 'applications/operationalStudies/components/SimulationResults/SimulationResultsMap/types';
Expand Down Expand Up @@ -51,12 +45,11 @@ import TracksSchematic from 'common/Map/Layers/TracksSchematic';
import TrainHoverPosition from 'applications/operationalStudies/components/SimulationResults/SimulationResultsMap/TrainHoverPosition';

import colors from 'common/Map/Consts/colors';
import { datetime2Isostring, datetime2sec, timeString2datetime } from 'utils/timeManipulation';
import { datetime2Isostring } from 'utils/timeManipulation';
import osmBlankStyle from 'common/Map/Layers/osmBlankStyle';
import {
getDirection,
interpolateOnPosition,
interpolateOnTime,
} from 'applications/operationalStudies/components/SimulationResults/ChartHelpers/ChartHelpers';
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder';

Expand All @@ -69,33 +62,12 @@ import { CUSTOM_ATTRIBUTION } from 'common/Map/const';
import { SimulationReport, osrdEditoastApi } from 'common/api/osrdEditoastApi';
import Terrain from 'common/Map/Layers/Terrain';
import { getTerrain3DExaggeration } from 'reducers/map/selectors';

function getPosition(
positionValues: PositionValues,
allowancesSettings?: AllowancesSettings,
trainId?: number,
baseKey?: string
) {
const key = (
allowancesSettings && trainId && allowancesSettings[trainId]?.ecoBlocks
? `eco_${baseKey}`
: baseKey
) as keyof PositionValues;
return positionValues[key] as PositionSpeedTime;
}
import { getRegimeKey, getSimulationHoverPositions } from './SimulationResultsMap/helpers';

interface MapProps {
setExtViewport: (viewport: Viewport) => void;
}

type InterpoledTrain = {
name: string;
id: number;
head_positions?: PositionSpeedTime;
tail_positions?: PositionSpeedTime;
speeds?: PositionSpeedTime;
};

const Map: FC<MapProps> = ({ setExtViewport }) => {
const [mapLoaded, setMapLoaded] = useState(false);
const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM } = useSelector(
Expand All @@ -105,6 +77,7 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
(state: RootState) => state.osrdsimulation
);
const simulation = useSelector(getPresentSimulation);
const trains = useMemo(() => keyBy(simulation.trains, 'id'), [simulation.trains]);
const selectedTrain = useSelector(getSelectedTrain);
const terrain3DExaggeration = useSelector(getTerrain3DExaggeration);
const [geojsonPath, setGeojsonPath] = useState<Feature<LineString>>();
Expand All @@ -124,123 +97,6 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
);
const mapRef = React.useRef<MapRef>(null);

/**
*
* @param {int} trainId
* @returns correct key (eco or base) to get positions in a train simulation
*/
const getRegimeKey = (trainId: number) =>
allowancesSettings && allowancesSettings[trainId]?.ecoBlocks ? 'eco' : 'base';

const createOtherPoints = (): InterpoledTrain[] => {
const timePositionDate = timeString2datetime(timePosition);
let actualTime = 0;
if (timePositionDate instanceof Date) {
actualTime = datetime2sec(timePositionDate);
} else {
console.warn('Try to create Other Train Point from unspecified current time Position');
return [];
}

// First find trains where actual time from position is between start & stop
const concernedTrains: InterpoledTrain[] = [];
simulation.trains.forEach((train, idx: number) => {
const baseOrEco = getRegimeKey(train.id);
const trainRegime = train[baseOrEco];
if (trainRegime && trainRegime.head_positions[0]) {
const trainTime = trainRegime.head_positions[0][0].time;
const train2ndTime = last(last(trainRegime.head_positions))?.time as number;
if (
actualTime >= trainTime &&
actualTime <= train2ndTime &&
train.id !== selectedTrain?.id
) {
const interpolation = interpolateOnTime(
train[baseOrEco],
['time', 'position'],
['head_positions', 'tail_positions', 'speeds'],
actualTime
) as Record<string, PositionSpeedTime>;
if (interpolation.head_positions && interpolation.speeds) {
concernedTrains.push({
...interpolation,
name: train.name,
id: idx,
});
}
}
}
});
return concernedTrains;
};

// specifies the position of the trains when hovering over the simulation
const getSimulationHoverPositions = () => {
if (geojsonPath) {
const line = lineString(geojsonPath.geometry.coordinates);
if (selectedTrain) {
const headPositionRaw = getPosition(
positionValues,
allowancesSettings,
selectedTrain.id,
'headPosition'
);
const tailPositionRaw = getPosition(
positionValues,
allowancesSettings,
selectedTrain.id,
'tailPosition'
);
if (headPositionRaw) {
setTrainHoverPosition(() => {
const headDistanceAlong = headPositionRaw.position / 1000;
const tailDistanceAlong = tailPositionRaw.position / 1000;
const headPosition = along(line, headDistanceAlong, {
units: 'kilometers',
});
const tailPosition = tailPositionRaw
? along(line, tailDistanceAlong, { units: 'kilometers' })
: headPosition;
const trainLength = Math.abs(headDistanceAlong - tailDistanceAlong);
return {
id: 'main-train',
headPosition,
tailPosition,
headDistanceAlong,
tailDistanceAlong,
speedTime: positionValues.speed,
trainLength,
};
});
}
}

// Found trains including timePosition, and organize them with geojson collection of points
setOtherTrainsHoverPosition(
createOtherPoints().map((train) => {
const headDistanceAlong = (train.head_positions?.position ?? 0) / 1000;
const tailDistanceAlong = (train.tail_positions?.position ?? 0) / 1000;
const headPosition = along(line, headDistanceAlong, {
units: 'kilometers',
});
const tailPosition = train.tail_positions
? along(line, tailDistanceAlong, { units: 'kilometers' })
: headPosition;
const trainLength = Math.abs(headDistanceAlong - tailDistanceAlong);
return {
id: `other-train-${train.id}`,
headPosition,
tailPosition,
headDistanceAlong,
tailDistanceAlong,
speedTime: positionValues.speed,
trainLength,
};
})
);
}
};

const zoomToFeature = (boundingBox: BBox) => {
const [minLng, minLat, maxLng, maxLat] = boundingBox;
const viewportTemp = new WebMercatorViewport({ ...viewport, width: 600, height: 400 });
Expand Down Expand Up @@ -372,7 +228,16 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {

useEffect(() => {
if (timePosition && geojsonPath) {
getSimulationHoverPositions();
const positions = getSimulationHoverPositions(
geojsonPath,
simulation,
timePosition,
positionValues,
selectedTrain?.id,
allowancesSettings
);
setTrainHoverPosition(positions.find((train) => train.isSelected));
setOtherTrainsHoverPosition(positions.filter((train) => !train.isSelected));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [timePosition]);
Expand Down Expand Up @@ -431,7 +296,7 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
</>
)}

{/* Have to duplicate objects with sourceLayer to avoid cache problems */}
{/* Have to duplicate objects with sourceLayer to avoid cache problems */}
{mapTrackSources === 'geographic' ? (
<>
<Platforms
Expand Down Expand Up @@ -577,23 +442,31 @@ const Map: FC<MapProps> = ({ setExtViewport }) => {
/>
)}

{geojsonPath && selectedTrainHoverPosition && (
{geojsonPath && selectedTrainHoverPosition && selectedTrain && (
<TrainHoverPosition
point={selectedTrainHoverPosition}
isSelectedTrain
geojsonPath={geojsonPath}
layerOrder={LAYER_GROUPS_ORDER[LAYERS.TRAIN.GROUP]}
viewport={viewport}
train={selectedTrain}
allowancesSettings={allowancesSettings}
/>
)}
{geojsonPath &&
otherTrainsHoverPosition.map((pt) => (
<TrainHoverPosition
point={pt}
geojsonPath={geojsonPath}
key={pt.id}
layerOrder={LAYER_GROUPS_ORDER[LAYERS.TRAIN.GROUP]}
/>
))}
otherTrainsHoverPosition.map((pt) =>
trains[pt.trainId] ? (
<TrainHoverPosition
point={pt}
geojsonPath={geojsonPath}
key={pt.id}
layerOrder={LAYER_GROUPS_ORDER[LAYERS.TRAIN.GROUP]}
train={trains[pt.trainId] as Train}
viewport={viewport}
allowancesSettings={allowancesSettings}
/>
) : null
)}
</ReactMapGL>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { Source, Marker } from 'react-map-gl/maplibre';
import along from '@turf/along';
import lineSliceAlong from '@turf/line-slice-along';
Expand All @@ -11,10 +10,8 @@ import { Feature, LineString } from 'geojson';
import cx from 'classnames';
import { mapValues, get } from 'lodash';

import { RootState } from 'reducers';
import { Viewport } from 'reducers/map';
import { AllowancesSetting } from 'reducers/osrdsimulation/types';
import { getSelectedTrain } from 'reducers/osrdsimulation/selectors';
import { AllowancesSetting, AllowancesSettings, Train } from 'reducers/osrdsimulation/types';
import { sec2time } from 'utils/timeManipulation';
import { boundedValue } from 'utils/numbers';
import { getCurrentBearing } from 'utils/geometry';
Expand Down Expand Up @@ -176,9 +173,12 @@ function getTrainPieces(

interface TrainHoverPositionProps {
point: TrainPosition;
isSelectedTrain?: boolean;
geojsonPath: Feature<LineString>;
layerOrder: number;
train: Train;
viewport: Viewport;
allowancesSettings?: AllowancesSettings;
isSelectedTrain?: boolean;
}

const labelShiftFactor = {
Expand All @@ -187,13 +187,18 @@ const labelShiftFactor = {
};

function TrainHoverPosition(props: TrainHoverPositionProps) {
const { point, isSelectedTrain = false, geojsonPath, layerOrder } = props;
const { allowancesSettings } = useSelector((state: RootState) => state.osrdsimulation);
const { viewport } = useSelector((state: RootState) => state.map);
const selectedTrain = useSelector(getSelectedTrain);
const {
point,
isSelectedTrain = false,
geojsonPath,
layerOrder,
viewport,
train,
allowancesSettings,
} = props;

if (selectedTrain && geojsonPath && point.headDistanceAlong && point.tailDistanceAlong) {
const { ecoBlocks } = get(allowancesSettings, selectedTrain.id, {} as AllowancesSetting);
if (train && geojsonPath && point.headDistanceAlong && point.tailDistanceAlong) {
const { ecoBlocks } = get(allowancesSettings, train.id, {} as AllowancesSetting);
const fill = getFill(isSelectedTrain as boolean, ecoBlocks);
const label = getSpeedAndTimeLabel(isSelectedTrain, ecoBlocks, point);

Expand Down
Loading