Pregunta

I am trying to create an animated plot of a series of points with lat/lon positions on a matplotlib.basemap map. Each point has a series of positions for a series of days, which I have read into a pandas DataFrame.

I've tried to modify the procedure used HERE to do this, but I am getting an error that global name 'points' is not defined. I've tried to declare this as a global within the init routine, but that didn't help.

How might I do this?

Example data:

day,id,      lon,      lat
156, 1, 67.53453, -4.00454
156, 2, 66.73453,  0.78454
156, 3, 68.23453, -1.01454
157, 1, 67.81453, -4.26454
157, 2, 66.42653,  0.91454
157, 3, 69.11253, -1.01454
158, 1, 68.12453, -3.26454
158, 2, 67.10053,  1.01454
158, 3, 68.01253, -2.61454

Calling routine:

if datafile != None:
    data = readdata(datafile)
    dates = np.unique(data.daynr).values

    x,y = m(0,0)
    point = m.plot(x,y, 'ro', markersize=5)[0]
    points = list()

    anim = animation.FuncAnimation(plt.gcf(), animate,
                                   init_func=init, frames=20,
                                   interval=500, blit=True)

    # Add current date/time or something to make unique
    anim.save('movement.mp4', fps=15,
              extra_args=['-vcodec', 'libx264'])

init, animate, and data reading routines:

def init():
    for pt in points:
        pt.set_data([], [])
    return points

def animate(i):
    lons = data.lons[data.daynr==dates[i]]
    lats = data.lats[data.daynr==dates[i]]

    i = 0
    for lon,lat, pt in zip(points, lons, lats):
        x, y = map(lon,lat)
        pt.set_data(x, y)
        i = i + 1
    return points

def readdata(datafile):
    dtypes = np.dtype([
                    ('daynr',int),      #00 - Simulation day number
                    ('id',int),         #01 - Id
                    ('lon',float),      #02 - Longitude
                    ('lat',float),      #03 - Latitude
                    ])
    f = open(datafile, 'rb')
    data = pd.read_csv(f, index_col=False, names=dtypes.names,
                           dtype=dtypes, header=None)
    f.close()
    return data
¿Fue útil?

Solución

So... my first problem was that I hadn't realized that variables within a function in python were not considered 'global' the functions that are called within it.

To get around this, I made my init() and animate(i) functions 'subfunctions', which then allowed variables declared in the parent function to be treated as global by the init() and animate(i) sub functions (see code below).

I found this blog article very helpful to arrive at my solution.

As was this SO question.

NOTE: I've edited my code a bit for the purpose of this answer, so please comment if this doesn't work properly for you.

My plotting function and the calling routine:

import pandas as pd
import numpy as np
import pyproj
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap


def makeplot(plot_data=False):
''' plot function with optional data animation
    if data is supplied (as a `pandas` DataFrame), subfuntions `init()` 
    and `animate(i)` will animate recurring, multiple position values 
    per unique day and save to file.'''

    def init():
        # initialize each plot point created in the list `points`
        for pt in points:
            pt.set_data([], [])
        return points

    def animate(i):
        #Only routine if `i` doesn't exceed number of unique days to animate
        if i < len(data_dates):
            print 'Animation frame:', i, '; Simulation Day:', data_dates[i]

            lons = data.lons[data.daynr==dates[i]].values
            lats = data.lats[data.daynr==dates[i]].values

            j = 0
            for pt,lon,lat in zip(points, lons, lats):
                x, y = m(lon,lat)
                pt.set_data(x, y)
                j = j + 1
        return points

    # Define ellipsoid object for distance measurements
    g = pyproj.Geod(ellps='WGS84') # Use WGS84 ellipsoid
    r_equator = g.a # earth's radius at equator
    r_poles = g.b   # earth's radius through poles

    lon0, lat0, map_width, map_height = center_map(poly_lons, poly_lats, 1.1)

    m = Basemap(width=map_width,height=map_height,
                rsphere=(r_equator, r_poles),\
                resolution='f', projection='laea',\
                lat_ts=lat0,\
                lat_0=lat0,lon_0=lon0)

    # Draw parallels and meridians.
    m.drawparallels(np.arange(-80.,81.,5.), labels=[1,0,0,0], fontsize=10)
    m.drawmeridians(np.arange(-180.,181.,10.), labels=[0,0,0,1], fontsize=10)
    m.drawmapboundary(fill_color='white')
    m.drawcoastlines(linewidth=0.2)
    m.fillcontinents(color='gray', lake_color='white') #aqua

    # Animate if position data is supplied with plotting function
    if plot_data == True:
        # Automatically determine frame number
        f_num = len(data_dates)
        # Declare list of point objects
        points = list()

        # Create a placeholder plot point
        x,y = m(0,0)
        # Fill list with same number of placeholders as points to animate
        for i in range(len(data.lons)):
            points.append(m.plot(x,y, 'ro', markersize=5)[0])

        anim = animation.FuncAnimation(plt.gcf(), animate,
                                       init_func=init, frames=f_num,
                                       interval=500, blit=True)

        # Save animation to file
        anim.save('plot_animation.mp4', fps=f_num,
                  extra_args=['-vcodec', 'libx264'])

    plt.show()

if __name__ == '__main__':

    # WGS84 datum
    wgs84 = pyproj.Proj(init='EPSG:4326')

    # CSV data with columns 'daynr', 'lons', and 'lats'
    datafile = '/home/dude/datalocations/data.csv'
    data = readwhales(whale_datafile)
    data_dates = np.unique(data.daynr).values

    makeplot(plot_data=True)
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top