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).
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()