diff --git a/src/app/features/body-detail/body-view-model.spec.ts b/src/app/features/body-detail/body-view-model.spec.ts index 999d6dd..bf5315a 100644 --- a/src/app/features/body-detail/body-view-model.spec.ts +++ b/src/app/features/body-detail/body-view-model.spec.ts @@ -74,6 +74,12 @@ describe('buildBodyViewModel', () => { expect(bodyReadouts(model).measured.find((row) => row.label === 'Inclination')?.value).toBe('0.00°'); }); + it('prints the eccentricity measured for a moon whose orbit keeps an older one', () => { + const hyperion: BodyRecord = { ...luna, id: 'hyperion', orbit: orbit({ eccentricity: 0.0232 }), measuredEccentricity: 0.105 }; + const model = buildBodyViewModel('hyperion', { ...catalogues, bodies: [earth, hyperion] })!; + expect(bodyReadouts(model).measured.find((row) => row.label === 'Eccentricity')?.value).toBe('0.105'); + }); + it('says where the orbit comes from, in the card’s provenance', () => { expect(bodyReadouts(buildBodyViewModel('luna', catalogues)!).provenance).toContain('Orbit: JPL SSD satellite mean elements, epoch 2000 Jan 1.'); }); diff --git a/src/app/features/body-detail/body-view-model.ts b/src/app/features/body-detail/body-view-model.ts index 9df6c18..f7b562c 100644 --- a/src/app/features/body-detail/body-view-model.ts +++ b/src/app/features/body-detail/body-view-model.ts @@ -52,7 +52,7 @@ export function buildBodyViewModel(id: string, catalogues: BodyCatalogues): Body hostStarName: hostStar?.name ?? 'Unknown star', hostStarId: body.systemStarId, radiusKm: body.radiusKm, - orbit: body.orbit, + orbit: body.measuredEccentricity === undefined ? body.orbit : { ...body.orbit, eccentricity: body.measuredEccentricity }, appearance: appearanceForBody(body, catalogues.bodies, luminosityOf(hostStar)), hasPhotography: bodyTexturePath(body.id) !== undefined, orbitalPeriodDays: periodDays, diff --git a/src/app/shared/models/body.model.ts b/src/app/shared/models/body.model.ts index d1a53ae..aa607ea 100644 --- a/src/app/shared/models/body.model.ts +++ b/src/app/shared/models/body.model.ts @@ -63,6 +63,13 @@ export interface BodyRecord { laplacePole?: { raDeg: number; decDeg: number }; /** Where the elements come from and the span they hold over, as the card prints it. */ orbitSource: string; + /** + * The eccentricity the card prints, where it is not the orbit's own: Hyperion's row in the table + * its orbit is drawn from gives 0.0232, under a quarter of the 0.105 JPL's current table (SAT441) + * and Horizons (0.074 to 0.132 from 1980 to 2100) give. The older row still places Hyperion + * nearer where Horizons has it than the same row with 0.105 does, so the orbit keeps it. + */ + measuredEccentricity?: number; /** * For `kind: 'moon'`, the `id` of the planet it orbits — its `orbit` is expressed * relative to that planet, not heliocentrically. Undefined for planets/dwarfs. diff --git a/src/assets/data/bodies.json b/src/assets/data/bodies.json index 7ff2a89..78c0544 100644 --- a/src/assets/data/bodies.json +++ b/src/assets/data/bodies.json @@ -1512,6 +1512,7 @@ "decDeg": 83.862 }, "orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1", + "measuredEccentricity": 0.105, "parentBodyId": "saturn" }, { diff --git a/tools/etl/build.ts b/tools/etl/build.ts index e6908d6..cefeb76 100644 --- a/tools/etl/build.ts +++ b/tools/etl/build.ts @@ -145,6 +145,8 @@ function validateMerge(stars: StarRecord[]): void { * 0.9. */ const MAX_PLANET_OFFSET_DEG = 0.25; +/** Measured on this catalogue: at most 0.0151 (Phoebe and the Moon) once Hyperion prints its current 0.105. */ +const MAX_ECCENTRICITY_OFFSET = 0.03; const MAX_MOON_OFFSET_DEG = 2.5; const KM_PER_AU = 149597870.7; const DEG_TO_RAD = Math.PI / 180; @@ -259,6 +261,13 @@ function validateBodies(bodies: BodyRecord[], horizonsOrbits: Map 0, `Body ${body.id} has no radius; its page states it in a form the ETL does not read.`); diff --git a/tools/etl/fetchSolarSystem.ts b/tools/etl/fetchSolarSystem.ts index 8b0d1ef..6a635ee 100644 --- a/tools/etl/fetchSolarSystem.ts +++ b/tools/etl/fetchSolarSystem.ts @@ -33,6 +33,8 @@ interface BodySpec { radiusKm?: number; /** A measured sidereal day, in hours, where a later measurement overturns the one its source gives. */ rotationPeriodHours?: number; + /** The eccentricity for the card, where the row the orbit is drawn from gives an outdated one; see `BodyRecord.measuredEccentricity`. */ + measuredEccentricity?: number; /** A moon that does not keep one face to its planet: its page's own spin, or none, is kept. */ spinsFreely?: boolean; /** A moon heavy enough to move its planet round their barycentre visibly; see `BodyRecord.massRatio`. */ @@ -111,7 +113,8 @@ const BODY_SPECS: BodySpec[] = [ { id: 'rhea', name: 'Rhea', kind: 'moon', horizonsCommand: '605', center: '500@699', parentBodyId: 'saturn' }, { id: 'titan', name: 'Titan', kind: 'moon', horizonsCommand: '606', center: '500@699', parentBodyId: 'saturn' }, // Hyperion tumbles ("Rotational period = Chaotic") and Phoebe, captured, turns in 9.27 hours. - { id: 'hyperion', name: 'Hyperion', kind: 'moon', horizonsCommand: '607', center: '500@699', parentBodyId: 'saturn', spinsFreely: true }, + // Hyperion's eccentricity is 0.105 in JPL's current table (ssd.jpl.nasa.gov/sats/elem, SAT441). + { id: 'hyperion', name: 'Hyperion', kind: 'moon', horizonsCommand: '607', center: '500@699', parentBodyId: 'saturn', spinsFreely: true, measuredEccentricity: 0.105 }, { id: 'iapetus', name: 'Iapetus', kind: 'moon', horizonsCommand: '608', center: '500@699', parentBodyId: 'saturn' }, // Phoebe's row gives a mean motion of 0.6569114 degrees a day, a 548.02-day year, where its // Horizons page and JPL's current table (SAT441) give 550.30: the table's own note warns that @@ -232,6 +235,7 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo rates: mean.rates, ...(mean.laplacePole ? { laplacePole: mean.laplacePole } : {}), orbitSource: mean.orbitSource, + ...(spec.measuredEccentricity !== undefined ? { measuredEccentricity: spec.measuredEccentricity } : {}), ...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {}), ...(parentGm !== undefined ? { massRatio: result.gmKm3PerS2! / parentGm } : {}), ...(rotationPeriodHours !== undefined ? { rotationPeriodHours } : {}),