Skip to content

Commit 96ac78f

Browse files
committed
front: introduce BaseMap
Signed-off-by: Clara Ni <[email protected]>
1 parent f0a02b7 commit 96ac78f

File tree

5 files changed

+245
-371
lines changed

5 files changed

+245
-371
lines changed
+27-95
Original file line numberDiff line numberDiff line change
@@ -1,142 +1,74 @@
1-
import { useCallback, useEffect, useRef, useState } from 'react';
1+
import { useCallback, useMemo, useRef } from 'react';
22

3-
import { isNil } from 'lodash';
4-
import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl/maplibre';
53
import type { MapRef } from 'react-map-gl/maplibre';
64
import { useSelector } from 'react-redux';
7-
import { useParams } from 'react-router-dom';
85

6+
import BaseMap from 'common/Map/BaseMap';
97
import MapButtons from 'common/Map/Buttons/MapButtons';
10-
import { CUSTOM_ATTRIBUTION } from 'common/Map/const';
11-
import colors from 'common/Map/Consts/colors';
12-
import { useMapBlankStyle } from 'common/Map/Layers/blankStyle';
13-
import IGNLayers from 'common/Map/Layers/IGNLayers';
14-
import InfraObjectLayers from 'common/Map/Layers/InfraObjectLayers';
15-
import LineSearchLayer from 'common/Map/Layers/LineSearchLayer';
16-
import OSMLayers from 'common/Map/Layers/OSMLayers';
17-
import SearchMarker from 'common/Map/Layers/SearchMarker';
188
import { removeSearchItemMarkersOnMap } from 'common/Map/utils';
199
import { useInfraID } from 'common/osrdContext';
20-
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder';
21-
import VirtualLayers from 'modules/simulationResult/components/SimulationResultsMap/VirtualLayers';
2210
import type { Viewport } from 'reducers/map';
2311
import { updateViewport } from 'reducers/map';
2412
import { getMap, getTerrain3DExaggeration } from 'reducers/map/selectors';
2513
import { useAppDispatch } from 'store';
2614

27-
function Map() {
28-
const mapBlankStyle = useMapBlankStyle();
15+
const REFERENCE_MAP_ID = 'reference-map';
2916

30-
const [mapLoaded, setMapLoaded] = useState(false);
17+
const Map = () => {
18+
const dispatch = useAppDispatch();
3119
const { viewport, mapSearchMarker, mapStyle, showOSM, layersSettings } = useSelector(getMap);
3220
const infraID = useInfraID();
3321
const terrain3DExaggeration = useSelector(getTerrain3DExaggeration);
22+
3423
const mapRef = useRef<MapRef | null>(null);
35-
const { urlLat, urlLon, urlZoom, urlBearing, urlPitch } = useParams();
36-
const dispatch = useAppDispatch();
24+
3725
const updateViewportChange = useCallback(
38-
(value: Partial<Viewport>, updateRouter = false) => {
26+
(value: Partial<Viewport>, { updateRouter } = { updateRouter: false }) => {
3927
dispatch(updateViewport(value, `/map`, updateRouter));
4028
},
4129
[dispatch]
4230
);
4331

44-
const scaleControlStyle = {
45-
left: 20,
46-
bottom: 20,
47-
};
48-
4932
const resetPitchBearing = () => {
5033
updateViewportChange({
51-
...viewport,
5234
bearing: 0,
5335
pitch: 0,
5436
});
5537
};
5638

57-
const defineInteractiveLayers = () => {
58-
const interactiveLayersLocal = [];
59-
if (layersSettings.tvds) {
60-
interactiveLayersLocal.push('chartis/osrd_tvd_section/geo');
61-
}
62-
return interactiveLayersLocal;
63-
};
64-
65-
/**
66-
* When the component mount
67-
* => we check if url has viewport and set it in store
68-
*/
69-
useEffect(() => {
70-
// viewport
71-
const newViewport: Partial<Viewport> = {};
72-
if (!isNil(urlLat)) newViewport.latitude = parseFloat(urlLat);
73-
if (!isNil(urlLon)) newViewport.longitude = parseFloat(urlLon);
74-
if (!isNil(urlZoom)) newViewport.zoom = parseFloat(urlZoom);
75-
if (!isNil(urlBearing)) newViewport.bearing = parseFloat(urlBearing);
76-
if (!isNil(urlPitch)) newViewport.pitch = parseFloat(urlPitch);
77-
if (Object.keys(newViewport).length > 0) updateViewportChange(newViewport);
78-
// we only do it at mount time
79-
// eslint-disable-next-line react-hooks/exhaustive-deps
80-
}, []);
39+
const interactiveLayerIds = useMemo(
40+
() => (layersSettings.tvds ? ['chartis/osrd_tvd_section/geo'] : []),
41+
[layersSettings]
42+
);
8143

8244
return (
8345
<main className="mastcontainer mastcontainer-map">
8446
<MapButtons
8547
map={mapRef.current ?? undefined}
8648
resetPitchBearing={resetPitchBearing}
87-
withInfraButton
88-
withMapKeyButton
8949
bearing={viewport.bearing}
9050
viewPort={viewport}
51+
withInfraButton
52+
withMapKeyButton
9153
/>
92-
<ReactMapGL
93-
{...viewport}
94-
ref={mapRef}
54+
<BaseMap
55+
mapId={REFERENCE_MAP_ID}
56+
mapRef={mapRef}
9557
cursor="normal"
96-
style={{ width: '100%', height: '100%' }}
97-
mapStyle={mapBlankStyle}
98-
onMove={(e) => updateViewportChange(e.viewState)}
99-
onMoveEnd={(e) => updateViewportChange(e.viewState, true)}
100-
attributionControl={false} // Defined below
101-
onResize={(e) => {
102-
updateViewportChange({
103-
width: e.target.getContainer().offsetWidth,
104-
height: e.target.getContainer().offsetHeight,
105-
});
106-
}}
107-
interactiveLayerIds={defineInteractiveLayers()}
108-
touchZoomRotate
109-
maxPitch={85}
110-
terrain={
111-
terrain3DExaggeration
112-
? { source: 'terrain', exaggeration: terrain3DExaggeration }
113-
: undefined
114-
}
115-
onLoad={() => {
116-
setMapLoaded(true);
117-
}}
58+
infraId={infraID}
59+
interactiveLayerIds={interactiveLayerIds}
60+
mapSearchMarker={mapSearchMarker}
61+
mapStyle={mapStyle}
11862
onClick={() => {
11963
removeSearchItemMarkersOnMap(dispatch);
12064
}}
121-
>
122-
<VirtualLayers />
123-
<AttributionControl customAttribution={CUSTOM_ATTRIBUTION} />
124-
<ScaleControl maxWidth={100} unit="metric" style={scaleControlStyle} />
125-
126-
<OSMLayers mapStyle={mapStyle} showOSM={showOSM && mapLoaded} />
127-
<IGNLayers />
128-
129-
{infraID && <InfraObjectLayers infraId={infraID} mapStyle={mapStyle} />}
130-
131-
<LineSearchLayer
132-
layerOrder={LAYER_GROUPS_ORDER[LAYERS.LINE_SEARCH.GROUP]}
133-
infraID={infraID}
134-
/>
135-
136-
{mapSearchMarker && <SearchMarker data={mapSearchMarker} colors={colors[mapStyle]} />}
137-
</ReactMapGL>
65+
showOSM={showOSM}
66+
viewPort={viewport}
67+
updatePartialViewPort={updateViewportChange}
68+
terrain3DExaggeration={terrain3DExaggeration}
69+
/>
13870
</main>
13971
);
140-
}
72+
};
14173

14274
export default Map;

front/src/common/Map/BaseMap.tsx

+155
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import { type MutableRefObject, type PropsWithChildren, useEffect, useState } from 'react';
2+
3+
import type { MapLayerMouseEvent, MapLibreEvent } from 'maplibre-gl';
4+
import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl/maplibre';
5+
import type { MapRef } from 'react-map-gl/maplibre';
6+
import { useParams } from 'react-router-dom';
7+
8+
import colors from 'common/Map/Consts/colors';
9+
import { useMapBlankStyle } from 'common/Map/Layers/blankStyle';
10+
import IGNLayers from 'common/Map/Layers/IGNLayers';
11+
import InfraObjectLayers from 'common/Map/Layers/InfraObjectLayers';
12+
import LineSearchLayer from 'common/Map/Layers/LineSearchLayer';
13+
import OSMLayers from 'common/Map/Layers/OSMLayers';
14+
import SearchMarker from 'common/Map/Layers/SearchMarker';
15+
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder';
16+
import VirtualLayers from 'modules/simulationResult/components/SimulationResultsMap/VirtualLayers';
17+
import type { MapState, Viewport } from 'reducers/map';
18+
19+
import { CUSTOM_ATTRIBUTION } from './const';
20+
21+
type MapProps = Pick<MapState, 'mapSearchMarker' | 'mapStyle' | 'showOSM'> & {
22+
mapId: string;
23+
mapRef: MutableRefObject<MapRef | null>;
24+
interactiveLayerIds: string[];
25+
infraId?: number;
26+
terrain3DExaggeration?: number;
27+
viewPort: Viewport;
28+
updatePartialViewPort: (
29+
newPartialViewPort: Partial<Viewport>,
30+
options?: { updateRouter: boolean }
31+
) => void;
32+
cursor?: 'default' | 'pointer' | 'normal';
33+
hideAttribution?: boolean;
34+
hoveredOperationalPointId?: string;
35+
onClick?: (e: MapLayerMouseEvent) => void;
36+
onMouseEnter?: (e: MapLayerMouseEvent) => void;
37+
onMouseMove?: (e: MapLayerMouseEvent) => void;
38+
onIdle?: (e: MapLibreEvent) => void;
39+
};
40+
41+
const BaseMap = ({
42+
mapId,
43+
mapRef,
44+
children,
45+
interactiveLayerIds,
46+
viewPort,
47+
infraId,
48+
mapSearchMarker,
49+
mapStyle,
50+
showOSM,
51+
cursor = 'default',
52+
hideAttribution = false,
53+
hoveredOperationalPointId,
54+
terrain3DExaggeration,
55+
updatePartialViewPort,
56+
onClick,
57+
onMouseEnter,
58+
onMouseMove,
59+
onIdle,
60+
}: PropsWithChildren<MapProps>) => {
61+
const mapBlankStyle = useMapBlankStyle();
62+
63+
const [mapIsLoaded, setMapIsLoaded] = useState(false);
64+
65+
const { urlLat = '', urlLon = '', urlZoom = '', urlBearing = '', urlPitch = '' } = useParams();
66+
67+
useEffect(() => {
68+
if (urlLat) {
69+
updatePartialViewPort({
70+
latitude: parseFloat(urlLat),
71+
longitude: parseFloat(urlLon),
72+
zoom: parseFloat(urlZoom),
73+
bearing: parseFloat(urlBearing),
74+
pitch: parseFloat(urlPitch),
75+
});
76+
}
77+
// eslint-disable-next-line react-hooks/exhaustive-deps
78+
}, []);
79+
80+
return (
81+
<ReactMapGL
82+
id={mapId}
83+
ref={mapRef}
84+
{...viewPort}
85+
interactiveLayerIds={interactiveLayerIds}
86+
cursor={cursor}
87+
mapStyle={mapBlankStyle}
88+
terrain={
89+
terrain3DExaggeration
90+
? { source: 'terrain', exaggeration: terrain3DExaggeration }
91+
: undefined
92+
}
93+
onMouseEnter={onMouseEnter}
94+
onMouseMove={onMouseMove}
95+
onClick={onClick}
96+
onIdle={onIdle}
97+
// default behavior
98+
onMove={(e) => {
99+
updatePartialViewPort(e.viewState);
100+
}}
101+
onMoveEnd={(e) => updatePartialViewPort(e.viewState, { updateRouter: true })}
102+
onResize={(e) => {
103+
updatePartialViewPort({
104+
width: e.target.getContainer().offsetWidth,
105+
height: e.target.getContainer().offsetHeight,
106+
});
107+
}}
108+
onLoad={() => {
109+
setMapIsLoaded(true);
110+
}}
111+
attributionControl={false} // Defined below
112+
dragPan
113+
maxPitch={85}
114+
preserveDrawingBuffer
115+
scrollZoom
116+
style={{ width: '100%', height: '100%' }}
117+
touchZoomRotate
118+
>
119+
<VirtualLayers />
120+
{!hideAttribution && (
121+
<AttributionControl position="bottom-right" customAttribution={CUSTOM_ATTRIBUTION} />
122+
)}
123+
<ScaleControl
124+
maxWidth={100}
125+
unit="metric"
126+
style={{
127+
left: 20,
128+
bottom: 20,
129+
}}
130+
/>
131+
132+
{infraId && (
133+
<InfraObjectLayers
134+
infraId={infraId}
135+
mapStyle={mapStyle}
136+
hoveredOperationalPointId={hoveredOperationalPointId}
137+
/>
138+
)}
139+
140+
<OSMLayers mapStyle={mapStyle} showOSM={showOSM && mapIsLoaded} />
141+
<IGNLayers />
142+
143+
<LineSearchLayer
144+
layerOrder={LAYER_GROUPS_ORDER[LAYERS.LINE_SEARCH.GROUP]}
145+
infraID={infraId}
146+
/>
147+
148+
{mapSearchMarker && <SearchMarker data={mapSearchMarker} colors={colors[mapStyle]} />}
149+
150+
{children}
151+
</ReactMapGL>
152+
);
153+
};
154+
155+
export default BaseMap;

0 commit comments

Comments
 (0)