动画两点之间的点以及旋转的地球

问题描述 投票:1回答:1

这是this question的扩展,只有这一点正在移动。

现在我想移动地球,以及动画点,使移动点始终位于中心。例如:

现在我可以使用此代码创建每个帧并将它们添加到一起以创建动画图像。

import matplotlib.pyplot as plt
import cartopy.crs as ccrs

lonlats = np.array([[-73.134961, 40.789142],  [-75.46884485, 41.13443837],
  [-77.825617, 41.43196017],  [-80.20222645, 41.68077343],
  [-82.5953765, 41.88007994],  [-85.00155934, 42.02922872],
  [-87.4170967, 42.12772575],  [-89.83818577, 42.17524151],
  [-92.26094893, 42.17161608],  [-94.68148587, 42.11686169],
  [-97.09592644, 42.01116249],  [-99.50048253, 41.8548717],
  [-101.89149735, 41.6485061],  [-104.26549065, 41.39273816],
  [-106.61919861, 41.08838607],  [-108.94960746, 40.73640202],
  [-111.25398017, 40.33785904],  [-113.52987601, 39.89393695],
  [-115.7751629, 39.40590768],  [-117.98802295, 38.87512048],
  [-120.16695169, 38.3029872],  [-122.3107517, 37.6909682]])

for frame in range(0, len(lonlats)):
  ax = plt.axes(projection=ccrs.Orthographic(central_longitude=lonlats[frame][0], central_latitude=30))
  ax.background_img(name='BM', resolution='medium')
  line = plt.plot(lonlats[:frame + 1, 0], lonlats[:frame + 1, 1], color='red', transform=ccrs.PlateCarree())[0]
  plt.savefig(f'img{frame:03}.png')
  #print(f'img{frame:03}.png')
  plt.close()

有没有办法只在不保存图像的情况下在绘图窗口中获取此动画?

python-3.x matplotlib cartopy
1个回答
2
投票

在我们开始之前,重要的是要记住,cartopy的强项在于处理预测数据。无论正交投影看起来多么3D,它实际上只是数据的2D表示 - matplotlib和cartopy的matplotlib界面从根本上在2D空间中运行,因此必须计算任何3D外观(在CPU上,而不是GPU)每个角度。例如,如果将某些海岸线投影到“正交投影”,然后想要稍微旋转“正交投影”,则需要再次执行所有投影计算。在3d中做这样的事情(使用类似OpenGL的东西)是可以想象的,但是在cartopy / matpltolib中没有任何东西可以让你可以使用现成的东西。

好吧,请注意,或许最值得关注的问题是:cartopy的GeoAxes是专为单个不可变的投影而设计的。没有API允许您在实例化后更改投影。因此,我们唯一能做的就是为每次旋转创建一个新的GeoAxes。

实现这样的事情的模式可能类似于:

def decorate_axes(ax):
    ax.coastlines()
    ...

def animate(i):
    ax = plt.gca()
    ax.remove()

    ax = plt.axes(projection=...)
    decorate_axes(ax)

ani = animation.FuncAnimation(
    plt.gcf(), animate,
    ...)

快速概念证明:

import cartopy.crs as ccrs
import matplotlib.animation as animation
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6, 6))


def decorate_axes(ax):
    ax.set_global()
    ax.coastlines()


def animate(i):
    lon = i

    ax = plt.gca()
    ax.remove()

    ax = plt.axes([0, 0, 1, 1], projection=ccrs.Orthographic(
        central_latitude=0, central_longitude=lon))
    decorate_axes(ax)


ani = animation.FuncAnimation(
    plt.gcf(), animate,
    frames=np.linspace(0, 360, 40),
    interval=125, repeat=False)

ani.save('poc.gif', writer='imagemagick', dpi=plt.gcf().dpi)

Rotating globe

所以现在我们已经有了基础知识,让我们建立在the answer you referred to的基础上,使用我们上面开发的模式,根据大圆的路径为正投影的旋转设置动画...

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.animation as animation
import matplotlib.image as mimage
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
import numpy as np
import shapely.geometry as sgeom


plt.figure(figsize=(6, 6))

line = sgeom.LineString([[0, 15], [-140, -40], [120, -20],
                         [0, -20], [-140, 15], [90, 45],
                         [0, 15]])


class HighResPC(ccrs.PlateCarree):
    @property
    def threshold(self):
        return super(HighResPC, self).threshold / 100


projected_line = HighResPC().project_geometry(line, ccrs.Geodetic())


verts = np.concatenate([np.array(l.coords) for l in projected_line])


def setup_axes(ax, x, y):
    ax.set_global()

    ax.add_feature(cfeature.LAND)
    ax.add_feature(cfeature.OCEAN)

    # Add the projected line to the map.
    ax.add_geometries(
        [projected_line], HighResPC(),
        edgecolor='blue', facecolor='none')

    # Image from http://madmen.wikia.com/wiki/File:Superman.gif.
    superman = plt.imread('superman.png')

    # Scale the actual image down a little.
    img_size = np.array(superman.shape) / 2

    x, y = ax.projection.transform_point(x, y, ccrs.PlateCarree())
    # Convert the projected coordinates into pixels.
    x_pix, y_pix = ax.transData.transform((x, y))

    # Make the extent handle the appropriate image size.
    extent = [x_pix - 0.5 * img_size[1], y_pix - 0.5 * img_size[0],
              x_pix + 0.5 * img_size[1], y_pix + 0.5 * img_size[0]]

    bbox = mtransforms.Bbox.from_extents(extent)
    img = mimage.BboxImage(bbox, zorder=10)
    img.set_data(superman)
    ax.add_artist(img)

    return img


def animate_superman(i):
    i = i % verts.shape[0]

    ax = plt.gca()
    ax.remove()

    ax = plt.axes([0, 0, 1, 1], projection=ccrs.Orthographic(
        central_latitude=verts[i, 1], central_longitude=verts[i, 0]))
    ax.coastlines()

    img = setup_axes(ax, verts[i, 0], verts[i, 1])


ani = animation.FuncAnimation(
    plt.gcf(), animate_superman,
    frames=verts.shape[0],
    interval=125, repeat=False)

ani.save('superman.gif', writer='imagemagick', dpi=plt.gcf().dpi)

Animated spinning globe using cartopy

我真正喜欢这个动画的是,当它们首次出现在地球上时,被跟随的大圆开始非常弯曲,并且当它们包括投影的中心点时它们变成直线。更一般地说,对于所有Azimuthal投影(其中Orthographic是一个),中心点的大圆总是直线...维基百科说:

因此,通过中心点的大圆圈在地图上用直线表示。

资料来源:https://en.wikipedia.org/wiki/Map_projection#Azimuthal_.28projections_onto_a_plane.29

这个动画有一些值得注意的问题:

  • 动画不是特别流畅:解决方案是使用自定义投影(HighResPC)的阈值来提高分辨率。
  • 地球不是以恒定速度旋转:解决方案可能是使用geographiclib工具根据特定步长(以米为单位)生成大圆
  • 动画的速度并不是特别快:虽然折叠可以以合理的帧速率处理投影的低分辨率海岸线,但图像转换的东西却没有(它根本没有针对性能进行优化)。因此,您无法在接近交互式动画所需频率的任何地方执行类似ax.stock_img()的操作。
© www.soinside.com 2019 - 2024. All rights reserved.