ADSB enrichment

This commit is contained in:
2026-06-25 10:59:55 -04:00
parent 7c08d2ab60
commit 3887654211

View File

@@ -1,43 +1,23 @@
import {cfg} from './config.mjs'; import {cfg} from './config.mjs';
import Database from 'better-sqlite3'; import Database from 'better-sqlite3';
import {createWriteStream, existsSync, mkdirSync} from 'fs'; import {createWriteStream, existsSync, mkdirSync} from 'fs';
import {promises as fsp} from 'fs';
import {resolve, dirname} from 'path'; import {resolve, dirname} from 'path';
import {fileURLToPath} from 'url'; import {fileURLToPath} from 'url';
import {pipeline} from 'stream/promises'; import {pipeline} from 'stream/promises';
import {createGunzip} from 'zlib';
import AdmZip from 'adm-zip'; import AdmZip from 'adm-zip';
import * as fs from 'node:fs';
const DIR = dirname(fileURLToPath(import.meta.url)); const DIR = dirname(fileURLToPath(import.meta.url));
const DATA = resolve(DIR, '../data'); const DATA = resolve(DIR, '../data');
const DB_PATH = resolve(DATA, 'aircraft.db'); const DB_PATH = resolve(DATA, 'aircraft.db');
const DB_ZIP = resolve(DATA, 'aircraft_db.zip'); const DB_ZIP = resolve(DATA, 'aircraft_db.zip');
const TY_GZ = resolve(DATA, 'aircraft_types.gz');
const SHAPES_PATH = resolve(DATA, 'shapes.json'); 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; const MAX_HISTORY = 500;
let db; const DB_URL = 'https://www.mictronics.de/aircraft-database/aircraft_db.php';
function initSchema() { let db;
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
);
`);
}
async function downloadFile(url, dest) { async function downloadFile(url, dest) {
const res = await fetch(url); const res = await fetch(url);
@@ -45,49 +25,40 @@ async function downloadFile(url, dest) {
await pipeline(res.body, createWriteStream(dest)); await pipeline(res.body, createWriteStream(dest));
} }
async function downloadTypes() { function initSchema() {
await downloadFile(TY_URL, TY_GZ); db.exec(`
const chunks = []; CREATE TABLE IF NOT EXISTS aircraft (
await new Promise((res, rej) => { icao TEXT PRIMARY KEY,
const gz = fsp.open(TY_GZ).then(f => f.createReadStream()); reg TEXT,
const gunzip = createGunzip(); type_code TEXT,
gz.then(stream => { flags TEXT,
stream.pipe(gunzip); desc TEXT
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}`);
} }
async function importAircraftDb() { async function importAircraftDb() {
await downloadFile(DB_URL, DB_ZIP); await downloadFile(DB_URL, DB_ZIP);
const zip = new AdmZip(DB_ZIP); const zip = new AdmZip(DB_ZIP);
const entries = zip.getEntries(); const entries = zip.getEntries();
const insert = db.prepare(`INSERT OR REPLACE INTO aircraft(icao, reg, type_code) VALUES (?,?,?)`);
let total = 0; 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) { for(const entry of entries) {
if(!entry.entryName.endsWith('.json')) continue; if(!entry.entryName.endsWith('.json')) continue;
const rows = JSON.parse(entry.getData().toString('utf8')); const rows = JSON.parse(entry.getData().toString('utf8'));
db.transaction(() => { run(rows);
for(const [icao, a] of Object.entries(rows)) {
insert.run(icao.toUpperCase(), a.r || null, a.t || null);
total++;
} }
})();
} await fs.promises.rm(DB_ZIP);
console.log(`✈️ Aircraft upserted: ${total}`); console.log(`✈️ Aircraft imported: ${total}`);
} }
export async function initAircraftDb() { export async function initAircraftDb() {
@@ -95,22 +66,23 @@ export async function initAircraftDb() {
db = new Database(DB_PATH); db = new Database(DB_PATH);
initSchema(); initSchema();
console.log('✈️ Syncing aircraft DB...'); console.log('✈️ Downloading aircraft DB...');
await Promise.all([ await importAircraftDb();
importAircraftDb(), }
downloadTypes().then(importTypes)
]); export function decodeFlags(f) {
await Promise.all([fsp.rm(DB_ZIP), fsp.rm(TY_GZ)]); if(!f) return {};
console.log('✈️ Aircraft DB sync complete'); const n = parseInt(f, 16);
return {
military: !!(n & 0x01),
interesting: !!(n & 0x02),
pia: !!(n & 0x04),
ladd: !!(n & 0x08),
};
} }
const lookupAircraft = (icao) => const lookupAircraft = (icao) =>
db?.prepare(` db?.prepare(`SELECT icao, reg, type_code, flags, desc FROM aircraft WHERE icao = ?`).get(icao.toUpperCase());
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());
export function enrichAircraft(a) { export function enrichAircraft(a) {
if(!a.hex) return a; if(!a.hex) return a;
@@ -120,13 +92,12 @@ export function enrichAircraft(a) {
registration: row?.reg || a.r || null, registration: row?.reg || a.r || null,
type_code: row?.type_code || a.t || null, type_code: row?.type_code || a.t || null,
type_desc: row?.desc || null, type_desc: row?.desc || null,
wtc: row?.wtc || null, ...decodeFlags(row?.flags),
icon: row?.icon || null,
}; };
} }
export async function getShapes() { 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() { export async function getADSB() {