mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 10:53:59 +00:00
Preserve positionless aircraft identities for classification
This commit is contained in:
1 parent
1edebcb6f8
commit
22138212b9
8 files changed
+99
-4
No files matched your search
@@ -129,10 +129,17 @@ export function createMilitaryRegistry({ source, now = Date.now } = {}) {
|
||||
const signal = AbortSignal.any([owner.signal, AbortSignal.timeout(10000)]);
|
||||
_polling = true;
|
||||
try {
|
||||
const snapshot = await requestSource.getSnapshot({}, { signal });
|
||||
const ids =
|
||||
typeof requestSource.getIdentities === 'function'
|
||||
? await requestSource.getIdentities({}, { signal })
|
||||
: (await requestSource.getSnapshot({}, { signal })).records.map(
|
||||
(record) => record.id,
|
||||
);
|
||||
signal.throwIfAborted();
|
||||
if (lifetime !== owner) return;
|
||||
registerMilitaryIcaos(snapshot.records.map((record) => record.id));
|
||||
if (!Array.isArray(ids))
|
||||
throw new TypeError('Expected aircraft identities');
|
||||
registerMilitaryIcaos(ids);
|
||||
} catch {
|
||||
// Keep the existing classification through source outages.
|
||||
} finally {
|
||||
|
||||
@@ -121,3 +121,21 @@ test('disposing a registry fed directly by its layer also clears classification'
|
||||
registry.dispose();
|
||||
assert.equal(registry.isMilitaryIcao('abc123'), false);
|
||||
});
|
||||
|
||||
test('classification prefers an explicit identity capability over positioned observations', async () => {
|
||||
const registry = createMilitaryRegistry({
|
||||
source: {
|
||||
getSnapshot() {
|
||||
assert.fail(
|
||||
'position snapshots must not replace an available identity capability',
|
||||
);
|
||||
},
|
||||
async getIdentities() {
|
||||
return ['abc123'];
|
||||
},
|
||||
},
|
||||
});
|
||||
await registry.refreshMilitaryRegistryIfStale();
|
||||
assert.equal(registry.isMilitaryIcao('abc123'), true);
|
||||
registry.dispose();
|
||||
});
|
||||
@@ -165,3 +165,16 @@ export function normalizeAircraftTrack(
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
/** Known source identities are useful for classification even without positions. */
|
||||
export function readsbIdentities(payload) {
|
||||
const admitted = admitRecords(
|
||||
payload?.ac,
|
||||
(row) => {
|
||||
const id = cleanText(row?.hex).toLowerCase();
|
||||
return id ? { id } : null;
|
||||
},
|
||||
'aircraft identities',
|
||||
);
|
||||
return admitted.records.map((record) => record.id);
|
||||
}
|
||||
@@ -271,3 +271,45 @@ test('out-of-range source epochs do not reach Date or globe time constructors',
|
||||
assert.equal(snapshot.records[0].positionTimeMs, null);
|
||||
assert.equal(snapshot.records[0].contactTimeMs, null);
|
||||
});
|
||||
|
||||
test('classification identities include positionless aircraft without admitting them to rendering', async () => {
|
||||
const source = createAdsbLolSource({
|
||||
fetchImpl: async () =>
|
||||
response({
|
||||
ac: [
|
||||
{ hex: ' ABC123 ' },
|
||||
{ hex: 'abc123' },
|
||||
{ hex: 'def456', lat: 30, lon: -97 },
|
||||
],
|
||||
}),
|
||||
});
|
||||
assert.deepEqual(await source.getIdentities(), ['abc123', 'def456']);
|
||||
const snapshot = await source.getSnapshot();
|
||||
assert.deepEqual(
|
||||
snapshot.records.map((record) => record.id),
|
||||
['def456'],
|
||||
);
|
||||
assert.equal(snapshot.complete, false);
|
||||
});
|
||||
|
||||
test('identity lookup validates its response and honors body-parse cancellation', async () => {
|
||||
const malformed = createAdsbLolSource({
|
||||
fetchImpl: async () => response({ ac: [{}] }),
|
||||
});
|
||||
await assert.rejects(malformed.getIdentities(), /Malformed/);
|
||||
const abort = new AbortController();
|
||||
const source = createAdsbLolSource({
|
||||
fetchImpl: async () => ({
|
||||
ok: true,
|
||||
status: 200,
|
||||
headers: new Headers(),
|
||||
async json() {
|
||||
abort.abort();
|
||||
return { ac: [{ hex: 'abc123' }] };
|
||||
},
|
||||
}),
|
||||
});
|
||||
await assert.rejects(source.getIdentities({}, { signal: abort.signal }), {
|
||||
name: 'AbortError',
|
||||
});
|
||||
});
|
||||
@@ -5,6 +5,7 @@ export {
|
||||
normalizeAircraftTrack,
|
||||
openSkySnapshot,
|
||||
readsbSnapshot,
|
||||
readsbIdentities,
|
||||
} from './aircraft.js';
|
||||
export {
|
||||
normalizeVesselObservation,
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
normalizeAircraftTrack,
|
||||
openSkySnapshot,
|
||||
readsbSnapshot,
|
||||
readsbIdentities,
|
||||
} from './aircraft.js';
|
||||
import { normalizeVesselTrack, vesselSnapshot } from './vessels.js';
|
||||
|
||||
@@ -113,6 +114,16 @@ export function createAdsbLolSource({
|
||||
} = {}) {
|
||||
return {
|
||||
label: 'adsb.lol',
|
||||
async getIdentities(_query = {}, { signal } = {}) {
|
||||
const { response, payload } = await readResponse(
|
||||
fetchImpl,
|
||||
'/api/adsblol/mil',
|
||||
{ signal },
|
||||
'adsb.lol',
|
||||
);
|
||||
if (!response.ok) throw httpError(response, 'adsb.lol');
|
||||
return readsbIdentities(payload);
|
||||
},
|
||||
async getSnapshot(_query = {}, { signal } = {}) {
|
||||
const { response, payload } = await readResponse(
|
||||
fetchImpl,
|
||||
|
||||
Reference in new issue
Block a user