如何用python实现旋转地球动态图

大家好,今天给大家分享一个如何用python实现旋转地球动态图编程实例。

先上效果图

640-31

素材

本文只讲最简单也是最常见的正侧面旋转地球。 首先你得有一张360度贴图,效果如下图。在网上搜一下,这种图很多,容易获得。 640-10 它的特点是高度恰好是宽度的一半,因为地球一周有360度,而从南极到北极只有180度。这种360度贴图,标明了每个经纬度上地球的表面效果。仔细观察会注意到,南北两极变形剧烈,而中间地带和我们经常见到的地图比较相似。 我们称它为经纬度贴图。

基本原理

旋转地球之所以看起来是旋转的,只是因为它上面显示的内容在变。其实起点只是一个圆。我们所做的,只是把经纬度贴图中的像素以合理的方式贴到圆上去。然后再把一帧帧图片连接为GIF动图即可。 贴图的方法是: 1、选择圆内的所有点。 2、计算每一点对应的经纬度。 3、从经纬度贴图上获取该经纬度对应的xy坐标 4、获取像素写入图片 5、制作GIF 计算过程是比较简单的球面几何,下面结合代码讲解。

1 获得圆内所有的点

这个比较简单
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)

2 计算经纬度

  1. 首先从平面坐标获得三维坐标 球面上的任意一点,有一个规律是x2 + y2 + z2 = 1 我们就用这个原理,可以获得圆内每个点的z值
  2. 根据x,y,z计算球面的经纬度
为了让计算速度尽量快一些,这里用numpy
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
# print("z:", max(v1z), min(v1z))
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, v1lat

3 获取经纬度对应的xy坐标

就是一个简单的坐标映射
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, v3y2
 

4 获得像素点,并写入图片

首先,载入经纬度贴图的素材并转为numpy格式
imgBack= Image.open("EARTH_small.bmp")
width= imgBack.size[0]
height= imgBack.size[1]
imgBack= imgBack.convert("RGBA")
arrayBack= np.array(imgBack)
 
注意转换后的XY是反的,取点并直接赋值即可
color= arrayBack[npy, npx]
for i in range(len(pxList)):
x= pxList[i]
y= pyList[i]
cc=color[i]
# print(cc)
cc= tuple(cc)
img.putpixel((x,y), cc)

5 GIF制作

使用的是imageio库
frames=[]
for i in range(0, 360, 10):
a= -i*math.pi/ 180
img= getPic(a)
str1= "temp%03d.png"%i
img.save(str1)
im = imageio.imread(str1)
frames.append(im)
# img.show()
imageio.mimsave("earth.gif", frames, "GIF", duration=0.25)

整代码

最后,贴一下完整代码。
from PIL import Image, ImageDraw
import math
import numpy as np
import imageio
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)
# print(max(v1x), min(v1x))
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
# print("z:", max(v1z), min(v1z))
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, v1lat
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, v3y2
def getPic(a):
# imgBack= Image.open("地球3.jpg")
imgBack= Image.open("EARTH_small.bmp")
width= imgBack.size[0]
height= imgBack.size[1]
imgBack= imgBack.convert("RGBA")
arrayBack= np.array(imgBack)
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)
nplon, nplat= calcSphereXY2XYZ(pxList, pyList, h/2, a)
npx, npy= calcShpereLatLong2XY(nplon, nplat, width-1, height)
color= arrayBack[npy, npx]
for i in range(len(pxList)):
x= pxList[i]
y= pyList[i]
cc=color[i]
# print(cc)
cc= tuple(cc)
img.putpixel((x,y), cc)
return img
if __name__=="__main__":
frames=[]
for i in range(0, 360, 10):
a= -i*math.pi/ 180
img= getPic(a)
str1= "temp%03d.png"%i
img.save(str1)
im = imageio.imread(str1)
frames.append(im)
# img.show()
imageio.mimsave("earth.gif", frames, "GIF", duration=0.25)