-
Notifications
You must be signed in to change notification settings - Fork 46
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Clara Ni <[email protected]>
- Loading branch information
Showing
5 changed files
with
245 additions
and
370 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,142 +1,72 @@ | ||
import { useCallback, useEffect, useRef, useState } from 'react'; | ||
import { useCallback, useMemo, useRef } from 'react'; | ||
|
||
import { isNil } from 'lodash'; | ||
import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl/maplibre'; | ||
import type { MapRef } from 'react-map-gl/maplibre'; | ||
import { useSelector } from 'react-redux'; | ||
import { useParams } from 'react-router-dom'; | ||
|
||
import BaseMap from 'common/Map/BaseMap'; | ||
import MapButtons from 'common/Map/Buttons/MapButtons'; | ||
import { CUSTOM_ATTRIBUTION } from 'common/Map/const'; | ||
import colors from 'common/Map/Consts/colors'; | ||
import { useMapBlankStyle } from 'common/Map/Layers/blankStyle'; | ||
import IGNLayers from 'common/Map/Layers/IGNLayers'; | ||
import InfraObjectLayers from 'common/Map/Layers/InfraObjectLayers'; | ||
import LineSearchLayer from 'common/Map/Layers/LineSearchLayer'; | ||
import OSMLayers from 'common/Map/Layers/OSMLayers'; | ||
import SearchMarker from 'common/Map/Layers/SearchMarker'; | ||
import { removeSearchItemMarkersOnMap } from 'common/Map/utils'; | ||
import { useInfraID } from 'common/osrdContext'; | ||
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder'; | ||
import VirtualLayers from 'modules/simulationResult/components/SimulationResultsMap/VirtualLayers'; | ||
import type { Viewport } from 'reducers/map'; | ||
import { updateViewport } from 'reducers/map'; | ||
import { getMap, getTerrain3DExaggeration } from 'reducers/map/selectors'; | ||
import { useAppDispatch } from 'store'; | ||
|
||
function Map() { | ||
const mapBlankStyle = useMapBlankStyle(); | ||
|
||
const [mapLoaded, setMapLoaded] = useState(false); | ||
const Map = () => { | ||
const dispatch = useAppDispatch(); | ||
const { viewport, mapSearchMarker, mapStyle, showOSM, layersSettings } = useSelector(getMap); | ||
const infraID = useInfraID(); | ||
const terrain3DExaggeration = useSelector(getTerrain3DExaggeration); | ||
|
||
const mapRef = useRef<MapRef | null>(null); | ||
const { urlLat, urlLon, urlZoom, urlBearing, urlPitch } = useParams(); | ||
const dispatch = useAppDispatch(); | ||
|
||
const updateViewportChange = useCallback( | ||
(value: Partial<Viewport>, updateRouter = false) => { | ||
(value: Partial<Viewport>, { updateRouter } = { updateRouter: false }) => { | ||
dispatch(updateViewport(value, `/map`, updateRouter)); | ||
}, | ||
[dispatch] | ||
); | ||
|
||
const scaleControlStyle = { | ||
left: 20, | ||
bottom: 20, | ||
}; | ||
|
||
const resetPitchBearing = () => { | ||
updateViewportChange({ | ||
...viewport, | ||
bearing: 0, | ||
pitch: 0, | ||
}); | ||
}; | ||
|
||
const defineInteractiveLayers = () => { | ||
const interactiveLayersLocal = []; | ||
if (layersSettings.tvds) { | ||
interactiveLayersLocal.push('chartis/osrd_tvd_section/geo'); | ||
} | ||
return interactiveLayersLocal; | ||
}; | ||
|
||
/** | ||
* When the component mount | ||
* => we check if url has viewport and set it in store | ||
*/ | ||
useEffect(() => { | ||
// viewport | ||
const newViewport: Partial<Viewport> = {}; | ||
if (!isNil(urlLat)) newViewport.latitude = parseFloat(urlLat); | ||
if (!isNil(urlLon)) newViewport.longitude = parseFloat(urlLon); | ||
if (!isNil(urlZoom)) newViewport.zoom = parseFloat(urlZoom); | ||
if (!isNil(urlBearing)) newViewport.bearing = parseFloat(urlBearing); | ||
if (!isNil(urlPitch)) newViewport.pitch = parseFloat(urlPitch); | ||
if (Object.keys(newViewport).length > 0) updateViewportChange(newViewport); | ||
// we only do it at mount time | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
const interactiveLayerIds = useMemo( | ||
() => (layersSettings.tvds ? ['chartis/osrd_tvd_section/geo'] : []), | ||
[layersSettings] | ||
); | ||
|
||
return ( | ||
<main className="mastcontainer mastcontainer-map"> | ||
<MapButtons | ||
map={mapRef.current ?? undefined} | ||
resetPitchBearing={resetPitchBearing} | ||
withInfraButton | ||
withMapKeyButton | ||
bearing={viewport.bearing} | ||
viewPort={viewport} | ||
withInfraButton | ||
withMapKeyButton | ||
/> | ||
<ReactMapGL | ||
{...viewport} | ||
ref={mapRef} | ||
<BaseMap | ||
mapId="reference-map" | ||
mapRef={mapRef} | ||
cursor="normal" | ||
style={{ width: '100%', height: '100%' }} | ||
mapStyle={mapBlankStyle} | ||
onMove={(e) => updateViewportChange(e.viewState)} | ||
onMoveEnd={(e) => updateViewportChange(e.viewState, true)} | ||
attributionControl={false} // Defined below | ||
onResize={(e) => { | ||
updateViewportChange({ | ||
width: e.target.getContainer().offsetWidth, | ||
height: e.target.getContainer().offsetHeight, | ||
}); | ||
}} | ||
interactiveLayerIds={defineInteractiveLayers()} | ||
touchZoomRotate | ||
maxPitch={85} | ||
terrain={ | ||
terrain3DExaggeration | ||
? { source: 'terrain', exaggeration: terrain3DExaggeration } | ||
: undefined | ||
} | ||
onLoad={() => { | ||
setMapLoaded(true); | ||
}} | ||
infraId={infraID} | ||
interactiveLayerIds={interactiveLayerIds} | ||
mapSearchMarker={mapSearchMarker} | ||
mapStyle={mapStyle} | ||
onClick={() => { | ||
removeSearchItemMarkersOnMap(dispatch); | ||
}} | ||
> | ||
<VirtualLayers /> | ||
<AttributionControl customAttribution={CUSTOM_ATTRIBUTION} /> | ||
<ScaleControl maxWidth={100} unit="metric" style={scaleControlStyle} /> | ||
|
||
<OSMLayers mapStyle={mapStyle} showOSM={showOSM && mapLoaded} /> | ||
<IGNLayers /> | ||
|
||
{infraID && <InfraObjectLayers infraId={infraID} mapStyle={mapStyle} />} | ||
|
||
<LineSearchLayer | ||
layerOrder={LAYER_GROUPS_ORDER[LAYERS.LINE_SEARCH.GROUP]} | ||
infraID={infraID} | ||
/> | ||
|
||
{mapSearchMarker && <SearchMarker data={mapSearchMarker} colors={colors[mapStyle]} />} | ||
</ReactMapGL> | ||
showOSM={showOSM} | ||
viewPort={viewport} | ||
updatePartialViewPort={updateViewportChange} | ||
terrain3DExaggeration={terrain3DExaggeration} | ||
/> | ||
</main> | ||
); | ||
} | ||
}; | ||
|
||
export default Map; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
import { type MutableRefObject, type PropsWithChildren, useEffect, useState } from 'react'; | ||
|
||
import type { MapLayerMouseEvent, MapLibreEvent } from 'maplibre-gl'; | ||
import ReactMapGL, { AttributionControl, ScaleControl } from 'react-map-gl/maplibre'; | ||
import type { MapRef } from 'react-map-gl/maplibre'; | ||
import { useParams } from 'react-router-dom'; | ||
|
||
import colors from 'common/Map/Consts/colors'; | ||
import { useMapBlankStyle } from 'common/Map/Layers/blankStyle'; | ||
import IGNLayers from 'common/Map/Layers/IGNLayers'; | ||
import InfraObjectLayers from 'common/Map/Layers/InfraObjectLayers'; | ||
import LineSearchLayer from 'common/Map/Layers/LineSearchLayer'; | ||
import OSMLayers from 'common/Map/Layers/OSMLayers'; | ||
import SearchMarker from 'common/Map/Layers/SearchMarker'; | ||
import { LAYER_GROUPS_ORDER, LAYERS } from 'config/layerOrder'; | ||
import VirtualLayers from 'modules/simulationResult/components/SimulationResultsMap/VirtualLayers'; | ||
import type { MapState, Viewport } from 'reducers/map'; | ||
|
||
import { CUSTOM_ATTRIBUTION } from './const'; | ||
|
||
type MapProps = Pick<MapState, 'mapSearchMarker' | 'mapStyle' | 'showOSM'> & { | ||
mapId: string; | ||
mapRef: MutableRefObject<MapRef | null>; | ||
interactiveLayerIds: string[]; | ||
infraId?: number; | ||
terrain3DExaggeration?: number; | ||
viewPort: Viewport; | ||
updatePartialViewPort: ( | ||
newPartialViewPort: Partial<Viewport>, | ||
options?: { updateRouter: boolean } | ||
) => void; | ||
cursor?: 'default' | 'pointer' | 'normal'; | ||
hideAttribution?: boolean; | ||
hoveredOperationalPointId?: string; | ||
onClick?: ((e: MapLayerMouseEvent) => void) | undefined; | ||
onMouseEnter?: ((e: MapLayerMouseEvent) => void) | undefined; | ||
onMouseMove?: ((e: MapLayerMouseEvent) => void) | undefined; | ||
onIdle?: ((e: MapLibreEvent) => void) | undefined; | ||
}; | ||
|
||
const BaseMap = ({ | ||
mapId, | ||
mapRef, | ||
children, | ||
interactiveLayerIds, | ||
viewPort, | ||
infraId, | ||
mapSearchMarker, | ||
mapStyle, | ||
showOSM, | ||
cursor = 'default', | ||
hideAttribution = false, | ||
hoveredOperationalPointId, | ||
terrain3DExaggeration, | ||
updatePartialViewPort, | ||
onClick, | ||
onMouseEnter, | ||
onMouseMove, | ||
onIdle, | ||
}: PropsWithChildren<MapProps>) => { | ||
const mapBlankStyle = useMapBlankStyle(); | ||
|
||
const [mapIsLoaded, setMapIsLoaded] = useState(false); | ||
|
||
const { urlLat = '', urlLon = '', urlZoom = '', urlBearing = '', urlPitch = '' } = useParams(); | ||
|
||
useEffect(() => { | ||
if (urlLat) { | ||
updatePartialViewPort({ | ||
latitude: parseFloat(urlLat), | ||
longitude: parseFloat(urlLon), | ||
zoom: parseFloat(urlZoom), | ||
bearing: parseFloat(urlBearing), | ||
pitch: parseFloat(urlPitch), | ||
}); | ||
} | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
}, []); | ||
|
||
return ( | ||
<ReactMapGL | ||
id={mapId} | ||
ref={mapRef} | ||
{...viewPort} | ||
interactiveLayerIds={interactiveLayerIds} | ||
cursor={cursor} | ||
mapStyle={mapBlankStyle} | ||
terrain={ | ||
terrain3DExaggeration | ||
? { source: 'terrain', exaggeration: terrain3DExaggeration } | ||
: undefined | ||
} | ||
onMouseEnter={onMouseEnter} | ||
onMouseMove={onMouseMove} | ||
onClick={onClick} | ||
onIdle={onIdle} | ||
// default behavior | ||
onMove={(e) => { | ||
updatePartialViewPort(e.viewState); | ||
}} | ||
onMoveEnd={(e) => updatePartialViewPort(e.viewState, { updateRouter: true })} | ||
onResize={(e) => { | ||
updatePartialViewPort({ | ||
width: e.target.getContainer().offsetWidth, | ||
height: e.target.getContainer().offsetHeight, | ||
}); | ||
}} | ||
onLoad={() => { | ||
setMapIsLoaded(true); | ||
}} | ||
attributionControl={false} // Defined below | ||
dragPan | ||
maxPitch={85} | ||
preserveDrawingBuffer | ||
scrollZoom | ||
style={{ width: '100%', height: '100%' }} | ||
touchZoomRotate | ||
> | ||
<VirtualLayers /> | ||
{!hideAttribution && ( | ||
<AttributionControl position="bottom-right" customAttribution={CUSTOM_ATTRIBUTION} /> | ||
)} | ||
<ScaleControl | ||
maxWidth={100} | ||
unit="metric" | ||
style={{ | ||
left: 20, | ||
bottom: 20, | ||
}} | ||
/> | ||
|
||
{infraId && ( | ||
<InfraObjectLayers | ||
infraId={infraId} | ||
mapStyle={mapStyle} | ||
hoveredOperationalPointId={hoveredOperationalPointId} | ||
/> | ||
)} | ||
|
||
<OSMLayers mapStyle={mapStyle} showOSM={showOSM && mapIsLoaded} /> | ||
<IGNLayers /> | ||
|
||
<LineSearchLayer | ||
layerOrder={LAYER_GROUPS_ORDER[LAYERS.LINE_SEARCH.GROUP]} | ||
infraID={infraId} | ||
/> | ||
|
||
{mapSearchMarker && <SearchMarker data={mapSearchMarker} colors={colors[mapStyle]} />} | ||
|
||
{children} | ||
</ReactMapGL> | ||
); | ||
}; | ||
|
||
export default BaseMap; |
Oops, something went wrong.