Planetary Parameters

Planetary Mass Parameters

Table 8 Mass, gravitational parameter, and mass fraction of the Sun, planets, and other solar system bodies

Celestial Body

Mass [kg]

\(\mu\) [km3/s2]

Mass Fraction in Solar System

Sun

1.98842 × 1030 kg

1.32712 × 1011 km3/s2

9.98657 × 10-1

Mercury

3.30101 × 1023 kg

2.20319 × 104 km3/s2

1.65789 × 10-7

Venus

4.86732 × 1024 kg

3.24859 × 105 km3/s2

2.44455 × 10-6

Earth

5.97219 × 1024 kg

3.98600 × 105 km3/s2

2.99946 × 10-6

Moon

7.34581 × 1022 kg

4.90280 × 103 km3/s2

3.68934 × 10-8

Mars

6.41693 × 1023 kg

4.28284 × 104 km3/s2

3.22282 × 10-7

Jupiter

1.89852 × 1027 kg

1.26713 × 108 km3/s2

9.53510 × 10-4

Saturn

5.68460 × 1026 kg

3.79406 × 107 km3/s2

2.85502 × 10-4

Uranus

8.68192 × 1025 kg

5.79456 × 106 km3/s2

4.36039 × 10-5

Neptune

1.02431 × 1026 kg

6.83653 × 106 km3/s2

5.14447 × 10-5

Pluto

1.46158 × 1022 kg

9.75500 × 102 km3/s2

7.34061 × 10-9

The data in Table 8 is generated by the following script, using data from [PFWB21]:

from dataclasses import dataclass
from typing import Optional

import pint
from IPython.display import display
from myst_nb.nb_glue import GLUE_PREFIX

units = pint.UnitRegistry()
Q_ = units.Quantity


@dataclass
class CelestialObject:
    G = 6.67428e-11 * units("m**3 / (kg * s**2)")
    identifier: str
    name: str
    GM_in_au: pint.Quantity
    GM_ratio: pint.Quantity
    GM_in_km: pint.Quantity
    mass: Optional[pint.Quantity] = None
    mass_fraction: Optional[pint.Quantity] = None

    def compute_mass(self):
        self.mass = (self.GM_in_km / self.G).to("kg")

    def format_units(self, obj):
        initial_format = format(obj, ".5~EH")
        mantissa, remainder = initial_format.split("E")
        remainder = remainder.split(" ", maxsplit=1)
        exponent = remainder[0]
        if len(remainder) == 1:
            units = ""
        else:
            units = " " + remainder[1]

        sign = exponent[0] if exponent[0] == "-" else ""
        value = int(exponent[1:])
        return f"{mantissa} × 10<sup>{sign}{value}</sup>{units}"

    def glue(self):
        assert self.mass is not None, "Mass has not been computed"
        assert self.mass_fraction is not None, "Mass fraction has not been computed"
        metadata = {"scrapbook": {"mime_prefix": GLUE_PREFIX}}
        for attr in ("GM_in_km", "mass", "mass_fraction"):
            metadata["scrapbook"]["name"] = f"{self.name}_{attr}"
            display(
                {GLUE_PREFIX + "text/html": self.format_units(getattr(self, attr))},
                raw=True,
                metadata=metadata,
            )


object_identifier_map = {
    "GM1": "Mercury",
    "GM2": "Venus",
    "GM3": "Earth",
    "GM4": "Mars",
    "GM5": "Jupiter",
    "GM6": "Saturn",
    "GM7": "Uranus",
    "GM8": "Neptune",
    "GM9": "Pluto",
    "GMS": "Sun",
    "GMM": "Moon",
    "GMB": "Earth-Moon-Barycenter",
}

celestial_objects = {}

# https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/de440_tech-comments.txt
# Mass parameter (GM) for Sun, Moon, and Planets:

#           AU**3/DAY**2                    GMSun/GM(I)         KM**3/SEC**2

data = """\
   GM1     4.9125001948893182e-11        6023657.944929           22031.868551
   GM2     7.2434523326441187e-10         408523.718656          324858.592000
   GM3     8.8876924467071022e-10         332946.048773          398600.435507
   GM4     9.5495488297258119e-11        3098703.546737           42828.375816
   GM5     2.8253458252257917e-07           1047.348631       126712764.100000
   GM6     8.4597059933762903e-08           3497.901801        37940584.841800
   GM7     1.2920265649682399e-08          22902.950783         5794556.400000
   GM8     1.5243573478851939e-08          19412.259776         6836527.100580
   GM9     2.1750964648933581e-12      136045556.167380             975.500000
   GMS     2.9591220828411956e-04              1.000000    132712440041.279419
   GMM     1.0931894624024351e-11       27068702.952351            4902.800118
   GMB     8.9970113929473466e-10         328900.559708          403503.235625
"""

for line in data.splitlines():
    identifier, GM_in_au, GM_ratio, GM_in_km = line.split()
    name = object_identifier_map[identifier]
    c_object = CelestialObject(
        identifier,
        name,
        Q_(GM_in_au + "au**3/day**2"),
        Q_(GM_ratio + "kg/kg"),
        Q_(GM_in_km + "km**3/s**2"),
    )
    c_object.compute_mass()
    celestial_objects[name] = c_object

total_mass = sum(c.mass for c in celestial_objects.values())
for c in celestial_objects.values():
    c.mass_fraction = c.mass / total_mass
    c.glue()

Planetary Radius Parameters

Table 9 Radial parameters for objects in the Solar System. The gas giants assume a surface at the 1-bar pressure altitude.

Celestial Body

Mean radius (km)

Equatorial radius (km)

Polar radius (km)

Sun

695700

Mercury

2439.4 ± 0.1

2440.53 ± 0.04

2438.26 ± 0.04

Venus

6051.8 ± 1.0

Same

Same

Earth

6371.0084 ± 0.0001

6378.1366 ± 0.0001

6356.7519 ± 0.0001

Moon

1737.4

Mars

3389.50 ± 0.2

3396.19 ± 0.1

3376.20 ± 0.1

Jupiter

69911 ± 6

71492 ± 4

66854 ± 10

Saturn

58232 ± 6

60268 ± 4

54364 ± 10

Uranus

25362 ± 7

25559 ± 4

24973 ± 20

Neptune

24622 ± 19

24764 ± 15

24341 ± 30

Pluto

1188.3 ± 1.6

The data in Table 9 is generated by the following script, using data from [AAAHearn+18, AAC+19]:

import csv
from myst_nb.nb_glue import GLUE_PREFIX
from IPython.display import display

# Data source: https://link.springer.com/article/10.1007/s10569-017-9805-5/tables/5
planet_data = """\
Planet,Mean radius (km),Equatorial radius (km),Polar radius (km),RMS deviation from spheroid (km),Maximum elevation (km),Maximum depression (km)
Sun,,695700,,,,
Mercury,2439.4 ± 0.1,2440.53 ± 0.04,2438.26 ± 0.04,1.0,4.6,2.5
Venus,6051.8 ± 1.0,Same,Same,1.0,11,2
Earth,6371.0084 ± 0.0001,6378.1366 ± 0.0001,6356.7519 ± 0.0001,3.57,8.85,11.52
Mars,3389.50 ± 0.2,3396.19 ± 0.1,3376.20 ± 0.1,3.0,22.64 ± 0.1,7.55 ± 0.1
Mars,3389.50 ± 0.2,3396.19 ± 0.1,N 3373.19 ± 0.1,3.0,22.64 ± 0.1,7.55 ± 0.1
Mars,3389.50 ± 0.2,3396.19 ± 0.1,S 3379.21 ± 0.1,3.0,22.64 ± 0.1,7.55 ± 0.1
Jupiter,69911 ± 6,71492 ± 4,66854 ± 10,62.1,31,102
Saturn,58232 ± 6,60268 ± 4,54364 ± 10,102.9,8,205
Uranus,25362 ± 7,25559 ± 4,24973 ± 20,16.8,28,0
Neptune,24622 ± 19,24764 ± 15,24341 ± 30,8.0,14,0
"""

# Data source: https://link.springer.com/article/10.1007/s10569-017-9805-5/tables/6
moon_data = """\
Planet,Unnamed: 1,Satellite,Mean radius (km),Subplanetary equatorial radius (km),Along orbit equatorial radius (km),Polar radius (km),RMS deviation from ellipsoid (km),Maximum elevation (km),Maximum depression (km)
Earth,,Moon,1737.4,Same,Same,Same,2.5,7.5,5.6
Mars,I,Phobos,11.08 ± 0.04,13.0,11.4,9.1,0.5,,
Mars,II,Deimos,6.2 ± 0.25,7.8,6.0,5.1,0.2,,
Jupiter,XVI,Metis,21.5 ± 4,30,20,17,,,
Jupiter,XV,Adrastea,8.2 ± 4,10,8,7,,,
Jupiter,V,Amalthea,83.5 ± 3,125,73,64,3.2,,
Jupiter,XIV,Thebe,49.3 ± 4,58,49,42,,,
Jupiter,I,Io,1821.49,1829.4,1819.4,1815.7,,13.0,3.0
Jupiter,II,Europa,1560.8 ± 0.3,1562.6,1560.3,1559.5,0.32,,
Jupiter,III,Ganymede,2631.2± 1.7,Same,Same,Same,,,
Jupiter,IV,Callisto,2410.3± 1.5,Same,Same,Same,0.6,,
Jupiter,XIII,Leda,5,,,,,,
Jupiter,VI,Himalia,85 ± 10,,,,,,
Jupiter,X,Lysithea,12,,,,,,
Jupiter,VII,Elara,40 ± 10,,,,,,
Jupiter,XII,Ananke,10,,,,,,
Jupiter,XI,Carme,15,,,,,,
Jupiter,VIII,Pasiphae,18,,,,,,
Jupiter,IX,Sinope,14,,,,,,
Saturn,XVIII,Pan,14.0 ± 1.2,17.2 ± 1.7,15.4 ± 1.2,10.4 ± 0.9,,,
Saturn,XXXV,Daphnis,3.8 ± 0.8,4.6 ± 0.7,4.5 ± 0.9,2.8 ± 0.8,,,
Saturn,XV,Atlas,15.1 ± 0.8,20.5 ± 0.9,17.8 ± 0.7,9.4 ± 0.8,,,
Saturn,XVI,Prometheus,43.1 ± 1.2,68.2 ± 0.8,41.6 ± 1.8,28.2 ± 0.8,,,
Saturn,XVII,Pandora,40.6 ± 1.5,52.2 ± 1.8,40.8 ± 2.0,31.5 ± 0.9,,,
Saturn,XI,Epimetheus,58.2 ± 1.2,64.9 ± 1.3,57.3 ± 2.5,53.0 ± 0.5,,,
Saturn,X,Janus,89.2 ± 0.8,101.7 ± 1.6,93.0 ± 0.7,76.3 ± 0.4,,,
Saturn,I,Mimas,198.2 ± 0.4,207.8 ± 0.5,196.7 ± 0.5,190.6 ± 0.3,,,
Saturn,LIII,Aegaeon,0.33 ± 0.06,0.7 ± 0.05,0.25 ± 0.06,0.2 ± 0.08,,,
Saturn,XXXII,Methone,1.45 ± 0.03,1.94 ± 0.02,1.29 ± 0.04,1.21 ± 0.02,,,
Saturn,XLIX,Anthe,0.5,,,,,,
Saturn,XXXIII,Pallene,2.23 ± 0.07,2.88 ± 0.07,2.08 ± 0.07,1.8 ± 0.07,,,
Saturn,II,Enceladus,252.1 ± 0.2,256.6 ± 0.6,251.4 ± 0.2,248.3 ± 0.2,0.4,,
Saturn,III,Tethys,531.0 ± 0.6,538.4± 0.3,528.3 ± 1.1,526.3 ± 0.6,,,
Saturn,XIII,Telesto,12.4 ± 0.4,16.3 ± 0.5,11.8 ± 0.3,9.8 ± 0.3,,,
Saturn,XIV,Calypso,9.6 ± 0.6,15.3 ± 0.3,9.3 ± 2.2,6.3 ± 0.6,,,
Saturn,IV,Dione,561.4 ± 0.4,563.4 ± 0.6,561.3 ± 0.5,559.6 ± 0.4,0.5,,
Saturn,XII,Helene,18.0 ± 0.4,22.5 ± 0.5,19.6 ± 0.3,13.3 ± 0.2,,,
Saturn,XXXIV,Polydeuces,1.3 ± 0.4,1.5 ± 0.6,1.2 ± 0.4,1.0 ± 0.2,,,
Saturn,V,Rhea,763.5 ± 0.6,765.0 ± 0.7,763.1 ± 0.6,762.4 ± 0.6,,,
Saturn,VI,Titan,2575.0,2575.15 ± 0.02,2574.78 ± 0.06,2574.47 ± 0.06,0.26,,
Saturn,VII,Hyperion,135 ± 4,180.1 ± 2.0,133.0 ± 4.5,102.7 ± 4.5,,,
Saturn,VIII,Iapetus,734.3 ± 2.8,745.7 ± 2.9,745.7 ± 2.9,712.1 ± 1.6,,,
Saturn,IX,Phoebe,106.5 ± 0.7,109.4 ± 1.4,108.5 ± 0.6,101.8 ± 0.3,,,
Uranus,VI,Cordelia,13 ± 2,,,,,,
Uranus,VII,Ophelia,15 ± 2,,,,,,
Uranus,VIII,Bianca,21 ± 3,,,,,,
Uranus,IX,Cressida,31 ± 4,,,,,,
Uranus,X,Desdemona,27 ± 3,,,,,,
Uranus,XI,Juliet,42 ± 5,,,,,,
Uranus,XII,Portia,54 ± 6,,,,,,
Uranus,XIII,Rosalind,27 ± 4,,,,,,
Uranus,XIV,Belinda,33 ± 4,,,,,,
Uranus,XV,Puck,77 ± 51.9,,,,1.9,,
Uranus,V,Miranda,235.8 ± 0.7,240.4 ± 0.6,234.2 ± 0.9,232.9 ± 1.2,1.6,5.0,8.0
Uranus,I,Ariel,578.9 ± 0.6,581.1 ± 0.9,577.9 ± 0.6,577.7 ± 1.0,0.9,4.0,4.0
Uranus,II,Umbriel,584.7 ± 2.8,Same,Same,Same,2.6,,6.0
Uranus,III,Titania,788.9 ± 1.8,Same,Same,Same,1.3,4.0,
Uranus,IV,Oberon,761.4 ± 2.6,Same,Same,Same,1.5,12.0,2.0
Neptune,III,Naiad,29 ± 6,,,,,,
Neptune,IV,Thalassa,40 ± 8,,,,,,
Neptune,V,Despina,74 ± 10,,,,,,
Neptune,VI,Galatea,79 ± 12,,,,,,
Neptune,VII,Larissa,96 ± 7,104,,89,2.9,6.0,5.0
Neptune,VIII,Proteus,208 ± 8,218,208,201,7.9,18.0,13.0
Neptune,I,Triton,1352.6 ± 2.4,,,,,,
Neptune,II,Nereid,170 ± 25,,,,,,
"""

# Data source: https://link.springer.com/article/10.1007/s10569-017-9805-5/tables/7
dwarf_data = """\
Body,Mean radius (km),Radii measured along principal axes.x (km),Radii measured along principal axes.y (km),Radii measured along principal axes.z (km),Unnamed: 5
(1) Ceres,470,487.3,487.3,446,(a)
(4) Vesta,,289 ± 5,280 ± 5,229 ± 5,
(16) Psyche,113 ± 23,139.5 ± 10%,116 ± 10%,94.5 ± 10%,(b)
(21) Lutetia,52.5 ± 2.5,62.0 ± 2.5,50.5 ± 2.0,46.5 ± 6.5,
(52) Europa,157.5 ± 7,189.5 ± 16,165 ± 8,124.5 ± 10,
(243) Ida,15.65 ± 0.6,26.8,12.0,7.6,
(253) Mathilde,26.5 ± 1.3,33,24,23,
(433) Eros,8.45 ± 0.02,17.0,5.5,5.5,
(511) Davida,150,180,147,127,(c)
(951) Gaspr,6.1 ± 0.4,9.1,5.2,4.4,
(2867) Šteins,2.70,3.24,2.73,2.04,
(4179) Toutatis,,2.13,1.015,0.85,
(25143) Itokawa,,0.268,0.147,0.104,
(134340) Pluto,1188.3 ± 1.6,Same,Same,Same,
(134340) Pluto: I Charon,606.0 ± 1.0,Same,Same,Same,
1P/Halley,,8.0 ± 0.5,4.0 ± 0.25,4.0 ± 0.25,
9P/Tempel 1,3.0 ± 0.1,3.7,2.5,,(d)
19P/Borrelly,4.22 ± 0.05,3.5 ± 0.2,–,–,(e)
67P/Churyumov–Gerasimenko,1.65,2.40,1.55,1.20,(f)
81P/Wild 2,1.975,2.7,1.9,1.5,
103P/Hartley 2,0.58,0.34,1.16,1.16,(g)
"""


def glue(row: dict[str, str], name: str, object: str) -> None:
    meta_name = f"{object}_{name.replace(' ', '_').replace('(', '').replace(')', '')}"
    value = row[name]
    display(
        {GLUE_PREFIX + "text/html": value},
        raw=True,
        metadata={"scrapbook": {"mime_prefix": GLUE_PREFIX, "name": meta_name}},
    )


reader = csv.DictReader(planet_data.splitlines())
did_mars = False
for row in reader:
    if did_mars and row["Planet"] == "Mars":
        continue
    elif row["Planet"] == "Mars":
        did_mars = True
    glue(row, "Mean radius (km)", row["Planet"])
    glue(row, "Equatorial radius (km)", row["Planet"])
    glue(row, "Polar radius (km)", row["Planet"])

reader = csv.DictReader(moon_data.splitlines())
# Just want Earth's Moon
glue(next(reader), "Mean radius (km)", object="Moon")

reader = csv.DictReader(dwarf_data.splitlines())
for row in reader:
    body = row["Body"]
    if "Pluto" not in body or "Charon" in body:
        continue
    body = body.split()[1]
    glue(row, "Mean radius (km)", object="Pluto")

Planetary Orbital elements

Table 10 Orbital elements for objects in the Solar System. The values for the Moon are relative to Earth; all other values are relative to the Sun.

Celestial Body

Semi-Major Axis (km)

Perihelion (km)

Aphelion (km)

Period (days)

Velocity (km/s)

Eccentricity

Inclination (degrees)

Mercury

5.79091 × 107

4.60009 × 107

6.98173 × 107

87.97

47.87

0.20564

7.01

Venus

1.08209 × 108

1.07477 × 108

1.08940 × 108

224.70

35.02

0.00676

3.39

Earth

1.49598 × 108

1.47100 × 108

1.52096 × 108

365.26

29.78

0.01670

0.00

Moon

3.83398 × 105

3.62106 × 105

4.04689 × 105

27.18

1.03

0.05555

23.71

Mars

2.27939 × 108

2.06645 × 108

2.49233 × 108

686.97

24.13

0.09342

1.85

Jupiter

7.78321 × 108

7.40603 × 108

8.16038 × 108

4332.52

13.06

0.04846

1.30

Saturn

1.42910 × 109

1.35096 × 109

1.50724 × 109

10783.05

9.64

0.05468

2.49

Uranus

2.87479 × 109

2.73854 × 109

3.01104 × 109

30768.84

6.79

0.04739

0.77

Neptune

4.50489 × 109

4.46384 × 109

4.54594 × 109

60357.05

5.43

0.00911

1.77

Pluto

5.91540 × 109

4.44212 × 109

7.38868 × 109

90821.51

4.74

0.24906

17.14

The data in Table 10 is generated by the following script, using data from [PFWB21, Rho19]:

from __future__ import annotations
from pathlib import Path
import re
from datetime import datetime

from dateutil.rrule import rrule, DAILY
from dateutil import tz
from skyfield.iokit import load_file, Loader
from skyfield.elementslib import OsculatingElements, osculating_elements_of
from skyfield.framelib import build_ecliptic_matrix
from skyfield.units import Distance, Velocity
import numpy as np
from myst_nb.nb_glue import GLUE_PREFIX
from IPython.display import display


class CelestialObject:
    def __init__(
        self, identifier: int | None, name: str, elems: OsculatingElements
    ) -> None:
        self.identifier = identifier
        self.name = name
        self.aphelion: np.float64 = np.average(elems.apoapsis_distance.km)  # type: ignore
        self.semimajor_axis: np.float64 = np.average(elems.semi_major_axis.km)  # type: ignore
        self.perihelion: np.float64 = np.average(elems.periapsis_distance.km)  # type: ignore
        self.period_in_days: np.float64 = np.average(elems.period_in_days)  # type: ignore
        self.orbital_velocity: np.float64 = np.sqrt(elems._mu / self.semimajor_axis)  # type: ignore
        self.eccentricity: np.float64 = np.average(elems.eccentricity)  # type: ignore
        self.inclination: np.float64 = np.average(elems.inclination.degrees)  # type: ignore

    def format_units(self, obj: float):
        initial_format = format(obj, ".5E")
        mantissa, exponent = initial_format.split("E")
        sign = exponent[0] if exponent[0] == "-" else ""
        value = int(exponent[1:])
        return f"{mantissa} × 10<sup>{sign}{value}</sup>"

    def glue(self):
        metadata = {"scrapbook": {"mime_prefix": GLUE_PREFIX}}
        for attr in ("aphelion", "semimajor_axis", "perihelion"):
            metadata["scrapbook"]["name"] = f"{self.name}_{attr}"
            display(
                {GLUE_PREFIX + "text/html": self.format_units(getattr(self, attr))},
                raw=True,
                metadata=metadata,
            )
        for attr in (
            "period_in_days",
            "orbital_velocity",
            "inclination",
        ):
            metadata["scrapbook"]["name"] = f"{self.name}_{attr}"
            display(
                {GLUE_PREFIX + "text/html": f"{getattr(self, attr):.2F}"},
                raw=True,
                metadata=metadata,
            )
        metadata["scrapbook"]["name"] = f"{self.name}_eccentricity"
        display(
            {GLUE_PREFIX + "text/html": f"{self.eccentricity:.5F}"},
            raw=True,
            metadata=metadata,
        )


def parse_pck_file(filename: Path) -> dict[int | None, float]:
    m = re.compile(
        r"^\s+BODY(?P<identifier>\d+)_GM\s+=\s+\(\s(?P<value>[\d.ED+-]+)\s\)$"
    )
    lines = iter(filename.read_text().splitlines())
    for line in lines:
        if "begindata" in line:
            break
    GM_dict: dict[int | None, float] = {None: 0.0}
    for line in lines:
        mat = m.search(line)
        if mat is not None:
            identifier = mat.group("identifier")
            value = mat.group("value")
            if isinstance(value, str) and isinstance(identifier, str):
                identifier = int(identifier)
                value = value.replace("D", "E")
                GM_dict[identifier] = float(value)
            else:
                raise ValueError(value)

    return GM_dict


HERE = Path.cwd()  # .joinpath("reference", "scripts")
if HERE.name == "reference":
    HERE /= "scripts"
elif HERE.name == "orbital-mechanics":
    HERE = HERE.joinpath("reference", "scripts")
GM_dict = parse_pck_file(HERE / "gm_Horizons.pck")
planets = load_file(HERE / "excerpt.bsp")
load = Loader(HERE)
ts = load.timescale()
start_date = datetime(1770, 10, 29, tzinfo=tz.UTC)
end_date = datetime(2271, 11, 1, tzinfo=tz.UTC)
dates: list[datetime] = list(rrule(DAILY, dtstart=start_date, until=end_date))
t = ts.from_datetimes(dates)
ecliptic_frame = build_ecliptic_matrix(t)

sun = planets["Sun"]
for planet_name in (
    "Mercury",
    "Venus",
    "Earth",
    "Mars",
    "Jupiter",
    "Saturn",
    "Uranus",
    "Neptune",
    "Pluto",
):
    planet = planet_name + " Barycenter"
    position = (planets[planet] - sun).at(t)

    # This is broken, see
    # https://github.com/skyfielders/python-skyfield/issues/655#issuecomment-960377889
    # osculating_elements_of(position, ecliptic_frame)

    # So we have to do things manually
    elems = OsculatingElements(
        Distance(np.einsum("mnr,nr->mr", ecliptic_frame, position.position.au)),  # type: ignore
        Velocity(np.einsum("mnr,nr->mr", ecliptic_frame, position.velocity.au_per_d)),  # type: ignore
        position.t,
        GM_dict[position.center] + GM_dict[position.target],
    )
    c_object = CelestialObject(position.target, planet_name, elems)
    c_object.glue()

position = (planets["Moon"] - planets["Earth"]).at(t)
elems = osculating_elements_of(position)
c_object = CelestialObject(position.target, "Moon", elems)
c_object.glue()