#!/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[ ]: