Planetary Moons
pg_orbit computes positions for 19 planetary moons across four systems: the four Galilean moons of Jupiter, eight moons of Saturn, five moons of Uranus, and two moons of Mars. Each uses a dedicated analytic theory optimized for that system.
How you do it today
Section titled “How you do it today”Observing planetary moons usually means one of:
- Stellarium: Renders moon positions graphically. Good for identifying which moon is which at the eyepiece. Not scriptable.
- JPL Horizons: Computes precise ephemerides for any solar system body, including all natural satellites. One query per moon, rate-limited web API.
- Skyfield: Can load JPL satellite ephemeris kernels (BSP files) for high-precision moon positions. Requires downloading and managing kernel files.
- IMCCE: Provides specialized ephemeris services for natural satellites. Web-based, per-body queries.
The common limitation: getting positions for many moons at many times means many separate requests or script iterations. Comparing moon positions across systems (all of Jupiter’s moons vs. all of Saturn’s) requires stitching results together outside the ephemeris tool.
What changes with pg_orbit
Section titled “What changes with pg_orbit”Four observation functions cover all 19 moons:
| Function | Theory | Moons | Accuracy |
|---|---|---|---|
galilean_observe(body_id, observer, time) | L1.2 (Lieske, 1998) | 4 (Io, Europa, Ganymede, Callisto) | ~1 arcsecond |
saturn_moon_observe(body_id, observer, time) | TASS 1.7 (Vienne & Duriez, 1995) | 8 (Mimas through Hyperion) | ~1-5 arcseconds |
uranus_moon_observe(body_id, observer, time) | GUST86 (Laskar & Jacobson, 1987) | 5 (Miranda through Oberon) | ~2-10 arcseconds |
mars_moon_observe(body_id, observer, time) | MarsSat (Jacobson, 2010) | 2 (Phobos, Deimos) | ~1-5 arcseconds |
All functions return the same topocentric type. Every moon is identified by a system-specific body ID (integer).
Body ID reference
Section titled “Body ID reference”| ID | Moon | Orbital Period | Notes |
|---|---|---|---|
| 0 | Io | 1.77 days | Volcanic, drives Jupiter radio bursts |
| 1 | Europa | 3.55 days | Subsurface ocean candidate |
| 2 | Ganymede | 7.15 days | Largest moon in the solar system |
| 3 | Callisto | 16.69 days | Most heavily cratered body |
| ID | Moon | Orbital Period | Notes |
|---|---|---|---|
| 0 | Mimas | 0.94 days | ”Death Star” crater |
| 1 | Enceladus | 1.37 days | Cryovolcanic plumes |
| 2 | Tethys | 1.89 days | Odysseus crater |
| 3 | Dione | 2.74 days | Ice cliffs |
| 4 | Rhea | 4.52 days | Second-largest Saturn moon |
| 5 | Titan | 15.95 days | Thick atmosphere, hydrocarbon lakes |
| 6 | Iapetus | 79.32 days | Two-tone coloring |
| 7 | Hyperion | 21.28 days | Chaotic rotation |
| ID | Moon | Orbital Period | Notes |
|---|---|---|---|
| 0 | Miranda | 1.41 days | Extreme surface features |
| 1 | Ariel | 2.52 days | Youngest surface |
| 2 | Umbriel | 4.14 days | Dark, heavily cratered |
| 3 | Titania | 8.71 days | Largest Uranus moon |
| 4 | Oberon | 13.46 days | Most distant major moon |
| ID | Moon | Orbital Period | Notes |
|---|---|---|---|
| 0 | Phobos | 0.32 days | Slowly spiraling inward |
| 1 | Deimos | 1.26 days | Slowly receding |
What pg_orbit does not replace
Section titled “What pg_orbit does not replace”- Not sub-arcsecond. The analytic theories produce positions accurate to a few arcseconds at best. For astrometric reduction or spacecraft navigation, use JPL ephemerides via SPICE or Skyfield.
- No mutual events. pg_orbit does not predict eclipses, occultations, or transits between moons. Use IMCCE’s MULTISAT service for mutual event predictions.
- No libration or physical ephemerides. The functions return topocentric position only — no rotation state, no sub-observer longitude, no apparent disk size.
- 19 moons, not hundreds. Only the major moons with well-characterized analytic theories are included. Irregular satellites, small inner moons, and ring-embedded moonlets are not covered.
Try it
Section titled “Try it”Observe all four Galilean moons
Section titled “Observe all four Galilean moons”SELECT CASE moon_id WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' END AS moon, round(topo_azimuth(obs)::numeric, 1) AS az, round(topo_elevation(obs)::numeric, 1) AS el, round(topo_range(obs)::numeric, 0) AS range_kmFROM generate_series(0, 3) AS moon_id, LATERAL galilean_observe(moon_id, '40.0N 105.3W 1655m'::observer, '2024-03-15 03:00:00+00') obs;The range values should cluster near Jupiter’s range (about 4-6 AU or 600-900 million km), since the Galilean moons orbit within 0.013 AU of Jupiter.
Compare Galilean moon ranges to Jupiter
Section titled “Compare Galilean moon ranges to Jupiter”Verify that the moons are near their parent planet:
SELECT 'Jupiter' AS body, round(topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, '2024-03-15 03:00:00+00'))::numeric, -4) AS range_kmUNION ALLSELECT CASE moon_id WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' END, round(topo_range(galilean_observe(moon_id, '40.0N 105.3W 1655m'::observer, '2024-03-15 03:00:00+00'))::numeric, -4)FROM generate_series(0, 3) AS moon_id;The moon ranges should differ from Jupiter’s by at most a few million km. Io orbits closest; Callisto, the farthest Galilean moon, sits about 1.9 million km from Jupiter.
All eight Saturn moons
Section titled “All eight Saturn moons”SELECT CASE moon_id WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' END AS moon, round(topo_azimuth(obs)::numeric, 1) AS az, round(topo_elevation(obs)::numeric, 1) AS el, round(topo_range(obs)::numeric, 0) AS range_kmFROM generate_series(0, 7) AS moon_id, LATERAL saturn_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') obs;Uranus moons
Section titled “Uranus moons”The five major Uranian moons are faint targets, but their positions are still useful for planning deep imaging sessions:
SELECT CASE moon_id WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' WHEN 4 THEN 'Oberon' END AS moon, round(topo_azimuth(obs)::numeric, 1) AS az, round(topo_elevation(obs)::numeric, 1) AS el, round(topo_range(obs)::numeric, 0) AS range_kmFROM generate_series(0, 4) AS moon_id, LATERAL uranus_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') obs;Mars moons
Section titled “Mars moons”Phobos and Deimos are challenging visual targets due to Mars glare, but their positions are computed at every epoch:
SELECT CASE moon_id WHEN 0 THEN 'Phobos' WHEN 1 THEN 'Deimos' END AS moon, round(topo_azimuth(obs)::numeric, 1) AS az, round(topo_elevation(obs)::numeric, 1) AS el, round(topo_range(obs)::numeric, 0) AS range_kmFROM generate_series(0, 1) AS moon_id, LATERAL mars_moon_observe(moon_id, '40.0N 105.3W 1655m'::observer, '2024-01-15 06:00:00+00') obs;All 19 moons at once
Section titled “All 19 moons at once”A single query that observes every supported moon in the solar system:
WITH all_moons AS ( -- Galilean moons (Jupiter) SELECT 'Jupiter' AS parent, CASE id WHEN 0 THEN 'Io' WHEN 1 THEN 'Europa' WHEN 2 THEN 'Ganymede' WHEN 3 THEN 'Callisto' END AS moon, galilean_observe(id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') AS obs FROM generate_series(0, 3) AS id
UNION ALL
-- Saturn moons SELECT 'Saturn', CASE id WHEN 0 THEN 'Mimas' WHEN 1 THEN 'Enceladus' WHEN 2 THEN 'Tethys' WHEN 3 THEN 'Dione' WHEN 4 THEN 'Rhea' WHEN 5 THEN 'Titan' WHEN 6 THEN 'Iapetus' WHEN 7 THEN 'Hyperion' END, saturn_moon_observe(id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') FROM generate_series(0, 7) AS id
UNION ALL
-- Uranus moons SELECT 'Uranus', CASE id WHEN 0 THEN 'Miranda' WHEN 1 THEN 'Ariel' WHEN 2 THEN 'Umbriel' WHEN 3 THEN 'Titania' WHEN 4 THEN 'Oberon' END, uranus_moon_observe(id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') FROM generate_series(0, 4) AS id
UNION ALL
-- Mars moons SELECT 'Mars', CASE id WHEN 0 THEN 'Phobos' WHEN 1 THEN 'Deimos' END, mars_moon_observe(id, '40.0N 105.3W 1655m'::observer, '2024-06-15 03:00:00+00') FROM generate_series(0, 1) AS id)SELECT parent, moon, round(topo_azimuth(obs)::numeric, 1) AS az, round(topo_elevation(obs)::numeric, 1) AS el, round((topo_range(obs) / 149597870.7)::numeric, 3) AS dist_auFROM all_moonsWHERE topo_elevation(obs) > 0ORDER BY parent, moon;This returns every visible moon from Boulder at the specified time. 19 moons, 19 function calls, one result set.
Track Galilean moon positions over time
Section titled “Track Galilean moon positions over time”Watch Io complete part of its 1.77-day orbit around Jupiter:
SELECT t, round(topo_range(galilean_observe(0, '40.0N 105.3W 1655m'::observer, t))::numeric, 0) AS io_range_km, round(topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, t))::numeric, 0) AS jupiter_range_km, round((topo_range(galilean_observe(0, '40.0N 105.3W 1655m'::observer, t)) - topo_range(planet_observe(5, '40.0N 105.3W 1655m'::observer, t)))::numeric, 0) AS separation_kmFROM generate_series( '2024-03-15 00:00:00+00'::timestamptz, '2024-03-16 18:00:00+00'::timestamptz, interval '2 hours') AS t;The separation_km column shows Io oscillating between being closer and farther than Jupiter as seen from Earth — the projection of its orbit along the line of sight.
Titan observation windows
Section titled “Titan observation windows”Find when Titan is above the horizon over a week:
SELECT t::date AS date, round(topo_elevation( saturn_moon_observe(5, '40.0N 105.3W 1655m'::observer, t) )::numeric, 1) AS titan_elFROM generate_series( '2024-06-15 00:00:00+00'::timestamptz, '2024-06-22 00:00:00+00'::timestamptz, interval '1 hour') AS tWHERE topo_elevation( saturn_moon_observe(5, '40.0N 105.3W 1655m'::observer, t)) > 10ORDER BY t;Titan (body ID 5 in the Saturn system) is the only moon in the solar system with a thick atmosphere, making it a frequent target for amateur imaging.