diff options
author | Anton Tananaev <anton.tananaev@gmail.com> | 2021-06-26 18:25:45 -0700 |
---|---|---|
committer | Anton Tananaev <anton.tananaev@gmail.com> | 2021-06-26 18:25:45 -0700 |
commit | de199fa668b61ca1def0b8d18de8d666d8148361 (patch) | |
tree | 7eaace63676732087f2348c1201d580ba120b207 /modern/src | |
parent | 698e27607bddd10a2f5b10a5ff07692a3d1f4ae0 (diff) | |
download | trackermap-web-de199fa668b61ca1def0b8d18de8d666d8148361.tar.gz trackermap-web-de199fa668b61ca1def0b8d18de8d666d8148361.tar.bz2 trackermap-web-de199fa668b61ca1def0b8d18de8d666d8148361.zip |
Implement map clustering
Diffstat (limited to 'modern/src')
-rw-r--r-- | modern/src/map/Map.js | 3 | ||||
-rw-r--r-- | modern/src/map/PositionsMap.js | 48 | ||||
-rw-r--r-- | modern/src/map/mapUtil.js | 14 |
3 files changed, 53 insertions, 12 deletions
diff --git a/modern/src/map/Map.js b/modern/src/map/Map.js index 8a43e97b..014268cd 100644 --- a/modern/src/map/Map.js +++ b/modern/src/map/Map.js @@ -37,9 +37,10 @@ const updateReadyValue = value => { const initMap = async () => { const background = await loadImage('images/background.svg'); + map.addImage('background', await loadIcon(background), { pixelRatio: window.devicePixelRatio }); await Promise.all(deviceCategories.map(async category => { if (!map.hasImage(category)) { - const imageData = await loadIcon(category, background, `images/icon/${category}.svg`); + const imageData = await loadIcon(background, `images/icon/${category}.svg`); map.addImage(category, imageData, { pixelRatio: window.devicePixelRatio }); } })); diff --git a/modern/src/map/PositionsMap.js b/modern/src/map/PositionsMap.js index fa7b431a..4a590b7b 100644 --- a/modern/src/map/PositionsMap.js +++ b/modern/src/map/PositionsMap.js @@ -10,6 +10,7 @@ import StatusView from './StatusView'; const PositionsMap = ({ positions }) => { const id = 'positions'; + const clusters = `${id}-clusters`; const history = useHistory(); const devices = useSelector(state => state.devices.items); @@ -26,7 +27,7 @@ const PositionsMap = ({ positions }) => { const onMouseEnter = () => map.getCanvas().style.cursor = 'pointer'; const onMouseLeave = () => map.getCanvas().style.cursor = ''; - const onClickCallback = useCallback(event => { + const onMarkerClick = useCallback(event => { const feature = event.features[0]; let coordinates = feature.geometry.coordinates.slice(); while (Math.abs(event.lngLat.lng - coordinates[0]) > 180) { @@ -50,18 +51,35 @@ const PositionsMap = ({ positions }) => { .addTo(map); }, [history]); + const onClusterClick = event => { + 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: zoom, + }); + }); + }; + useEffect(() => { map.addSource(id, { 'type': 'geojson', 'data': { type: 'FeatureCollection', features: [], - } + }, + 'cluster': true, + 'clusterMaxZoom': 14, + 'clusterRadius': 50, }); map.addLayer({ 'id': id, 'type': 'symbol', 'source': id, + 'filter': ['!', ['has', 'point_count']], 'layout': { 'icon-image': '{category}', 'icon-allow-overlap': true, @@ -77,22 +95,42 @@ const PositionsMap = ({ positions }) => { 'text-halo-width': 1, }, }); + map.addLayer({ + 'id': clusters, + 'type': 'symbol', + 'source': id, + 'filter': ['has', 'point_count'], + 'layout': { + 'icon-image': 'background', + 'text-field': '{point_count_abbreviated}', + 'text-font': ['Roboto Regular'], + 'text-size': 14, + }, + }); + map.on('mouseenter', id, onMouseEnter); map.on('mouseleave', id, onMouseLeave); - map.on('click', id, onClickCallback); + map.on('mouseenter', clusters, onMouseEnter); + map.on('mouseleave', clusters, onMouseLeave); + map.on('click', id, onMarkerClick); + map.on('click', clusters, onClusterClick); return () => { Array.from(map.getContainer().getElementsByClassName('mapboxgl-popup')).forEach(el => el.remove()); map.off('mouseenter', id, onMouseEnter); map.off('mouseleave', id, onMouseLeave); - map.off('click', id, onClickCallback); + map.off('mouseenter', clusters, onMouseEnter); + map.off('mouseleave', clusters, onMouseLeave); + map.off('click', id, onMarkerClick); + map.off('click', clusters, onClusterClick); map.removeLayer(id); + map.removeLayer(clusters); map.removeSource(id); }; - }, [onClickCallback]); + }, [onMarkerClick]); useEffect(() => { map.getSource(id).setData({ diff --git a/modern/src/map/mapUtil.js b/modern/src/map/mapUtil.js index e7dc0327..4780940d 100644 --- a/modern/src/map/mapUtil.js +++ b/modern/src/map/mapUtil.js @@ -9,8 +9,7 @@ export const loadImage = (url) => { }); }; -export const loadIcon = async (key, background, url) => { - const image = await loadImage(url); +export const loadIcon = async (background, url) => { const pixelRatio = window.devicePixelRatio; const canvas = document.createElement('canvas'); @@ -22,10 +21,13 @@ export const loadIcon = async (key, background, url) => { const context = canvas.getContext('2d'); context.drawImage(background, 0, 0, canvas.width, canvas.height); - const iconRatio = 0.5; - const imageWidth = canvas.width * iconRatio; - const imageHeight = canvas.height * iconRatio; - context.drawImage(image, (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight); + if (url) { + const image = await loadImage(url); + const iconRatio = 0.5; + const imageWidth = canvas.width * iconRatio; + const imageHeight = canvas.height * iconRatio; + context.drawImage(image, (canvas.width - imageWidth) / 2, (canvas.height - imageHeight) / 2, imageWidth, imageHeight); + } return context.getImageData(0, 0, canvas.width, canvas.height); }; |