0

I am trying to animate a GeoPandas map using dates (iterates through the day). I'm using MPL's FuncAnimation function and although there are no errors, there is no animation happening. Here is my code:

link = 'https://raw.githubusercontent.com/leakyMirror/map-of-europe/master/GeoJSON/europe.geojson'
europe = gpd.read_file(link)
europe_w_needed_countries = europe[europe.NAME.isin(train.country.unique())]

def create_gdf(date):
    a = new_train.query(f"date == '{date}'").groupby('country').sum().reset_index()
    b = europe_w_needed_countries[['LON', 'LAT', 'geometry']]
    b.index = a.index
    return gpd.GeoDataFrame(pd.concat([a,b], axis=1))

create_gdf('2017-01-01')
    country num_sold    LON LAT geometry
0   Belgium 3320    2.550   46.565  MULTIPOLYGON (((9.48583 42.61527, 9.49472 42.6...
1   France  2939    9.851   51.110  MULTIPOLYGON (((8.71026 47.69681, 8.67859 47.6...
2   Germany 3437    12.800  42.700  MULTIPOLYGON (((12.12778 47.00166, 12.13611 46...
3   Italy   2431    4.664   50.643  POLYGON ((4.30237 51.26318, 4.30968 51.26203, ...
4   Poland  1074    19.401  52.125  POLYGON ((18.85125 49.51736, 18.85222 49.52777...
5   Spain   2151    -3.649  40.227  MULTIPOLYGON (((-2.92528 35.26666, -2.93694 35...

For more context of this dataset, a is supposed to represent the amount of books sold in European countries from 2017 to 2020. The data is aggregated. b is a geojson-loaded Data Frame that displays the polygons of the countries. Both datasets are merged together, which is seen in the output code.

This is my code for the animation plot:

fig, ax = plt.subplots(figsize=(15,8))
date = '2017-01-01'
fontsize = 13

def animate(date):
    gdf = create_gdf(date)
    gdf.plot(ax=ax, 
             column='num_sold', 
             cmap='OrRd', 
             edgecolor='black', 
             legend=True, 
             legend_kwds={'label': 'Books Sold'})
    ax.set_title('Books Sold From 1/1/2017 - 12/31/2020')
    ax.set_axis_off()
    ax.text(20, 45, 'Date:\n{}'.format(date), fontsize=fontsize, horizontalalignment='center')
    
    for lon, lat, country in zip(gdf.LON, gdf.LAT, gdf.country):
        ax.text(lon - 1.5, lat, country, fontsize=fontsize)
    date += TimeDelta(1)
    
FuncAnimation(fig, update, interval=250, frames=new_train.index.unique())

As seen here, the map does not change. Also, I know the country names are in the wrong places, I'll change that later.

enter image description here

I don't know why this is happening, I followed other threads and articles and it never works.

javanewb1
  • 33
  • 5
  • I have no experience with Geopandas, but if you're running within a Jupyter notebook I'd try running it with a python script from a shell to see if that makes a difference. – Riley Sep 19 '22 at 23:34
  • animating patch collections, which are created by geopandas.plot, is complicated - you need to clear the axis before re-plotting each frame. see this: https://stackoverflow.com/questions/53093347/dynamically-update-plot-of-patches-without-artistanimations-in-matplotlib – Michael Delgado Sep 20 '22 at 05:16
  • 1
    would you consider a different plotting library? it's simple to animate using **plotly express** – Rob Raymond Sep 20 '22 at 05:53

2 Answers2

0

Animation of geopandas can be achieved with plot_polygon_collection(). I modified it to fit your code with this information. one difference from your image is that I added a string annotation to the graph area, but I could not delete the string in gdf.plotting, so I changed the title to start date and end date for equivalent functionality. Also, I couldn't add a color bar in the collection, so I added a new one. (I referenced it here) User data was created as a sample in a similar format.

import geopandas as gpd

link = 'https://raw.githubusercontent.com/leakyMirror/map-of-europe/master/GeoJSON/europe.geojson'
europe = gpd.read_file(link)
countries = ['Spain','France','Italy','Belgium','Germany','Poland']
europe_w_needed_countries = europe[europe.NAME.isin(countries)]

import pandas as pd
import numpy as np

df = pd.DataFrame({'date': np.tile(pd.date_range('2020-08-01', periods=30), 6),
                   'country': np.tile(countries, 30),
                   'num_sold': np.random.randint(1000,5000,180)})

df['date'] = pd.to_datetime(df['date']).dt.date

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from datetime import datetime

fig, ax = plt.subplots()
fontsize = 12

ims = []
t = []
dates = df['date'].unique()
vmin, vmax = df['num_sold'].min(),df['num_sold'].min()

def update_fig(i):
    if len(ims) > 0:
        del ims[0]
    geos = europe_w_needed_countries['geometry']
    num_sold = df[df['date'] == dates[i]]['num_sold'].tolist()
    artist = gpd.plotting.plot_polygon_collection(ax, geos, num_sold, cmap="Reds")
    ims.append(artist)
    #ax.text(20, 45, 'Date:\n{}'.format(dates[i]), fontsize=fontsize, horizontalalignment='center')
    for lon, lat, country in zip(europe_w_needed_countries.LON, europe_w_needed_countries.LAT, europe_w_needed_countries.NAME):
        ax.text(lon - 1.5, lat, country, fontsize=fontsize)
    ax.set_title('Books Sold From {} - {}'.format(dates[0], dates[i]))
    ax.set_axis_off()
    fig = ax.get_figure()
    cax = fig.add_axes([0.9, 0.1, 0.03, 0.8])
    sm = plt.cm.ScalarMappable(cmap='Reds', norm=plt.Normalize(vmin=vmin, vmax=vmax))
    # fake up the array of the scalar mappable. Urgh...
    sm._A = []
    fig.colorbar(sm, cax=cax)
    return ims

anim = FuncAnimation(fig, update_fig, interval=1000, repeat_delay=300, frames=len(df['date'].unique()))

fig.show()

enter image description here

r-beginners
  • 31,170
  • 3
  • 14
  • 32
0

Alternative approach, use plotly express Animation is built into the high level API

import geopandas as gpd
import pandas as pd
import numpy as np
import plotly.express as px

countries = ["Spain", "France", "Italy", "Belgium", "Germany", "Poland"]
europe = gpd.read_file(gpd.datasets.get_path("naturalearth_lowres")).loc[
    lambda d: d["name"].isin(countries)
]

df = pd.DataFrame(
    {
        "date": np.tile(pd.date_range("2020-08-01", periods=30), 6),
        "country": np.repeat(countries, 30),
        "num_sold": np.random.randint(1000, 5000, 180),
    }
)

px.choropleth_mapbox(
    df.assign(date_str=lambda d: d["date"].dt.strftime("%Y-%b-%d")),
    geojson=europe.set_index("name")["geometry"].__geo_interface__,
    locations="country",
    color="num_sold",
    color_continuous_scale='OrRd',
    animation_frame="date_str",
).update_layout(
    mapbox={
        "style": "carto-positron",
        "zoom": 2.5,
        "center": {
            "lat": europe.unary_union.centroid.y,
            "lon": europe.unary_union.centroid.x,
        },
    },
    coloraxis={"cmin": df["num_sold"].min(), "cmax": df["num_sold"].max()},
    autosize=False,
    width=600,
)

enter image description here

FYI I generated animated GIF using answer I provided here: animated GIF

Rob Raymond
  • 29,118
  • 3
  • 14
  • 30