import { useEffect, useState } from 'react';
import { useMap } from 'react-leaflet';
import L from 'leaflet';
interface CoordinateGridProps {
visible: boolean;
}
/**
* Coordinate grid overlay with adaptive spacing based on zoom level.
* Drawn directly with Leaflet polylines — no extra dependencies.
*/
export default function CoordinateGrid({ visible }: CoordinateGridProps) {
const map = useMap();
const [zoom, setZoom] = useState(map.getZoom());
useEffect(() => {
const handleZoom = () => setZoom(map.getZoom());
map.on('zoomend', handleZoom);
return () => {
map.off('zoomend', handleZoom);
};
}, [map]);
useEffect(() => {
if (!visible) return;
// Adaptive grid spacing based on zoom
let interval: number;
if (zoom <= 7) interval = 1;
else if (zoom <= 10) interval = 0.5;
else if (zoom <= 13) interval = 0.1;
else interval = 0.01;
const bounds = map.getBounds();
const minLat = Math.floor(bounds.getSouth() / interval) * interval;
const maxLat = Math.ceil(bounds.getNorth() / interval) * interval;
const minLon = Math.floor(bounds.getWest() / interval) * interval;
const maxLon = Math.ceil(bounds.getEast() / interval) * interval;
const layerGroup = L.layerGroup();
// Latitude lines (horizontal)
for (let lat = minLat; lat <= maxLat; lat += interval) {
const line = L.polyline(
[
[lat, minLon - 1],
[lat, maxLon + 1],
],
{ color: '#666', weight: 0.7, opacity: 0.4, dashArray: '3,3' }
);
layerGroup.addLayer(line);
// Label
const label = L.marker([lat, bounds.getWest() + 0.002], {
icon: L.divIcon({
className: '',
html: `${lat.toFixed(interval < 0.1 ? 2 : 1)}°`,
iconAnchor: [0, 6],
}),
interactive: false,
});
layerGroup.addLayer(label);
}
// Longitude lines (vertical)
for (let lon = minLon; lon <= maxLon; lon += interval) {
const line = L.polyline(
[
[minLat - 1, lon],
[maxLat + 1, lon],
],
{ color: '#666', weight: 0.7, opacity: 0.4, dashArray: '3,3' }
);
layerGroup.addLayer(line);
// Label
const label = L.marker([bounds.getNorth() - 0.002, lon], {
icon: L.divIcon({
className: '',
html: `${lon.toFixed(interval < 0.1 ? 2 : 1)}°`,
iconAnchor: [12, 12],
}),
interactive: false,
});
layerGroup.addLayer(label);
}
layerGroup.addTo(map);
return () => {
map.removeLayer(layerGroup);
};
}, [map, visible, zoom]);
return null;
}