【问题标题】:Animate a point moving along path between two points为沿两点之间的路径移动的点设置动画
【发布时间】:2018-07-23 15:11:26
【问题描述】:

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

例如,我使用大地变换绘制了一条从纽约到新德里的路径。例如。取自文档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,但我无法使用它,因为这给了我相同数量的点数,而不是两者之间的点数。

提前致谢!

【问题讨论】:

    标签: cartopy


    【解决方案1】:

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

    matplotlib 方法

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

    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 路径更方便。

    使用这种方法,我们只需定义一个形状匀称的几何图形,然后构建我们想要转换几何图形的源和目标坐标参考系:

    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()
    

    最后的remaaaaaarrrrrrks....

    该方法自然地推广到为任何类型的 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找到。

    【讨论】:

    • 感谢您提供详细且解释清楚的答案。这些也可以用于 matplotlib 绘制的几乎所有类型的线!只是添加信息,我找到了一种更直接的方法来使用pyproj获取大圆上的点。
    猜你喜欢
    • 2017-09-29
    • 2015-07-15
    • 1970-01-01
    • 2012-07-14
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 1970-01-01
    • 2011-09-28
    相关资源
    最近更新 更多