MIF_E31221222/sigap-website/app/_components/map/layers/district-layer.tsx

160 lines
4.9 KiB
TypeScript

"use client"
import { useEffect, useMemo } from "react"
import { Layer, Source, useMap, Popup } from "react-map-gl/mapbox"
import { useState } from "react"
import { IGeoJSONPolygon } from "@/app/_utils/types/map"
import { CRIME_COLORS, CRIME_RATES } from "@/app/_utils/const/crime"
export type DistrictFeature = {
id: string
name: string
cityName: string
code: string
polygon: IGeoJSONPolygon
crimeRate: "low" | "medium" | "high" | "no_data"
crimeCount: number
year: number
}
type DistrictLayerProps = {
data: DistrictFeature[]
visible?: boolean
onClick?: (feature: any) => void
}
type hoverInfoType = {
feature: {
properties: {
crimeRate: "low" | "medium" | "high" | "no_data"
name: string
cityName: string
crimeCount: number
}
geometry: {
coordinates: number[][][]
}
}
x: number
y: number
}
export default function DistrictLayer({ data, visible = true, onClick }: DistrictLayerProps) {
const { current: map } = useMap()
const [hoverInfo, setHoverInfo] = useState<hoverInfoType | null>(null)
// Convert data to GeoJSON
const geojson = useMemo(() => {
return {
type: "FeatureCollection",
features: data
.filter((district) => district.polygon) // Only include districts with polygon data
.map((district) => ({
type: "Feature",
properties: {
id: district.id,
name: district.name,
cityName: district.cityName,
crimeRate: district.crimeRate,
crimeCount: district.crimeCount,
color: CRIME_COLORS[district.crimeRate],
},
geometry: district.polygon,
})),
}
}, [data])
// Handle hover events
useEffect(() => {
if (!map) return
const onHover = (event: any) => {
const { features, point } = event
const hoveredFeature = features && features[0]
// Update hover state
setHoverInfo(
hoveredFeature
? {
feature: hoveredFeature,
x: point.x,
y: point.y,
}
: null,
)
}
// Change cursor on hover
const onMouseEnter = () => {
if (map) map.getCanvas().style.cursor = "pointer"
}
const onMouseLeave = () => {
if (map) map.getCanvas().style.cursor = ""
setHoverInfo(null)
}
// Add event listeners
map.on("mousemove", "district-fills", onHover)
map.on("mouseenter", "district-fills", onMouseEnter)
map.on("mouseleave", "district-fills", onMouseLeave)
map.on("click", "district-fills", (e) => {
if (onClick && e.features && e.features[0]) {
onClick(e.features[0])
}
})
// Clean up
return () => {
map.off("mousemove", "district-fills", onHover)
map.off("mouseenter", "district-fills", onMouseEnter)
map.off("mouseleave", "district-fills", onMouseLeave)
map.off("click", "district-fills", onClick as any)
}
}, [map, onClick])
if (!visible) return null
return (
<>
<Source id="districts" type="geojson" data={geojson as any}>
<Layer
id="district-fills"
type="fill"
paint={{
"fill-color": ["get", "color"],
"fill-opacity": 0.6,
}}
/>
<Layer
id="district-borders"
type="line"
paint={{
"line-color": "#627D98",
"line-width": 1,
}}
/>
</Source>
{/* Popup on hover */}
{hoverInfo && (
<Popup
longitude={hoverInfo.feature.geometry.coordinates[0][0][0]}
latitude={hoverInfo.feature.geometry.coordinates[0][0][1]}
closeButton={false}
closeOnClick={false}
anchor="bottom"
offset={[0, -10]}
>
<div className="p-2">
<h3 className="font-bold">{hoverInfo.feature.properties.name}</h3>
<p>City: {hoverInfo.feature.properties.cityName}</p>
<p>Crime Rate: {CRIME_RATES[hoverInfo.feature.properties.crimeRate]}</p>
<p>Crime Count: {hoverInfo.feature.properties.crimeCount}</p>
</div>
</Popup>
)}
</>
)
}