Earth–Mars Orbital Transfer Animation Python Script

This Python script animates a simplified simulation of Earth and Mars orbiting the Sun along circular paths, while a spacecraft travels between them using a basic interpolation trajectory with a sinusoidal arc. It dynamically updates the planet positions and labels to clearly indicate which is Earth (blue) and which is Mars (red).

This script uses NumPy for efficient numerical computations and trigonometric functions, and Matplotlib (including mpl_toolkits.mplot3d for 3D rendering and matplotlib.animation for dynamic visualizations) to animate the planetary orbits and spacecraft trajectory. To install these libraries, open your Terminal and run:

pip install numpy matplotlib

(Use pip3 if you’re working with Python 3.)

Click to view script…
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D  # needed for 3D projection
from matplotlib.animation import FuncAnimation

# -----------------------------------------
# 1) Orbital and Planetary Parameters
# -----------------------------------------
# Simplified units: 1 "unit" = 1 Astronomical Unit (AU)
# Time is in days

# Orbital radii (approx in AU)
R_EARTH = 1.0
R_MARS  = 1.52

# Orbital periods (approx in days)
T_EARTH = 365.0
T_MARS  = 687.0

# Angular velocities (radians per day)
w_EARTH = 2.0 * np.pi / T_EARTH
w_MARS  = 2.0 * np.pi / T_MARS

# Initial phases (starting positions)
phi_earth0 = 0.0
phi_mars0  = 0.0

# Radii of the planets (for plotting spheres)
EARTH_RADIUS = 0.05
MARS_RADIUS  = 0.03

# -----------------------------------------
# 2) Define Planet Position Functions
# -----------------------------------------
def planet_position(time, R, omega, phi0=0.0):
    """
    Returns the (x, y, z) position of a planet orbiting in the XY plane.
    """
    x = R * np.cos(omega * time + phi0)
    y = R * np.sin(omega * time + phi0)
    z = 0.0
    return np.array([x, y, z])

# -----------------------------------------
# 3) Spacecraft Trajectory
# -----------------------------------------
def find_launch_day(t_range):
    """
    Finds a launch day in t_range where the Earth-Mars distance is minimized.
    """
    best_day = None
    min_dist = 1e9
    for t in t_range:
        earth_pos = planet_position(t, R_EARTH, w_EARTH, phi_earth0)
        mars_pos  = planet_position(t, R_MARS,  w_MARS,  phi_mars0)
        dist = np.linalg.norm(mars_pos - earth_pos)
        if dist < min_dist:
            min_dist = dist
            best_day = t
    return best_day

def spacecraft_trajectory(t, t_launch, t_arrival, home_func, target_func):
    """
    Computes a simple interpolated trajectory between two planets.
    Outside the travel window, it holds the departure or arrival position.
    """
    if t <= t_launch:
        return home_func(t_launch)
    elif t >= t_arrival:
        return target_func(t_arrival)
    else:
        # Fraction of travel completed
        frac = (t - t_launch) / (t_arrival - t_launch)
        pos_home = home_func(t_launch)
        pos_target = target_func(t_arrival)
        # Add a sinusoidal 'arc' in the Z direction for visual flair
        arc_height = 0.2 * np.sin(np.pi * frac)
        interp = (1 - frac) * pos_home + frac * pos_target
        interp[2] += arc_height
        return interp

# -----------------------------------------
# 4) Set Up the Animation Plot
# -----------------------------------------
fig = plt.figure(figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')

# Function to draw a sphere (used for Earth and Mars)
def plot_sphere(ax, center, radius, color, alpha=1.0):
    u, v = np.mgrid[0:2*np.pi:20j, 0:np.pi:10j]
    x = center[0] + radius * np.cos(u) * np.sin(v)
    y = center[1] + radius * np.sin(u) * np.sin(v)
    z = center[2] + radius * np.cos(v)
    ax.plot_surface(x, y, z, color=color, alpha=alpha)

# Global variables to store our plot elements
earth_surf = None
mars_surf = None
spacecraft_marker, = ax.plot([], [], [], 'go', markersize=6)
earth_label = None
mars_label = None

# Time settings for the simulation (e.g., 1200 days)
total_frames = 1000
times = np.linspace(0, 1200, total_frames)

# Find a simplistic launch day within the first 300 days
launch_day = find_launch_day(range(300))
travel_time = 180.0  # days for Earth-to-Mars travel
arrival_day = launch_day + travel_time

# Return trip parameters (optional)
stay_time_on_mars = 100.0
return_launch_day = arrival_day + stay_time_on_mars
return_arrival_day = return_launch_day + 200.0

# Utility to get the x, y, z coordinates for a sphere surface
def _sphere_xyz(center, radius, n_u=20, n_v=10):
    u = np.linspace(0, 2*np.pi, n_u)
    v = np.linspace(0, np.pi, n_v)
    u, v = np.meshgrid(u, v)
    x = center[0] + radius * np.cos(u) * np.sin(v)
    y = center[1] + radius * np.sin(u) * np.sin(v)
    z = center[2] + radius * np.cos(v)
    return x, y, z

# -----------------------------------------
# 5) Animation Update Function
# -----------------------------------------
def update(frame):
    global earth_surf, mars_surf, earth_label, mars_label

    # Current time in days
    t = times[frame]

    # Update positions for Earth and Mars
    earth_pos = planet_position(t, R_EARTH, w_EARTH, phi_earth0)
    mars_pos  = planet_position(t, R_MARS,  w_MARS,  phi_mars0)

    # Remove old spheres if they exist
    if earth_surf is not None:
        earth_surf.remove()
    if mars_surf is not None:
        mars_surf.remove()

    # Plot new spheres for Earth and Mars
    earth_surf = ax.plot_surface(*_sphere_xyz(earth_pos, EARTH_RADIUS),
                                 color='blue', alpha=0.6)
    mars_surf = ax.plot_surface(*_sphere_xyz(mars_pos, MARS_RADIUS),
                                color='red', alpha=0.6)
    
    # Remove old labels if they exist
    if earth_label is not None:
        earth_label.remove()
    if mars_label is not None:
        mars_label.remove()
    
    # Add new text labels above each planet
    earth_label = ax.text(earth_pos[0], earth_pos[1], earth_pos[2] + EARTH_RADIUS + 0.05,
                          'Earth', color='blue', fontsize=10, weight='bold')
    mars_label = ax.text(mars_pos[0], mars_pos[1], mars_pos[2] + MARS_RADIUS + 0.05,
                         'Mars', color='red', fontsize=10, weight='bold')
    
    # Update spacecraft position based on current time
    if t < return_launch_day:
        # Outbound: Earth to Mars
        sc_pos = spacecraft_trajectory(t, launch_day, arrival_day,
                                       lambda tau: planet_position(tau, R_EARTH, w_EARTH, phi_earth0),
                                       lambda tau: planet_position(tau, R_MARS,  w_MARS,  phi_mars0))
    else:
        # Return: Mars to Earth
        sc_pos = spacecraft_trajectory(t, return_launch_day, return_arrival_day,
                                       lambda tau: planet_position(tau, R_MARS, w_MARS, phi_mars0),
                                       lambda tau: planet_position(tau, R_EARTH, w_EARTH, phi_earth0))
    
    # Update the spacecraft marker (green dot)
    spacecraft_marker.set_data([sc_pos[0]], [sc_pos[1]])
    spacecraft_marker.set_3d_properties([sc_pos[2]])
    
    # Return all updated artists
    return earth_surf, mars_surf, spacecraft_marker, earth_label, mars_label

# -----------------------------------------
# 6) Plot Aesthetics and Animation Setup
# -----------------------------------------
ax.set_xlim(-1.6, 1.6)
ax.set_ylim(-1.6, 1.6)
ax.set_zlim(-0.6, 0.6)
ax.set_xlabel('X (AU)')
ax.set_ylabel('Y (AU)')
ax.set_zlabel('Z')
ax.set_title('Earth–Mars Orbits with Spacecraft Launch')

# Draw the Sun at the origin as a yellow sphere
plot_sphere(ax, [0, 0, 0], 0.1, 'yellow', alpha=0.9)

# Create the animation
anim = FuncAnimation(fig, update, frames=total_frames, interval=30, blit=False)

plt.show()