为沿两点之间的路径移动的点设置动画

Animate a point moving along path between two points

我想为地图上的一个点沿路径从一个位置移动到另一个位置设置动画。

例如,我使用大地变换绘制了一条从纽约到新德里的路径。例如。摘自文档 Adding data to the map

plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat],
     color='blue', linewidth=2, marker='o',
     transform=ccrs.Geodetic(),
     )

现在我想沿着这条路径移动一个点。

我的想法是以某种方式沿着路径获取一些(比如 50 个)点,并在每个帧的每个点上绘制一个标记。但是我无法找到一种方法来获取路径上的点。

我在 classCRS 下找到了一个函数 transform_points,但我无法使用它,因为它给了我相同数量的点数,而不是之间。

提前致谢!

有几种方法可以解决这个问题。

matplotlib 方法

如果您熟悉 matplotlib,我将从最基本的开始,但这种方法会间接使用 cartopy 的功能,因此更难 configure/extend。

Line2D 对象(从 plt.plot 返回的东西)上有一个私有 _get_transformed_path 方法。生成的 TransformedPath 对象有一个 get_transformed_path_and_affine 方法,它基本上会给我们投影线(在正在绘制的 Axes 的坐标系中)。

In [1]: import cartopy.crs as ccrs

In [3]: import matplotlib.pyplot as plt

In [4]: ax = plt.axes(projection=ccrs.Robinson())

In [6]: ny_lon, ny_lat = -75, 43

In [7]: delhi_lon, delhi_lat = 77.23, 28.61

In [8]: [line] = plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat],
   ...:          color='blue', linewidth=2, marker='o',
   ...:          transform=ccrs.Geodetic(),
   ...:          )

In [9]: t_path = line._get_transformed_path()

In [10]: path_in_data_coords, _ = t_path.get_transformed_path_and_affine()

In [11]: path_in_data_coords.vertices
Out[11]: 
array([[-6425061.82215208,  4594257.92617961],
       [-5808923.84969279,  5250795.00604155],
       [-5206753.88613758,  5777772.51828996],
       [-4554622.94040482,  6244967.03723341],
       [-3887558.58343227,  6627927.97123701],
       [-3200922.19194864,  6932398.19937816],
       [-2480001.76507805,  7165675.95095855],
       [-1702269.5101901 ,  7332885.72276795],
       [ -859899.12295981,  7431215.78426759],
       [   23837.23431173,  7453455.61302756],
       [  889905.10635756,  7397128.77301289],
       [ 1695586.66856764,  7268519.87627204],
       [ 2434052.81300274,  7073912.54130764],
       [ 3122221.22299409,  6812894.40443648],
       [ 3782033.80448001,  6478364.28561403],
       [ 4425266.18173684,  6062312.15662039],
       [ 5049148.25986903,  5563097.6328901 ],
       [ 5616318.74912886,  5008293.21452795],
       [ 6213232.98764984,  4307186.23400115],
       [ 6720608.93929235,  3584542.06839575],
       [ 7034261.06659143,  3059873.62740856]])

我们可以将其与 matplotlib 的动画功能结合起来按要求执行:

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

ax = plt.axes(projection=ccrs.Robinson())
ax.stock_img()

ny_lon, ny_lat = -75, 43
delhi_lon, delhi_lat = 77.23, 28.61

[line] = plt.plot([ny_lon, delhi_lon], [ny_lat, delhi_lat],
         color='blue', linewidth=2, marker='o',
         transform=ccrs.Geodetic(),
         )

t_path = line._get_transformed_path()
path_in_data_coords, _ = t_path.get_transformed_path_and_affine()


# Draw the point that we want to animate.
[point] = plt.plot(ny_lon, ny_lat, marker='o', transform=ax.projection)

def animate_point(i):
    verts = path_in_data_coords.vertices
    i = i % verts.shape[0]
    # Set the coordinates of the line to the coordinate of the path.
    point.set_data(verts[i, 0], verts[i, 1])

ani = animation.FuncAnimation(
    ax.figure, animate_point,
    frames= path_in_data_coords.vertices.shape[0],
    interval=125, repeat=True)

ani.save('point_ani.gif', writer='imagemagick')
plt.show()

cartopy 方法

在幕后,cartopy 的 matplotlib 实现(如上所用)正在调用 project_geometry 方法。我们不妨直接使用它,因为使用 Shapely 几何图形通常比使用 matplotlib 路径更方便。

使用这种方法,我们只需定义一个形状几何体,然后构造我们要转换几何体的源和目标坐标参考系统from/to:

target_cs.project_geometry(geometry, source_cs)

我们唯一需要注意的是结果可以是 MultiLineString(或者更一般地说,任何多几何类型)。然而,在我们的简单情况下,我们不需要处理它(顺便说一句,第一个示例中返回的简单 Path 也是如此)。

生成与上面类似的图的代码:

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


ax = plt.axes(projection=ccrs.Robinson())
ax.stock_img()

ny_lon, ny_lat = -75, 43
delhi_lon, delhi_lat = 77.23, 28.61


line = sgeom.LineString([[ny_lon, ny_lat], [delhi_lon, delhi_lat]])

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

# We only animate along one of the projected lines.
if isinstance(projected_line, sgeom.MultiLineString):
    projected_line = projected_line.geoms[0]

ax.add_geometries(
    [projected_line], ccrs.PlateCarree(),
    edgecolor='blue', facecolor='none')

[point] = plt.plot(ny_lon, ny_lat, marker='o', transform=ccrs.PlateCarree())


def animate_point(i):
    verts = np.array(projected_line.coords)
    i = i % verts.shape[0]
    # Set the coordinates of the line to the coordinate of the path.
    point.set_data(verts[i, 0], verts[i, 1])

ani = animation.FuncAnimation(
    ax.figure, animate_point,
    frames=len(projected_line.coords),
    interval=125, repeat=True)

ani.save('projected_line_ani.gif', writer='imagemagick')
plt.show()

最终 remaaaaarrrrrrrks....

该方法自然而然地概括为对任何类型的 matplotlib Arrrrtist 进行动画处理....在这种情况下,我对大圆分辨率进行了更多控制,并沿大圆制作了图像:

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


ax = plt.axes(projection=ccrs.Mercator())
ax.stock_img()

line = sgeom.LineString([[-5.9845, 37.3891], [-82.3666, 23.1136]])


# Higher resolution version of Mercator. Same workaround as found in
# https://github.com/SciTools/cartopy/issues/8#issuecomment-326987465.
class HighRes(ax.projection.__class__):
    @property
    def threshold(self):
        return super(HighRes, self).threshold / 100


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

# We only animate along one of the projected lines.
if isinstance(projected_line, sgeom.MultiLineString):
    projected_line = projected_line.geoms[0]

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


def ll_to_extent(x, y, ax_size=(4000000, 4000000)):
    """
    Return an image extent in centered on the given
    point with the given width and height.

    """
    return [x - ax_size[0] / 2, x + ax_size[0] / 2,
            y - ax_size[1] / 2, y + ax_size[1] / 2]


# Image from https://pixabay.com/en/sailing-ship-boat-sail-pirate-28930/.
pirate = plt.imread('pirates.png')
img = ax.imshow(pirate, extent=ll_to_extent(0, 0), transform=ax.projection, origin='upper')

ax.set_global()


def animate_ship(i):
    verts = np.array(projected_line.coords)
    i = i % verts.shape[0]

    # Set the extent of the image to the coordinate of the path.
    img.set_extent(ll_to_extent(verts[i, 0], verts[i, 1]))


ani = animation.FuncAnimation(
    ax.figure, animate_ship,
    frames=len(projected_line.coords),
    interval=125, repeat=False)

ani.save('arrrr.gif', writer='imagemagick')
plt.show()

此答案的所有代码和图片都可以在 https://gist.github.com/pelson/618a5f4ca003e56f06d43815b21848f6 找到。