diff options
Diffstat (limited to 'modern/src/map/core')
-rw-r--r-- | modern/src/map/core/MapView.jsx | 123 | ||||
-rw-r--r-- | modern/src/map/core/mapUtil.js | 105 | ||||
-rw-r--r-- | modern/src/map/core/preloadImages.js | 78 | ||||
-rw-r--r-- | modern/src/map/core/useMapStyles.js | 259 |
4 files changed, 0 insertions, 565 deletions
diff --git a/modern/src/map/core/MapView.jsx b/modern/src/map/core/MapView.jsx deleted file mode 100644 index 35b3a65a..00000000 --- a/modern/src/map/core/MapView.jsx +++ /dev/null @@ -1,123 +0,0 @@ -import 'maplibre-gl/dist/maplibre-gl.css'; -import maplibregl from 'maplibre-gl'; -import React, { - useRef, useLayoutEffect, useEffect, useState, -} from 'react'; -import { SwitcherControl } from '../switcher/switcher'; -import { useAttributePreference, usePreference } from '../../common/util/preferences'; -import usePersistedState, { savePersistedState } from '../../common/util/usePersistedState'; -import { mapImages } from './preloadImages'; -import useMapStyles from './useMapStyles'; - -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, - attributionControl: false, -}); - -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()); - -const switcher = new SwitcherControl( - () => updateReadyValue(false), - (styleId) => savePersistedState('selectedMapStyle', styleId), - () => { - 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 [mapReady, setMapReady] = useState(false); - - const mapStyles = useMapStyles(); - const activeMapStyles = useAttributePreference('activeMapStyles', 'locationIqStreets,osm,carto'); - const [defaultMapStyle] = usePersistedState('selectedMapStyle', usePreference('map', 'locationIqStreets')); - const mapboxAccessToken = useAttributePreference('mapboxAccessToken'); - const maxZoom = useAttributePreference('web.maxZoom'); - - useEffect(() => { - if (maxZoom) { - map.setMaxZoom(maxZoom); - } - }, [maxZoom]); - - useEffect(() => { - maplibregl.accessToken = mapboxAccessToken; - }, [mapboxAccessToken]); - - useEffect(() => { - const filteredStyles = mapStyles.filter((s) => s.available && activeMapStyles.includes(s.id)); - const styles = filteredStyles.length ? filteredStyles : mapStyles.filter((s) => s.id === 'osm'); - switcher.updateStyles(styles, defaultMapStyle); - }, [mapStyles, defaultMapStyle]); - - 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; diff --git a/modern/src/map/core/mapUtil.js b/modern/src/map/core/mapUtil.js deleted file mode 100644 index 8dcded2c..00000000 --- a/modern/src/map/core/mapUtil.js +++ /dev/null @@ -1,105 +0,0 @@ -import { parse, stringify } from 'wellknown'; -import circle from '@turf/circle'; - -export const loadImage = (url) => new Promise((imageLoaded) => { - const image = new Image(); - image.onload = () => imageLoaded(image); - image.src = url; -}); - -const canvasTintImage = (image, color) => { - const canvas = document.createElement('canvas'); - canvas.width = image.width * devicePixelRatio; - canvas.height = image.height * devicePixelRatio; - canvas.style.width = `${image.width}px`; - canvas.style.height = `${image.height}px`; - - const context = canvas.getContext('2d'); - - context.save(); - context.fillStyle = color; - context.globalAlpha = 1; - context.fillRect(0, 0, canvas.width, canvas.height); - context.globalCompositeOperation = 'destination-atop'; - context.globalAlpha = 1; - context.drawImage(image, 0, 0, canvas.width, canvas.height); - context.restore(); - - return canvas; -}; - -export const prepareIcon = (background, icon, color) => { - const canvas = document.createElement('canvas'); - canvas.width = background.width * devicePixelRatio; - canvas.height = background.height * devicePixelRatio; - canvas.style.width = `${background.width}px`; - canvas.style.height = `${background.height}px`; - - const context = canvas.getContext('2d'); - context.drawImage(background, 0, 0, canvas.width, canvas.height); - - if (icon) { - const iconRatio = 0.5; - const imageWidth = canvas.width * iconRatio; - const imageHeight = canvas.height * iconRatio; - context.drawImage(canvasTintImage(icon, color), (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight); - } - - return context.getImageData(0, 0, canvas.width, canvas.height); -}; - -export const reverseCoordinates = (it) => { - if (!it) { - return it; - } if (Array.isArray(it)) { - if (it.length === 2 && typeof it[0] === 'number' && typeof it[1] === 'number') { - return [it[1], it[0]]; - } - return it.map((it) => reverseCoordinates(it)); - } - return { - ...it, - coordinates: reverseCoordinates(it.coordinates), - }; -}; - -export const geofenceToFeature = (theme, item) => { - let geometry; - if (item.area.indexOf('CIRCLE') > -1) { - const coordinates = item.area.replace(/CIRCLE|\(|\)|,/g, ' ').trim().split(/ +/); - const options = { steps: 32, units: 'meters' }; - const polygon = circle([Number(coordinates[1]), Number(coordinates[0])], Number(coordinates[2]), options); - geometry = polygon.geometry; - } else { - geometry = reverseCoordinates(parse(item.area)); - } - return { - id: item.id, - type: 'Feature', - geometry, - properties: { - name: item.name, - color: item.attributes.color || theme.palette.geometry.main, - }, - }; -}; - -export const geometryToArea = (geometry) => stringify(reverseCoordinates(geometry)); - -export const findFonts = (map) => { - const fontSet = new Set(); - const { layers } = map.getStyle(); - layers?.forEach?.((layer) => { - layer.layout?.['text-font']?.forEach?.(fontSet.add, fontSet); - }); - const availableFonts = [...fontSet]; - const regularFont = availableFonts.find((it) => it.includes('Regular')); - if (regularFont) { - return [regularFont]; - } - const anyFont = availableFonts.find(Boolean); - if (anyFont) { - return [anyFont]; - } - return ['Roboto Regular']; -}; diff --git a/modern/src/map/core/preloadImages.js b/modern/src/map/core/preloadImages.js deleted file mode 100644 index a0056d4c..00000000 --- a/modern/src/map/core/preloadImages.js +++ /dev/null @@ -1,78 +0,0 @@ -import { grey } from '@mui/material/colors'; -import createPalette from '@mui/material/styles/createPalette'; -import { loadImage, prepareIcon } from './mapUtil'; - -import arrowSvg from '../../resources/images/arrow.svg'; -import directionSvg from '../../resources/images/direction.svg'; -import backgroundSvg from '../../resources/images/background.svg'; -import animalSvg from '../../resources/images/icon/animal.svg'; -import bicycleSvg from '../../resources/images/icon/bicycle.svg'; -import boatSvg from '../../resources/images/icon/boat.svg'; -import busSvg from '../../resources/images/icon/bus.svg'; -import carSvg from '../../resources/images/icon/car.svg'; -import camperSvg from '../../resources/images/icon/camper.svg'; -import craneSvg from '../../resources/images/icon/crane.svg'; -import defaultSvg from '../../resources/images/icon/default.svg'; -import helicopterSvg from '../../resources/images/icon/helicopter.svg'; -import motorcycleSvg from '../../resources/images/icon/motorcycle.svg'; -import offroadSvg from '../../resources/images/icon/offroad.svg'; -import personSvg from '../../resources/images/icon/person.svg'; -import pickupSvg from '../../resources/images/icon/pickup.svg'; -import planeSvg from '../../resources/images/icon/plane.svg'; -import scooterSvg from '../../resources/images/icon/scooter.svg'; -import shipSvg from '../../resources/images/icon/ship.svg'; -import tractorSvg from '../../resources/images/icon/tractor.svg'; -import trainSvg from '../../resources/images/icon/train.svg'; -import tramSvg from '../../resources/images/icon/tram.svg'; -import trolleybusSvg from '../../resources/images/icon/trolleybus.svg'; -import truckSvg from '../../resources/images/icon/truck.svg'; -import vanSvg from '../../resources/images/icon/van.svg'; - -export const mapIcons = { - animal: animalSvg, - bicycle: bicycleSvg, - boat: boatSvg, - bus: busSvg, - car: carSvg, - camper: camperSvg, - crane: craneSvg, - default: defaultSvg, - helicopter: helicopterSvg, - motorcycle: motorcycleSvg, - offroad: offroadSvg, - person: personSvg, - pickup: pickupSvg, - plane: planeSvg, - scooter: scooterSvg, - ship: shipSvg, - tractor: tractorSvg, - train: trainSvg, - tram: tramSvg, - trolleybus: trolleybusSvg, - truck: truckSvg, - van: vanSvg, -}; - -export const mapIconKey = (category) => (mapIcons.hasOwnProperty(category) ? category : 'default'); - -export const mapImages = {}; - -const mapPalette = createPalette({ - neutral: { main: grey[500] }, -}); - -export default async () => { - const background = await loadImage(backgroundSvg); - mapImages.background = await prepareIcon(background); - mapImages.direction = await prepareIcon(await loadImage(directionSvg)); - mapImages.arrow = await prepareIcon(await loadImage(arrowSvg)); - await Promise.all(Object.keys(mapIcons).map(async (category) => { - const results = []; - ['info', 'success', 'error', 'neutral'].forEach((color) => { - results.push(loadImage(mapIcons[category]).then((icon) => { - mapImages[`${category}-${color}`] = prepareIcon(background, icon, mapPalette[color].main); - })); - }); - await Promise.all(results); - })); -}; diff --git a/modern/src/map/core/useMapStyles.js b/modern/src/map/core/useMapStyles.js deleted file mode 100644 index 7c3412b5..00000000 --- a/modern/src/map/core/useMapStyles.js +++ /dev/null @@ -1,259 +0,0 @@ -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { useTranslation } from '../../common/components/LocalizationProvider'; -import { useAttributePreference } from '../../common/util/preferences'; - -const styleCustom = ({ tiles, minZoom, maxZoom, attribution }) => { - const source = { - type: 'raster', - tiles, - attribution, - tileSize: 256, - minzoom: minZoom, - maxzoom: maxZoom, - }; - Object.keys(source).forEach((key) => source[key] === undefined && delete source[key]); - return { - version: 8, - sources: { - custom: source, - }, - glyphs: 'https://cdn.traccar.com/map/fonts/{fontstack}/{range}.pbf', - layers: [{ - id: 'custom', - type: 'raster', - source: 'custom', - }], - }; -}; - -export default () => { - const t = useTranslation(); - - const mapTilerKey = useAttributePreference('mapTilerKey'); - const locationIqKey = useAttributePreference('locationIqKey') || 'pk.0f147952a41c555a5b70614039fd148b'; - const bingMapsKey = useAttributePreference('bingMapsKey'); - const tomTomKey = useAttributePreference('tomTomKey'); - const hereKey = useAttributePreference('hereKey'); - const mapboxAccessToken = useAttributePreference('mapboxAccessToken'); - const customMapUrl = useSelector((state) => state.session.server.mapUrl); - - return useMemo(() => [ - { - id: 'locationIqStreets', - title: t('mapLocationIqStreets'), - style: `https://tiles.locationiq.com/v3/streets/vector.json?key=${locationIqKey}`, - available: true, - }, - { - id: 'locationIqDark', - title: t('mapLocationIqDark'), - style: `https://tiles.locationiq.com/v3/dark/vector.json?key=${locationIqKey}`, - available: true, - }, - { - id: 'osm', - title: t('mapOsm'), - style: styleCustom({ - tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'], - maxZoom: 19, - attribution: '© <a target="_top" rel="noopener" href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors', - }), - available: true, - }, - { - id: 'openTopoMap', - title: t('mapOpenTopoMap'), - style: styleCustom({ - tiles: ['a', 'b', 'c'].map((i) => `https://${i}.tile.opentopomap.org/{z}/{x}/{y}.png`), - maxZoom: 17, - }), - available: true, - }, - { - id: 'carto', - title: t('mapCarto'), - style: styleCustom({ - tiles: ['a', 'b', 'c', 'd'].map((i) => `https://${i}.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png`), - maxZoom: 22, - attribution: '© <a target="_top" rel="noopener" href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a target="_top" rel="noopener" href="https://carto.com/attribution">CARTO</a>', - }), - available: true, - }, - { - id: 'googleRoad', - title: t('mapGoogleRoad'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://mt${i}.google.com/vt/lyrs=m&hl=en&x={x}&y={y}&z={z}&s=Ga`), - maxZoom: 20, - attribution: '© Google', - }), - available: true, - }, - { - id: 'googleSatellite', - title: t('mapGoogleSatellite'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://mt${i}.google.com/vt/lyrs=s&hl=en&x={x}&y={y}&z={z}&s=Ga`), - maxZoom: 20, - attribution: '© Google', - }), - available: true, - }, - { - id: 'googleHybrid', - title: t('mapGoogleHybrid'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://mt${i}.google.com/vt/lyrs=y&hl=en&x={x}&y={y}&z={z}&s=Ga`), - maxZoom: 20, - attribution: '© Google', - }), - available: true, - }, - { - id: 'mapTilerBasic', - title: t('mapMapTilerBasic'), - style: `https://api.maptiler.com/maps/basic/style.json?key=${mapTilerKey}`, - available: !!mapTilerKey, - attribute: 'mapTilerKey', - }, - { - id: 'mapTilerHybrid', - title: t('mapMapTilerHybrid'), - style: `https://api.maptiler.com/maps/hybrid/style.json?key=${mapTilerKey}`, - available: !!mapTilerKey, - attribute: 'mapTilerKey', - }, - { - id: 'bingRoad', - title: t('mapBingRoad'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://t${i}.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/{quadkey}?mkt=en-US&it=G,L&shading=hill&og=1885&n=z`), - maxZoom: 21, - }), - available: !!bingMapsKey, - attribute: 'bingMapsKey', - }, - { - id: 'bingAerial', - title: t('mapBingAerial'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://ecn.t${i}.tiles.virtualearth.net/tiles/a{quadkey}.jpeg?g=12327`), - maxZoom: 19, - }), - available: !!bingMapsKey, - attribute: 'bingMapsKey', - }, - { - id: 'bingHybrid', - title: t('mapBingHybrid'), - style: styleCustom({ - tiles: [0, 1, 2, 3].map((i) => `https://t${i}.ssl.ak.dynamic.tiles.virtualearth.net/comp/ch/{quadkey}?mkt=en-US&it=A,G,L&og=1885&n=z`), - maxZoom: 19, - }), - available: !!bingMapsKey, - attribute: 'bingMapsKey', - }, - { - id: 'tomTomBasic', - title: t('mapTomTomBasic'), - style: `https://api.tomtom.com/map/1/style/20.0.0-8/basic_main.json?key=${tomTomKey}`, - available: !!tomTomKey, - attribute: 'tomTomKey', - }, - { - id: 'hereBasic', - title: t('mapHereBasic'), - style: `https://assets.vector.hereapi.com/styles/berlin/base/mapbox/tilezen?apikey=${hereKey}`, - available: !!hereKey, - attribute: 'hereKey', - }, - { - id: 'hereHybrid', - title: t('mapHereHybrid'), - style: styleCustom({ - tiles: [1, 2, 3, 4].map((i) => `https://${i}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/hybrid.day/{z}/{x}/{y}/256/png8?apiKey=${hereKey}`), - maxZoom: 20, - }), - available: !!hereKey, - attribute: 'hereKey', - }, - { - id: 'hereSatellite', - title: t('mapHereSatellite'), - style: styleCustom({ - tiles: [1, 2, 3, 4].map((i) => `https://${i}.aerial.maps.ls.hereapi.com/maptile/2.1/maptile/newest/satellite.day/{z}/{x}/{y}/256/png8?apiKey=${hereKey}`), - maxZoom: 19, - }), - available: !!hereKey, - attribute: 'hereKey', - }, - { - id: 'autoNavi', - title: t('mapAutoNavi'), - style: styleCustom({ - tiles: [1, 2, 3, 4].map((i) => `https://webrd0${i}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}`), - minZoom: 3, - maxZoom: 18, - }), - available: true, - }, - { - id: 'ordnanceSurvey', - title: t('mapOrdnanceSurvey'), - style: 'https://api.os.uk/maps/vector/v1/vts/resources/styles?key=EAZ8p83u72FTGiLjLC2MsTAl1ko6XQHC', - transformRequest: (url) => ({ - url: `${url}&srs=3857`, - }), - available: true, - }, - { - id: 'mapboxStreets', - title: t('mapMapboxStreets'), - style: styleCustom({ - tiles: [`https://api.mapbox.com/styles/v1/mapbox/streets-v11/tiles/{z}/{x}/{y}?access_token=${mapboxAccessToken}`], - maxZoom: 22, - }), - available: !!mapboxAccessToken, - attribute: 'mapboxAccessToken', - }, - { - id: 'mapboxStreetsDark', - title: t('mapMapboxStreetsDark'), - style: styleCustom({ - tiles: [`https://api.mapbox.com/styles/v1/mapbox/dark-v11/tiles/{z}/{x}/{y}?access_token=${mapboxAccessToken}`], - maxZoom: 22, - }), - available: !!mapboxAccessToken, - attribute: 'mapboxAccessToken', - }, - { - id: 'mapboxOutdoors', - title: t('mapMapboxOutdoors'), - style: styleCustom({ - tiles: [`https://api.mapbox.com/styles/v1/mapbox/outdoors-v11/tiles/{z}/{x}/{y}?access_token=${mapboxAccessToken}`], - maxZoom: 22, - }), - available: !!mapboxAccessToken, - attribute: 'mapboxAccessToken', - }, - { - id: 'mapboxSatelliteStreet', - title: t('mapMapboxSatellite'), - style: styleCustom({ - tiles: [`https://api.mapbox.com/styles/v1/mapbox/satellite-streets-v11/tiles/{z}/{x}/{y}?access_token=${mapboxAccessToken}`], - maxZoom: 22, - }), - available: !!mapboxAccessToken, - attribute: 'mapboxAccessToken', - }, - { - id: 'custom', - title: t('mapCustom'), - style: styleCustom({ - tiles: [customMapUrl], - }), - available: !!customMapUrl, - }, - ], [t, mapTilerKey, locationIqKey, bingMapsKey, tomTomKey, hereKey, mapboxAccessToken, customMapUrl]); -}; |