year-vs-climatology / hvplot_docs /Geographic_Data.md
ahuang11's picture
Upload 52 files
b9a0f21 verified
import xarray as xr
import hvplot.pandas  # noqa
import hvplot.xarray  # noqa
import cartopy.crs as ccrs

from bokeh.sampledata.airport_routes import airports

Installation

The plot API also has support for geographic data built on top of Cartopy and GeoViews. Both can be installed using conda with:

conda install geoviews

or with pip:

pip install geoviews

Usage

Only certain hvPlot types support geographic coordinates, currently including: 'points', 'polygons', 'paths', 'image', 'quadmesh', 'contour', and 'contourf'. As an initial example, consider a dataframe of all US airports (including military bases overseas):

airports.head(3)

Plotting points

If we want to overlay our data on geographic maps or reproject it into a geographic plot, we can set geo=True, which declares that the data will be plotted in a geographic coordinate system. The default coordinate system is the PlateCarree projection, i.e., raw longitudes and latitudes. If the data is in another coordinate system, you will need to declare an explicit crs as an argument, in which case geo=True is assumed. Once hvPlot knows that your data is in geo coordinates, you can use the tiles option to overlay a the plot on top of map tiles.

airports.hvplot.points('Longitude', 'Latitude', geo=True, color='red', alpha=0.2,
                       xlim=(-180, -30), ylim=(0, 72), tiles='ESRI')

Declaring a CRS

To declare a geographic plot we have to supply a cartopy.crs.CRS (or coordinate reference system). Coordinate reference systems are described in the GeoViews documentation and the full list of available CRSs is in the cartopy documentation.

Geopandas

Since a GeoPandas DataFrame is just a Pandas DataFrames with additional geographic information, it inherits the .hvplot method. We can thus easily load shapefiles and plot them on a map:

import geopandas as gpd

cities = gpd.read_file(gpd.datasets.get_path('naturalearth_cities'))

cities.hvplot(global_extent=True, frame_height=450, tiles=True)

The GeoPandas support allows plotting GeoDataFrames containing 'Point', 'Polygon', 'LineString' and 'LineRing' geometries, but not ones containing a mixture of different geometry types. Calling .hvplot will automatically figure out the geometry type to plot, but it also possible to call .hvplot.points, .hvplot.polygons, and .hvplot.paths explicitly.

To draw multiple GeoDataFrames onto the same plot, use the * operator:

world = gpd.read_file(gpd.datasets.get_path('naturalearth_lowres'))

world.hvplot(geo=True) * cities.hvplot(geo=True, color='orange')

It is possible to declare a specific column to use as color with the c keyword:

world.hvplot(geo=True) + world.hvplot(c='continent', geo=True)

Spatialpandas

Spatialpandas is another powerful library for working with geometries and is optimized for rendering with datashader, making it possible to plot millions of individual geometries very quickly:

import spatialpandas as spd

spd_world = spd.GeoDataFrame(world)

spd_world.hvplot(datashade=True, project=True, aggregator='count_cat', c='continent', color_key='Category10')

Declaring an output projection

The crs= argument specifies the input projection, i.e. it declares how to interpret the incoming data values. You can independently choose any output projection, i.e. how you want to map the data points onto the screen for display, using the projection= argument. After loading the same temperature dataset explored in the Gridded Data section, the data can be displayed on an Orthographic projection:

air_ds = xr.tutorial.open_dataset('air_temperature').load()

air_ds.hvplot.quadmesh(
    'lon', 'lat', 'air', projection=ccrs.Orthographic(-90, 30),
    global_extent=True, frame_height=540, cmap='viridis',
    coastline=True
)

If you don't need to pass any keyword arguments to a given projection and you don't have cartopy.crs (ccrs) imported, you can use the string representation: e.g. 'LambertConformal' instead of ccrs.LambertConformal(). Note that it is case sensitive!

air_ds.hvplot.quadmesh(
    'lon', 'lat', 'air', projection='LambertConformal',
)

Note that when displaying raster data in a projection other than the one in which the data is stored, it is more accurate to render it as a quadmesh rather than an image. As you can see above, a QuadMesh will project each original bin or pixel into the correct non-rectangular shape determined by the projection, accurately showing the geographic extent covered by each sample. An Image, on the other hand, will always be rectangularly aligned in the 2D plane, which requires warping and resampling the data in a way that allows efficient display but loses accuracy at the pixel level. Unfortunately, rendering a large QuadMesh using Bokeh can be very slow, but there are two useful alternatives for datasets too large to be practical as native QuadMeshes.

The first is using the rasterize or datashade options to regrid the data before rendering it, i.e., rendering the data on the backend and then sending a more efficient image-based representation to the browser. One thing to note when using these operations is that it may be necessary to project the data before rasterizing it, e.g. to address wrapping issues. To do this provide project=True, which will project the data before it is rasterized (this also works for other types and even when not using these operations). Another reason why this is important when rasterizing the data is that if the CRS of the data does not match the displayed projection, all the data will be projected every time you zoom or pan, which can be very slow. Deciding whether to project is therefore a tradeoff between projecting the raw data ahead of time or accepting the overhead on dynamic zoom and pan actions.

rasm = xr.tutorial.open_dataset('rasm').load()



rasm.hvplot.quadmesh(
    'xc', 'yc', crs=ccrs.PlateCarree(), projection=ccrs.PlateCarree(),
    ylim=(0, 90), cmap='viridis', project=True, geo=True,
    rasterize=True, coastline=True, frame_width=800, dynamic=False,
)

Another option that's still relatively slow for larger data but avoids sending large data into your browser is to plot the data using contour and contourf visualizations, generating a line or filled contour with a discrete number of levels:

rasm.hvplot.contourf(
    'xc', 'yc', crs=ccrs.PlateCarree(), projection=ccrs.PlateCarree(),
    ylim=(0, 90), frame_width=800, cmap='viridis', levels=10,
    coastline=True
)

As you can see, hvPlot makes it simple to work with geographic data visually. For more complex plot types and additional details, see the GeoViews documentation.

Geographic options

The API provides various geo-specific options:

  • coastline (default=False): Whether to display a coastline on top of the plot, setting coastline='10m'/'50m'/'110m' specifies a specific scale
  • crs (default=None): Coordinate reference system of the data specified as Cartopy CRS object, proj.4 string or EPSG code
  • features features (default=None): A list of features or a dictionary of features and the scale at which to render it. Available features include 'borders', 'coastline', 'lakes', 'land', 'ocean', 'rivers' and 'states'. Available scales include '10m'/'50m'/'110m'.
  • geo (default=False): Whether the plot should be treated as geographic (and assume PlateCarree, i.e. lat/lon coordinates)
  • global_extent (default=False): Whether to expand the plot extent to span the whole globe
  • project (default=False): Whether to project the data before plotting (adds initial overhead but avoids projecting data when plot is dynamically updated)
  • tiles (default=False): Whether to overlay the plot on a tile source. Tiles sources can be selected by name, the default is 'Wikipedia'. Other options are: 'CartoDark', 'CartoEco', 'CartoLight', 'CartoMidnight', 'EsriImagery', 'EsriNatGeo', 'EsriReference''EsriTerrain', 'EsriUSATopo', 'OSM', 'StamenLabels', 'StamenTerrain', 'StamenTerrainRetina', 'StamenToner', 'StamenTonerBackground', 'StamenWatercolor'. Stamen tile sources require a Stadia account when not running locally; see stadiamaps.com.