Reputation: 39
I want to create an animation of a trajectory using Cartopy and FuncAnimation. I have the trajectory stored in a GeoDataFrame with latitude, longitude and date time columns, which I want to visualise.
The trajectory moves across the globe, but I want to zoom in and therefore aim to have the basemap to be updated depending on the extent. I want to use satellite imagery as basemap, and I am using GoogleTiles from cartopy.io.img_tiles.
However, the image seems to only load at the initial extent, and is not updated in the new frames. The same happens when using 'stock_img()'. Interestingly, features such as coastlines and borders do load. To visualise my problem, view the images attached.
My function:
# Function to animate with moving basemaps
def plot_animation_moving(gdf, zoom_level = 8, extent_margin = 4, tail_length = 50, frames_between_points = 10, interval=100):
tiler = GoogleTiles(style="satellite")
mercator = tiler.crs
# Set up the figure and axis
fig, ax = plt.subplots(figsize=(12, 12), subplot_kw={'projection': mercator})
# Initial extent
initial_extent = [gdf.iloc[0]['lon']-extent_margin, gdf.iloc[0]['lon']+extent_margin, gdf.iloc[0]['lat']-extent_margin, gdf.iloc[0]['lat']+extent_margin]
ax.set_extent(initial_extent)
# Add background and features
ax.add_image(tiler, zoom_level)
# ax.stock_img()
ax.add_feature(cfeature.BORDERS, linestyle=':')
ax.coastlines(resolution='10m')
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
ax.add_feature(cfeature.LAKES, alpha=0.5)
ax.add_feature(cfeature.RIVERS)
# Loop through each pair of points and create points in between
lats_list = []
lons_list = []
dates_list = []
for i in range(len(gdf)):
# create lat, lon and date sequences
...
lats_list.append(lats)
lons_list.append(lons)
dates_list.append(dates)
# Concatenate points
lats = np.concatenate(lats_list)
lons = np.concatenate(lons_list)
dates = np.concatenate(dates_list)
# Create a point with tail object on the Basemap
point, = ...
tail, = ...
date_text = ...
# Update the point position
def update(frame):
# Update point, tail and date
point.set_data([lons[frame]], [lats[frame]])
current_date = pd.to_datetime(dates[frame])
date_text.set_text(f'{current_date.strftime("%Y-%m-%d %H:00")}')
# Update the map extent to follow the line
ax.set_extent([lons[frame] - extent_margin, lons[frame] + extent_margin, lats[frame] - extent_margin, lats[frame] + extent_margin])
return point, tail, date_text
# Create the animation
ani = FuncAnimation(fig, update, frames=tqdm.tqdm(range(len(lats)), file=sys.stdout), blit=False, interval=interval),
ax.add_image(tiler, zoom_level) # --> doesn't help
ani.save("example.mp4")
In the update
function, I have tried adding the image again using ax.add_image(tiler, zoom_level)
but it doesn't seem to help.
My question therefore is: how do I correctly update the basemap image so that it loads when the point is moving across the globe?
Upvotes: 0
Views: 115
Reputation: 37857
You could be sneaky and go one level deeper by passing the tiler to imshow
at each frame :
def add_image(factory, *factory_args, **factory_kwargs):
img, extent, origin = factory.image_for_domain(
ax._get_extent_geom(factory.crs),
factory_args[0],
)
ax.imshow(
img,
extent=extent,
origin=origin,
transform=factory.crs,
*factory_args[1:],
**factory_kwargs
)
NB: You need to place add_image(tiler, zoom_level)
right before update's return.
Upvotes: 3