The transition to spring always gets me thinking. Last night, as I watched the first hints of blossoms on the trees, I felt a familiar wonder at the cycle of the seasons. I wanted to capture that feeling, but it was too late to call anyone.
Instead, I turned to the modern-day muses: ChatGPT and Gemini. One of them (I can't quite recall which!) proposed an animation explaining the science behind the seasons. Intrigued, I dove in, using the code generated by these AI companions as a starting point. I became a kind of digital conductor, orchestrating a collaboration between two AIs, asking one to critique and improve the other's work while I added my own touches and corrections. The challenge was to see if I could create something beautiful and informative, even without pretending to be a coding expert.
Below, you'll find the code I ended up with and the animation it produces. I hope you enjoy it!
Earth Orbit and Seasons
Why We Have Seasons: A Tale of Tilt and Orbit
Imagine the Earth as a dancer, gracefully twirling on a slightly tilted stage as it circles a brilliant spotlight – the Sun. This tilt, a subtle 23.5 degrees from vertical, is the secret behind the magic of the seasons. It's not our distance from the Sun that matters most (the Earth's orbit is slightly elliptical, but the effect on temperature is minimal compared to the tilt). It's all about the angle at which sunlight strikes different parts of our planet.
Let's break it down, hemisphere by hemisphere:
Northern Hemisphere's Perspective:
- Summer Solstice (around June 21st): Our dancer leans towards the spotlight. The Northern Hemisphere is bathed in direct sunlight, receiving more concentrated energy. This means longer days, higher Sun in the sky, and warmer temperatures. The Sun's rays hit the Northern Hemisphere at a steeper angle, delivering more energy per unit area.
- Autumn Equinox (around September 22nd): The dancer stands upright momentarily. Both hemispheres receive roughly equal amounts of sunlight. Days and nights are approximately equal in length, and we experience the transition from summer's warmth to autumn's crispness.
- Winter Solstice (around December 21st): Now, our dancer leans away from the spotlight. The Northern Hemisphere receives sunlight at a shallow angle, spreading the energy over a larger area. This results in shorter days, a lower sun in the sky, and colder temperatures.
- Spring Equinox (around March 20th): The dancer returns to the upright position. Again, both hemispheres receive equal sunlight, and we transition from winter's chill to Spring's renewal. Days and nights are roughly equal in length.
Southern Hemisphere's Perspective: The Mirror Image
The Southern Hemisphere experiences the opposite effect. When the Northern Hemisphere is tilted towards the Sun (Northern Hemisphere summer), the Southern Hemisphere is tilted away (Southern Hemisphere winter). Think of it as the other half of our dancer – when one side leans in, the other leans out.
- Summer Solstice (around December 21st): The Southern Hemisphere basks in direct sunlight, enjoying long, warm days, while the Northern Hemisphere experiences winter.
- Autumnal Equinox (around March 20th): The Southern Hemisphere sees this equinox as the transition from summer to autumn
- Winter Solstice (around June 21st): The Southern Hemisphere receives slanted sunlight, experiencing short days and cold temperatures. The Northern Hemisphere is experiencing summer.
- Spring Equinox (around September 22nd): The Southern Hemisphere sees this equinox as the transition from winter to Spring
The Constant Dance:
This cycle repeats yearly, a continuous dance between the Earth and the Sun. The tilt of our planet's axis, combined with our orbit around the Sun, creates the beautiful and predictable rhythm of the seasons, which has shaped life on Earth for billions of years. The animation shows this dance, illustrating how the angle of sunlight changes throughout the year and how that angle determines which hemisphere is experiencing summer or winter. The illuminated and shaded portions of the Earth in the animation directly correspond to the amount of sunlight each hemisphere receives.
The Python code
This animation was created using Python, a powerful programming language often used in scientific computing. The code, shown below, leverages two key libraries:
- NumPy: Provides the mathematical tools to calculate positions, angles, and other numerical data needed for the simulation.
- Matplotlib: A versatile plotting library that allows us to draw the Earth, Sun, orbit, and sun rays, and to create the animation itself using
FuncAnimation
. The animation is saved as an MP4 video usingFFMpegWriter
. The shapes are created usingArc
andPolygon
.
The script simulates Earth's elliptical orbit around the Sun, taking into account the Earth's axial tilt (23.5 degrees), which is the primary cause of the seasons. It also models the Moon's orbit around the earth. The changing angle of sunlight, and thus the varying intensity of solar radiation, is what drives the seasonal changes we experience.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, FFMpegWriter
from matplotlib.patches import Arc, Polygon
# Constants defining the simulation parameters
a, b = 1.0, 0.98 # Semi-major and semi-minor axes of Earth's elliptical orbit
earth_radius = 0.12 # Earth's radius, relative to the plot size
tilt_rad = np.radians(23.5) # Earth's axial tilt in radians
axis_length = 0.18 # Length of the Earth's axis line, for visualization
moon_orbit_radius = 0.18 # Radius of the Moon's orbit around Earth
moon_orbit_freq = 13.368 # Moon's orbital frequency (number of orbits per Earth year)
# Month names (starting with December for alignment with the winter solstice)
months = ['Dec', 'Jan', 'Feb', 'Mar', 'Apr', 'May',
'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov']
# Season names and their corresponding angles in Earth's orbit
seasons = ['Winter Solstice', 'Spring Equinox', 'Summer Solstice', 'Autumn Equinox']
season_angles = [0, np.pi / 2, np.pi, 3 * np.pi / 2] # Angles in radians
# Angles for placing month labels around the orbit
month_angles = np.linspace(0, 2 * np.pi, 13) # 13 to include the starting point (0 and 2pi are the same)
# --- Figure Setup ---
fig, ax = plt.subplots(figsize=(10, 10)) # Create a 10x10 inch figure
ax.set_aspect('equal') # Ensure the x and y axes are scaled equally
ax.set_xlim(-1.6, 1.6) # Set x-axis limits
ax.set_ylim(-1.6, 1.6) # Set y-axis limits
ax.axis('off') # Turn off the axis lines and labels
title = ax.text(0.5, 0.98, "", transform=ax.transAxes, ha='center', va='top', fontsize=16) #Changed to be higher
# --- Drawing Static Elements ---
# Draw Earth's elliptical orbit
theta = np.linspace(0, 2 * np.pi, 500) # Angles for drawing the ellipse
ax.plot(a * np.cos(theta), b * np.sin(theta), '--', alpha=0.4, label="Earth Orbit")
# Draw the Sun
ax.plot(0, 0, 'yo', markersize=16, label="Sun")
y_offset = 0.05 # smaller moves labels slightly down
# Place month labels around the orbit
for i, angle in enumerate(month_angles[:-1]):
ax.text(1.25 * a * np.cos(angle), 1.25 * b * np.sin(angle),
months[i], ha='center', va='center', fontsize=10, alpha=0.7)
# Place season labels around the orbit
season_labels = []
for i, angle in enumerate(season_angles):
label = ax.text(
1.45 * a * np.cos(angle),
1.45 * b * np.sin(angle) - y_offset, #Position adjusted
seasons[i],
ha='center',
va='center',
fontsize=12,
color='darkgreen',
fontweight='bold'
)
season_labels.append(label)
# --- Dynamic Elements (will be updated in the animation) ---
# Earth (represented as a blue dot)
earth_dot, = ax.plot([], [], 'bo', markersize=earth_radius * 180, zorder=5)
# Earth's axis (represented as a line)
axis_line, = ax.plot([], [], 'k-', linewidth=2.5, zorder=5)
# Moon (represented as a gray dot)
moon_dot, = ax.plot([], [], 'o', color='gray', markersize=6, zorder=4)
# Arc to highlight the current month segment
highlight_arc = Arc((0, 0), 2*a, 2*b, color='red', linewidth=4, alpha=0.6)
ax.add_patch(highlight_arc) # Add the arc to the axes
# Sun rays (represented as polygons)
sun_rays = []
for _ in range(15):
ray = ax.fill([], [], color='yellow', alpha=0.08, zorder=1)[0]
sun_rays.append(ray)
# Hemisphere shading (light blue for illuminated, light coral for dark)
north_poly = ax.fill([], [], color='lightblue', alpha=0.5, label='Northern Hemisphere')[0]
south_poly = ax.fill([], [], color='lightcoral', alpha=0.5, label='Southern Hemisphere')[0]
# Show the legend
ax.legend(loc='lower right')
# --- Animation Functions ---
# Initialization function (called once at the beginning)
def init():
earth_dot.set_data([], [])
axis_line.set_data([], [])
moon_dot.set_data([], [])
highlight_arc.set_visible(False)
for ray in sun_rays:
ray.set_xy(np.empty((0, 2)))
north_poly.set_xy(np.empty((0,2)))
south_poly.set_xy(np.empty((0,2)))
title.set_text("")
for label in season_labels:
label.set_visible(True)
return [earth_dot, axis_line, moon_dot, highlight_arc] + sun_rays + [north_poly, south_poly, title] + season_labels
# Update function (called for each frame)
def update(frame):
# Calculate Earth's position in its orbit
angle = 2 * np.pi * frame / 365 # One frame per day
ex, ey = a * np.cos(angle), b * np.sin(angle)
# Calculate Earth's axis tilt
tilt_angle = np.pi / 2 - tilt_rad
dx = axis_length * np.cos(tilt_angle)
dy = axis_length * np.sin(tilt_angle)
# Update Earth and axis positions
earth_dot.set_data(ex, ey)
axis_line.set_data([ex - dx, ex + dx], [ey - dy, ey + dy])
# Update Moon's position
moon_angle = moon_orbit_freq * angle
mx, my = ex + moon_orbit_radius * np.cos(moon_angle), ey + moon_orbit_radius * np.sin(moon_angle)
moon_dot.set_data(mx, my)
# Highlight the current month segment
month_idx = int((angle % (2 * np.pi)) / (2 * np.pi) * 12)
arc_start = np.degrees(month_angles[month_idx])
arc_end = np.degrees(month_angles[month_idx + 1])
highlight_arc.theta1, highlight_arc.theta2 = arc_start, arc_end
highlight_arc.set_visible(True)
# Update sun ray positions and shapes
for i, ray in enumerate(sun_rays):
offset = (i - len(sun_rays) / 2) * 0.02
ray_length = np.sqrt(ex**2 + ey**2) * (1.0 + np.random.uniform(-0.05, 0.05))
ray_width = 0.08 + np.random.uniform(-0.02, 0.02)
x1, y1 = 0, 0
x2, y2 = ex + offset * ey, ey - offset * ex
x3, y3 = x2 + ray_width * (-ey + offset*ex), y2 + ray_width*(ex + offset * ey)
x4, y4 = x3 + (ray_length / np.sqrt(ex ** 2 + ey ** 2)) * (-ex - offset * ey), y3 + (ray_length / np.sqrt(ex ** 2 + ey ** 2)) * (-ey + offset * ex)
x5, y5 = x2 - ray_width*(-ey+offset*ex) + (ray_length/np.sqrt(ex**2+ey**2))*(-ex - offset * ey), y2 -ray_width*(ex+offset*ey) + (ray_length/np.sqrt(ex**2+ey**2))*(-ey + offset*ex)
ray.set_xy([[x1, y1], [x2, y2],[x5,y5], [x4, y4],[x3,y3]])
# Update the title with the current month
title.set_text(f"Earth Orbit & Seasons — Month: {months[month_idx]}")
# Calculate hemisphere illumination
sun_vec = np.array([-ex, -ey]) # Vector from Earth to Sun
sun_vec /= np.linalg.norm(sun_vec) # Normalize the vector
axis_vec = np.array([dx, dy]) # Earth's axis vector
axis_vec /= np.linalg.norm(axis_vec) # Normalize
dot = np.dot(sun_vec, axis_vec) # Dot product to determine which hemisphere is tilted towards the Sun
if dot > 0: # Northern Hemisphere summer
illum_center = np.arctan2(dy, dx)
else: # Southern Hemisphere summer
illum_center = np.arctan2(-dy, -dx)
illum_angles = np.linspace(illum_center - np.pi/2, illum_center + np.pi/2, 50)
dark_angles = illum_angles + np.pi
illum_x = [ex] + [ex + earth_radius * np.cos(a_) for a_ in illum_angles] + [ex]
illum_y = [ey] + [ey + earth_radius * np.sin(a_) for a_ in illum_angles] + [ey]
dark_x = [ex] + [ex + earth_radius * np.cos(a_) for a_ in dark_angles] + [ex]
dark_y = [ey] + [ey + earth_radius * np.sin(a_) for a_ in dark_angles] + [ey]
if dot > 0: # Northern Hemisphere summer
north_poly.set_xy(np.column_stack((illum_x, illum_y)))
south_poly.set_xy(np.column_stack((dark_x, dark_y)))
else: # Southern Hemisphere summer
south_poly.set_xy(np.column_stack((illum_x, illum_y)))
north_poly.set_xy(np.column_stack((dark_x, dark_y)))
return [earth_dot, axis_line, moon_dot, highlight_arc] + sun_rays + [north_poly, south_poly, title] + season_labels
# --- Create and Save Animation ---
# Create the animation
ani = FuncAnimation(fig, update, frames=365, init_func=init, blit=True, interval=20)
# Save the animation as an MP4 file using FFMpegWriter
#writer = FFMpegWriter(fps=30, metadata=dict(artist='Doradame'), bitrate=1800) #Added my name
#ani.save("earth_orbit_seasons.mp4", writer=writer)
plt.show()
Final Thoughts
This animation and its accompanying explanation were created with the support of several AI models, including OpenAI's GPT-4.5 and Google's Gemini 2.0 Flash and Gemini 2.0 Pro (Experimental). Although this wasn't a formal or repeatable benchmark, my experience showcased the impressive capabilities of Gemini 2.0 Pro, especially in addressing the more complex aspects of the project, such as the nuanced calculations for the sun's rays and hemisphere illumination.
Disclaimer: At MojaLab, we aim to provide accurate and useful content, but hey, we’re human (well, mostly)! If you spot an error, have questions, or think something could be improved, feel free to reach out—we’d love to hear from you. Use the tutorials and tips here with care, and always test in a safe environment. Happy learning!!!
No AI was mistreated in the making of this tutorial—every LLM was used with the respect it deserves.