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 size = 10
|
||||||
|
let frameCount = 0
|
||||||
|
const animationSpeed = 3; // Higher value = slower animation (skip frames)
|
||||||
|
|
||||||
const animatePulse = () => {
|
const animatePulse = () => {
|
||||||
if (!map || !map.getLayer("target-incident-highlight")) {
|
if (!map || !map.getLayer("target-incident-highlight")) {
|
||||||
if (animationRef.current) {
|
if (animationRef.current) {
|
||||||
|
@ -75,7 +78,11 @@ export default function FlyToHandler({ map }: Pick<IBaseLayerProps, "map">) {
|
||||||
return
|
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", [
|
map.setPaintProperty("target-incident-highlight", "circle-radius", [
|
||||||
"interpolate",
|
"interpolate",
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
import { IUnclusteredPointLayerProps } from "@/app/_utils/types/map"
|
import { IUnclusteredPointLayerProps } from "@/app/_utils/types/map"
|
||||||
import { useEffect, useCallback } from "react"
|
import { useEffect, useCallback } from "react"
|
||||||
|
|
||||||
|
|
||||||
export default function UnclusteredPointLayer({
|
export default function UnclusteredPointLayer({
|
||||||
visible = true,
|
visible = true,
|
||||||
map,
|
map,
|
||||||
|
@ -11,6 +10,37 @@ export default function UnclusteredPointLayer({
|
||||||
filterCategory = "all",
|
filterCategory = "all",
|
||||||
focusedDistrictId,
|
focusedDistrictId,
|
||||||
}: IUnclusteredPointLayerProps) {
|
}: 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(
|
const handleIncidentClick = useCallback(
|
||||||
(e: any) => {
|
(e: any) => {
|
||||||
if (!map) return
|
if (!map) return
|
||||||
|
@ -75,20 +105,31 @@ export default function UnclusteredPointLayer({
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!map || !visible) return
|
if (!map || !visible) return
|
||||||
|
|
||||||
const onStyleLoad = () => {
|
const setupLayerAndSource = () => {
|
||||||
if (!map) return
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const layers = map.getStyle().layers
|
// First check if source exists and update it
|
||||||
let firstSymbolId: string | undefined
|
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) {
|
for (const layer of layers) {
|
||||||
if (layer.type === "symbol") {
|
if (layer.type === "symbol") {
|
||||||
firstSymbolId = layer.id
|
firstSymbolId = layer.id;
|
||||||
break
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map.getSource("crime-incidents") && !map.getLayer("unclustered-point")) {
|
// Check if layer exists
|
||||||
|
if (!map.getLayer("unclustered-point")) {
|
||||||
map.addLayer(
|
map.addLayer(
|
||||||
{
|
{
|
||||||
id: "unclustered-point",
|
id: "unclustered-point",
|
||||||
|
@ -106,43 +147,53 @@ export default function UnclusteredPointLayer({
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
firstSymbolId,
|
firstSymbolId,
|
||||||
)
|
);
|
||||||
|
|
||||||
map.on("mouseenter", "unclustered-point", () => {
|
map.on("mouseenter", "unclustered-point", () => {
|
||||||
map.getCanvas().style.cursor = "pointer"
|
map.getCanvas().style.cursor = "pointer";
|
||||||
})
|
});
|
||||||
|
|
||||||
map.on("mouseleave", "unclustered-point", () => {
|
map.on("mouseleave", "unclustered-point", () => {
|
||||||
map.getCanvas().style.cursor = ""
|
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 {
|
} else {
|
||||||
map.once("style.load", onStyleLoad)
|
// Update visibility based on focused district
|
||||||
|
map.setLayoutProperty("unclustered-point", "visibility", focusedDistrictId ? "none" : "visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()) {
|
||||||
|
setupLayerAndSource();
|
||||||
|
} else {
|
||||||
|
// 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 () => {
|
return () => {
|
||||||
if (map) {
|
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[];
|
crimes: ICrimes[];
|
||||||
filterCategory: string | 'all';
|
filterCategory: string | 'all';
|
||||||
focusedDistrictId: string | null;
|
focusedDistrictId: string | null;
|
||||||
|
showIncidentMarkers?: boolean; // Add this prop to control marker visibility
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue