118 lines
4.2 KiB
TypeScript
118 lines
4.2 KiB
TypeScript
import { useEffect, useMemo } from 'react';
|
|
import { CircleMarker, Popup, useMap } from 'react-leaflet';
|
|
import proj4 from 'proj4';
|
|
import { Node, AdcValues } from '../types';
|
|
|
|
// Définition de la projection UTM Zone 31N (WGS84)
|
|
const UTM31N = "+proj=utm +zone=31 +datum=WGS84 +units=m +no_defs";
|
|
const WGS84 = "EPSG:4326";
|
|
|
|
function utmToLatLon(easting: number, northing: number): [number, number] | null {
|
|
if (!easting || !northing || isNaN(easting) || isNaN(northing)) return null;
|
|
try {
|
|
// Proj4 attend [longitude, latitude] pour le résultat
|
|
const [lon, lat] = proj4(UTM31N, WGS84, [easting, northing]);
|
|
return [lat, lon];
|
|
} catch (e) {
|
|
console.error('UTM Conversion error:', e);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function getMarkerColor(value: number | undefined, maxVal = 500): string {
|
|
if (value === undefined || value <= 0) return '#444444';
|
|
const normalized = Math.min(1, Math.sqrt(value) / Math.sqrt(maxVal));
|
|
if (normalized < 0.3) return `rgb(0, ${Math.round(normalized * 3.3 * 255)}, 255)`;
|
|
if (normalized < 0.6) return `rgb(0, 255, ${Math.round((1 - (normalized - 0.3) * 3.3) * 255)})`;
|
|
return `rgb(255, ${Math.round((1 - (normalized - 0.6) * 2.5) * 255)}, 0)`;
|
|
}
|
|
|
|
function getMarkerRadius(value: number | undefined, maxVal = 500): number {
|
|
if (value === undefined || value <= 0) return 6;
|
|
const normalized = Math.min(1, Math.sqrt(value) / Math.sqrt(maxVal));
|
|
return 6 + normalized * 24;
|
|
}
|
|
|
|
interface NodeMarkersProps {
|
|
nodes: Node[];
|
|
selectedNode: Node | null;
|
|
onSelectNode: (node: Node) => void;
|
|
adcValues: AdcValues;
|
|
showOnlyWithData?: boolean;
|
|
}
|
|
|
|
function NodeMarkers({ nodes, selectedNode, onSelectNode, adcValues, showOnlyWithData = true }: NodeMarkersProps) {
|
|
const map = useMap();
|
|
|
|
const nodesWithLatLon = useMemo(() => {
|
|
console.log(`NodeMarkers: Processing ${nodes.length} nodes`);
|
|
const results = nodes
|
|
.filter(node => node.position && (!showOnlyWithData || node.hasDates))
|
|
.map(node => {
|
|
const coords = utmToLatLon(node.position!.easting, node.position!.northing);
|
|
if (!coords) return null;
|
|
return { ...node, lat: coords[0], lon: coords[1] };
|
|
})
|
|
.filter((n): n is Node & { lat: number; lon: number } => n !== null);
|
|
|
|
console.log(`NodeMarkers: ${results.length} nodes converted to LatLon`);
|
|
if (results.length > 0) {
|
|
console.log('Sample node:', results[0].id, results[0].lat, results[0].lon);
|
|
}
|
|
return results;
|
|
}, [nodes, showOnlyWithData]);
|
|
|
|
useEffect(() => {
|
|
if (nodesWithLatLon.length === 0) return;
|
|
try {
|
|
const lats = nodesWithLatLon.map(n => n.lat);
|
|
const lons = nodesWithLatLon.map(n => n.lon);
|
|
const bounds: [[number, number], [number, number]] = [
|
|
[Math.min(...lats), Math.min(...lons)],
|
|
[Math.max(...lats), Math.max(...lons)]
|
|
];
|
|
map.fitBounds(bounds, { padding: [50, 50] });
|
|
} catch (e) {
|
|
console.error('fitBounds error:', e);
|
|
}
|
|
}, [nodesWithLatLon, map]);
|
|
|
|
const currentMax = useMemo(() => {
|
|
const vals = Object.values(adcValues).filter(v => v > 0);
|
|
return vals.length > 0 ? Math.max(...vals) : 500;
|
|
}, [adcValues]);
|
|
|
|
return (
|
|
<>
|
|
<CircleMarker center={[43.40, 3.70]} radius={10} pathOptions={{ color: 'yellow', fillColor: 'yellow', fillOpacity: 1 }}>
|
|
<Popup>DEBUG: Centre de Sète</Popup>
|
|
</CircleMarker>
|
|
{nodesWithLatLon.map(node => {
|
|
const adcValue = adcValues[node.id];
|
|
const isSelected = selectedNode?.id === node.id;
|
|
return (
|
|
<CircleMarker
|
|
key={node.id}
|
|
center={[node.lat, node.lon]}
|
|
radius={getMarkerRadius(adcValue, currentMax)}
|
|
pathOptions={{
|
|
fillColor: getMarkerColor(adcValue, currentMax),
|
|
fillOpacity: 0.85,
|
|
color: isSelected ? '#ff0000' : '#ffffff',
|
|
weight: isSelected ? 4 : 1,
|
|
}}
|
|
eventHandlers={{ click: () => onSelectNode(node) }}
|
|
>
|
|
<Popup>
|
|
<strong>Node {node.id}</strong><br/>
|
|
E: {node.position?.easting.toFixed(0)} N: {node.position?.northing.toFixed(0)}<br/>
|
|
RMS: {adcValue?.toFixed(2) || 'N/A'}
|
|
</Popup>
|
|
</CircleMarker>
|
|
);
|
|
})}
|
|
</>
|
|
);
|
|
}
|
|
|
|
export default NodeMarkers; |