AIS, ADSB, and Range
This commit is contained in:
@@ -13,7 +13,8 @@ const SHAPES_PATH = resolve(DATA, 'shapes.json');
|
||||
const history = new Map();
|
||||
const MAX_HISTORY = 500;
|
||||
|
||||
const CSV_URL = 'https://s3.opensky-network.org/data-samples/metadata/aircraft-database-complete-2024-06.csv';
|
||||
const CSV_URL = 'https://s3.opensky-network.org/data-samples/metadata/aircraft-database-complete-2024-06.csv';
|
||||
const MIL_RANGES_URL = ':8080/db-3.14.1707/ranges.js';
|
||||
|
||||
const MILITARY_OPERATORS = ['air force', 'army', 'navy', 'marine', 'coast guard', 'military', 'defence', 'defense', 'luftwaffe', 'RAF', 'USAF', 'USN', 'USMC'];
|
||||
const CARGO_OPERATORS = ['fedex', 'ups', 'dhl', 'cargo', 'freight', 'logistic', 'atlas air', 'kalitta', 'air freight'];
|
||||
@@ -31,23 +32,37 @@ export async function initAircraftDb() {
|
||||
db = new Database(DB_PATH);
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS aircraft (
|
||||
icao24 TEXT PRIMARY KEY,
|
||||
built TEXT,
|
||||
categoryDescription TEXT,
|
||||
country TEXT,
|
||||
engines TEXT,
|
||||
class TEXT,
|
||||
manufacturer TEXT,
|
||||
model TEXT,
|
||||
modes TEXT,
|
||||
operator TEXT,
|
||||
operatorCallsign TEXT,
|
||||
owner TEXT,
|
||||
registration TEXT,
|
||||
serialNumber TEXT,
|
||||
aircraft TEXT
|
||||
)
|
||||
`);
|
||||
icao24 TEXT PRIMARY KEY,
|
||||
built TEXT,
|
||||
categoryDescription TEXT,
|
||||
country TEXT,
|
||||
engines TEXT,
|
||||
class TEXT,
|
||||
manufacturer TEXT,
|
||||
model TEXT,
|
||||
modes TEXT,
|
||||
operator TEXT,
|
||||
operatorCallsign TEXT,
|
||||
owner TEXT,
|
||||
registration TEXT,
|
||||
serialNumber TEXT,
|
||||
aircraft TEXT
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS military_ranges (
|
||||
start TEXT NOT NULL,
|
||||
end TEXT NOT NULL
|
||||
);
|
||||
`);
|
||||
|
||||
// Always refresh military ranges on startup
|
||||
const { ADSB_URL } = cfg();
|
||||
const res = await fetch(ADSB_URL + MIL_RANGES_URL);
|
||||
if (!res.ok) throw new Error(`Failed to fetch military ranges: HTTP ${res.status}`);
|
||||
const { military } = await res.json();
|
||||
db.exec('DELETE FROM military_ranges');
|
||||
const insertRange = db.prepare('INSERT INTO military_ranges (start, end) VALUES (?, ?)');
|
||||
const insertRanges = db.transaction(ranges => { for (const [s, e] of ranges) insertRange.run(s.toUpperCase(), e.toUpperCase()); });
|
||||
insertRanges(military);
|
||||
|
||||
if (missing) {
|
||||
const res = await fetch(CSV_URL);
|
||||
@@ -62,7 +77,7 @@ export async function initAircraftDb() {
|
||||
@class, @manufacturer, @model, @modes, @operator,
|
||||
@operatorCallsign, @owner, @registration, @serialNumber, @aircraft
|
||||
)
|
||||
`);
|
||||
`);
|
||||
|
||||
const insertMany = db.transaction(rows => {
|
||||
for (const row of rows) insert.run(row);
|
||||
@@ -94,6 +109,13 @@ export async function initAircraftDb() {
|
||||
}
|
||||
}
|
||||
|
||||
function isIcaoInMilitaryRange(icao) {
|
||||
if (!icao) return false;
|
||||
const hex = icao.toUpperCase();
|
||||
const match = db.prepare('SELECT 1 FROM military_ranges WHERE start <= ? AND end >= ? LIMIT 1').get(hex, hex);
|
||||
return !!match;
|
||||
}
|
||||
|
||||
function wordMatch(text, keyword) {
|
||||
if (!text) return false;
|
||||
const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
||||
@@ -110,7 +132,7 @@ export function classifyAircraft(row) {
|
||||
const ownerFields = [row.owner];
|
||||
const allFields = [...operatorFields, ...ownerFields, row.categoryDescription];
|
||||
|
||||
if (MILITARY_CLASSES.includes(row.class) || matchesAny(allFields, MILITARY_OPERATORS)) return 'military';
|
||||
if (isIcaoInMilitaryRange(row.icao24) || MILITARY_CLASSES.includes(row.class) || matchesAny(allFields, MILITARY_OPERATORS)) return 'military';
|
||||
if (row.categoryDescription?.toLowerCase().includes('cargo') || matchesAny(operatorFields, CARGO_OPERATORS)) return 'cargo';
|
||||
if (matchesAny(operatorFields, PASSENGER_OPERATORS)) return 'passenger';
|
||||
if (row.owner && !row.operator) return 'private';
|
||||
@@ -121,7 +143,7 @@ export function classifyAircraft(row) {
|
||||
export function enrichAircraft(a) {
|
||||
if (!a.hex) return a;
|
||||
const row = db.prepare('SELECT * FROM aircraft WHERE icao24 = ?').get(a.hex.toUpperCase());
|
||||
if (!row) return a;
|
||||
if (!row) return { ...a, type: isIcaoInMilitaryRange(a.hex) ? 'military' : 'unknown' };
|
||||
return { ...a, ...row, type: classifyAircraft(row) };
|
||||
}
|
||||
|
||||
@@ -148,6 +170,14 @@ export async function getADSB() {
|
||||
return aircraft.map(enrichAircraft);
|
||||
}
|
||||
|
||||
export async function getAirTrafficHistory(icao) {
|
||||
export async function getADSBHistory(icao) {
|
||||
return { history: history.get(icao?.toLowerCase()) || [] };
|
||||
}
|
||||
|
||||
export async function getADSBRange() {
|
||||
const { ADSB_URL } = cfg();
|
||||
if (!ADSB_URL) return [];
|
||||
const r = await fetch(`${ADSB_URL}:8080/data/outline.json`);
|
||||
const j = await r.json();
|
||||
return j?.actualRange?.last24h?.points || [];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user