From 19d2dd2d9d057ddfda408bc3edb66862b518cea4 Mon Sep 17 00:00:00 2001 From: ztimson Date: Thu, 25 Jun 2026 09:13:16 -0400 Subject: [PATCH] ADSB updates --- client/src/services/airtraffic.ts | 40 +++++++--------------- server/src/airtraffic.mjs | 57 ++++++++++++++++++------------- server/src/server.mjs | 15 ++++---- 3 files changed, 53 insertions(+), 59 deletions(-) diff --git a/client/src/services/airtraffic.ts b/client/src/services/airtraffic.ts index 6a389a4..f986636 100644 --- a/client/src/services/airtraffic.ts +++ b/client/src/services/airtraffic.ts @@ -213,7 +213,7 @@ export class AirTrafficLayer { if (this.visible) return this.visible = true - if (!this.shapes) this.shapes = await fetch(`${API}/air-traffic-shapes`).then(r => r.json()) + if (!this.shapes) this.shapes = await fetch(`${API}/vehicles`).then(r => r.json()) this.layer = new VectorLayer({ source: new VectorSource(), zIndex: 100 }) this.map.addLayer(this.layer) @@ -226,7 +226,7 @@ export class AirTrafficLayer { await this._fetch() this._draw() this._refreshPopups() - }, 15_000) + }, 1_000) } hide() { @@ -247,7 +247,7 @@ export class AirTrafficLayer { // ── Private ─────────────────────────────────────────────────────────────── private async _fetch() { - const j = await fetch(`${API}/air-traffic`).then(r => r.json()) + const j = await fetch(`${API}/adsb`).then(r => r.json()) this.data = (j.data || []).map((a: any) => ({ ...a, icao: a.hex, @@ -261,47 +261,31 @@ export class AirTrafficLayer { } private _draw() { - const source = this.layer.getSource()! - const existing: Record = {} - source.getFeatures().forEach(f => { existing[f.get('icao')] = f }) - const incoming = new Set(this.data.filter(p => p.latitude != null).map(p => p.icao)) + const source = this.layer.getSource()! + source.clear() - // Remove stale - Object.keys(existing).forEach(icao => { - if (!incoming.has(icao)) { source.removeFeature(existing[icao]); this._closePopup(icao) } - }) - - // Update / add for (const plane of this.data) { if (plane.latitude == null) continue const coord = fromLonLat([plane.longitude, plane.latitude]) - const style = new Style({ + const f = new Feature({ geometry: new Point(coord) }) + f.set('icao', plane.icao) + f.set('planeData', plane) + f.setStyle(new Style({ image: new Icon({ src: buildPlaneIcon(plane, this.shapes), scale: 1, rotation: (plane.heading ?? 0) * (Math.PI / 180), anchor: [0.5, 0.5], }), - }) - - if (existing[plane.icao]) { - ;(((existing[plane.icao]).getGeometry() as Point)).setCoordinates(coord) - (existing[plane.icao]).setStyle(style) - (existing[plane.icao]).set('planeData', plane) - } else { - const f = new Feature({ geometry: new Point(coord) }) - f.set('icao', plane.icao) - f.set('planeData', plane) - f.setStyle(style) - source.addFeature(f) - } + })) + source.addFeature(f) } } private async _fetchTrace(plane: any) { const icao = plane.icao if (!this.historyCache[icao]) { - const j = await fetch(`${API}/air-traffic/${icao}`).then(r => r.json()) + const j = await fetch(`${API}/adsb/${icao}`).then(r => r.json()) this.historyCache[icao] = j.history || [] } diff --git a/server/src/airtraffic.mjs b/server/src/airtraffic.mjs index 707b52c..76ddb22 100644 --- a/server/src/airtraffic.mjs +++ b/server/src/airtraffic.mjs @@ -1,37 +1,46 @@ -import { cfg } from './config.mjs' -import { readFile } from 'fs/promises' -import { resolve, dirname } from 'path' -import { fileURLToPath } from 'url' +import {cfg} from './config.mjs'; +import {readFile} from 'fs/promises'; +import {resolve, dirname} from 'path'; +import {fileURLToPath} from 'url'; -const DIR = dirname(fileURLToPath(import.meta.url)) -const SHAPES_PATH = resolve(DIR, '../public/aircraft.json') +const DIR = dirname(fileURLToPath(import.meta.url)); +const SHAPES_PATH = resolve(DIR, '../public/aircraft.json'); -const history = new Map() // icao -> [{lat, lon, alt, ts}] -const MAX_HISTORY = 500 +const history = new Map(); // icao -> [{lat, lon, alt, ts}] +const MAX_HISTORY = 500; export async function getShapes() { - return JSON.parse(await readFile(SHAPES_PATH, 'utf8')) + return JSON.parse(await readFile(SHAPES_PATH, 'utf8')); } -export async function getAirTraffic() { - const { ADSB_URL } = cfg() - if (!ADSB_URL) return { data: [] } - const r = await fetch(`${ADSB_URL}/data/aircraft.json`) - const j = await r.json() - const aircraft = j.aircraft || [] +export async function getADSB() { + const {ADSB_URL} = cfg(); + if(!ADSB_URL) return {data: []}; + const r = await fetch(`${ADSB_URL}/data/aircraft.json`); + const j = await r.json(); + const aircraft = j.aircraft || []; - for (const a of aircraft) { - if (!a.hex || !a.lat || !a.lon) continue - const key = a.hex.toLowerCase() - if (!history.has(key)) history.set(key, []) - const trail = history.get(key) - trail.unshift({ latitude: a.lat, longitude: a.lon, altitude: a.alt_baro || 0, ts: Date.now() }) - if(trail.length > MAX_HISTORY) trail.shift() + for(const a of aircraft) { + if(!a.hex || !a.lat || !a.lon) continue; + const key = a.hex.toLowerCase(); + if(!history.has(key)) history.set(key, []); + const trail = history.get(key); + trail.push({latitude: a.lat, longitude: a.lon, altitude: a.alt_baro || 0, ts: Date.now()}); + if(trail.length > MAX_HISTORY) trail.shift(); } - return { data: aircraft } + return {data: aircraft}; +} + +export async function getAIS() { + const {ADSB_URL} = cfg(); + if(!ADSB_URL) return {data: []}; + const r = await fetch(`${ADSB_URL}:9990/api/ships_array.json`); + const j = await r.json(); + console.log('todo'); + // TODO } export async function getAirTrafficHistory(icao) { - return { history: history.get(icao?.toLowerCase()) || [] } + return {history: history.get(icao?.toLowerCase()) || []}; } diff --git a/server/src/server.mjs b/server/src/server.mjs index 63764a8..e9a0960 100644 --- a/server/src/server.mjs +++ b/server/src/server.mjs @@ -1,18 +1,18 @@ import express from 'express'; import {resolve, dirname} from 'path'; import {fileURLToPath} from 'url'; -import {cfg, localDateStr, tzOffsetMinutes} from './config.mjs'; +import {cfg, localDateStr} from './config.mjs'; import {queryCurrent, queryHourly, queryDaily, getCoords} from './influx.mjs'; import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs'; import {getSpaceWeather} from './space.mjs'; import {apiReference} from '@scalar/express-api-reference'; import {spec} from './spec.mjs'; import {existsSync} from 'fs'; -import {getAirTraffic, getAirTrafficHistory, getShapes} from './airtraffic.mjs'; +import {getADSB, getAirTrafficHistory, getAIS, getShapes} from './airtraffic.mjs'; import {getWeatherCondition} from './openweather.mjs'; import {lastForecast, getForecast, forecastTTL} from './forecast.mjs'; import {dailyWeather, hourlyWeather} from './openmeteo.mjs'; -import {Aurora} from './aurora.mjs'; +// import {Aurora} from './aurora.mjs'; // ── Uncaught error handlers ─────────────────────────────────────────────────── @@ -146,7 +146,7 @@ app.get('/api/position', async (req, res) => { // ── Space ───────────────────────────────────────────────────────────────────── -app.get('/api/aurora', async (req, res) => res.json(await Aurora.get())); +// app.get('/api/aurora', async (req, res) => res.json(await Aurora.get())); app.get('/api/space', async (req, res) => { const {fields} = req.query; res.json(filterFields(await getSpaceWeather(), fields)); @@ -154,9 +154,10 @@ app.get('/api/space', async (req, res) => { // ── ADSB Proxy ──────────────────────────────────────────────────────────────── -app.get('/api/air-traffic', async (req, res) => res.json(await getAirTraffic())); -app.get('/api/air-traffic/:icao', async (req, res) => res.json(await getAirTrafficHistory(req.params.icao))); -app.get('/api/air-traffic-shapes', async (req, res) => res.json(await getShapes())); +app.get('/api/adsb', async (req, res) => res.json(await getADSB())); +app.get('/api/adsb/:icao', async (req, res) => res.json(await getAirTrafficHistory(req.params.icao))); +app.get('/api/ais', async (req, res) => res.json(await getAIS())); +app.get('/api/vehicles', async (req, res) => res.json(await getShapes())); // ── DOCS ──────────────────────────────────────────────────────────────────────