refactor: enhance unclustered point layer to include GeoJSON conversion and improve layer setup; add optional prop for incident marker visibility
This commit is contained in:
parent
6bfc148821
commit
ae6eb40a13
|
@ -64,8 +64,11 @@ export default function FlyToHandler({ map }: Pick<IBaseLayerProps, "map">) {
|
|||
},
|
||||
})
|
||||
|
||||
// Add a pulsing effect using animations
|
||||
// Add a slower pulsing effect
|
||||
let size = 10
|
||||
let frameCount = 0
|
||||
const animationSpeed = 3; // Higher value = slower animation (skip frames)
|
||||
|
||||
const animatePulse = () => {
|
||||
if (!map || !map.getLayer("target-incident-highlight")) {
|
||||
if (animationRef.current) {
|
||||
|
@ -75,7 +78,11 @@ export default function FlyToHandler({ map }: Pick<IBaseLayerProps, "map">) {
|
|||
return
|
||||
}
|
||||
|
||||
size = (size % 20) + 1
|
||||
frameCount++;
|
||||
// Only update size every few frames to slow down the animation
|
||||
if (frameCount % animationSpeed === 0) {
|
||||
size = (size % 20) + 0.5; // Smaller increment for smoother, slower animation
|
||||
}
|
||||
|
||||
map.setPaintProperty("target-incident-highlight", "circle-radius", [
|
||||
"interpolate",
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
import { IUnclusteredPointLayerProps } from "@/app/_utils/types/map"
|
||||
import { useEffect, useCallback } from "react"
|
||||
|
||||
|
||||
export default function UnclusteredPointLayer({
|
||||
visible = true,
|
||||
map,
|
||||
|
@ -11,6 +10,37 @@ export default function UnclusteredPointLayer({
|
|||
filterCategory = "all",
|
||||
focusedDistrictId,
|
||||
}: IUnclusteredPointLayerProps) {
|
||||
// Konversi crimes ke GeoJSON FeatureCollection
|
||||
const geojsonData = {
|
||||
type: "FeatureCollection" as const,
|
||||
features: crimes.flatMap((crime) =>
|
||||
crime.crime_incidents
|
||||
.filter(
|
||||
(incident) =>
|
||||
(filterCategory === "all" || incident.crime_categories.name === filterCategory) &&
|
||||
incident.locations &&
|
||||
typeof incident.locations.longitude === "number" &&
|
||||
typeof incident.locations.latitude === "number"
|
||||
)
|
||||
.map((incident) => ({
|
||||
type: "Feature" as const,
|
||||
geometry: {
|
||||
type: "Point" as const,
|
||||
coordinates: [incident.locations.longitude, incident.locations.latitude],
|
||||
},
|
||||
properties: {
|
||||
id: incident.id,
|
||||
district: crime.districts.name,
|
||||
category: incident.crime_categories.name,
|
||||
incidentType: incident.crime_categories.type || "",
|
||||
description: incident.description,
|
||||
status: incident.status || "",
|
||||
timestamp: incident.timestamp ? incident.timestamp.toString() : "",
|
||||
},
|
||||
}))
|
||||
),
|
||||
}
|
||||
|
||||
const handleIncidentClick = useCallback(
|
||||
(e: any) => {
|
||||
if (!map) return
|
||||
|
@ -75,20 +105,31 @@ export default function UnclusteredPointLayer({
|
|||
useEffect(() => {
|
||||
if (!map || !visible) return
|
||||
|
||||
const onStyleLoad = () => {
|
||||
if (!map) return
|
||||
|
||||
const setupLayerAndSource = () => {
|
||||
try {
|
||||
const layers = map.getStyle().layers
|
||||
let firstSymbolId: string | undefined
|
||||
// First check if source exists and update it
|
||||
if (map.getSource("crime-incidents")) {
|
||||
(map.getSource("crime-incidents") as any).setData(geojsonData);
|
||||
} else {
|
||||
// If not, add source
|
||||
map.addSource("crime-incidents", {
|
||||
type: "geojson",
|
||||
data: geojsonData,
|
||||
});
|
||||
}
|
||||
|
||||
// Get layers to find first symbol layer
|
||||
const layers = map.getStyle().layers;
|
||||
let firstSymbolId: string | undefined;
|
||||
for (const layer of layers) {
|
||||
if (layer.type === "symbol") {
|
||||
firstSymbolId = layer.id
|
||||
break
|
||||
firstSymbolId = layer.id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (map.getSource("crime-incidents") && !map.getLayer("unclustered-point")) {
|
||||
// Check if layer exists
|
||||
if (!map.getLayer("unclustered-point")) {
|
||||
map.addLayer(
|
||||
{
|
||||
id: "unclustered-point",
|
||||
|
@ -106,43 +147,53 @@ export default function UnclusteredPointLayer({
|
|||
},
|
||||
},
|
||||
firstSymbolId,
|
||||
)
|
||||
);
|
||||
|
||||
map.on("mouseenter", "unclustered-point", () => {
|
||||
map.getCanvas().style.cursor = "pointer"
|
||||
})
|
||||
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")) {
|
||||
map.getCanvas().style.cursor = "";
|
||||
});
|
||||
} else {
|
||||
// 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)
|
||||
map.setLayoutProperty("unclustered-point", "visibility", focusedDistrictId ? "none" : "visible");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error adding unclustered point layer:", error)
|
||||
}
|
||||
}
|
||||
|
||||
// Always ensure click handler is properly registered
|
||||
map.off("click", "unclustered-point", handleIncidentClick);
|
||||
map.on("click", "unclustered-point", handleIncidentClick);
|
||||
} catch (error) {
|
||||
console.error("Error setting up unclustered point layer:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Check if style is loaded and set up layer accordingly
|
||||
if (map.isStyleLoaded()) {
|
||||
onStyleLoad()
|
||||
setupLayerAndSource();
|
||||
} else {
|
||||
map.once("style.load", onStyleLoad)
|
||||
// Add event listener for style loading completion
|
||||
const onStyleLoad = () => {
|
||||
setupLayerAndSource();
|
||||
};
|
||||
|
||||
map.once("style.load", onStyleLoad);
|
||||
|
||||
// Also wait a bit and try again as a fallback
|
||||
setTimeout(() => {
|
||||
if (map.isStyleLoaded()) {
|
||||
setupLayerAndSource();
|
||||
}
|
||||
}, 500);
|
||||
}
|
||||
|
||||
return () => {
|
||||
if (map) {
|
||||
map.off("click", "unclustered-point", handleIncidentClick)
|
||||
map.off("click", "unclustered-point", handleIncidentClick);
|
||||
}
|
||||
}
|
||||
}, [map, visible, focusedDistrictId, handleIncidentClick])
|
||||
};
|
||||
}, [map, visible, focusedDistrictId, handleIncidentClick, crimes, filterCategory, geojsonData]);
|
||||
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -104,4 +104,5 @@ export interface IUnclusteredPointLayerProps extends IBaseLayerProps {
|
|||
crimes: ICrimes[];
|
||||
filterCategory: string | 'all';
|
||||
focusedDistrictId: string | null;
|
||||
showIncidentMarkers?: boolean; // Add this prop to control marker visibility
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue