// Military aircraft (REAL) — the free, keyless adsb.lol /v2/mil feed via // proxy/mil. This is the unfiltered military traffic OpenSky/FR24 hide (C-17 // Reach flights, tankers, the Area 51 "Janet" shuttle, etc.) — the OSINT prize. // // BillboardCollection primitive like the civilian aircraft layer, but a distinct // red so it never blends in. Live-only (no history feed for this), polled every // minute (the feed is free/unmetered; a 60s server cache coalesces callers). export default function create(ctx) { const { viewer, CONFIG, lib, ui, Cesium } = ctx; const A = CONFIG.adsbx; const bc = viewer.scene.primitives.add(new Cesium.BillboardCollection()); const glyph = lib.aircraftGlyph(); // shared triangle, tinted per-billboard let enabled = true; let isLive = true; let inFlight = false; let timer = null; let delay = A.pollMs; ui.addLayer('military', 'Military (adsb.lol)', true, (on) => { enabled = on; bc.show = on && isLive; if (on && isLive) kick(); }); ui.setStatus('military', 'loading…', 'warn'); // Transponder emergency codes → the highlight label. const EMERG = { '7500': 'HIJACK', '7600': 'RADIO FAIL', '7700': 'EMERGENCY' }; function canPoll() { return enabled && isLive && !document.hidden; } function schedule(ms) { if (timer) clearTimeout(timer); timer = setTimeout(tick, ms); } function kick() { if (!canPoll() || timer || inFlight) return; schedule(0); } async function tick() { timer = null; if (!canPoll() || inFlight) return; inFlight = true; try { await poll(); } finally { inFlight = false; } if (canPoll()) schedule(delay); } // ADS-B Exchange altitude is in FEET (and "ground" for on-ground aircraft). function altFeet(a) { return typeof a.alt_baro === 'number' ? a.alt_baro : 0; } async function poll() { let res; try { res = await fetch(A.proxyMil); } catch (err) { ui.setStatus('military', 'network error — retrying', 'warn'); return; } if (res.status === 503) { // Key not configured (e.g. prod before the key is installed) — degrade quietly. bc.removeAll(); ui.setStatus('military', 'ADS-B Exchange not configured', 'warn'); return; } if (!res.ok) { ui.setStatus('military', `HTTP ${res.status} — retrying`, 'warn'); return; } let data; try { data = await res.json(); } catch (err) { ui.setStatus('military', 'bad response — retrying', 'warn'); return; } const list = Array.isArray(data && data.ac) ? data.ac : []; bc.removeAll(); let count = 0; let emergencies = 0; for (const a of list) { if (a.lat == null || a.lon == null) continue; const sq = a.squawk; const isEmerg = !!EMERG[sq] || (a.emergency && a.emergency !== 'none'); if (isEmerg) emergencies++; bc.add({ position: Cesium.Cartesian3.fromDegrees(a.lon, a.lat, altFeet(a) * 0.3048), image: glyph, color: lib.cz(isEmerg ? CONFIG.colors.militaryEmerg : CONFIG.colors.militaryReal), rotation: lib.headingToRotation(a.track), // lib negates — track may be absent (→0) scale: isEmerg ? 1.05 : 0.82, // bolder than civilian; emergencies bigger still id: { layer: 'military', hex: a.hex, callsign: (a.flight || '').trim(), type: a.t, reg: a.r, altFt: altFeet(a), gs: a.gs, track: a.track, squawk: sq, emerg: isEmerg ? (EMERG[sq] || String(a.emergency).toUpperCase()) : null, }, }); count++; } delay = A.pollMs; const stamp = data.now ? new Date(data.now).toLocaleTimeString() : new Date().toLocaleTimeString(); const em = emergencies ? ` · ${emergencies}⚠` : ''; ui.setStatus('military', `${count} military · ${stamp}${em}`, emergencies ? 'err' : 'ok'); } function onClockTick(currentTime, live) { isLive = live; if (!isLive) { bc.show = false; return; } // live-only feed; pause when scrubbed bc.show = enabled; if (enabled) kick(); } function handlePick(picked) { if (picked?.id?.layer !== 'military') return false; const id = picked.id; const rows = [ ['Type', id.type || '—'], ['Reg', id.reg || '—'], ['Altitude', `${Math.round(id.altFt || 0)} ft`], ['Speed', `${Math.round(id.gs || 0)} kt`], ['Squawk', id.squawk || '—'], ]; if (id.emerg) rows.unshift(['⚠ Status', id.emerg]); ui.showPickOverlay('✈ MIL ' + (id.callsign || id.hex || '—'), rows); return true; } function clearPick() { ui.hidePickOverlay(); } document.addEventListener('visibilitychange', () => { if (!document.hidden) kick(); }); kick(); return { id: 'military', onClockTick, handlePick, clearPick }; }