ADSB updates
This commit is contained in:
@@ -213,7 +213,7 @@ export class AirTrafficLayer {
|
|||||||
if (this.visible) return
|
if (this.visible) return
|
||||||
this.visible = true
|
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.layer = new VectorLayer({ source: new VectorSource(), zIndex: 100 })
|
||||||
this.map.addLayer(this.layer)
|
this.map.addLayer(this.layer)
|
||||||
@@ -226,7 +226,7 @@ export class AirTrafficLayer {
|
|||||||
await this._fetch()
|
await this._fetch()
|
||||||
this._draw()
|
this._draw()
|
||||||
this._refreshPopups()
|
this._refreshPopups()
|
||||||
}, 15_000)
|
}, 1_000)
|
||||||
}
|
}
|
||||||
|
|
||||||
hide() {
|
hide() {
|
||||||
@@ -247,7 +247,7 @@ export class AirTrafficLayer {
|
|||||||
// ── Private ───────────────────────────────────────────────────────────────
|
// ── Private ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
private async _fetch() {
|
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) => ({
|
this.data = (j.data || []).map((a: any) => ({
|
||||||
...a,
|
...a,
|
||||||
icao: a.hex,
|
icao: a.hex,
|
||||||
@@ -262,46 +262,30 @@ export class AirTrafficLayer {
|
|||||||
|
|
||||||
private _draw() {
|
private _draw() {
|
||||||
const source = this.layer.getSource()!
|
const source = this.layer.getSource()!
|
||||||
const existing: Record<string, Feature> = {}
|
source.clear()
|
||||||
source.getFeatures().forEach(f => { existing[f.get('icao')] = f })
|
|
||||||
const incoming = new Set(this.data.filter(p => p.latitude != null).map(p => p.icao))
|
|
||||||
|
|
||||||
// Remove stale
|
|
||||||
Object.keys(existing).forEach(icao => {
|
|
||||||
if (!incoming.has(icao)) { source.removeFeature(<any>existing[icao]); this._closePopup(icao) }
|
|
||||||
})
|
|
||||||
|
|
||||||
// Update / add
|
|
||||||
for (const plane of this.data) {
|
for (const plane of this.data) {
|
||||||
if (plane.latitude == null) continue
|
if (plane.latitude == null) continue
|
||||||
const coord = fromLonLat([plane.longitude, plane.latitude])
|
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({
|
image: new Icon({
|
||||||
src: buildPlaneIcon(plane, this.shapes),
|
src: buildPlaneIcon(plane, this.shapes),
|
||||||
scale: 1,
|
scale: 1,
|
||||||
rotation: (plane.heading ?? 0) * (Math.PI / 180),
|
rotation: (plane.heading ?? 0) * (Math.PI / 180),
|
||||||
anchor: [0.5, 0.5],
|
anchor: [0.5, 0.5],
|
||||||
}),
|
}),
|
||||||
})
|
}))
|
||||||
|
|
||||||
if (existing[plane.icao]) {
|
|
||||||
;(<any>((<any>existing[plane.icao]).getGeometry() as Point)).setCoordinates(coord)
|
|
||||||
(<any>existing[plane.icao]).setStyle(style)
|
|
||||||
(<any>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) {
|
private async _fetchTrace(plane: any) {
|
||||||
const icao = plane.icao
|
const icao = plane.icao
|
||||||
if (!this.historyCache[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 || []
|
this.historyCache[icao] = j.history || []
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,37 +1,46 @@
|
|||||||
import { cfg } from './config.mjs'
|
import {cfg} from './config.mjs';
|
||||||
import { readFile } from 'fs/promises'
|
import {readFile} from 'fs/promises';
|
||||||
import { resolve, dirname } from 'path'
|
import {resolve, dirname} from 'path';
|
||||||
import { fileURLToPath } from 'url'
|
import {fileURLToPath} from 'url';
|
||||||
|
|
||||||
const DIR = dirname(fileURLToPath(import.meta.url))
|
const DIR = dirname(fileURLToPath(import.meta.url));
|
||||||
const SHAPES_PATH = resolve(DIR, '../public/aircraft.json')
|
const SHAPES_PATH = resolve(DIR, '../public/aircraft.json');
|
||||||
|
|
||||||
const history = new Map() // icao -> [{lat, lon, alt, ts}]
|
const history = new Map(); // icao -> [{lat, lon, alt, ts}]
|
||||||
const MAX_HISTORY = 500
|
const MAX_HISTORY = 500;
|
||||||
|
|
||||||
export async function getShapes() {
|
export async function getShapes() {
|
||||||
return JSON.parse(await readFile(SHAPES_PATH, 'utf8'))
|
return JSON.parse(await readFile(SHAPES_PATH, 'utf8'));
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getAirTraffic() {
|
export async function getADSB() {
|
||||||
const { ADSB_URL } = cfg()
|
const {ADSB_URL} = cfg();
|
||||||
if (!ADSB_URL) return { data: [] }
|
if(!ADSB_URL) return {data: []};
|
||||||
const r = await fetch(`${ADSB_URL}/data/aircraft.json`)
|
const r = await fetch(`${ADSB_URL}/data/aircraft.json`);
|
||||||
const j = await r.json()
|
const j = await r.json();
|
||||||
const aircraft = j.aircraft || []
|
const aircraft = j.aircraft || [];
|
||||||
|
|
||||||
for(const a of aircraft) {
|
for(const a of aircraft) {
|
||||||
if (!a.hex || !a.lat || !a.lon) continue
|
if(!a.hex || !a.lat || !a.lon) continue;
|
||||||
const key = a.hex.toLowerCase()
|
const key = a.hex.toLowerCase();
|
||||||
if (!history.has(key)) history.set(key, [])
|
if(!history.has(key)) history.set(key, []);
|
||||||
const trail = history.get(key)
|
const trail = history.get(key);
|
||||||
trail.unshift({ latitude: a.lat, longitude: a.lon, altitude: a.alt_baro || 0, ts: Date.now() })
|
trail.push({latitude: a.lat, longitude: a.lon, altitude: a.alt_baro || 0, ts: Date.now()});
|
||||||
if(trail.length > MAX_HISTORY) trail.shift()
|
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) {
|
export async function getAirTrafficHistory(icao) {
|
||||||
return { history: history.get(icao?.toLowerCase()) || [] }
|
return {history: history.get(icao?.toLowerCase()) || []};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import express from 'express';
|
import express from 'express';
|
||||||
import {resolve, dirname} from 'path';
|
import {resolve, dirname} from 'path';
|
||||||
import {fileURLToPath} from 'url';
|
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 {queryCurrent, queryHourly, queryDaily, getCoords} from './influx.mjs';
|
||||||
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
|
import {getCelestialCurrent, getCelestialForecast} from './celestial.mjs';
|
||||||
import {getSpaceWeather} from './space.mjs';
|
import {getSpaceWeather} from './space.mjs';
|
||||||
import {apiReference} from '@scalar/express-api-reference';
|
import {apiReference} from '@scalar/express-api-reference';
|
||||||
import {spec} from './spec.mjs';
|
import {spec} from './spec.mjs';
|
||||||
import {existsSync} from 'fs';
|
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 {getWeatherCondition} from './openweather.mjs';
|
||||||
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
|
import {lastForecast, getForecast, forecastTTL} from './forecast.mjs';
|
||||||
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
import {dailyWeather, hourlyWeather} from './openmeteo.mjs';
|
||||||
import {Aurora} from './aurora.mjs';
|
// import {Aurora} from './aurora.mjs';
|
||||||
|
|
||||||
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
// ── Uncaught error handlers ───────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -146,7 +146,7 @@ app.get('/api/position', async (req, res) => {
|
|||||||
|
|
||||||
// ── Space ─────────────────────────────────────────────────────────────────────
|
// ── 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) => {
|
app.get('/api/space', async (req, res) => {
|
||||||
const {fields} = req.query;
|
const {fields} = req.query;
|
||||||
res.json(filterFields(await getSpaceWeather(), fields));
|
res.json(filterFields(await getSpaceWeather(), fields));
|
||||||
@@ -154,9 +154,10 @@ app.get('/api/space', async (req, res) => {
|
|||||||
|
|
||||||
// ── ADSB Proxy ────────────────────────────────────────────────────────────────
|
// ── ADSB Proxy ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
app.get('/api/air-traffic', async (req, res) => res.json(await getAirTraffic()));
|
app.get('/api/adsb', async (req, res) => res.json(await getADSB()));
|
||||||
app.get('/api/air-traffic/:icao', async (req, res) => res.json(await getAirTrafficHistory(req.params.icao)));
|
app.get('/api/adsb/: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/ais', async (req, res) => res.json(await getAIS()));
|
||||||
|
app.get('/api/vehicles', async (req, res) => res.json(await getShapes()));
|
||||||
|
|
||||||
// ── DOCS ──────────────────────────────────────────────────────────────────────
|
// ── DOCS ──────────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user