@mytec: iter 7.1 ready for test
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect } from 'react';
|
||||||
import { useMap } from 'react-leaflet';
|
import { useMap } from 'react-leaflet';
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
import 'leaflet.heat';
|
import 'leaflet.heat';
|
||||||
@@ -21,58 +21,48 @@ interface HeatmapProps {
|
|||||||
/**
|
/**
|
||||||
* Normalize RSRP to 0-1 intensity for heatmap.
|
* Normalize RSRP to 0-1 intensity for heatmap.
|
||||||
*
|
*
|
||||||
* Wide range -130 to -50 dBm ensures the full gradient is used
|
* Range: -140 to -40 dBm (wide range prevents color shifting)
|
||||||
* and close-in strong signals don't all saturate to one color.
|
|
||||||
*
|
*
|
||||||
* -130 dBm → 0.0 (deep blue, no service)
|
* -140 dBm → 0.0 (deep blue, no service)
|
||||||
* -90 dBm → 0.5 (green, fair)
|
* -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 {
|
function rsrpToIntensity(rsrp: number): number {
|
||||||
const minRSRP = -130;
|
const minRSRP = -140;
|
||||||
const maxRSRP = -50;
|
const maxRSRP = -40;
|
||||||
return Math.max(0, Math.min(1, (rsrp - minRSRP) / (maxRSRP - minRSRP)));
|
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).
|
* radius/blur/max are constant so overlapping point contributions
|
||||||
* maxIntensity is CONSTANT at 1.0 so the same RSRP always
|
* don't change as the user zooms, which was the root cause of
|
||||||
* maps to the same color regardless of zoom level.
|
* the color-shifting bug.
|
||||||
*
|
|
||||||
* 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
|
|
||||||
*/
|
*/
|
||||||
function getHeatmapParams(zoom: number) {
|
const HEATMAP_RADIUS = 25;
|
||||||
const radius = Math.max(10, Math.min(40, 60 - zoom * 3));
|
const HEATMAP_BLUR = 15;
|
||||||
const blur = Math.max(8, Math.min(25, 35 - zoom * 1.5));
|
const HEATMAP_MAX = 0.75;
|
||||||
return {
|
|
||||||
radius: Math.round(radius),
|
const HEATMAP_GRADIENT = {
|
||||||
blur: Math.round(blur),
|
0.0: '#1a237e', // Deep blue (-140 dBm, no service)
|
||||||
maxIntensity: 0.75, // FIXED at 0.75 — zoom-independent colors
|
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) {
|
export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps) {
|
||||||
const map = useMap();
|
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(() => {
|
useEffect(() => {
|
||||||
if (!visible || points.length === 0) return;
|
if (!visible || points.length === 0) return;
|
||||||
|
|
||||||
@@ -82,9 +72,7 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps
|
|||||||
rsrpToIntensity(p.rsrp),
|
rsrpToIntensity(p.rsrp),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const { radius, blur, maxIntensity } = getHeatmapParams(mapZoom);
|
// Debug: log RSRP stats (dev only)
|
||||||
|
|
||||||
// Debug: log RSRP stats and heatmap params (detailed per spec)
|
|
||||||
if (import.meta.env.DEV && heatData.length > 0) {
|
if (import.meta.env.DEV && heatData.length > 0) {
|
||||||
const rsrpValues = points.map((p) => p.rsrp);
|
const rsrpValues = points.map((p) => p.rsrp);
|
||||||
const intensityValues = heatData.map((d) => d[2]);
|
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),
|
normalized: rsrpToIntensity(p.rsrp),
|
||||||
}));
|
}));
|
||||||
console.log('🔍 Heatmap Debug:', {
|
console.log('🔍 Heatmap Debug:', {
|
||||||
zoom: mapZoom,
|
zoom: map.getZoom(),
|
||||||
totalPoints: points.length,
|
totalPoints: points.length,
|
||||||
rsrpRange: `${Math.min(...rsrpValues).toFixed(1)} to ${Math.max(...rsrpValues).toFixed(1)} dBm`,
|
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)}`,
|
intensityRange: `${Math.min(...intensityValues).toFixed(3)} to ${Math.max(...intensityValues).toFixed(3)}`,
|
||||||
radius,
|
radius: HEATMAP_RADIUS,
|
||||||
blur,
|
blur: HEATMAP_BLUR,
|
||||||
maxIntensity, // Should ALWAYS be 0.75
|
max: HEATMAP_MAX,
|
||||||
|
note: 'ALL FIXED — no zoom dependency',
|
||||||
sample: normalizedSample,
|
sample: normalizedSample,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const heatLayer = L.heatLayer(heatData, {
|
const heatLayer = L.heatLayer(heatData, {
|
||||||
radius,
|
radius: HEATMAP_RADIUS,
|
||||||
blur,
|
blur: HEATMAP_BLUR,
|
||||||
|
max: HEATMAP_MAX,
|
||||||
maxZoom: 17,
|
maxZoom: 17,
|
||||||
max: maxIntensity,
|
|
||||||
minOpacity: 0.3,
|
minOpacity: 0.3,
|
||||||
gradient: {
|
gradient: HEATMAP_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)
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
heatLayer.addTo(map);
|
heatLayer.addTo(map);
|
||||||
@@ -136,7 +113,7 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps
|
|||||||
return () => {
|
return () => {
|
||||||
map.removeLayer(heatLayer);
|
map.removeLayer(heatLayer);
|
||||||
};
|
};
|
||||||
}, [map, points, visible, mapZoom, opacity]);
|
}, [map, points, visible, opacity]);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user