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:
@@ -978,6 +978,33 @@ body {
|
|||||||
background: rgba(0, 200, 255, 0.9);
|
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 */
|
/* Trilaterated device markers - gold border */
|
||||||
.marker-3d.trilaterated .marker-icon {
|
.marker-3d.trilaterated .marker-icon {
|
||||||
border: 2px dashed #ffd700 !important;
|
border: 2px dashed #ffd700 !important;
|
||||||
|
|||||||
@@ -2548,6 +2548,13 @@ function toggleHeatMap() {
|
|||||||
const btn = document.getElementById('btn-heatmap');
|
const btn = document.getElementById('btn-heatmap');
|
||||||
if (btn) btn.classList.toggle('active', heatMapEnabled);
|
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) {
|
if (heatMapEnabled) {
|
||||||
renderHeatMap();
|
renderHeatMap();
|
||||||
} else {
|
} else {
|
||||||
@@ -2555,95 +2562,13 @@ function toggleHeatMap() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render heat map layer on 3D map
|
// Render heat map visualization
|
||||||
// Uses circle layers for scanner coverage visualization
|
// Coverage rings are CSS-based on scanner markers (via .heatmap-enabled class)
|
||||||
async function renderHeatMap() {
|
async function renderHeatMap() {
|
||||||
if (!map3d || !map3dLoaded) {
|
console.log('[HeatMap] Enabled - coverage rings shown around scanners');
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove heat map layer from 3D map
|
// Remove heat map visualization
|
||||||
function removeHeatMap() {
|
function removeHeatMap() {
|
||||||
if (!map3d) return;
|
console.log('[HeatMap] Disabled');
|
||||||
|
|
||||||
// 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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user