ADSB enrichment
This commit is contained in:
@@ -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 history = new Map();
|
||||||
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 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++;
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
}
|
}
|
||||||
console.log(`✈️ Aircraft upserted: ${total}`);
|
|
||||||
|
await fs.promises.rm(DB_ZIP);
|
||||||
|
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() {
|
||||||
|
|||||||
Reference in New Issue
Block a user