Spaces:
Configuration error
Configuration error
| import { JulianDate } from "@cesium/engine"; | |
| import dayjs from "dayjs"; | |
| import utc from "dayjs/plugin/utc"; | |
| import type { OrbitClass } from "../../config/orbitClass"; | |
| import { satcatLabel, SATCAT_LAUNCH_SITE, SATCAT_OPS_STATUS, SATCAT_ORBIT_TYPE, SATCAT_OWNER } from "../../config/satcatCodes"; | |
| import { swathExtentsOf, type SatelliteMetadata } from "../../config/satelliteMetadata"; | |
| import type Orbit from "../Orbit"; | |
| import { recordTleLines } from "./gp"; | |
| import { derivedOrbitRows, orbitRegimeLabel } from "./orbitFacts"; | |
| dayjs.extend(utc); | |
| export type ElementsInfo = { kind: "tle"; epoch: string; lines: string } | { kind: "omm"; epoch: string; rows: [string, string][] }; | |
| /** | |
| * Label/value rows describing the satellite itself, as opposed to its orbit | |
| * elements or current position: what follows from the element set, plus whatever | |
| * static facts its record carries. | |
| * | |
| * Two provenances, in that order. The derived rows come first: orbit regime, then | |
| * the apsides and node times from ./orbitFacts.ts. They are true of every satellite, | |
| * and are what the served fields are read against. Every served row | |
| * appears only when the record actually carries the field: a satellite absent from | |
| * the satellite table shows nothing rather than the fallback values the renderer | |
| * happens to use, which would read as data about the satellite when it is really a | |
| * default. | |
| * | |
| * Takes the class rather than reading it off the metadata bag, so the caller | |
| * resolves the one cache miss (`CatalogEntry.orbitClass`) instead of this | |
| * function needing a second opinion about how to derive it. Takes the orbit as | |
| * well, because the apsides and the node times need the satrec the class does not. | |
| */ | |
| export function getSatelliteInfo(orbit: Orbit, orbitClass: OrbitClass, metadata: SatelliteMetadata): [string, string][] { | |
| const rows: [string, string][] = [["Orbit", orbitRegimeLabel(orbitClass, orbit)], ...derivedOrbitRows(orbit)]; | |
| const { coneFovDeg, operator, missionType, owner, launchDate, launchSite, opsStatus, orbitType, decayDate } = metadata; | |
| const extents = swathExtentsOf(metadata); | |
| if (extents !== undefined) { | |
| const { starboardKm, portKm } = extents; | |
| const total = starboardKm + portKm; | |
| // Spell out the sides only when they differ — otherwise the total says it all. | |
| rows.push(["Swath", starboardKm === portKm ? `${total} km` : `${total} km (${starboardKm} stbd / ${portKm} port)`]); | |
| } | |
| if (coneFovDeg !== undefined) { | |
| rows.push(["Sensor FOV", `${coneFovDeg}°`]); | |
| } | |
| if (operator !== undefined) { | |
| rows.push(["Operator", operator]); | |
| } | |
| if (missionType !== undefined) { | |
| rows.push(["Mission", missionType]); | |
| } | |
| if (owner !== undefined) { | |
| rows.push(["Owner", satcatLabel(SATCAT_OWNER, owner)]); | |
| } | |
| if (launchDate !== undefined) { | |
| // One row, not two: the site alone reads as a fact about a place rather than | |
| // about this satellite, and the pair is how anyone actually reads it. | |
| rows.push(["Launched", launchSite === undefined ? launchDate : `${launchDate} · ${satcatLabel(SATCAT_LAUNCH_SITE, launchSite)}`]); | |
| } | |
| if (opsStatus !== undefined) { | |
| rows.push(["Status", satcatLabel(SATCAT_OPS_STATUS, opsStatus)]); | |
| } | |
| // "Orbiting" is true of all but a handful of the satellites served, so stating | |
| // it would cost a row on every panel to say nothing. Docked, impacted and | |
| // landed are the cases worth a line. | |
| if (orbitType !== undefined && orbitType !== "ORB") { | |
| rows.push(["Orbit type", satcatLabel(SATCAT_ORBIT_TYPE, orbitType)]); | |
| } | |
| if (decayDate !== undefined) { | |
| rows.push(["Decayed", decayDate]); | |
| } | |
| return rows; | |
| } | |
| export function getElementsInfo(orbit: Orbit): ElementsInfo { | |
| const epoch = formatEpoch(orbit.julianDate); | |
| if (orbit.record.kind === "tle") { | |
| // TLE-sourced: the two element-set lines. | |
| const tle = orbit.tle ?? recordTleLines(orbit.record)!; | |
| return { kind: "tle", epoch, lines: tle.slice(1, 3).join("\n") }; | |
| } | |
| // OMM-sourced: a compact element table. | |
| const { omm } = orbit.record; | |
| const rows: [string, unknown][] = [ | |
| ["OBJECT_ID", omm.OBJECT_ID], | |
| ["NORAD_CAT_ID", omm.NORAD_CAT_ID], | |
| ["INCLINATION", omm.INCLINATION], | |
| ["RA_OF_ASC_NODE", omm.RA_OF_ASC_NODE], | |
| ["ECCENTRICITY", omm.ECCENTRICITY], | |
| ["ARG_OF_PERICENTER", omm.ARG_OF_PERICENTER], | |
| ["MEAN_ANOMALY", omm.MEAN_ANOMALY], | |
| ["MEAN_MOTION", omm.MEAN_MOTION], | |
| ["BSTAR", omm.BSTAR], | |
| ]; | |
| return { | |
| kind: "omm", | |
| epoch, | |
| rows: rows.filter(([, value]) => value !== undefined && value !== null).map(([label, value]) => [label, String(value)]), | |
| }; | |
| } | |
| export function formatEpoch(julianDate: number): string { | |
| const julianDayNumber = Math.floor(julianDate); | |
| const secondsOfDay = (julianDate - julianDayNumber) * 60 * 60 * 24; | |
| const epochDate = new JulianDate(julianDayNumber, secondsOfDay); | |
| return dayjs.utc(epochDate as unknown as Date).format("YYYY-MM-DD HH:mm:ss"); | |
| } | |