Question

The code below generates a animated basemap, but not exactly the one I want: I want the scatterplot from the previous frame to disappear, but it persists through the remainder of the animation.

I suspect it has something to do with my not understanding what the basemap really is. I understand calling it on lat/lons to project them to x/y, but I don't entirely get what's going on when I call event_map.scatter().

import random
import os
import numpy as np

import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap
from matplotlib import animation
import pandas as pd
from IPython.display import HTML


# Enables animation display directly in IPython 
#(http://jakevdp.github.io/blog/2013/05/12/embedding-matplotlib-animations/)
from tempfile import NamedTemporaryFile

VIDEO_TAG = """<video controls>
 <source src="data:video/x-m4v;base64,{0}" type="video/mp4">
 Your browser does not support the video tag.
</video>"""

def anim_to_html(anim):
    if not hasattr(anim, '_encoded_video'):
        with NamedTemporaryFile(suffix='.mp4') as f:
            anim.save(f.name, fps=20, extra_args=['-vcodec', 'libx264'])
            video = open(f.name, "rb").read()
        anim._encoded_video = video.encode("base64")

    return VIDEO_TAG.format(anim._encoded_video)

def display_animation(anim):
    plt.close(anim._fig)
    return HTML(anim_to_html(anim))

animation.Animation._repr_html_ = anim_to_html



FRAMES = 20
POINTS_PER_FRAME = 30
LAT_MIN = 40.5
LAT_MAX = 40.95
LON_MIN = -74.15
LON_MAX = -73.85
FIGSIZE = (10,10)
MAP_BACKGROUND = '.95'
MARKERSIZE = 20

#Make Sample Data
data_frames = {}
for i in range(FRAMES):
    lats = [random.uniform(LAT_MIN, LAT_MAX) for x in range(POINTS_PER_FRAME)]
    lons = [random.uniform(LON_MIN, LON_MAX) for x in range(POINTS_PER_FRAME)]
    data_frames[i] = pd.DataFrame({'lat':lats, 'lon':lons})    


class AnimatedMap(object):
    """ An animated scatter plot over a basemap"""
    def __init__(self, data_frames):
        self.dfs = data_frames
        self.fig = plt.figure(figsize=FIGSIZE)
        self.event_map = Basemap(projection='merc', 
                resolution='i', area_thresh=1.0, # Medium resolution
                lat_0 = (LAT_MIN + LAT_MAX)/2, lon_0=(LON_MIN + LON_MAX)/2, # Map center 
                llcrnrlon=LON_MIN, llcrnrlat=LAT_MIN, # Lower left corner
                urcrnrlon=LON_MAX, urcrnrlat=LAT_MAX) # Upper right corner 
        self.ani = animation.FuncAnimation(self.fig, self.update, frames=FRAMES, interval=1000, 
                                           init_func=self.setup_plot, blit=True,
                                           repeat=False)

    def setup_plot(self):
        self.event_map.drawcoastlines() 
        self.event_map.drawcounties()
        self.event_map.fillcontinents(color=MAP_BACKGROUND) # Light gray
        self.event_map.drawmapboundary()
        self.scat = self.event_map.scatter(x = [], y=[], s=MARKERSIZE,marker='o', zorder=10) 
        return self.scat

    def project_lat_lons(self, i):
        df = data_frames[i]
        x, y = self.event_map(df.lon.values, df.lat.values)
        x_y = pd.DataFrame({'x': x, 'y': y}, index=df.index)
        df = df.join(x_y)
        return df

    def update(self, i):
        """Update the scatter plot."""
        df = self.project_lat_lons(i)
        self.scat = self.event_map.scatter(x = df.x.values, y=df.y.values,  marker='o', zorder=10)  
        return self.scat,


s = AnimatedMap(data_frames)
s.ani
Was it helpful?

Solution

It looks like you're simply adding a new scatter plot at each update. What you should do instead is change the data in the existing path collection at each update. Try something along the lines of

def update(self, i):
    """Update the scatter plot."""
    df = self.project_lat_lons(i)
    new_offsets = np.vstack([df.x.values, df.y.values]).T
    self.scat.set_offsets(new_offsets)
    return self.scat,

Note that I haven't tested this.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top