Plotly maps with Matplotlib Basemap in Python/v3
An IPython Notebook showing how to make an interactive world map using plotly and Maplotlib Basemap
See our Version 4 Migration Guide for information about how to upgrade.
This notebook comes in response to this Rhett Allain tweet.
Version Check¶
import plotly
plotly.__version__
Next, if you have a plotly account as well as a credentials file set up on your machine, singing in to Plotly's servers is done automatically while importing plotly.plotly
. Import the plotly graph objects (in particular Contour
) to help build our figure:
import plotly.plotly as py
from plotly.graph_objs import *
Data with this notebook will be taken from a NetCDF file, so import netcdf class from the scipy.io module, along with numpy:
import numpy as np
from scipy.io import netcdf
from mpl_toolkits.basemap import Basemap
Get the Data¶
The data is taken from NOAA Earth System Research Laboratory.
Unfortunately, this website does not allow to code your output demand and/or use wget
to download the data.
That said, the data used for this notebook can be downloaded in a only a few clicks:
- Select Air Temperature in Varaibles
- Select Surface in Analysis level?
- Select Jul | 1 and Jul | 31
- Enter 2014 in the Enter Year of last day of range field
- Select Anomaly in Plot type?
- Select All in Region of globe
- Click on Create Plot
Then on the following page, click on Get a copy of the netcdf data file used for the plot to download the NetCDF on your machine.
Note that the data represents the average daily surface air temperature anomaly (in deg. C) for July 2014 with respect to 1981-2010 climatology.
Now, import the NetCDF file into this IPython session. The following was inspired by this earthpy blog post.
# Path the downloaded NetCDF file (different for each download)
f_path = '/home/etienne/Downloads/compday.Bo3cypJYyE.nc'
# Retrieve data from NetCDF file
with netcdf.netcdf_file(f_path, 'r') as f:
lon = f.variables['lon'][::] # copy as list
lat = f.variables['lat'][::-1] # invert the latitude vector -> South to North
air = f.variables['air'][0,::-1,:] # squeeze out the time dimension,
# invert latitude index
The values lon
start a 0 degrees and increase eastward to 360 degrees. So, the air
array is centered about the Pacific Ocean. For a better-looking plot, shift the data so that it is centered about the 0 meridian:
# Shift 'lon' from [0,360] to [-180,180], make numpy array
tmp_lon = np.array([lon[n]-360 if l>=180 else lon[n]
for n,l in enumerate(lon)]) # => [0,180]U[-180,2.5]
i_east, = np.where(tmp_lon>=0) # indices of east lon
i_west, = np.where(tmp_lon<0) # indices of west lon
lon = np.hstack((tmp_lon[i_west], tmp_lon[i_east])) # stack the 2 halves
# Correspondingly, shift the 'air' array
tmp_air = np.array(air)
air = np.hstack((tmp_air[:,i_west], tmp_air[:,i_east]))
Make Contour Graph Object¶
Very simply,
trace1 = Contour(
z=air,
x=lon,
y=lat,
colorscale="RdBu",
zauto=False, # custom contour levels
zmin=-5, # first contour level
zmax=5 # last contour level => colorscale is centered about 0
)
Get Coastlines and Country boundaries with Basemap¶
The Basemap module includes data for drawing coastlines and country boundaries onto world maps. Adding coastlines and/or country boundaries on a matplotlib figure is done with the .drawcoaslines()
or .drawcountries()
Basemap methods.
Next, we will retrieve the Basemap plotting data (or polygons) and convert them to longitude/latitude arrays (inspired by this stackoverflow post) and then package them into Plotly Scatter
graph objects .
In other words, the goal is to plot each continuous coastline and country boundary lines as 1 Plolty scatter line trace.
# Make shortcut to Basemap object,
# not specifying projection type for this example
m = Basemap()
# Make trace-generating function (return a Scatter object)
def make_scatter(x,y):
return Scatter(
x=x,
y=y,
mode='lines',
line=Line(color="black"),
name=' ' # no name on hover
)
# Functions converting coastline/country polygons to lon/lat traces
def polygons_to_traces(poly_paths, N_poly):
'''
pos arg 1. (poly_paths): paths to polygons
pos arg 2. (N_poly): number of polygon to convert
'''
traces = [] # init. plotting list
for i_poly in range(N_poly):
poly_path = poly_paths[i_poly]
# get the Basemap coordinates of each segment
coords_cc = np.array(
[(vertex[0],vertex[1])
for (vertex,code) in poly_path.iter_segments(simplify=False)]
)
# convert coordinates to lon/lat by 'inverting' the Basemap projection
lon_cc, lat_cc = m(coords_cc[:,0],coords_cc[:,1], inverse=True)
# add plot.ly plotting options
traces.append(make_scatter(lon_cc,lat_cc))
return traces
# Function generating coastline lon/lat traces
def get_coastline_traces():
poly_paths = m.drawcoastlines().get_paths() # coastline polygon paths
N_poly = 91 # use only the 91st biggest coastlines (i.e. no rivers)
return polygons_to_traces(poly_paths, N_poly)
# Function generating country lon/lat traces
def get_country_traces():
poly_paths = m.drawcountries().get_paths() # country polygon paths
N_poly = len(poly_paths) # use all countries
return polygons_to_traces(poly_paths, N_poly)
# Get list of of coastline and country lon/lat traces
traces_cc = get_coastline_traces()+get_country_traces()
Make a Figure Object and Plot!¶
Package the Contour
trace with the coastline and country traces. Note that the Contour
trace must be placed before the coastline and country traces in order to make all traces visible. Layout options are set in a Layout
object:
data = Data([trace1]+traces_cc)
title = u"Average daily surface air temperature anomalies [\u2103]<br> \
in July 2014 with respect to 1981-2010 climatology"
anno_text = "Data courtesy of \
<a href='http://www.esrl.noaa.gov/psd/data/composites/day/'>\
NOAA Earth System Research Laboratory</a>"
axis_style = dict(
zeroline=False,
showline=False,
showgrid=False,
ticks='',
showticklabels=False,
)
layout = Layout(
title=title,
showlegend=False,
hovermode="closest", # highlight closest point on hover
xaxis=XAxis(
axis_style,
range=[lon[0],lon[-1]] # restrict y-axis to range of lon
),
yaxis=YAxis(
axis_style,
),
annotations=Annotations([
Annotation(
text=anno_text,
xref='paper',
yref='paper',
x=0,
y=1,
yanchor='bottom',
showarrow=False
)
]),
autosize=False,
width=1000,
height=500,
)
Package data and layout in a Figure
object and send it to plotly:
fig = Figure(data=data, layout=layout)
py.iplot(fig, filename="maps", width=1000)
See this graph in full screen here.
Reference¶
See our online documentation page or our User Guide.