Skip to content

Commit 05dabf5

Browse files
fix lineString crash (#2077)
* fix lineString crash * Prevent out of bound train to cause a crash * fix crash * maxHeight of ref is not defined * typescript * more ts shit * maxheight 2 * check if map is loaded
1 parent 5df5190 commit 05dabf5

File tree

12 files changed

+309
-188
lines changed

12 files changed

+309
-188
lines changed

front/src/applications/osrd/components/Helpers/ChartHelpers.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -258,14 +258,14 @@ export const interpolateOnTime = (dataSimulation, keyValues, listValues, timePos
258258
// If not array of array
259259
if (listValue === 'speed' || listValue === 'speeds') {
260260
if (
261-
dataSimulation[listValue] &&
262-
dataSimulation[listValue][0] &&
263-
timePositionLocal >= dataSimulation[listValue][0][keyValues[0]]
261+
dataSimulation?.[listValue] &&
262+
dataSimulation?.[listValue][0] &&
263+
timePositionLocal >= dataSimulation?.[listValue][0][keyValues[0]]
264264
) {
265265
const index = bisect(dataSimulation[listValue], timePositionLocal, 1);
266266
bisection = [dataSimulation[listValue][index - 1], dataSimulation[listValue][index]];
267267
}
268-
} else if (dataSimulation[listValue]) {
268+
} else if (dataSimulation?.[listValue]) {
269269
// Array of array
270270
dataSimulation[listValue].forEach((section) => {
271271
const index = bisect(section, timePositionLocal, 1);

front/src/applications/osrd/components/OSRDConfMap/RenderItinerary.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ export default function RenderItinerary() {
1818
</Source>
1919
);
2020
}
21-
return '';
21+
return null;
2222
}

front/src/applications/osrd/components/OSRDConfMap/RenderItineraryMarkers.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,5 +54,5 @@ export default function RenderItineraryMarkers() {
5454
});
5555
}
5656

57-
return markers.map((marker) => marker);
57+
return <>{markers.map((marker) => marker)}</>;
5858
}

front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx

+16-29
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,15 @@ import React from 'react';
22
import { useSelector } from 'react-redux';
33
import { Layer, Source, Marker } from 'react-map-gl';
44
import lineSliceAlong from '@turf/line-slice-along';
5-
import { Point, Feature, LineString } from 'geojson';
5+
import length from '@turf/length';
6+
import { Feature, LineString } from 'geojson';
67
import cx from 'classnames';
78

89
import { RootState } from 'reducers';
910
import { datetime2time } from 'utils/timeManipulation';
10-
11-
interface TrainPosition {
12-
id: string;
13-
headPosition: Feature<Point>;
14-
tailPosition: Feature<Point>;
15-
headDistanceAlong: number;
16-
tailDistanceAlong: number;
17-
speedTime: {
18-
speed: number;
19-
time: number;
20-
};
21-
trainLength: number;
22-
}
11+
import { boundedValue } from 'utils/numbers';
12+
import { Viewport } from 'reducers/map';
13+
import { TrainPosition } from './types';
2314

2415
function getFill(isSelectedTrain: boolean, ecoBlocks) {
2516
if (isSelectedTrain) {
@@ -54,27 +45,23 @@ function getSpeedAndTimeLabel(isSelectedTrain, ecoBlocks, point: TrainPosition)
5445
}
5546

5647
// When the train is backward, lineSliceAlong will crash. we need to have head and tail in the right order
57-
function makeDisplayedHeadAndTail(point: TrainPosition) {
48+
export function makeDisplayedHeadAndTail(point: TrainPosition, geojsonPath: Feature<LineString>) {
49+
const pathLength = length(geojsonPath);
5850
const trueHead = Math.max(point.tailDistanceAlong, point.headDistanceAlong);
59-
const trueTail = Math.max(trueHead - point.trainLength, 0);
60-
const head = Math.max(trueHead, trueTail);
61-
const tail = Math.min(trueHead, trueTail);
62-
return { tail, head };
51+
const trueTail = Math.min(point.tailDistanceAlong, point.headDistanceAlong);
52+
return {
53+
head: boundedValue(trueHead, 0, pathLength),
54+
tail: boundedValue(trueTail, 0, pathLength),
55+
};
6356
}
6457

65-
function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(
66-
viewport: {
67-
zoom: number;
68-
transformRequest: (url: string, resourceType: string, urlmap: string) => any;
69-
},
70-
threshold = 12
71-
) {
58+
function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(viewport: Viewport, threshold = 12) {
7259
return 2 ** (threshold - viewport?.zoom);
7360
}
7461

7562
interface TrainHoverPositionProps {
7663
point: TrainPosition;
77-
isSelectedTrain: boolean;
64+
isSelectedTrain?: boolean;
7865
geojsonPath: Feature<LineString>;
7966
}
8067

@@ -91,12 +78,12 @@ function TrainHoverPosition(props: TrainHoverPositionProps) {
9178
const simulation = useSelector((state: RootState) => state.osrdsimulation.simulation.present);
9279
const trainID = simulation.trains[selectedTrain].id;
9380
const { ecoBlocks } = allowancesSettings[trainID];
94-
const fill = getFill(isSelectedTrain, ecoBlocks);
81+
const fill = getFill(isSelectedTrain as boolean, ecoBlocks);
9582
const label = getSpeedAndTimeLabel(isSelectedTrain, ecoBlocks, point);
9683

9784
if (geojsonPath && point.headDistanceAlong && point.tailDistanceAlong) {
9885
const zoomLengthFactor = getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(viewport);
99-
const { tail, head } = makeDisplayedHeadAndTail(point);
86+
const { tail, head } = makeDisplayedHeadAndTail(point, geojsonPath);
10087
const trainGeoJsonPath = lineSliceAlong(geojsonPath, tail, head);
10188

10289
return (
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import { lineString, point, lengthToDegrees } from '@turf/helpers';
2+
3+
import { makeDisplayedHeadAndTail, TrainPosition } from '../TrainHoverPosition';
4+
5+
// test examples are given on a grid with kilometer unit
6+
function convertKmCoordsToDegree(coords) {
7+
return coords.map((v) => lengthToDegrees(v));
8+
}
9+
10+
// test examples are given on a grid with kilometer unit
11+
function pointFromKmCoords(coords) {
12+
return point(convertKmCoordsToDegree(coords));
13+
}
14+
15+
const unitKmSquare = [
16+
convertKmCoordsToDegree([0, 0]),
17+
convertKmCoordsToDegree([1, 0]),
18+
convertKmCoordsToDegree([1, 1]),
19+
convertKmCoordsToDegree([0, 1]),
20+
];
21+
22+
const unitKmLine = [convertKmCoordsToDegree([0, 0]), convertKmCoordsToDegree([1, 0])];
23+
24+
describe('makeDisplayedHeadAndTail', () => {
25+
describe('normal train', () => {
26+
it('should return train head and tail to display', () => {
27+
const trainPosition: TrainPosition = {
28+
id: 'train',
29+
headPosition: pointFromKmCoords([1, 0]),
30+
tailPosition: pointFromKmCoords([0, 0]),
31+
headDistanceAlong: 1,
32+
tailDistanceAlong: 0,
33+
speedTime: { speed: 0, time: 0 },
34+
trainLength: 1,
35+
};
36+
const pathPoints = unitKmSquare;
37+
const pathLineString = lineString(pathPoints);
38+
const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString);
39+
expect(head).toEqual(1);
40+
expect(tail).toEqual(0);
41+
});
42+
});
43+
describe('backward train', () => {
44+
it('should return train head and tail', () => {
45+
const trainPosition: TrainPosition = {
46+
id: 'train',
47+
headPosition: pointFromKmCoords([0, 0]),
48+
tailPosition: pointFromKmCoords([1, 0]),
49+
headDistanceAlong: 1,
50+
tailDistanceAlong: 0,
51+
speedTime: { speed: 0, time: 0 },
52+
trainLength: 1,
53+
};
54+
const pathPoints = unitKmSquare;
55+
const pathLineString = lineString(pathPoints);
56+
const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString);
57+
expect(head).toEqual(1);
58+
expect(tail).toEqual(0);
59+
});
60+
});
61+
describe('train outside of the path', () => {
62+
it('should return train head and tail bounded in path', () => {
63+
const trainPosition: TrainPosition = {
64+
id: 'train',
65+
headPosition: pointFromKmCoords([1, 0]),
66+
tailPosition: pointFromKmCoords([2, 0]),
67+
headDistanceAlong: 2,
68+
tailDistanceAlong: 1,
69+
speedTime: { speed: 0, time: 0 },
70+
trainLength: 1,
71+
};
72+
const pathPoints = unitKmLine;
73+
const pathLineString = lineString(pathPoints);
74+
const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString);
75+
expect(head).toBeCloseTo(1, 6);
76+
expect(tail).toBeCloseTo(1, 6);
77+
});
78+
});
79+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { Point, Feature } from 'geojson';
2+
3+
export interface TrainPosition {
4+
id: string;
5+
headPosition: Feature<Point>;
6+
tailPosition: Feature<Point>;
7+
headDistanceAlong: number;
8+
tailDistanceAlong: number;
9+
speedTime: {
10+
speed: number;
11+
time: number;
12+
};
13+
trainLength: number;
14+
}

front/src/applications/osrd/views/OSRDConfig/Map.js front/src/applications/osrd/views/OSRDConfig/Map.tsx

+51-39
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
1-
import 'common/Map/Map.scss';
2-
31
import React, { useCallback, useEffect, useRef, useState } from 'react';
4-
import ReactMapGL, { AttributionControl, FlyToInterpolator, ScaleControl } from 'react-map-gl';
2+
3+
import ReactMapGL, {
4+
AttributionControl,
5+
FlyToInterpolator,
6+
ScaleControl,
7+
MapRef,
8+
MapEvent,
9+
} from 'react-map-gl';
510
import { lineString as turfLineString, point as turfPoint } from '@turf/helpers';
611
import { useDispatch, useSelector } from 'react-redux';
12+
import turfNearestPointOnLine, { NearestPointOnLine } from '@turf/nearest-point-on-line';
13+
import { Feature, Position, LineString } from 'geojson';
14+
import { useParams } from 'react-router-dom';
15+
16+
import { RootState } from 'reducers';
17+
import { updateFeatureInfoClickOSRD } from 'reducers/osrdconf';
18+
import { updateViewport } from 'reducers/map';
719

820
/* Main data & layers */
921
import Background from 'common/Map/Layers/Background';
@@ -34,26 +46,24 @@ import TracksGeographic from 'common/Map/Layers/TracksGeographic';
3446
import TracksSchematic from 'common/Map/Layers/TracksSchematic';
3547
import colors from 'common/Map/Consts/colors';
3648
import osmBlankStyle from 'common/Map/Layers/osmBlankStyle';
37-
import turfNearestPointOnLine from '@turf/nearest-point-on-line';
38-
import { updateFeatureInfoClickOSRD } from 'reducers/osrdconf';
39-
import { updateViewport } from 'reducers/map';
40-
import { useParams } from 'react-router-dom';
49+
50+
import 'common/Map/Map.scss';
4151

4252
function Map() {
4353
const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM, layersSettings } =
44-
useSelector((state) => state.map);
45-
const [idHover, setIdHover] = useState(undefined);
46-
const [trackSectionHover, setTrackSectionHover] = useState(undefined);
47-
const [lngLatHover, setLngLatHover] = useState(undefined);
48-
const [trackSectionGeoJSON, setTrackSectionGeoJSON] = useState(undefined);
49-
const [snappedPoint, setSnappedPoint] = useState(undefined);
50-
const { urlLat, urlLon, urlZoom, urlBearing, urlPitch } = useParams();
54+
useSelector((state: RootState) => state.map);
55+
const [idHover, setIdHover] = useState();
56+
const [trackSectionHover, setTrackSectionHover] = useState<Feature<any>>();
57+
const [lngLatHover, setLngLatHover] = useState<Position>();
58+
const [trackSectionGeoJSON, setTrackSectionGeoJSON] = useState<LineString>();
59+
const [snappedPoint, setSnappedPoint] = useState<NearestPointOnLine>();
60+
const { urlLat = '', urlLon = '', urlZoom = '', urlBearing = '', urlPitch = '' } = useParams();
5161
const dispatch = useDispatch();
5262
const updateViewportChange = useCallback(
5363
(value) => dispatch(updateViewport(value, undefined)),
5464
[dispatch]
5565
);
56-
const mapRef = useRef(null);
66+
const mapRef = useRef<MapRef>(null);
5767

5868
const scaleControlStyle = {
5969
left: 20,
@@ -63,8 +73,8 @@ function Map() {
6373
const resetPitchBearing = () => {
6474
updateViewportChange({
6575
...viewport,
66-
bearing: parseFloat(0),
67-
pitch: parseFloat(0),
76+
bearing: 0,
77+
pitch: 0,
6878
transitionDuration: 1000,
6979
transitionInterpolator: new FlyToInterpolator(),
7080
});
@@ -94,47 +104,49 @@ function Map() {
94104
}
95105
};
96106

97-
const getGeoJSONFeature = (e) => {
107+
const getGeoJSONFeature = (e: MapEvent) => {
98108
if (
99109
trackSectionHover === undefined ||
100-
e.features[0].properties.id !== trackSectionHover.properties.id
110+
e?.features?.[0].properties.id !== trackSectionHover?.properties?.id
101111
) {
102-
setTrackSectionHover(e.features[0]);
112+
setTrackSectionHover(e?.features?.[0]);
103113
}
104114

105115
// Get GEOJSON of features hovered for snapping
106116
const width = 5;
107117
const height = 5;
108-
const features = mapRef.current.queryRenderedFeatures(
109-
[
110-
[e.point[0] - width / 2, e.point[1] - height / 2],
111-
[e.point[0] + width / 2, e.point[1] + height / 2],
112-
],
113-
{
114-
layers:
115-
mapTrackSources === 'geographic'
116-
? ['chartis/tracks-geo/main']
117-
: ['chartis/tracks-sch/main'],
118+
if (mapRef.current) {
119+
const features = mapRef.current.queryRenderedFeatures(
120+
[
121+
[e.point[0] - width / 2, e.point[1] - height / 2],
122+
[e.point[0] + width / 2, e.point[1] + height / 2],
123+
],
124+
{
125+
layers:
126+
mapTrackSources === 'geographic'
127+
? ['chartis/tracks-geo/main']
128+
: ['chartis/tracks-sch/main'],
129+
}
130+
);
131+
if (features[0] !== undefined) {
132+
setTrackSectionGeoJSON(features[0].geometry);
118133
}
119-
);
120-
if (features[0] !== undefined) {
121-
setTrackSectionGeoJSON(features[0].geometry);
122134
}
123135
};
124136

125-
const onFeatureHover = (e) => {
126-
if (e.features !== null && e.features[0] !== undefined) {
137+
const onFeatureHover = (e: MapEvent) => {
138+
if (e.features !== null && e?.features?.[0] !== undefined) {
127139
getGeoJSONFeature(e);
128140
setIdHover(e.features[0].properties.id);
129-
setLngLatHover(e.lngLat);
141+
setLngLatHover(e?.lngLat);
130142
} else {
131143
setIdHover(undefined);
132144
setSnappedPoint(undefined);
133145
}
134146
};
135147

136148
const defineInteractiveLayers = () => {
137-
const interactiveLayersLocal = [];
149+
const interactiveLayersLocal: Array<string> = [];
138150
if (mapTrackSources === 'geographic') {
139151
interactiveLayersLocal.push('chartis/tracks-geo/main');
140152
if (layersSettings.operationalpoints) {
@@ -215,7 +227,7 @@ function Map() {
215227
{/* Have to duplicate objects with sourceLayer to avoid cache problems in mapbox */}
216228
{mapTrackSources === 'geographic' ? (
217229
<>
218-
<TVDs geomType="geo" colors={colors[mapStyle]} idHover={idHover} />
230+
<TVDs geomType="geo" idHover={idHover} />
219231
<Catenaries geomType="geo" colors={colors[mapStyle]} />
220232
<Platform colors={colors[mapStyle]} />
221233
<TracksGeographic colors={colors[mapStyle]} />
@@ -247,7 +259,7 @@ function Map() {
247259
<RenderItinerary />
248260
<RenderItineraryMarkers />
249261
{mapSearchMarker !== undefined ? (
250-
<SearchMarker data={mapSearchMarker} colors={colors[mapStyle]} />
262+
<SearchMarker data={mapSearchMarker as object} colors={colors[mapStyle]} />
251263
) : null}
252264
{snappedPoint !== undefined ? <SnappedMarker geojson={snappedPoint} /> : null}
253265
</ReactMapGL>

0 commit comments

Comments
 (0)