diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 6460b3f..78b7341 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -380,6 +380,7 @@ export default function App() { visible={heatmapVisible} opacity={settings.heatmapOpacity} radiusMeters={settings.heatmapRadius} + rsrpThreshold={settings.rsrpThreshold} /> (null); @@ -53,7 +55,12 @@ export default function GeographicHeatmap({ rendererRef.current.setRadiusMeters(radiusMeters); }, [radiusMeters]); - // Invalidate cache when points change (use length + first/last coords as fingerprint) + // Update renderer threshold when prop changes + useEffect(() => { + rendererRef.current.setRsrpThreshold(rsrpThreshold); + }, [rsrpThreshold]); + + // Invalidate cache when points or threshold change useEffect(() => { if (points.length === 0) { rendererRef.current.setPointsHash('empty'); @@ -61,9 +68,9 @@ export default function GeographicHeatmap({ } const first = points[0]; const last = points[points.length - 1]; - const hash = `${points.length}:${first.lat.toFixed(4)},${first.lon.toFixed(4)}:${last.rsrp}`; + const hash = `${points.length}:${first.lat.toFixed(4)},${first.lon.toFixed(4)}:${last.rsrp}:t${rsrpThreshold}`; rendererRef.current.setPointsHash(hash); - }, [points]); + }, [points, rsrpThreshold]); // Create / destroy GridLayer const createLayer = useCallback(() => { diff --git a/frontend/src/components/map/HeatmapTileRenderer.ts b/frontend/src/components/map/HeatmapTileRenderer.ts index f66d1d6..f3453d2 100644 --- a/frontend/src/components/map/HeatmapTileRenderer.ts +++ b/frontend/src/components/map/HeatmapTileRenderer.ts @@ -34,6 +34,7 @@ export interface HeatmapPoint { export class HeatmapTileRenderer { private tileSize = 256; private radiusMeters: number; + private rsrpThreshold: number; // LRU cache: key → canvas private cache = new Map(); @@ -42,9 +43,10 @@ export class HeatmapTileRenderer { // Points fingerprint for cache invalidation private pointsHash = ''; - constructor(radiusMeters = 400, maxCacheSize = 150) { + constructor(radiusMeters = 400, maxCacheSize = 150, rsrpThreshold = -100) { this.radiusMeters = radiusMeters; this.maxCacheSize = maxCacheSize; + this.rsrpThreshold = rsrpThreshold; } /** Update the geographic radius of each coverage point. */ @@ -55,6 +57,14 @@ export class HeatmapTileRenderer { } } + /** Update the RSRP threshold — points below this are not rendered. */ + setRsrpThreshold(threshold: number): void { + if (threshold !== this.rsrpThreshold) { + this.rsrpThreshold = threshold; + this.clearCache(); + } + } + /** Call when points change to invalidate cache. */ setPointsHash(hash: string): void { if (hash !== this.pointsHash) { @@ -105,9 +115,10 @@ export class HeatmapTileRenderer { // still contribute their gaussian tail inside the tile const bufferDeg = (this.radiusMeters / 111_000) * 2; - // Filter relevant points + // Filter relevant points — geographic bounds AND RSRP threshold const relevant = points.filter( (p) => + p.rsrp >= this.rsrpThreshold && p.lat >= latMin - bufferDeg && p.lat <= latMax + bufferDeg && p.lon >= lonMin - bufferDeg &&