From c9c44491b0760576109f6130e85ab1687a18220d Mon Sep 17 00:00:00 2001 From: mytec Date: Fri, 30 Jan 2026 13:20:31 +0200 Subject: [PATCH] @mytec: iter 7.1 ready for test --- frontend/src/components/map/Heatmap.tsx | 103 +++++++++--------------- 1 file changed, 40 insertions(+), 63 deletions(-) diff --git a/frontend/src/components/map/Heatmap.tsx b/frontend/src/components/map/Heatmap.tsx index 08b499c..8f232ac 100644 --- a/frontend/src/components/map/Heatmap.tsx +++ b/frontend/src/components/map/Heatmap.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from 'react'; +import { useEffect } from 'react'; import { useMap } from 'react-leaflet'; import L from 'leaflet'; import 'leaflet.heat'; @@ -21,58 +21,48 @@ interface HeatmapProps { /** * Normalize RSRP to 0-1 intensity for heatmap. * - * Wide range -130 to -50 dBm ensures the full gradient is used - * and close-in strong signals don't all saturate to one color. + * Range: -140 to -40 dBm (wide range prevents color shifting) * - * -130 dBm → 0.0 (deep blue, no service) + * -140 dBm → 0.0 (deep blue, no service) * -90 dBm → 0.5 (green, fair) - * -50 dBm → 1.0 (red, very strong) + * -40 dBm → 1.0 (red, very strong) */ function rsrpToIntensity(rsrp: number): number { - const minRSRP = -130; - const maxRSRP = -50; + const minRSRP = -140; + const maxRSRP = -40; return Math.max(0, Math.min(1, (rsrp - minRSRP) / (maxRSRP - minRSRP))); } /** - * Calculate adaptive heatmap visual params based on zoom level. + * ALL heatmap parameters are FIXED constants. + * NO zoom-dependent logic — this is the only way to guarantee + * that the same RSRP → same color at any zoom level. * - * Only radius and blur change with zoom (visual quality). - * maxIntensity is CONSTANT at 1.0 so the same RSRP always - * maps to the same color regardless of zoom level. - * - * Zoom 6 (country): radius=35, blur=18 - * Zoom 10 (region): radius=25, blur=13 - * Zoom 14 (city): radius=15, blur=9 - * Zoom 18 (street): radius=8, blur=6 + * radius/blur/max are constant so overlapping point contributions + * don't change as the user zooms, which was the root cause of + * the color-shifting bug. */ -function getHeatmapParams(zoom: number) { - const radius = Math.max(10, Math.min(40, 60 - zoom * 3)); - const blur = Math.max(8, Math.min(25, 35 - zoom * 1.5)); - return { - radius: Math.round(radius), - blur: Math.round(blur), - maxIntensity: 0.75, // FIXED at 0.75 — zoom-independent colors - }; -} +const HEATMAP_RADIUS = 25; +const HEATMAP_BLUR = 15; +const HEATMAP_MAX = 0.75; + +const HEATMAP_GRADIENT = { + 0.0: '#1a237e', // Deep blue (-140 dBm, no service) + 0.1: '#0d47a1', // Dark blue (-130 dBm) + 0.2: '#2196f3', // Blue (-120 dBm) + 0.3: '#00bcd4', // Cyan (-110 dBm, weak) + 0.4: '#00897b', // Teal (-100 dBm) + 0.5: '#4caf50', // Green ( -90 dBm, fair) + 0.6: '#8bc34a', // Light green ( -80 dBm) + 0.7: '#ffeb3b', // Yellow ( -70 dBm, good) + 0.8: '#ffc107', // Amber ( -60 dBm) + 0.9: '#ff9800', // Orange ( -50 dBm, excellent) + 1.0: '#f44336', // Red ( -40 dBm, very strong) +}; export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps) { const map = useMap(); - const [mapZoom, setMapZoom] = useState(map.getZoom()); - // Track zoom changes - useEffect(() => { - const handleZoomEnd = () => { - setMapZoom(map.getZoom()); - }; - - map.on('zoomend', handleZoomEnd); - return () => { - map.off('zoomend', handleZoomEnd); - }; - }, [map]); - - // Recreate heatmap layer when points, visibility, or zoom changes useEffect(() => { if (!visible || points.length === 0) return; @@ -82,9 +72,7 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps rsrpToIntensity(p.rsrp), ]); - const { radius, blur, maxIntensity } = getHeatmapParams(mapZoom); - - // Debug: log RSRP stats and heatmap params (detailed per spec) + // Debug: log RSRP stats (dev only) if (import.meta.env.DEV && heatData.length > 0) { const rsrpValues = points.map((p) => p.rsrp); const intensityValues = heatData.map((d) => d[2]); @@ -93,36 +81,25 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps normalized: rsrpToIntensity(p.rsrp), })); console.log('🔍 Heatmap Debug:', { - zoom: mapZoom, + zoom: map.getZoom(), totalPoints: points.length, rsrpRange: `${Math.min(...rsrpValues).toFixed(1)} to ${Math.max(...rsrpValues).toFixed(1)} dBm`, intensityRange: `${Math.min(...intensityValues).toFixed(3)} to ${Math.max(...intensityValues).toFixed(3)}`, - radius, - blur, - maxIntensity, // Should ALWAYS be 0.75 + radius: HEATMAP_RADIUS, + blur: HEATMAP_BLUR, + max: HEATMAP_MAX, + note: 'ALL FIXED — no zoom dependency', sample: normalizedSample, }); } const heatLayer = L.heatLayer(heatData, { - radius, - blur, + radius: HEATMAP_RADIUS, + blur: HEATMAP_BLUR, + max: HEATMAP_MAX, maxZoom: 17, - max: maxIntensity, minOpacity: 0.3, - gradient: { - 0.0: '#1a237e', // Deep blue (-130 dBm, no service) - 0.1: '#0d47a1', // Dark blue (-122 dBm) - 0.2: '#2196f3', // Blue (-114 dBm) - 0.3: '#00bcd4', // Cyan (-106 dBm, weak) - 0.4: '#00897b', // Teal ( -98 dBm) - 0.5: '#4caf50', // Green ( -90 dBm, fair) - 0.6: '#8bc34a', // Light green ( -82 dBm) - 0.7: '#ffeb3b', // Yellow ( -74 dBm, good) - 0.8: '#ffc107', // Amber ( -66 dBm) - 0.9: '#ff9800', // Orange ( -58 dBm, excellent) - 1.0: '#f44336', // Red ( -50 dBm, very strong) - }, + gradient: HEATMAP_GRADIENT, }); heatLayer.addTo(map); @@ -136,7 +113,7 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps return () => { map.removeLayer(heatLayer); }; - }, [map, points, visible, mapZoom, opacity]); + }, [map, points, visible, opacity]); return null; }