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

286 lines
11 KiB
TypeScript

"use client"
import { BASE_BEARING, BASE_PITCH, BASE_ZOOM } from "@/app/_utils/const/map"
import { createFillColorExpression, getFillOpacity, processDistrictFeature } from "@/app/_utils/map"
import type { IDistrictLayerProps } from "@/app/_utils/types/map"
import { useEffect } from "react"
export default function DistrictFillLineLayer({
visible = true,
map,
onClick,
onDistrictClick, // Add the new prop
year,
month,
filterCategory = "all",
crimes = [],
tilesetId,
focusedDistrictId,
setFocusedDistrictId,
crimeDataByDistrict,
showFill = true,
activeControl,
}: IDistrictLayerProps & { onDistrictClick?: (district: any) => void }) {
// Extend the type inline
useEffect(() => {
if (!map || !visible) return
const handleDistrictClick = (e: any) => {
// Only include layers that exist in the map style
const possibleLayers = [
"unclustered-point",
"clusters",
"crime-points",
"units-points",
"incidents-points",
"timeline-markers",
"recent-incidents",
]
const availableLayers = possibleLayers.filter(layer => map.getLayer(layer))
const incidentFeatures = map.queryRenderedFeatures(e.point, {
layers: availableLayers,
})
if (incidentFeatures && incidentFeatures.length > 0) {
// Click was on a marker or cluster, so don't process it as a district click
return
}
if (!map || !e.features || e.features.length === 0) return
const feature = e.features[0]
const districtId = feature.properties.kode_kec
// If clicking the same district, deselect it
if (focusedDistrictId === districtId) {
// Add null check for setFocusedDistrictId
if (setFocusedDistrictId) {
setFocusedDistrictId(null)
}
// Reset pitch and bearing with animation
map.easeTo({
zoom: BASE_ZOOM,
pitch: BASE_PITCH,
bearing: BASE_BEARING,
duration: 1500,
easing: (t) => t * (2 - t), // easeOutQuad
})
// Restore fill color for all districts when unfocusing
const fillColorExpression = createFillColorExpression(null, crimeDataByDistrict)
map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any)
// Show all clusters again when unfocusing
if (map.getLayer("clusters")) {
map.setLayoutProperty("clusters", "visibility", "visible")
}
if (map.getLayer("unclustered-point")) {
map.setLayoutProperty("unclustered-point", "visibility", "visible")
}
return
} else if (focusedDistrictId) {
// If we're already focusing on a district and clicking a different one,
// we need to reset the current one and move to the new one
if (setFocusedDistrictId) {
setFocusedDistrictId(null)
}
// Wait a moment before selecting the new district to ensure clean transitions
// setTimeout(() => {
// const district = processDistrictFeature(feature, e, districtId, crimeDataByDistrict, crimes, year, month)
// if (!district || !setFocusedDistrictId) return
// setFocusedDistrictId(district.id)
// // Fly to the new district
// map.flyTo({
// center: [district.longitude, district.latitude],
// zoom: 12.5,
// pitch: 75,
// bearing: 0,
// duration: 1500,
// easing: (t) => t * (2 - t), // easeOutQuad
// })
// // Use onDistrictClick if available, otherwise fall back to onClick
// if (onDistrictClick) {
// onDistrictClick(district)
// } else if (onClick) {
// onClick(district)
// }
// }, 100)
return
}
const district = processDistrictFeature(feature, e, districtId, crimeDataByDistrict, crimes, year, month)
if (!district) return
// Set the fill color expression immediately to show the focus
const focusedFillColorExpression = createFillColorExpression(district.id, crimeDataByDistrict)
map.setPaintProperty("district-fill", "fill-color", focusedFillColorExpression as any)
// Add null check for setFocusedDistrictId
if (setFocusedDistrictId) {
setFocusedDistrictId(district.id)
}
// Hide clusters when focusing on a district
if (map.getLayer("clusters")) {
map.setLayoutProperty("clusters", "visibility", "none")
}
if (map.getLayer("unclustered-point")) {
map.setLayoutProperty("unclustered-point", "visibility", "none")
}
// Animate to a pitched view focused on the district
map.flyTo({
center: [district.longitude, district.latitude],
zoom: 12.5,
pitch: 75,
bearing: 0,
duration: 1500,
easing: (t) => t * (2 - t), // easeOutQuad
})
// Use onDistrictClick if available, otherwise fall back to onClick
if (onDistrictClick) {
onDistrictClick(district)
} else if (onClick) {
onClick(district)
}
}
const onStyleLoad = () => {
if (!map) return
try {
if (!map.getSource("districts")) {
const layers = map.getStyle().layers
let firstSymbolId: string | undefined
for (const layer of layers) {
if (layer.type === "symbol") {
firstSymbolId = layer.id
break
}
}
map.addSource("districts", {
type: "vector",
url: `mapbox://${tilesetId}`,
})
const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict)
// Determine fill opacity based on active control
const fillOpacity = getFillOpacity(activeControl, showFill)
if (!map.getLayer("district-fill")) {
map.addLayer(
{
id: "district-fill",
type: "fill",
source: "districts",
"source-layer": "Districts",
paint: {
"fill-color": fillColorExpression as any,
"fill-opacity": fillOpacity,
},
},
firstSymbolId,
)
}
if (!map.getLayer("district-line")) {
map.addLayer(
{
id: "district-line",
type: "line",
source: "districts",
"source-layer": "Districts",
paint: {
"line-color": "#ffffff",
"line-width": 1,
"line-opacity": 0.5,
},
},
firstSymbolId,
)
}
map.on("mouseenter", "district-fill", () => {
map.getCanvas().style.cursor = "pointer"
})
map.on("mouseleave", "district-fill", () => {
map.getCanvas().style.cursor = ""
})
map.off("click", "district-fill", handleDistrictClick)
map.on("click", "district-fill", handleDistrictClick)
} else {
if (map.getLayer("district-fill")) {
const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict)
map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any)
// Update fill opacity when active control changes
const fillOpacity = getFillOpacity(activeControl, showFill)
map.setPaintProperty("district-fill", "fill-opacity", fillOpacity)
}
}
} catch (error) {
console.error("Error adding district layers:", error)
}
}
if (map.isStyleLoaded()) {
onStyleLoad()
} else {
map.once("style.load", onStyleLoad)
}
return () => {
if (map) {
map.off("click", "district-fill", handleDistrictClick)
}
}
}, [
map,
visible,
tilesetId,
crimes,
filterCategory,
year,
month,
focusedDistrictId,
crimeDataByDistrict,
onClick,
onDistrictClick, // Add to dependency array
setFocusedDistrictId,
showFill,
activeControl,
])
// Add an effect to update the fill color and opacity whenever relevant props change
useEffect(() => {
if (!map || !map.getLayer("district-fill")) return
try {
const fillColorExpression = createFillColorExpression(focusedDistrictId, crimeDataByDistrict)
map.setPaintProperty("district-fill", "fill-color", fillColorExpression as any)
// Update fill opacity when active control changes
const fillOpacity = getFillOpacity(activeControl, showFill)
map.setPaintProperty("district-fill", "fill-opacity", fillOpacity)
} catch (error) {
console.error("Error updating district fill colors or opacity:", error)
}
}, [map, focusedDistrictId, crimeDataByDistrict, activeControl, showFill])
return null
}