{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import xarray as xr\n", "import pandas as pd\n", "import geoviews as gv\n", "import geoviews.feature as gf\n", "from geoviews import dim, opts\n", "\n", "gv.extension('bokeh')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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](Projections.ipynb) user guide, using web mapping tile sources is only supported when using the default ``GOOGLE_MERCATOR`` ``crs``." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# WMTS - Tile Sources" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import geoviews.tile_sources as gts\n", "\n", "gv.Layout([ts.relabel(name) for name, ts in gts.tile_sources.items()]).opts(\n", " 'WMTS', xaxis=None, yaxis=None, width=225, height=225).cols(4)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:\n", "\n", "1. Web mapping tile sources: ``{X}``, ``{Y}`` defining the location and a ``{Z}`` parameter defining the zoom level \n", "2. Bounding box tile source: ``{XMIN}``, ``{XMAX}``, ``{YMIN}``, and ``{YMAX}`` parameters defining the bounds\n", "3. Quad-key tile source: a single ``{Q}`` parameter\n", "\n", "Additional, freely available tile sources can be found at [wiki.openstreetmap.org](http://wiki.openstreetmap.org/wiki/Tile_servers).\n", "\n", "Stamen tile sources are also available but require a Stadia account when not running locally; see [stadiamaps.com](https://stadiamaps.com/).\n", "\n", "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':" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gts.EsriImagery.opts(width=600, height=570, global_extent=True) * gts.StamenLabels.options(level='annotation')" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import cartopy.crs as ccrs\n", "\n", "tiles = gv.tile_sources.EsriImagery()\n", "\n", "gv.util.get_tile_rgb(tiles, bbox=(-180, -70, 180, 70), zoom_level=1).opts(width=500, height=500, projection=ccrs.Orthographic())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Plotting data\n", "\n", "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "cities = pd.read_csv('../assets/cities.csv', encoding=\"ISO-8859-1\")\n", "population = gv.Dataset(cities, kdims=['City', 'Country', 'Year'])\n", "cities.head()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "points = population.to(gv.Points, ['Longitude', 'Latitude'], ['Population', 'City', 'Country'])\n", "(gts.OSM * points).opts(\n", " opts.Points(width=600, height=350, tools=['hover'], size=np.sqrt(dim('Population'))*0.005,\n", " color='Population', cmap='viridis'))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And because this is a fully interactive Bokeh plot, you can now hover over each datapoint to see all of the values associated with it (name, location, etc.), and you can zoom and pan using the tools provided. Each time, the map tiles should seamlessly update to provide additional detail appropriate for that zoom level.\n", "\n", "\n", "## Choropleths" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The tutorial on [Geometries](Geometries.ipynb) covers working with shapefiles in more detail but here we will quickly combine a shapefile with a pandas DataFrame to plot the results of the EU Referendum in the UK. We begin by loading the shapefile and then us ``pd.merge`` by combining it with some CSV data containing the referendum results:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import geopandas as gpd\n", "geometries = gpd.read_file('../assets/boundaries/boundaries.shp')\n", "referendum = pd.read_csv('../assets/referendum.csv')\n", "gdf = gpd.GeoDataFrame(pd.merge(geometries, referendum))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can easily pass the GeoDataFrame to a Polygons object and declare the ``leaveVoteshare`` as the first value dimension which it will color by:" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "gv.Polygons(gdf, vdims=['name', 'leaveVoteshare']).opts(\n", " tools=['hover'], width=450, height=600, color='leaveVoteshare',\n", " colorbar=True, toolbar='above', xaxis=None, yaxis=None)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Images" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Bokeh backend also provides basic support for working with images. In this example we will load a very simple Iris Cube and display it overlaid with the coastlines feature from Cartopy. Note that the Bokeh backend does not project the image directly into the web Mercator projection, instead relying on regridding, i.e. resampling the data using a new grid. This means the actual display may be subtly different from the more powerful image support for the matplotlib backend, which will project each of the pixels into the chosen display coordinate system without regridding." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "dataset = xr.open_dataset('../data/pre-industrial.nc')\n", "air_temperature = gv.Dataset(dataset, ['longitude', 'latitude'], 'air_temperature',\n", " group='Pre-industrial air temperature')\n", "air_temperature.to.image().opts(tools=['hover'], cmap='viridis') *\\\n", "gf.coastline().opts(line_color='black', width=600, height=500)" ] } ], "metadata": { "language_info": { "name": "python", "pygments_lexer": "ipython3" } }, "nbformat": 4, "nbformat_minor": 2 }