diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2020-06-06 23:52:25 -0700 |
---|---|---|
committer | Anton Tananaev <anton.tananaev@gmail.com> | 2020-06-06 23:52:25 -0700 |
commit | cf20c43948b83539a9a049a81796e15509c393b4 (patch) | |
tree | add07433f014145fd9f65fde58eb26d0edd9d212 /modern | |
parent | b0afb5deea4de4cc5951d4e6fe17d2040546b8b0 (diff) | |
download | trackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.tar.gz trackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.tar.bz2 trackermap-web-cf20c43948b83539a9a049a81796e15509c393b4.zip |
Reuse map component
Diffstat (limited to 'modern')
-rw-r--r-- | modern/src/MainMap.js | 263 | ||||
-rw-r--r-- | modern/src/MainPage.js | 2 | ||||
-rw-r--r-- | modern/src/SocketController.js | 2 | ||||
-rw-r--r-- | modern/src/mapManager.js | 150 | ||||
-rw-r--r-- | modern/src/reactHelper.js | 10 |
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; +} |