GODSIGH/js/layers/radius.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

108 lines
3.9 KiB
JavaScript

// Radius focus (Wave 3.1) — all traffic within 250 nm of where you're looking,
// via adsb.lol's free lat/lon/dist query (proxy/adsb/lat/.../lon/.../dist/250).
// Camera-driven: refetches when you stop panning/zooming, plus a slow refresh
// for freshness. Most useful zoomed into a region. Live-only; off by default.
export default function create(ctx) {
const { viewer, Cesium, lib, ui } = ctx;
const bc = viewer.scene.primitives.add(new Cesium.BillboardCollection());
const glyph = lib.aircraftGlyph();
const DIST_NM = 250; // adsb.lol free radius cap
let enabled = false;
let isLive = true;
let inFlight = false;
let debounce = null;
let poller = null;
let lastKey = null;
bc.show = false;
ui.addLayer('radius', `Traffic near view (${DIST_NM}nm)`, false, (on) => {
enabled = on;
bc.show = on && isLive;
if (on && isLive) { refresh(); ensurePoller(); }
else { clearInterval(poller); poller = null; ui.setStatus('radius', on ? 'locating…' : '', on ? 'warn' : 'ok'); }
});
viewer.camera.percentageChanged = 0.25; // fire camera.changed a bit more eagerly
viewer.camera.changed.addEventListener(() => {
if (!enabled || !isLive) return;
if (debounce) clearTimeout(debounce);
debounce = setTimeout(refresh, 1800); // refetch after panning stops
});
function ensurePoller() {
if (poller) return;
poller = setInterval(() => { if (enabled && isLive && !document.hidden) refresh(); }, 60000);
}
// Center = camera nadir point, rounded (also improves proxy cache hits).
function center() {
const c = viewer.camera.positionCartographic;
return [
+Cesium.Math.toDegrees(c.latitude).toFixed(2),
+Cesium.Math.toDegrees(c.longitude).toFixed(2),
];
}
async function refresh() {
if (!enabled || !isLive || document.hidden || inFlight) return;
const [lat, lon] = center();
if (!isFinite(lat) || !isFinite(lon)) return;
const key = `${lat}/${lon}`;
lastKey = key;
inFlight = true;
try {
const res = await fetch(`proxy/adsb/lat/${lat}/lon/${lon}/dist/${DIST_NM}`);
if (key !== lastKey) return; // camera moved again mid-fetch — stale
if (!res.ok) { ui.setStatus('radius', `HTTP ${res.status}`, 'warn'); return; }
const data = await res.json();
const list = Array.isArray(data.ac) ? data.ac : [];
bc.removeAll();
let n = 0;
for (const a of list) {
if (a.lat == null || a.lon == null) continue;
const altFt = typeof a.alt_baro === 'number' ? a.alt_baro : 0;
bc.add({
position: Cesium.Cartesian3.fromDegrees(a.lon, a.lat, altFt * 0.3048),
image: glyph,
color: lib.cz('#7bff9d'),
rotation: lib.headingToRotation(a.track),
scale: 0.7,
disableDepthTestDistance: 50000, // occlude when behind the globe (far side)
id: { layer: 'radius', hex: a.hex, flight: (a.flight || '').trim(), t: a.t,
reg: a.r, altFt, gs: a.gs, track: a.track },
});
n++;
}
ui.setStatus('radius', `${n} within ${DIST_NM}nm of view`, 'ok');
} catch {
ui.setStatus('radius', 'network error', 'warn');
} finally {
inFlight = false;
}
}
function onClockTick(currentTime, live) {
isLive = live;
if (!isLive) { bc.show = false; return; }
bc.show = enabled;
if (enabled) { refresh(); ensurePoller(); }
}
function handlePick(picked) {
if (picked?.id?.layer !== 'radius') return false;
const id = picked.id;
ui.showPickOverlay('✈ ' + (id.flight || id.hex || '—'), [
['Type', id.t || '—'], ['Reg', id.reg || '—'],
['Altitude', `${Math.round(id.altFt || 0)} ft`], ['Speed', `${Math.round(id.gs || 0)} kt`],
]);
return true;
}
function clearPick() { ui.hidePickOverlay(); }
document.addEventListener('visibilitychange', () => { if (!document.hidden && enabled && isLive) refresh(); });
return { id: 'radius', onClockTick, handlePick, clearPick };
}