"use client" import { useEffect, useCallback, useRef, useState } from "react" import type { ICrimes } from "@/app/_utils/types/crimes" interface HistoricalIncidentsLayerProps { visible?: boolean map: any crimes?: ICrimes[] filterCategory?: string | "all" focusedDistrictId?: string | null } export default function HistoricalIncidentsLayer({ visible = false, map, crimes = [], filterCategory = "all", focusedDistrictId, }: HistoricalIncidentsLayerProps) { const isInteractingWithMarker = useRef(false); const currentYear = new Date().getFullYear(); const startYear = 2020; const [yearColors, setYearColors] = useState>({}); // Generate colors for each year from 2020 to current year useEffect(() => { const colors: Record = {}; const yearCount = currentYear - startYear + 1; for (let i = 0; i < yearCount; i++) { const year = startYear + i; // Generate a color gradient from red (2020) to blue (current year) const red = Math.floor(255 - (i * 255 / yearCount)); const blue = Math.floor(i * 255 / yearCount); colors[year] = `rgb(${red}, 70, ${blue})`; } setYearColors(colors); }, [currentYear]); const handleIncidentClick = useCallback( (e: any) => { if (!map) return; const features = map.queryRenderedFeatures(e.point, { layers: ["historical-incidents"] }); if (!features || features.length === 0) return; isInteractingWithMarker.current = true; 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()), year: incident.properties.year, }; // console.log("Historical incident clicked:", incidentDetails); // Ensure markers stay visible when clicking on them if (map.getLayer("historical-incidents")) { map.setLayoutProperty("historical-incidents", "visibility", "visible"); } // First fly to the incident location map.flyTo({ center: [incidentDetails.longitude, incidentDetails.latitude], zoom: 15, bearing: 0, pitch: 45, duration: 2000, }); // Then dispatch the incident_click event to show the popup const customEvent = new CustomEvent("incident_click", { detail: incidentDetails, bubbles: true, }); map.getCanvas().dispatchEvent(customEvent); document.dispatchEvent(customEvent); // Reset the flag after a delay to allow the event to process setTimeout(() => { isInteractingWithMarker.current = false; }, 5000); }, [map] ); useEffect(() => { if (!map || !visible) return; // console.log("Setting up historical incidents layer"); // Filter incidents from 2020 to current year const historicalData = { type: "FeatureCollection" as const, features: crimes.flatMap((crime) => crime.crime_incidents .filter( (incident) => { const incidentYear = incident.timestamp ? new Date(incident.timestamp).getFullYear() : null; return ( (filterCategory === "all" || incident.crime_categories.name === filterCategory) && incident.locations && typeof incident.locations.longitude === "number" && typeof incident.locations.latitude === "number" && incidentYear !== null && incidentYear >= startYear && incidentYear <= currentYear ); } ) .map((incident) => { const incidentYear = incident.timestamp ? new Date(incident.timestamp).getFullYear() : currentYear; return { type: "Feature" as const, geometry: { type: "Point" as const, coordinates: [incident.locations.longitude, incident.locations.latitude], }, properties: { id: incident.id, district: crime.districts.name, district_id: crime.district_id, category: incident.crime_categories.name, incidentType: incident.crime_categories.type || "", description: incident.description, status: incident.status || "", timestamp: incident.timestamp ? incident.timestamp.toString() : "", year: incidentYear, }, }; }) ), }; // console.log(`Found ${historicalData.features.length} historical incidents from 2020 to ${currentYear}`); const setupLayerAndSource = () => { try { // Check if source exists and update it if (map.getSource("historical-incidents-source")) { (map.getSource("historical-incidents-source") as any).setData(historicalData); } else { // If not, add source map.addSource("historical-incidents-source", { type: "geojson", data: historicalData, // No clustering configuration }); } // Find first symbol layer for proper layering const layers = map.getStyle().layers; let firstSymbolId: string | undefined; for (const layer of layers) { if (layer.type === "symbol") { firstSymbolId = layer.id; break; } } // Style for year-based coloring const circleColorExpression: any[] = [ "match", ["get", "year"], ]; // Add entries for each year and its color Object.entries(yearColors).forEach(([year, color]) => { circleColorExpression.push(parseInt(year), color); }); // Default color for unknown years circleColorExpression.push("#888888"); // Check if layer exists already if (!map.getLayer("historical-incidents")) { map.addLayer({ id: "historical-incidents", type: "circle", source: "historical-incidents-source", paint: { "circle-color": circleColorExpression, "circle-radius": [ "interpolate", ["linear"], ["zoom"], 7, 2, // Smaller circles at lower zoom levels 12, 4, 15, 6, // Smaller maximum size ], "circle-stroke-width": 1, "circle-stroke-color": "#ffffff", "circle-opacity": 0.8, }, layout: { visibility: visible ? "visible" : "none", } }, firstSymbolId); // Add mouse events map.on("mouseenter", "historical-incidents", () => { map.getCanvas().style.cursor = "pointer"; }); map.on("mouseleave", "historical-incidents", () => { map.getCanvas().style.cursor = ""; }); } else { // Update existing layer visibility map.setLayoutProperty("historical-incidents", "visibility", visible ? "visible" : "none"); } // Ensure click handler is properly registered map.off("click", "historical-incidents", handleIncidentClick); map.on("click", "historical-incidents", handleIncidentClick); } catch (error) { console.error("Error setting up historical incidents layer:", error); } }; // Check if style is loaded and set up layer accordingly if (map.isStyleLoaded()) { setupLayerAndSource(); } else { map.once("style.load", setupLayerAndSource); // Fallback setTimeout(() => { if (map.isStyleLoaded()) { setupLayerAndSource(); } }, 1000); } return () => { if (map) { map.off("click", "historical-incidents", handleIncidentClick); } }; }, [map, visible, crimes, filterCategory, handleIncidentClick, currentYear, yearColors]); return null; }