@mytec: iter7.2 ready for test
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import 'leaflet.heat';
|
||||
@@ -21,48 +21,75 @@ interface HeatmapProps {
|
||||
/**
|
||||
* Normalize RSRP to 0-1 intensity for heatmap.
|
||||
*
|
||||
* Range: -140 to -40 dBm (wide range prevents color shifting)
|
||||
* Range: -130 to -50 dBm
|
||||
*
|
||||
* -140 dBm → 0.0 (deep blue, no service)
|
||||
* -130 dBm → 0.0 (deep blue, no service)
|
||||
* -90 dBm → 0.5 (green, fair)
|
||||
* -40 dBm → 1.0 (red, very strong)
|
||||
* -50 dBm → 1.0 (red, very strong)
|
||||
*/
|
||||
function rsrpToIntensity(rsrp: number): number {
|
||||
const minRSRP = -140;
|
||||
const maxRSRP = -40;
|
||||
const minRSRP = -130;
|
||||
const maxRSRP = -50;
|
||||
return Math.max(0, Math.min(1, (rsrp - minRSRP) / (maxRSRP - minRSRP)));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* Zoom-compensated heatmap parameters.
|
||||
*
|
||||
* 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.
|
||||
* radius & blur are zoom-dependent for visual quality (smooth coverage
|
||||
* at any zoom level). But changing radius changes point overlap, which
|
||||
* shifts apparent color. To compensate:
|
||||
*
|
||||
* maxIntensity = baseMax * (radius / baselineRadius)
|
||||
*
|
||||
* When radius is large (zoomed out, more overlap) → higher max → same color.
|
||||
* When radius is small (zoomed in, less overlap) → lower max → same color.
|
||||
*
|
||||
* Clamped to [0.4, 0.9] for safety.
|
||||
*/
|
||||
const HEATMAP_RADIUS = 25;
|
||||
const HEATMAP_BLUR = 15;
|
||||
const HEATMAP_MAX = 0.75;
|
||||
function getHeatmapParams(zoom: number) {
|
||||
const radius = Math.max(12, Math.min(45, 55 - zoom * 2.5));
|
||||
const blur = Math.max(10, Math.min(25, 30 - zoom * 1.2));
|
||||
|
||||
const BASE_MAX = 0.6;
|
||||
const BASELINE_RADIUS = 30;
|
||||
const radiusScale = radius / BASELINE_RADIUS;
|
||||
const maxIntensity = Math.max(0.4, Math.min(0.9, BASE_MAX * radiusScale));
|
||||
|
||||
return {
|
||||
radius: Math.round(radius),
|
||||
blur: Math.round(blur),
|
||||
maxIntensity,
|
||||
};
|
||||
}
|
||||
|
||||
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)
|
||||
0.0: '#1a237e', // Deep blue (-130 dBm, no service)
|
||||
0.15: '#0d47a1', // Dark blue
|
||||
0.25: '#2196f3', // Blue (-110 dBm, weak)
|
||||
0.35: '#00bcd4', // Cyan
|
||||
0.45: '#00897b', // Teal
|
||||
0.55: '#4caf50', // Green ( -90 dBm, fair)
|
||||
0.65: '#8bc34a', // Light green
|
||||
0.75: '#ffeb3b', // Yellow ( -70 dBm, good)
|
||||
0.85: '#ff9800', // Orange
|
||||
1.0: '#f44336', // Red ( -50 dBm, excellent)
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
@@ -72,31 +99,26 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps
|
||||
rsrpToIntensity(p.rsrp),
|
||||
]);
|
||||
|
||||
// Debug: log RSRP stats (dev only)
|
||||
const { radius, blur, maxIntensity } = getHeatmapParams(mapZoom);
|
||||
|
||||
// Debug: log heatmap params (dev only)
|
||||
if (import.meta.env.DEV && heatData.length > 0) {
|
||||
const rsrpValues = points.map((p) => p.rsrp);
|
||||
const intensityValues = heatData.map((d) => d[2]);
|
||||
const normalizedSample = points.slice(0, 5).map((p) => ({
|
||||
rsrp: p.rsrp,
|
||||
normalized: rsrpToIntensity(p.rsrp),
|
||||
}));
|
||||
console.log('🔍 Heatmap Debug:', {
|
||||
zoom: map.getZoom(),
|
||||
totalPoints: points.length,
|
||||
console.log('🔍 Heatmap:', {
|
||||
zoom: mapZoom,
|
||||
points: 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: HEATMAP_RADIUS,
|
||||
blur: HEATMAP_BLUR,
|
||||
max: HEATMAP_MAX,
|
||||
note: 'ALL FIXED — no zoom dependency',
|
||||
sample: normalizedSample,
|
||||
radius,
|
||||
blur,
|
||||
maxIntensity: maxIntensity.toFixed(3),
|
||||
radiusScale: (radius / 30).toFixed(3),
|
||||
});
|
||||
}
|
||||
|
||||
const heatLayer = L.heatLayer(heatData, {
|
||||
radius: HEATMAP_RADIUS,
|
||||
blur: HEATMAP_BLUR,
|
||||
max: HEATMAP_MAX,
|
||||
radius,
|
||||
blur,
|
||||
max: maxIntensity,
|
||||
maxZoom: 17,
|
||||
minOpacity: 0.3,
|
||||
gradient: HEATMAP_GRADIENT,
|
||||
@@ -113,7 +135,7 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps
|
||||
return () => {
|
||||
map.removeLayer(heatLayer);
|
||||
};
|
||||
}, [map, points, visible, opacity]);
|
||||
}, [map, points, visible, mapZoom, opacity]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user