Working with Bokeh#

import numpy as np
import xarray as xr
import pandas as pd
import geoviews as gv
import geoviews.feature as gf
from geoviews import dim, opts

gv.extension('bokeh')

The Bokeh backend offers much more advanced tools to interactively explore data, making good use of GeoViews support for web mapping tile sources. As we learned in the Projections user guide, using web mapping tile sources is only supported when using the default GOOGLE_MERCATOR crs.

WMTS - Tile Sources#

GeoViews provides a number of tile sources by default, provided by CartoDB, Stamen/Stadia, OpenStreetMap, and Esri. These can be imported from the geoviews.tile_sources module.

import geoviews.tile_sources as gts

gv.Layout([ts.relabel(name) for name, ts in gts.tile_sources.items()]).opts(
    'WMTS', xaxis=None, yaxis=None, width=225, height=225).cols(4)

The tile sources that are defined as part of GeoViews are simply instances of the gv.WMTS and gv.Tiles elements, which accept tile source URLs of three formats:

  1. Web mapping tile sources: {X}, {Y} defining the location and a {Z} parameter defining the zoom level

  2. Bounding box tile source: {XMIN}, {XMAX}, {YMIN}, and {YMAX} parameters defining the bounds

  3. Quad-key tile source: a single {Q} parameter

Additional, freely available tile sources can be found at wiki.openstreetmap.org.

Stamen tile sources are also available but require a Stadia account when not running locally; see stadiamaps.com.

A tile source may also be drawn at a different level allowing us to overlay a regular tile source with a set of labels. Valid options for the ‘level’ option include ‘image’, ‘underlay’, ‘glyph’, ‘annotation’ and ‘overlay’:

gts.EsriImagery.opts(width=600, height=570, global_extent=True) * gts.StamenLabels.options(level='annotation')

Note that since the frontend tile renderer only support Mercator coordinates we have to download the tiles and let Cartopy handle reprojecting them in Python. This allows us view them in any coordinate system:

import cartopy.crs as ccrs

tiles = gv.tile_sources.EsriImagery()

gv.util.get_tile_rgb(tiles, bbox=(-180, -70, 180, 70), zoom_level=1).opts(width=500, height=500, projection=ccrs.Orthographic())

Plotting data#

One of the main benefits of plotting data with Bokeh is the interactivity it allows. Here we will load a dataset of all the major cities in the world with their population counts over time:

cities = pd.read_csv('../assets/cities.csv', encoding="ISO-8859-1")
population = gv.Dataset(cities, kdims=['City', 'Country', 'Year'])
cities.head()
City Country Latitude Longitude Year Population
0 Sofia Bulgaria 42.70 23.33 1950.0 520000.0
1 Mandalay Myanmar 21.97 96.08 1950.0 170000.0
2 Nay Pyi Taw Myanmar 19.75 96.10 1950.0 0.0
3 Yangon Myanmar 16.87 96.12 1950.0 1300000.0
4 Minsk Belarus 53.89 27.57 1950.0 280000.0

Now we can convert this dataset to a set of points mapped by the latitude and longitude and containing the population, country and city as values. The longitudes and latitudes in the dataframe are supplied in simple Plate Carree coordinates, which we will need to declare (as the values are not stored with any associated units). The .to conversion interface lets us do this succinctly. Note that since we did not assign the Year dimension to the points key or value dimensions, it is automatically assigned to a HoloMap, rendering the data as an animation using a slider widget:

points = population.to(gv.Points, ['Longitude', 'Latitude'], ['Population', 'City', 'Country'])
(gts.OSM * points).opts(
    opts.Points(width=600, height=350, tools=['hover'], size=np.sqrt(dim('Population'))*0.005,
                color='Population', cmap='viridis'))