{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This notebook comes in response to this Rhett Allain tweet."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Version Check"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'1.2.6'"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"import plotly\n",
"plotly.__version__"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [],
"source": [
"import plotly.plotly as py\n",
"from plotly.graph_objs import *"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Data with this notebook will be taken from a NetCDF file, so import netcdf class from the scipy.io module, along with numpy:"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"import numpy as np \n",
"from scipy.io import netcdf "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Finally, import the Matplotlib Basemap Toolkit, its installation instructions can found here."
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"from mpl_toolkits.basemap import Basemap"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Get the Data"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The data is taken from NOAA Earth System Research Laboratory.\n",
"\n",
"Unfortunately, this website does not allow to *code* your output demand and/or use `wget` to download the data. \n",
"\n",
"That said, the data used for this notebook can be downloaded in a only a few clicks:\n",
"\n",
"- Select *Air Temperature* in **Varaibles**\n",
"- Select *Surface* in **Analysis level?**\n",
"- Select *Jul | 1* and *Jul | 31*\n",
"- Enter *2014* in the **Enter Year of last day of range** field\n",
"- Select *Anomaly* in **Plot type?**\n",
"- Select *All* in **Region of globe**\n",
"- Click on **Create Plot** \n",
"\n",
"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.\n",
"\n",
"Note that the data represents the average daily surface air temperature anomaly (in deg. C) for July 2014 with respect to 1981-2010 climatology.\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Now, import the NetCDF file into this IPython session. The following was inspired by this earthpy blog post."
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [],
"source": [
"# Path the downloaded NetCDF file (different for each download)\n",
"f_path = '/home/etienne/Downloads/compday.Bo3cypJYyE.nc'\n",
"\n",
"# Retrieve data from NetCDF file\n",
"with netcdf.netcdf_file(f_path, 'r') as f:\n",
" lon = f.variables['lon'][::] # copy as list\n",
" lat = f.variables['lat'][::-1] # invert the latitude vector -> South to North\n",
" air = f.variables['air'][0,::-1,:] # squeeze out the time dimension, \n",
" # invert latitude index"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"# Shift 'lon' from [0,360] to [-180,180], make numpy array\n",
"tmp_lon = np.array([lon[n]-360 if l>=180 else lon[n] \n",
" for n,l in enumerate(lon)]) # => [0,180]U[-180,2.5]\n",
"\n",
"i_east, = np.where(tmp_lon>=0) # indices of east lon\n",
"i_west, = np.where(tmp_lon<0) # indices of west lon\n",
"lon = np.hstack((tmp_lon[i_west], tmp_lon[i_east])) # stack the 2 halves\n",
"\n",
"# Correspondingly, shift the 'air' array\n",
"tmp_air = np.array(air)\n",
"air = np.hstack((tmp_air[:,i_west], tmp_air[:,i_east]))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Make Contour Graph Object"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Very simply,"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"trace1 = Contour(\n",
" z=air,\n",
" x=lon,\n",
" y=lat,\n",
" colorscale=\"RdBu\",\n",
" zauto=False, # custom contour levels\n",
" zmin=-5, # first contour level\n",
" zmax=5 # last contour level => colorscale is centered about 0\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Get Coastlines and Country boundaries with Basemap"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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. \n",
"\n",
"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 .\n",
"\n",
"In other words, the goal is to plot each *continuous* coastline and country boundary lines as 1 Plolty scatter line trace."
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {},
"outputs": [],
"source": [
"# Make shortcut to Basemap object, \n",
"# not specifying projection type for this example\n",
"m = Basemap() \n",
"\n",
"# Make trace-generating function (return a Scatter object)\n",
"def make_scatter(x,y):\n",
" return Scatter(\n",
" x=x,\n",
" y=y,\n",
" mode='lines',\n",
" line=Line(color=\"black\"),\n",
" name=' ' # no name on hover\n",
" )\n",
"\n",
"# Functions converting coastline/country polygons to lon/lat traces\n",
"def polygons_to_traces(poly_paths, N_poly):\n",
" ''' \n",
" pos arg 1. (poly_paths): paths to polygons\n",
" pos arg 2. (N_poly): number of polygon to convert\n",
" '''\n",
" traces = [] # init. plotting list \n",
"\n",
" for i_poly in range(N_poly):\n",
" poly_path = poly_paths[i_poly]\n",
" \n",
" # get the Basemap coordinates of each segment\n",
" coords_cc = np.array(\n",
" [(vertex[0],vertex[1]) \n",
" for (vertex,code) in poly_path.iter_segments(simplify=False)]\n",
" )\n",
" \n",
" # convert coordinates to lon/lat by 'inverting' the Basemap projection\n",
" lon_cc, lat_cc = m(coords_cc[:,0],coords_cc[:,1], inverse=True)\n",
" \n",
" # add plot.ly plotting options\n",
" traces.append(make_scatter(lon_cc,lat_cc))\n",
" \n",
" return traces\n",
"\n",
"# Function generating coastline lon/lat traces\n",
"def get_coastline_traces():\n",
" poly_paths = m.drawcoastlines().get_paths() # coastline polygon paths\n",
" N_poly = 91 # use only the 91st biggest coastlines (i.e. no rivers)\n",
" return polygons_to_traces(poly_paths, N_poly)\n",
"\n",
"# Function generating country lon/lat traces\n",
"def get_country_traces():\n",
" poly_paths = m.drawcountries().get_paths() # country polygon paths\n",
" N_poly = len(poly_paths) # use all countries\n",
" return polygons_to_traces(poly_paths, N_poly)\n",
"\n",
"# Get list of of coastline and country lon/lat traces\n",
"traces_cc = get_coastline_traces()+get_country_traces()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Make a Figure Object and Plot!"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"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:"
]
},
{
"cell_type": "code",
"execution_count": 13,
"metadata": {},
"outputs": [],
"source": [
"data = Data([trace1]+traces_cc)\n",
"\n",
"title = u\"Average daily surface air temperature anomalies [\\u2103] \\\n",
"in July 2014 with respect to 1981-2010 climatology\"\n",
"\n",
"anno_text = \"Data courtesy of \\\n",
"\\\n",
"NOAA Earth System Research Laboratory\"\n",
"\n",
"axis_style = dict(\n",
" zeroline=False,\n",
" showline=False,\n",
" showgrid=False,\n",
" ticks='',\n",
" showticklabels=False,\n",
")\n",
"\n",
"layout = Layout(\n",
" title=title,\n",
" showlegend=False,\n",
" hovermode=\"closest\", # highlight closest point on hover\n",
" xaxis=XAxis(\n",
" axis_style,\n",
" range=[lon[0],lon[-1]] # restrict y-axis to range of lon\n",
" ),\n",
" yaxis=YAxis(\n",
" axis_style,\n",
" ),\n",
" annotations=Annotations([\n",
" Annotation(\n",
" text=anno_text,\n",
" xref='paper',\n",
" yref='paper',\n",
" x=0,\n",
" y=1,\n",
" yanchor='bottom',\n",
" showarrow=False\n",
" )\n",
" ]),\n",
" autosize=False,\n",
" width=1000,\n",
" height=500,\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Package data and layout in a `Figure` object and send it to plotly:"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig = Figure(data=data, layout=layout)\n",
"\n",
"py.iplot(fig, filename=\"maps\", width=1000)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"See this graph in full screen here."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"#### Reference"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"See our online documentation page or our User Guide."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"text/html": [
""
],
"text/plain": [
""
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"from IPython.display import display, HTML\n",
"\n",
"display(HTML(''))\n",
"display(HTML(''))\n",
"\n",
"import publisher\n",
"publisher.publish(\n",
" 'basemap.ipynb', 'ipython-notebooks/basemap-maps/', 'Plotly maps with Matplotlib Basemap', \n",
" 'An IPython Notebook showing how to make an interactive world map using plotly and Maplotlib Basemap')"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"collapsed": true
},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 2",
"language": "python",
"name": "python2"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.10"
}
},
"nbformat": 4,
"nbformat_minor": 1
}