fix: use CSS-based coverage rings on scanner markers

Coverage rings now render as CSS pseudo-elements on scanner markers,
following the same floor offset positioning. Toggle .heatmap-enabled
class on map container to show/hide coverage visualization.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
User
2026-02-01 10:42:30 +01:00
parent 4b4cc47e67
commit 1cc403eea6
2 changed files with 39 additions and 87 deletions

View File

@@ -978,6 +978,33 @@ body {
background: rgba(0, 200, 255, 0.9);
}
/* Scanner coverage rings (shown when heatmap enabled) */
.heatmap-enabled .marker-3d.center .marker-icon::before,
.heatmap-enabled .marker-3d.peer-scanner .marker-icon::before {
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 120px;
height: 120px;
border-radius: 50%;
background: radial-gradient(circle,
rgba(100, 255, 100, 0.3) 0%,
rgba(100, 255, 100, 0.2) 25%,
rgba(255, 255, 100, 0.15) 50%,
rgba(255, 100, 100, 0.1) 75%,
transparent 100%
);
pointer-events: none;
z-index: -1;
}
.heatmap-enabled .marker-3d.center .marker-icon,
.heatmap-enabled .marker-3d.peer-scanner .marker-icon {
position: relative;
}
/* Trilaterated device markers - gold border */
.marker-3d.trilaterated .marker-icon {
border: 2px dashed #ffd700 !important;

View File

@@ -2548,6 +2548,13 @@ function toggleHeatMap() {
const btn = document.getElementById('btn-heatmap');
if (btn) btn.classList.toggle('active', heatMapEnabled);
// Toggle CSS class on map container for scanner coverage rings
const mapContainer = document.getElementById('map-3d');
if (mapContainer) {
mapContainer.classList.toggle('heatmap-enabled', heatMapEnabled);
}
// Also render/remove MapLibre layers for additional visualization
if (heatMapEnabled) {
renderHeatMap();
} else {
@@ -2555,95 +2562,13 @@ function toggleHeatMap() {
}
}
// Render heat map layer on 3D map
// Uses circle layers for scanner coverage visualization
// Render heat map visualization
// Coverage rings are CSS-based on scanner markers (via .heatmap-enabled class)
async function renderHeatMap() {
if (!map3d || !map3dLoaded) {
console.log('[HeatMap] 3D map not ready');
return;
}
try {
const resp = await fetch('/api/heatmap/signal');
if (!resp.ok) return;
const data = await resp.json();
console.log(`[HeatMap] Loaded ${data.count} points`);
// Remove existing heat map if present
removeHeatMap();
// 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: scannerPoints.map(p => ({
type: 'Feature',
geometry: { type: 'Point', coordinates: [p.lon, p.lat] },
properties: { weight: p.weight }
}))
}
});
// Add outer coverage ring (weak signal ~15m)
map3d.addLayer({
id: 'signal-heatmap-outer',
type: 'circle',
source: 'signal-heatmap',
paint: {
'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) {
console.error('[HeatMap] Error:', e);
}
console.log('[HeatMap] Enabled - coverage rings shown around scanners');
}
// Remove heat map layer from 3D map
// Remove heat map visualization
function removeHeatMap() {
if (!map3d) return;
// 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');
}
console.log('[HeatMap] Disabled');
}