This notebook comes in response to this Rhett Allain tweet.
Although Plotly does not feature built-in maps functionality (yet), this notebook demonstrates how to plotly-fy maps generated by Basemap.
First, check the version which version of the Python API library installed on your machine:
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
import plotly.plotly as py
Import the plotly graph objects (in particular
Contour) to help build our figure:
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
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:
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
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]))
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 )
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
.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,vertex) 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()
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.
data = Data([trace1]+traces_cc)
Layout options are set in a
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,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)
from IPython.display import display, HTML import urllib2 url = 'https://raw.githubusercontent.com/plotly/python-user-guide/master/custom.css' display(HTML(urllib2.urlopen(url).read()))