GODSIGH/js/layers/military.js
type-two 2a51bcad2f fix: markers bled through the globe (far-side dots didn't track rotation)
Every marker used disableDepthTestDistance: Infinity, so points/billboards on
the FAR side of the Earth drew on top of the near hemisphere instead of being
occluded — reading as 'dots stuck, not moving with the globe' when you rotate.
Changed to 50000 (50km) across all layers: the globe now occludes far-side
markers, while you can still zoom right up to one without it clipping into the
surface. Also made the billboard layers (aircraft/military/adsb/radius) explicit
for consistent behavior.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-21 10:19:21 +10:00

140 lines
4.8 KiB
JavaScript

// 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
disableDepthTestDistance: 50000, // occlude when behind the globe (far side)
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 };
}