mirror of
https://github.com/smittix/intercept.git
synced 2026-04-24 06:40:00 -07:00
feat: add station location and distance tracking to radiosonde mode
- Pass observer location and gpsd status to radiosonde_auto_rx station config - Add station marker on radiosonde map with GPS live position updates - Display distance from station to each balloon in cards and popups - Update aircraft database Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -1,4 +1,4 @@
|
|||||||
{
|
{
|
||||||
"version": "2026-02-15_ae16bb62",
|
"version": "2026-02-22_17194a71",
|
||||||
"downloaded": "2026-02-20T00:29:06.228007Z"
|
"downloaded": "2026-02-27T10:41:04.872620Z"
|
||||||
}
|
}
|
||||||
@@ -12,8 +12,8 @@ import os
|
|||||||
import queue
|
import queue
|
||||||
import shutil
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import sys
|
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import sys
|
||||||
import threading
|
import threading
|
||||||
import time
|
import time
|
||||||
from typing import Any
|
from typing import Any
|
||||||
@@ -29,10 +29,16 @@ from utils.constants import (
|
|||||||
SSE_KEEPALIVE_INTERVAL,
|
SSE_KEEPALIVE_INTERVAL,
|
||||||
SSE_QUEUE_TIMEOUT,
|
SSE_QUEUE_TIMEOUT,
|
||||||
)
|
)
|
||||||
|
from utils.gps import is_gpsd_running
|
||||||
from utils.logging import get_logger
|
from utils.logging import get_logger
|
||||||
from utils.sdr import SDRFactory, SDRType
|
from utils.sdr import SDRFactory, SDRType
|
||||||
from utils.sse import sse_stream_fanout
|
from utils.sse import sse_stream_fanout
|
||||||
from utils.validation import validate_device_index, validate_gain
|
from utils.validation import (
|
||||||
|
validate_device_index,
|
||||||
|
validate_gain,
|
||||||
|
validate_latitude,
|
||||||
|
validate_longitude,
|
||||||
|
)
|
||||||
|
|
||||||
logger = get_logger('intercept.radiosonde')
|
logger = get_logger('intercept.radiosonde')
|
||||||
|
|
||||||
@@ -83,6 +89,10 @@ def generate_station_cfg(
|
|||||||
ppm: int = 0,
|
ppm: int = 0,
|
||||||
bias_t: bool = False,
|
bias_t: bool = False,
|
||||||
udp_port: int = RADIOSONDE_UDP_PORT,
|
udp_port: int = RADIOSONDE_UDP_PORT,
|
||||||
|
latitude: float = 0.0,
|
||||||
|
longitude: float = 0.0,
|
||||||
|
station_alt: float = 0.0,
|
||||||
|
gpsd_enabled: bool = False,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Generate a station.cfg for radiosonde_auto_rx and return the file path."""
|
"""Generate a station.cfg for radiosonde_auto_rx and return the file path."""
|
||||||
cfg_dir = os.path.abspath(os.path.join('data', 'radiosonde'))
|
cfg_dir = os.path.abspath(os.path.join('data', 'radiosonde'))
|
||||||
@@ -116,10 +126,10 @@ always_scan = []
|
|||||||
always_decode = []
|
always_decode = []
|
||||||
|
|
||||||
[location]
|
[location]
|
||||||
station_lat = 0.0
|
station_lat = {latitude}
|
||||||
station_lon = 0.0
|
station_lon = {longitude}
|
||||||
station_alt = 0.0
|
station_alt = {station_alt}
|
||||||
gpsd_enabled = False
|
gpsd_enabled = {str(gpsd_enabled)}
|
||||||
gpsd_host = localhost
|
gpsd_host = localhost
|
||||||
gpsd_port = 2947
|
gpsd_port = 2947
|
||||||
|
|
||||||
@@ -471,6 +481,20 @@ def start_radiosonde():
|
|||||||
bias_t = data.get('bias_t', False)
|
bias_t = data.get('bias_t', False)
|
||||||
ppm = int(data.get('ppm', 0))
|
ppm = int(data.get('ppm', 0))
|
||||||
|
|
||||||
|
# Validate optional location
|
||||||
|
latitude = 0.0
|
||||||
|
longitude = 0.0
|
||||||
|
if data.get('latitude') is not None and data.get('longitude') is not None:
|
||||||
|
try:
|
||||||
|
latitude = validate_latitude(data['latitude'])
|
||||||
|
longitude = validate_longitude(data['longitude'])
|
||||||
|
except ValueError:
|
||||||
|
latitude = 0.0
|
||||||
|
longitude = 0.0
|
||||||
|
|
||||||
|
# Check if gpsd is available for live position updates
|
||||||
|
gpsd_enabled = is_gpsd_running()
|
||||||
|
|
||||||
# Find auto_rx
|
# Find auto_rx
|
||||||
auto_rx_path = find_auto_rx()
|
auto_rx_path = find_auto_rx()
|
||||||
if not auto_rx_path:
|
if not auto_rx_path:
|
||||||
@@ -515,6 +539,9 @@ def start_radiosonde():
|
|||||||
device_index=device_int,
|
device_index=device_int,
|
||||||
ppm=ppm,
|
ppm=ppm,
|
||||||
bias_t=bias_t,
|
bias_t=bias_t,
|
||||||
|
latitude=latitude,
|
||||||
|
longitude=longitude,
|
||||||
|
gpsd_enabled=gpsd_enabled,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build command - auto_rx -c expects a file path, not a directory
|
# Build command - auto_rx -c expects a file path, not a directory
|
||||||
|
|||||||
@@ -148,6 +148,8 @@
|
|||||||
freq_min: freqMin,
|
freq_min: freqMin,
|
||||||
freq_max: freqMax,
|
freq_max: freqMax,
|
||||||
bias_t: typeof getBiasTEnabled === 'function' ? getBiasTEnabled() : false,
|
bias_t: typeof getBiasTEnabled === 'function' ? getBiasTEnabled() : false,
|
||||||
|
latitude: radiosondeStationLocation.lat,
|
||||||
|
longitude: radiosondeStationLocation.lon,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.then(r => r.json())
|
.then(r => r.json())
|
||||||
@@ -230,15 +232,23 @@
|
|||||||
let radiosondeMarkers = new Map();
|
let radiosondeMarkers = new Map();
|
||||||
let radiosondeTracks = new Map();
|
let radiosondeTracks = new Map();
|
||||||
let radiosondeTrackPoints = new Map();
|
let radiosondeTrackPoints = new Map();
|
||||||
|
let radiosondeStationLocation = { lat: 0, lon: 0 };
|
||||||
|
let radiosondeStationMarker = null;
|
||||||
|
|
||||||
function initRadiosondeMap() {
|
function initRadiosondeMap() {
|
||||||
if (radiosondeMap) return;
|
if (radiosondeMap) return;
|
||||||
const container = document.getElementById('radiosondeMapContainer');
|
const container = document.getElementById('radiosondeMapContainer');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
|
// Resolve observer location
|
||||||
|
if (window.ObserverLocation && ObserverLocation.getForModule) {
|
||||||
|
radiosondeStationLocation = ObserverLocation.getForModule('radiosonde_observerLocation');
|
||||||
|
}
|
||||||
|
const hasLocation = radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0;
|
||||||
|
|
||||||
radiosondeMap = L.map('radiosondeMapContainer', {
|
radiosondeMap = L.map('radiosondeMapContainer', {
|
||||||
center: [40, -95],
|
center: hasLocation ? [radiosondeStationLocation.lat, radiosondeStationLocation.lon] : [40, -95],
|
||||||
zoom: 4,
|
zoom: hasLocation ? 7 : 4,
|
||||||
zoomControl: true,
|
zoomControl: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -246,6 +256,50 @@
|
|||||||
attribution: '© OpenStreetMap © CARTO',
|
attribution: '© OpenStreetMap © CARTO',
|
||||||
maxZoom: 18,
|
maxZoom: 18,
|
||||||
}).addTo(radiosondeMap);
|
}).addTo(radiosondeMap);
|
||||||
|
|
||||||
|
// Add station marker if we have a location
|
||||||
|
if (hasLocation) {
|
||||||
|
radiosondeStationMarker = L.circleMarker(
|
||||||
|
[radiosondeStationLocation.lat, radiosondeStationLocation.lon], {
|
||||||
|
radius: 8,
|
||||||
|
fillColor: '#00e5ff',
|
||||||
|
color: '#00e5ff',
|
||||||
|
weight: 2,
|
||||||
|
opacity: 1,
|
||||||
|
fillOpacity: 0.5,
|
||||||
|
}).addTo(radiosondeMap);
|
||||||
|
radiosondeStationMarker.bindTooltip('Station', { permanent: false, direction: 'top' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try GPS for live position updates
|
||||||
|
fetch('/gps/position')
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.status === 'ok' && data.position && data.position.latitude != null) {
|
||||||
|
radiosondeStationLocation = { lat: data.position.latitude, lon: data.position.longitude };
|
||||||
|
const ll = [data.position.latitude, data.position.longitude];
|
||||||
|
if (radiosondeStationMarker) {
|
||||||
|
radiosondeStationMarker.setLatLng(ll);
|
||||||
|
} else {
|
||||||
|
radiosondeStationMarker = L.circleMarker(ll, {
|
||||||
|
radius: 8,
|
||||||
|
fillColor: '#00e5ff',
|
||||||
|
color: '#00e5ff',
|
||||||
|
weight: 2,
|
||||||
|
opacity: 1,
|
||||||
|
fillOpacity: 0.5,
|
||||||
|
}).addTo(radiosondeMap);
|
||||||
|
radiosondeStationMarker.bindTooltip('Station (GPS)', { permanent: false, direction: 'top' });
|
||||||
|
}
|
||||||
|
if (!radiosondeMap._gpsInitialized) {
|
||||||
|
radiosondeMap.setView(ll, 7);
|
||||||
|
radiosondeMap._gpsInitialized = true;
|
||||||
|
}
|
||||||
|
// Re-render cards with updated distances
|
||||||
|
updateRadiosondeCards();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateRadiosondeMap(balloon) {
|
function updateRadiosondeMap(balloon) {
|
||||||
@@ -284,12 +338,19 @@
|
|||||||
const tempStr = balloon.temp != null ? `${balloon.temp.toFixed(1)} °C` : '--';
|
const tempStr = balloon.temp != null ? `${balloon.temp.toFixed(1)} °C` : '--';
|
||||||
const humStr = balloon.humidity != null ? `${balloon.humidity.toFixed(0)}%` : '--';
|
const humStr = balloon.humidity != null ? `${balloon.humidity.toFixed(0)}%` : '--';
|
||||||
const velStr = balloon.vel_v != null ? `${balloon.vel_v.toFixed(1)} m/s` : '--';
|
const velStr = balloon.vel_v != null ? `${balloon.vel_v.toFixed(1)} m/s` : '--';
|
||||||
|
let distStr = '';
|
||||||
|
if ((radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0) && balloon.lat && balloon.lon) {
|
||||||
|
const distM = radiosondeMap.distance(
|
||||||
|
[radiosondeStationLocation.lat, radiosondeStationLocation.lon], latlng);
|
||||||
|
distStr = `Dist: ${(distM / 1000).toFixed(1)} km<br>`;
|
||||||
|
}
|
||||||
radiosondeMarkers.get(id).bindPopup(
|
radiosondeMarkers.get(id).bindPopup(
|
||||||
`<strong>${id}</strong><br>` +
|
`<strong>${id}</strong><br>` +
|
||||||
`Type: ${balloon.sonde_type || '--'}<br>` +
|
`Type: ${balloon.sonde_type || '--'}<br>` +
|
||||||
`Alt: ${altStr}<br>` +
|
`Alt: ${altStr}<br>` +
|
||||||
`Temp: ${tempStr} | Hum: ${humStr}<br>` +
|
`Temp: ${tempStr} | Hum: ${humStr}<br>` +
|
||||||
`Vert: ${velStr}<br>` +
|
`Vert: ${velStr}<br>` +
|
||||||
|
distStr +
|
||||||
(balloon.freq ? `Freq: ${balloon.freq.toFixed(3)} MHz` : '')
|
(balloon.freq ? `Freq: ${balloon.freq.toFixed(3)} MHz` : '')
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -322,6 +383,7 @@
|
|||||||
if (!container) return;
|
if (!container) return;
|
||||||
|
|
||||||
const sorted = Object.values(radiosondeBalloons).sort((a, b) => (b.alt || 0) - (a.alt || 0));
|
const sorted = Object.values(radiosondeBalloons).sort((a, b) => (b.alt || 0) - (a.alt || 0));
|
||||||
|
const hasStation = radiosondeStationLocation.lat !== 0 || radiosondeStationLocation.lon !== 0;
|
||||||
container.innerHTML = sorted.map(b => {
|
container.innerHTML = sorted.map(b => {
|
||||||
const alt = b.alt ? `${Math.round(b.alt).toLocaleString()} m` : '--';
|
const alt = b.alt ? `${Math.round(b.alt).toLocaleString()} m` : '--';
|
||||||
const temp = b.temp != null ? `${b.temp.toFixed(1)}°C` : '--';
|
const temp = b.temp != null ? `${b.temp.toFixed(1)}°C` : '--';
|
||||||
@@ -329,6 +391,13 @@
|
|||||||
const press = b.pressure != null ? `${b.pressure.toFixed(1)} hPa` : '--';
|
const press = b.pressure != null ? `${b.pressure.toFixed(1)} hPa` : '--';
|
||||||
const vel = b.vel_v != null ? `${b.vel_v > 0 ? '+' : ''}${b.vel_v.toFixed(1)} m/s` : '--';
|
const vel = b.vel_v != null ? `${b.vel_v > 0 ? '+' : ''}${b.vel_v.toFixed(1)} m/s` : '--';
|
||||||
const freq = b.freq ? `${b.freq.toFixed(3)} MHz` : '--';
|
const freq = b.freq ? `${b.freq.toFixed(3)} MHz` : '--';
|
||||||
|
let dist = '--';
|
||||||
|
if (hasStation && b.lat && b.lon && radiosondeMap) {
|
||||||
|
const distM = radiosondeMap.distance(
|
||||||
|
[radiosondeStationLocation.lat, radiosondeStationLocation.lon],
|
||||||
|
[b.lat, b.lon]);
|
||||||
|
dist = `${(distM / 1000).toFixed(1)} km`;
|
||||||
|
}
|
||||||
return `
|
return `
|
||||||
<div class="radiosonde-card" onclick="radiosondeMap && radiosondeMap.setView([${b.lat || 0}, ${b.lon || 0}], 10)">
|
<div class="radiosonde-card" onclick="radiosondeMap && radiosondeMap.setView([${b.lat || 0}, ${b.lon || 0}], 10)">
|
||||||
<div class="radiosonde-card-header">
|
<div class="radiosonde-card-header">
|
||||||
@@ -360,6 +429,10 @@
|
|||||||
<span class="radiosonde-stat-value">${freq}</span>
|
<span class="radiosonde-stat-value">${freq}</span>
|
||||||
<span class="radiosonde-stat-label">FREQ</span>
|
<span class="radiosonde-stat-label">FREQ</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="radiosonde-stat">
|
||||||
|
<span class="radiosonde-stat-value">${dist}</span>
|
||||||
|
<span class="radiosonde-stat-label">DIST</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|||||||
Reference in New Issue
Block a user