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__
'1.2.6'
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 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
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]))
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
)
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)
Then,
# Get list of of coastline and country lon/lat traces
traces_cc = get_coastline_traces()+get_country_traces()
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.
data = Data([trace1]+traces_cc)
Layout options are set in a Layout
object:
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.
Refer to
About Plotly
Big thanks to
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()))