All FREE via adsb.lol. New generic pieces:
- serve.py proxy/adsb/<path>: regex-allowlisted (SSRF-safe) adsb.lol v2 proxy
with per-path 120s cache + upstream call-spacing (adsb.lol 429s on bursts;
reservation slots space call *starts* without serializing network time),
12s fail-fast timeout, stale-on-error
- js/layers/adsb-layer.js: generic {ac:[...]} billboard factory (parallel
allSettled fetch, live/scrub/visibility gating, optional client-side filter)
- js/adsb-registry.js: emergency (sqk 7700/7600/7500, default-on, alerts red),
rare-type hunter (filters the shared mil feed by ICAO type — 1 cached call,
no per-type rate-limit), shadow (LADD+PIA, purple/pink)
- js/layers/radius.js: camera-driven 'all traffic within 250nm of view'
- verified live: all four render, worst-case simultaneous-enable ok, 0 errors
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
70 lines
2.8 KiB
JavaScript
70 lines
2.8 KiB
JavaScript
// ADS-B OSINT layer specs (Wave 3.1), rendered by createAdsbLayer. All feeds are
|
|
// FREE via adsb.lol (proxy/adsb/<path>) — no key, works forever. Emergency is on
|
|
// by default (usually empty = cheap, dramatic when not); the hunters are off.
|
|
|
|
// ICAO type designator → readable name (rare-type hunter).
|
|
const TYPE_NAMES = {
|
|
U2: 'U-2 Dragon Lady (spyplane)',
|
|
GHWK: 'RQ-4 Global Hawk (drone)',
|
|
E3TF: 'E-3 Sentry (AWACS)',
|
|
E3CF: 'E-3 Sentry (AWACS)',
|
|
E6: 'E-6 Mercury (TACAMO)',
|
|
E4: 'E-4B Nightwatch ("doomsday")',
|
|
RC135: 'RC-135 Rivet Joint (recon)',
|
|
B52: 'B-52 Stratofortress',
|
|
P8: 'P-8 Poseidon (sub-hunter)',
|
|
};
|
|
const HUNT_TYPES = Object.keys(TYPE_NAMES);
|
|
|
|
const SQUAWK_MEANING = { 7500: 'HIJACK', 7600: 'RADIO FAIL', 7700: 'EMERGENCY' };
|
|
const SQUAWK_COLOR = { 7500: '#ff2d95', 7600: '#ff9f40', 7700: '#ff453a' };
|
|
|
|
const ftRow = (id) => [
|
|
['Type', id.t || '—'],
|
|
['Reg', id.reg || '—'],
|
|
['Altitude', `${Math.round(id.altFt || 0)} ft`],
|
|
['Speed', `${Math.round(id.gs || 0)} kt`],
|
|
];
|
|
|
|
export const ADSB_LAYERS = [
|
|
{
|
|
id: 'emergency', name: 'Emergency squawks', category: 'Air', defaultOn: true,
|
|
pollMs: 60000, alertOnHits: true,
|
|
endpoints: ['sqk/7700', 'sqk/7600', 'sqk/7500'],
|
|
color: (a) => SQUAWK_COLOR[a.squawk] || '#ff453a',
|
|
scale: 1.15,
|
|
pickTitle: (id) => `⚠ ${SQUAWK_MEANING[id.squawk] || 'SQUAWK'} ${id.flight || id.hex}`,
|
|
describe: (id) => [['Status', SQUAWK_MEANING[id.squawk] || id.squawk || '—'], ...ftRow(id)],
|
|
status: (n) => `${n} squawking emergency ⚠`,
|
|
emptyStatus: () => 'clear — no emergencies',
|
|
},
|
|
{
|
|
id: 'rare-types', name: 'Rare-type hunter', category: 'Air', defaultOn: false,
|
|
pollMs: 120000,
|
|
// These are all military types, so filter the shared /v2/mil feed (one cached
|
|
// call the military layer already warms) instead of 8 rate-limited /type calls.
|
|
endpoints: ['mil'],
|
|
filter: (a) => HUNT_TYPES.includes(a.t),
|
|
color: () => '#35e0ff',
|
|
scale: 0.95,
|
|
pickTitle: (id) => `✈ ${TYPE_NAMES[id.t] || id.t || 'aircraft'}`,
|
|
describe: (id) => [['Aircraft', TYPE_NAMES[id.t] || id.t || '—'], ['Callsign', id.flight || '—'],
|
|
['Reg', id.reg || '—'], ['Altitude', `${Math.round(id.altFt || 0)} ft`], ['Speed', `${Math.round(id.gs || 0)} kt`]],
|
|
status: (n) => `${n} rare-type aircraft up`,
|
|
emptyStatus: () => 'none of the tracked types airborne',
|
|
},
|
|
{
|
|
id: 'shadow', name: 'Blocked / shadow aircraft', category: 'Air', defaultOn: false,
|
|
pollMs: 120000,
|
|
endpoints: ['ladd', 'pia'],
|
|
color: (a) => (a._src === 'pia' ? '#ff7bd5' : '#b57bff'),
|
|
scale: 0.8,
|
|
pickTitle: (id) => `👤 ${id.reg || id.flight || id.hex}`,
|
|
describe: (id) => [
|
|
['Program', id.src === 'pia' ? 'PIA (privacy address)' : 'LADD (FAA limited-display)'],
|
|
...ftRow(id),
|
|
],
|
|
status: (n) => `${n} blocked/shadow aircraft`,
|
|
},
|
|
];
|