@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 * The key insight: we want each heatmap point to cover a constant
* at any zoom level). But changing radius changes point overlap, which * GEOGRAPHIC area (400m radius) regardless of zoom level. Since
* shifts apparent color. To compensate: * 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. * With progressive clamps for visual quality:
* When radius is small (zoomed in, less overlap) → lower max → same color. * - 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) { function getHeatmapParams(zoom: number) {
const radius = Math.max(12, Math.min(45, 55 - zoom * 2.5)); const pixelsPerKm = Math.pow(2, zoom) * 6.4;
const blur = Math.max(10, Math.min(25, 30 - zoom * 1.2)); const targetRadiusKm = 0.4; // 400 meters
const radiusPixels = targetRadiusKm * pixelsPerKm;
const BASE_MAX = 0.6; // Progressive clamps: wider range at high zoom for smooth fill
const BASELINE_RADIUS = 30; const minRadius = zoom < 10 ? 15 : 30;
const radiusScale = radius / BASELINE_RADIUS; const maxRadius = zoom < 10 ? 45 : 80;
const maxIntensity = Math.max(0.4, Math.min(0.9, BASE_MAX * radiusScale)); const radius = Math.max(minRadius, Math.min(maxRadius, radiusPixels));
const blur = Math.round(radius * 0.6);
return { return {
radius: Math.round(radius), radius: Math.round(radius),
blur: Math.round(blur), blur,
maxIntensity, 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), 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) { if (import.meta.env.DEV && heatData.length > 0) {
const rsrpValues = points.map((p) => p.rsrp); const rsrpValues = points.map((p) => p.rsrp);
console.log('🔍 Heatmap:', { console.log('🔍 Heatmap Geographic:', {
zoom: mapZoom, zoom: mapZoom,
pixelsPerKm: pixelsPerKm.toFixed(1),
targetRadiusKm: 0.4,
radiusPixelsRaw: radiusPixels.toFixed(1),
radiusClamped: radius,
blur,
maxIntensity,
points: points.length, points: 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`,
radius,
blur,
maxIntensity: maxIntensity.toFixed(3),
radiusScale: (radius / 30).toFixed(3),
}); });
} }