auto-sync: 2026-04-19 20:30:01
This commit is contained in:
@@ -81,6 +81,7 @@ services:
|
||||
environment:
|
||||
<<: *common-env
|
||||
SERVICE_ROLE: preprocess
|
||||
BATCH_SIZE: "500"
|
||||
volumes:
|
||||
- ../logs/preprocess:/var/log/fr24
|
||||
- ../backup:/backup
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user