From 4b4cc47e67019ab8c61cbaa5a30014609ea795cd Mon Sep 17 00:00:00 2001 From: User Date: Sun, 1 Feb 2026 10:39:20 +0100 Subject: [PATCH] fix: use coverage circles instead of heatmap layer Replace heatmap layer with concentric circle layers around scanner positions. This avoids the z-index issue where heatmap appeared at floor 0 below all markers. Shows three coverage rings: - Green inner (good signal ~5m) - Yellow middle (fair signal ~10m) - Red outer (weak signal ~15m) Co-Authored-By: Claude Opus 4.5 --- src/rf_mapper/web/static/js/app.js | 66 +++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/src/rf_mapper/web/static/js/app.js b/src/rf_mapper/web/static/js/app.js index cc09713..85448b8 100644 --- a/src/rf_mapper/web/static/js/app.js +++ b/src/rf_mapper/web/static/js/app.js @@ -2556,6 +2556,7 @@ function toggleHeatMap() { } // Render heat map layer on 3D map +// Uses circle layers for scanner coverage visualization async function renderHeatMap() { if (!map3d || !map3dLoaded) { console.log('[HeatMap] 3D map not ready'); @@ -2572,12 +2573,15 @@ async function renderHeatMap() { // Remove existing heat map if present removeHeatMap(); - // Add heat map source + // Filter to scanner positions only (weight=1.0 indicates scanners) + const scannerPoints = data.points.filter(p => p.weight >= 0.9); + + // Add coverage circles source map3d.addSource('signal-heatmap', { type: 'geojson', data: { type: 'FeatureCollection', - features: data.points.map(p => ({ + features: scannerPoints.map(p => ({ type: 'Feature', geometry: { type: 'Point', coordinates: [p.lon, p.lat] }, properties: { weight: p.weight } @@ -2585,24 +2589,42 @@ async function renderHeatMap() { } }); - // Add heat map layer + // Add outer coverage ring (weak signal ~15m) map3d.addLayer({ - id: 'signal-heatmap-layer', - type: 'heatmap', + id: 'signal-heatmap-outer', + type: 'circle', source: 'signal-heatmap', paint: { - 'heatmap-weight': ['get', 'weight'], - 'heatmap-intensity': 0.6, - 'heatmap-radius': 40, - 'heatmap-opacity': 0.6, - 'heatmap-color': [ - 'interpolate', ['linear'], ['heatmap-density'], - 0, 'rgba(0,0,255,0)', - 0.3, 'rgba(0,255,255,0.5)', - 0.5, 'rgba(0,255,0,0.6)', - 0.7, 'rgba(255,255,0,0.7)', - 1, 'rgba(255,0,0,0.8)' - ] + 'circle-radius': 80, + 'circle-color': 'rgba(255, 100, 100, 0.15)', + 'circle-stroke-width': 1, + 'circle-stroke-color': 'rgba(255, 100, 100, 0.3)' + } + }); + + // Add middle coverage ring (fair signal ~10m) + map3d.addLayer({ + id: 'signal-heatmap-middle', + type: 'circle', + source: 'signal-heatmap', + paint: { + 'circle-radius': 50, + 'circle-color': 'rgba(255, 255, 100, 0.2)', + 'circle-stroke-width': 1, + 'circle-stroke-color': 'rgba(255, 255, 100, 0.4)' + } + }); + + // Add inner coverage ring (good signal ~5m) + map3d.addLayer({ + id: 'signal-heatmap-inner', + type: 'circle', + source: 'signal-heatmap', + paint: { + 'circle-radius': 25, + 'circle-color': 'rgba(100, 255, 100, 0.25)', + 'circle-stroke-width': 1, + 'circle-stroke-color': 'rgba(100, 255, 100, 0.5)' } }); } catch (e) { @@ -2614,9 +2636,13 @@ async function renderHeatMap() { function removeHeatMap() { if (!map3d) return; - if (map3d.getLayer('signal-heatmap-layer')) { - map3d.removeLayer('signal-heatmap-layer'); - } + // Remove all heatmap layers + ['signal-heatmap-inner', 'signal-heatmap-middle', 'signal-heatmap-outer', 'signal-heatmap-layer'].forEach(layerId => { + if (map3d.getLayer(layerId)) { + map3d.removeLayer(layerId); + } + }); + if (map3d.getSource('signal-heatmap')) { map3d.removeSource('signal-heatmap'); }