Fix coverage: add /api/coverage route, remove stray gather code from loadCoverage
This commit is contained in:
118
frontend_src/NodeMarkers.tsx
Normal file
118
frontend_src/NodeMarkers.tsx
Normal file
@@ -0,0 +1,118 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user