152 lines
4.3 KiB
Markdown
152 lines
4.3 KiB
Markdown
# 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.
|