diff --git a/repeater/templates/neighbors.html b/repeater/templates/neighbors.html index 4858434..47654a5 100644 --- a/repeater/templates/neighbors.html +++ b/repeater/templates/neighbors.html @@ -42,6 +42,7 @@ Public Key Contact Type Location + Distance RSSI SNR Last Seen @@ -51,7 +52,7 @@ - + No repeaters discovered yet - waiting for adverts... @@ -71,6 +72,29 @@ let configLat = null; let configLng = null; + // Haversine formula to calculate distance between two lat/lng points in kilometers + function calculateDistance(lat1, lng1, lat2, lng2) { + const R = 6371; // Earth's radius in kilometers + const dLat = (lat2 - lat1) * Math.PI / 180; + const dLng = (lng2 - lng1) * Math.PI / 180; + const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + + Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) * + Math.sin(dLng / 2) * Math.sin(dLng / 2); + const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); + return R * c; + } + + // Format distance with appropriate units + function formatDistance(distanceKm) { + if (distanceKm < 1) { + return (distanceKm * 1000).toFixed(0) + ' m'; + } else if (distanceKm < 10) { + return distanceKm.toFixed(2) + ' km'; + } else { + return distanceKm.toFixed(1) + ' km'; + } + } + // Initialize map function initMap() { if (map || !configLat || !configLng) return; // Already initialized or no coords yet @@ -259,7 +283,7 @@ if (!neighbors || Object.keys(neighbors).length === 0) { tbody.innerHTML = ` - + No repeaters discovered yet - waiting for adverts... @@ -282,6 +306,16 @@ const location = neighbor.latitude && neighbor.longitude && (neighbor.latitude !== 0.0 || neighbor.longitude !== 0.0) ? `${neighbor.latitude.toFixed(6)}, ${neighbor.longitude.toFixed(6)}` : 'N/A'; + + // Calculate distance if both local and neighbor have valid coordinates + let distance = 'N/A'; + if (configLat && configLng && + neighbor.latitude && neighbor.longitude && + (neighbor.latitude !== 0.0 || neighbor.longitude !== 0.0)) { + const distanceKm = calculateDistance(configLat, configLng, neighbor.latitude, neighbor.longitude); + distance = formatDistance(distanceKm); + } + const rssi = neighbor.rssi || 'N/A'; const snr = neighbor.snr !== undefined ? neighbor.snr.toFixed(1) + ' dB' : 'N/A'; const lastSeen = new Date(neighbor.last_seen * 1000).toLocaleString(); @@ -302,6 +336,7 @@ ${pubkeyShort} ${contactType} ${location} + ${distance} ${rssi} ${snr} ${lastSeen} @@ -654,16 +689,17 @@ .data-table td:nth-child(2)::before { content: "Public Key"; } .data-table td:nth-child(3)::before { content: "Contact Type"; } .data-table td:nth-child(4)::before { content: "Location"; } - .data-table td:nth-child(5)::before { content: "RSSI"; } - .data-table td:nth-child(6)::before { content: "SNR"; } - .data-table td:nth-child(7)::before { content: "Last Seen"; } - .data-table td:nth-child(8)::before { content: "First Seen"; } - .data-table td:nth-child(9)::before { content: "Advert Count"; } + .data-table td:nth-child(5)::before { content: "Distance"; } + .data-table td:nth-child(6)::before { content: "RSSI"; } + .data-table td:nth-child(7)::before { content: "SNR"; } + .data-table td:nth-child(8)::before { content: "Last Seen"; } + .data-table td:nth-child(9)::before { content: "First Seen"; } + .data-table td:nth-child(10)::before { content: "Advert Count"; } /* Location and timestamps wrap to next line */ .data-table td:nth-child(4), - .data-table td:nth-child(7), - .data-table td:nth-child(8) { + .data-table td:nth-child(8), + .data-table td:nth-child(9) { display: block; width: 100%; margin-right: 0; @@ -694,8 +730,8 @@ .data-table td:nth-child(1), .data-table td:nth-child(2), .data-table td:nth-child(4), - .data-table td:nth-child(7), - .data-table td:nth-child(8) { + .data-table td:nth-child(8), + .data-table td:nth-child(9) { display: block; width: 100%; margin-right: 0; @@ -731,8 +767,8 @@ .data-table td:nth-child(1), .data-table td:nth-child(2), .data-table td:nth-child(4), - .data-table td:nth-child(7), - .data-table td:nth-child(8) { + .data-table td:nth-child(8), + .data-table td:nth-child(9) { display: block; width: 100%; margin-right: 0; @@ -750,6 +786,11 @@ padding: 2px 4px; } } + + .distance { + color: #dcdcaa; + font-weight: 600; + }