Creating a Rotating Earth GIF with Python, Pillow, NumPy, and imageio
This tutorial demonstrates how to generate a rotating Earth animation by mapping a latitude‑longitude texture onto a circular canvas using Python's Pillow, NumPy, and imageio libraries, detailing the steps of point selection, spherical coordinate conversion, pixel mapping, and GIF assembly.
The article explains how to create a simple yet common rotating Earth animation using a 360° latitude‑longitude texture and Python.
First, obtain a 360° equirectangular map where the height is half the width, representing the Earth's surface from pole to pole.
The rotation effect is achieved by projecting each pixel of the texture onto a circle, then stitching the resulting frames into a GIF.
The process consists of five steps: (1) select all points inside a circle, (2) compute the latitude and longitude for each point, (3) map those coordinates back to x‑y positions on the texture, (4) write the corresponding pixel colors to a new image, and (5) assemble the frames into a GIF.
Below is the code for selecting points inside the circle:
img = Image.new('RGBA', (300,300), 'black')
w = img.size[0]
h = img.size[1]
pxList = []
pyList = []
for i in range(w):
for j in range(h):
r = math.sqrt((i-w/2)**2 + (j-h/2)**2)
if r < 150:
pxList.append(i)
pyList.append(j)The function calcSphereXY2XYZ converts planar coordinates to spherical latitude and longitude using NumPy for speed:
def calcSphereXY2XYZ(px, py, maxHeight, longOffset):
v0x = np.array(px)
v0y = np.array(py)
v03 = np.subtract(v0x, maxHeight)
v04 = np.subtract(v0y, maxHeight)
v1x = np.true_divide(v03, maxHeight)
v1y = np.true_divide(v04, maxHeight)
v07 = np.power(v1x,2)
v08 = np.power(v1y,2)
v09 = np.add(v07, v08)
v0a = np.subtract(1, v09)
v1z = np.power(v0a, 1/2) # z
v1lat = np.multiply(v1y, math.pi/2) # lat
v0lon = np.arctan2(v1z, -v1x)
v1lon = np.add(v0lon, longOffset) # long
v2lon = np.fmod(v1lon, math.pi*2) # long
return v2lon, v1latThe function calcShpereLatLong2XY maps latitude‑longitude to image coordinates:
def calcShpereLatLong2XY(vlon, vlat, width, height):
v3x0 = np.multiply(vlon, width/2/math.pi)
v3y0 = np.multiply(vlat, height/math.pi)
v3y1 = np.add(v3y0, height/2)
v3x2 = v3x0.astype(np.integer)
v3y2 = v3y1.astype(np.integer)
return v3x2, v3y2After loading the texture and converting it to a NumPy array, the pixel colors are transferred to the circular image:
color = arrayBack[npy, npx]
for i in range(len(pxList)):
x = pxList[i]
y = pyList[i]
cc = color[i]
cc = tuple(cc)
img.putpixel((x, y), cc)Finally, the frames are saved and combined into an animated GIF using the imageio library:
frames = []
for i in range(0, 360, 10):
a = -i * math.pi / 180
img = getPic(a)
filename = 'temp%03d.png' % i
img.save(filename)
im = imageio.imread(filename)
frames.append(im)
imageio.mimsave('earth.gif', frames, 'GIF', duration=0.25)The article concludes with the full source code that integrates all the steps into a single script.
Python Programming Learning Circle
A global community of Chinese Python developers offering technical articles, columns, original video tutorials, and problem sets. Topics include web full‑stack development, web scraping, data analysis, natural language processing, image processing, machine learning, automated testing, DevOps automation, and big data.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.