From 0c656cff2b5de3938cb08808e24c5f79ca18bd91 Mon Sep 17 00:00:00 2001 From: Smittix Date: Sun, 8 Feb 2026 21:27:36 +0000 Subject: [PATCH] Fix heatmap for towers with CID=0 or no geocoded coordinates The monitored tower may have CID=0 (partially decoded cell) which OpenCellID can't geocode, leaving it without coordinates. The heatmap now falls back through: monitored tower by ARFCN, any geocoded tower, then observer location. Also tracks the monitored ARFCN so the fallback can find the right tower even when CID matching fails. Co-Authored-By: Claude Opus 4.6 --- templates/gsm_spy_dashboard.html | 79 ++++++++++++++++++++++---------- 1 file changed, 56 insertions(+), 23 deletions(-) diff --git a/templates/gsm_spy_dashboard.html b/templates/gsm_spy_dashboard.html index 82e0689..85b12a2 100644 --- a/templates/gsm_spy_dashboard.html +++ b/templates/gsm_spy_dashboard.html @@ -1818,6 +1818,7 @@ let heatmapLayer = null; let heatmapVisible = false; let heatmapLastRefresh = 0; + let monitoredArfcn = null; // Statistics let stats = { @@ -2168,6 +2169,7 @@ updateScanStatus('Scan #' + data.scan + ' complete (' + data.towers_found + ' towers, ' + data.duration + 's)'); document.getElementById('scanProgressBar').style.width = '100%'; } else if (data.type === 'auto_monitor_started') { + monitoredArfcn = data.arfcn; showMonitorStatus(data.arfcn); console.log('[GSM SPY] Auto-monitor started on ARFCN', data.arfcn); } else if (data.type === 'monitor_heartbeat') { @@ -3172,6 +3174,48 @@ } } + function findTowerCoords(cid, lac) { + // Try exact CID+LAC match first + for (const key in towers) { + const t = towers[key]; + if (Number(t.cid) === cid && Number(t.lac) === lac && + t.lat && t.lon && !isNaN(parseFloat(t.lat)) && !isNaN(parseFloat(t.lon))) { + return [parseFloat(t.lat), parseFloat(t.lon)]; + } + } + return null; + } + + function findFallbackCoords() { + // 1) Try the monitored tower (by ARFCN) + if (monitoredArfcn !== null) { + for (const key in towers) { + const t = towers[key]; + if (Number(t.arfcn) === Number(monitoredArfcn) && + t.lat && t.lon && !isNaN(parseFloat(t.lat)) && !isNaN(parseFloat(t.lon))) { + console.log(`[GSM SPY] Heatmap: using monitored tower ARFCN ${monitoredArfcn} coords as fallback`); + return [parseFloat(t.lat), parseFloat(t.lon)]; + } + } + } + // 2) Try any geocoded tower + for (const key in towers) { + const t = towers[key]; + if (t.lat && t.lon && !isNaN(parseFloat(t.lat)) && !isNaN(parseFloat(t.lon))) { + console.log(`[GSM SPY] Heatmap: using tower CID ${t.cid} coords as fallback`); + return [parseFloat(t.lat), parseFloat(t.lon)]; + } + } + // 3) Fall back to observer location + const obsLat = parseFloat(document.getElementById('obsLat').value); + const obsLon = parseFloat(document.getElementById('obsLon').value); + if (!isNaN(obsLat) && !isNaN(obsLon)) { + console.log('[GSM SPY] Heatmap: using observer location as fallback'); + return [obsLat, obsLon]; + } + return null; + } + async function updateHeatmap() { try { const response = await fetch('/gsm_spy/crowd_density?hours=1'); @@ -3188,39 +3232,28 @@ const points = []; let maxIntensity = 1; - let matched = 0; - let unmatched = 0; + const fallbackCoords = findFallbackCoords(); data.forEach(item => { const itemCid = Number(item.cid); const itemLac = Number(item.lac); - let found = false; + const intensity = item.unique_devices || 1; + if (intensity > maxIntensity) maxIntensity = intensity; - for (const key in towers) { - const t = towers[key]; - if (Number(t.cid) === itemCid && Number(t.lac) === itemLac) { - if (t.lat && t.lon && !isNaN(parseFloat(t.lat)) && !isNaN(parseFloat(t.lon))) { - const intensity = item.unique_devices || 1; - if (intensity > maxIntensity) maxIntensity = intensity; - points.push([parseFloat(t.lat), parseFloat(t.lon), intensity]); - found = true; - } else { - console.log(`[GSM SPY] Heatmap: tower CID ${itemCid} LAC ${itemLac} has no coordinates yet`); - } - break; - } + // Try exact CID+LAC tower match + const coords = findTowerCoords(itemCid, itemLac); + if (coords) { + points.push([coords[0], coords[1], intensity]); + } else if (fallbackCoords) { + // Tower not geocoded or CID=0 — use fallback location + points.push([fallbackCoords[0], fallbackCoords[1], intensity]); } - - if (found) matched++; - else unmatched++; }); - console.log(`[GSM SPY] Heatmap: ${matched} matched, ${unmatched} unmatched, ${points.length} points with coords`); + console.log(`[GSM SPY] Heatmap: ${points.length} points from ${data.length} density records`); if (points.length === 0) { - if (!heatmapVisible) { - console.warn('[GSM SPY] Heatmap: density data exists but no towers have coordinates. Wait for geocoding.'); - } + console.warn('[GSM SPY] Heatmap: no plottable points (no tower coords and no fallback)'); return false; }