aboutsummaryrefslogtreecommitdiff
path: root/modern
diff options
context:
space:
mode:
authorAnton Tananaev <anton@traccar.org>2022-06-12 06:30:41 -0700
committerAnton Tananaev <anton@traccar.org>2022-06-12 06:30:41 -0700
commit127a8bfc5df8cbe66d5f60023fcf983e15ee13e8 (patch)
tree4df390424bd2671851fcb69121d4db36be757be9 /modern
parente7995f3b7a6b0828b81bebaf556ef7afb59bef61 (diff)
downloadtrackermap-web-127a8bfc5df8cbe66d5f60023fcf983e15ee13e8.tar.gz
trackermap-web-127a8bfc5df8cbe66d5f60023fcf983e15ee13e8.tar.bz2
trackermap-web-127a8bfc5df8cbe66d5f60023fcf983e15ee13e8.zip
Support map overlays
Diffstat (limited to 'modern')
-rw-r--r--modern/src/main/MainPage.js2
-rw-r--r--modern/src/map/overlay/MapOverlay.js39
-rw-r--r--modern/src/map/overlay/useMapOverlays.js29
-rw-r--r--modern/src/settings/PreferencesPage.js27
-rw-r--r--modern/src/settings/ServerPage.js5
5 files changed, 102 insertions, 0 deletions
diff --git a/modern/src/main/MainPage.js b/modern/src/main/MainPage.js
index 4bc703f6..b93a3940 100644
--- a/modern/src/main/MainPage.js
+++ b/modern/src/main/MainPage.js
@@ -33,6 +33,7 @@ import MapLiveRoutes from '../map/main/MapLiveRoutes';
import { useDeviceReadonly } from '../common/util/permissions';
import MapPositions from '../map/MapPositions';
import MapDirection from '../map/MapDirection';
+import MapOverlay from '../map/overlay/MapOverlay';
const useStyles = makeStyles((theme) => ({
root: {
@@ -199,6 +200,7 @@ const MainPage = () => {
return (
<div className={classes.root}>
<MapView>
+ <MapOverlay />
<MapGeofence />
<MapAccuracy />
{mapLiveRoutes && <MapLiveRoutes />}
diff --git a/modern/src/map/overlay/MapOverlay.js b/modern/src/map/overlay/MapOverlay.js
new file mode 100644
index 00000000..ec51c7c9
--- /dev/null
+++ b/modern/src/map/overlay/MapOverlay.js
@@ -0,0 +1,39 @@
+import { useEffect } from 'react';
+import usePersistedState from '../../common/util/usePersistedState';
+import { map } from '../core/MapView';
+import useMapOverlays from './useMapOverlays';
+
+const MapOverlay = () => {
+ const id = 'overlay';
+
+ const mapOverlays = useMapOverlays();
+ const [selectedMapOverlay] = usePersistedState('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);
+ }
+ };
+ });
+
+ return null;
+};
+
+export default MapOverlay;
diff --git a/modern/src/map/overlay/useMapOverlays.js b/modern/src/map/overlay/useMapOverlays.js
new file mode 100644
index 00000000..ede1e5b9
--- /dev/null
+++ b/modern/src/map/overlay/useMapOverlays.js
@@ -0,0 +1,29 @@
+import { useSelector } from 'react-redux';
+import { useTranslation } from '../../common/components/LocalizationProvider';
+
+const sourceCustom = (url) => ({
+ type: 'raster',
+ tiles: [url],
+ tileSize: 256,
+});
+
+export default () => {
+ const t = useTranslation();
+
+ const customMapOverlay = useSelector((state) => state.session.server?.overlayUrl);
+
+ return [
+ {
+ id: 'openSeaMap',
+ title: t('mapOpenSeaMap'),
+ source: sourceCustom('http://tiles.openseamap.org/seamark/{z}/{x}/{y}.png'),
+ available: true,
+ },
+ {
+ id: 'custom',
+ title: t('mapOverlayCustom'),
+ source: sourceCustom(customMapOverlay),
+ available: !!customMapOverlay,
+ },
+ ];
+};
diff --git a/modern/src/settings/PreferencesPage.js b/modern/src/settings/PreferencesPage.js
index 2a2674f7..d6589231 100644
--- a/modern/src/settings/PreferencesPage.js
+++ b/modern/src/settings/PreferencesPage.js
@@ -14,6 +14,7 @@ import usePositionAttributes from '../common/attributes/usePositionAttributes';
import { prefixString, unprefixString } from '../common/util/stringUtils';
import SelectField from '../common/components/SelectField';
import useMapStyles from '../map/core/useMapStyles';
+import useMapOverlays from '../map/overlay/useMapOverlays';
const useStyles = makeStyles((theme) => ({
container: {
@@ -40,6 +41,9 @@ const PreferencesPage = () => {
const mapStyles = useMapStyles();
const [activeMapStyles, setActiveMapStyles] = usePersistedState('activeMapStyles', ['locationIqStreets', 'osm', 'carto']);
+ const mapOverlays = useMapOverlays();
+ const [selectedMapOverlay, setSelectedMapOverlay] = usePersistedState('selectedMapOverlay');
+
const positionAttributes = usePositionAttributes(t);
const [positionItems, setPositionItems] = usePersistedState('positionItems', ['speed', 'address', 'totalDistance', 'course']);
@@ -109,6 +113,29 @@ const PreferencesPage = () => {
</Select>
</FormControl>
<FormControl>
+ <InputLabel>{t('mapOverlay')}</InputLabel>
+ <Select
+ label={t('mapOverlay')}
+ value={selectedMapOverlay}
+ onChange={(e) => {
+ const clicked = mapOverlays.find((o) => o.id === e.target.value);
+ if (!clicked || clicked.available) {
+ setSelectedMapOverlay(e.target.value);
+ } else if (clicked.id !== 'custom') {
+ const query = new URLSearchParams({ attribute: clicked.attribute });
+ navigate(`/settings/user/${userId}?${query.toString()}`);
+ }
+ }}
+ >
+ <MenuItem value="">{'\u00a0'}</MenuItem>
+ {mapOverlays.map((overlay) => (
+ <MenuItem key={overlay.id} value={overlay.id}>
+ <Typography component="span" color={overlay.available ? 'textPrimary' : 'error'}>{overlay.title}</Typography>
+ </MenuItem>
+ ))}
+ </Select>
+ </FormControl>
+ <FormControl>
<InputLabel>{t('sharedAttributes')}</InputLabel>
<Select
label={t('sharedAttributes')}
diff --git a/modern/src/settings/ServerPage.js b/modern/src/settings/ServerPage.js
index 25b71e37..26ef5bbf 100644
--- a/modern/src/settings/ServerPage.js
+++ b/modern/src/settings/ServerPage.js
@@ -96,6 +96,11 @@ const ServerPage = () => {
label={t('mapCustomLabel')}
/>
<TextField
+ value={item.overlayUrl || ''}
+ onChange={(event) => setItem({ ...item, overlayUrl: event.target.value })}
+ label={t('mapOverlayCustom')}
+ />
+ <TextField
type="number"
value={item.latitude || 0}
onChange={(event) => setItem({ ...item, latitude: Number(event.target.value) })}