98 lines
2.8 KiB
TypeScript
98 lines
2.8 KiB
TypeScript
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: `<span style="font:10px monospace;color:#444;background:rgba(255,255,255,0.7);padding:0 2px;white-space:nowrap;">${lat.toFixed(interval < 0.1 ? 2 : 1)}°</span>`,
|
|
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: `<span style="font:10px monospace;color:#444;background:rgba(255,255,255,0.7);padding:0 2px;white-space:nowrap;">${lon.toFixed(interval < 0.1 ? 2 : 1)}°</span>`,
|
|
iconAnchor: [12, 12],
|
|
}),
|
|
interactive: false,
|
|
});
|
|
layerGroup.addLayer(label);
|
|
}
|
|
|
|
layerGroup.addTo(map);
|
|
|
|
return () => {
|
|
map.removeLayer(layerGroup);
|
|
};
|
|
}, [map, visible, zoom]);
|
|
|
|
return null;
|
|
}
|