Files
rfcp/RFCP-Heatmap-Fix.md

4.3 KiB

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:

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 (
    <HeatmapLayer
      points={heatmapPoints}
      longitudeExtractor={(p) => 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:

    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:

    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:

// 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.