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)