diff --git a/docs/COVERAGE.md b/docs/COVERAGE.md index 6aa6858..f478dbf 100644 --- a/docs/COVERAGE.md +++ b/docs/COVERAGE.md @@ -32,27 +32,5 @@ and the last point above the threshold forms the outline. - No terrain or building data is used (area mode only). - Results are sensitive to power, height, and threshold. - Environmental factors can cause large real-world deviations. - - Observed coverage depends on gateway locations and recent traffic volume. - - - -## Observed coverage (real data) - -Meshview can also draw an **observed coverage** perimeter based on real packet -sightings. This uses packets **from the node** and the gateways that heard them. -We filter to **direct/1-hop** sightings (`hop_start - hop_limit <= 1`) and then: - -1. Compute distance + bearing from the sender to each gateway with location. -2. Bucket by bearing (default 5°). -3. Keep the **farthest** gateway in each bearing bucket. -4. Connect those points into a perimeter polygon. - -This gives a **real-world envelope** that reflects terrain, antenna placement, -and environment. It improves over time as more packets are observed. - -Tuning knobs: -- `max_hops` (default 1) -- `bearing_step` (default 10°) -- `packets_limit` (default 50 most recent packets) diff --git a/meshview/lang/en.json b/meshview/lang/en.json index a471e11..a4ed9d2 100644 --- a/meshview/lang/en.json +++ b/meshview/lang/en.json @@ -109,7 +109,9 @@ "firmware": "Firmware:", "link_copied": "Link Copied!", "legend_traceroute": "Traceroute (with arrows)", - "legend_neighbor": "Neighbor" + "legend_neighbor": "Neighbor", + "nearby_nodes_title": "Multiple nodes here", + "nearby_nodes_hint": "Select a node:" }, @@ -217,12 +219,6 @@ "copy_import_url": "Copy Import URL", "show_qr_code": "Show QR Code", "toggle_coverage": "Predicted Coverage", - "toggle_observed_coverage": "Observed Coverage", - "observed_settings": "Observed Settings", - "max_hops": "Max Hops", - "bearing_step": "Bearing Step", - "packets_limit": "Packets", - "refresh": "Refresh", "location_required": "Location required for coverage", "coverage_help": "Coverage Help", "share_contact_qr": "Share Contact QR", diff --git a/meshview/lang/es.json b/meshview/lang/es.json index ee7bb52..f444363 100644 --- a/meshview/lang/es.json +++ b/meshview/lang/es.json @@ -107,7 +107,9 @@ "firmware": "Firmware:", "link_copied": "¡Enlace copiado!", "legend_traceroute": "Ruta de traceroute (flechas de dirección)", - "legend_neighbor": "Vínculo de vecinos" + "legend_neighbor": "Vínculo de vecinos", + "nearby_nodes_title": "Varios nodos aquí", + "nearby_nodes_hint": "Selecciona un nodo:" }, "stats": { @@ -203,12 +205,6 @@ "copy_import_url": "Copiar URL de importación", "show_qr_code": "Mostrar código QR", "toggle_coverage": "Cobertura predicha", - "toggle_observed_coverage": "Cobertura observada", - "observed_settings": "Ajustes observados", - "max_hops": "Máx. saltos", - "bearing_step": "Paso de rumbo", - "packets_limit": "Paquetes", - "refresh": "Actualizar", "location_required": "Se requiere ubicación para la cobertura", "coverage_help": "Ayuda de cobertura", "share_contact_qr": "Compartir contacto QR", diff --git a/meshview/templates/map.html b/meshview/templates/map.html index 78aafbe..9e1c42e 100644 --- a/meshview/templates/map.html +++ b/meshview/templates/map.html @@ -71,6 +71,10 @@ #unmapped-list li:last-child { border-bottom: none; } .unmapped-node { font-weight: 400; color: #000; } .unmapped-empty { color: #666; font-style: italic; } + .nearby-popup { font-size: 13px; } + .nearby-popup .nearby-list { margin: 6px 0 0; padding-left: 16px; } + .nearby-popup .nearby-list li { margin: 3px 0; } + .nearby-popup .nearby-distance { color: #666; font-size: 12px; } {% endblock %} @@ -201,6 +205,58 @@ function timeAgoFromUs(us){ return d>0?d+"d":h>0?h+"h":m>0?m+"m":s+"s"; } +const NEARBY_METERS = 20; + +function escapeHtml(text){ + return String(text) + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function getNearbyNodes(latlng){ + const nearby = []; + nodes.forEach(n=>{ + const marker = markerById[n.key]; + if(!marker) return; + const dist = map.distance(latlng, marker.getLatLng()); + if(dist <= NEARBY_METERS){ + nearby.push({ node: n, marker, dist }); + } + }); + nearby.sort((a,b)=>a.dist - b.dist); + return nearby; +} + +function openNearbyPopup(latlng, nearby){ + const items = nearby.map(item => { + const label = item.node.long_name || item.node.short_name || item.node.node_id; + return ` +
  • + + ${escapeHtml(label)} + + (${Math.round(item.dist)} m) +
  • `; + }).join(""); + + const html = ` +
    +
    Multiple nodes here
    +
    Select a node:
    + +
    `; + L.popup().setLatLng(latlng).setContent(html).openOn(map); +} + +function focusNode(nodeId){ + const marker = markerById[nodeId]; + const node = nodeMap.get(nodeId); + if(!marker || !node) return; + window.location.href = `/node/${node.node_id ?? node.id}`; +} + function hashToColor(str){ if(colorMap.has(str)) return colorMap.get(str); const c = palette[nextColorIndex++ % palette.length]; @@ -404,9 +460,15 @@ function renderNodesOnMap(){ `; marker.on('click', () => { + const nearby = getNearbyNodes(marker.getLatLng()); + if(nearby.length > 1){ + openNearbyPopup(marker.getLatLng(), nearby); + return; + } onNodeClick(node); marker.bindPopup(popup).openPopup(); }); + marker.popupHtml = popup; }); setTimeout(() => applyTranslationsMap(), 50); diff --git a/meshview/templates/node.html b/meshview/templates/node.html index b71576e..6142a67 100644 --- a/meshview/templates/node.html +++ b/meshview/templates/node.html @@ -341,31 +341,10 @@ - Coverage Help -