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

149 lines
5.3 KiB
TypeScript

"use client"
import { IUnclusteredPointLayerProps } from "@/app/_utils/types/map"
import { useEffect, useCallback } from "react"
export default function UnclusteredPointLayer({
visible = true,
map,
crimes = [],
filterCategory = "all",
focusedDistrictId,
}: IUnclusteredPointLayerProps) {
const handleIncidentClick = useCallback(
(e: any) => {
if (!map) return
const features = map.queryRenderedFeatures(e.point, { layers: ["unclustered-point"] })
if (!features || features.length === 0) return
const incident = features[0]
if (!incident.properties) return
e.originalEvent.stopPropagation()
e.preventDefault()
const incidentDetails = {
id: incident.properties.id,
district: incident.properties.district,
category: incident.properties.category,
type: incident.properties.incidentType,
description: incident.properties.description,
status: incident.properties?.status || "Unknown",
longitude: (incident.geometry as any).coordinates[0],
latitude: (incident.geometry as any).coordinates[1],
timestamp: new Date(incident.properties.timestamp || Date.now()),
}
console.log("Incident clicked:", incidentDetails)
// Create a custom event with incident details
const customEvent = new CustomEvent("incident_click", {
detail: incidentDetails,
bubbles: true,
})
// Dispatch the event on both the map canvas and document to ensure it's captured
if (map.getCanvas()) {
map.getCanvas().dispatchEvent(customEvent)
}
document.dispatchEvent(customEvent)
// Also trigger a fly-to event to zoom to the incident
const flyToEvent = new CustomEvent("mapbox_fly_to", {
detail: {
longitude: incidentDetails.longitude,
latitude: incidentDetails.latitude,
zoom: 15,
bearing: 0,
pitch: 45,
duration: 1000,
},
bubbles: true,
})
if (map.getCanvas()) {
map.getCanvas().dispatchEvent(flyToEvent)
} else {
document.dispatchEvent(flyToEvent)
}
},
[map],
)
useEffect(() => {
if (!map || !visible) return
const onStyleLoad = () => {
if (!map) return
try {
const layers = map.getStyle().layers
let firstSymbolId: string | undefined
for (const layer of layers) {
if (layer.type === "symbol") {
firstSymbolId = layer.id
break
}
}
if (map.getSource("crime-incidents") && !map.getLayer("unclustered-point")) {
map.addLayer(
{
id: "unclustered-point",
type: "circle",
source: "crime-incidents",
filter: ["!", ["has", "point_count"]],
paint: {
"circle-color": "#11b4da",
"circle-radius": 8,
"circle-stroke-width": 1,
"circle-stroke-color": "#fff",
},
layout: {
visibility: focusedDistrictId ? "none" : "visible",
},
},
firstSymbolId,
)
map.on("mouseenter", "unclustered-point", () => {
map.getCanvas().style.cursor = "pointer"
})
map.on("mouseleave", "unclustered-point", () => {
map.getCanvas().style.cursor = ""
})
map.off("click", "unclustered-point", handleIncidentClick)
map.on("click", "unclustered-point", handleIncidentClick)
} else if (map.getLayer("unclustered-point")) {
// Update visibility based on focused district
map.setLayoutProperty("unclustered-point", "visibility", focusedDistrictId ? "none" : "visible")
// Ensure click handler is registered
map.off("click", "unclustered-point", handleIncidentClick)
map.on("click", "unclustered-point", handleIncidentClick)
}
} catch (error) {
console.error("Error adding unclustered point layer:", error)
}
}
if (map.isStyleLoaded()) {
onStyleLoad()
} else {
map.once("style.load", onStyleLoad)
}
return () => {
if (map) {
map.off("click", "unclustered-point", handleIncidentClick)
}
}
}, [map, visible, focusedDistrictId, handleIncidentClick])
return null
}