auto-sync: 2026-04-19 20:30:01
This commit is contained in:
@@ -81,6 +81,7 @@ services:
|
|||||||
environment:
|
environment:
|
||||||
<<: *common-env
|
<<: *common-env
|
||||||
SERVICE_ROLE: preprocess
|
SERVICE_ROLE: preprocess
|
||||||
|
BATCH_SIZE: "500"
|
||||||
volumes:
|
volumes:
|
||||||
- ../logs/preprocess:/var/log/fr24
|
- ../logs/preprocess:/var/log/fr24
|
||||||
- ../backup:/backup
|
- ../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) {
|
function fmt(v, unit, decimals=0) {
|
||||||
if (v == null) return '—';
|
if (v == null) return '—';
|
||||||
@@ -86,8 +87,12 @@ function fmt(v, unit, decimals=0) {
|
|||||||
function showPopup(props) {
|
function showPopup(props) {
|
||||||
document.getElementById('pop-callsign').textContent = props.callsign || props.icao24;
|
document.getElementById('pop-callsign').textContent = props.callsign || props.icao24;
|
||||||
document.getElementById('pop-icao').textContent = props.icao24;
|
document.getElementById('pop-icao').textContent = props.icao24;
|
||||||
document.getElementById('pop-alt').textContent = fmt(props.altitude_m, 'm');
|
// altitude: m → ft
|
||||||
document.getElementById('pop-spd').textContent = fmt(props.ground_speed_kt, 'kt');
|
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-hdg').textContent = fmt(props.heading_deg, '°');
|
||||||
document.getElementById('pop-vr').textContent = fmt(props.vertical_rate_fpm, 'fpm');
|
document.getElementById('pop-vr').textContent = fmt(props.vertical_rate_fpm, 'fpm');
|
||||||
document.getElementById('pop-ts').textContent = props.observed_at
|
document.getElementById('pop-ts').textContent = props.observed_at
|
||||||
@@ -99,11 +104,42 @@ function closePopup() {
|
|||||||
document.getElementById('popup-box').style.display = 'none';
|
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() {
|
async function refresh() {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/aircraft/live?minutes=60');
|
const [acRes] = await Promise.all([
|
||||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
fetch('/api/aircraft/live?minutes=180'),
|
||||||
const geojson = await res.json();
|
refreshTracks(),
|
||||||
|
]);
|
||||||
|
if (!acRes.ok) throw new Error('HTTP ' + acRes.status);
|
||||||
|
const geojson = await acRes.json();
|
||||||
const features = geojson.features || [];
|
const features = geojson.features || [];
|
||||||
const seen = new Set();
|
const seen = new Set();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user