aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnton Tananaev <anton.tananaev@gmail.com>2020-06-06 23:52:25 -0700
committerAnton Tananaev <anton.tananaev@gmail.com>2020-06-06 23:52:25 -0700
commitcf20c43948b83539a9a049a81796e15509c393b4 (patch)
treeadd07433f014145fd9f65fde58eb26d0edd9d212
parentb0afb5deea4de4cc5951d4e6fe17d2040546b8b0 (diff)
downloadtrackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.tar.gz
trackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.tar.bz2
trackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.zip
Reuse map component
-rw-r--r--modern/src/MainMap.js263
-rw-r--r--modern/src/MainPage.js2
-rw-r--r--modern/src/SocketController.js2
-rw-r--r--modern/src/mapManager.js150
-rw-r--r--modern/src/reactHelper.js10
5 files changed, 240 insertions, 187 deletions
diff --git a/modern/src/MainMap.js b/modern/src/MainMap.js
index 35e5348a..72f718e7 100644
--- a/modern/src/MainMap.js
+++ b/modern/src/MainMap.js
@@ -1,28 +1,31 @@
-import 'mapbox-gl/dist/mapbox-gl.css';
-import React, { Component } from 'react';
-import { connect } from 'react-redux';
-import mapboxgl from 'mapbox-gl';
+import React, { useRef, useLayoutEffect, useEffect, useState } from 'react';
+import { useSelector } from 'react-redux';
-const calculateMapCenter = (state) => {
- if (state.devices.selectedId) {
- const position = state.positions.items[state.devices.selectedId] || null;
- if (position) {
- return [position.longitude, position.latitude];
- }
- }
- return null;
-};
+import mapManager from './mapManager';
+
+const MainMap = () => {
+ const containerEl = useRef(null);
-const mapFeatureProperties = (state, position) => {
- const device = state.devices.items[position.deviceId] || null;
- return {
- name: device ? device.name : ''
- }
-};
+ const [mapReady, setMapReady] = useState(false);
-const mapStateToProps = state => ({
- mapCenter: calculateMapCenter(state),
- data: {
+ const mapCenter = useSelector(state => {
+ if (state.devices.selectedId) {
+ const position = state.positions.items[state.devices.selectedId] || null;
+ if (position) {
+ return [position.longitude, position.latitude];
+ }
+ }
+ return null;
+ });
+
+ const createFeature = (state, position) => {
+ const device = state.devices.items[position.deviceId] || null;
+ return {
+ name: device ? device.name : '',
+ }
+ };
+
+ const positions = useSelector(state => ({
type: 'FeatureCollection',
features: Object.values(state.positions.items).map(position => ({
type: 'Feature',
@@ -30,179 +33,69 @@ const mapStateToProps = state => ({
type: 'Point',
coordinates: [position.longitude, position.latitude]
},
- properties: mapFeatureProperties(state, position)
- }))
- }
-});
-
-class MainMap extends Component {
- componentDidMount() {
- /*const map = new mapboxgl.Map({
- container: this.mapContainer,
- style: 'https://cdn.traccar.com/map/basic.json',
- center: [0, 0],
- zoom: 1
- });*/
-
- const map = new mapboxgl.Map({
- container: this.mapContainer,
- style: {
- 'version': 8,
- 'sources': {
- 'raster-tiles': {
- 'type': 'raster',
- 'tiles': [
- 'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
- 'https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
- 'https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
- 'https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png'
- ],
- 'tileSize': 256,
- 'attribution': '© <a target="_top" rel="noopener" href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a target="_top" rel="noopener" href="https://carto.com/attribution">CARTO</a>'
- }
- },
- 'glyphs': 'https://cdn.traccar.com/map/fonts/{fontstack}/{range}.pbf',
- 'layers': [
- {
- 'id': 'simple-tiles',
- 'type': 'raster',
- 'source': 'raster-tiles',
- 'minzoom': 0,
- 'maxzoom': 22
- }
- ]
- },
- center: [0, 0],
- zoom: 1
- });
+ properties: createFeature(state, position),
+ })),
+ }));
+
+ useLayoutEffect(() => {
+ const currentEl = containerEl.current;
+ currentEl.appendChild(mapManager.element);
+ if (mapManager.map) {
+ mapManager.map.resize();
+ }
+ return () => {
+ currentEl.removeChild(mapManager.element);
+ };
+ }, [containerEl]);
- map.on('load', () => this.mapDidLoad(map));
- }
+ useEffect(() => {
+ mapManager.registerListener(() => setMapReady(true));
+ }, []);
- loadImage(key, url) {
- return new Promise(resolutionFunc => {
- const image = new Image();
- image.onload = () => {
- const canvas = document.createElement('canvas');
- canvas.width = image.width * window.devicePixelRatio;
- canvas.height = image.height * window.devicePixelRatio;
- canvas.style.width = `${image.width}px`;
- canvas.style.height = `${image.height}px`;
- const context = canvas.getContext('2d');
- context.drawImage(image, 0, 0, canvas.width, canvas.height);
- this.map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), {
- pixelRatio: window.devicePixelRatio
+ useEffect(() => {
+ if (mapReady) {
+ mapManager.map.addSource('positions', {
+ 'type': 'geojson',
+ 'data': positions,
+ });
+ mapManager.addLayer('device-background', 'positions', 'background', '{name}');
+ mapManager.addLayer('device-icon', 'positions', 'icon-marker');
+
+ const bounds = mapManager.calculateBounds(positions.features);
+ if (bounds) {
+ mapManager.map.fitBounds(bounds, {
+ padding: 100,
+ maxZoom: 9
});
- resolutionFunc()
}
- image.src = url;
- });
- }
-
- mapDidLoad(map) {
- this.map = map;
-
- Promise.all([
- this.loadImage('background', 'images/background.svg'),
- this.loadImage('icon-marker', 'images/icon/marker.svg')
- ]).then(() => {
- this.imagesDidLoad();
- });
- }
- imagesDidLoad() {
- this.map.addSource('positions', {
- 'type': 'geojson',
- 'data': this.props.data
- });
-
- this.map.addLayer({
- 'id': 'device-background',
- 'type': 'symbol',
- 'source': 'positions',
- 'layout': {
- 'icon-image': 'background',
- 'icon-allow-overlap': true,
- 'text-field': '{name}',
- 'text-allow-overlap': true,
- 'text-anchor': 'bottom',
- 'text-offset': [0, -2],
- 'text-font': ['Roboto Regular'],
- 'text-size': 12
- },
- 'paint':{
- 'text-halo-color': 'white',
- 'text-halo-width': 1
- }
- });
+ return () => {
+ mapManager.map.removeLayer('device-background');
+ mapManager.map.removeLayer('device-icon');
+ mapManager.map.removeSource('positions');
+ };
+ }
+ }, [mapReady]);
- this.map.addLayer({
- 'id': 'device-icon',
- 'type': 'symbol',
- 'source': 'positions',
- 'layout': {
- 'icon-image': 'icon-marker',
- 'icon-allow-overlap': true
- }
+ useEffect(() => {
+ mapManager.map.easeTo({
+ center: mapCenter
});
+ }, [mapCenter]);
- this.map.addControl(new mapboxgl.NavigationControl());
-
- const bounds = this.calculateBounds();
- if (bounds) {
- this.map.fitBounds(bounds, {
- padding: 100,
- maxZoom: 9
- });
- }
- }
-
- calculateBounds() {
- if (this.props.data.features && this.props.data.features.length) {
- const first = this.props.data.features[0].geometry.coordinates;
- const bounds = [[...first], [...first]];
- for (let feature of this.props.data.features) {
- const longitude = feature.geometry.coordinates[0]
- const latitude = feature.geometry.coordinates[1]
- if (longitude < bounds[0][0]) {
- bounds[0][0] = longitude;
- } else if (longitude > bounds[1][0]) {
- bounds[1][0] = longitude;
- }
- if (latitude < bounds[0][1]) {
- bounds[0][1] = latitude;
- } else if (latitude > bounds[1][1]) {
- bounds[1][1] = latitude;
- }
- }
- return bounds;
- } else {
- return null;
+ useEffect(() => {
+ const source = mapManager.map.getSource('positions');
+ if (source) {
+ source.setData(positions);
}
- }
+ }, [positions]);
- componentDidUpdate(prevProps) {
- if (this.map) {
- if (prevProps.mapCenter !== this.props.mapCenter) {
- this.map.easeTo({
- center: this.props.mapCenter
- });
- }
- if (prevProps.data.features !== this.props.data.features) {
- this.map.getSource('positions').setData(this.props.data);
- }
- }
- }
+ const style = {
+ width: '100%',
+ height: '100%',
+ };
- render() {
- const style = {
- position: 'relative',
- overflow: 'hidden',
- width: '100%',
- height: '100%'
- };
- return <div style={style} ref={el => this.mapContainer = el} />;
- }
+ return <div style={style} ref={containerEl} />;
}
-export default connect(mapStateToProps)(MainMap);
+export default MainMap;
diff --git a/modern/src/MainPage.js b/modern/src/MainPage.js
index 99311b2b..bc792470 100644
--- a/modern/src/MainPage.js
+++ b/modern/src/MainPage.js
@@ -57,7 +57,7 @@ const MainPage = ({ width }) => {
}
});
}
- }, [authenticated, history]);
+ }, [authenticated]);
return !authenticated ? (<LinearProgress />) : (
<div className={classes.root}>
diff --git a/modern/src/SocketController.js b/modern/src/SocketController.js
index d693c43d..6de3369a 100644
--- a/modern/src/SocketController.js
+++ b/modern/src/SocketController.js
@@ -54,7 +54,7 @@ const SocketController = (props) => {
}
connectSocket();
});
- }, [dispatch, connectSocket]);
+ }, []);
return null;
}
diff --git a/modern/src/mapManager.js b/modern/src/mapManager.js
new file mode 100644
index 00000000..a16fc5f7
--- /dev/null
+++ b/modern/src/mapManager.js
@@ -0,0 +1,150 @@
+import 'mapbox-gl/dist/mapbox-gl.css';
+import mapboxgl from 'mapbox-gl';
+
+let ready = false;
+let registeredListener = null;
+
+const registerListener = listener => {
+ if (ready) {
+ listener();
+ } else {
+ registeredListener = listener;
+ }
+};
+
+const loadImage = (key, url) => {
+ return new Promise(imageLoaded => {
+ const image = new Image();
+ image.onload = () => {
+ const canvas = document.createElement('canvas');
+ canvas.width = image.width * window.devicePixelRatio;
+ canvas.height = image.height * window.devicePixelRatio;
+ canvas.style.width = `${image.width}px`;
+ canvas.style.height = `${image.height}px`;
+ const context = canvas.getContext('2d');
+ context.drawImage(image, 0, 0, canvas.width, canvas.height);
+ map.addImage(key, context.getImageData(0, 0, canvas.width, canvas.height), {
+ pixelRatio: window.devicePixelRatio
+ });
+ imageLoaded()
+ }
+ image.src = url;
+ });
+};
+
+const addLayer = (id, source, icon, text) => {
+ const layer = {
+ 'id': id,
+ 'type': 'symbol',
+ 'source': source,
+ 'layout': {
+ 'icon-image': icon,
+ 'icon-allow-overlap': true,
+ },
+ };
+ if (text) {
+ layer.layout = {
+ ...layer.layout,
+ 'text-field': text,
+ 'text-anchor': 'bottom',
+ 'text-offset': [0, -2],
+ 'text-font': ['Roboto Regular'],
+ 'text-size': 12,
+ }
+ layer.paint = {
+ 'text-halo-color': 'white',
+ 'text-halo-width': 1,
+ }
+ }
+ map.addLayer(layer);
+}
+
+const calculateBounds = features => {
+ if (features && features.length) {
+ const first = features[0].geometry.coordinates;
+ const bounds = [[...first], [...first]];
+ for (let feature of features) {
+ const longitude = feature.geometry.coordinates[0]
+ const latitude = feature.geometry.coordinates[1]
+ if (longitude < bounds[0][0]) {
+ bounds[0][0] = longitude;
+ } else if (longitude > bounds[1][0]) {
+ bounds[1][0] = longitude;
+ }
+ if (latitude < bounds[0][1]) {
+ bounds[0][1] = latitude;
+ } else if (latitude > bounds[1][1]) {
+ bounds[1][1] = latitude;
+ }
+ }
+ return bounds;
+ } else {
+ return null;
+ }
+}
+
+const element = document.createElement('div');
+element.style.width = '100%';
+element.style.height = '100%';
+
+/*map = new mapboxgl.Map({
+ container: this.mapContainer,
+ style: 'https://cdn.traccar.com/map/basic.json',
+ center: [0, 0],
+ zoom: 1
+});*/
+
+const map = new mapboxgl.Map({
+ container: element,
+ style: {
+ 'version': 8,
+ 'sources': {
+ 'raster-tiles': {
+ 'type': 'raster',
+ 'tiles': [
+ 'https://a.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
+ 'https://b.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
+ 'https://c.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png',
+ 'https://d.basemaps.cartocdn.com/rastertiles/voyager/{z}/{x}/{y}@2x.png'
+ ],
+ 'tileSize': 256,
+ 'attribution': '© <a target="_top" rel="noopener" href="http://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, © <a target="_top" rel="noopener" href="https://carto.com/attribution">CARTO</a>'
+ }
+ },
+ 'glyphs': 'https://cdn.traccar.com/map/fonts/{fontstack}/{range}.pbf',
+ 'layers': [
+ {
+ 'id': 'simple-tiles',
+ 'type': 'raster',
+ 'source': 'raster-tiles',
+ 'minzoom': 0,
+ 'maxzoom': 22
+ }
+ ]
+ },
+ center: [0, 0],
+ zoom: 1
+});
+
+map.addControl(new mapboxgl.NavigationControl());
+
+map.on('load', () => {
+ Promise.all([
+ loadImage('background', 'images/background.svg'),
+ loadImage('icon-marker', 'images/icon/marker.svg')
+ ]).then(() => {
+ ready = true;
+ if (registeredListener) {
+ registeredListener();
+ registeredListener = null;
+ }
+ });
+});
+
+export default {
+ element,
+ map,
+ registerListener,
+ addLayer,
+ calculateBounds,
+};
diff --git a/modern/src/reactHelper.js b/modern/src/reactHelper.js
new file mode 100644
index 00000000..e8a4135a
--- /dev/null
+++ b/modern/src/reactHelper.js
@@ -0,0 +1,10 @@
+
+import { useRef, useEffect } from 'react';
+
+export const usePrevious = value => {
+ const ref = useRef();
+ useEffect(() => {
+ ref.current = value;
+ });
+ return ref.current;
+}