mirror of
https://github.com/bilawalsidhu/gods-eye-view.git
synced 2026-11-05 02:44:02 +00:00
createGlobe() implements the Globe API in mobile/DESIGN.md: Esri day imagery blended with NASA Black Marble night lights across the terminator, Google 3D tiles below 25 km when keyed, a single-pass grade with half-res bloom and lenses, an adaptive quality governor, live layers on the repo's /api routes and parsers, time scrubbing, picking, AR contact queries and live situation cards. Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01VVYRAqiQj9Y1V9ZfG1RcDH
127 lines
3.8 KiB
JavaScript
127 lines
3.8 KiB
JavaScript
/**
|
|
* contactsNear(): aircraft, satellites and ships around a point, for the AR
|
|
* Look mode. Reads the shared feeds directly (not layer state), so AR works
|
|
* whether or not those layers are on the globe, and nudges a stale feed to
|
|
* refresh in the background instead of blocking the caller's frame.
|
|
*/
|
|
import { twoline2satrec } from 'satellite.js';
|
|
import { satPosition } from './layers/orbit.js';
|
|
import { haversineKm, offsetLonLat } from './geo.js';
|
|
|
|
const SAT_GROUPS = ['stations', 'visual'];
|
|
|
|
export function createContactIndex(feeds, { active }) {
|
|
let satCache = { stamp: '', sats: [] };
|
|
|
|
function satrecs() {
|
|
const stamp = SAT_GROUPS.map((g) => feeds[g].updatedAt).join(':');
|
|
if (stamp === satCache.stamp) return satCache.sats;
|
|
const seen = new Set();
|
|
const sats = [];
|
|
for (const g of SAT_GROUPS) {
|
|
for (const e of feeds[g].data?.entries || []) {
|
|
const id = e.line1.slice(2, 7).trim();
|
|
if (seen.has(id)) continue;
|
|
seen.add(id);
|
|
try {
|
|
sats.push({
|
|
id,
|
|
name: e.name.trim(),
|
|
satrec: twoline2satrec(e.line1, e.line2),
|
|
});
|
|
} catch {
|
|
/* skip malformed */
|
|
}
|
|
}
|
|
}
|
|
satCache = { stamp, sats };
|
|
return sats;
|
|
}
|
|
|
|
function nudge(feed, maxAgeMs) {
|
|
if (!active()) return;
|
|
if (!feed.data || Date.now() - feed.updatedAt > maxAgeMs)
|
|
feed.get({ maxAgeMs }).catch(() => {});
|
|
}
|
|
|
|
return function contactsNear(
|
|
lon,
|
|
lat,
|
|
km = 50,
|
|
{ timeMs = Date.now() } = {},
|
|
) {
|
|
nudge(feeds.flights, 20_000);
|
|
nudge(feeds.military, 30_000);
|
|
nudge(feeds.vessels, 60_000);
|
|
for (const g of SAT_GROUPS) nudge(feeds[g], 6 * 3600_000);
|
|
|
|
const out = [];
|
|
const seen = new Set();
|
|
const now = Date.now();
|
|
const addMover = (r, kind, title) => {
|
|
if (seen.has(r.id)) return;
|
|
let [rlon, rlat] = [r.lon, r.lat];
|
|
if (r.speed && !r.onGround && r.seenAt) {
|
|
const dt = Math.min(90, Math.max(0, (now - r.seenAt) / 1000));
|
|
[rlon, rlat] = offsetLonLat(r.lon, r.lat, r.heading || 0, r.speed * dt);
|
|
}
|
|
const d = haversineKm(lat, lon, rlat, rlon);
|
|
if (d > km) return;
|
|
seen.add(r.id);
|
|
out.push({
|
|
kind,
|
|
id: r.id,
|
|
title,
|
|
lon: rlon,
|
|
lat: rlat,
|
|
altM: r.altM ?? 0,
|
|
headingDeg: r.heading ?? null,
|
|
speedMps: r.speed ?? null,
|
|
distanceKm: d,
|
|
});
|
|
};
|
|
for (const r of feeds.military.data?.records || [])
|
|
addMover(r, r.helicopter ? 'helicopter' : 'military', r.callsign);
|
|
for (const r of feeds.flights.data?.records || [])
|
|
addMover(r, r.helicopter ? 'helicopter' : 'aircraft', r.callsign);
|
|
for (const r of feeds.vessels.data?.records || [])
|
|
addMover(
|
|
{ ...r, altM: 0, seenAt: feeds.vessels.updatedAt },
|
|
'vessel',
|
|
r.name,
|
|
);
|
|
|
|
// Satellites: anything whose sub-point is within km plus the horizon
|
|
// reach of its altitude is potentially overhead.
|
|
for (const s of satrecs()) {
|
|
const p = satPosition(s.satrec, timeMs);
|
|
if (!p) continue;
|
|
const reachKm = km + Math.sqrt(2 * 6371 * (p.altM / 1000));
|
|
const d = haversineKm(lat, lon, p.lat, p.lon);
|
|
if (d > reachKm) continue;
|
|
const q = satPosition(s.satrec, timeMs + 10_000);
|
|
const heading = q
|
|
? (Math.atan2(
|
|
(q.lon - p.lon) * Math.cos((p.lat * Math.PI) / 180),
|
|
q.lat - p.lat,
|
|
) *
|
|
180) /
|
|
Math.PI +
|
|
360
|
|
: 0;
|
|
out.push({
|
|
kind: s.id === '25544' ? 'station' : 'satellite',
|
|
id: s.id,
|
|
title: s.name,
|
|
lon: p.lon,
|
|
lat: p.lat,
|
|
altM: p.altM,
|
|
headingDeg: heading % 360,
|
|
speedMps: p.speedMps,
|
|
distanceKm: d,
|
|
});
|
|
}
|
|
return out.sort((a, b) => a.distanceKm - b.distanceKm);
|
|
};
|
|
}
|