From c211982b70807c1f2cfd6fca4603e62f425e477e Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Tue, 11 Oct 2022 17:55:35 +0200 Subject: [PATCH 1/8] fix lineString crash --- front/src/applications/osrd/views/OSRDSimulation/Map.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/front/src/applications/osrd/views/OSRDSimulation/Map.js b/front/src/applications/osrd/views/OSRDSimulation/Map.js index a4fe6d3f96e..a9fb6e20977 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/Map.js +++ b/front/src/applications/osrd/views/OSRDSimulation/Map.js @@ -228,8 +228,8 @@ function Map(props) { }; const onFeatureHover = (e) => { - if (!isPlaying && e) { - const line = lineString(geojsonPath?.geometry.coordinates); + if (!isPlaying && e && geojsonPath?.geometry?.coordinates) { + const line = lineString(geojsonPath.geometry.coordinates); const cursorPoint = point(e.lngLat); const key = getRegimeKey(simulation.trains[selectedTrain].id); const startCoordinates = getDirection(simulation.trains[selectedTrain][key].head_positions) From 066fdbb477412fdeecf7bbc414cf473e6460a151 Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 13:50:25 +0200 Subject: [PATCH 2/8] Prevent out of bound train to cause a crash --- .../SimulationMap/TrainHoverPosition.tsx | 18 +++-- .../__tests__/TrainHoverPosition.spec.ts | 79 +++++++++++++++++++ front/src/utils/numbers.ts | 10 +++ 3 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 front/src/applications/osrd/components/SimulationMap/__tests__/TrainHoverPosition.spec.ts create mode 100644 front/src/utils/numbers.ts diff --git a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx index 5669ca37f0a..0c4f79e8b98 100644 --- a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx +++ b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx @@ -2,13 +2,15 @@ import React from 'react'; import { useSelector } from 'react-redux'; import { Layer, Source, Marker } from 'react-map-gl'; import lineSliceAlong from '@turf/line-slice-along'; +import length from '@turf/length'; import { Point, Feature, LineString } from 'geojson'; import cx from 'classnames'; import { RootState } from 'reducers'; import { datetime2time } from 'utils/timeManipulation'; +import { boundedValue } from 'utils/numbers'; -interface TrainPosition { +export interface TrainPosition { id: string; headPosition: Feature; tailPosition: Feature; @@ -54,12 +56,14 @@ function getSpeedAndTimeLabel(isSelectedTrain, ecoBlocks, point: TrainPosition) } // When the train is backward, lineSliceAlong will crash. we need to have head and tail in the right order -function makeDisplayedHeadAndTail(point: TrainPosition) { +export function makeDisplayedHeadAndTail(point: TrainPosition, geojsonPath: Feature) { + const pathLength = length(geojsonPath); const trueHead = Math.max(point.tailDistanceAlong, point.headDistanceAlong); - const trueTail = Math.max(trueHead - point.trainLength, 0); - const head = Math.max(trueHead, trueTail); - const tail = Math.min(trueHead, trueTail); - return { tail, head }; + const trueTail = Math.min(point.tailDistanceAlong, point.headDistanceAlong); + return { + head: boundedValue(trueHead, 0, pathLength), + tail: boundedValue(trueTail, 0, pathLength), + }; } function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming( @@ -96,7 +100,7 @@ function TrainHoverPosition(props: TrainHoverPositionProps) { if (geojsonPath && point.headDistanceAlong && point.tailDistanceAlong) { const zoomLengthFactor = getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(viewport); - const { tail, head } = makeDisplayedHeadAndTail(point); + const { tail, head } = makeDisplayedHeadAndTail(point, geojsonPath); const trainGeoJsonPath = lineSliceAlong(geojsonPath, tail, head); return ( diff --git a/front/src/applications/osrd/components/SimulationMap/__tests__/TrainHoverPosition.spec.ts b/front/src/applications/osrd/components/SimulationMap/__tests__/TrainHoverPosition.spec.ts new file mode 100644 index 00000000000..8aab4ed816a --- /dev/null +++ b/front/src/applications/osrd/components/SimulationMap/__tests__/TrainHoverPosition.spec.ts @@ -0,0 +1,79 @@ +import { lineString, point, lengthToDegrees } from '@turf/helpers'; + +import { makeDisplayedHeadAndTail, TrainPosition } from '../TrainHoverPosition'; + +// test examples are given on a grid with kilometer unit +function convertKmCoordsToDegree(coords) { + return coords.map((v) => lengthToDegrees(v)); +} + +// test examples are given on a grid with kilometer unit +function pointFromKmCoords(coords) { + return point(convertKmCoordsToDegree(coords)); +} + +const unitKmSquare = [ + convertKmCoordsToDegree([0, 0]), + convertKmCoordsToDegree([1, 0]), + convertKmCoordsToDegree([1, 1]), + convertKmCoordsToDegree([0, 1]), +]; + +const unitKmLine = [convertKmCoordsToDegree([0, 0]), convertKmCoordsToDegree([1, 0])]; + +describe('makeDisplayedHeadAndTail', () => { + describe('normal train', () => { + it('should return train head and tail to display', () => { + const trainPosition: TrainPosition = { + id: 'train', + headPosition: pointFromKmCoords([1, 0]), + tailPosition: pointFromKmCoords([0, 0]), + headDistanceAlong: 1, + tailDistanceAlong: 0, + speedTime: { speed: 0, time: 0 }, + trainLength: 1, + }; + const pathPoints = unitKmSquare; + const pathLineString = lineString(pathPoints); + const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString); + expect(head).toEqual(1); + expect(tail).toEqual(0); + }); + }); + describe('backward train', () => { + it('should return train head and tail', () => { + const trainPosition: TrainPosition = { + id: 'train', + headPosition: pointFromKmCoords([0, 0]), + tailPosition: pointFromKmCoords([1, 0]), + headDistanceAlong: 1, + tailDistanceAlong: 0, + speedTime: { speed: 0, time: 0 }, + trainLength: 1, + }; + const pathPoints = unitKmSquare; + const pathLineString = lineString(pathPoints); + const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString); + expect(head).toEqual(1); + expect(tail).toEqual(0); + }); + }); + describe('train outside of the path', () => { + it('should return train head and tail bounded in path', () => { + const trainPosition: TrainPosition = { + id: 'train', + headPosition: pointFromKmCoords([1, 0]), + tailPosition: pointFromKmCoords([2, 0]), + headDistanceAlong: 2, + tailDistanceAlong: 1, + speedTime: { speed: 0, time: 0 }, + trainLength: 1, + }; + const pathPoints = unitKmLine; + const pathLineString = lineString(pathPoints); + const { head, tail } = makeDisplayedHeadAndTail(trainPosition, pathLineString); + expect(head).toBeCloseTo(1, 6); + expect(tail).toBeCloseTo(1, 6); + }); + }); +}); diff --git a/front/src/utils/numbers.ts b/front/src/utils/numbers.ts new file mode 100644 index 00000000000..a8e0af129f3 --- /dev/null +++ b/front/src/utils/numbers.ts @@ -0,0 +1,10 @@ +// eslint-disable-next-line import/prefer-default-export +export function boundedValue(value: number, min: number, max: number) { + if (value >= max) { + return max; + } + if (value <= min) { + return min; + } + return value; +} From ba0c15c1f15a8fd5d5742bac8a7f797cfab6d87e Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 14:55:06 +0200 Subject: [PATCH 3/8] fix crash --- .../applications/osrd/components/Helpers/ChartHelpers.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/front/src/applications/osrd/components/Helpers/ChartHelpers.js b/front/src/applications/osrd/components/Helpers/ChartHelpers.js index 18c52b67343..29d0f10503e 100644 --- a/front/src/applications/osrd/components/Helpers/ChartHelpers.js +++ b/front/src/applications/osrd/components/Helpers/ChartHelpers.js @@ -258,14 +258,14 @@ export const interpolateOnTime = (dataSimulation, keyValues, listValues, timePos // If not array of array if (listValue === 'speed' || listValue === 'speeds') { if ( - dataSimulation[listValue] && - dataSimulation[listValue][0] && - timePositionLocal >= dataSimulation[listValue][0][keyValues[0]] + dataSimulation?.[listValue] && + dataSimulation?.[listValue][0] && + timePositionLocal >= dataSimulation?.[listValue][0][keyValues[0]] ) { const index = bisect(dataSimulation[listValue], timePositionLocal, 1); bisection = [dataSimulation[listValue][index - 1], dataSimulation[listValue][index]]; } - } else if (dataSimulation[listValue]) { + } else if (dataSimulation?.[listValue]) { // Array of array dataSimulation[listValue].forEach((section) => { const index = bisect(section, timePositionLocal, 1); From b5bcdcd74716ed44f3317a1aafc02673c8b083e0 Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 15:00:34 +0200 Subject: [PATCH 4/8] maxHeight of ref is not defined --- .../osrd/views/OSRDSimulation/OSRDSimulation.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js b/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js index 03733c80add..9bdf91c07bc 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js +++ b/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js @@ -44,6 +44,13 @@ export const KEY_VALUES_FOR_CONSOLIDATED_SIMULATION = ['time', 'position']; export const timetableURI = '/timetable/'; const MAP_MIN_HEIGHT = 450; +function getMapMaxHeight(timeTableRef) { + if (timeTableRef.current) { + return timeTableRef.current.clientHeight - 42; + } + return 10000; +} + function OSRDSimulation() { const { t } = useTranslation(['translation', 'simulation', 'allowances']); const timeTableRef = useRef(); From e750e98ec7bac7093cd305a96f4c68008b57eacc Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 17:00:50 +0200 Subject: [PATCH 5/8] typescript --- .../components/OSRDConfMap/RenderItinerary.js | 2 +- .../OSRDConfMap/RenderItineraryMarkers.js | 2 +- .../SimulationMap/TrainHoverPosition.tsx | 9 +- .../osrd/views/OSRDConfig/{Map.js => Map.tsx} | 96 +++++++++++-------- front/src/reducers/map.ts | 21 ++-- 5 files changed, 72 insertions(+), 58 deletions(-) rename front/src/applications/osrd/views/OSRDConfig/{Map.js => Map.tsx} (79%) diff --git a/front/src/applications/osrd/components/OSRDConfMap/RenderItinerary.js b/front/src/applications/osrd/components/OSRDConfMap/RenderItinerary.js index 103d96b6f1a..20bb683d99d 100644 --- a/front/src/applications/osrd/components/OSRDConfMap/RenderItinerary.js +++ b/front/src/applications/osrd/components/OSRDConfMap/RenderItinerary.js @@ -18,5 +18,5 @@ export default function RenderItinerary() { ); } - return ''; + return null; } diff --git a/front/src/applications/osrd/components/OSRDConfMap/RenderItineraryMarkers.js b/front/src/applications/osrd/components/OSRDConfMap/RenderItineraryMarkers.js index 7aedb51afa6..d8c29e046f8 100644 --- a/front/src/applications/osrd/components/OSRDConfMap/RenderItineraryMarkers.js +++ b/front/src/applications/osrd/components/OSRDConfMap/RenderItineraryMarkers.js @@ -54,5 +54,5 @@ export default function RenderItineraryMarkers() { }); } - return markers.map((marker) => marker); + return <>{markers.map((marker) => marker)}; } diff --git a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx index 0c4f79e8b98..081be038a7f 100644 --- a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx +++ b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx @@ -9,6 +9,7 @@ import cx from 'classnames'; import { RootState } from 'reducers'; import { datetime2time } from 'utils/timeManipulation'; import { boundedValue } from 'utils/numbers'; +import { Viewport } from 'reducers/map'; export interface TrainPosition { id: string; @@ -66,13 +67,7 @@ export function makeDisplayedHeadAndTail(point: TrainPosition, geojsonPath: Feat }; } -function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming( - viewport: { - zoom: number; - transformRequest: (url: string, resourceType: string, urlmap: string) => any; - }, - threshold = 12 -) { +function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(viewport: Viewport, threshold = 12) { return 2 ** (threshold - viewport?.zoom); } diff --git a/front/src/applications/osrd/views/OSRDConfig/Map.js b/front/src/applications/osrd/views/OSRDConfig/Map.tsx similarity index 79% rename from front/src/applications/osrd/views/OSRDConfig/Map.js rename to front/src/applications/osrd/views/OSRDConfig/Map.tsx index d92f52f0613..643bebf11a3 100644 --- a/front/src/applications/osrd/views/OSRDConfig/Map.js +++ b/front/src/applications/osrd/views/OSRDConfig/Map.tsx @@ -1,9 +1,21 @@ -import 'common/Map/Map.scss'; - import React, { useCallback, useEffect, useRef, useState } from 'react'; -import ReactMapGL, { AttributionControl, FlyToInterpolator, ScaleControl } from 'react-map-gl'; + +import ReactMapGL, { + AttributionControl, + FlyToInterpolator, + ScaleControl, + MapRef, + MapEvent, +} from 'react-map-gl'; import { lineString as turfLineString, point as turfPoint } from '@turf/helpers'; import { useDispatch, useSelector } from 'react-redux'; +import turfNearestPointOnLine, { NearestPointOnLine } from '@turf/nearest-point-on-line'; +import { Feature, Position, LineString } from 'geojson'; +import { useParams } from 'react-router-dom'; + +import { RootState } from 'reducers'; +import { updateFeatureInfoClickOSRD } from 'reducers/osrdconf'; +import { updateViewport } from 'reducers/map'; /* Main data & layers */ import Background from 'common/Map/Layers/Background'; @@ -34,26 +46,28 @@ import TracksGeographic from 'common/Map/Layers/TracksGeographic'; import TracksSchematic from 'common/Map/Layers/TracksSchematic'; import colors from 'common/Map/Consts/colors'; import osmBlankStyle from 'common/Map/Layers/osmBlankStyle'; -import turfNearestPointOnLine from '@turf/nearest-point-on-line'; -import { updateFeatureInfoClickOSRD } from 'reducers/osrdconf'; -import { updateViewport } from 'reducers/map'; -import { useParams } from 'react-router-dom'; -function Map() { +import 'common/Map/Map.scss'; + +interface MapProps { + setMapLoaded: (boolean) => void; +} + +function Map({ setMapLoaded }: MapProps) { const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM, layersSettings } = - useSelector((state) => state.map); - const [idHover, setIdHover] = useState(undefined); - const [trackSectionHover, setTrackSectionHover] = useState(undefined); - const [lngLatHover, setLngLatHover] = useState(undefined); - const [trackSectionGeoJSON, setTrackSectionGeoJSON] = useState(undefined); - const [snappedPoint, setSnappedPoint] = useState(undefined); - const { urlLat, urlLon, urlZoom, urlBearing, urlPitch } = useParams(); + useSelector((state: RootState) => state.map); + const [idHover, setIdHover] = useState(); + const [trackSectionHover, setTrackSectionHover] = useState>(); + const [lngLatHover, setLngLatHover] = useState(); + const [trackSectionGeoJSON, setTrackSectionGeoJSON] = useState(); + const [snappedPoint, setSnappedPoint] = useState(); + const { urlLat = '', urlLon = '', urlZoom = '', urlBearing = '', urlPitch = '' } = useParams(); const dispatch = useDispatch(); const updateViewportChange = useCallback( (value) => dispatch(updateViewport(value, undefined)), [dispatch] ); - const mapRef = useRef(null); + const mapRef = useRef(null); const scaleControlStyle = { left: 20, @@ -63,8 +77,8 @@ function Map() { const resetPitchBearing = () => { updateViewportChange({ ...viewport, - bearing: parseFloat(0), - pitch: parseFloat(0), + bearing: 0, + pitch: 0, transitionDuration: 1000, transitionInterpolator: new FlyToInterpolator(), }); @@ -94,39 +108,41 @@ function Map() { } }; - const getGeoJSONFeature = (e) => { + const getGeoJSONFeature = (e: MapEvent) => { if ( trackSectionHover === undefined || - e.features[0].properties.id !== trackSectionHover.properties.id + e?.features?.[0].properties.id !== trackSectionHover?.properties?.id ) { - setTrackSectionHover(e.features[0]); + setTrackSectionHover(e?.features?.[0]); } // Get GEOJSON of features hovered for snapping const width = 5; const height = 5; - const features = mapRef.current.queryRenderedFeatures( - [ - [e.point[0] - width / 2, e.point[1] - height / 2], - [e.point[0] + width / 2, e.point[1] + height / 2], - ], - { - layers: - mapTrackSources === 'geographic' - ? ['chartis/tracks-geo/main'] - : ['chartis/tracks-sch/main'], + if (mapRef.current) { + const features = mapRef.current.queryRenderedFeatures( + [ + [e.point[0] - width / 2, e.point[1] - height / 2], + [e.point[0] + width / 2, e.point[1] + height / 2], + ], + { + layers: + mapTrackSources === 'geographic' + ? ['chartis/tracks-geo/main'] + : ['chartis/tracks-sch/main'], + } + ); + if (features[0] !== undefined) { + setTrackSectionGeoJSON(features[0].geometry); } - ); - if (features[0] !== undefined) { - setTrackSectionGeoJSON(features[0].geometry); } }; - const onFeatureHover = (e) => { - if (e.features !== null && e.features[0] !== undefined) { + const onFeatureHover = (e: MapEvent) => { + if (e.features !== null && e?.features?.[0] !== undefined) { getGeoJSONFeature(e); setIdHover(e.features[0].properties.id); - setLngLatHover(e.lngLat); + setLngLatHover(e?.lngLat); } else { setIdHover(undefined); setSnappedPoint(undefined); @@ -134,7 +150,7 @@ function Map() { }; const defineInteractiveLayers = () => { - const interactiveLayersLocal = []; + const interactiveLayersLocal: Array = []; if (mapTrackSources === 'geographic') { interactiveLayersLocal.push('chartis/tracks-geo/main'); if (layersSettings.operationalpoints) { @@ -211,7 +227,7 @@ function Map() { {/* Have to duplicate objects with sourceLayer to avoid cache problems in mapbox */} {mapTrackSources === 'geographic' ? ( <> - + @@ -243,7 +259,7 @@ function Map() { {mapSearchMarker !== undefined ? ( - + ) : null} {snappedPoint !== undefined ? : null} diff --git a/front/src/reducers/map.ts b/front/src/reducers/map.ts index 4f8a4171cc5..7239ed9c192 100644 --- a/front/src/reducers/map.ts +++ b/front/src/reducers/map.ts @@ -1,4 +1,5 @@ /* eslint-disable default-case */ +import { MapRequest } from 'react-map-gl'; import produce from 'immer'; import { transformRequest, gpsRound } from 'utils/helpers'; import history from 'main/history'; @@ -17,6 +18,14 @@ export const UPDATE_FEATURE_INFO_CLICK = 'map/UPDATE_FEATURE_INFO_CLICK'; export const UPDATE_LAYERS_SETTINGS = 'osrdconf/UPDATE_LAYERS_SETTINGS'; export const UPDATE_SIGNALS_SETTINGS = 'osrdconf/UPDATE_SIGNALS_SETTINGS'; +export interface Viewport { + latitude: number; + longitude: number; + zoom: number; + bearing: number; + pitch: number; + transformRequest: (url?: string, resourceType?: string) => MapRequest; +} export interface MapState { ref: unknown; url: typeof MAP_URL; @@ -24,14 +33,7 @@ export interface MapState { mapTrackSources: string; showOSM: boolean; showOSMtracksections: boolean; - viewport: { - latitude: number; - longitude: number; - zoom: number; - bearing: number; - pitch: number; - transformRequest: (url: string, resourceType: string, urlmap: string) => any; - }; + viewport: Viewport; featureInfoHoverID: unknown; featureInfoClickID: unknown; featureSource: unknown; @@ -70,7 +72,8 @@ export const initialState: MapState = { zoom: 6.2, bearing: 0, pitch: 0, - transformRequest: (url, resourceType) => transformRequest(url, resourceType, MAP_URL as string), + transformRequest: (url, resourceType) => + transformRequest(url as string, resourceType as string, MAP_URL as string), }, featureInfoHoverID: undefined, featureInfoClickID: undefined, From 8eab3678a5f869c49c7266746add09f45f89461c Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 17:47:58 +0200 Subject: [PATCH 6/8] more ts shit --- .../SimulationMap/TrainHoverPosition.tsx | 20 +- .../osrd/components/SimulationMap/types.ts | 14 ++ .../osrd/views/OSRDConfig/Map.tsx | 2 +- .../views/OSRDSimulation/{Map.js => Map.tsx} | 193 +++++++++--------- front/src/reducers/osrdsimulation.ts | 7 +- 5 files changed, 119 insertions(+), 117 deletions(-) create mode 100644 front/src/applications/osrd/components/SimulationMap/types.ts rename front/src/applications/osrd/views/OSRDSimulation/{Map.js => Map.tsx} (73%) diff --git a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx index 081be038a7f..55cbad5979a 100644 --- a/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx +++ b/front/src/applications/osrd/components/SimulationMap/TrainHoverPosition.tsx @@ -3,26 +3,14 @@ import { useSelector } from 'react-redux'; import { Layer, Source, Marker } from 'react-map-gl'; import lineSliceAlong from '@turf/line-slice-along'; import length from '@turf/length'; -import { Point, Feature, LineString } from 'geojson'; +import { Feature, LineString } from 'geojson'; import cx from 'classnames'; import { RootState } from 'reducers'; import { datetime2time } from 'utils/timeManipulation'; import { boundedValue } from 'utils/numbers'; import { Viewport } from 'reducers/map'; - -export interface TrainPosition { - id: string; - headPosition: Feature; - tailPosition: Feature; - headDistanceAlong: number; - tailDistanceAlong: number; - speedTime: { - speed: number; - time: number; - }; - trainLength: number; -} +import { TrainPosition } from './types'; function getFill(isSelectedTrain: boolean, ecoBlocks) { if (isSelectedTrain) { @@ -73,7 +61,7 @@ function getLengthFactorToKeepLabelPlacedCorrectlyWhenZooming(viewport: Viewport interface TrainHoverPositionProps { point: TrainPosition; - isSelectedTrain: boolean; + isSelectedTrain?: boolean; geojsonPath: Feature; } @@ -90,7 +78,7 @@ function TrainHoverPosition(props: TrainHoverPositionProps) { const simulation = useSelector((state: RootState) => state.osrdsimulation.simulation.present); const trainID = simulation.trains[selectedTrain].id; const { ecoBlocks } = allowancesSettings[trainID]; - const fill = getFill(isSelectedTrain, ecoBlocks); + const fill = getFill(isSelectedTrain as boolean, ecoBlocks); const label = getSpeedAndTimeLabel(isSelectedTrain, ecoBlocks, point); if (geojsonPath && point.headDistanceAlong && point.tailDistanceAlong) { diff --git a/front/src/applications/osrd/components/SimulationMap/types.ts b/front/src/applications/osrd/components/SimulationMap/types.ts new file mode 100644 index 00000000000..5bb90d87631 --- /dev/null +++ b/front/src/applications/osrd/components/SimulationMap/types.ts @@ -0,0 +1,14 @@ +import { Point, Feature } from 'geojson'; + +export interface TrainPosition { + id: string; + headPosition: Feature; + tailPosition: Feature; + headDistanceAlong: number; + tailDistanceAlong: number; + speedTime: { + speed: number; + time: number; + }; + trainLength: number; +} diff --git a/front/src/applications/osrd/views/OSRDConfig/Map.tsx b/front/src/applications/osrd/views/OSRDConfig/Map.tsx index 643bebf11a3..697382d9a85 100644 --- a/front/src/applications/osrd/views/OSRDConfig/Map.tsx +++ b/front/src/applications/osrd/views/OSRDConfig/Map.tsx @@ -259,7 +259,7 @@ function Map({ setMapLoaded }: MapProps) { {mapSearchMarker !== undefined ? ( - + ) : null} {snappedPoint !== undefined ? : null} diff --git a/front/src/applications/osrd/views/OSRDSimulation/Map.js b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx similarity index 73% rename from front/src/applications/osrd/views/OSRDSimulation/Map.js rename to front/src/applications/osrd/views/OSRDSimulation/Map.tsx index 2e3823e47ce..0ece34b4058 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/Map.js +++ b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx @@ -6,9 +6,11 @@ import ReactMapGL, { FlyToInterpolator, ScaleControl, WebMercatorViewport, + MapRef, } from 'react-map-gl'; import PropTypes from 'prop-types'; -import { lineString, point } from '@turf/helpers'; +import { Feature, LineString } from 'geojson'; +import { lineString, point, feature } from '@turf/helpers'; import along from '@turf/along'; import bbox from '@turf/bbox'; import lineLength from '@turf/length'; @@ -17,6 +19,8 @@ import { CgLoadbar } from 'react-icons/cg'; import { updateTimePositionValues } from 'reducers/osrdsimulation'; import { updateViewport } from 'reducers/map'; +import { RootState } from 'reducers'; +import { TrainPosition } from 'applications/osrd/components/SimulationMap/types'; /* Main data & layers */ import Background from 'common/Map/Layers/Background'; @@ -68,21 +72,21 @@ function checkIfEcoAndAddPrefix(allowancesSettings, id, baseKey) { function Map(props) { const { setExtViewport } = props; const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM, layersSettings } = - useSelector((state) => state.map); + useSelector((state: RootState) => state.map); const { isPlaying, selectedTrain, positionValues, timePosition, allowancesSettings } = - useSelector((state) => state.osrdsimulation); - const simulation = useSelector((state) => state.osrdsimulation.simulation.present); - const [geojsonPath, setGeojsonPath] = useState(undefined); - const [selectedTrainHoverPosition, setTrainHoverPosition] = useState(undefined); - const [otherTrainsHoverPosition, setOtherTrainsHoverPosition] = useState([]); + useSelector((state: RootState) => state.osrdsimulation); + const simulation = useSelector((state: RootState) => state.osrdsimulation.simulation.present); + const [geojsonPath, setGeojsonPath] = useState>(); + const [selectedTrainHoverPosition, setTrainHoverPosition] = useState(); + const [otherTrainsHoverPosition, setOtherTrainsHoverPosition] = useState([]); const [idHover, setIdHover] = useState(undefined); - const { urlLat, urlLon, urlZoom, urlBearing, urlPitch } = useParams(); + const { urlLat = '', urlLon = '', urlZoom = '', urlBearing = '', urlPitch = '' } = useParams(); const dispatch = useDispatch(); const updateViewportChange = useCallback( (value) => dispatch(updateViewport(value, undefined)), [dispatch] ); - const mapRef = React.useRef(); + const mapRef = React.useRef(null); /** * @@ -95,7 +99,7 @@ function Map(props) { const createOtherPoints = () => { const actualTime = datetime2sec(timePosition); // First find trains where actual time from position is between start & stop - const concernedTrains = []; + const concernedTrains: any[] = []; simulation.trains.forEach((train, idx) => { const key = getRegimeKey(train.id); if ( @@ -126,56 +130,58 @@ function Map(props) { // specifies the position of the trains when hovering over the simulation const getSimulationHoverPositions = () => { - const line = lineString(geojsonPath.geometry.coordinates); - const id = simulation.trains[selectedTrain]?.id; - const headKey = checkIfEcoAndAddPrefix(allowancesSettings, id, 'headPosition'); - const tailKey = checkIfEcoAndAddPrefix(allowancesSettings, id, 'tailPosition'); - if (positionValues[headKey]) { - setTrainHoverPosition(() => { - const headDistanceAlong = positionValues[headKey].position / 1000; - const tailDistanceAlong = positionValues[tailKey].position / 1000; - const headPosition = along(line, headDistanceAlong, { - units: 'kilometers', + if (geojsonPath) { + const line = lineString(geojsonPath.geometry.coordinates); + const id = simulation.trains[selectedTrain]?.id; + const headKey = checkIfEcoAndAddPrefix(allowancesSettings, id, 'headPosition'); + const tailKey = checkIfEcoAndAddPrefix(allowancesSettings, id, 'tailPosition'); + if (positionValues[headKey]) { + setTrainHoverPosition(() => { + const headDistanceAlong = positionValues[headKey].position / 1000; + const tailDistanceAlong = positionValues[tailKey].position / 1000; + const headPosition = along(line, headDistanceAlong, { + units: 'kilometers', + }); + const tailPosition = positionValues[tailKey] + ? along(line, tailDistanceAlong, { units: 'kilometers' }) + : headPosition; + const trainLength = Math.abs(headDistanceAlong - tailDistanceAlong); + return { + id: 'main-train', + headPosition, + tailPosition, + headDistanceAlong, + tailDistanceAlong, + speedTime: positionValues.speed, + trainLength, + }; }); - const tailPosition = positionValues[tailKey] - ? 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 / 1000; - const tailDistanceAlong = train.tail_positions.position / 1000; - const headPosition = along(line, headDistanceAlong, { - units: 'kilometers', - }); - const tailPosition = train.tail_position - ? 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, - }; - }) - ); + // Found trains including timePosition, and organize them with geojson collection of points + setOtherTrainsHoverPosition( + createOtherPoints().map((train) => { + const headDistanceAlong = train.head_positions.position / 1000; + const tailDistanceAlong = train.tail_positions.position / 1000; + const headPosition = along(line, headDistanceAlong, { + units: 'kilometers', + }); + const tailPosition = train.tail_position + ? 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) => { @@ -199,18 +205,11 @@ function Map(props) { const getGeoJSONPath = async (pathID) => { try { const path = await get(`${PATHFINDING_URI}${pathID}/`); - const features = { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: path.geographic.coordinates, - }, - properties: {}, - }; + const features = lineString(path.geographic.coordinates); setGeojsonPath(features); zoomToFeature(bbox(features)); } catch (e) { - console.log('ERROR', e); + console.info('ERROR', e); } }; @@ -222,8 +221,8 @@ function Map(props) { const resetPitchBearing = () => { updateViewportChange({ ...viewport, - bearing: parseFloat(0), - pitch: parseFloat(0), + bearing: 0, + pitch: 0, transitionDuration: 1000, transitionInterpolator: new FlyToInterpolator(), }); @@ -240,14 +239,7 @@ function Map(props) { geojsonPath.geometry.coordinates[geojsonPath.geometry.coordinates.length - 1][0], geojsonPath.geometry.coordinates[geojsonPath.geometry.coordinates.length - 1][1], ]; - const start = { - type: 'Feature', - properties: {}, - geometry: { - type: 'Point', - coordinates: startCoordinates, - }, - }; + const start = point(startCoordinates); const sliced = lineSlice(start, cursorPoint, line); const positionLocal = lineLength(sliced, { units: 'kilometers' }) * 1000; const timePositionLocal = interpolateOnPosition( @@ -265,8 +257,10 @@ function Map(props) { }; const onClick = (e) => { - console.info('Click on map'); - console.info(mapRef.current.queryRenderedFeatures(e.point)); + if (mapRef.current) { + console.info('Click on map'); + console.info(mapRef.current.queryRenderedFeatures(e.point)); + } }; const displayPath = () => { @@ -276,7 +270,7 @@ function Map(props) { }; function defineInteractiveLayers() { - const interactiveLayersLocal = []; + const interactiveLayersLocal: string[] = []; if (geojsonPath) { interactiveLayersLocal.push('geojsonPath'); interactiveLayersLocal.push('main-train-path'); @@ -289,23 +283,25 @@ function Map(props) { } return interactiveLayersLocal; } - const [interactiveLayerIds, setInteractiveLayerIds] = useState([]); + const [interactiveLayerIds, setInteractiveLayerIds] = useState([]); useEffect(() => { setInteractiveLayerIds(defineInteractiveLayers()); }, [geojsonPath, otherTrainsHoverPosition.length]); useEffect(() => { - mapRef.current.getMap().on('click', () => {}); - - if (urlLat) { - updateViewportChange({ - ...viewport, - latitude: parseFloat(urlLat), - longitude: parseFloat(urlLon), - zoom: parseFloat(urlZoom), - bearing: parseFloat(urlBearing), - pitch: parseFloat(urlPitch), - }); + if (mapRef.current) { + mapRef.current.getMap().on('click', () => {}); + + if (urlLat) { + updateViewportChange({ + ...viewport, + latitude: parseFloat(urlLat), + longitude: parseFloat(urlLon), + zoom: parseFloat(urlZoom), + bearing: parseFloat(urlBearing), + pitch: parseFloat(urlPitch), + }); + } } }, []); @@ -357,7 +353,7 @@ function Map(props) { {mapTrackSources === 'geographic' ? ( <> - + @@ -394,21 +390,22 @@ function Map(props) { )} {mapSearchMarker !== undefined ? ( - + ) : null} {geojsonPath !== undefined ? : null} - {selectedTrainHoverPosition && ( + {geojsonPath && selectedTrainHoverPosition && ( )} - {otherTrainsHoverPosition.map((pt) => ( - - ))} + {geojsonPath && + otherTrainsHoverPosition.map((pt) => ( + + ))}
diff --git a/front/src/reducers/osrdsimulation.ts b/front/src/reducers/osrdsimulation.ts index e43ad778925..28f0fd8a1f6 100644 --- a/front/src/reducers/osrdsimulation.ts +++ b/front/src/reducers/osrdsimulation.ts @@ -51,6 +51,10 @@ export interface OsrdSimulationState { tailPosition: number; routeEndOccupancy: number; routeBeginOccupancy: number; + speed: { + speed: number; + time: number; + }; }; selectedProjection: any; selectedTrain: number; @@ -318,12 +322,11 @@ export function updateConsolidatedSimulation(consolidatedSimulation) { }); }; } -export function updateTimePositionValues(timePosition, positionValues) { +export function updateTimePositionValues(timePosition) { return (dispatch) => { dispatch({ type: UPDATE_TIME_POSITION_VALUES, timePosition, - positionValues, }); }; } From 918554fa5d075c4d1bbf3f0d4aef1793dd9599a6 Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 17:57:54 +0200 Subject: [PATCH 7/8] maxheight 2 --- .../applications/osrd/views/OSRDSimulation/OSRDSimulation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js b/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js index 9bdf91c07bc..602162d58b4 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js +++ b/front/src/applications/osrd/views/OSRDSimulation/OSRDSimulation.js @@ -211,7 +211,7 @@ function OSRDSimulation() { ); - const mapMaxHeight = timeTableRef?.current?.clientHeight - 42; + const mapMaxHeight = getMapMaxHeight(timeTableRef); return (
{!simulation || simulation.trains.length === 0 ? ( From 2ede6e5896990096aada446920980f5a818f028b Mon Sep 17 00:00:00 2001 From: Valentin Chanas Date: Wed, 12 Oct 2022 18:00:26 +0200 Subject: [PATCH 8/8] check if map is loaded --- .../applications/osrd/views/OSRDConfig/Map.tsx | 6 +----- .../osrd/views/OSRDSimulation/Map.tsx | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/front/src/applications/osrd/views/OSRDConfig/Map.tsx b/front/src/applications/osrd/views/OSRDConfig/Map.tsx index 697382d9a85..95b7ea9f31c 100644 --- a/front/src/applications/osrd/views/OSRDConfig/Map.tsx +++ b/front/src/applications/osrd/views/OSRDConfig/Map.tsx @@ -49,11 +49,7 @@ import osmBlankStyle from 'common/Map/Layers/osmBlankStyle'; import 'common/Map/Map.scss'; -interface MapProps { - setMapLoaded: (boolean) => void; -} - -function Map({ setMapLoaded }: MapProps) { +function Map() { const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM, layersSettings } = useSelector((state: RootState) => state.map); const [idHover, setIdHover] = useState(); diff --git a/front/src/applications/osrd/views/OSRDSimulation/Map.tsx b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx index 0ece34b4058..fffbe4828b5 100644 --- a/front/src/applications/osrd/views/OSRDSimulation/Map.tsx +++ b/front/src/applications/osrd/views/OSRDSimulation/Map.tsx @@ -69,8 +69,12 @@ function checkIfEcoAndAddPrefix(allowancesSettings, id, baseKey) { return baseKey; } -function Map(props) { +interface MapProps { + setExtViewport: (any) => void; +} +function Map(props: MapProps) { const { setExtViewport } = props; + const [mapLoaded, setMapLoaded] = useState(false); const { viewport, mapSearchMarker, mapStyle, mapTrackSources, showOSM, layersSettings } = useSelector((state: RootState) => state.map); const { isPlaying, selectedTrain, positionValues, timePosition, allowancesSettings } = @@ -229,7 +233,7 @@ function Map(props) { }; const onFeatureHover = (e) => { - if (!isPlaying && e && geojsonPath?.geometry?.coordinates) { + if (mapLoaded && !isPlaying && e && geojsonPath?.geometry?.coordinates) { const line = lineString(geojsonPath.geometry.coordinates); const cursorPoint = point(e.lngLat); const key = getRegimeKey(simulation.trains[selectedTrain].id); @@ -271,7 +275,7 @@ function Map(props) { function defineInteractiveLayers() { const interactiveLayersLocal: string[] = []; - if (geojsonPath) { + if (mapLoaded && geojsonPath) { interactiveLayersLocal.push('geojsonPath'); interactiveLayersLocal.push('main-train-path'); otherTrainsHoverPosition.forEach((train) => { @@ -315,6 +319,10 @@ function Map(props) { } }, [timePosition]); + const handleLoadFinished = () => { + setMapLoaded(true); + }; + return ( <> @@ -333,6 +341,7 @@ function Map(props) { interactiveLayerIds={interactiveLayerIds} touchRotate asyncRender + onLoad={handleLoadFinished} >