better seismic drawings, and adsb enrichment
This commit is contained in:
@@ -170,31 +170,145 @@ export function classifyAircraft(row) {
|
||||
return 'unknown';
|
||||
}
|
||||
|
||||
async function scrapeHexDatabase(icao) {
|
||||
const url = `https://hexdatabase.com/h/${icao}`;
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) return null;
|
||||
const html = await resp.text();
|
||||
const rowMatch = html.match(new RegExp(`<tr[^>]*>.*?<span[^>]*>.*?${icao}.*?<\/span>.*?<\/tr>`, 's'));
|
||||
if (!rowMatch) return null;
|
||||
// 0: Hex (ICAO), 1: Co (Country), 2: Reg (Registration), 3: Atype (Aircraft Type), 4: Operator, 5: Source, 6: MSN (Serial number), 7: Remarks (Description)
|
||||
const cells = rowMatch.match(/<td[^>]*>.*?<\/td>/g) || [] || null;
|
||||
const country = cells[1]?.replace(/<[^>]*>/g, '').trim() || null;
|
||||
const registration = cells[2]?.replace(/<[^>]*>/g, '').trim() || null;
|
||||
const aircraft = cells[3]?.replace(/<[^>]*>/g, '').trim() || null;
|
||||
const operator = cells[4]?.replace(/<[^>]*>/g, '').trim() || null;
|
||||
const serialNumber = cells[6]?.replace(/<[^>]*>/g, '').trim() || null;
|
||||
|
||||
return {
|
||||
country,
|
||||
registration,
|
||||
aircraft,
|
||||
operator,
|
||||
serialNumber
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function scrapeHexDatabase(icao) {
|
||||
const url = `https://hexdatabase.com/h/${icao}`;
|
||||
try {
|
||||
const resp = await fetch(url);
|
||||
if (!resp.ok) return null;
|
||||
const html = await resp.text();
|
||||
const rowMatch = html.match(new RegExp(`<tr[^>]*>.*?<span[^>]*>.*?${icao}.*?<\/span>.*?<\/tr>`, 's'));
|
||||
if (!rowMatch) return null;
|
||||
const cells = rowMatch.match(/<td[^>]*>.*?<\/td>/g) || [];
|
||||
return {
|
||||
country: cells[1]?.replace(/<[^>]*>/g, '').trim() || null,
|
||||
registration: cells[2]?.replace(/<[^>]*>/g, '').trim() || null,
|
||||
aircraft: cells[3]?.replace(/<[^>]*>/g, '').trim() || null,
|
||||
operator: cells[4]?.replace(/<[^>]*>/g, '').trim() || null,
|
||||
serialNumber: cells[6]?.replace(/<[^>]*>/g, '').trim() || null,
|
||||
};
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchHexDb(icao) {
|
||||
const resp = await fetchWithTimeout(`https://hexdb.io/api/v1/aircraft/${icao}`);
|
||||
if (!resp.ok) return null;
|
||||
const found = await resp.json();
|
||||
return {
|
||||
registration: found.Registration || null,
|
||||
manufacturer: found.Manufacturer || null,
|
||||
aircraft: found.ICAOTypeCode || null,
|
||||
model: found.Type || null,
|
||||
operator: found.RegisteredOwners || null,
|
||||
};
|
||||
}
|
||||
|
||||
function backfillFromSimilar(aircraft) {
|
||||
if (!aircraft) return {};
|
||||
const similar = db.prepare(`
|
||||
SELECT manufacturer, model, engines, categoryDescription, class
|
||||
FROM aircraft
|
||||
WHERE aircraft = ?
|
||||
AND (manufacturer IS NOT NULL OR model IS NOT NULL OR engines IS NOT NULL)
|
||||
LIMIT 1
|
||||
`).get(aircraft);
|
||||
if (!similar) return {};
|
||||
return {
|
||||
manufacturer: similar.manufacturer || null,
|
||||
model: similar.model || null,
|
||||
engines: similar.engines || null,
|
||||
categoryDescription: similar.categoryDescription || null,
|
||||
class: similar.class || null,
|
||||
};
|
||||
}
|
||||
|
||||
export async function enrichAircraft(a) {
|
||||
if (!a.hex) return a;
|
||||
const row = db.prepare('SELECT * FROM aircraft WHERE icao24 = ?').get(a.hex.toUpperCase());
|
||||
if (!row) {
|
||||
const resp = await fetch(`https://hexdb.io/api/v1/aircraft/${a.hex.toUpperCase()}`);
|
||||
if (resp.ok) {
|
||||
const found = await resp.json();
|
||||
const insert = db.prepare(`
|
||||
INSERT OR IGNORE INTO aircraft (icao24, registration, manufacturer, aircraft, model, operator)
|
||||
VALUES (@icao24, @registration, @manufacturer, @aircraft, @model, @operator)
|
||||
`);
|
||||
const mapped = {
|
||||
icao24: a.hex.toUpperCase(),
|
||||
registration: found.Registration || null,
|
||||
manufacturer: found.Manufacturer || null,
|
||||
aircraft: found.ICAOTypeCode || null,
|
||||
model: found.Type || null,
|
||||
operator: found.RegisteredOwners || null,
|
||||
};
|
||||
insert.run(mapped);
|
||||
return { ...a, ...mapped, type: classifyAircraft(mapped) };
|
||||
}
|
||||
return { ...a, type: isIcaoInMilitaryRange(a.hex) ? 'military' : 'unknown' };
|
||||
const icao = a.hex.toUpperCase();
|
||||
|
||||
// 1. Check own DB first
|
||||
const row = db.prepare('SELECT * FROM aircraft WHERE icao24 = ?').get(icao);
|
||||
if (row?.manufacturer && row?.model) {
|
||||
return { ...a, ...row, type: classifyAircraft(row) };
|
||||
}
|
||||
return { ...a, ...row, type: classifyAircraft(row) };
|
||||
|
||||
// 2. Race the two external sources
|
||||
let found = await Promise.any([
|
||||
fetchHexDb(icao),
|
||||
scrapeHexDatabase(icao),
|
||||
]).catch(() => null);
|
||||
|
||||
// Merge with any partial DB row we already have
|
||||
const merged = { ...row, ...found };
|
||||
|
||||
// 3. Backfill manufacturer/model/engines from similar aircraft type in DB
|
||||
if (merged.aircraft && (!merged.manufacturer || !merged.model)) {
|
||||
const similar = backfillFromSimilar(merged.aircraft);
|
||||
Object.assign(merged, { ...similar, ...merged }); // don't overwrite fetched data
|
||||
}
|
||||
|
||||
// 4. Save back to DB
|
||||
if (found) {
|
||||
db.prepare(`
|
||||
INSERT INTO aircraft (icao24, registration, manufacturer, aircraft, model, operator, country, serialNumber, engines, categoryDescription, class)
|
||||
VALUES (@icao24, @registration, @manufacturer, @aircraft, @model, @operator, @country, @serialNumber, @engines, @categoryDescription, @class)
|
||||
ON CONFLICT(icao24) DO UPDATE SET
|
||||
registration = COALESCE(excluded.registration, registration),
|
||||
manufacturer = COALESCE(excluded.manufacturer, manufacturer),
|
||||
aircraft = COALESCE(excluded.aircraft, aircraft),
|
||||
model = COALESCE(excluded.model, model),
|
||||
operator = COALESCE(excluded.operator, operator),
|
||||
country = COALESCE(excluded.country, country),
|
||||
serialNumber = COALESCE(excluded.serialNumber, serialNumber),
|
||||
engines = COALESCE(excluded.engines, engines),
|
||||
categoryDescription = COALESCE(excluded.categoryDescription, categoryDescription),
|
||||
class = COALESCE(excluded.class, class)
|
||||
`).run({
|
||||
icao24: icao,
|
||||
registration: merged.registration || null,
|
||||
manufacturer: merged.manufacturer || null,
|
||||
aircraft: merged.aircraft || null,
|
||||
model: merged.model || null,
|
||||
operator: merged.operator || null,
|
||||
country: merged.country || null,
|
||||
serialNumber: merged.serialNumber || null,
|
||||
engines: merged.engines || null,
|
||||
categoryDescription: merged.categoryDescription || null,
|
||||
class: merged.class || null,
|
||||
});
|
||||
}
|
||||
|
||||
const result = { icao24: icao, ...merged };
|
||||
return { ...a, ...result, type: classifyAircraft(result) };
|
||||
}
|
||||
|
||||
export async function getADSB() {
|
||||
|
||||
Reference in New Issue
Block a user