# RFCP Heatmap Fix - Zoom-Dependent Intensity ## Problem At close zoom (12-15), heatmap becomes solid yellow/orange with no gradient visible. ## Root Cause The `intensity` values and `max` parameter aren't scaling properly with zoom level, causing saturation. ## Solution ### File: `frontend/src/components/map/Heatmap.tsx` Replace the current implementation with this improved version: ```typescript import { useEffect, useState } from 'react'; import { useMap } from 'react-leaflet'; import { HeatmapLayerFactory } from '@vgrid/react-leaflet-heatmap-layer'; const HeatmapLayer = HeatmapLayerFactory<[number, number, number]>(); interface HeatmapProps { points: Array<{ lat: number; lon: number; rsrp: number; siteId: string; }>; visible: boolean; } export function Heatmap({ points, visible }: HeatmapProps) { const map = useMap(); const [mapZoom, setMapZoom] = useState(map.getZoom()); useEffect(() => { const handleZoomEnd = () => { setMapZoom(map.getZoom()); }; map.on('zoomend', handleZoomEnd); return () => { map.off('zoomend', handleZoomEnd); }; }, [map]); if (!visible || points.length === 0) { return null; } // Zoom-dependent heatmap parameters const getHeatmapParams = (zoom: number) => { // Radius scales inversely with zoom // zoom 6 (country): radius=40, blur=20 // zoom 10 (region): radius=28, blur=14 // zoom 14 (city): radius=16, blur=10 // zoom 18 (street): radius=8, blur=6 const radius = Math.max(8, Math.min(40, 50 - zoom * 2.5)); const blur = Math.max(6, Math.min(20, 30 - zoom * 1.5)); // Max intensity also scales with zoom // Lower zoom (zoomed out) = higher max (more spread) // Higher zoom (zoomed in) = lower max (more detail) const maxIntensity = Math.max(0.3, Math.min(1.0, 1.2 - zoom * 0.05)); return { radius, blur, maxIntensity }; }; const { radius, blur, maxIntensity } = getHeatmapParams(mapZoom); // Normalize RSRP to 0-1 intensity // RSRP ranges: -120 (very weak) to -70 (excellent) const normalizeRSRP = (rsrp: number): number => { const minRSRP = -120; const maxRSRP = -70; const normalized = (rsrp - minRSRP) / (maxRSRP - minRSRP); return Math.max(0, Math.min(1, normalized)); }; // Convert points to heatmap format: [lat, lon, intensity] const heatmapPoints = points.map(point => [ point.lat, point.lon, normalizeRSRP(point.rsrp) ] as [number, number, number]); return ( p[1]} latitudeExtractor={(p) => p[0]} intensityExtractor={(p) => p[2]} gradient={{ 0.0: '#0d47a1', // Dark Blue (very weak) 0.2: '#00bcd4', // Cyan (weak) 0.4: '#4caf50', // Green (fair) 0.6: '#ffeb3b', // Yellow (good) 0.8: '#ff9800', // Orange (strong) 1.0: '#f44336', // Red (excellent) }} radius={radius} blur={blur} max={maxIntensity} // ← KEY FIX: dynamic max minOpacity={0.3} /> ); } ``` ## Key Changes 1. **Dynamic `max` parameter**: ```typescript const maxIntensity = Math.max(0.3, Math.min(1.0, 1.2 - zoom * 0.05)); ``` - Zoom 6: max = 0.9 (spread out, needs higher threshold) - Zoom 12: max = 0.6 (medium detail) - Zoom 18: max = 0.3 (tight detail, lower threshold) 2. **Better RSRP normalization**: ```typescript const normalized = (rsrp - minRSRP) / (maxRSRP - minRSRP); ``` - Ensures full 0-1 range is used - Maps -120 dBm → 0.0 (blue) - Maps -70 dBm → 1.0 (red) 3. **Clearer variable names** and comments ## Testing After applying this fix: 1. **Zoom out (level 6-8)**: Should see smooth gradient, larger blob 2. **Zoom medium (level 10-12)**: Clear color transitions 3. **Zoom close (level 14-16)**: Should still show gradient, not solid color 4. **Very close (level 18+)**: Small detailed dots with gradient ## If Still Solid at Close Zoom Try adjusting the `maxIntensity` formula: ```typescript // More aggressive scaling (lower max at high zoom) const maxIntensity = Math.max(0.2, Math.min(1.0, 1.5 - zoom * 0.08)); // Or even more aggressive const maxIntensity = Math.max(0.15, Math.min(1.0, 2.0 - zoom * 0.1)); ``` This will make the gradient more visible at close zoom levels.