Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

create map summary #9

Merged
merged 1 commit into from
Aug 10, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 129 additions & 14 deletions apps/console/src/app/(summary)/screen.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
'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'
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<string>('light')
Expand All @@ -30,6 +35,125 @@ export const Summary = () => {
setDetectTheme(theme || 'light')
}
}, [theme])
const mapContainerRef = useRef<any>(null)
const mapRef = useRef<any>(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(`<span style=\"color:#000;\">Consoles: ${consoles}</span><br><span style=\"color:#000;\">Gateways: ${gateways}</span><br><span style=\"color:#000;\">Medias: ${medias}</span><br><span style=\"color:#000;\">Connectors: ${connectors}</span>`)
.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 (
<Layout
Expand Down Expand Up @@ -93,16 +217,7 @@ export const Summary = () => {
</Card>
</div>
<div className="rounded-lg overflow-hidden">
<Map
mapStyle={`mapbox://styles/mapbox/${detectTheme}-v10`}
mapboxAccessToken={MAPBOX_TOKEN}
style={{
height: 500,
}}
initialViewState={{
zoom: 2,
}}
/>
<div id="map" ref={mapContainerRef} style={{ height: '70vh', width: '100%' }} className="absolute top-0" />
</div>
</div>
</Layout>
Expand Down