Animate a point moving along path between two points

前端 未结 1 1334
长发绾君心
长发绾君心 2021-01-03 05:11

I want to animate a point moving along a path from one location to another on the map.

For example, I drawn a path from New York to New Delhi, using Geodetic transfo

相关标签:
1条回答
  • 2021-01-03 06:04

    There are a couple of approaches to this.

    The matplotlib approach

    I'll start with perhaps the most basic if you are familiar with matplotlib, but this approach suffers from indirectly using cartopy's functionality, and is therefore harder to configure/extend.

    There is a private _get_transformed_path method on a Line2D object (the thing that is returned from plt.plot). The resulting TransformedPath object has a get_transformed_path_and_affine method, which basically will give us the projected line (in the coordinate system of the Axes being drawn).

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

    We can pull this together with matplotlib's animation functionality to do as requested:

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

    The matplotlib way

    The cartopy approach

    Under the hood, cartopy's matplotlib implementation (as used above), is calling the project_geometry method. We may as well make use of this directly as it is often more convenient to be using Shapely geometries than it is matplotlib Paths.

    With this approach, we simply define a shapely geometry, and then construct the source and target coordinate reference systems that we want to convert the geometry from/to:

    target_cs.project_geometry(geometry, source_cs)
    

    The only thing we have to watch out for is that the result can be a MultiLineString (or more generally, any Multi- geometry type). However, in our simple case, we don't need to deal with that (incidentally, the same was true of the simple Path returned in the first example).

    The code to produce a similar plot to above:

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

    The cartopy way

    Final remaaaaarrrrrrks....

    The approach naturally generalises to animating any type of matplotlib Arrrrtist.... in this case, I took a bit more control over the great circle resolution, and I animated an image along the great circle:

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

    Arrrr, here be pirates!

    All code and images for this answer can be found at https://gist.github.com/pelson/618a5f4ca003e56f06d43815b21848f6.

    0 讨论(0)
提交回复
热议问题