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

90 lines
3.2 KiB
TypeScript

import { Layer, Source } from "react-map-gl/mapbox";
import { useMemo } from "react";
import { ICrimes } from "@/app/_utils/types/crimes";
interface HeatmapLayerProps {
crimes: ICrimes[];
year: string;
month: string;
filterCategory: string | "all";
visible?: boolean;
}
export default function HeatmapLayer({ crimes, visible = true }: HeatmapLayerProps) {
// Convert crime data to GeoJSON format for the heatmap
const heatmapData = useMemo(() => {
const features = crimes.flatMap(crime =>
crime.crime_incidents
.filter(incident => incident.locations?.latitude && incident.locations?.longitude)
.map(incident => ({
type: "Feature" as const,
properties: {
id: incident.id,
category: incident.crime_categories?.name || "Unknown",
intensity: 1, // Base intensity value
},
geometry: {
type: "Point" as const,
coordinates: [incident.locations!.longitude, incident.locations!.latitude],
},
}))
);
return {
type: "FeatureCollection" as const,
features,
};
}, [crimes]);
if (!visible) return null;
return (
<Source id="crime-heatmap-data" type="geojson" data={heatmapData}>
<Layer
id="crime-heatmap"
type="heatmap"
paint={{
// Heatmap radius
'heatmap-radius': [
'interpolate',
['linear'],
['zoom'],
8, 10, // At zoom level 8, radius will be 10px
13, 25 // At zoom level 13, radius will be 25px
],
// Heatmap intensity
'heatmap-intensity': [
'interpolate',
['linear'],
['zoom'],
8, 0.5, // Less intense at zoom level 8
13, 1.5 // More intense at zoom level 13
],
// Color gradient from low to high density
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0, 'rgba(33,102,172,0)',
0.2, 'rgb(103,169,207)',
0.4, 'rgb(209,229,240)',
0.6, 'rgb(253,219,199)',
0.8, 'rgb(239,138,98)',
1, 'rgb(178,24,43)'
],
// Heatmap opacity
'heatmap-opacity': 0.8,
// Heatmap weight based on properties
'heatmap-weight': [
'interpolate',
['linear'],
['get', 'intensity'],
0, 0.5,
5, 2
],
}}
/>
</Source>
);
}