diff --git a/README.md b/README.md index b44be02..65ea0c8 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,14 @@ in it is measured and what is not. **System view** — selecting a star flies the camera continuously into its system rather than cutting to a new scene. The Sun gets the real solar-system bodies, moving on JPL's mean orbital elements — Standish's for the planets, JPL SSD's satellite table for the moons, the Small-Body -Database for Ceres, Eris, Haumea and Makemake — and turned by the IAU's rotational elements, Earth -by the IERS Earth Rotation Angle; a tidally locked moon's prime meridian (but the Moon's and -Phobos's) turns at its JPL mean motion and its pole's terms on its node at its JPL node rate, both -re-phased to the IAU's values on 2025-01-01, and Iapetus's pole follows its orbit normal +Database for Ceres, Eris, Haumea and Makemake — and turned by the IAU's rotational elements +(Eris, Haumea, Makemake and Nereid, which have none, at their measured days about their orbit +normals, and Hyperion, which tumbles, not at all), Earth by the IERS Earth Rotation Angle; a +tidally locked moon's prime meridian turns at its JPL mean motion, and its pole's terms that turn +within 5 per cent of a multiple of its node's rate at that multiple of its JPL node rate, both +re-phased to the IAU's values on 2025-01-01 (the Moon's and Phobos's are left as the IAU has them, +and so are the circles Ariel's, Umbriel's, Titania's and Oberon's poles go round on, at rates none +of their nodes has), and Iapetus's pole follows its orbit normal (`lockedToOrbit`), so each keeps its face to its planet from AD 1 to 3000; other stars get their confirmed exoplanets. Orbits are drawn as ellipses and bodies are propagated along them by a Kepler solver to the date on the map's clock. Under them, a dashed grid marks out @@ -333,7 +337,7 @@ plugin's own files are kept so it can be listed from a marketplace of its own la ## Data credits Star catalogue: [HYG database](https://github.com/astronexus/HYG-Database) (Hipparcos, Yale -Bright Star, Gliese) — 68 388 stars within 250 pc. Solar-system orbits: JPL approximate planetary mean elements (Standish), JPL SSD satellite mean elements and the JPL Small-Body Database; rotation: the IAU WGCCRE 2015 report via NAIF's pck00011, with a locked moon's W and node terms re-rated to its JPL mean elements and Iapetus's pole carried round its orbit normal, and for Earth the IERS Conventions 2010; physical data, and the positions the orbits are checked against: NASA/JPL Horizons. Exoplanets: NASA Exoplanet +Bright Star, Gliese) — 68 388 stars within 250 pc. Solar-system orbits: JPL approximate planetary mean elements (Standish), JPL SSD satellite mean elements and the JPL Small-Body Database; rotation: the IAU WGCCRE 2015 report via NAIF's pck00011, with a locked moon's W and its pole's terms within 5 per cent of its node's rate re-rated to its JPL mean elements (but the Moon's and Phobos's) and Iapetus's pole carried round its orbit normal, and for Earth the IERS Conventions 2010; physical data, and the positions the orbits are checked against: NASA/JPL Horizons. Exoplanets: NASA Exoplanet Archive. Deep-sky objects: [OpenNGC](https://github.com/mattiaverga/OpenNGC). Body and skybox imagery: NASA/JPL/USGS public domain and Solar System Scope (CC BY 4.0) — per-file provenance is recorded in `src/assets/textures/README.md`. diff --git a/src/app/features/galaxy-system/system-orbits-renderer.spec.ts b/src/app/features/galaxy-system/system-orbits-renderer.spec.ts index 08857db..b3186ea 100644 --- a/src/app/features/galaxy-system/system-orbits-renderer.spec.ts +++ b/src/app/features/galaxy-system/system-orbits-renderer.spec.ts @@ -920,7 +920,22 @@ describe('locked moons across the clock’s window', () => { // As shipped, pole, W and all: the IAU gives each a W fitted near the present, and its rate is // not quite its orbit's, nor Iapetus's pole a line for twenty centuries. const shipped: BodyRecord[] = JSON.parse(readFileSync(`${process.cwd()}/src/assets/data/bodies.json`, 'utf8')); - const renderer = new SystemOrbitsRenderer(shipped.filter((body) => ['saturn', 'uranus', 'neptune', 'mimas', 'iapetus', 'miranda', 'proteus'].includes(body.id)), []); + const renderer = new SystemOrbitsRenderer( + shipped.filter((body) => ['jupiter', 'saturn', 'uranus', 'neptune', 'europa', 'ganymede', 'callisto', 'mimas', 'rhea', 'iapetus', 'miranda', 'triton', 'proteus'].includes(body.id)), + [] + ); + + /** Degrees between two lines, the way a spin axis and an orbit normal are compared: Miranda turns backwards against the IAU's pole. */ + function linesApartDeg(a: THREE.Vector3, b: THREE.Vector3): number { + return (Math.acos(Math.min(1, Math.abs(a.clone().normalize().dot(b.clone().normalize())))) * 180) / Math.PI; + } + + /** A moon's drawn spin axis and the normal of its drawn orbit line, in the scene's ICRF frame. */ + function axisAndOrbitNormal(id: string): { axis: THREE.Vector3; normal: THREE.Vector3 } { + const moon = renderer.members.find((member) => member.id === id)!.marker; + const line = moon.parent!.children.find((child) => child.name === 'orbit-line')!; + return { axis: new THREE.Vector3(0, 1, 0).applyQuaternion(moon.quaternion), normal: new THREE.Vector3(0, 0, 1).applyQuaternion(line.quaternion) }; + } /** East longitude, on its map, of the point on a moon's drawn sphere that faces its planet. */ function facingPlanet(id: string): number { @@ -944,20 +959,49 @@ describe('locked moons across the clock’s window', () => { } }); - it('keeps the axes of Miranda, Mimas and Iapetus on their drawn orbits’ normals, as a Cassini state holds them, at AD 1, today and AD 3000', () => { - // Measured over AD 1-3000: Miranda 0.42 degrees at most, Mimas 0.47, Iapetus 0.74. With their - // poles going round at the IAU's node rates, Miranda was 7.6 off at AD 1 and Mimas 2.6; with - // Iapetus's pole on its Laplace pole, 8.3 off at every date. + it('keeps the axes of Mimas and Iapetus on their drawn orbits’ normals, as a Cassini state holds them, at AD 1, today and AD 3000', () => { + // Measured over AD 1-3000: Mimas 0.44 degrees at most, Iapetus 0.74. With Iapetus's pole on + // its Laplace pole, 8.3 off at every date. for (const jd of [1721425.5, 2460676.5, 2816787.4]) { renderer.update(jd); - for (const id of ['miranda', 'mimas', 'iapetus']) { - const moon = renderer.members.find((member) => member.id === id)!.marker; - const line = moon.parent!.children.find((child) => child.name === 'orbit-line')!; - const axis = new THREE.Vector3(0, 1, 0).applyQuaternion(moon.quaternion); - const normal = new THREE.Vector3(0, 0, 1).applyQuaternion(line.quaternion); - // A line, not a direction: Miranda turns backwards against the IAU's pole. - expect((Math.acos(Math.min(1, Math.abs(axis.dot(normal)))) * 180) / Math.PI).toBeLessThan(1); + for (const id of ['mimas', 'iapetus']) { + const { axis, normal } = axisAndOrbitNormal(id); + expect(linesApartDeg(axis, normal)).toBeLessThan(1); } } }); + + it('turns the poles of Europa, Ganymede, Callisto, Rhea, Miranda and Triton round with their drawn nodes, at AD 1, today and AD 3000', () => { + // Each pole goes round on a term of its node's angle, re-rated to the node's drawn rate (see + // `lockedToOrbit`), each node at JPL's current rate. Measured at these dates: at most 0.23 + // degrees (Miranda). On the IAU's rates Rhea is 0.73, Miranda 0.51 and Triton 0.42, and on the + // archived table's node periods Callisto 0.48 and Miranda 0.42. + for (const jd of [1721425.5, 2460676.5, 2816787.4]) { + renderer.update(jd); + for (const id of ['europa', 'ganymede', 'callisto', 'rhea', 'miranda', 'triton']) { + const { axis, normal } = axisAndOrbitNormal(id); + expect(linesApartDeg(axis, normal), id).toBeLessThan(0.25); + } + } + }); + + it('draws Miranda’s orbit, and turns its axis, where Horizons has its orbit in 1601 and 2390', () => { + // Horizons' osculating orbit normal (ura184, ICRF), averaged over three of Miranda's orbits about + // each date; it wobbles 0.01 degrees about that. The drawn node turns at JPL's current 17.787-year + // period (see `nodePeriodYears` in the ETL); on the archived table's 17.727, which the IAU's pole + // was once turned after too, the drawn orbit was 2.1 degrees from Horizons' at both dates and the + // axis 2.4 at 1601. A date this far back is TDB less some two minutes; the node moves 0.004 degrees in that. + const HORIZONS_NORMALS: Array<[jd: number, raDeg: number, decDeg: number]> = [ + [2305813.5, 72.83137, 16.17526], + [2594102.5, 81.27691, 17.43782] + ]; + for (const [jd, raDeg, decDeg] of HORIZONS_NORMALS) { + renderer.update(jd); + const [ra, dec] = [(raDeg * Math.PI) / 180, (decDeg * Math.PI) / 180]; + const horizons = new THREE.Vector3(Math.cos(dec) * Math.cos(ra), Math.cos(dec) * Math.sin(ra), Math.sin(dec)); + const { axis, normal } = axisAndOrbitNormal('miranda'); + expect(linesApartDeg(normal, horizons)).toBeLessThan(0.5); + expect(linesApartDeg(axis, horizons)).toBeLessThan(0.5); + } + }); }); diff --git a/src/app/shared/models/body.model.ts b/src/app/shared/models/body.model.ts index 50611a6..ea06f3c 100644 --- a/src/app/shared/models/body.model.ts +++ b/src/app/shared/models/body.model.ts @@ -105,11 +105,13 @@ export interface BodyRecord { /** * Where the body's pole points and which way its prime meridian faces at any date, from the IAU * WGCCRE 2015 report (Archinal et al. 2018) as NAIF's `pck00011.tpc` carries it, but that a locked - * moon's W, and its pole's terms on its node, turn at its drawn orbit's rates and Iapetus's pole - * goes round with its orbit's, so they keep their faces to their planets, and their poles round - * their orbits', over the clock's AD 1 to 3000 (see `lockedToOrbit` in the - * ETL). Where present it alone sets how the body is drawn, and the ETL checks the period and - * obliquity above against it. Absent where the report gives none: Hyperion tumbles, and Nereid, + * moon's W, and its pole's terms that turn within 5 per cent of a multiple of its node's rate, turn + * at its drawn orbit's rates and Iapetus's pole goes round with its orbit's, so they keep their + * faces to their planets, and their poles round their orbits', over the clock's AD 1 to 3000 (see + * `lockedToOrbit` in the ETL). The Moon's and Phobos's, whose W has a quadratic, are the IAU's + * whole, and so are the terms Ariel's, Umbriel's, Titania's and Oberon's poles go round on, which + * turn at none of their nodes' multiples. Where present it alone sets how the body is drawn, and + * the ETL checks the period and obliquity above against it. Absent where the report gives none: Hyperion tumbles, and Nereid, * Eris, Haumea and Makemake have no model. */ rotationalElements?: RotationalElements; diff --git a/src/assets/data/bodies.json b/src/assets/data/bodies.json index b6a7305..7148700 100644 --- a/src/assets/data/bodies.json +++ b/src/assets/data/bodies.json @@ -1025,8 +1025,8 @@ }, "rates": { "meanMotionDegPerDay": 50.3176072, - "longitudeOfAscendingNodeDegPerDay": -0.007430053246547835, - "argumentOfPeriapsisDegPerDay": 0.015509705634511267 + "longitudeOfAscendingNodeDegPerDay": -0.00715196269822335, + "argumentOfPeriapsisDegPerDay": 0.015231615086186782 }, "laplacePole": { "raDeg": 268.168, @@ -1063,8 +1063,8 @@ }, { "angleDeg": [ - 117.57926275587673, - 271.38269483015966 + 120.11864659790176, + 261.22543755260784 ], "ra": 0.431, "dec": 0.186, @@ -1099,14 +1099,14 @@ }, "rates": { "meanMotionDegPerDay": 21.5710728, - "longitudeOfAscendingNodeDegPerDay": -0.002908996763377476, - "argumentOfPeriapsisDegPerDay": 0.004790407209562851 + "longitudeOfAscendingNodeDegPerDay": -0.0017074099257316522, + "argumentOfPeriapsisDegPerDay": 0.003588820371917027 }, "laplacePole": { "raDeg": 268.639, "decDeg": 64.749 }, - "orbitSource": "JPL SSD satellite mean elements, epoch 1997 Jan 16, within 0.2 degrees of Horizons from 1950 to 2100", + "orbitSource": "JPL SSD satellite mean elements, epoch 1997 Jan 16, within 0.1 degrees of Horizons from 1950 to 2100", "parentBodyId": "jupiter", "rotationPeriodHours": 400.5364072574082, "rotationalElements": { @@ -1137,8 +1137,8 @@ }, { "angleDeg": [ - 229.8, - 64.3 + 230.28422637269546, + 62.363147537348596 ], "ra": 0.59, "dec": 0.254, @@ -1173,8 +1173,8 @@ }, "rates": { "meanMotionDegPerDay": 381.9944948, - "longitudeOfAscendingNodeDegPerDay": -0.9996209770462032, - "argumentOfPeriapsisDegPerDay": 1.9992419540924065, + "longitudeOfAscendingNodeDegPerDay": -0.9994661190965093, + "argumentOfPeriapsisDegPerDay": 1.9990870961427125, "meanAnomalyTerms": { "b": 0, "c": 30.901081394463684, @@ -1208,8 +1208,8 @@ "terms": [ { "angleDeg": [ - 178.81408536763, - -36511.15618661257 + 177.4, + -36505.5 ], "ra": 13.56, "dec": -1.53, @@ -1461,8 +1461,8 @@ }, "rates": { "meanMotionDegPerDay": 22.5769756, - "longitudeOfAscendingNodeDegPerDay": -0.001398845136769169, - "argumentOfPeriapsisDegPerDay": 0.002799120423059061 + "longitudeOfAscendingNodeDegPerDay": -0.0014339093695790572, + "argumentOfPeriapsisDegPerDay": 0.002834184655868949 }, "laplacePole": { "raDeg": 36.214, @@ -1671,14 +1671,14 @@ }, "rates": { "meanMotionDegPerDay": 254.6906576, - "longitudeOfAscendingNodeDegPerDay": -0.05560028675847895, - "argumentOfPeriapsisDegPerDay": 0.11058300049002093 + "longitudeOfAscendingNodeDegPerDay": -0.05541273308413766, + "argumentOfPeriapsisDegPerDay": 0.11039544681567964 }, "laplacePole": { "raDeg": 77.311, "decDeg": 15.175 }, - "orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1, within 1.8 degrees of Horizons from 1950 to 2100", + "orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1, within 1.7 degrees of Horizons from 1950 to 2100", "parentBodyId": "uranus", "rotationPeriodHours": 33.9235057988244, "rotationalElements": { @@ -1700,8 +1700,8 @@ "terms": [ { "angleDeg": [ - 103.87516350424971, - -2030.8004738534437 + 102.16251712700222, + -2023.950075898128 ], "ra": 4.41, "dec": 4.25, @@ -1718,8 +1718,8 @@ }, { "angleDeg": [ - 207.75032700849943, - -4061.6009477068874 + 204.32503425400444, + -4047.900151796256 ], "ra": -0.04, "dec": -0.02, diff --git a/tools/etl/build.ts b/tools/etl/build.ts index e6f20ba..15740a2 100644 --- a/tools/etl/build.ts +++ b/tools/etl/build.ts @@ -180,12 +180,12 @@ const MOON_OFFSET_CEILINGS_DEG: Record = { hyperion: 10, iapetus * - Iapetus, 10.34: the row sits 9.4 degrees behind Horizons at its own epoch, 2000 Jan 1.5, and * keeps that offset; its plane agrees with Horizons' to 0.07 degrees and its period to 0.001 per * cent, so the fault is in the row's longitude, which this has no second source to correct. - * - Mimas, 7.43: its orbit carries the 44-degree libration of its resonance with Tethys (see + * - Mimas, 7.42: its orbit carries the 44-degree libration of its resonance with Tethys (see * `orbitFromW` in `fetchSolarSystem.ts`), but not the rest of what Horizons integrates. * - Ceres, 7.12 (1953): the SBDB's elements are osculating, exact at 2026 Jun 9 and drifting * either side; 1.9 by 2050, 5.3 by 2100, and 39 at 1600 on Horizons' own figures. * - * And three are held tighter than the rest, each where one reading of its row is all that keeps it + * And four are held tighter than the rest, each where one reading of its row is all that keeps it * close, and without it the card would quietly restate itself under the general ceiling: * * - Tethys, 0.28, which takes the other half of that libration, 2.23 degrees, from its W. Without @@ -193,9 +193,12 @@ const MOON_OFFSET_CEILINGS_DEG: Record = { hyperion: 10, iapetus * - Io, 0.07, and Europa, 0.23, whose periapses turn backwards, held by the Laplace resonance at * 2n(Europa) - n(Io), -0.7395 degrees a day (`apsidesRegress`). Read as advancing, Io strays 0.96 * and Europa 2.24, and their cards said "within 1.0" and "within 2.3". + * - Callisto, 0.08, whose node turns at JPL's current rate and its periapsis's longitude at the + * row's (`nodePeriodYears`). On the row's argument its periapsis moves 44 degrees by 2100 and it + * strays 0.71; on the row's node, 0.19. */ const MAX_TRACK_OFFSET_DEG = 3; -const TRACK_OFFSET_CEILINGS_DEG: Record = { hyperion: 23, nereid: 12, iapetus: 11, mimas: 8, ceres: 8, tethys: 0.5, io: 0.2, europa: 0.5 }; +const TRACK_OFFSET_CEILINGS_DEG: Record = { hyperion: 23, nereid: 12, iapetus: 11, mimas: 8, ceres: 8, tethys: 0.5, io: 0.2, europa: 0.5, callisto: 0.15 }; /** * The bodies the IAU WGCCRE 2015 report gives no rotational elements for: Hyperion tumbles, and @@ -273,18 +276,36 @@ const SUB_PLANET_CEILINGS_DEG: Record = { moon: 8, mimas: 9.5, i * How far a locked moon's spin axis may lean from the normal of the orbit it is drawn going round, * over the same dates. A locked moon sits in a Cassini state, its axis on its orbit normal as the * node carries both round the Laplace pole, and the IAU's pole goes round on a term of the node's - * angle; at the rate the IAU's source had for it and not the drawn orbit's, Miranda's axis was 7.89 - * degrees off at AD 9 and Mimas's 2.63, and an Iapetus pole left on the Laplace pole is 8.30 off at - * every date (see `lockedToOrbit`). + * angle; at the rate the IAU's source had for it and not the drawn orbit's, Rhea's axis is 0.77 + * degrees off by AD 1 and Triton's 0.51, and an Iapetus pole left on the Laplace pole is 8.30 off + * at every date (see `lockedToOrbit`). * * Measured on this catalogue: at most 0.97 degrees (Tethys, whose IAU pole sits 0.69 from its orbit - * normal today; Titan 0.94, whose pole the IAU holds still while its node turns in 705 years) but + * normal today; Titan 0.94, whose pole the IAU holds still while its node turns in 687 years) but * for four. The Moon 6.98, its real 6.7-degree tilt to its orbit. Phobos 1.81 and Deimos 1.74, and * Proteus 1.09: their IAU poles nod with Mars's and Neptune's precessing poles, the Laplace poles * their orbits are drawn round are fixed. + * + * And six are held tighter, each where its node terms turned at the node's rate, or its node at + * JPL's current rate, are what keep it close: Europa 0.13, Ganymede 0.16, Callisto 0.22, Rhea 0.17, + * Miranda 0.23 and Triton 0.15. On the IAU's rates they are 0.33, 0.21, 0.33, 0.77, 0.59 and 0.51, + * on a tolerance of 1 per cent Callisto and Rhea are left there, on the node's angle alone and not + * its harmonics Triton is 0.29, and on the archived table's node periods Callisto is 0.56 and + * Miranda 0.42: all under the general ceiling. */ const MAX_AXIS_FROM_ORBIT_DEG = 1; -const AXIS_FROM_ORBIT_CEILINGS_DEG: Record = { moon: 7.1, phobos: 2, deimos: 2, proteus: 1.2 }; +const AXIS_FROM_ORBIT_CEILINGS_DEG: Record = { + moon: 7.1, + phobos: 2, + deimos: 2, + proteus: 1.2, + europa: 0.25, + ganymede: 0.25, + callisto: 0.25, + rhea: 0.25, + miranda: 0.25, + triton: 0.25 +}; /** The clock's window, AD 1 to 3000 (`CLOCK_WINDOW` in `time.store.ts`), as Julian dates. */ const CLOCK_START_JD = Date.parse('0001-01-01T00:00Z') / 86400000 + 2440587.5; const CLOCK_END_JD = Date.parse('3000-01-01T00:00Z') / 86400000 + 2440587.5; diff --git a/tools/etl/fetchSolarSystem.ts b/tools/etl/fetchSolarSystem.ts index 4176a43..780d9bf 100644 --- a/tools/etl/fetchSolarSystem.ts +++ b/tools/etl/fetchSolarSystem.ts @@ -13,6 +13,7 @@ import { lockedToOrbit } from './lib/locked-spin'; import { dataPath, ensureDataDir } from './lib/paths'; const HOURS_PER_DAY = 24; +const DAYS_PER_JULIAN_YEAR = 365.25; interface BodySpec { id: string; @@ -51,6 +52,25 @@ interface BodySpec { nodeOffsetDeg?: number; epochJd?: number; periodDays?: number; + /** + * The node's period, in Julian years, where the row's is out of date. The archived table the rows + * are read from (see `fetchSatelliteMeanElementsHtml`) has older node periods for four moons than + * JPL's current one (ssd.jpl.nasa.gov/sats/elem), and Horizons and the IAU agree with the current + * ones: Miranda 17.787 years there (URA182) against the row's 17.727, Ganymede 137.812 (JUP365) + * against 132.654, Callisto 577.264 against 338.82, Titan 687.370 (SAT441) against 704.60. Fitted + * to Horizons' osculating elements on Uranus's equator over 1601-2399, Miranda's node turns + * 2023.97 degrees a century (rms 0.04): the current table's 2023.95, the IAU's U11 2024.22, the + * row's 2030.80. On the row's rate Miranda's drawn orbit was 2.0 degrees from Horizons' at 1601 + * and 2.1 at 2399, and the axis `lockedToOrbit` turned after it 2.4 at 1601; on this one, at most 0.12 and 0.34 over 1601-2399. + * + * The row's periapsis turns at its argument's rate from that node, and it is the longitude, node + * plus argument, the row gives the rate of: Callisto's turns 68.7 degrees a century in the row and + * 67.2 in the current table, where their arguments turn at 175.0 and 129.5. So the argument takes + * up what the node's rate gives: on the new node and the row's argument, Callisto's periapsis + * moved 44 degrees by 2100 and Callisto strayed 0.71 degrees from Horizons over 1950-2100 (0.19 + * before); keeping the row's longitude, 0.08. + */ + nodePeriodYears?: number; /** * The terms of the IAU's W that are this locked moon's motion along its orbit, which its row has * no column for: W's quadratic, and the term whose angle turns at `angleRateDegPerCentury`, if @@ -72,6 +92,14 @@ interface BodySpec { /** S5 in pck00011.tpc, 316.45 + 506.2 T: the libration of Mimas and Tethys in their 4:2 resonance. */ const MIMAS_TETHYS_LIBRATION = { angleRateDegPerCentury: 506.2 }; +/** + * Mimas's node period, 0.986 years in both JPL's tables, is given to three figures: anywhere from + * 36 493 to 36 530 degrees a century. It takes the IAU's S3, 36 505.5, which a fit to Horizons' + * osculating elements on Saturn's equator over 1750-2249, 36 506.7, is 1.2 from; the row's figure, + * 36 511.2, is 4.5. + */ +const MIMAS_NODE_PERIOD_YEARS = 36000 / 36505.5; + /** * The poles of the equators JPL refers Uranus's and Pluto's moons to, from the IAU WGCCRE 2015 * report, each taken at the end the table's inclinations are measured from (Titania 0.079 @@ -125,14 +153,14 @@ const BODY_SPECS: BodySpec[] = [ { id: 'deimos', name: 'Deimos', kind: 'moon', horizonsCommand: '402', center: '500@499', parentBodyId: 'mars' }, { id: 'io', name: 'Io', kind: 'moon', horizonsCommand: '501', center: '500@599', parentBodyId: 'jupiter', apsidesRegress: true }, { id: 'europa', name: 'Europa', kind: 'moon', horizonsCommand: '502', center: '500@599', parentBodyId: 'jupiter', apsidesRegress: true }, - { id: 'ganymede', name: 'Ganymede', kind: 'moon', horizonsCommand: '503', center: '500@599', parentBodyId: 'jupiter' }, - { id: 'callisto', name: 'Callisto', kind: 'moon', horizonsCommand: '504', center: '500@599', parentBodyId: 'jupiter' }, - { id: 'mimas', name: 'Mimas', kind: 'moon', horizonsCommand: '601', center: '500@699', parentBodyId: 'saturn', orbitFromW: MIMAS_TETHYS_LIBRATION }, + { id: 'ganymede', name: 'Ganymede', kind: 'moon', horizonsCommand: '503', center: '500@599', parentBodyId: 'jupiter', nodePeriodYears: 137.812 }, + { id: 'callisto', name: 'Callisto', kind: 'moon', horizonsCommand: '504', center: '500@599', parentBodyId: 'jupiter', nodePeriodYears: 577.264 }, + { id: 'mimas', name: 'Mimas', kind: 'moon', horizonsCommand: '601', center: '500@699', parentBodyId: 'saturn', orbitFromW: MIMAS_TETHYS_LIBRATION, nodePeriodYears: MIMAS_NODE_PERIOD_YEARS }, { id: 'enceladus', name: 'Enceladus', kind: 'moon', horizonsCommand: '602', center: '500@699', parentBodyId: 'saturn' }, { id: 'tethys', name: 'Tethys', kind: 'moon', horizonsCommand: '603', center: '500@699', parentBodyId: 'saturn', orbitFromW: MIMAS_TETHYS_LIBRATION }, { id: 'dione', name: 'Dione', kind: 'moon', horizonsCommand: '604', center: '500@699', parentBodyId: 'saturn' }, { 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' }, + { id: 'titan', name: 'Titan', kind: 'moon', horizonsCommand: '606', center: '500@699', parentBodyId: 'saturn', nodePeriodYears: 687.37 }, // Hyperion tumbles ("Rotational period = Chaotic") and Phoebe, captured, turns in 9.27 hours. // 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, trackStepDays: 1 }, @@ -147,7 +175,7 @@ const BODY_SPECS: BodySpec[] = [ // (2.58 in 1969). The table's note on misstated retrograde mean motions is about another source, // Jacobson 2000 on Jupiter's outer moons, and says the table carries the corrected values. { id: 'phoebe', name: 'Phoebe', kind: 'moon', horizonsCommand: '609', center: '500@699', parentBodyId: 'saturn', spinsFreely: true, periodDays: 550.30391 }, - { id: 'miranda', name: 'Miranda', horizonsCommand: '705', ...URANUS_MOON }, + { id: 'miranda', name: 'Miranda', horizonsCommand: '705', ...URANUS_MOON, nodePeriodYears: 17.787 }, { id: 'ariel', name: 'Ariel', horizonsCommand: '701', ...URANUS_MOON }, { id: 'umbriel', name: 'Umbriel', horizonsCommand: '702', ...URANUS_MOON }, { id: 'titania', name: 'Titania', horizonsCommand: '703', ...URANUS_MOON }, @@ -215,6 +243,9 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo (parentName ? parseSatelliteMeanElements(satelliteElements, parentName, spec.name, spec.apsidesRegress ?? false, spec.equatorPole) : parsePlanetMeanElements(planetElements, spec.id)); + const nodeRate = spec.nodePeriodYears + ? Math.sign(read.rates.longitudeOfAscendingNodeDegPerDay) * (360 / (spec.nodePeriodYears * DAYS_PER_JULIAN_YEAR)) + : read.rates.longitudeOfAscendingNodeDegPerDay; const corrected: MeanOrbit = { ...read, orbit: { @@ -222,7 +253,13 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo longitudeOfAscendingNodeDeg: read.orbit.longitudeOfAscendingNodeDeg + (spec.nodeOffsetDeg ?? 0), epochJd: spec.epochJd ?? read.orbit.epochJd }, - rates: spec.periodDays ? { ...read.rates, meanMotionDegPerDay: 360 / spec.periodDays } : read.rates + rates: { + ...read.rates, + ...(spec.periodDays ? { meanMotionDegPerDay: 360 / spec.periodDays } : {}), + longitudeOfAscendingNodeDegPerDay: nodeRate, + // The periapsis's longitude keeps the row's rate; see `nodePeriodYears`. + argumentOfPeriapsisDegPerDay: read.rates.argumentOfPeriapsisDegPerDay + (read.rates.longitudeOfAscendingNodeDegPerDay - nodeRate) + } }; if (spec.orbitFromW && !rotation) { throw new Error(`${spec.name}'s orbit takes terms from a W the kernel does not give.`); diff --git a/tools/etl/lib/locked-spin.ts b/tools/etl/lib/locked-spin.ts index 82de187..ed61980 100644 --- a/tools/etl/lib/locked-spin.ts +++ b/tools/etl/lib/locked-spin.ts @@ -23,8 +23,8 @@ const POLE_HARMONICS = 5; /** * How far a periodic term's angle may turn from a multiple of the node's rate, as a fraction of it, - * and still be taken for the node's angle as the IAU's source had it. Measured: at most 3.4e-2 - * (Ganymede's J5), then Rhea's R4 1.2e-2 and Miranda's U11 3.2e-3; the nearest that is not a node is + * and still be taken for the node's angle as the IAU's source had it. Measured: at most 3.1e-2 + * (Callisto's J6), then Rhea's R4 1.2e-2 and Ganymede's J5 3.3e-3; the nearest that is not a node is * a term of Miranda's W alone, 6.0e-2 from three times it. Multiples go up to the ninth, the most the * report takes (Triton's N7); past that, Umbriel's W has a term 1.0e-2 from ten times its node's. */ @@ -58,14 +58,20 @@ export function subPlanetLongitudeDeg(body: Pick