Add Charon, the moons of Uranus, Saturn's other large moons and the four dwarf planets past Pluto's table
The solar system stopped at 18 bodies: Pluto without Charon, Uranus without a moon, Saturn with Titan alone, no dwarf planet but Pluto (audit #22). bodies.json now holds 38: the eight planets, the five IAU dwarf planets, and every moon in JPL's mean-element table more than 100 km in mean radius. New: Ceres, Eris, Haumea, Makemake; Mimas, Enceladus, Tethys, Dione, Rhea, Hyperion, Iapetus, Phoebe; Miranda, Ariel, Umbriel, Titania, Oberon; Nereid, Proteus; Charon. Search finds each by name (it indexes bodies.json), each has a body page, and the Sun's system draws them. Where they come from - Moons: the same archived JPL satellite table as the others. Uranus's and Pluto's are given against the planet's equator, with the IAU WGCCRE 2015 poles: Pluto's as the IAU gives it (132.993, -6.163), Uranus's at the end the table measures inclinations from (77.311, 15.175) with its nodes counted 180 degrees on, from the IAU pole's crossing; read without that offset every Uranian moon was 180 degrees from Horizons at every date from 1980 to 2100. - Two rows are corrected where they disagree with JPL's own ephemeris and the reason is known. Pluto's section prints epoch 2000 Jan 1.0; JPL's current table gives Charon's as 2000-01-01.5, and at 1.0 Charon was 27.8-28.2 degrees from Horizons at every date, half a day of its motion. Phoebe's mean motion gives 548.02 days where its Horizons page and the current table give 550.30 (the table's own note says its source misstated retrograde moons' mean motions); on the row's figure Phoebe was 24.6 degrees out by 2025 and 100 by 2075. - Dwarf planets: JPL SBDB osculating heliocentric elements with their epoch (2026 Jun 9), carried at their own n. Against Horizons (heliocentric, 1950-2300; the clock only runs forward from now): Ceres 0.02 degrees in 2025, 1.9 in 2050, 4.0 in 2075, 5.3 in 2100, 11.6 in 2200 (Jupiter pulls on it and nothing here carries that); Eris within 0.06 to 2100 and 0.5 to 2300; Haumea within 0.35 to 2100; Makemake within 0.25 to 2100 and 1.7 by 2200. - Size and spin: Horizons pages for the moons (Charon 606 km, Miranda 235.7 as the mean of its three axes). The SBDB for Ceres (469.7 km, 9.074 h) and for the other three's spins (Eris 25.9 h, Haumea 3.915 h, Makemake 22.83 h). Neither source nor the WGCCRE 2015 report has a radius for Eris, Haumea or Makemake, so each carries its stellar-occultation measurement: Eris 1163 km (Sicardy et al. 2011), Makemake 715 (Brown 2013, the mean of 1434 x 1434 x 1422 km), and Haumea 797.6, the radius of a sphere of its volume: it is triaxial, 1161 x 852 x 513 km (Ortiz et al. 2017), and is drawn as that sphere. - Rotation uses the branch's model. Every moon is locked except three: Hyperion's page says "Chaotic" and Nereid's gives no spin, so both are left still; Phoebe turns in 9.274 h. - Charon carries massRatio 0.12205, the GM ratio of the two Horizons pages (106.10 / 869.326), so it and Pluto are drawn round their barycentre 2 131 km from Pluto's centre. Validators (tools/etl/build.ts, on the real catalogue; full npm run etl passes) - Offsets from Horizons on 2025-01-01, new bodies: dwarf planets at most 0.016 degrees (Ceres), under the 0.25 ceiling; moons Dione 0.009, Ariel 0.058, Rhea 0.070, Charon 0.111, Oberon 0.142, Titania 0.185, Umbriel 0.219, Proteus 0.245, Enceladus 0.309, Phoebe 0.984, Miranda 1.162, Tethys 2.042, under the 2.5 ceiling, which is unchanged. - Four moons get their own ceiling, each just above its worst offset at twelve dates from 1980 to 2100 and each named with its reason: Mimas 46 (measured up to 44.7: its resonance with Tethys swings its longitude 44 degrees either way over 70.8 years, which the table has no column for), Hyperion 21 (20.2; held in resonance by Titan, and the row's eccentricity 0.0232 is under a quarter of the current table's 0.105), Iapetus 11 (10.1; the row sits 9.4 degrees behind Horizons at its own epoch and keeps that, with its plane within 0.07 degrees and its period within 0.001 per cent), Nereid 3 (2.6 in 2025; eccentricity 0.75). - New checks: every body has a radius over 0 (Charon's would have been 0 before the page parser learnt its form); a freely spinning moon is not locked; a moon with a mass ratio puts the barycentre outside its planet; there are 5 dwarf planets. - Negative controls, each a full npm run etl on the real catalogue refused with the named message: Uranus's node offset removed (Miranda 172.50 degrees), Charon at the printed epoch (28.08), Phoebe on the row's mean motion (24.61), Charon's radius unread (no radius), free spinners locked (Hyperion), mass ratio inverted (barycentre 17 460 km out). Measured in the running app (port 4311): the Sun's system has 38 members ("13 + 25 moons"); Charon comes back to within 0.0004 degrees of where it started after 6.38723 days and is 179.98 degrees round after half that; Pluto is 2 130.6 km from the barycentre and Charon 17 456.8, exactly opposite; Saturn's moons in order of distance now: Mimas 185 617 km, Enceladus 238 042, Tethys 294 648, Dione 376 805, Rhea 526 964, Titan 1 231 389, Hyperion 1 470 453, Iapetus 3 637 059, Phoebe 11 740 900. At the arrival framing the dwarf planets are held at the 3 px floor and the moons at 1.5 px, half their planet's drawn radius, the scene's existing rule. Searching Charon, Enceladus, Ceres, Titania, Makemake and Phoebe each finds the body; the body pages show Charon 6.39 d and 606 km, Titania 8.71 d, Ceres 4.6 yr and 470 km, Haumea 283 yr and 798 km, Hyperion 21.3 d, each with its orbit source. Long tasks on entering: see the previous commit. The Sun's note now says the four dwarf planets are on the SBDB's osculating elements. Holding Eris's orbit, the arrival framing widens: 192 AU of range on a 1600 x 1000 window, under the 200 AU ceiling. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -836,7 +836,7 @@ describe('GalaxySystemSceneComponent camera-flight transitions', () => {
|
|||||||
navigationStore.selectStar(SUN.id);
|
navigationStore.selectStar(SUN.id);
|
||||||
await flushAsync();
|
await flushAsync();
|
||||||
await advanceFrames(engine, 2.5);
|
await advanceFrames(engine, 2.5);
|
||||||
expect(note()).toBe('Orbits propagated from JPL mean elements, the planets’ fit for 3000 BC to AD 3000, to the current date.');
|
expect(note()).toBe('Orbits propagated from JPL mean elements, the planets’ fit for 3000 BC to AD 3000, and the SBDB’s osculating ones for Ceres, Eris, Haumea and Makemake, to the current date.');
|
||||||
|
|
||||||
navigationStore.selectStar(ALPHA_CENTAURI.id);
|
navigationStore.selectStar(ALPHA_CENTAURI.id);
|
||||||
await flushAsync();
|
await flushAsync();
|
||||||
|
|||||||
@@ -1762,7 +1762,7 @@ export class GalaxySystemSceneComponent implements AfterViewInit, OnDestroy {
|
|||||||
]);
|
]);
|
||||||
// Where the orbits come from, and for the Sun how far from the present they hold: each
|
// Where the orbits come from, and for the Sun how far from the present they hold: each
|
||||||
// body's card names its own source and epoch.
|
// body's card names its own source and epoch.
|
||||||
const source = this.bodies.some((body) => body.systemStarId === star.id) ? 'JPL mean elements, the planets’ fit for 3000 BC to AD 3000,' : 'published elements';
|
const source = this.bodies.some((body) => body.systemStarId === star.id) ? 'JPL mean elements, the planets’ fit for 3000 BC to AD 3000, and the SBDB’s osculating ones for Ceres, Eris, Haumea and Makemake,' : 'published elements';
|
||||||
this.hudNote.set(`Orbits propagated from ${source} to ${this.time.atNow() ? 'the current date' : 'the date on the clock'}.`);
|
this.hudNote.set(`Orbits propagated from ${source} to ${this.time.atNow() ? 'the current date' : 'the date on the clock'}.`);
|
||||||
this.hudRange.set(
|
this.hudRange.set(
|
||||||
formatAu(
|
formatAu(
|
||||||
|
|||||||
@@ -272,6 +272,98 @@
|
|||||||
"rotationPeriodHours": 153.29335198,
|
"rotationPeriodHours": 153.29335198,
|
||||||
"obliquityDeg": 119.6
|
"obliquityDeg": 119.6
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "ceres",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Ceres",
|
||||||
|
"kind": "dwarf",
|
||||||
|
"radiusKm": 469.7,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 2.765552595034094,
|
||||||
|
"eccentricity": 0.07969229514816586,
|
||||||
|
"inclinationDeg": 10.58802780183462,
|
||||||
|
"longitudeOfAscendingNodeDeg": 80.24862682043221,
|
||||||
|
"argumentOfPeriapsisDeg": 73.29421453021587,
|
||||||
|
"meanAnomalyAtEpochDeg": 274.4193463761342,
|
||||||
|
"epochJd": 2461200.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.21430445064843,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": 0,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SBDB osculating elements, epoch 2026 Jun 9",
|
||||||
|
"rotationPeriodHours": 9.07417
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "eris",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Eris",
|
||||||
|
"kind": "dwarf",
|
||||||
|
"radiusKm": 1163,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 67.93394687853566,
|
||||||
|
"eccentricity": 0.4382385347971672,
|
||||||
|
"inclinationDeg": 43.9258279471791,
|
||||||
|
"longitudeOfAscendingNodeDeg": 36.00477044417249,
|
||||||
|
"argumentOfPeriapsisDeg": 150.7949235840312,
|
||||||
|
"meanAnomalyAtEpochDeg": 211.774434275007,
|
||||||
|
"epochJd": 2461200.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.001760247770619088,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": 0,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SBDB osculating elements, epoch 2026 Jun 9",
|
||||||
|
"rotationPeriodHours": 25.9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "haumea",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Haumea",
|
||||||
|
"kind": "dwarf",
|
||||||
|
"radiusKm": 797.6,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 43.06029023650952,
|
||||||
|
"eccentricity": 0.1944430148898797,
|
||||||
|
"inclinationDeg": 28.20847393040364,
|
||||||
|
"longitudeOfAscendingNodeDeg": 121.7860561329425,
|
||||||
|
"argumentOfPeriapsisDeg": 240.6905472508661,
|
||||||
|
"meanAnomalyAtEpochDeg": 223.2104118812299,
|
||||||
|
"epochJd": 2461200.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.003488097731816818,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": 0,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SBDB osculating elements, epoch 2026 Jun 9",
|
||||||
|
"rotationPeriodHours": 3.9154
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "makemake",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Makemake",
|
||||||
|
"kind": "dwarf",
|
||||||
|
"radiusKm": 715,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 45.57093317300052,
|
||||||
|
"eccentricity": 0.1588889953992523,
|
||||||
|
"inclinationDeg": 29.02785603743067,
|
||||||
|
"longitudeOfAscendingNodeDeg": 79.2948338209406,
|
||||||
|
"argumentOfPeriapsisDeg": 297.0922733397207,
|
||||||
|
"meanAnomalyAtEpochDeg": 169.9379962048232,
|
||||||
|
"epochJd": 2461200.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.003203850120050116,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": 0,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SBDB osculating elements, epoch 2026 Jun 9",
|
||||||
|
"rotationPeriodHours": 22.8266
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "moon",
|
"id": "moon",
|
||||||
"systemStarId": 0,
|
"systemStarId": 0,
|
||||||
@@ -465,6 +557,146 @@
|
|||||||
"parentBodyId": "jupiter",
|
"parentBodyId": "jupiter",
|
||||||
"rotationPeriodHours": 400.5364072574082
|
"rotationPeriodHours": 400.5364072574082
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "mimas",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Mimas",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 198.8,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0012402516100785653,
|
||||||
|
"eccentricity": 0.0196,
|
||||||
|
"inclinationDeg": 1.574,
|
||||||
|
"longitudeOfAscendingNodeDeg": 173.027,
|
||||||
|
"argumentOfPeriapsisDeg": 332.499,
|
||||||
|
"meanAnomalyAtEpochDeg": 14.848,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 381.9944948,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.9996209770462032,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 1.9992419540924065
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 40.589,
|
||||||
|
"decDeg": 83.536
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 22.618127008672275
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "enceladus",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Enceladus",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 252.3,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0015912124877590255,
|
||||||
|
"eccentricity": 0,
|
||||||
|
"inclinationDeg": 0.003,
|
||||||
|
"longitudeOfAscendingNodeDeg": 342.507,
|
||||||
|
"argumentOfPeriapsisDeg": 0.076,
|
||||||
|
"meanAnomalyAtEpochDeg": 199.686,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 262.7318978,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.41763825566421886,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.8324546312225984
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 40.586,
|
||||||
|
"decDeg": 83.536
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 32.88523423439451
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "tethys",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Tethys",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 536.3,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0019697606564930874,
|
||||||
|
"eccentricity": 0.0001,
|
||||||
|
"inclinationDeg": 1.091,
|
||||||
|
"longitudeOfAscendingNodeDeg": 259.842,
|
||||||
|
"argumentOfPeriapsisDeg": 45.202,
|
||||||
|
"meanAnomalyAtEpochDeg": 243.367,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 190.6979109,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.1978374715711675,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.3958338487419905
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 40.578,
|
||||||
|
"decDeg": 83.537
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 45.30726088829954
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "dione",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Dione",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 562.5,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0025228634487509454,
|
||||||
|
"eccentricity": 0.0022,
|
||||||
|
"inclinationDeg": 0.028,
|
||||||
|
"longitudeOfAscendingNodeDeg": 290.415,
|
||||||
|
"argumentOfPeriapsisDeg": 284.315,
|
||||||
|
"meanAnomalyAtEpochDeg": 322.232,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 131.5349307,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.08417681128768952,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.1684255439794184
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 40.544,
|
||||||
|
"decDeg": 83.54
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 65.68597371070803
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "rhea",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Rhea",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 764.5,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.003523231965359785,
|
||||||
|
"eccentricity": 0.0002,
|
||||||
|
"inclinationDeg": 0.333,
|
||||||
|
"longitudeOfAscendingNodeDeg": 351.042,
|
||||||
|
"argumentOfPeriapsisDeg": 241.619,
|
||||||
|
"meanAnomalyAtEpochDeg": 179.781,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 79.6900459,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.02750687327996083,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.054943212183932014
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 40.328,
|
||||||
|
"decDeg": 83.559
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 108.42006554798584
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "titan",
|
"id": "titan",
|
||||||
"systemStarId": 0,
|
"systemStarId": 0,
|
||||||
@@ -493,6 +725,229 @@
|
|||||||
"parentBodyId": "saturn",
|
"parentBodyId": "saturn",
|
||||||
"rotationPeriodHours": 382.69076217631203
|
"rotationPeriodHours": 382.69076217631203
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "hyperion",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Hyperion",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 133,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.010033117403187745,
|
||||||
|
"eccentricity": 0.0232,
|
||||||
|
"inclinationDeg": 0.615,
|
||||||
|
"longitudeOfAscendingNodeDeg": 263.847,
|
||||||
|
"argumentOfPeriapsisDeg": 303.178,
|
||||||
|
"meanAnomalyAtEpochDeg": 86.342,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 16.9199503,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.006372858420842858,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.007700806964353125
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 36.372,
|
||||||
|
"decDeg": 83.862
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "iapetus",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Iapetus",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 734.5,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.023802838792678086,
|
||||||
|
"eccentricity": 0.0293,
|
||||||
|
"inclinationDeg": 8.298,
|
||||||
|
"longitudeOfAscendingNodeDeg": 81.105,
|
||||||
|
"argumentOfPeriapsisDeg": 271.606,
|
||||||
|
"meanAnomalyAtEpochDeg": 201.789,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 4.5379416,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.00028662508640328154,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.0005878404972699524
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 284.715,
|
||||||
|
"decDeg": 78.749
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 1903.9469348834284
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "phoebe",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Phoebe",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 106.6,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.08655148592298781,
|
||||||
|
"eccentricity": 0.1634,
|
||||||
|
"inclinationDeg": 175.243,
|
||||||
|
"longitudeOfAscendingNodeDeg": 241.086,
|
||||||
|
"argumentOfPeriapsisDeg": 342.5,
|
||||||
|
"meanAnomalyAtEpochDeg": 53.038,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.6541839762686767,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": 0.0013629247388132204,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.002138389055297137
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 275.954,
|
||||||
|
"decDeg": 67.455
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "saturn",
|
||||||
|
"rotationPeriodHours": 9.273966666666666
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "miranda",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Miranda",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 235.67989734197351,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0008683278671826712,
|
||||||
|
"eccentricity": 0.0013,
|
||||||
|
"inclinationDeg": 4.338,
|
||||||
|
"longitudeOfAscendingNodeDeg": 506.438,
|
||||||
|
"argumentOfPeriapsisDeg": 68.312,
|
||||||
|
"meanAnomalyAtEpochDeg": 311.33,
|
||||||
|
"epochJd": 2444239.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 254.6906576,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.05560028675847895,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.11058300049002093
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 77.311,
|
||||||
|
"decDeg": 15.175
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1",
|
||||||
|
"parentBodyId": "uranus",
|
||||||
|
"rotationPeriodHours": 33.9235057988244
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "ariel",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Ariel",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 578.8979066892538,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0012760876816410463,
|
||||||
|
"eccentricity": 0.0012,
|
||||||
|
"inclinationDeg": 0.041,
|
||||||
|
"longitudeOfAscendingNodeDeg": 202.394,
|
||||||
|
"argumentOfPeriapsisDeg": 115.349,
|
||||||
|
"meanAnomalyAtEpochDeg": 39.481,
|
||||||
|
"epochJd": 2444239.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 142.8356579,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.017216781081741836,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.034237400422660706
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 77.311,
|
||||||
|
"decDeg": 15.175
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1",
|
||||||
|
"parentBodyId": "uranus",
|
||||||
|
"rotationPeriodHours": 60.48909723963263
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "umbriel",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Umbriel",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 584.7,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0017781001745234067,
|
||||||
|
"eccentricity": 0.0039,
|
||||||
|
"inclinationDeg": 0.128,
|
||||||
|
"longitudeOfAscendingNodeDeg": 213.485,
|
||||||
|
"argumentOfPeriapsisDeg": 84.709,
|
||||||
|
"meanAnomalyAtEpochDeg": 12.469,
|
||||||
|
"epochJd": 2444239.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 86.8688879,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.007763832371289367,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.015608689123104496
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 77.311,
|
||||||
|
"decDeg": 15.175
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1",
|
||||||
|
"parentBodyId": "uranus",
|
||||||
|
"rotationPeriodHours": 99.46023494563465
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "titania",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Titania",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 788.9,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.002916485361445723,
|
||||||
|
"eccentricity": 0.0011,
|
||||||
|
"inclinationDeg": 0.079,
|
||||||
|
"longitudeOfAscendingNodeDeg": 279.771,
|
||||||
|
"argumentOfPeriapsisDeg": 284.4,
|
||||||
|
"meanAnomalyAtEpochDeg": 24.614,
|
||||||
|
"epochJd": 2444239.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 41.3514246,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.005044947168524978,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.006102004540272753
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 77.311,
|
||||||
|
"decDeg": 15.175
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1",
|
||||||
|
"parentBodyId": "uranus",
|
||||||
|
"rotationPeriodHours": 208.94080635857947
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "oberon",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Oberon",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 761.4,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0039004565858436384,
|
||||||
|
"eccentricity": 0.0014,
|
||||||
|
"inclinationDeg": 0.068,
|
||||||
|
"longitudeOfAscendingNodeDeg": 459.771,
|
||||||
|
"argumentOfPeriapsisDeg": 104.4,
|
||||||
|
"meanAnomalyAtEpochDeg": 283.088,
|
||||||
|
"epochJd": 2444239.5
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 26.7394888,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.005044921345997627,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.006102193433429646
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 77.311,
|
||||||
|
"decDeg": 15.175
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 1980 Jan 1",
|
||||||
|
"parentBodyId": "uranus",
|
||||||
|
"rotationPeriodHours": 323.1176207078424
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "triton",
|
"id": "triton",
|
||||||
"systemStarId": 0,
|
"systemStarId": 0,
|
||||||
@@ -520,5 +975,89 @@
|
|||||||
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
"parentBodyId": "neptune",
|
"parentBodyId": "neptune",
|
||||||
"rotationPeriodHours": 141.0444976486201
|
"rotationPeriodHours": 141.0444976486201
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nereid",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Nereid",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 170,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.03685759679733196,
|
||||||
|
"eccentricity": 0.7507,
|
||||||
|
"inclinationDeg": 7.09,
|
||||||
|
"longitudeOfAscendingNodeDeg": 335.57,
|
||||||
|
"argumentOfPeriapsisDeg": 281.117,
|
||||||
|
"meanAnomalyAtEpochDeg": 216.692,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 0.9996276,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.0001042358742653985,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.0001218108353098093
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 269.302,
|
||||||
|
"decDeg": 69.117
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "neptune"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "proteus",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Proteus",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 208,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.0007864149365863937,
|
||||||
|
"eccentricity": 0.0005,
|
||||||
|
"inclinationDeg": 0.075,
|
||||||
|
"longitudeOfAscendingNodeDeg": 315.131,
|
||||||
|
"argumentOfPeriapsisDeg": 67.968,
|
||||||
|
"meanAnomalyAtEpochDeg": 250.938,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 320.7656245,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.0774437246301215,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.15448687827077687
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 299.406,
|
||||||
|
"decDeg": 42.432
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "neptune",
|
||||||
|
"rotationPeriodHours": 26.93555462331033
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "charon",
|
||||||
|
"systemStarId": 0,
|
||||||
|
"name": "Charon",
|
||||||
|
"kind": "moon",
|
||||||
|
"radiusKm": 606,
|
||||||
|
"orbit": {
|
||||||
|
"semiMajorAxisAu": 0.00013095774631236113,
|
||||||
|
"eccentricity": 0.0002,
|
||||||
|
"inclinationDeg": 0.08,
|
||||||
|
"longitudeOfAscendingNodeDeg": 26.928,
|
||||||
|
"argumentOfPeriapsisDeg": 146.106,
|
||||||
|
"meanAnomalyAtEpochDeg": 131.07,
|
||||||
|
"epochJd": 2451545
|
||||||
|
},
|
||||||
|
"rates": {
|
||||||
|
"meanMotionDegPerDay": 56.362521,
|
||||||
|
"longitudeOfAscendingNodeDegPerDay": -0.00010926638529337138,
|
||||||
|
"argumentOfPeriapsisDegPerDay": 0.00009683851540842405
|
||||||
|
},
|
||||||
|
"laplacePole": {
|
||||||
|
"raDeg": 132.993,
|
||||||
|
"decDeg": -6.163
|
||||||
|
},
|
||||||
|
"orbitSource": "JPL SSD satellite mean elements, epoch 2000 Jan 1",
|
||||||
|
"parentBodyId": "pluto",
|
||||||
|
"massRatio": 0.1220485755631374,
|
||||||
|
"rotationPeriodHours": 153.29335605836368
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
+51
-10
@@ -8,7 +8,7 @@ import { ExoplanetRecord } from '../../src/app/shared/models/exoplanet.model';
|
|||||||
import { StarRecord, SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
import { StarRecord, SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
||||||
import { fetchDeepSky } from './fetchDeepSky';
|
import { fetchDeepSky } from './fetchDeepSky';
|
||||||
import { fetchExoplanets } from './fetchExoplanets';
|
import { fetchExoplanets } from './fetchExoplanets';
|
||||||
import { fetchSolarSystem } from './fetchSolarSystem';
|
import { fetchSolarSystem, FREELY_SPINNING_MOONS } from './fetchSolarSystem';
|
||||||
import { BYTES_PER_STAR_META, BYTES_PER_STAR_POSITION, decodeStarCatalog, encodeStarCatalog } from '../../src/app/shared/models/star-catalog';
|
import { BYTES_PER_STAR_META, BYTES_PER_STAR_POSITION, decodeStarCatalog, encodeStarCatalog } from '../../src/app/shared/models/star-catalog';
|
||||||
import { fetchStars } from './fetchStars';
|
import { fetchStars } from './fetchStars';
|
||||||
import { describeSources } from './sources/registry';
|
import { describeSources } from './sources/registry';
|
||||||
@@ -145,6 +145,23 @@ function validateMerge(stars: StarRecord[]): void {
|
|||||||
*/
|
*/
|
||||||
const MAX_PLANET_OFFSET_DEG = 0.25;
|
const MAX_PLANET_OFFSET_DEG = 0.25;
|
||||||
const MAX_MOON_OFFSET_DEG = 2.5;
|
const MAX_MOON_OFFSET_DEG = 2.5;
|
||||||
|
const KM_PER_AU = 149597870.7;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The moons whose table row cannot come within that, each for a reason no mean ellipse carries,
|
||||||
|
* with a ceiling just above its worst offset from Horizons at twelve dates from 1980 to 2100:
|
||||||
|
*
|
||||||
|
* - Mimas, 44.7 degrees: its resonance with Tethys swings its mean longitude 44 degrees either
|
||||||
|
* way over 70.8 years, and the table has no column for it (Tethys, on the other end, swings 2).
|
||||||
|
* - Hyperion, 20.2: held in a 4:3 resonance by Titan; the row's eccentricity, 0.0232, is less than
|
||||||
|
* a quarter of the 0.105 JPL's current table gives.
|
||||||
|
* - Iapetus, 10.1: 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.
|
||||||
|
* - Nereid, 2.6: an eccentricity of 0.75, the largest here, which a mean ellipse follows least
|
||||||
|
* well: under 0.9 degrees in every year measured but 2025 and 2030 (2.6 and 2.3) and 2100 (1.7).
|
||||||
|
*/
|
||||||
|
const MOON_OFFSET_CEILINGS_DEG: Record<string, number> = { mimas: 46, hyperion: 21, iapetus: 11, nereid: 3 };
|
||||||
|
|
||||||
function angleBetweenDeg(a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }): number {
|
function angleBetweenDeg(a: { x: number; y: number; z: number }, b: { x: number; y: number; z: number }): number {
|
||||||
const cosine = (a.x * b.x + a.y * b.y + a.z * b.z) / (Math.hypot(a.x, a.y, a.z) * Math.hypot(b.x, b.y, b.z));
|
const cosine = (a.x * b.x + a.y * b.y + a.z * b.z) / (Math.hypot(a.x, a.y, a.z) * Math.hypot(b.x, b.y, b.z));
|
||||||
@@ -169,28 +186,52 @@ function validateBodies(bodies: BodyRecord[], horizonsOrbits: Map<string, Orbita
|
|||||||
const truth = eclipticToEquatorial(positionAtEpoch(horizons!));
|
const truth = eclipticToEquatorial(positionAtEpoch(horizons!));
|
||||||
const mean = positionAtEpoch(meanElementsAt(body.orbit, body.rates, horizons!.epochJd));
|
const mean = positionAtEpoch(meanElementsAt(body.orbit, body.rates, horizons!.epochJd));
|
||||||
const offset = angleBetweenDeg(body.laplacePole ? laplacePlaneToEquatorial(mean, body.laplacePole) : eclipticToEquatorial(mean), truth);
|
const offset = angleBetweenDeg(body.laplacePole ? laplacePlaneToEquatorial(mean, body.laplacePole) : eclipticToEquatorial(mean), truth);
|
||||||
const ceiling = body.kind === 'moon' ? MAX_MOON_OFFSET_DEG : MAX_PLANET_OFFSET_DEG;
|
const ceiling = body.kind === 'moon' ? (MOON_OFFSET_CEILINGS_DEG[body.id] ?? MAX_MOON_OFFSET_DEG) : MAX_PLANET_OFFSET_DEG;
|
||||||
assertCondition(
|
assertCondition(
|
||||||
offset <= ceiling,
|
offset <= ceiling,
|
||||||
`${body.name}'s mean elements put it ${offset.toFixed(2)} degrees from where Horizons has it (at most ${ceiling} expected) — the elements were read wrongly.`
|
`${body.name}'s mean elements put it ${offset.toFixed(2)} degrees from where Horizons has it (at most ${ceiling} expected) — the elements were read wrongly.`
|
||||||
);
|
);
|
||||||
offsets.push(`${body.id} ${offset.toFixed(3)}`);
|
offsets.push(`${body.id} ${offset.toFixed(3)}`);
|
||||||
|
|
||||||
|
// A radius of 0 is what a page whose radius no pattern reads comes out as — Charon's did.
|
||||||
|
assertCondition(body.radiusKm > 0, `Body ${body.id} has no radius; its page states it in a form the ETL does not read.`);
|
||||||
|
|
||||||
if (body.kind === 'moon') {
|
if (body.kind === 'moon') {
|
||||||
assertCondition(!!body.parentBodyId && ids.has(body.parentBodyId), `Moon ${body.id} has no valid parentBodyId.`);
|
const parent = bodies.find((candidate) => candidate.id === body.parentBodyId);
|
||||||
// Every moon here is tidally locked: its day is its orbit, from the same mean motion that
|
assertCondition(parent !== undefined, `Moon ${body.id} has no valid parentBodyId.`);
|
||||||
// carries it round, or its face turns away from its planet: the Kepler period of the
|
|
||||||
// osculating orbit this used to take would turn the Moon's five degrees an orbit.
|
|
||||||
const orbitHours = (360 / body.rates.meanMotionDegPerDay) * 24;
|
const orbitHours = (360 / body.rates.meanMotionDegPerDay) * 24;
|
||||||
assertCondition(
|
if (FREELY_SPINNING_MOONS.has(body.id)) {
|
||||||
body.rotationPeriodHours !== undefined && Math.abs(body.rotationPeriodHours - orbitHours) <= orbitHours * 1e-9,
|
// Hyperion tumbles and Nereid's page gives no spin, so they have none; Phoebe turns in
|
||||||
`Moon ${body.id} turns once in ${body.rotationPeriodHours} hours but goes round in ${orbitHours} — it will not keep one face to its planet.`
|
// 9.27 hours against a 550-day orbit. A lock here would be the rule below misapplied.
|
||||||
);
|
assertCondition(
|
||||||
|
body.rotationPeriodHours === undefined || Math.abs(body.rotationPeriodHours - orbitHours) > orbitHours * 0.1,
|
||||||
|
`Moon ${body.id} does not keep one face to its planet, yet turns once in ${body.rotationPeriodHours} hours against an orbit of ${orbitHours}.`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// Every other moon here is tidally locked: its day is its orbit, from the same mean motion
|
||||||
|
// that carries it round, or its face turns away from its planet: the Kepler period of the
|
||||||
|
// osculating orbit this used to take would turn the Moon's five degrees an orbit.
|
||||||
|
assertCondition(
|
||||||
|
body.rotationPeriodHours !== undefined && Math.abs(body.rotationPeriodHours - orbitHours) <= orbitHours * 1e-9,
|
||||||
|
`Moon ${body.id} turns once in ${body.rotationPeriodHours} hours but goes round in ${orbitHours} — it will not keep one face to its planet.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (body.massRatio !== undefined) {
|
||||||
|
// The pair's barycentre, which the planet's elements place, must lie outside the planet —
|
||||||
|
// that is why the two are drawn going round it — and nearer the planet than the moon.
|
||||||
|
const offsetKm = (body.orbit.semiMajorAxisAu * KM_PER_AU * body.massRatio) / (1 + body.massRatio);
|
||||||
|
assertCondition(
|
||||||
|
body.massRatio > 0 && body.massRatio < 1 && offsetKm > parent!.radiusKm,
|
||||||
|
`${body.name}'s mass ratio ${body.massRatio} puts its barycentre ${offsetKm.toFixed(0)} km from ${parent!.name}'s centre, which is not between its surface, ${parent!.radiusKm} km out, and the moon.`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const planetCount = bodies.filter((body) => body.kind === 'planet').length;
|
const planetCount = bodies.filter((body) => body.kind === 'planet').length;
|
||||||
assertCondition(planetCount === 8, `Expected 8 planets, found ${planetCount}.`);
|
assertCondition(planetCount === 8, `Expected 8 planets, found ${planetCount}.`);
|
||||||
|
const dwarfCount = bodies.filter((body) => body.kind === 'dwarf').length;
|
||||||
|
assertCondition(dwarfCount === 5, `Expected the IAU's 5 dwarf planets, found ${dwarfCount}.`);
|
||||||
console.log(` mean elements against Horizons, degrees: ${offsets.join(', ')}.`);
|
console.log(` mean elements against Horizons, degrees: ${offsets.join(', ')}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+117
-21
@@ -3,8 +3,8 @@ import { writeFileSync } from 'node:fs';
|
|||||||
import { BodyRecord, OrbitalElements } from '../../src/app/shared/models/body.model';
|
import { BodyRecord, OrbitalElements } from '../../src/app/shared/models/body.model';
|
||||||
import { SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
import { SUN_STAR_ID } from '../../src/app/shared/models/star.model';
|
||||||
import { fetchHorizonsBody } from './lib/horizons';
|
import { fetchHorizonsBody } from './lib/horizons';
|
||||||
import { parsePlanetMeanElements, parseSatelliteMeanElements } from '../../src/app/shared/astro/mean-elements';
|
import { MeanOrbit, parsePlanetMeanElements, parseSatelliteMeanElements, parseSmallBodyElements } from '../../src/app/shared/astro/mean-elements';
|
||||||
import { fetchPlanetMeanElementsText, fetchSatelliteMeanElementsHtml } from './lib/mean-elements';
|
import { fetchPlanetMeanElementsText, fetchSatelliteMeanElementsHtml, fetchSmallBodyAnswer } from './lib/mean-elements';
|
||||||
import { dataPath, ensureDataDir } from './lib/paths';
|
import { dataPath, ensureDataDir } from './lib/paths';
|
||||||
|
|
||||||
const HOURS_PER_DAY = 24;
|
const HOURS_PER_DAY = 24;
|
||||||
@@ -23,8 +23,41 @@ interface BodySpec {
|
|||||||
obliquityDeg?: number;
|
obliquityDeg?: number;
|
||||||
/** The periapsis turns backwards; see `parseSatelliteMeanElements`. */
|
/** The periapsis turns backwards; see `parseSatelliteMeanElements`. */
|
||||||
apsidesRegress?: boolean;
|
apsidesRegress?: boolean;
|
||||||
|
/** The pole of the planet's equator, where JPL gives its moons against that plane. */
|
||||||
|
equatorPole?: { raDeg: number; decDeg: number };
|
||||||
|
/** The Small-Body Database's name for a dwarf planet past Standish's tables: its orbit comes from there. */
|
||||||
|
sbdb?: string;
|
||||||
|
/** A measured mean radius, in km, for a body neither Horizons nor the SBDB gives one for. */
|
||||||
|
radiusKm?: 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`. */
|
||||||
|
barycentric?: boolean;
|
||||||
|
/**
|
||||||
|
* Corrections to a row of the satellite table, each where the row disagrees with JPL's own
|
||||||
|
* Horizons ephemeris and the reason is known; see the specs that carry them.
|
||||||
|
*/
|
||||||
|
nodeOffsetDeg?: number;
|
||||||
|
epochJd?: number;
|
||||||
|
periodDays?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* degrees, Charon 0.080): the end the moons go round anticlockwise. For Pluto that is the pole
|
||||||
|
* the IAU gives, 132.993 / -6.163, which for dwarf planets follows the right-hand rule. For
|
||||||
|
* Uranus the IAU gives the other end, 257.311 / -15.175, named north because it lies on the
|
||||||
|
* ecliptic's north side; the table measures inclinations from 77.311 / 15.175 but counts its
|
||||||
|
* nodes from where the equator rises through the ICRF equator going round the IAU's pole, which
|
||||||
|
* is 180 degrees from where it rises going round this one: hence Uranus's moons' 180-degree node
|
||||||
|
* offset. Read with this pole and no offset, Ariel was 180 degrees from Horizons at every date
|
||||||
|
* from 1980 to 2100; read against the IAU's pole, anywhere from 1 to 179.
|
||||||
|
*/
|
||||||
|
const URANUS_EQUATOR_POLE = { raDeg: 77.311, decDeg: 15.175 };
|
||||||
|
const PLUTO_EQUATOR_POLE = { raDeg: 132.993, decDeg: -6.163 };
|
||||||
|
const URANUS_MOON = { kind: 'moon', center: '500@799', parentBodyId: 'uranus', equatorPole: URANUS_EQUATOR_POLE, nodeOffsetDeg: 180 } as const;
|
||||||
|
|
||||||
// Sun-centered planets/dwarf, then their major moons (planetocentric elements).
|
// Sun-centered planets/dwarf, then their major moons (planetocentric elements).
|
||||||
const BODY_SPECS: BodySpec[] = [
|
const BODY_SPECS: BodySpec[] = [
|
||||||
{ id: 'mercury', name: 'Mercury', kind: 'planet', horizonsCommand: '199', center: '500@10' },
|
{ id: 'mercury', name: 'Mercury', kind: 'planet', horizonsCommand: '199', center: '500@10' },
|
||||||
@@ -36,6 +69,16 @@ const BODY_SPECS: BodySpec[] = [
|
|||||||
{ id: 'uranus', name: 'Uranus', kind: 'planet', horizonsCommand: '799', center: '500@10' },
|
{ id: 'uranus', name: 'Uranus', kind: 'planet', horizonsCommand: '799', center: '500@10' },
|
||||||
{ id: 'neptune', name: 'Neptune', kind: 'planet', horizonsCommand: '899', center: '500@10' },
|
{ id: 'neptune', name: 'Neptune', kind: 'planet', horizonsCommand: '899', center: '500@10' },
|
||||||
{ id: 'pluto', name: 'Pluto', kind: 'dwarf', horizonsCommand: '999', center: '500@10', obliquityDeg: 119.6 },
|
{ id: 'pluto', name: 'Pluto', kind: 'dwarf', horizonsCommand: '999', center: '500@10', obliquityDeg: 119.6 },
|
||||||
|
{ id: 'ceres', name: 'Ceres', kind: 'dwarf', horizonsCommand: '1;', center: '500@10', sbdb: 'Ceres' },
|
||||||
|
// Eris, Haumea and Makemake have no radius in the SBDB, the Horizons pages ("RAD= n.a.") or the
|
||||||
|
// IAU WGCCRE 2015 report, so each carries its stellar-occultation measurement. Eris: 1163 km,
|
||||||
|
// Sicardy et al. 2011 (Nature 478, 493). Haumea is triaxial, 1161 x 852 x 513 km, Ortiz et al.
|
||||||
|
// 2017 (Nature 550, 219); drawn as a sphere, at the radius of the sphere of the same volume.
|
||||||
|
// Makemake: 1434 km across its equator and 1422 across its projected pole, Brown 2013 (ApJ 767,
|
||||||
|
// L7); the same mean.
|
||||||
|
{ id: 'eris', name: 'Eris', kind: 'dwarf', horizonsCommand: '136199;', center: '500@10', sbdb: 'Eris', radiusKm: 1163 },
|
||||||
|
{ id: 'haumea', name: 'Haumea', kind: 'dwarf', horizonsCommand: '136108;', center: '500@10', sbdb: 'Haumea', radiusKm: 797.6 },
|
||||||
|
{ id: 'makemake', name: 'Makemake', kind: 'dwarf', horizonsCommand: '136472;', center: '500@10', sbdb: 'Makemake', radiusKm: 715 },
|
||||||
{ id: 'moon', name: 'Moon', kind: 'moon', horizonsCommand: '301', center: '500@399', parentBodyId: 'earth' },
|
{ id: 'moon', name: 'Moon', kind: 'moon', horizonsCommand: '301', center: '500@399', parentBodyId: 'earth' },
|
||||||
{ id: 'phobos', name: 'Phobos', kind: 'moon', horizonsCommand: '401', center: '500@499', parentBodyId: 'mars' },
|
{ id: 'phobos', name: 'Phobos', kind: 'moon', horizonsCommand: '401', center: '500@499', parentBodyId: 'mars' },
|
||||||
{ id: 'deimos', name: 'Deimos', kind: 'moon', horizonsCommand: '402', center: '500@499', parentBodyId: 'mars' },
|
{ id: 'deimos', name: 'Deimos', kind: 'moon', horizonsCommand: '402', center: '500@499', parentBodyId: 'mars' },
|
||||||
@@ -43,15 +86,45 @@ const BODY_SPECS: BodySpec[] = [
|
|||||||
{ id: 'europa', name: 'Europa', kind: 'moon', horizonsCommand: '502', 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: '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: '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' },
|
||||||
|
{ 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' },
|
||||||
|
{ 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' },
|
||||||
{ id: 'triton', name: 'Triton', kind: 'moon', horizonsCommand: '801', center: '500@899', parentBodyId: 'neptune' }
|
// 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 },
|
||||||
|
{ 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
|
||||||
|
// its source misstated the mean motions of retrograde moons. On the row's figure Phoebe was
|
||||||
|
// 25 degrees from Horizons by 2025 and 100 by 2075; on the current period, within 2.0 from 1980
|
||||||
|
// to 2100.
|
||||||
|
{ 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: 'ariel', name: 'Ariel', horizonsCommand: '701', ...URANUS_MOON },
|
||||||
|
{ id: 'umbriel', name: 'Umbriel', horizonsCommand: '702', ...URANUS_MOON },
|
||||||
|
{ id: 'titania', name: 'Titania', horizonsCommand: '703', ...URANUS_MOON },
|
||||||
|
{ id: 'oberon', name: 'Oberon', horizonsCommand: '704', ...URANUS_MOON },
|
||||||
|
{ id: 'triton', name: 'Triton', kind: 'moon', horizonsCommand: '801', center: '500@899', parentBodyId: 'neptune' },
|
||||||
|
// Nereid's eccentric orbit, 0.75, cannot hold a face to Neptune; its page states no spin.
|
||||||
|
{ id: 'nereid', name: 'Nereid', kind: 'moon', horizonsCommand: '802', center: '500@899', parentBodyId: 'neptune', spinsFreely: true },
|
||||||
|
{ id: 'proteus', name: 'Proteus', kind: 'moon', horizonsCommand: '808', center: '500@899', parentBodyId: 'neptune' },
|
||||||
|
// Pluto's section prints its epoch as 2000 Jan 1.0; JPL's current table gives Charon's as
|
||||||
|
// 2000-01-01.5, and read at 1.0 Charon sat 27.8 to 28.2 degrees — half a day of its motion is
|
||||||
|
// 28.2 — from Horizons at every date from 1980 to 2100. At 1.5 it is within 0.4.
|
||||||
|
{ id: 'charon', name: 'Charon', kind: 'moon', horizonsCommand: '901', center: '500@999', parentBodyId: 'pluto', equatorPole: PLUTO_EQUATOR_POLE, epochJd: 2451545.0, barycentric: true }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/** The moons whose day is not their orbit; `build.ts` holds every other moon to its lock. */
|
||||||
|
export const FREELY_SPINNING_MOONS = new Set(BODY_SPECS.filter((spec) => spec.spinsFreely).map((spec) => spec.id));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Writes `bodies.json` for the major planets, Pluto, and a curated set of major moons: JPL's
|
* Writes `bodies.json` for the major planets, the five dwarf planets, and every moon in JPL's
|
||||||
* mean orbital elements for where they go, and JPL Horizons for their size and spin. Horizons'
|
* mean-element table more than 100 km in mean radius — Phoebe, at 106.6, the smallest: JPL's
|
||||||
* osculating elements for the same date come back alongside, for `build.ts` to check the mean
|
* mean orbital elements for where they go, or the SBDB's osculating ones where there are none,
|
||||||
* ones against.
|
* and JPL Horizons for their size and spin. Horizons' osculating elements for the same date come
|
||||||
|
* back alongside, for `build.ts` to check the mean ones against.
|
||||||
*/
|
*/
|
||||||
export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizonsOrbits: Map<string, OrbitalElements> }> {
|
export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizonsOrbits: Map<string, OrbitalElements> }> {
|
||||||
console.log(`Fetching ${BODY_SPECS.length} solar-system bodies from JPL (mean elements, Horizons)...`);
|
console.log(`Fetching ${BODY_SPECS.length} solar-system bodies from JPL (mean elements, Horizons)...`);
|
||||||
@@ -59,6 +132,7 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo
|
|||||||
const horizonsOrbits = new Map<string, OrbitalElements>();
|
const horizonsOrbits = new Map<string, OrbitalElements>();
|
||||||
const planetElements = await fetchPlanetMeanElementsText();
|
const planetElements = await fetchPlanetMeanElementsText();
|
||||||
const satelliteElements = await fetchSatelliteMeanElementsHtml();
|
const satelliteElements = await fetchSatelliteMeanElementsHtml();
|
||||||
|
const gmById = new Map<string, number | undefined>();
|
||||||
|
|
||||||
for (const spec of BODY_SPECS) {
|
for (const spec of BODY_SPECS) {
|
||||||
const result = await fetchHorizonsBody({
|
const result = await fetchHorizonsBody({
|
||||||
@@ -68,23 +142,44 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo
|
|||||||
});
|
});
|
||||||
|
|
||||||
horizonsOrbits.set(spec.id, result.orbit);
|
horizonsOrbits.set(spec.id, result.orbit);
|
||||||
if (result.radiusKm === undefined) {
|
gmById.set(spec.id, result.gmKm3PerS2);
|
||||||
|
|
||||||
|
const parentName = BODY_SPECS.find((candidate) => candidate.id === spec.parentBodyId)?.name;
|
||||||
|
const smallBody = spec.sbdb ? parseSmallBodyElements(await fetchSmallBodyAnswer(spec.sbdb, `sbdb-${spec.id}.json`)) : undefined;
|
||||||
|
const read: MeanOrbit =
|
||||||
|
smallBody ??
|
||||||
|
(parentName
|
||||||
|
? parseSatelliteMeanElements(satelliteElements, parentName, spec.name, spec.apsidesRegress ?? false, spec.equatorPole)
|
||||||
|
: parsePlanetMeanElements(planetElements, spec.id));
|
||||||
|
const mean: MeanOrbit = {
|
||||||
|
...read,
|
||||||
|
orbit: {
|
||||||
|
...read.orbit,
|
||||||
|
longitudeOfAscendingNodeDeg: read.orbit.longitudeOfAscendingNodeDeg + (spec.nodeOffsetDeg ?? 0),
|
||||||
|
epochJd: spec.epochJd ?? read.orbit.epochJd
|
||||||
|
},
|
||||||
|
rates: spec.periodDays ? { ...read.rates, meanMotionDegPerDay: 360 / spec.periodDays } : read.rates
|
||||||
|
};
|
||||||
|
const radiusKm = smallBody?.radiusKm ?? spec.radiusKm ?? result.radiusKm;
|
||||||
|
if (radiusKm === undefined) {
|
||||||
console.warn(` no physical radius found for ${spec.name}; defaulting to 0.`);
|
console.warn(` no physical radius found for ${spec.name}; defaulting to 0.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const parentName = BODY_SPECS.find((candidate) => candidate.id === spec.parentBodyId)?.name;
|
// A moon listed here is tidally locked unless its spec says otherwise, so its day is its
|
||||||
const mean = parentName
|
// orbit: the sidereal period from the same mean motion that carries it round, which keeps one
|
||||||
? parseSatelliteMeanElements(satelliteElements, parentName, spec.name, spec.apsidesRegress ?? false)
|
// face towards the parent however long the clock runs. Not every page says so — the Moon's
|
||||||
: parsePlanetMeanElements(planetElements, spec.id);
|
// gives a rate, Titan's and Proteus's nothing. The Kepler period of the osculating orbit this
|
||||||
|
// used to take, 27.70 days for the Moon, would now turn its face five degrees an orbit away
|
||||||
// Every moon listed here is tidally locked, so its day is its orbit: the sidereal period from
|
// from the orbit it is drawn on.
|
||||||
// the same mean motion that carries it round, which keeps one face towards the parent however
|
const rotationPeriodHours = result.tidallyLocked || (spec.kind === 'moon' && !spec.spinsFreely)
|
||||||
// long the clock runs. Not every page says so — the Moon's gives a rate, Titan's nothing. The
|
|
||||||
// Kepler period of the osculating orbit this used to take, 27.70 days for the Moon, would now
|
|
||||||
// turn its face five degrees an orbit away from the orbit it is drawn on.
|
|
||||||
const rotationPeriodHours = result.tidallyLocked || spec.kind === 'moon'
|
|
||||||
? (360 / mean.rates.meanMotionDegPerDay) * HOURS_PER_DAY
|
? (360 / mean.rates.meanMotionDegPerDay) * HOURS_PER_DAY
|
||||||
: result.rotationPeriodHours;
|
: smallBody
|
||||||
|
? smallBody.rotationPeriodHours
|
||||||
|
: result.rotationPeriodHours;
|
||||||
|
const parentGm = spec.barycentric && spec.parentBodyId ? gmById.get(spec.parentBodyId) : undefined;
|
||||||
|
if (spec.barycentric && (result.gmKm3PerS2 === undefined || parentGm === undefined)) {
|
||||||
|
throw new Error(`${spec.name} and its planet need a GM each to place their barycentre.`);
|
||||||
|
}
|
||||||
if (rotationPeriodHours === undefined) {
|
if (rotationPeriodHours === undefined) {
|
||||||
console.warn(` no rotation period found for ${spec.name}; it will not turn.`);
|
console.warn(` no rotation period found for ${spec.name}; it will not turn.`);
|
||||||
}
|
}
|
||||||
@@ -94,12 +189,13 @@ export async function fetchSolarSystem(): Promise<{ bodies: BodyRecord[]; horizo
|
|||||||
systemStarId: SUN_STAR_ID,
|
systemStarId: SUN_STAR_ID,
|
||||||
name: spec.name,
|
name: spec.name,
|
||||||
kind: spec.kind,
|
kind: spec.kind,
|
||||||
radiusKm: result.radiusKm ?? 0,
|
radiusKm: radiusKm ?? 0,
|
||||||
orbit: mean.orbit,
|
orbit: mean.orbit,
|
||||||
rates: mean.rates,
|
rates: mean.rates,
|
||||||
...(mean.laplacePole ? { laplacePole: mean.laplacePole } : {}),
|
...(mean.laplacePole ? { laplacePole: mean.laplacePole } : {}),
|
||||||
orbitSource: mean.orbitSource,
|
orbitSource: mean.orbitSource,
|
||||||
...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {}),
|
...(spec.parentBodyId ? { parentBodyId: spec.parentBodyId } : {}),
|
||||||
|
...(parentGm !== undefined ? { massRatio: result.gmKm3PerS2! / parentGm } : {}),
|
||||||
...(rotationPeriodHours !== undefined ? { rotationPeriodHours } : {}),
|
...(rotationPeriodHours !== undefined ? { rotationPeriodHours } : {}),
|
||||||
...((result.obliquityDeg ?? spec.obliquityDeg) !== undefined ? { obliquityDeg: result.obliquityDeg ?? spec.obliquityDeg } : {})
|
...((result.obliquityDeg ?? spec.obliquityDeg) !== undefined ? { obliquityDeg: result.obliquityDeg ?? spec.obliquityDeg } : {})
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user