{ "metadata": { "name": "", "signature": "sha256:ebea0a53d0b3b22a9be9a679c002a36ccb819adf759d1782bc8432ad2984670e" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "heading", "level": 1, "metadata": {}, "source": [ "Plotly maps" ] }, { "cell_type": "heading", "level": 2, "metadata": {}, "source": [ "with Plotly's Python API library and Basemap" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This notebook comes in response to this Rhett Allain tweet.\n", "\n", "> Although Plotly does not feature built-in maps functionality (yet), this notebook demonstrates how to *plotly-fy* maps generated by Basemap." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, check the version which version of the Python API library installed on your machine:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import plotly\n", "plotly.__version__" ], "language": "python", "metadata": {}, "outputs": [ { "metadata": {}, "output_type": "pyout", "prompt_number": 1, "text": [ "'1.2.6'" ] } ], "prompt_number": 1 }, { "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`. " ] }, { "cell_type": "code", "collapsed": false, "input": [ "import plotly.plotly as py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Import the plotly graph objects (in particular `Contour`) to help build our figure:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from plotly.graph_objs import *" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 3 }, { "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", "collapsed": false, "input": [ "import numpy as np \n", "from scipy.io import netcdf " ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 4 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, import the Matplotlib Basemap Toolkit, its installation instructions can found here." ] }, { "cell_type": "code", "collapsed": false, "input": [ "from mpl_toolkits.basemap import Basemap" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 5 }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "1. 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", "collapsed": false, "input": [ "# 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" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 6 }, { "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", "collapsed": false, "input": [ "# 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]))" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 7 }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "2. Make Contour graph object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Very simply," ] }, { "cell_type": "code", "collapsed": false, "input": [ "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", ")" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 8 }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "3. Get the 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", "collapsed": false, "input": [ "# Make shortcut to Basemap object, \n", "# not specifying projection type for this example\n", "m = Basemap() " ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "code", "collapsed": false, "input": [ "# 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)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then," ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Get list of of coastline and country lon/lat traces\n", "traces_cc = get_coastline_traces()+get_country_traces()" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 11 }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "4. Make a figue 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." ] }, { "cell_type": "code", "collapsed": false, "input": [ "data = Data([trace1]+traces_cc)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Layout options are set in a `Layout` object:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "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", ")" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Package data and layout in a `Figure` object and send it to plotly:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "fig = Figure(data=data, layout=layout)\n", "\n", "py.iplot(fig, filename=\"maps\", width=1000)" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": {}, "source": [ "See this graph in full screen here." ] }, { "cell_type": "heading", "level": 3, "metadata": {}, "source": [ "To learn more about Plotly's Python API" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Refer to\n", "\n", "* our online documentation page or\n", "* our User Guide." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", "
\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "
\n", " \n", "
\n", "\n", "

Got Questions or Feedback?

\n", "\n", "About Plotly\n", "\n", "* email: feedback@plot.ly \n", "* tweet: \n", "@plotlygraphs\n", "\n", "

Notebook styling ideas

\n", "\n", "Big thanks to\n", "\n", "* Cam Davidson-Pilon\n", "* Lorena A. Barba\n", "\n", "
" ] }, { "cell_type": "code", "collapsed": false, "input": [ "from IPython.display import display, HTML\n", "import urllib2\n", "url = 'https://raw.githubusercontent.com/plotly/python-user-guide/master/custom.css'\n", "display(HTML(urllib2.urlopen(url).read()))" ], "language": "python", "metadata": {}, "outputs": [ { "html": [ "\n", "\n" ], "metadata": {}, "output_type": "display_data", "text": [ "" ] } ], "prompt_number": 15 } ], "metadata": {} } ] }