aboutsummaryrefslogtreecommitdiff
path: root/modern/src/map
diff options
context:
space:
mode:
Diffstat (limited to 'modern/src/map')
-rw-r--r--modern/src/map/MapCamera.js32
-rw-r--r--modern/src/map/MapCurrentLocation.js21
-rw-r--r--modern/src/map/MapGeofence.js94
-rw-r--r--modern/src/map/MapMarkers.js89
-rw-r--r--modern/src/map/MapPadding.js20
-rw-r--r--modern/src/map/MapPositions.js216
-rw-r--r--modern/src/map/MapRoutePath.js100
-rw-r--r--modern/src/map/MapRoutePoints.js77
-rw-r--r--modern/src/map/MapScale.js34
-rw-r--r--modern/src/map/core/MapView.jsx123
-rw-r--r--modern/src/map/core/mapUtil.js105
-rw-r--r--modern/src/map/core/preloadImages.js78
-rw-r--r--modern/src/map/core/useMapStyles.js259
-rw-r--r--modern/src/map/draw/MapGeofenceEdit.js161
-rw-r--r--modern/src/map/draw/theme.js230
-rw-r--r--modern/src/map/geocoder/MapGeocoder.js56
-rw-r--r--modern/src/map/geocoder/geocoder.css223
-rw-r--r--modern/src/map/main/MapAccuracy.js56
-rw-r--r--modern/src/map/main/MapDefaultCamera.js52
-rw-r--r--modern/src/map/main/MapLiveRoutes.js83
-rw-r--r--modern/src/map/main/MapSelectedDevice.js31
-rw-r--r--modern/src/map/main/PoiMap.js87
-rw-r--r--modern/src/map/notification/MapNotification.js49
-rw-r--r--modern/src/map/notification/notification.css13
-rw-r--r--modern/src/map/overlay/MapOverlay.js39
-rw-r--r--modern/src/map/overlay/useMapOverlays.js103
-rw-r--r--modern/src/map/switcher/switcher.css34
-rw-r--r--modern/src/map/switcher/switcher.js123
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';
- }
- }
-}