# 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 (
    <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**: 
   ```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.
