195 lines
7.9 KiB
TypeScript
195 lines
7.9 KiB
TypeScript
"use client"
|
|
|
|
import { useEffect, useRef } from "react"
|
|
import { useMap } from "react-map-gl/mapbox"
|
|
import { CRIME_RATE_COLORS } from "@/app/_utils/const/map"
|
|
import { $Enums } from "@prisma/client"
|
|
|
|
export interface DistrictExtrusionLayerProps {
|
|
visible?: boolean
|
|
focusedDistrictId: string | null
|
|
crimeDataByDistrict: Record<string, { number_of_crime?: number; level?: $Enums.crime_rates }>
|
|
tilesetId: string
|
|
beforeId?: string
|
|
}
|
|
|
|
export default function DistrictExtrusionLayer({
|
|
visible = true,
|
|
focusedDistrictId,
|
|
crimeDataByDistrict,
|
|
tilesetId,
|
|
beforeId,
|
|
}: DistrictExtrusionLayerProps) {
|
|
const { current: map } = useMap()
|
|
const layersAdded = useRef(false)
|
|
|
|
useEffect(() => {
|
|
if (!map || !visible) return
|
|
|
|
const onStyleLoad = () => {
|
|
if (!map) return
|
|
|
|
try {
|
|
// Make sure the districts source exists
|
|
if (!map.getMap().getSource("districts")) {
|
|
map.getMap().addSource("districts", {
|
|
type: "vector",
|
|
url: `mapbox://${tilesetId}`,
|
|
})
|
|
}
|
|
|
|
// Get the first symbol layer
|
|
let firstSymbolId = beforeId
|
|
if (!firstSymbolId) {
|
|
const layers = map.getStyle().layers
|
|
for (const layer of layers) {
|
|
if (layer.type === "symbol") {
|
|
firstSymbolId = layer.id
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!map.getMap().getLayer("district-extrusion")) {
|
|
map.getMap().addLayer(
|
|
{
|
|
id: "district-extrusion",
|
|
type: "fill-extrusion",
|
|
source: "districts",
|
|
"source-layer": "Districts",
|
|
paint: {
|
|
"fill-extrusion-color": [
|
|
"case",
|
|
["has", "kode_kec"],
|
|
[
|
|
"match",
|
|
["get", "kode_kec"],
|
|
focusedDistrictId || "",
|
|
crimeDataByDistrict[focusedDistrictId || ""]?.level === "low"
|
|
? CRIME_RATE_COLORS.low
|
|
: crimeDataByDistrict[focusedDistrictId || ""]?.level === "medium"
|
|
? CRIME_RATE_COLORS.medium
|
|
: crimeDataByDistrict[focusedDistrictId || ""]?.level === "high"
|
|
? CRIME_RATE_COLORS.high
|
|
: CRIME_RATE_COLORS.default,
|
|
"transparent",
|
|
],
|
|
"transparent",
|
|
],
|
|
"fill-extrusion-height": [
|
|
"case",
|
|
["has", "kode_kec"],
|
|
["match", ["get", "kode_kec"], focusedDistrictId || "", 500, 0],
|
|
0,
|
|
],
|
|
"fill-extrusion-base": 0,
|
|
"fill-extrusion-opacity": 0.8,
|
|
},
|
|
filter: ["==", ["get", "kode_kec"], focusedDistrictId || ""],
|
|
},
|
|
firstSymbolId,
|
|
)
|
|
layersAdded.current = true
|
|
}
|
|
} catch (error) {
|
|
console.error("Error adding extrusion layer:", error)
|
|
}
|
|
}
|
|
|
|
if (map.isStyleLoaded()) {
|
|
onStyleLoad()
|
|
} else {
|
|
map.once("style.load", onStyleLoad)
|
|
}
|
|
}, [map, visible, tilesetId, beforeId])
|
|
|
|
useEffect(() => {
|
|
if (!map || !layersAdded.current) return
|
|
|
|
try {
|
|
if (map.getMap().getLayer("district-extrusion")) {
|
|
map.getMap().setFilter("district-extrusion", ["==", ["get", "kode_kec"], focusedDistrictId || ""])
|
|
|
|
map.getMap().setPaintProperty("district-extrusion", "fill-extrusion-color", [
|
|
"case",
|
|
["has", "kode_kec"],
|
|
[
|
|
"match",
|
|
["get", "kode_kec"],
|
|
focusedDistrictId || "",
|
|
crimeDataByDistrict[focusedDistrictId || ""]?.level === "low"
|
|
? CRIME_RATE_COLORS.low
|
|
: crimeDataByDistrict[focusedDistrictId || ""]?.level === "medium"
|
|
? CRIME_RATE_COLORS.medium
|
|
: crimeDataByDistrict[focusedDistrictId || ""]?.level === "high"
|
|
? CRIME_RATE_COLORS.high
|
|
: CRIME_RATE_COLORS.default,
|
|
"transparent",
|
|
],
|
|
"transparent",
|
|
])
|
|
|
|
if (focusedDistrictId) {
|
|
const startHeight = 0
|
|
const targetHeight = 800
|
|
const duration = 700
|
|
const startTime = performance.now()
|
|
|
|
const animateHeight = (currentTime: number) => {
|
|
const elapsed = currentTime - startTime
|
|
const progress = Math.min(elapsed / duration, 1)
|
|
const easedProgress = progress * (2 - progress)
|
|
const currentHeight = startHeight + (targetHeight - startHeight) * easedProgress
|
|
|
|
map
|
|
.getMap()
|
|
.setPaintProperty("district-extrusion", "fill-extrusion-height", [
|
|
"case",
|
|
["has", "kode_kec"],
|
|
["match", ["get", "kode_kec"], focusedDistrictId, currentHeight, 0],
|
|
0,
|
|
])
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(animateHeight)
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(animateHeight)
|
|
} else {
|
|
const startHeight = 800
|
|
const targetHeight = 0
|
|
const duration = 500
|
|
const startTime = performance.now()
|
|
|
|
const animateHeightDown = (currentTime: number) => {
|
|
const elapsed = currentTime - startTime
|
|
const progress = Math.min(elapsed / duration, 1)
|
|
const easedProgress = progress * (2 - progress)
|
|
const currentHeight = startHeight + (targetHeight - startHeight) * easedProgress
|
|
|
|
map
|
|
.getMap()
|
|
.setPaintProperty("district-extrusion", "fill-extrusion-height", [
|
|
"case",
|
|
["has", "kode_kec"],
|
|
["match", ["get", "kode_kec"], "", currentHeight, 0],
|
|
0,
|
|
])
|
|
|
|
if (progress < 1) {
|
|
requestAnimationFrame(animateHeightDown)
|
|
}
|
|
}
|
|
|
|
requestAnimationFrame(animateHeightDown)
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.error("Error updating extrusion layer:", error)
|
|
}
|
|
}, [map, focusedDistrictId, crimeDataByDistrict])
|
|
|
|
return null
|
|
}
|