This page describes how we create the Moon's idealized orbit calculated from its orbital elements and how we are able to closely match the Moon's actual orbit. See the image above that illustrates this close match. The linked file provides a complete 3D plot that can be tilted and explored in detail.
The Moon's orbit changes significantly over time due to gravitational perturbations. In idealized_orbits.py the function calculate_moon_orbital_elements accounts for this:
def calculate_moon_orbital_elements(date):
# Calculate Julian centuries since J2000.0
T = (date - j2000).total_seconds() / (36525.0 * 86400.0)
# Node regression (retrograde motion) - 18.6 year cycle
Omega = 125.08 - 0.0529538083 * (date - j2000).days
# Apsidal precession - 8.85 year cycle
omega = 318.15 + 0.1643573223 * (date - j2000).days
The static version in idealized_orbits.py, planetary_params uses fixed values:
'Moon': {
'a': 0.002570, # Fixed semi-major axis
'e': 0.0549, # Fixed eccentricity
'i': 5.145, # Fixed inclination
'omega': 318.15, # Fixed argument of periapsis
'Omega': 125.08 # Fixed longitude of ascending node
}
The Moon experiences significant perturbations that the static model ignores:
# Mean anomalies for perturbation calculations
M_moon = (134.963 + 13.064993 * d) % 360 # Moon's mean anomaly
M_sun = (357.529 + 0.98560028 * d) % 360 # Sun's mean anomaly
D = (297.850 + 12.190749 * d) % 360 # Mean elongation
# Evection - largest lunar perturbation (~1.3° amplitude)
e_evection = 0.01098 * np.cos(2*D_rad - M_moon_rad)
e = e_base + e_evection
The Moon's ascending node moves backwards ~19.3°/year, completing a cycle in 18.6 years.
The Moon's line of apsides (perigee-apogee line) rotates ~40.7°/year, completing a cycle in 8.85 years.
Both functions use the same standard rotation sequence:
# 1. Argument of periapsis (ω) around z-axis
x_temp, y_temp, z_temp = rotate_points(x_orbit, y_orbit, z_orbit, omega_rad, 'z')
# 2. Inclination (i) around x-axis
x_temp, y_temp, z_temp = rotate_points(x_temp, y_temp, z_temp, i_rad, 'x')
# 3. Longitude of ascending node (Ω) around z-axis
x_final, y_final, z_final = rotate_points(x_temp, y_temp, z_temp, Omega_rad, 'z')
The rotation mechanics are identical - the accuracy improvement comes from using the correct, time-varying orbital elements rather than any special rotation technique.
The Moon's proximity to Earth makes it subject to strong perturbations that cause rapid changes in its orbital elements:
Node regression: ~19.3°/year change in Ω
Apsidal precession: ~40.7°/year change in ω
Evection: Periodic changes in eccentricity with ~31.8-day period
Solar perturbations: Additional periodic variations
For comparison, planetary orbits change much more slowly, so static elements work reasonably well for short time periods.
The time-varying approach produces an orbit that:
Correctly shows the Moon's changing orientation in space
Accounts for the precession of its orbital plane
Includes the major periodic perturbations
Matches JPL ephemeris data much more closely
This is why in idealized_orbits.py, plot_moon_ideal_orbit creates a "highly accurate ideal orbit" - it's using a sophisticated perturbation model rather than simple Keplerian elements.