From 3887654211e414f5efc796783e869b2229001630 Mon Sep 17 00:00:00 2001 From: ztimson Date: Thu, 25 Jun 2026 10:59:55 -0400 Subject: [PATCH] ADSB enrichment --- server/src/adsb.mjs | 123 +++++++++++++++++--------------------------- 1 file changed, 47 insertions(+), 76 deletions(-) diff --git a/server/src/adsb.mjs b/server/src/adsb.mjs index 84bd6d4..8febfa3 100644 --- a/server/src/adsb.mjs +++ b/server/src/adsb.mjs @@ -1,43 +1,23 @@ import {cfg} from './config.mjs'; import Database from 'better-sqlite3'; import {createWriteStream, existsSync, mkdirSync} from 'fs'; -import {promises as fsp} from 'fs'; import {resolve, dirname} from 'path'; import {fileURLToPath} from 'url'; import {pipeline} from 'stream/promises'; -import {createGunzip} from 'zlib'; import AdmZip from 'adm-zip'; +import * as fs from 'node:fs'; -const DIR = dirname(fileURLToPath(import.meta.url)); -const DATA = resolve(DIR, '../data'); -const DB_PATH = resolve(DATA, 'aircraft.db'); -const DB_ZIP = resolve(DATA, 'aircraft_db.zip'); -const TY_GZ = resolve(DATA, 'aircraft_types.gz'); +const DIR = dirname(fileURLToPath(import.meta.url)); +const DATA = resolve(DIR, '../data'); +const DB_PATH = resolve(DATA, 'aircraft.db'); +const DB_ZIP = resolve(DATA, 'aircraft_db.zip'); const SHAPES_PATH = resolve(DATA, 'shapes.json'); - -const DB_URL = 'https://www.mictronics.de/aircraft-database/aircraft_db.php'; -const TY_URL = 'https://www.mictronics.de/aircraft-database/aircraft_types.php'; - -const history = new Map(); +const history = new Map(); const MAX_HISTORY = 500; -let db; +const DB_URL = 'https://www.mictronics.de/aircraft-database/aircraft_db.php'; -function initSchema() { - db.exec(` - CREATE TABLE IF NOT EXISTS aircraft ( - icao TEXT PRIMARY KEY, - reg TEXT, - type_code TEXT - ); - CREATE TABLE IF NOT EXISTS aircraft_types ( - type_code TEXT PRIMARY KEY, - desc TEXT, - wtc TEXT, - icon TEXT - ); - `); -} +let db; async function downloadFile(url, dest) { const res = await fetch(url); @@ -45,49 +25,40 @@ async function downloadFile(url, dest) { await pipeline(res.body, createWriteStream(dest)); } -async function downloadTypes() { - await downloadFile(TY_URL, TY_GZ); - const chunks = []; - await new Promise((res, rej) => { - const gz = fsp.open(TY_GZ).then(f => f.createReadStream()); - const gunzip = createGunzip(); - gz.then(stream => { - stream.pipe(gunzip); - gunzip.on('data', c => chunks.push(c)); - gunzip.on('end', res); - gunzip.on('error', rej); - }); - }); - return JSON.parse(Buffer.concat(chunks).toString('utf8')); -} - -async function importTypes(types) { - const insert = db.prepare(`INSERT OR REPLACE INTO aircraft_types(type_code, desc, wtc, icon) VALUES (?,?,?,?)`); - db.transaction(() => { - for(const [code, t] of Object.entries(types)) - insert.run(code, t.desc || null, t.wtc || null, t.icon || null); - })(); - console.log(`✈️ Types upserted: ${Object.keys(types).length}`); +function initSchema() { + db.exec(` + CREATE TABLE IF NOT EXISTS aircraft ( + icao TEXT PRIMARY KEY, + reg TEXT, + type_code TEXT, + flags TEXT, + desc TEXT + ); + `); } async function importAircraftDb() { await downloadFile(DB_URL, DB_ZIP); - const zip = new AdmZip(DB_ZIP); + const zip = new AdmZip(DB_ZIP); const entries = zip.getEntries(); - const insert = db.prepare(`INSERT OR REPLACE INTO aircraft(icao, reg, type_code) VALUES (?,?,?)`); let total = 0; + const insert = db.prepare(`INSERT OR REPLACE INTO aircraft(icao, reg, type_code, flags, desc) VALUES (?,?,?,?,?)`); + const run = db.transaction((rows) => { + for(const [icao, a] of Object.entries(rows)) { + insert.run(icao.toUpperCase(), a.r || null, a.t || null, a.f || null, a.desc || null); + total++; + } + }); + for(const entry of entries) { if(!entry.entryName.endsWith('.json')) continue; const rows = JSON.parse(entry.getData().toString('utf8')); - db.transaction(() => { - for(const [icao, a] of Object.entries(rows)) { - insert.run(icao.toUpperCase(), a.r || null, a.t || null); - total++; - } - })(); + run(rows); } - console.log(`✈️ Aircraft upserted: ${total}`); + + await fs.promises.rm(DB_ZIP); + console.log(`✈️ Aircraft imported: ${total}`); } export async function initAircraftDb() { @@ -95,22 +66,23 @@ export async function initAircraftDb() { db = new Database(DB_PATH); initSchema(); - console.log('✈️ Syncing aircraft DB...'); - await Promise.all([ - importAircraftDb(), - downloadTypes().then(importTypes) - ]); - await Promise.all([fsp.rm(DB_ZIP), fsp.rm(TY_GZ)]); - console.log('✈️ Aircraft DB sync complete'); + console.log('✈️ Downloading aircraft DB...'); + await importAircraftDb(); +} + +export function decodeFlags(f) { + if(!f) return {}; + const n = parseInt(f, 16); + return { + military: !!(n & 0x01), + interesting: !!(n & 0x02), + pia: !!(n & 0x04), + ladd: !!(n & 0x08), + }; } const lookupAircraft = (icao) => - db?.prepare(` - SELECT a.icao, a.reg, a.type_code, t.desc, t.wtc, t.icon - FROM aircraft a - LEFT JOIN aircraft_types t ON a.type_code = t.type_code - WHERE a.icao = ? - `).get(icao.toUpperCase()); + db?.prepare(`SELECT icao, reg, type_code, flags, desc FROM aircraft WHERE icao = ?`).get(icao.toUpperCase()); export function enrichAircraft(a) { if(!a.hex) return a; @@ -120,13 +92,12 @@ export function enrichAircraft(a) { registration: row?.reg || a.r || null, type_code: row?.type_code || a.t || null, type_desc: row?.desc || null, - wtc: row?.wtc || null, - icon: row?.icon || null, + ...decodeFlags(row?.flags), }; } export async function getShapes() { - return JSON.parse(await fsp.readFile(SHAPES_PATH, 'utf8')); + return JSON.parse(await fs.promises.readFile(SHAPES_PATH, 'utf8')); } export async function getADSB() {