diff --git a/examples/user_guide/Working_with_Bokeh.ipynb b/examples/user_guide/Working_with_Bokeh.ipynb
index 553aa462..272d0079 100644
--- a/examples/user_guide/Working_with_Bokeh.ipynb
+++ b/examples/user_guide/Working_with_Bokeh.ipynb
@@ -16,8 +16,6 @@
"\n",
"from cartopy import crs as ccrs\n",
"\n",
- "from bokeh.tile_providers import STAMEN_TONER, STAMEN_TONER_LABELS\n",
- "\n",
"hv.extension('bokeh')"
]
},
@@ -25,7 +23,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "In most GeoViews examples, we have selected the matplotlib plotting backend, because it has general support for projecting data to different geographic projections using cartopy. The Bokeh backend offers much more advanced tools to interactively explore data, but is currently restricted to displaying data in web Mercator coordinates. Luckily, cartopy makes it possible to project points, geometries and even images from arbitrary coordinate systems into web Mercator so that they can be rendered by Bokeh. So as long as you choose the web Mercator format for your output (or don't specify the output format), you should be able to use Bokeh for any of the GeoViews examples from other notebooks. Bokeh also provides a general interface to render web-based map tile sources, making it simple to overlay your plots onto map tiles."
+ "In most GeoViews examples, we have selected the matplotlib plotting backend, because it has general support for projecting data to different geographic projections using cartopy. The Bokeh backend offers much more advanced tools to interactively explore data, but is currently restricted to displaying data in web Mercator coordinates, something GeoViews will handle automatically as long as you declare the appropriate coordinate reference system on your data.\n",
+ "\n",
+ "One of the most useful features Bokeh provides, is support for renderering your data on top of web-based map tile sources."
]
},
{
@@ -39,7 +39,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
- "When using the matplotlib backend, the ``gv.WMTS`` element accepts tile source URLs valid for cartopy. When using the Bokeh backend, you will need to first wrap the URL into a ``WMTSTileSource`` object, because Bokeh's tile support uses a different URL format. Here we provide a list of common tile sources for use with Bokeh. Additional open tile sources you could use can be found at [openstreetmap.org](http://wiki.openstreetmap.org/wiki/Tile_servers)."
+ "GeoViews provides a number of tile sources by default, provided by CartoDB, Stamen, OpenStreetMap, ESRI and Wikipedia. These can be imported from the ``geoviews.tile_sources`` module."
]
},
{
@@ -48,36 +48,25 @@
"metadata": {},
"outputs": [],
"source": [
- "tiles = {'OpenMap': 'http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png',\n",
- " 'ESRI': 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg',\n",
- " 'Wikipedia': 'https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png',\n",
- " 'Stamen Toner': STAMEN_TONER}"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Now we can lay these Elements out in an NdLayout by wrapping each ``WMTSTileSource`` in a ``WMTS`` Element and specifying both the extent and the coordinate reference system of these extents. Note that the extents are only required when displaying the tile source on its own; when it is overlaid with some data the data determines the extent automatically."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "metadata": {
- "scrolled": true
- },
- "outputs": [],
- "source": [
- "%%opts WMTS [width=450 height=250 xaxis=None yaxis=None]\n",
- "hv.NdLayout({name: gv.WMTS(wmts)\n",
- " for name, wmts in tiles.items()}, kdims=['Source']).cols(2)"
+ "import geoviews.tile_sources as gts\n",
+ "\n",
+ "hv.Layout([ts.relabel(name) for name, ts in gts.tile_sources.items()]).options(\n",
+ " 'WMTS', xaxis=None, yaxis=None, width=250, height=250\n",
+ ").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",
"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':"
]
},
@@ -88,15 +77,7 @@
"outputs": [],
"source": [
"%%opts WMTS [width=600 height=570]\n",
- "gv.WMTS(tiles['ESRI'], extents=(0, -90, 360, 90), crs=ccrs.PlateCarree()) *\\\n",
- "gv.WMTS(STAMEN_TONER_LABELS).opts(style=dict(level='annotation'))"
- ]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "You may also supply a tuple of tile sources pairing a Bokeh WMTSTileSource object with a simple cartopy string URL, allowing the `gv.WMTS` element to be used for both matplotlib and Bokeh rendering without having to declare separate objects."
+ "gts.ESRI * gts.StamenLabels.options(level='annotation')"
]
},
{
@@ -134,18 +115,13 @@
},
"outputs": [],
"source": [
- "%%opts Overlay [width=600 height=350] \n",
- "%%opts Points (size=0.005 cmap='viridis') [tools=['hover'] size_index=2 color_index=2]\n",
- "(gv.WMTS(tiles['Wikipedia']) *\\\n",
- "population.to(gv.Points, kdims=['Longitude', 'Latitude'],\n",
- " vdims=['Population', 'City', 'Country'], crs=ccrs.PlateCarree()))"
+ "points = population.to(gv.Points, ['Longitude', 'Latitude'], ['Population', 'City', 'Country'])\n",
+ "(gts.Wikipedia * points.options(width=600, height=350, tools=['hover'], size_index=2, color_index=2, size=0.005, cmap='viridis'))"
]
},
{
"cell_type": "markdown",
- "metadata": {
- "collapsed": true
- },
+ "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",
@@ -185,8 +161,10 @@
"metadata": {},
"outputs": [],
"source": [
- "%%opts Polygons [tools=['hover'] width=450 height=600 color_index='leaveVoteshare' colorbar=True toolbar='above' xaxis=None yaxis=None]\n",
- "gv.Polygons(gdf, vdims=['name', 'leaveVoteshare'])"
+ "gv.Polygons(gdf, vdims=['name', 'leaveVoteshare']).options(\n",
+ " tools=['hover'], width=450, height=600, color_index='leaveVoteshare',\n",
+ " colorbar=True, toolbar='above', xaxis=None, yaxis=None\n",
+ ")"
]
},
{
@@ -198,9 +176,7 @@
},
{
"cell_type": "markdown",
- "metadata": {
- "collapsed": true
- },
+ "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."
]
@@ -211,20 +187,13 @@
"metadata": {},
"outputs": [],
"source": [
- "%%opts Overlay [width=600 height=500] Image (cmap='viridis') Feature (line_color='black')\n",
+ "%%opts Overlay [width=600 height=500] Image [tools=['hover']] (cmap='viridis') Feature (line_color='black')\n",
"dataset = xr.open_dataset('../data/pre-industrial.nc')\n",
"air_temperature = gv.Dataset(dataset, kdims=['longitude', 'latitude'],\n",
" group='Pre-industrial air temperature', vdims=['air_temperature'],\n",
" crs=ccrs.PlateCarree())\n",
"air_temperature.to.image() * gf.coastline()"
]
- },
- {
- "cell_type": "markdown",
- "metadata": {},
- "source": [
- "Support for other projections is eventually planned for Bokeh, but meanwhile you should usually be able to use either backend interchangeably as long as you use web Mercator coordinates for display, and the additional interactivity provided by Bokeh is often very useful!"
- ]
}
],
"metadata": {
@@ -244,7 +213,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.6.3"
+ "version": "3.6.4"
},
"widgets": {
"state": {},
@@ -252,5 +221,5 @@
}
},
"nbformat": 4,
- "nbformat_minor": 1
+ "nbformat_minor": 2
}
diff --git a/geoviews/__init__.py b/geoviews/__init__.py
index 6a219c5f..ca42b830 100644
--- a/geoviews/__init__.py
+++ b/geoviews/__init__.py
@@ -11,7 +11,7 @@
from . import operation # noqa (API import)
from . import plotting # noqa (API import)
from . import feature # noqa (API import)
-
+from . import tile_sources # noqa (API import)
__version__ = str(param.version.Version(fpath=__file__, archive_commit="$Format:%h$",
reponame="geoviews"))
diff --git a/geoviews/plotting/bokeh/__init__.py b/geoviews/plotting/bokeh/__init__.py
index 30325f67..b3ea9c98 100644
--- a/geoviews/plotting/bokeh/__init__.py
+++ b/geoviews/plotting/bokeh/__init__.py
@@ -21,6 +21,7 @@
HexTiles)
from ...operation import (project_image, project_shape, project_points,
project_path, project_graph, project_quadmesh)
+from ...tile_sources import _ATTRIBUTIONS
from ...util import geom_to_array
from .plot import GeoPlot, OverlayPlot, DEFAULT_PROJ
from . import callbacks # noqa
@@ -50,8 +51,14 @@ def get_data(self, element, ranges, style):
elif all(kw in element.data for kw in ('{X}', '{Y}', '{Z}')):
tile_source = WMTSTileSource
else:
- raise ValueError('Tile source URL format not recognized.')
- return {}, {'tile_source': tile_source(url=element.data)}, style
+ raise ValueError('Tile source URL format not recognized. '
+ 'Must contain {X}/{Y}/{Z}, {XMIN}/{XMAX}/{YMIN}/{YMAX} '
+ 'or {Q} template strings.')
+ params = {'url': element.data}
+ for key, attribution in _ATTRIBUTIONS.items():
+ if all(k in element.data for k in key):
+ params['attribution'] = attribution
+ return {}, {'tile_source': tile_source(**params)}, style
def _update_glyph(self, renderer, properties, mapping, glyph):
allowed_properties = glyph.properties()
diff --git a/geoviews/tile_sources.py b/geoviews/tile_sources.py
new file mode 100644
index 00000000..e2a688ab
--- /dev/null
+++ b/geoviews/tile_sources.py
@@ -0,0 +1,49 @@
+from .element import WMTS
+
+# Mapping between patterns to match specified as tuples and tuples containing attributions
+_ATTRIBUTIONS = {
+ ('openstreetmap',) : (
+ '© OpenStreetMap contributors'
+ ),
+ ('cartodb') : (
+ '© OpenStreetMap contributors,'
+ '© CartoDB'
+ ),
+ ('cartocdn') : (
+ '© OpenStreetMap contributors,'
+ '© CartoDB'
+ ),
+ ('stamen', 'terrain'): (
+ 'Map tiles by Stamen Design, '
+ 'under CC BY 3.0. '
+ 'Data by OpenStreetMap, '
+ 'under CC BY SA.'
+ ),
+ ('stamen', 'toner'): (
+ 'Map tiles by Stamen Design, '
+ 'under CC BY 3.0. '
+ 'Data by OpenStreetMap, '
+ 'under ODbL.'
+ )
+}
+
+# CartoDB basemaps
+CartoDark = WMTS('https://cartodb-basemaps-4.global.ssl.fastly.net/dark_all/{Z}/{X}/{Y}.png')
+CartoEco = WMTS('http://3.api.cartocdn.com/base-eco/{Z}/{X}/{Y}.png')
+CartoLight = WMTS('https://cartodb-basemaps-4.global.ssl.fastly.net/light_all/{Z}/{X}/{Y}.png')
+CartoMidnight = WMTS('http://3.api.cartocdn.com/base-midnight/{Z}/{X}/{Y}.png')
+
+# Stamen basemaps
+StamenTerrain = WMTS('http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png')
+StamenTerrainRetina = WMTS('http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png')
+StamenWatercolor = WMTS('http://c.tile.stamen.com/watercolor/{Z}/{X}/{Y}.jpg')
+StamenToner = WMTS('http://tile.stamen.com/toner/{Z}/{X}/{Y}.png')
+StamenTonerBackground = WMTS('http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png')
+StamenLabels = WMTS('http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png')
+
+# Miscellaneous
+OSM = WMTS('http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png')
+ESRI = WMTS('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg')
+Wikipedia = WMTS('https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png')
+
+tile_sources = {k: v for k, v in locals().items() if isinstance(v, WMTS)}