@mytec: iter7.2 ready for test

This commit is contained in:
2026-01-30 13:44:32 +02:00
parent bc1996b55d
commit 55a4e1d27a

View File

@@ -34,32 +34,41 @@ function rsrpToIntensity(rsrp: number): number {
}
/**
* Zoom-compensated heatmap parameters.
* Geographic scale-aware heatmap parameters.
*
* 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:
* The key insight: we want each heatmap point to cover a constant
* GEOGRAPHIC area (400m radius) regardless of zoom level. Since
* leaflet.heat works in pixels, we convert:
*
* maxIntensity = baseMax * (radius / baselineRadius)
* pixelsPerKm = 2^zoom * 6.4 (at equator, simplified)
* radiusPixels = targetRadiusKm * pixelsPerKm
*
* When radius is large (zoomed out, more overlap) → higher max → same color.
* When radius is small (zoomed in, less overlap) → lower max → same color.
* With progressive clamps for visual quality:
* - Low zoom (≤9): min 15px, max 45px (avoid giant blobs)
* - High zoom (≥10): min 30px, max 80px (fill gaps between grid points)
*
* Clamped to [0.4, 0.9] for safety.
* maxIntensity is CONSTANT at 0.75 — no compensation needed because
* the geographic overlap between adjacent points stays consistent
* when radius tracks geographic scale.
*/
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 pixelsPerKm = Math.pow(2, zoom) * 6.4;
const targetRadiusKm = 0.4; // 400 meters
const radiusPixels = targetRadiusKm * pixelsPerKm;
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));
// Progressive clamps: wider range at high zoom for smooth fill
const minRadius = zoom < 10 ? 15 : 30;
const maxRadius = zoom < 10 ? 45 : 80;
const radius = Math.max(minRadius, Math.min(maxRadius, radiusPixels));
const blur = Math.round(radius * 0.6);
return {
radius: Math.round(radius),
blur: Math.round(blur),
maxIntensity,
blur,
maxIntensity: 0.75, // CONSTANT — geographic consistency means no compensation needed
pixelsPerKm,
radiusPixels,
};
}
@@ -99,19 +108,22 @@ export default function Heatmap({ points, visible, opacity = 0.7 }: HeatmapProps
rsrpToIntensity(p.rsrp),
]);
const { radius, blur, maxIntensity } = getHeatmapParams(mapZoom);
const { radius, blur, maxIntensity, pixelsPerKm, radiusPixels } =
getHeatmapParams(mapZoom);
// Debug: log heatmap params (dev only)
// Debug: log geographic scale params (dev only)
if (import.meta.env.DEV && heatData.length > 0) {
const rsrpValues = points.map((p) => p.rsrp);
console.log('🔍 Heatmap:', {
console.log('🔍 Heatmap Geographic:', {
zoom: mapZoom,
pixelsPerKm: pixelsPerKm.toFixed(1),
targetRadiusKm: 0.4,
radiusPixelsRaw: radiusPixels.toFixed(1),
radiusClamped: radius,
blur,
maxIntensity,
points: points.length,
rsrpRange: `${Math.min(...rsrpValues).toFixed(1)} to ${Math.max(...rsrpValues).toFixed(1)} dBm`,
radius,
blur,
maxIntensity: maxIntensity.toFixed(3),
radiusScale: (radius / 30).toFixed(3),
});
}