diff options
Diffstat (limited to 'modern/src/map')
28 files changed, 0 insertions, 2588 deletions
diff --git a/modern/src/map/MapCamera.js b/modern/src/map/MapCamera.js deleted file mode 100644 index afb52f89..00000000 --- a/modern/src/map/MapCamera.js +++ /dev/null @@ -1,32 +0,0 @@ -import { useEffect } from 'react'; -import maplibregl from 'maplibre-gl'; -import { map } from './core/MapView'; - -const MapCamera = ({ - latitude, longitude, positions, coordinates, -}) => { - useEffect(() => { - if (coordinates || positions) { - if (!coordinates) { - coordinates = positions.map((item) => [item.longitude, item.latitude]); - } - if (coordinates.length) { - const bounds = coordinates.reduce((bounds, item) => bounds.extend(item), new maplibregl.LngLatBounds(coordinates[0], coordinates[0])); - const canvas = map.getCanvas(); - map.fitBounds(bounds, { - padding: Math.min(canvas.width, canvas.height) * 0.1, - duration: 0, - }); - } - } else { - map.jumpTo({ - center: [longitude, latitude], - zoom: Math.max(map.getZoom(), 10), - }); - } - }, [latitude, longitude, positions, coordinates]); - - return null; -}; - -export default MapCamera; diff --git a/modern/src/map/MapCurrentLocation.js b/modern/src/map/MapCurrentLocation.js deleted file mode 100644 index 66fc46ec..00000000 --- a/modern/src/map/MapCurrentLocation.js +++ /dev/null @@ -1,21 +0,0 @@ -import maplibregl from 'maplibre-gl'; -import { useEffect } from 'react'; -import { map } from './core/MapView'; - -const MapCurrentLocation = () => { - useEffect(() => { - const control = new maplibregl.GeolocateControl({ - positionOptions: { - enableHighAccuracy: true, - timeout: 5000, - }, - trackUserLocation: true, - }); - map.addControl(control); - return () => map.removeControl(control); - }, []); - - return null; -}; - -export default MapCurrentLocation; diff --git a/modern/src/map/MapGeofence.js b/modern/src/map/MapGeofence.js deleted file mode 100644 index 73b32724..00000000 --- a/modern/src/map/MapGeofence.js +++ /dev/null @@ -1,94 +0,0 @@ -import { useId, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { useTheme } from '@mui/styles'; -import { map } from './core/MapView'; -import { findFonts, geofenceToFeature } from './core/mapUtil'; -import { useAttributePreference } from '../common/util/preferences'; - -const MapGeofence = () => { - const id = useId(); - - const theme = useTheme(); - - const mapGeofences = useAttributePreference('mapGeofences', true); - - const geofences = useSelector((state) => state.geofences.items); - - useEffect(() => { - if (mapGeofences) { - map.addSource(id, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - }); - map.addLayer({ - source: id, - id: 'geofences-fill', - type: 'fill', - filter: [ - 'all', - ['==', '$type', 'Polygon'], - ], - paint: { - 'fill-color': ['get', 'color'], - 'fill-outline-color': ['get', 'color'], - 'fill-opacity': 0.1, - }, - }); - map.addLayer({ - source: id, - id: 'geofences-line', - type: 'line', - paint: { - 'line-color': ['get', 'color'], - 'line-width': 2, - }, - }); - map.addLayer({ - source: id, - id: 'geofences-title', - type: 'symbol', - layout: { - 'text-field': '{name}', - 'text-font': findFonts(map), - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }); - - return () => { - if (map.getLayer('geofences-fill')) { - map.removeLayer('geofences-fill'); - } - if (map.getLayer('geofences-line')) { - map.removeLayer('geofences-line'); - } - if (map.getLayer('geofences-title')) { - map.removeLayer('geofences-title'); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - } - return () => {}; - }, [mapGeofences]); - - useEffect(() => { - if (mapGeofences) { - map.getSource(id)?.setData({ - type: 'FeatureCollection', - features: Object.values(geofences).map((geofence) => geofenceToFeature(theme, geofence)), - }); - } - }, [mapGeofences, geofences]); - - return null; -}; - -export default MapGeofence; diff --git a/modern/src/map/MapMarkers.js b/modern/src/map/MapMarkers.js deleted file mode 100644 index 8fbe92b6..00000000 --- a/modern/src/map/MapMarkers.js +++ /dev/null @@ -1,89 +0,0 @@ -import { useId, useEffect } from 'react'; -import { useTheme } from '@mui/styles'; -import { useMediaQuery } from '@mui/material'; -import { map } from './core/MapView'; -import { useAttributePreference } from '../common/util/preferences'; -import { findFonts } from './core/mapUtil'; - -const MapMarkers = ({ markers, showTitles }) => { - const id = useId(); - - const theme = useTheme(); - const desktop = useMediaQuery(theme.breakpoints.up('md')); - const iconScale = useAttributePreference('iconScale', desktop ? 0.75 : 1); - - useEffect(() => { - map.addSource(id, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - }); - - if (showTitles) { - map.addLayer({ - id, - type: 'symbol', - source: id, - filter: ['!has', 'point_count'], - layout: { - 'icon-image': '{image}', - 'icon-size': iconScale, - 'icon-allow-overlap': true, - 'text-field': '{title}', - 'text-allow-overlap': true, - 'text-anchor': 'bottom', - 'text-offset': [0, -2 * iconScale], - 'text-font': findFonts(map), - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }); - } else { - map.addLayer({ - id, - type: 'symbol', - source: id, - layout: { - 'icon-image': '{image}', - 'icon-size': iconScale, - 'icon-allow-overlap': true, - }, - }); - } - - return () => { - if (map.getLayer(id)) { - map.removeLayer(id); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - }, [showTitles]); - - useEffect(() => { - map.getSource(id)?.setData({ - type: 'FeatureCollection', - features: markers.map(({ latitude, longitude, image, title }) => ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [longitude, latitude], - }, - properties: { - image: image || 'default-neutral', - title: title || '', - }, - })), - }); - }, [showTitles, markers]); - - return null; -}; - -export default MapMarkers; diff --git a/modern/src/map/MapPadding.js b/modern/src/map/MapPadding.js deleted file mode 100644 index b1927a1f..00000000 --- a/modern/src/map/MapPadding.js +++ /dev/null @@ -1,20 +0,0 @@ -import { useEffect } from 'react'; - -import { map } from './core/MapView'; - -const MapPadding = ({ - top, right, bottom, left, -}) => { - useEffect(() => { - map.setPadding({ - top, right, bottom, left, - }); - return () => map.setPadding({ - top: 0, right: 0, bottom: 0, left: 0, - }); - }, [top, right, bottom, left]); - - return null; -}; - -export default MapPadding; diff --git a/modern/src/map/MapPositions.js b/modern/src/map/MapPositions.js deleted file mode 100644 index 751c61b9..00000000 --- a/modern/src/map/MapPositions.js +++ /dev/null @@ -1,216 +0,0 @@ -import { useId, useCallback, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { useMediaQuery } from '@mui/material'; -import { useTheme } from '@mui/styles'; -import { map } from './core/MapView'; -import { formatTime, getStatusColor } from '../common/util/formatter'; -import { mapIconKey } from './core/preloadImages'; -import { findFonts } from './core/mapUtil'; -import { useAttributePreference, usePreference } from '../common/util/preferences'; - -const MapPositions = ({ positions, onClick, showStatus, selectedPosition, titleField }) => { - const id = useId(); - const clusters = `${id}-clusters`; - const selected = `${id}-selected`; - - const theme = useTheme(); - const desktop = useMediaQuery(theme.breakpoints.up('md')); - const iconScale = useAttributePreference('iconScale', desktop ? 0.75 : 1); - - const devices = useSelector((state) => state.devices.items); - const selectedDeviceId = useSelector((state) => state.devices.selectedId); - - const mapCluster = useAttributePreference('mapCluster', true); - const hours12 = usePreference('twelveHourFormat'); - const directionType = useAttributePreference('mapDirection', 'selected'); - - const createFeature = (devices, position, selectedPositionId) => { - const device = devices[position.deviceId]; - let showDirection; - switch (directionType) { - case 'none': - showDirection = false; - break; - case 'all': - showDirection = true; - break; - default: - showDirection = selectedPositionId === position.id; - break; - } - return { - id: position.id, - deviceId: position.deviceId, - name: device.name, - fixTime: formatTime(position.fixTime, 'seconds', hours12), - category: mapIconKey(device.category), - color: showStatus ? position.attributes.color || getStatusColor(device.status) : 'neutral', - rotation: position.course, - direction: showDirection, - }; - }; - - const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer'; - const onMouseLeave = () => map.getCanvas().style.cursor = ''; - - const onMapClick = useCallback((event) => { - if (!event.defaultPrevented && onClick) { - onClick(); - } - }, [onClick]); - - const onMarkerClick = useCallback((event) => { - event.preventDefault(); - const feature = event.features[0]; - if (onClick) { - onClick(feature.properties.id, feature.properties.deviceId); - } - }, [onClick]); - - const onClusterClick = useCallback((event) => { - event.preventDefault(); - const features = map.queryRenderedFeatures(event.point, { - layers: [clusters], - }); - const clusterId = features[0].properties.cluster_id; - map.getSource(id).getClusterExpansionZoom(clusterId, (error, zoom) => { - if (!error) { - map.easeTo({ - center: features[0].geometry.coordinates, - zoom, - }); - } - }); - }, [clusters]); - - useEffect(() => { - map.addSource(id, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - cluster: mapCluster, - clusterMaxZoom: 14, - clusterRadius: 50, - }); - map.addSource(selected, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - }); - [id, selected].forEach((source) => { - map.addLayer({ - id: source, - type: 'symbol', - source, - filter: ['!has', 'point_count'], - layout: { - 'icon-image': '{category}-{color}', - 'icon-size': iconScale, - 'icon-allow-overlap': true, - 'text-field': `{${titleField || 'name'}}`, - 'text-allow-overlap': true, - 'text-anchor': 'bottom', - 'text-offset': [0, -2 * iconScale], - 'text-font': findFonts(map), - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }); - map.addLayer({ - id: `direction-${source}`, - type: 'symbol', - source, - filter: [ - 'all', - ['!has', 'point_count'], - ['==', 'direction', true], - ], - layout: { - 'icon-image': 'direction', - 'icon-size': iconScale, - 'icon-allow-overlap': true, - 'icon-rotate': ['get', 'rotation'], - 'icon-rotation-alignment': 'map', - }, - }); - - map.on('mouseenter', source, onMouseEnter); - map.on('mouseleave', source, onMouseLeave); - map.on('click', source, onMarkerClick); - }); - map.addLayer({ - id: clusters, - type: 'symbol', - source: id, - filter: ['has', 'point_count'], - layout: { - 'icon-image': 'background', - 'icon-size': iconScale, - 'text-field': '{point_count_abbreviated}', - 'text-font': findFonts(map), - 'text-size': 14, - }, - }); - - map.on('mouseenter', clusters, onMouseEnter); - map.on('mouseleave', clusters, onMouseLeave); - map.on('click', clusters, onClusterClick); - map.on('click', onMapClick); - - return () => { - map.off('mouseenter', clusters, onMouseEnter); - map.off('mouseleave', clusters, onMouseLeave); - map.off('click', clusters, onClusterClick); - map.off('click', onMapClick); - - if (map.getLayer(clusters)) { - map.removeLayer(clusters); - } - - [id, selected].forEach((source) => { - map.off('mouseenter', source, onMouseEnter); - map.off('mouseleave', source, onMouseLeave); - map.off('click', source, onMarkerClick); - - if (map.getLayer(source)) { - map.removeLayer(source); - } - if (map.getLayer(`direction-${source}`)) { - map.removeLayer(`direction-${source}`); - } - if (map.getSource(source)) { - map.removeSource(source); - } - }); - }; - }, [mapCluster, clusters, onMarkerClick, onClusterClick]); - - useEffect(() => { - [id, selected].forEach((source) => { - map.getSource(source)?.setData({ - type: 'FeatureCollection', - features: positions.filter((it) => devices.hasOwnProperty(it.deviceId)) - .filter((it) => (source === id ? it.deviceId !== selectedDeviceId : it.deviceId === selectedDeviceId)) - .map((position) => ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [position.longitude, position.latitude], - }, - properties: createFeature(devices, position, selectedPosition && selectedPosition.id), - })), - }); - }); - }, [mapCluster, clusters, onMarkerClick, onClusterClick, devices, positions, selectedPosition]); - - return null; -}; - -export default MapPositions; diff --git a/modern/src/map/MapRoutePath.js b/modern/src/map/MapRoutePath.js deleted file mode 100644 index 18069a71..00000000 --- a/modern/src/map/MapRoutePath.js +++ /dev/null @@ -1,100 +0,0 @@ -import { useTheme } from '@mui/styles'; -import { useId, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { map } from './core/MapView'; -import { findFonts } from './core/mapUtil'; - -const MapRoutePath = ({ name, positions, coordinates }) => { - const id = useId(); - - const theme = useTheme(); - - const reportColor = useSelector((state) => { - const position = positions?.find(() => true); - if (position) { - const attributes = state.devices.items[position.deviceId]?.attributes; - if (attributes) { - const color = attributes['web.reportColor']; - if (color) { - return color; - } - } - } - return theme.palette.geometry.main; - }); - - useEffect(() => { - map.addSource(id, { - type: 'geojson', - data: { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: [], - }, - }, - }); - map.addLayer({ - source: id, - id: `${id}-line`, - type: 'line', - layout: { - 'line-join': 'round', - 'line-cap': 'round', - }, - paint: { - 'line-color': ['get', 'color'], - 'line-width': 2, - }, - }); - if (name) { - map.addLayer({ - source: id, - id: `${id}-title`, - type: 'symbol', - layout: { - 'text-field': '{name}', - 'text-font': findFonts(map), - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }); - } - - return () => { - if (map.getLayer(`${id}-title`)) { - map.removeLayer(`${id}-title`); - } - if (map.getLayer(`${id}-line`)) { - map.removeLayer(`${id}-line`); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - }, []); - - useEffect(() => { - if (!coordinates) { - coordinates = positions.map((item) => [item.longitude, item.latitude]); - } - map.getSource(id)?.setData({ - type: 'Feature', - geometry: { - type: 'LineString', - coordinates, - }, - properties: { - name, - color: reportColor, - }, - }); - }, [theme, positions, coordinates, reportColor]); - - return null; -}; - -export default MapRoutePath; diff --git a/modern/src/map/MapRoutePoints.js b/modern/src/map/MapRoutePoints.js deleted file mode 100644 index e329da81..00000000 --- a/modern/src/map/MapRoutePoints.js +++ /dev/null @@ -1,77 +0,0 @@ -import { useId, useCallback, useEffect } from 'react'; -import { map } from './core/MapView'; - -const MapRoutePoints = ({ positions, onClick }) => { - const id = useId(); - - const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer'; - const onMouseLeave = () => map.getCanvas().style.cursor = ''; - - const onMarkerClick = useCallback((event) => { - event.preventDefault(); - const feature = event.features[0]; - if (onClick) { - onClick(feature.properties.id, feature.properties.index); - } - }, [onClick]); - - useEffect(() => { - map.addSource(id, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - }); - map.addLayer({ - id, - type: 'symbol', - source: id, - layout: { - 'icon-image': 'arrow', - 'icon-allow-overlap': true, - 'icon-rotate': ['get', 'rotation'], - 'icon-rotation-alignment': 'map', - }, - }); - - map.on('mouseenter', id, onMouseEnter); - map.on('mouseleave', id, onMouseLeave); - map.on('click', id, onMarkerClick); - - return () => { - map.off('mouseenter', id, onMouseEnter); - map.off('mouseleave', id, onMouseLeave); - map.off('click', id, onMarkerClick); - - if (map.getLayer(id)) { - map.removeLayer(id); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - }, [onMarkerClick]); - - useEffect(() => { - map.getSource(id)?.setData({ - type: 'FeatureCollection', - features: positions.map((position, index) => ({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: [position.longitude, position.latitude], - }, - properties: { - index, - id: position.id, - rotation: position.course, - }, - })), - }); - }, [onMarkerClick, positions]); - - return null; -}; - -export default MapRoutePoints; diff --git a/modern/src/map/MapScale.js b/modern/src/map/MapScale.js deleted file mode 100644 index c8a724c9..00000000 --- a/modern/src/map/MapScale.js +++ /dev/null @@ -1,34 +0,0 @@ -import maplibregl from 'maplibre-gl'; -import { useEffect, useMemo } from 'react'; -import { useAttributePreference } from '../common/util/preferences'; -import { map } from './core/MapView'; - -const MapScale = () => { - const distanceUnit = useAttributePreference('distanceUnit'); - - const control = useMemo(() => new maplibregl.ScaleControl(), []); - - useEffect(() => { - map.addControl(control, 'bottom-right'); - return () => map.removeControl(control); - }, [control]); - - useEffect(() => { - switch (distanceUnit) { - case 'mi': - control.setUnit('imperial'); - break; - case 'nmi': - control.setUnit('nautical'); - break; - case 'km': - default: - control.setUnit('metric'); - break; - } - }, [control, distanceUnit]); - - return null; -}; - -export default MapScale; 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]); -}; diff --git a/modern/src/map/draw/MapGeofenceEdit.js b/modern/src/map/draw/MapGeofenceEdit.js deleted file mode 100644 index e547ea05..00000000 --- a/modern/src/map/draw/MapGeofenceEdit.js +++ /dev/null @@ -1,161 +0,0 @@ -import 'mapbox-gl/dist/mapbox-gl.css'; -import '@mapbox/mapbox-gl-draw/dist/mapbox-gl-draw.css'; -import maplibregl from 'maplibre-gl'; -import MapboxDraw from '@mapbox/mapbox-gl-draw'; -import { useEffect } from 'react'; - -import { useDispatch, useSelector } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; -import { useTheme } from '@mui/styles'; -import { map } from '../core/MapView'; -import { geofenceToFeature, geometryToArea } from '../core/mapUtil'; -import { errorsActions, geofencesActions } from '../../store'; -import { useCatchCallback } from '../../reactHelper'; -import theme from './theme'; - -const draw = new MapboxDraw({ - displayControlsDefault: false, - controls: { - polygon: true, - line_string: true, - trash: true, - }, - userProperties: true, - styles: [...theme, { - id: 'gl-draw-title', - type: 'symbol', - filter: ['all'], - layout: { - 'text-field': '{user_name}', - 'text-font': ['Roboto Regular'], - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }], -}); - -const MapGeofenceEdit = ({ selectedGeofenceId }) => { - const theme = useTheme(); - const dispatch = useDispatch(); - const navigate = useNavigate(); - - const geofences = useSelector((state) => state.geofences.items); - - const refreshGeofences = useCatchCallback(async () => { - const response = await fetch('/api/geofences'); - if (response.ok) { - dispatch(geofencesActions.refresh(await response.json())); - } else { - throw Error(await response.text()); - } - }, [dispatch]); - - useEffect(() => { - refreshGeofences(); - - map.addControl(draw, 'top-left'); - return () => map.removeControl(draw); - }, [refreshGeofences]); - - useEffect(() => { - const listener = async (event) => { - const feature = event.features[0]; - const newItem = { name: '', area: geometryToArea(feature.geometry) }; - draw.delete(feature.id); - try { - const response = await fetch('/api/geofences', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(newItem), - }); - if (response.ok) { - const item = await response.json(); - navigate(`/settings/geofence/${item.id}`); - } else { - throw Error(await response.text()); - } - } catch (error) { - dispatch(errorsActions.push(error.message)); - } - }; - - map.on('draw.create', listener); - return () => map.off('draw.create', listener); - }, [dispatch, navigate]); - - useEffect(() => { - const listener = async (event) => { - const feature = event.features[0]; - try { - const response = await fetch(`/api/geofences/${feature.id}`, { method: 'DELETE' }); - if (response.ok) { - refreshGeofences(); - } else { - throw Error(await response.text()); - } - } catch (error) { - dispatch(errorsActions.push(error.message)); - } - }; - - map.on('draw.delete', listener); - return () => map.off('draw.delete', listener); - }, [dispatch, refreshGeofences]); - - useEffect(() => { - const listener = async (event) => { - const feature = event.features[0]; - const item = Object.values(geofences).find((i) => i.id === feature.id); - if (item) { - const updatedItem = { ...item, area: geometryToArea(feature.geometry) }; - try { - const response = await fetch(`/api/geofences/${feature.id}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(updatedItem), - }); - if (response.ok) { - refreshGeofences(); - } else { - throw Error(await response.text()); - } - } catch (error) { - dispatch(errorsActions.push(error.message)); - } - } - }; - - map.on('draw.update', listener); - return () => map.off('draw.update', listener); - }, [dispatch, geofences, refreshGeofences]); - - useEffect(() => { - draw.deleteAll(); - Object.values(geofences).forEach((geofence) => { - draw.add(geofenceToFeature(theme, geofence)); - }); - }, [geofences]); - - useEffect(() => { - if (selectedGeofenceId) { - const feature = draw.get(selectedGeofenceId); - let { coordinates } = feature.geometry; - if (Array.isArray(coordinates[0][0])) { - [coordinates] = coordinates; - } - const bounds = coordinates.reduce( - (bounds, coordinate) => bounds.extend(coordinate), - new maplibregl.LngLatBounds(coordinates[0], coordinates[1]), - ); - const canvas = map.getCanvas(); - map.fitBounds(bounds, { padding: Math.min(canvas.width, canvas.height) * 0.1 }); - } - }, [selectedGeofenceId]); - - return null; -}; - -export default MapGeofenceEdit; diff --git a/modern/src/map/draw/theme.js b/modern/src/map/draw/theme.js deleted file mode 100644 index c9864e2f..00000000 --- a/modern/src/map/draw/theme.js +++ /dev/null @@ -1,230 +0,0 @@ -// Copy of the original theme -// https://github.com/mapbox/mapbox-gl-draw/blob/v1.4.0/src/lib/theme.js - -export default [ - { - 'id': 'gl-draw-polygon-fill-inactive', - 'type': 'fill', - 'filter': ['all', - ['==', 'active', 'false'], - ['==', '$type', 'Polygon'], - ['!=', 'mode', 'static'] - ], - 'paint': { - 'fill-color': '#3bb2d0', - 'fill-outline-color': '#3bb2d0', - 'fill-opacity': 0.1 - } - }, - { - 'id': 'gl-draw-polygon-fill-active', - 'type': 'fill', - 'filter': ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']], - 'paint': { - 'fill-color': '#fbb03b', - 'fill-outline-color': '#fbb03b', - 'fill-opacity': 0.1 - } - }, - { - 'id': 'gl-draw-polygon-midpoint', - 'type': 'circle', - 'filter': ['all', - ['==', '$type', 'Point'], - ['==', 'meta', 'midpoint']], - 'paint': { - 'circle-radius': 3, - 'circle-color': '#fbb03b' - } - }, - { - 'id': 'gl-draw-polygon-stroke-inactive', - 'type': 'line', - 'filter': ['all', - ['==', 'active', 'false'], - ['==', '$type', 'Polygon'], - ['!=', 'mode', 'static'] - ], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#3bb2d0', - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-polygon-stroke-active', - 'type': 'line', - 'filter': ['all', ['==', 'active', 'true'], ['==', '$type', 'Polygon']], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#fbb03b', - 'line-dasharray': [0.2, 2], - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-line-inactive', - 'type': 'line', - 'filter': ['all', - ['==', 'active', 'false'], - ['==', '$type', 'LineString'], - ['!=', 'mode', 'static'] - ], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#3bb2d0', - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-line-active', - 'type': 'line', - 'filter': ['all', - ['==', '$type', 'LineString'], - ['==', 'active', 'true'] - ], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#fbb03b', - 'line-dasharray': [0.2, 2], - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-polygon-and-line-vertex-stroke-inactive', - 'type': 'circle', - 'filter': ['all', - ['==', 'meta', 'vertex'], - ['==', '$type', 'Point'], - ['!=', 'mode', 'static'] - ], - 'paint': { - 'circle-radius': 5, - 'circle-color': '#fff' - } - }, - { - 'id': 'gl-draw-polygon-and-line-vertex-inactive', - 'type': 'circle', - 'filter': ['all', - ['==', 'meta', 'vertex'], - ['==', '$type', 'Point'], - ['!=', 'mode', 'static'] - ], - 'paint': { - 'circle-radius': 3, - 'circle-color': '#fbb03b' - } - }, - { - 'id': 'gl-draw-point-point-stroke-inactive', - 'type': 'circle', - 'filter': ['all', - ['==', 'active', 'false'], - ['==', '$type', 'Point'], - ['==', 'meta', 'feature'], - ['!=', 'mode', 'static'] - ], - 'paint': { - 'circle-radius': 5, - 'circle-opacity': 1, - 'circle-color': '#fff' - } - }, - { - 'id': 'gl-draw-point-inactive', - 'type': 'circle', - 'filter': ['all', - ['==', 'active', 'false'], - ['==', '$type', 'Point'], - ['==', 'meta', 'feature'], - ['!=', 'mode', 'static'] - ], - 'paint': { - 'circle-radius': 3, - 'circle-color': '#3bb2d0' - } - }, - { - 'id': 'gl-draw-point-stroke-active', - 'type': 'circle', - 'filter': ['all', - ['==', '$type', 'Point'], - ['==', 'active', 'true'], - ['!=', 'meta', 'midpoint'] - ], - 'paint': { - 'circle-radius': 7, - 'circle-color': '#fff' - } - }, - { - 'id': 'gl-draw-point-active', - 'type': 'circle', - 'filter': ['all', - ['==', '$type', 'Point'], - ['!=', 'meta', 'midpoint'], - ['==', 'active', 'true']], - 'paint': { - 'circle-radius': 5, - 'circle-color': '#fbb03b' - } - }, - { - 'id': 'gl-draw-polygon-fill-static', - 'type': 'fill', - 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']], - 'paint': { - 'fill-color': '#404040', - 'fill-outline-color': '#404040', - 'fill-opacity': 0.1 - } - }, - { - 'id': 'gl-draw-polygon-stroke-static', - 'type': 'line', - 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Polygon']], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#404040', - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-line-static', - 'type': 'line', - 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'LineString']], - 'layout': { - 'line-cap': 'round', - 'line-join': 'round' - }, - 'paint': { - 'line-color': '#404040', - 'line-width': 2 - } - }, - { - 'id': 'gl-draw-point-static', - 'type': 'circle', - 'filter': ['all', ['==', 'mode', 'static'], ['==', '$type', 'Point']], - 'paint': { - 'circle-radius': 5, - 'circle-color': '#404040' - } - } -]; diff --git a/modern/src/map/geocoder/MapGeocoder.js b/modern/src/map/geocoder/MapGeocoder.js deleted file mode 100644 index de1791e6..00000000 --- a/modern/src/map/geocoder/MapGeocoder.js +++ /dev/null @@ -1,56 +0,0 @@ -import './geocoder.css'; -import maplibregl from 'maplibre-gl'; -import MaplibreGeocoder from '@maplibre/maplibre-gl-geocoder'; -import { useEffect } from 'react'; -import { useDispatch } from 'react-redux'; -import { map } from '../core/MapView'; -import { errorsActions } from '../../store'; - -const MapGeocoder = () => { - const dispatch = useDispatch(); - - useEffect(() => { - const geocoder = { - forwardGeocode: async (config) => { - const features = []; - try { - const request = `https://nominatim.openstreetmap.org/search?q=${config.query}&format=geojson&polygon_geojson=1&addressdetails=1`; - const response = await fetch(request); - const geojson = await response.json(); - geojson.features.forEach((feature) => { - const center = [ - feature.bbox[0] + (feature.bbox[2] - feature.bbox[0]) / 2, - feature.bbox[1] + (feature.bbox[3] - feature.bbox[1]) / 2, - ]; - features.push({ - type: 'Feature', - geometry: { - type: 'Point', - coordinates: center, - }, - place_name: feature.properties.display_name, - properties: feature.properties, - text: feature.properties.display_name, - place_type: ['place'], - center, - }); - }); - } catch (e) { - dispatch(errorsActions.push(e.message)); - } - return { features }; - }, - }; - - const control = new MaplibreGeocoder(geocoder, { - maplibregl, - collapsed: true, - }); - map.addControl(control); - return () => map.removeControl(control); - }, [dispatch]); - - return null; -}; - -export default MapGeocoder; diff --git a/modern/src/map/geocoder/geocoder.css b/modern/src/map/geocoder/geocoder.css deleted file mode 100644 index 86ebf6e5..00000000 --- a/modern/src/map/geocoder/geocoder.css +++ /dev/null @@ -1,223 +0,0 @@ -/* Basics */ -.maplibregl-ctrl-geocoder, -.maplibregl-ctrl-geocoder *, -.maplibregl-ctrl-geocoder *:after, -.maplibregl-ctrl-geocoder *:before { - box-sizing: border-box; -} - -.maplibregl-ctrl-geocoder { - font-size: 15px; - line-height: 20px; - font-family: "Open Sans", "Helvetica Neue", Arial, Helvetica, sans-serif; - position: relative; - background-color: #fff; - width: 100%; - min-width: 240px; - max-width: 360px; - z-index: 1; - border-radius: 4px; - transition: width 0.25s, min-width 0.25s; -} - -.maplibregl-ctrl-geocoder--input { - font: inherit; - width: 100%; - border: 0; - background-color: transparent; - margin: 0; - height: 29px; - color: #404040; /* fallback */ - color: rgba(0, 0, 0, 0.75); - padding: 6px 30px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; -} - -.maplibregl-ctrl-geocoder--input:focus { - color: #404040; /* fallback */ - color: rgba(0, 0, 0, 0.75); - outline: 0; - box-shadow: none; - outline: thin dotted; -} - -.maplibregl-ctrl-geocoder .maplibregl-ctrl-geocoder--pin-right > * { - z-index: 2; - position: absolute; - right: 5px; - top: 5px; - display: none; -} - -.maplibregl-ctrl-geocoder, -.maplibregl-ctrl-geocoder .suggestions { - box-shadow: 0 0 0 2px rgb(0 0 0 / 10%); -} - -/* Collapsed */ -.maplibregl-ctrl-geocoder.maplibregl-ctrl-geocoder--collapsed { - width: 29px; - min-width: 29px; - transition: width 0.25s, min-width 0.25s; -} - -/* Suggestions */ -.maplibregl-ctrl-geocoder .suggestions { - background-color: #fff; - border-radius: 4px; - left: 0; - list-style: none; - margin: 0; - padding: 0; - position: absolute; - width: 100%; - top: 110%; /* fallback */ - top: calc(100% + 6px); - z-index: 1000; - overflow: hidden; - font-size: 13px; -} - -.maplibregl-ctrl-bottom-left .suggestions, -.maplibregl-ctrl-bottom-right .suggestions { - top: auto; - bottom: 100%; -} - -.maplibregl-ctrl-geocoder .suggestions > li > a { - cursor: default; - display: block; - padding: 6px 12px; - color: #404040; -} - -.maplibregl-ctrl-geocoder .suggestions > .active > a, -.maplibregl-ctrl-geocoder .suggestions > li > a:hover { - color: #404040; - background-color: #f3f3f3; - text-decoration: none; - cursor: pointer; -} - -.maplibregl-ctrl-geocoder--suggestion { - display: flex; - flex-direction: row; - align-items: center; -} - -.maplibre-ctrl-geocoder--suggestion-icon { - min-width: 30px; - min-height: 24px; - max-width: 30px; - max-height: 24px; - padding-right: 12px; -} - -.maplibregl-ctrl-geocoder--suggestion-info { - display: flex; - flex-direction: column; -} - -.maplibregl-ctrl-geocoder--suggestion-match { - font-weight: bold; -} - -.maplibregl-ctrl-geocoder--suggestion-title, -.maplibregl-ctrl-geocoder--suggestion-address { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} - -.maplibregl-ctrl-geocoder--result { - display: flex; - flex-direction: row; - align-items: center; -} - -.maplibre-ctrl-geocoder--result-icon { - min-width: 30px; - min-height: 24px; - max-width: 30px; - max-height: 24px; - padding-right: 12px; -} - -.maplibregl-ctrl-geocoder--result-title { - font-weight: bold; -} - -.maplibregl-ctrl-geocoder--result-title, -.maplibregl-ctrl-geocoder--result-address { - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; -} - -/* Icons */ -.maplibregl-ctrl-geocoder--icon { - display: inline-block; - vertical-align: middle; -} - -.maplibregl-ctrl-geocoder--icon-search { - position: absolute; - top: 3px; - left: 3px; - width: 23px; - height: 23px; -} - -.maplibregl-ctrl-geocoder--button { - padding: 0; - margin: 0; - border: none; - cursor: pointer; - background: #fff; - line-height: 1; -} - -.maplibregl-ctrl-geocoder--icon-close { - width: 20px; - height: 20px; -} - -.maplibregl-ctrl-geocoder--icon-loading { - width: 20px; - height: 20px; - -moz-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); - -webkit-animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); - animation: rotate 0.8s infinite cubic-bezier(0.45, 0.05, 0.55, 0.95); -} - -/* Animation */ -@-webkit-keyframes rotate { - from { - -webkit-transform: rotate(0); - transform: rotate(0); - } - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -@keyframes rotate { - from { - -webkit-transform: rotate(0); - transform: rotate(0); - } - to { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } -} - -.maplibre-gl-geocoder--error { - color: #909090; - padding: 6px 12px; - font-size: 16px; - text-align: center; -} diff --git a/modern/src/map/main/MapAccuracy.js b/modern/src/map/main/MapAccuracy.js deleted file mode 100644 index 4f025b08..00000000 --- a/modern/src/map/main/MapAccuracy.js +++ /dev/null @@ -1,56 +0,0 @@ -import { useId, useEffect } from 'react'; -import circle from '@turf/circle'; -import { useTheme } from '@mui/styles'; -import { map } from '../core/MapView'; - -const MapAccuracy = ({ positions }) => { - const id = useId(); - - const theme = useTheme(); - - useEffect(() => { - map.addSource(id, { - type: 'geojson', - data: { - type: 'FeatureCollection', - features: [], - }, - }); - map.addLayer({ - source: id, - id, - type: 'fill', - filter: [ - 'all', - ['==', '$type', 'Polygon'], - ], - paint: { - 'fill-color': theme.palette.geometry.main, - 'fill-outline-color': theme.palette.geometry.main, - 'fill-opacity': 0.25, - }, - }); - - return () => { - if (map.getLayer(id)) { - map.removeLayer(id); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - }, []); - - useEffect(() => { - map.getSource(id)?.setData({ - type: 'FeatureCollection', - features: positions - .filter((position) => position.accuracy > 0) - .map((position) => circle([position.longitude, position.latitude], position.accuracy * 0.001)), - }); - }, [positions]); - - return null; -}; - -export default MapAccuracy; diff --git a/modern/src/map/main/MapDefaultCamera.js b/modern/src/map/main/MapDefaultCamera.js deleted file mode 100644 index 90b3061b..00000000 --- a/modern/src/map/main/MapDefaultCamera.js +++ /dev/null @@ -1,52 +0,0 @@ -import maplibregl from 'maplibre-gl'; -import { useEffect, useState } from 'react'; -import { useSelector } from 'react-redux'; -import { usePreference } from '../../common/util/preferences'; -import { map } from '../core/MapView'; - -const MapDefaultCamera = () => { - const selectedDeviceId = useSelector((state) => state.devices.selectedId); - const positions = useSelector((state) => state.session.positions); - - const defaultLatitude = usePreference('latitude'); - const defaultLongitude = usePreference('longitude'); - const defaultZoom = usePreference('zoom', 0); - - const [initialized, setInitialized] = useState(false); - - useEffect(() => { - if (selectedDeviceId) { - setInitialized(true); - } else if (!initialized) { - if (defaultLatitude && defaultLongitude) { - map.jumpTo({ - center: [defaultLongitude, defaultLatitude], - zoom: defaultZoom, - }); - setInitialized(true); - } else { - const coordinates = Object.values(positions).map((item) => [item.longitude, item.latitude]); - if (coordinates.length > 1) { - const bounds = coordinates.reduce((bounds, item) => bounds.extend(item), new maplibregl.LngLatBounds(coordinates[0], coordinates[1])); - const canvas = map.getCanvas(); - map.fitBounds(bounds, { - duration: 0, - padding: Math.min(canvas.width, canvas.height) * 0.1, - }); - setInitialized(true); - } else if (coordinates.length) { - const [individual] = coordinates; - map.jumpTo({ - center: individual, - zoom: Math.max(map.getZoom(), 10), - }); - setInitialized(true); - } - } - } - }, [selectedDeviceId, initialized, defaultLatitude, defaultLongitude, defaultZoom, positions]); - - return null; -}; - -export default MapDefaultCamera; diff --git a/modern/src/map/main/MapLiveRoutes.js b/modern/src/map/main/MapLiveRoutes.js deleted file mode 100644 index 44cdc6ca..00000000 --- a/modern/src/map/main/MapLiveRoutes.js +++ /dev/null @@ -1,83 +0,0 @@ -import { useId, useEffect } from 'react'; -import { useSelector } from 'react-redux'; -import { useTheme } from '@mui/styles'; -import { map } from '../core/MapView'; -import { useAttributePreference } from '../../common/util/preferences'; - -const MapLiveRoutes = () => { - const id = useId(); - - const theme = useTheme(); - - const type = useAttributePreference('mapLiveRoutes', 'none'); - - const devices = useSelector((state) => state.devices.items); - const selectedDeviceId = useSelector((state) => state.devices.selectedId); - - const history = useSelector((state) => state.session.history); - - useEffect(() => { - if (type !== 'none') { - map.addSource(id, { - type: 'geojson', - data: { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: [], - }, - }, - }); - map.addLayer({ - source: id, - id, - type: 'line', - layout: { - 'line-join': 'round', - 'line-cap': 'round', - }, - paint: { - 'line-color': ['get', 'color'], - 'line-width': 2, - }, - }); - - return () => { - if (map.getLayer(id)) { - map.removeLayer(id); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - } - return () => {}; - }, [type]); - - useEffect(() => { - if (type !== 'none') { - const deviceIds = Object.values(devices) - .map((device) => device.id) - .filter((id) => (type === 'selected' ? id === selectedDeviceId : true)) - .filter((id) => history.hasOwnProperty(id)); - - map.getSource(id)?.setData({ - type: 'FeatureCollection', - features: deviceIds.map((deviceId) => ({ - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: history[deviceId], - }, - properties: { - color: devices[deviceId].attributes['web.reportColor'] || theme.palette.geometry.main, - }, - })), - }); - } - }, [theme, type, devices, selectedDeviceId, history]); - - return null; -}; - -export default MapLiveRoutes; diff --git a/modern/src/map/main/MapSelectedDevice.js b/modern/src/map/main/MapSelectedDevice.js deleted file mode 100644 index caf40cf8..00000000 --- a/modern/src/map/main/MapSelectedDevice.js +++ /dev/null @@ -1,31 +0,0 @@ -import { useEffect } from 'react'; - -import { useSelector } from 'react-redux'; -import dimensions from '../../common/theme/dimensions'; -import { map } from '../core/MapView'; -import { usePrevious } from '../../reactHelper'; -import { useAttributePreference } from '../../common/util/preferences'; - -const MapSelectedDevice = () => { - const selectedDeviceId = useSelector((state) => state.devices.selectedId); - const previousDeviceId = usePrevious(selectedDeviceId); - - const selectZoom = useAttributePreference('web.selectZoom', 10); - const mapFollow = useAttributePreference('mapFollow', false); - - const position = useSelector((state) => state.session.positions[selectedDeviceId]); - - useEffect(() => { - if ((selectedDeviceId !== previousDeviceId || mapFollow) && position) { - map.easeTo({ - center: [position.longitude, position.latitude], - zoom: Math.max(map.getZoom(), selectZoom), - offset: [0, -dimensions.popupMapOffset / 2], - }); - } - }); - - return null; -}; - -export default MapSelectedDevice; diff --git a/modern/src/map/main/PoiMap.js b/modern/src/map/main/PoiMap.js deleted file mode 100644 index 07341183..00000000 --- a/modern/src/map/main/PoiMap.js +++ /dev/null @@ -1,87 +0,0 @@ -import { useId, useEffect, useState } from 'react'; -import { kml } from '@tmcw/togeojson'; -import { useTheme } from '@mui/styles'; -import { map } from '../core/MapView'; -import { useEffectAsync } from '../../reactHelper'; -import { usePreference } from '../../common/util/preferences'; -import { findFonts } from '../core/mapUtil'; - -const PoiMap = () => { - const id = useId(); - - const theme = useTheme(); - - const poiLayer = usePreference('poiLayer'); - - const [data, setData] = useState(null); - - useEffectAsync(async () => { - if (poiLayer) { - const file = await fetch(poiLayer); - const dom = new DOMParser().parseFromString(await file.text(), 'text/xml'); - setData(kml(dom)); - } - }, [poiLayer]); - - useEffect(() => { - if (data) { - map.addSource(id, { - type: 'geojson', - data, - }); - map.addLayer({ - source: id, - id: 'poi-point', - type: 'circle', - paint: { - 'circle-radius': 5, - 'circle-color': theme.palette.geometry.main, - }, - }); - map.addLayer({ - source: id, - id: 'poi-line', - type: 'line', - paint: { - 'line-color': theme.palette.geometry.main, - 'line-width': 2, - }, - }); - map.addLayer({ - source: id, - id: 'poi-title', - type: 'symbol', - layout: { - 'text-field': '{name}', - 'text-anchor': 'bottom', - 'text-offset': [0, -0.5], - 'text-font': findFonts(map), - 'text-size': 12, - }, - paint: { - 'text-halo-color': 'white', - 'text-halo-width': 1, - }, - }); - return () => { - if (map.getLayer('poi-point')) { - map.removeLayer('poi-point'); - } - if (map.getLayer('poi-line')) { - map.removeLayer('poi-line'); - } - if (map.getLayer('poi-title')) { - map.removeLayer('poi-title'); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - } - return () => {}; - }, [data]); - - return null; -}; - -export default PoiMap; diff --git a/modern/src/map/notification/MapNotification.js b/modern/src/map/notification/MapNotification.js deleted file mode 100644 index 81038f95..00000000 --- a/modern/src/map/notification/MapNotification.js +++ /dev/null @@ -1,49 +0,0 @@ -import { useEffect, useMemo } from 'react'; -import { map } from '../core/MapView'; -import './notification.css'; - -const statusClass = (status) => `maplibregl-ctrl-icon maplibre-ctrl-notification maplibre-ctrl-notification-${status}`; - -class NotificationControl { - constructor(eventHandler) { - this.eventHandler = eventHandler; - } - - onAdd() { - this.button = document.createElement('button'); - this.button.className = statusClass('off'); - this.button.type = 'button'; - this.button.onclick = () => this.eventHandler(this); - - this.container = document.createElement('div'); - this.container.className = 'maplibregl-ctrl-group maplibregl-ctrl'; - this.container.appendChild(this.button); - - return this.container; - } - - onRemove() { - this.container.parentNode.removeChild(this.container); - } - - setEnabled(enabled) { - this.button.className = statusClass(enabled ? 'on' : 'off'); - } -} - -const MapNotification = ({ enabled, onClick }) => { - const control = useMemo(() => new NotificationControl(onClick), [onClick]); - - useEffect(() => { - map.addControl(control); - return () => map.removeControl(control); - }, [onClick]); - - useEffect(() => { - control.setEnabled(enabled); - }, [enabled]); - - return null; -}; - -export default MapNotification; diff --git a/modern/src/map/notification/notification.css b/modern/src/map/notification/notification.css deleted file mode 100644 index 73db13bb..00000000 --- a/modern/src/map/notification/notification.css +++ /dev/null @@ -1,13 +0,0 @@ -.maplibre-ctrl-notification { - background-repeat: no-repeat; - background-position: center; - pointer-events: auto; -} - -.maplibre-ctrl-notification-on { - background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23f44336' d='m6.4728 3.4802-1.1412-1.1412c-1.9152 1.4604-3.1761 3.7108-3.2878 6.2645h1.596c0.1197-2.1148 1.205-3.9662 2.833-5.1233zm9.8875 5.1233h1.596c-0.1197-2.5537-1.3806-4.8041-3.2878-6.2645l-1.1332 1.1412c1.612 1.1571 2.7053 3.0085 2.825 5.1233zm-1.5721 0.39901c0-2.4499-1.3088-4.5008-3.5911-5.0435v-0.54265c0-0.66236-0.53467-1.197-1.197-1.197-0.66236 0-1.197 0.53467-1.197 1.197v0.54265c-2.2903 0.54265-3.5911 2.5856-3.5911 5.0435v3.9901l-1.596 1.596v0.79802h12.768v-0.79802l-1.596-1.596zm-4.7881 8.7782c0.11172 0 0.21546-8e-3 0.31921-0.03192 0.51871-0.11172 0.94166-0.46285 1.1491-0.94166 0.0798-0.19152 0.1197-0.39901 0.1197-0.62246h-3.1921c0.00798 0.87782 0.71822 1.596 1.604 1.596z' stroke-width='.79802'/%3E%3C/svg%3E"); -} - -.maplibre-ctrl-notification-off { - background-image: url("data:image/svg+xml,%3Csvg width='20' height='20' version='1.1' viewBox='0 0 20 20' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='m6.4728 3.4802-1.1412-1.1412c-1.9152 1.4604-3.1761 3.7108-3.2878 6.2645h1.596c0.1197-2.1148 1.205-3.9662 2.833-5.1233zm9.8875 5.1233h1.596c-0.1197-2.5537-1.3806-4.8041-3.2878-6.2645l-1.1332 1.1412c1.612 1.1571 2.7053 3.0085 2.825 5.1233zm-1.5721 0.39901c0-2.4499-1.3088-4.5008-3.5911-5.0435v-0.54265c0-0.66236-0.53467-1.197-1.197-1.197-0.66236 0-1.197 0.53467-1.197 1.197v0.54265c-2.2903 0.54265-3.5911 2.5856-3.5911 5.0435v3.9901l-1.596 1.596v0.79802h12.768v-0.79802l-1.596-1.596zm-4.7881 8.7782c0.11172 0 0.21546-8e-3 0.31921-0.03192 0.51871-0.11172 0.94166-0.46285 1.1491-0.94166 0.0798-0.19152 0.1197-0.39901 0.1197-0.62246h-3.1921c0.00798 0.87782 0.71822 1.596 1.604 1.596z' stroke-width='.79802'/%3E%3C/svg%3E"); -} diff --git a/modern/src/map/overlay/MapOverlay.js b/modern/src/map/overlay/MapOverlay.js deleted file mode 100644 index e436ea8d..00000000 --- a/modern/src/map/overlay/MapOverlay.js +++ /dev/null @@ -1,39 +0,0 @@ -import { useId, useEffect } from 'react'; -import { useAttributePreference } from '../../common/util/preferences'; -import { map } from '../core/MapView'; -import useMapOverlays from './useMapOverlays'; - -const MapOverlay = () => { - const id = useId(); - - const mapOverlays = useMapOverlays(); - const selectedMapOverlay = useAttributePreference('selectedMapOverlay'); - - const activeOverlay = mapOverlays.filter((overlay) => overlay.available).find((overlay) => overlay.id === selectedMapOverlay); - - useEffect(() => { - if (activeOverlay) { - map.addSource(id, activeOverlay.source); - map.addLayer({ - id, - type: 'raster', - source: id, - layout: { - visibility: 'visible', - }, - }); - } - return () => { - if (map.getLayer(id)) { - map.removeLayer(id); - } - if (map.getSource(id)) { - map.removeSource(id); - } - }; - }, [id, activeOverlay]); - - return null; -}; - -export default MapOverlay; diff --git a/modern/src/map/overlay/useMapOverlays.js b/modern/src/map/overlay/useMapOverlays.js deleted file mode 100644 index dafb5f83..00000000 --- a/modern/src/map/overlay/useMapOverlays.js +++ /dev/null @@ -1,103 +0,0 @@ -import { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { useTranslation } from '../../common/components/LocalizationProvider'; -import { useAttributePreference } from '../../common/util/preferences'; - -const sourceCustom = (urls) => ({ - type: 'raster', - tiles: urls, - tileSize: 256, - maxzoom: 18, -}); - -const sourceOpenWeather = (style, key) => sourceCustom([ - `https://tile.openweathermap.org/map/${style}/{z}/{x}/{y}.png?appid=${key}`, -]); - -export default () => { - const t = useTranslation(); - - const openWeatherKey = useAttributePreference('openWeatherKey'); - const tomTomKey = useAttributePreference('tomTomKey'); - const hereKey = useAttributePreference('hereKey'); - const customMapOverlay = useSelector((state) => state.session.server.overlayUrl); - - return useMemo(() => [ - { - id: 'openSeaMap', - title: t('mapOpenSeaMap'), - source: sourceCustom(['https://tiles.openseamap.org/seamark/{z}/{x}/{y}.png']), - available: true, - }, - { - id: 'openRailwayMap', - title: t('mapOpenRailwayMap'), - source: sourceCustom(['https://tiles.openrailwaymap.org/standard/{z}/{x}/{y}.png']), - available: true, - }, - { - id: 'openWeatherClouds', - title: t('mapOpenWeatherClouds'), - source: sourceOpenWeather('clouds_new', openWeatherKey), - available: !!openWeatherKey, - attribute: 'openWeatherKey', - }, - { - id: 'openWeatherPrecipitation', - title: t('mapOpenWeatherPrecipitation'), - source: sourceOpenWeather('precipitation_new', openWeatherKey), - available: !!openWeatherKey, - attribute: 'openWeatherKey', - }, - { - id: 'openWeatherPressure', - title: t('mapOpenWeatherPressure'), - source: sourceOpenWeather('pressure_new', openWeatherKey), - available: !!openWeatherKey, - attribute: 'openWeatherKey', - }, - { - id: 'openWeatherWind', - title: t('mapOpenWeatherWind'), - source: sourceOpenWeather('wind_new', openWeatherKey), - available: !!openWeatherKey, - attribute: 'openWeatherKey', - }, - { - id: 'openWeatherTemperature', - title: t('mapOpenWeatherTemperature'), - source: sourceOpenWeather('temp_new', openWeatherKey), - available: !!openWeatherKey, - attribute: 'openWeatherKey', - }, - { - id: 'tomTomFlow', - title: t('mapTomTomFlow'), - source: sourceCustom([`https://api.tomtom.com/traffic/map/4/tile/flow/absolute/{z}/{x}/{y}.png?key=${tomTomKey}`]), - available: !!tomTomKey, - attribute: 'tomTomKey', - }, - { - id: 'tomTomIncidents', - title: t('mapTomTomIncidents'), - source: sourceCustom([`https://api.tomtom.com/traffic/map/4/tile/incidents/s3/{z}/{x}/{y}.png?key=${tomTomKey}`]), - available: !!tomTomKey, - attribute: 'tomTomKey', - }, - { - id: 'hereFlow', - title: t('mapHereFlow'), - source: sourceCustom( - [1, 2, 3, 4].map((i) => `https://${i}.traffic.maps.ls.hereapi.com/maptile/2.1/flowtile/newest/normal.day/{z}/{x}/{y}/256/png8?apiKey=${hereKey}`), - ), - available: !!hereKey, - attribute: 'hereKey', - }, - { - id: 'custom', - title: t('mapOverlayCustom'), - source: sourceCustom(customMapOverlay), - available: !!customMapOverlay, - }, - ], [t, openWeatherKey, tomTomKey, hereKey, customMapOverlay]); -}; diff --git a/modern/src/map/switcher/switcher.css b/modern/src/map/switcher/switcher.css deleted file mode 100644 index afba84d6..00000000 --- a/modern/src/map/switcher/switcher.css +++ /dev/null @@ -1,34 +0,0 @@ -.maplibregl-style-list { - display: none; -} - -.maplibregl-ctrl-group .maplibregl-style-list button { - background: none; - border: none; - cursor: pointer; - display: block; - font-size: 14px; - padding: 8px 8px 6px; - text-align: right; - width: 100%; - height: auto; -} - -.maplibregl-style-list button.active { - font-weight: bold; -} - -.maplibregl-style-list button:hover { - background-color: rgba(0, 0, 0, 0.05); -} - -.maplibregl-style-list button + button { - border-top: 1px solid #ddd; -} - -.maplibregl-style-switcher { - background-image: url(data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/PjwhRE9DVFlQRSBzdmcgUFVCTElDICItLy9XM0MvL0RURCBTVkcgMS4xLy9FTiIgImh0dHA6Ly93d3cudzMub3JnL0dyYXBoaWNzL1NWRy8xLjEvRFREL3N2ZzExLmR0ZCI+PHN2ZyB2ZXJzaW9uPSIxLjEiIGlkPSJDYXBhXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IiB3aWR0aD0iNTQuODQ5cHgiIGhlaWdodD0iNTQuODQ5cHgiIHZpZXdCb3g9IjAgMCA1NC44NDkgNTQuODQ5IiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1NC44NDkgNTQuODQ5OyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+PGc+PGc+PGc+PHBhdGggZD0iTTU0LjQ5NywzOS42MTRsLTEwLjM2My00LjQ5bC0xNC45MTcsNS45NjhjLTAuNTM3LDAuMjE0LTEuMTY1LDAuMzE5LTEuNzkzLDAuMzE5Yy0wLjYyNywwLTEuMjU0LTAuMTA0LTEuNzktMC4zMThsLTE0LjkyMS01Ljk2OEwwLjM1MSwzOS42MTRjLTAuNDcyLDAuMjAzLTAuNDY3LDAuNTI0LDAuMDEsMC43MTZMMjYuNTYsNTAuODFjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMEw1NC40ODgsNDAuMzNDNTQuOTY0LDQwLjEzOSw1NC45NjksMzkuODE3LDU0LjQ5NywzOS42MTR6Ii8+PHBhdGggZD0iTTU0LjQ5NywyNy41MTJsLTEwLjM2NC00LjQ5MWwtMTQuOTE2LDUuOTY2Yy0wLjUzNiwwLjIxNS0xLjE2NSwwLjMyMS0xLjc5MiwwLjMyMWMtMC42MjgsMC0xLjI1Ni0wLjEwNi0xLjc5My0wLjMyMWwtMTQuOTE4LTUuOTY2TDAuMzUxLDI3LjUxMmMtMC40NzIsMC4yMDMtMC40NjcsMC41MjMsMC4wMSwwLjcxNkwyNi41NiwzOC43MDZjMC40NzcsMC4xOSwxLjI1MSwwLjE5LDEuNzI5LDBsMjYuMTk5LTEwLjQ3OUM1NC45NjQsMjguMDM2LDU0Ljk2OSwyNy43MTYsNTQuNDk3LDI3LjUxMnoiLz48cGF0aCBkPSJNMC4zNjEsMTYuMTI1bDEzLjY2Miw1LjQ2NWwxMi41MzcsNS4wMTVjMC40NzcsMC4xOTEsMS4yNTEsMC4xOTEsMS43MjksMGwxMi41NDEtNS4wMTZsMTMuNjU4LTUuNDYzYzAuNDc3LTAuMTkxLDAuNDgtMC41MTEsMC4wMS0wLjcxNkwyOC4yNzcsNC4wNDhjLTAuNDcxLTAuMjA0LTEuMjM2LTAuMjA0LTEuNzA4LDBMMC4zNTEsMTUuNDFDLTAuMTIxLDE1LjYxNC0wLjExNiwxNS45MzUsMC4zNjEsMTYuMTI1eiIvPjwvZz48L2c+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjxnPjwvZz48Zz48L2c+PGc+PC9nPjwvc3ZnPg==); - background-position: center; - background-repeat: no-repeat; - background-size: 70%; -} diff --git a/modern/src/map/switcher/switcher.js b/modern/src/map/switcher/switcher.js deleted file mode 100644 index b6c62a6e..00000000 --- a/modern/src/map/switcher/switcher.js +++ /dev/null @@ -1,123 +0,0 @@ -import './switcher.css'; - -export class SwitcherControl { - - constructor(onBeforeSwitch, onSwitch, onAfterSwitch) { - this.onBeforeSwitch = onBeforeSwitch; - this.onSwitch = onSwitch; - this.onAfterSwitch = onAfterSwitch; - this.onDocumentClick = this.onDocumentClick.bind(this); - this.styles = []; - this.currentStyle = null; - } - - getDefaultPosition() { - return 'top-right'; - } - - updateStyles(updatedStyles, defaultStyle) { - this.styles = updatedStyles; - - let selectedStyle = null; - for (const style of this.styles) { - if (style.id === (this.currentStyle || defaultStyle)) { - selectedStyle = style.id; - break; - } - } - if (!selectedStyle) { - selectedStyle = this.styles[0].id; - } - - while (this.mapStyleContainer.firstChild) { - this.mapStyleContainer.removeChild(this.mapStyleContainer.firstChild); - } - - let selectedStyleElement; - - for (const style of this.styles) { - const styleElement = document.createElement('button'); - styleElement.type = 'button'; - styleElement.innerText = style.title; - styleElement.dataset.id = style.id; - styleElement.dataset.style = JSON.stringify(style.style); - styleElement.addEventListener('click', (event) => { - const { target } = event; - if (!target.classList.contains('active')) { - this.onSelectStyle(target); - } - }); - if (style.id === selectedStyle) { - selectedStyleElement = styleElement; - styleElement.classList.add('active'); - } - this.mapStyleContainer.appendChild(styleElement); - } - - if (this.currentStyle !== selectedStyle) { - this.onSelectStyle(selectedStyleElement); - this.currentStyle = selectedStyle; - } - } - - onSelectStyle(target) { - this.onBeforeSwitch(); - - const style = this.styles.find((it) => it.id === target.dataset.id); - this.map.setStyle(style.style, { diff: false }); - this.map.setTransformRequest(style.transformRequest); - - this.onSwitch(target.dataset.id); - - this.mapStyleContainer.style.display = 'none'; - this.styleButton.style.display = 'block'; - - const elements = this.mapStyleContainer.getElementsByClassName('active'); - while (elements[0]) { - elements[0].classList.remove('active'); - } - target.classList.add('active'); - - this.currentStyle = target.dataset.id; - - this.onAfterSwitch(); - } - - onAdd(map) { - this.map = map; - this.controlContainer = document.createElement('div'); - this.controlContainer.classList.add('maplibregl-ctrl'); - this.controlContainer.classList.add('maplibregl-ctrl-group'); - this.mapStyleContainer = document.createElement('div'); - this.styleButton = document.createElement('button'); - this.styleButton.type = 'button'; - this.mapStyleContainer.classList.add('maplibregl-style-list'); - this.styleButton.classList.add('maplibregl-ctrl-icon'); - this.styleButton.classList.add('maplibregl-style-switcher'); - this.styleButton.addEventListener('click', () => { - this.styleButton.style.display = 'none'; - this.mapStyleContainer.style.display = 'block'; - }); - document.addEventListener('click', this.onDocumentClick); - this.controlContainer.appendChild(this.styleButton); - this.controlContainer.appendChild(this.mapStyleContainer); - return this.controlContainer; - } - - onRemove() { - if (!this.controlContainer || !this.controlContainer.parentNode || !this.map || !this.styleButton) { - return; - } - this.styleButton.removeEventListener('click', this.onDocumentClick); - this.controlContainer.parentNode.removeChild(this.controlContainer); - document.removeEventListener('click', this.onDocumentClick); - this.map = undefined; - } - - onDocumentClick(event) { - if (this.controlContainer && !this.controlContainer.contains(event.target) && this.mapStyleContainer && this.styleButton) { - this.mapStyleContainer.style.display = 'none'; - this.styleButton.style.display = 'block'; - } - } -} |