diff options
author | Anton Tananaev <anton@traccar.org> | 2022-05-28 07:37:24 -0700 |
---|---|---|
committer | Anton Tananaev <anton@traccar.org> | 2022-05-28 07:37:24 -0700 |
commit | 47fdc633966a8f2baefb5424042de6be2ecd1ab6 (patch) | |
tree | 9817e5b02898636f146c4ed7c62aa8221310bb6f /modern/src/map/core/MapView.js | |
parent | ab26340ead6eba063734286a374101951d08688b (diff) | |
download | trackermap-web-47fdc633966a8f2baefb5424042de6be2ecd1ab6.tar.gz trackermap-web-47fdc633966a8f2baefb5424042de6be2ecd1ab6.tar.bz2 trackermap-web-47fdc633966a8f2baefb5424042de6be2ecd1ab6.zip |
Add map to stops report
Diffstat (limited to 'modern/src/map/core/MapView.js')
-rw-r--r-- | modern/src/map/core/MapView.js | 134 |
1 files changed, 134 insertions, 0 deletions
diff --git a/modern/src/map/core/MapView.js b/modern/src/map/core/MapView.js new file mode 100644 index 00000000..ba5a67d6 --- /dev/null +++ b/modern/src/map/core/MapView.js @@ -0,0 +1,134 @@ +import 'maplibre-gl/dist/maplibre-gl.css'; +import '../switcher/switcher.css'; +import maplibregl from 'maplibre-gl'; +import React, { + useRef, useLayoutEffect, useEffect, useState, +} from 'react'; +import { useSelector } from 'react-redux'; +import { SwitcherControl } from '../switcher/switcher'; +import { + styleCarto, styleCustom, styleLocationIq, styleMapbox, styleMapTiler, styleOsm, +} from './mapStyles'; +import { useAttributePreference } from '../../common/util/preferences'; +import { useTranslation } from '../../common/components/LocalizationProvider'; +import usePersistedState, { savePersistedState } from '../../common/util/usePersistedState'; +import { mapImages } from './preloadImages'; + +const element = document.createElement('div'); +element.style.width = '100%'; +element.style.height = '100%'; +element.style.boxSizing = 'initial'; + +export const map = new maplibregl.Map({ + container: element, +}); + +let ready = false; +const readyListeners = new Set(); + +const addReadyListener = (listener) => { + readyListeners.add(listener); + listener(ready); +}; + +const removeReadyListener = (listener) => { + readyListeners.delete(listener); +}; + +const updateReadyValue = (value) => { + ready = value; + readyListeners.forEach((listener) => listener(value)); +}; + +const initMap = async () => { + if (ready) return; + if (!map.hasImage('background')) { + Object.entries(mapImages).forEach(([key, value]) => { + map.addImage(key, value, { + pixelRatio: window.devicePixelRatio, + }); + }); + } + updateReadyValue(true); +}; + +map.addControl(new maplibregl.NavigationControl({ + showCompass: false, +})); + +const switcher = new SwitcherControl( + () => updateReadyValue(false), + (layerId) => savePersistedState('mapLayer', layerId), + () => { + map.once('styledata', () => { + const waiting = () => { + if (!map.loaded()) { + setTimeout(waiting, 33); + } else { + initMap(); + } + }; + waiting(); + }); + }, +); + +map.addControl(switcher); + +const MapView = ({ children }) => { + const containerEl = useRef(null); + const t = useTranslation(); + + const [mapReady, setMapReady] = useState(false); + + const [defaultMapLayer] = usePersistedState('mapLayer', 'locationIqStreets'); + const mapboxAccessToken = useAttributePreference('mapboxAccessToken'); + const mapTilerKey = useAttributePreference('mapTilerKey'); + const locationIqKey = useAttributePreference('locationIqKey', 'pk.0f147952a41c555a5b70614039fd148b'); + const customMapUrl = useSelector((state) => state.session.server?.mapUrl); + + useEffect(() => { + maplibregl.accessToken = mapboxAccessToken; + }, [mapboxAccessToken]); + + useEffect(() => { + switcher.updateStyles([ + { id: 'locationIqStreets', title: t('mapLocationIqStreets'), uri: styleLocationIq('streets', locationIqKey) }, + { id: 'locationIqEarth', title: t('mapLocationIqEarth'), uri: styleLocationIq('earth', locationIqKey) }, + { id: 'locationIqHybrid', title: t('mapLocationIqHybrid'), uri: styleLocationIq('hybrid', locationIqKey) }, + { id: 'osm', title: t('mapOsm'), uri: styleOsm() }, + { id: 'carto', title: t('mapCarto'), uri: styleCarto() }, + { id: 'mapboxStreets', title: t('mapMapboxStreets'), uri: styleMapbox('streets-v11') }, + { id: 'mapboxOutdoors', title: t('mapMapboxOutdoors'), uri: styleMapbox('outdoors-v11') }, + { id: 'mapboxSatellite', title: t('mapMapboxSatellite'), uri: styleMapbox('satellite-v9') }, + { id: 'mapTilerBasic', title: t('mapMapTilerBasic'), uri: styleMapTiler('basic', mapTilerKey) }, + { id: 'mapTilerHybrid', title: t('mapMapTilerHybrid'), uri: styleMapTiler('hybrid', mapTilerKey) }, + { id: 'custom', title: t('mapCustom'), uri: styleCustom(customMapUrl) }, + ], defaultMapLayer); + }, [t, locationIqKey, mapTilerKey, customMapUrl, defaultMapLayer]); + + useEffect(() => { + const listener = (ready) => setMapReady(ready); + addReadyListener(listener); + return () => { + removeReadyListener(listener); + }; + }, []); + + useLayoutEffect(() => { + const currentEl = containerEl.current; + currentEl.appendChild(element); + map.resize(); + return () => { + currentEl.removeChild(element); + }; + }, [containerEl]); + + return ( + <div style={{ width: '100%', height: '100%' }} ref={containerEl}> + {mapReady && children} + </div> + ); +}; + +export default MapView; |