From 9329625d4ca3f3585a3b5bca8a94e3e8e671afde Mon Sep 17 00:00:00 2001 From: antntp_sysvpn Date: Fri, 9 Aug 2024 22:53:03 +0700 Subject: [PATCH] create map summary --- apps/console/src/app/(summary)/screen.tsx | 143 +++++++++++++++++++--- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/apps/console/src/app/(summary)/screen.tsx b/apps/console/src/app/(summary)/screen.tsx index 432b5eb..800e75e 100644 --- a/apps/console/src/app/(summary)/screen.tsx +++ b/apps/console/src/app/(summary)/screen.tsx @@ -1,8 +1,9 @@ 'use client' -import { sumBy } from 'lodash' -import { useEffect, useState } from 'react' -import { Map } from 'react-map-gl' +import { forEach, sumBy } from 'lodash' +import mapboxgl from 'mapbox-gl' +import 'mapbox-gl/dist/mapbox-gl.css' +import { useEffect, useRef, useState } from 'react' import { Card, CardContent, CardHeader, CardTitle } from '@packages/ui/components/index' import { Hash } from '@packages/ui/icons/index' import { useTheme } from '@packages/ui/providers/index' @@ -10,7 +11,11 @@ import { Layout } from '@/components' import { useZonesQuery } from '@/hooks' const MAPBOX_TOKEN = 'pk.eyJ1IjoiY2FvaGF2YW4iLCJhIjoiY2x5anNkcDBzMGw2bTJqcGF4OTNjbTk1dCJ9.quX_1lfj-fPC8hNzpwUWiA' - +type Feature = { + type: string + properties: { id: number } + geometry: { type: string; coordinates: [number, number, number] } +} export const Summary = () => { const { theme } = useTheme() const [detectTheme, setDetectTheme] = useState('light') @@ -30,6 +35,125 @@ export const Summary = () => { setDetectTheme(theme || 'light') } }, [theme]) + const mapContainerRef = useRef(null) + const mapRef = useRef(null) + useEffect(() => { + if (!zones) return + mapboxgl.accessToken = MAPBOX_TOKEN + mapRef.current = new mapboxgl.Map({ + container: mapContainerRef.current, + style: `mapbox://styles/mapbox/${detectTheme}-v10`, + center: [105.8342, 21.0278], + zoom: 6, + }) + + let arrayFeature: Feature[] = [] + forEach(zones?.data, (item) => { + const data: Feature = { + type: 'Feature', + properties: { id: item.zone_id, ...zones }, + geometry: { type: 'Point', coordinates: [item.lon, item.lat, 0.0] }, + } + arrayFeature = [...(arrayFeature || []), data] + }) + const temp = { + type: 'FeatureCollection', + crs: { type: 'name' }, + features: arrayFeature, + } + mapRef.current.on('load', () => { + mapRef.current.addSource('zones', { + type: 'geojson', + data: temp, + cluster: true, + clusterMaxZoom: 14, + clusterRadius: 50, + }) + // cricle big + mapRef.current.addLayer({ + id: 'clusters', + type: 'circle', + source: 'zones', + filter: ['has', 'point_count'], + paint: { + 'circle-color': ['step', ['get', 'point_count'], '#51bbd6', 100, '#f1f075', 750, '#f28cb1'], + 'circle-radius': ['step', ['get', 'point_count'], 20, 100, 30, 750, 40], + }, + }) + // number + mapRef.current.addLayer({ + id: 'cluster-count', + type: 'symbol', + source: 'zones', + filter: ['has', 'point_count'], + layout: { + 'text-field': ['get', 'point_count_abbreviated'], + 'text-font': ['DIN Offc Pro Medium', 'Arial Unicode MS Bold'], + 'text-size': 12, + }, + }) + // cricle small + mapRef.current.addLayer({ + id: 'unclustered-point', + type: 'circle', + source: 'zones', + filter: ['!', ['has', 'point_count']], + paint: { + 'circle-color': '#11b4da', + 'circle-radius': 4, + 'circle-stroke-width': 1, + 'circle-stroke-color': '#fff', + }, + }) + // inspect a cluster on click + mapRef.current.on('click', 'clusters', (e: any) => { + const features = mapRef.current.queryRenderedFeatures(e.point, { + layers: ['clusters'], + }) + const clusterId = features[0].properties.cluster_id + mapRef.current.getSource('zones').getClusterExpansionZoom(clusterId, (err: any, zoom: any) => { + if (err) return + + mapRef.current.easeTo({ + center: features[0].geometry.coordinates, + zoom: zoom, + }) + }) + }) + + // When a click event occurs on a feature in + // the unclustered-point layer, open a popup at + // the location of the feature, with + // description HTML from its properties. + mapRef.current.on('click', 'unclustered-point', (e: any) => { + const coordinates = e.features[0].geometry.coordinates.slice() + const consoles = sumBy(e.features[0].properties.zones?.data, 'consoles') + const gateways = sumBy(zones?.data, 'gateways') + const medias = sumBy(zones?.data, 'medias') + const connectors = sumBy(zones?.data, 'connectors') + // Ensure that if the map is zoomed out such that + // multiple copies of the feature are visible, the + // popup appears over the copy being pointed to. + while (Math.abs(e.lngLat.lng - coordinates[0]) > 180) { + coordinates[0] += e.lngLat.lng > coordinates[0] ? 360 : -360 + } + + new mapboxgl.Popup() + .setLngLat(coordinates) + .setHTML(`Consoles: ${consoles}
Gateways: ${gateways}
Medias: ${medias}
Connectors: ${connectors}`) + .addTo(mapRef.current) + }) + + mapRef.current.on('mouseenter', 'clusters', () => { + mapRef.current.getCanvas().style.cursor = 'pointer' + }) + mapRef.current.on('mouseleave', 'clusters', () => { + mapRef.current.getCanvas().style.cursor = '' + }) + }) + + return () => mapRef.current.remove() + }, [detectTheme, zones]) return ( {
- +