@mytec: iter10.3.2 start
This commit is contained in:
207
RFCP-Iteration10.3.2-Fix-Boundary-Rendering.md
Normal file
207
RFCP-Iteration10.3.2-Fix-Boundary-Rendering.md
Normal file
@@ -0,0 +1,207 @@
|
||||
# RFCP Iteration 10.3.2 — Fix Coverage Boundary Rendering
|
||||
|
||||
**Date:** 2025-01-30
|
||||
**Status:** Ready for Implementation
|
||||
**Priority:** High
|
||||
**Estimated Effort:** 30-45 minutes
|
||||
|
||||
---
|
||||
|
||||
## Problem Statement
|
||||
|
||||
CoverageBoundary component renders but is nearly invisible:
|
||||
- Boundary polygon is only **25x25 pixels** instead of covering the entire coverage area
|
||||
- Edge detection algorithm returns only **~11 points** instead of hundreds
|
||||
- Path exists in DOM with correct styling but wrong geometry
|
||||
|
||||
### Debug Evidence
|
||||
|
||||
```
|
||||
[CoverageBoundary] Computing: {visible: true, pointsCount: 9132, resolution: 200}
|
||||
[CoverageBoundary] Paths: 1
|
||||
```
|
||||
|
||||
```javascript
|
||||
document.querySelectorAll('.leaflet-overlay-pane path')[1].getBoundingClientRect()
|
||||
// → DOMRect {width: 25, height: 25} // Should be ~500x500 or larger!
|
||||
|
||||
// Path has only 11 vertices:
|
||||
// M780 422L774 422L768 416L768 402L773 397L787 397L793 403L793 415L791 419L786 422L780 422
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Root Cause Analysis
|
||||
|
||||
In `/opt/rfcp/frontend/src/components/map/CoverageBoundary.tsx`, function `computeEdgePath()`:
|
||||
|
||||
1. **Grid cell size too coarse:**
|
||||
- Cell size = `resolution` (200m) converted to degrees
|
||||
- With 9132 points spread over coverage area, most cells have neighbors
|
||||
- Only ~11 cells on the very edge are detected as "boundary"
|
||||
|
||||
2. **Angular sorting from centroid fails for sector shapes:**
|
||||
- Coverage is a sector (wedge), not a circle
|
||||
- Sorting by angle from centroid produces zigzag paths for concave shapes
|
||||
|
||||
3. **Single representative point per cell:**
|
||||
- Algorithm picks one point per grid cell
|
||||
- Loses boundary detail when cells are large
|
||||
|
||||
---
|
||||
|
||||
## Solution: Use Turf.js Concave Hull
|
||||
|
||||
Replace custom edge detection with `@turf/concave` — purpose-built for this exact use case.
|
||||
|
||||
### Why Turf.js?
|
||||
|
||||
- **Concave hull** follows actual shape (sectors, irregular coverage)
|
||||
- **Battle-tested** library used in GIS applications
|
||||
- **Configurable** maxEdge parameter controls detail level
|
||||
- **Fast** — optimized for thousands of points
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Step 1: Install Dependencies
|
||||
|
||||
```bash
|
||||
cd /opt/rfcp/frontend
|
||||
npm install @turf/concave @turf/helpers
|
||||
```
|
||||
|
||||
### Step 2: Rewrite computeEdgePath()
|
||||
|
||||
Replace the entire `computeEdgePath` function with:
|
||||
|
||||
```typescript
|
||||
import concave from '@turf/concave';
|
||||
import { featureCollection, point } from '@turf/helpers';
|
||||
|
||||
/**
|
||||
* Compute concave hull boundary for coverage points.
|
||||
* Uses Turf.js concave hull algorithm (alpha shape).
|
||||
*
|
||||
* @param pts - Coverage points for one site
|
||||
* @param resolutionM - Resolution in meters, used to set maxEdge
|
||||
* @returns Ordered boundary coordinates for Leaflet polyline
|
||||
*/
|
||||
function computeEdgePath(
|
||||
pts: CoveragePoint[],
|
||||
resolutionM: number
|
||||
): L.LatLngExpression[] {
|
||||
if (pts.length < 3) return [];
|
||||
|
||||
// Convert to GeoJSON points
|
||||
const features = pts.map(p => point([p.lon, p.lat]));
|
||||
const fc = featureCollection(features);
|
||||
|
||||
// Compute concave hull
|
||||
// maxEdge in kilometers — use resolution * 3 for good detail
|
||||
const maxEdge = (resolutionM * 3) / 1000;
|
||||
|
||||
try {
|
||||
const hull = concave(fc, { maxEdge, units: 'kilometers' });
|
||||
|
||||
if (!hull || hull.geometry.type !== 'Polygon') {
|
||||
console.warn('[CoverageBoundary] Concave hull failed, falling back to convex');
|
||||
return [];
|
||||
}
|
||||
|
||||
// Extract coordinates (GeoJSON is [lon, lat], Leaflet needs [lat, lon])
|
||||
const coords = hull.geometry.coordinates[0];
|
||||
return coords.map(([lon, lat]) => [lat, lon] as L.LatLngExpression);
|
||||
|
||||
} catch (error) {
|
||||
console.error('[CoverageBoundary] Hull computation error:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Step 3: Update Imports at Top of File
|
||||
|
||||
```typescript
|
||||
import { useEffect, useRef, useMemo } from 'react';
|
||||
import { useMap } from 'react-leaflet';
|
||||
import L from 'leaflet';
|
||||
import concave from '@turf/concave';
|
||||
import { featureCollection, point } from '@turf/helpers';
|
||||
import type { CoveragePoint } from '@/types/index.ts';
|
||||
```
|
||||
|
||||
### Step 4: Filter Points by Threshold
|
||||
|
||||
**Important:** Currently CoverageBoundary receives ALL points, but heatmap filters by `rsrpThreshold`. Boundary should match heatmap edge.
|
||||
|
||||
In `App.tsx`, filter points before passing:
|
||||
|
||||
```tsx
|
||||
<CoverageBoundary
|
||||
points={coverageResult.points.filter(p => p.rsrp >= settings.rsrpThreshold)}
|
||||
visible={heatmapVisible}
|
||||
resolution={settings.resolution}
|
||||
/>
|
||||
```
|
||||
|
||||
### Step 5: Remove Debug Logging
|
||||
|
||||
After confirming fix works, remove the `console.log` statements added during debugging.
|
||||
|
||||
---
|
||||
|
||||
## Files to Modify
|
||||
|
||||
| File | Changes |
|
||||
|------|---------|
|
||||
| `package.json` | Add @turf/concave, @turf/helpers |
|
||||
| `src/components/map/CoverageBoundary.tsx` | Replace computeEdgePath with Turf.js implementation |
|
||||
| `src/App.tsx` | Filter coverage points by rsrpThreshold |
|
||||
|
||||
---
|
||||
|
||||
## Expected Results
|
||||
|
||||
### Before (Current Bug)
|
||||
- Boundary: 25x25 pixels, ~11 vertices
|
||||
- Not visible at normal zoom
|
||||
|
||||
### After (Fixed)
|
||||
- Boundary: Follows heatmap edge closely
|
||||
- Purple dashed line (#7c3aed) visible around orange gradient
|
||||
- Updates when Min Signal slider changes
|
||||
- Properly handles sector shapes (wedges, not just circles)
|
||||
|
||||
---
|
||||
|
||||
## Testing Checklist
|
||||
|
||||
- [ ] Boundary visible around coverage area
|
||||
- [ ] Boundary follows actual coverage shape (sector/wedge)
|
||||
- [ ] Boundary updates when resolution changes
|
||||
- [ ] Boundary updates when Min Signal threshold changes
|
||||
- [ ] No console errors
|
||||
- [ ] Performance acceptable (< 100ms for 10k points)
|
||||
|
||||
---
|
||||
|
||||
## Rollback Plan
|
||||
|
||||
If Turf.js causes issues, revert to previous edge detection but with smaller grid cells:
|
||||
|
||||
```typescript
|
||||
// In computeEdgePath, change:
|
||||
const cellLat = resolutionM / 111_000;
|
||||
// To:
|
||||
const cellLat = (resolutionM / 4) / 111_000; // 4x finer grid
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Reference
|
||||
|
||||
- Turf.js concave: https://turfjs.org/docs/#concave
|
||||
- Leaflet polyline: https://leafletjs.com/reference.html#polyline
|
||||
- Previous iteration: RFCP-Iteration10.3.1-Threshold-Filter-Fix.md
|
||||
Reference in New Issue
Block a user