#!/usr/bin/env python
# coding: utf-8
# This notebook comes in response to this Rhett Allain tweet.
# #### Version Check
# In[1]:
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:
# In[2]:
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:
# In[4]:
import numpy as np
from scipy.io import netcdf
# Finally, import the Matplotlib Basemap Toolkit, its installation instructions can found here.
# In[5]:
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.
# In[6]:
# 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:
# In[7]:
# 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,
# In[8]:
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.
# In[10]:
# 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:
# In[13]:
data = Data([trace1]+traces_cc)
title = u"Average daily surface air temperature anomalies [\u2103] \
in July 2014 with respect to 1981-2010 climatology"
anno_text = "Data courtesy of \
\
NOAA Earth System Research Laboratory"
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:
# In[14]:
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.
# In[2]:
from IPython.display import display, HTML
display(HTML(''))
display(HTML(''))
import publisher
publisher.publish(
'basemap.ipynb', 'ipython-notebooks/basemap-maps/', 'Plotly maps with Matplotlib Basemap',
'An IPython Notebook showing how to make an interactive world map using plotly and Maplotlib Basemap')
# In[ ]: