Rttningar
This commit is contained in:
@@ -28,6 +28,21 @@ from maidenhead import latlon_to_subsquare, subsquare_bounds
|
|||||||
|
|
||||||
import re as _re
|
import re as _re
|
||||||
|
|
||||||
|
def _valid_position(lat, lon) -> bool:
|
||||||
|
"""Filter out corrupt or irrelevant positions."""
|
||||||
|
if lat is None or lon is None:
|
||||||
|
return False
|
||||||
|
if lat != lat or lon != lon: # NaN check
|
||||||
|
return False
|
||||||
|
if abs(lat) < 0.01 and abs(lon) < 0.01: # 0,0 = no GPS fix
|
||||||
|
return False
|
||||||
|
if lat < 54 or lat > 72: # outside Scandinavia
|
||||||
|
return False
|
||||||
|
if lon < 4 or lon > 32: # outside Scandinavia
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def _extract_digis(path: str) -> str:
|
def _extract_digis(path: str) -> str:
|
||||||
"""
|
"""
|
||||||
Reduce a raw APRS path to the actual physical digipeaters that handled it.
|
Reduce a raw APRS path to the actual physical digipeaters that handled it.
|
||||||
@@ -187,6 +202,10 @@ async def ingest_rf(frame: RFFrameIn) -> None:
|
|||||||
|
|
||||||
lat, lon, _ = _parse_position(tnc2)
|
lat, lon, _ = _parse_position(tnc2)
|
||||||
|
|
||||||
|
# Nullify invalid positions so they are stored as NULL rather than corrupt data
|
||||||
|
if not _valid_position(lat, lon):
|
||||||
|
lat, lon = None, None
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"RF %-6s %-9s [%s] lat=%s lon=%s",
|
"RF %-6s %-9s [%s] lat=%s lon=%s",
|
||||||
"DIRECT" if frame.heard_direct else "VIA",
|
"DIRECT" if frame.heard_direct else "VIA",
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>heardlog ??? APRS RF Coverage</title>
|
<title>heardlog – APRS RF Coverage</title>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.css">
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.min.js"></script>
|
||||||
<style>
|
<style>
|
||||||
@@ -115,10 +115,10 @@
|
|||||||
<header>
|
<header>
|
||||||
<div class="logo">heard<span>log</span> — <em>APRS RF COVERAGE</em></div>
|
<div class="logo">heard<span>log</span> — <em>APRS RF COVERAGE</em></div>
|
||||||
<div class="controls">
|
<div class="controls">
|
||||||
<div class="stat">SQUARES <strong id="sq-count" onclick="toggleSquaresPanel()">???</strong></div>
|
<div class="stat">SQUARES <strong id="sq-count" onclick="toggleSquaresPanel()">–</strong></div>
|
||||||
<div class="stat">POSITIONS <strong id="pt-count">???</strong></div>
|
<div class="stat">POSITIONS <strong id="pt-count">–</strong></div>
|
||||||
<div class="stat">RX <strong>SA6ANW-1</strong></div>
|
<div class="stat">RX <strong>SA6ANW-1</strong></div>
|
||||||
<button class="btn" onclick="refresh()">??? REFRESH</button>
|
<button class="btn" onclick="refresh()">↻ REFRESH</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<div id="map"></div>
|
<div id="map"></div>
|
||||||
@@ -167,7 +167,7 @@ async function fetchAll() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Popup HTML ??? square view + station view both in DOM, toggled with CSS
|
// Popup HTML – square view + station view both in DOM, toggled with CSS
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
function makePopupHTML(sq) {
|
function makePopupHTML(sq) {
|
||||||
const pathRows = sq.paths.map(p => {
|
const pathRows = sq.paths.map(p => {
|
||||||
@@ -242,7 +242,7 @@ async function showStation(sqId, callsign) {
|
|||||||
try {
|
try {
|
||||||
const res = await fetch(`${API}/api/coverage/station/${encodeURIComponent(callsign)}`);
|
const res = await fetch(`${API}/api/coverage/station/${encodeURIComponent(callsign)}`);
|
||||||
const d = await res.json();
|
const d = await res.json();
|
||||||
const fmt = iso => iso ? new Date(iso).toLocaleString('sv-SE',{dateStyle:'short',timeStyle:'short'}) : '???';
|
const fmt = iso => iso ? new Date(iso).toLocaleString('sv-SE',{dateStyle:'short',timeStyle:'short'}) : '–';
|
||||||
const topPaths = (d.top_paths||[]).map(p =>
|
const topPaths = (d.top_paths||[]).map(p =>
|
||||||
`<div class="detail-row"><span class="detail-label">${p.path}</span><span class="detail-value">${p.count}</span></div>`
|
`<div class="detail-row"><span class="detail-label">${p.path}</span><span class="detail-value">${p.count}</span></div>`
|
||||||
).join('');
|
).join('');
|
||||||
@@ -253,16 +253,16 @@ async function showStation(sqId, callsign) {
|
|||||||
<div class="detail-row"><span class="detail-label">Unique positions</span><span class="detail-value">${d.unique_positions}</span></div>
|
<div class="detail-row"><span class="detail-label">Unique positions</span><span class="detail-value">${d.unique_positions}</span></div>
|
||||||
<div class="detail-row"><span class="detail-label">Unique paths</span><span class="detail-value">${d.unique_paths}</span></div>
|
<div class="detail-row"><span class="detail-label">Unique paths</span><span class="detail-value">${d.unique_paths}</span></div>
|
||||||
<div class="detail-section">DISTANCE (LINE OF SIGHT)</div>
|
<div class="detail-section">DISTANCE (LINE OF SIGHT)</div>
|
||||||
<div class="detail-row"><span class="detail-label">Closest</span><span class="detail-value">${d.distance_min_km??'???'} km</span></div>
|
<div class="detail-row"><span class="detail-label">Closest</span><span class="detail-value">${d.distance_min_km??'–'} km</span></div>
|
||||||
<div class="detail-row"><span class="detail-label">Farthest</span><span class="detail-value">${d.distance_max_km??'???'} km</span></div>
|
<div class="detail-row"><span class="detail-label">Farthest</span><span class="detail-value">${d.distance_max_km??'–'} km</span></div>
|
||||||
<div class="detail-row"><span class="detail-label">Average</span><span class="detail-value">${d.distance_avg_km??'???'} km</span></div>
|
<div class="detail-row"><span class="detail-label">Average</span><span class="detail-value">${d.distance_avg_km??'–'} km</span></div>
|
||||||
<div class="detail-section">TIMESTAMPS</div>
|
<div class="detail-section">TIMESTAMPS</div>
|
||||||
<div class="detail-row"><span class="detail-label">First heard</span><span class="detail-value">${fmt(d.first_heard)}</span></div>
|
<div class="detail-row"><span class="detail-label">First heard</span><span class="detail-value">${fmt(d.first_heard)}</span></div>
|
||||||
<div class="detail-row"><span class="detail-label">Last heard</span><span class="detail-value">${fmt(d.last_heard)}</span></div>
|
<div class="detail-row"><span class="detail-label">Last heard</span><span class="detail-value">${fmt(d.last_heard)}</span></div>
|
||||||
<div class="detail-section">TOP PATHS</div>
|
<div class="detail-section">TOP PATHS</div>
|
||||||
${topPaths}
|
${topPaths}
|
||||||
<div class="detail-section">SQUARES</div>
|
<div class="detail-section">SQUARES</div>
|
||||||
<div class="squares-list">${(d.squares||[]).join(' ') || '???'}</div>`;
|
<div class="squares-list">${(d.squares||[]).join(' ') || '–'}</div>`;
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
body.innerHTML = '<div class="loading-msg">Failed to load data.</div>';
|
body.innerHTML = '<div class="loading-msg">Failed to load data.</div>';
|
||||||
}
|
}
|
||||||
@@ -509,7 +509,7 @@ function clearPathLines() {
|
|||||||
function drawPath(rawPath, stationLat, stationLon) {
|
function drawPath(rawPath, stationLat, stationLon) {
|
||||||
clearPathLines();
|
clearPathLines();
|
||||||
|
|
||||||
// Build waypoints: station ??? each digi ??? RX
|
// Build waypoints: station → each digi → RX
|
||||||
const waypoints = [{ lat: stationLat, lon: stationLon, label: 'Station' }];
|
const waypoints = [{ lat: stationLat, lon: stationLon, label: 'Station' }];
|
||||||
|
|
||||||
if (rawPath && rawPath !== 'DIRECT') {
|
if (rawPath && rawPath !== 'DIRECT') {
|
||||||
@@ -613,8 +613,8 @@ map.on('popupopen', e => {
|
|||||||
map.on('zoomend', renderLayers);
|
map.on('zoomend', renderLayers);
|
||||||
|
|
||||||
async function refresh() {
|
async function refresh() {
|
||||||
document.getElementById('sq-count').textContent = '???';
|
document.getElementById('sq-count').textContent = '…';
|
||||||
document.getElementById('pt-count').textContent = '???';
|
document.getElementById('pt-count').textContent = '…';
|
||||||
await fetchAll();
|
await fetchAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
|
source_charset utf-8;
|
||||||
|
|
||||||
location / {
|
location / {
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
|
|||||||
Reference in New Issue
Block a user