/** * Your sky now (DESIGN.md §12.3): what is above you this minute, on a sky * dome (zenith in the centre, horizon at the rim, north up) and as a list. * Live from globe.skyNow(); refreshes every 2 s while open. */ import { icon } from './icons.js'; import { $, esc, fmtNum, haptic } from './dom.js'; import { createSheet } from './sheet.js'; import { cardinal, utc } from './bands.js'; /** Sky dome SVG. size in px; items: [{ el, az, kind, id, label? }]. */ export function domeSvg(sky, { size = 300, labels = true, sel = null } = {}) { const c = size / 2; const R = c - (labels ? 18 : 4); const pos = (el, az) => { const r = R * (1 - Math.max(0, el) / 90); const a = ((az - 90) * Math.PI) / 180; return [c + r * Math.cos(a), c + r * Math.sin(a)]; }; const dots = []; for (const s of sky?.satellites || []) { const [x, y] = pos(s.elevationDeg, s.azimuthDeg); dots.push( ``, ); } for (const a of sky?.aircraft || []) { if (a.elevationDeg < 0) continue; const [x, y] = pos(a.elevationDeg, a.azimuthDeg); dots.push( ``, ); } const iss = sky?.iss; if (iss && Number.isFinite(iss.elevationDeg) && iss.elevationDeg > 0) { const [x, y] = pos(iss.elevationDeg, iss.azimuthDeg); dots.push( `${labels ? `ISS` : ''}`, ); } return ` ${[30, 60].map((e) => ``).join('')} ${labels ? `NESW60°30°` : ''} ${dots.join('')} `; } export function createSkySheet({ host, globe, getHome, onOpenEntity }) { const sheet = createSheet({ host, label: 'Your sky now', detents: ['medium', 'large'], modal: true, dismissible: true, closeButton: true, className: 'sky-sheet', heights: { medium: (H) => Math.round(H * 0.62), large: (H) => Math.round(H * 0.93), }, }); let timer = 0; let sky = null; sheet.on('close', () => clearInterval(timer)); function rowAc(a) { return ``; } function rowSat(s) { return ``; } async function refresh() { const home = getHome(); sky = await globe.skyNow({ lon: home.lon, lat: home.lat }); if (!sky) { sheet.body.innerHTML = '

The sky feed is not available on this engine yet.

'; return; } const ac = sky.aircraft || []; const sats = sky.satellites || []; const iss = sky.iss || {}; const np = iss.nextPass; const hm = (t) => new Date(t).toLocaleTimeString([], { hour: 'numeric', minute: '2-digit', }); $('.sky-dome', sheet.body).innerHTML = domeSvg(sky, { size: 300 }); $('.sky-count', sheet.body).innerHTML = `
${ac.length}aircraft
${sats.length}satellites
${iss.visibleNow ? 'Now' : np ? hm(np.start) : '—'}${iss.visibleNow ? 'ISS overhead' : 'next ISS pass'}
`; $('.sky-lists', sheet.body).innerHTML = ` ${np || iss.visibleNow ? `
${icon('satellite')}
${iss.visibleNow ? 'The ISS is above you right now' : `ISS passes over at ${hm(np.start)}`}${np ? `Rises ${hm(np.start)} · highest ${np.maxElevationDeg ?? '—'}° at ${hm(np.max)} · sets ${np.end ? hm(np.end) : '—'}` : `${Math.round(iss.elevationDeg)}° up, ${cardinal(iss.azimuthDeg)}`}
` : ''}
Aircraft${ac.length} within 50 km
${ac.slice(0, 12).map(rowAc).join('') || '
No aircraft overhead right now. A quiet sky.
'}
Satellites${sats.length} above the horizon
${sats.slice(0, 12).map(rowSat).join('') || '
None above the horizon.
'}

Live ADS-B and orbital elements, computed for ${esc(getHome().label || getHome().name)}${getHome().demo ? ' (demo location)' : ''} · ${esc(utc(new Date()))}

`; } sheet.body.addEventListener('click', (e) => { const r = e.target.closest('[data-ent]'); if (r) { haptic('light'); sheet.close(); onOpenEntity?.(r.dataset.ent); } }); return { async open() { const home = getHome(); sheet.head.innerHTML = `
LIVE · ${esc(utc(new Date(), { seconds: false }))}

Your sky now

Above ${esc(home.label || home.name)}${home.demo ? ' · demo location' : ''}, this minute
`; sheet.body.innerHTML = `
`; sheet.open('medium'); await refresh(); clearInterval(timer); timer = setInterval(() => sheet.isOpen && refresh(), 2000); }, close: () => sheet.close(), get isOpen() { return sheet.isOpen; }, }; }