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>
108 lines
3.9 KiB
JavaScript
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 };
|
|
}
|