diff --git a/tasks/flightradar24/compose/docker-compose.yml b/tasks/flightradar24/compose/docker-compose.yml index ed50eef..bced517 100644 --- a/tasks/flightradar24/compose/docker-compose.yml +++ b/tasks/flightradar24/compose/docker-compose.yml @@ -81,6 +81,7 @@ services: environment: <<: *common-env SERVICE_ROLE: preprocess + BATCH_SIZE: "500" volumes: - ../logs/preprocess:/var/log/fr24 - ../backup:/backup diff --git a/tasks/flightradar24/frontend/static/index.html b/tasks/flightradar24/frontend/static/index.html index 461bdb7..c7f1b8a 100644 --- a/tasks/flightradar24/frontend/static/index.html +++ b/tasks/flightradar24/frontend/static/index.html @@ -76,7 +76,8 @@ function planeIcon(heading) { }); } -const markers = {}; // icao24 -> L.marker +const markers = {}; // icao24 -> L.marker +let trackLayers = []; // active track polylines function fmt(v, unit, decimals=0) { if (v == null) return '—'; @@ -86,8 +87,12 @@ function fmt(v, unit, decimals=0) { function showPopup(props) { document.getElementById('pop-callsign').textContent = props.callsign || props.icao24; document.getElementById('pop-icao').textContent = props.icao24; - document.getElementById('pop-alt').textContent = fmt(props.altitude_m, 'm'); - document.getElementById('pop-spd').textContent = fmt(props.ground_speed_kt, 'kt'); + // altitude: m → ft + const altFt = props.altitude_m != null ? Math.round(props.altitude_m * 3.281) : null; + document.getElementById('pop-alt').textContent = altFt != null ? altFt + ' ft' : '—'; + // speed: kt → km/h + const spdKmh = props.ground_speed_kt != null ? Math.round(props.ground_speed_kt * 1.852) : null; + document.getElementById('pop-spd').textContent = spdKmh != null ? spdKmh + ' km/h' : '—'; document.getElementById('pop-hdg').textContent = fmt(props.heading_deg, '°'); document.getElementById('pop-vr').textContent = fmt(props.vertical_rate_fpm, 'fpm'); document.getElementById('pop-ts').textContent = props.observed_at @@ -99,11 +104,42 @@ function closePopup() { document.getElementById('popup-box').style.display = 'none'; } +async function refreshTracks() { + try { + const res = await fetch('/api/tracks?minutes=180'); + if (!res.ok) return; + const geojson = await res.json(); + let features = geojson.features || []; + + // cap at 200 tracks — keep the most recent (already ordered by last_point_at DESC) + if (features.length > 200) features = features.slice(0, 200); + + // remove old track layers + for (const layer of trackLayers) map.removeLayer(layer); + trackLayers = []; + + for (const f of features) { + const coords = f.geometry.coordinates.map(([lon, lat]) => [lat, lon]); + const callsign = f.properties.callsign || f.properties.icao24; + const line = L.polyline(coords, { + color: '#58a6ff', + weight: 1.5, + opacity: 0.4, + }).addTo(map); + line.bindPopup(callsign); + trackLayers.push(line); + } + } catch (_) { /* silent — tracks are non-critical */ } +} + async function refresh() { try { - const res = await fetch('/api/aircraft/live?minutes=60'); - if (!res.ok) throw new Error('HTTP ' + res.status); - const geojson = await res.json(); + const [acRes] = await Promise.all([ + fetch('/api/aircraft/live?minutes=180'), + refreshTracks(), + ]); + if (!acRes.ok) throw new Error('HTTP ' + acRes.status); + const geojson = await acRes.json(); const features = geojson.features || []; const seen = new Set();