-
-
Notifications
You must be signed in to change notification settings - Fork 404
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Added Tiles element from geoviews (#3515)
- Loading branch information
1 parent
1ce1649
commit e3b3d4d
Showing
7 changed files
with
327 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"<div class=\"contentcontainer med left\" style=\"margin-left: -50px;\">\n", | ||
"<dl class=\"dl-horizontal\">\n", | ||
" <dt>Title</dt> <dd> Tiles Element</dd>\n", | ||
" <dt>Dependencies</dt> <dd>Bokeh</dd>\n", | ||
" <dt>Backends</dt> <dd><a href='./Tiles.ipynb'>Bokeh</a></dd>\n", | ||
"</dl>\n", | ||
"</div>" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import numpy as np\n", | ||
"import holoviews as hv\n", | ||
"from holoviews import opts, dim\n", | ||
"hv.extension('bokeh')" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"The ``Tiles`` element represents a so called web mapping tile source usually used for geographic plots, which fetches tiles appropriate to the current zoom level. To declare a ``Tiles`` element simply provide a URL to the tile server, a standard tile server URL has a number of templated variables which describe the location and zoom level. In the most common case that is a WMTS tile source which looks like this:\n", | ||
"\n", | ||
" 'https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png'\n", | ||
" \n", | ||
"Here ``{X}``, ``{Y}`` and ``{Z}`` describe the location and zoom level of each tile. Alternative formats include bbox tile sources with ``{XMIN}``, ``{XMAX}``, ``{YMIN}``, ``{YMAX}`` variables, and quad-key tile sources with a single ``{Q}`` variable.\n", | ||
"\n", | ||
"A simple example of a WMTS tile source is the Wikipedia maps:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"hv.Tiles('https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png', name=\"Wikipedia\").opts(width=600, height=550)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"One thing to note about tile sources is that they are always defined in the [pseudo-Mercator projection](https://epsg.io/3857), which means that if you want to overlay any data on top of a tile source the values have to be expressed as eastings and northings. If you have data in another projection, e.g. latitudes and longitudes, it may make sense to use [GeoViews](http://geoviews.org/) for it to handle the projections for you.\n", | ||
"\n", | ||
"Both HoloViews and GeoViews provides a number of tile sources by default, provided by CartoDB, Stamen, OpenStreetMap, Esri and Wikipedia. These can be imported from the ``holoviews.element.tiles`` module and are provided as callable functions which return a ``Tiles`` element:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"hv.element.tiles.EsriImagery().opts(width=600, height=550)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"The full set of predefined tile sources can be accessed on the ``holoviews.element.tiles.tile_sources`` dictionary:" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"hv.Layout([ts().relabel(name) for name, ts in hv.element.tiles.tile_sources.items()]).opts(\n", | ||
" opts.Tiles(xaxis=None, yaxis=None, width=225, height=225)).cols(4)" | ||
] | ||
}, | ||
{ | ||
"cell_type": "markdown", | ||
"metadata": {}, | ||
"source": [ | ||
"For full documentation and the available style and plot options, use ``hv.help(hv.Tiles).``" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"language_info": { | ||
"name": "python", | ||
"pygments_lexer": "ipython3" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 2 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
from __future__ import absolute_import, division, unicode_literals | ||
|
||
from types import FunctionType | ||
|
||
import param | ||
import numpy as np | ||
|
||
from ..core import util | ||
from ..core.dimension import Dimension | ||
from ..core.element import Element2D | ||
|
||
|
||
class Tiles(Element2D): | ||
""" | ||
The Tiles element represents tile sources, specified as URL | ||
containing different template variables. These variables | ||
correspond to three different formats for specifying the spatial | ||
location and zoom level of the requested tiles: | ||
* Web mapping tiles sources containing {x}, {y}, and {z} variables | ||
* Bounding box tile sources containing {XMIN}, {XMAX}, {YMIN}, {YMAX} variables | ||
* Quadkey tile sources containin a {Q} variable | ||
Tiles are defined in a pseudo-Mercator projection (EPSG:3857) | ||
defined as eastings and northings. Any data overlaid on a tile | ||
source therefore has to be defined in those coordinates or be | ||
projected (e.g. using GeoViews). | ||
""" | ||
|
||
kdims = param.List(default=[Dimension('x'), Dimension('y')], | ||
bounds=(2, 2), constant=True, doc=""" | ||
The key dimensions of a geometry represent the x- and y- | ||
coordinates in a 2D space.""") | ||
|
||
group = param.String(default='Tiles', constant=True) | ||
|
||
def __init__(self, data, kdims=None, vdims=None, **params): | ||
try: | ||
from bokeh.models import MercatorTileSource | ||
except: | ||
MercatorTileSource = None | ||
if MercatorTileSource and isinstance(data, MercatorTileSource): | ||
data = data.url | ||
elif not isinstance(data, util.basestring): | ||
raise TypeError('%s data should be a tile service URL not a %s type.' | ||
% (type(self).__name__, type(data).__name__) ) | ||
super(Tiles, self).__init__(data, kdims=kdims, vdims=vdims, **params) | ||
|
||
def range(self, dim, data_range=True, dimension_range=True): | ||
return np.nan, np.nan | ||
|
||
def dimension_values(self, dimension, expanded=True, flat=True): | ||
return np.array([]) | ||
|
||
|
||
# Mapping between patterns to match specified as tuples and tuples containing attributions | ||
_ATTRIBUTIONS = { | ||
('openstreetmap',) : ( | ||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | ||
), | ||
('cartodb',) : ( | ||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' | ||
'© <a href="https://cartodb.com/attributions">CartoDB</a>' | ||
), | ||
('cartocdn',) : ( | ||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors, ' | ||
'© <a href="https://cartodb.com/attributions">CartoDB</a>' | ||
), | ||
('stamen', 'com/t') : ( # to match both 'toner' and 'terrain' | ||
'Map tiles by <a href="https://stamen.com">Stamen Design</a>, ' | ||
'under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. ' | ||
'Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, ' | ||
'under <a href="https://www.openstreetmap.org/copyright">ODbL</a>.' | ||
), | ||
('stamen', 'watercolor') : ( | ||
'Map tiles by <a href="https://stamen.com">Stamen Design</a>, ' | ||
'under <a href="https://creativecommons.org/licenses/by/3.0">CC BY 3.0</a>. ' | ||
'Data by <a href="https://openstreetmap.org">OpenStreetMap</a>, ' | ||
'under <a href="https://creativecommons.org/licenses/by-sa/3.0">CC BY SA</a>.' | ||
), | ||
('wikimedia',) : ( | ||
'© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors' | ||
), | ||
('arcgis','Terrain') : ( | ||
'© <a href="http://downloads.esri.com/ArcGISOnline/docs/tou_summary.pdf">Esri</a>, ' | ||
'USGS, NOAA' | ||
), | ||
('arcgis','Reference') : ( | ||
'© <a href="http://downloads.esri.com/ArcGISOnline/docs/tou_summary.pdf">Esri</a>, ' | ||
'Garmin, USGS, NPS' | ||
), | ||
('arcgis','Imagery') : ( | ||
'© <a href="http://downloads.esri.com/ArcGISOnline/docs/tou_summary.pdf">Esri</a>, ' | ||
'Earthstar Geographics' | ||
), | ||
('arcgis','NatGeo') : ( | ||
'© <a href="http://downloads.esri.com/ArcGISOnline/docs/tou_summary.pdf">Esri</a>, ' | ||
'NatGeo, Garmin, HERE, UNEP-WCMC, USGS, NASA, ESA, METI, NRCAN, GEBCO, NOAA, Increment P' | ||
), | ||
('arcgis','USA_Topo') : ( | ||
'© <a href="http://downloads.esri.com/ArcGISOnline/docs/tou_summary.pdf">Esri</a>, ' | ||
'NatGeo, i-cubed' | ||
) | ||
} | ||
|
||
# CartoDB basemaps | ||
CartoDark = lambda: Tiles('https://cartodb-basemaps-4.global.ssl.fastly.net/dark_all/{Z}/{X}/{Y}.png', name="CartoDark") | ||
CartoEco = lambda: Tiles('http://3.api.cartocdn.com/base-eco/{Z}/{X}/{Y}.png', name="CartoEco") | ||
CartoLight = lambda: Tiles('https://cartodb-basemaps-4.global.ssl.fastly.net/light_all/{Z}/{X}/{Y}.png', name="CartoLight") | ||
CartoMidnight = lambda: Tiles('http://3.api.cartocdn.com/base-midnight/{Z}/{X}/{Y}.png', name="CartoMidnight") | ||
|
||
# Stamen basemaps | ||
StamenTerrain = lambda: Tiles('http://tile.stamen.com/terrain/{Z}/{X}/{Y}.png', name="StamenTerrain") | ||
StamenTerrainRetina = lambda: Tiles('http://tile.stamen.com/terrain/{Z}/{X}/{Y}@2x.png', name="StamenTerrainRetina") | ||
StamenWatercolor = lambda: Tiles('http://tile.stamen.com/watercolor/{Z}/{X}/{Y}.jpg', name="StamenWatercolor") | ||
StamenToner = lambda: Tiles('http://tile.stamen.com/toner/{Z}/{X}/{Y}.png', name="StamenToner") | ||
StamenTonerBackground = lambda: Tiles('http://tile.stamen.com/toner-background/{Z}/{X}/{Y}.png', name="StamenTonerBackground") | ||
StamenLabels = lambda: Tiles('http://tile.stamen.com/toner-labels/{Z}/{X}/{Y}.png', name="StamenLabels") | ||
|
||
# Esri maps (see https://server.arcgisonline.com/arcgis/rest/services for the full list) | ||
EsriImagery = lambda: Tiles('https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{Z}/{Y}/{X}.jpg', name="EsriImagery") | ||
EsriNatGeo = lambda: Tiles('https://server.arcgisonline.com/ArcGIS/rest/services/NatGeo_World_Map/MapServer/tile/{Z}/{Y}/{X}', name="EsriNatGeo") | ||
EsriUSATopo = lambda: Tiles('https://server.arcgisonline.com/ArcGIS/rest/services/USA_Topo_Maps/MapServer/tile/{Z}/{Y}/{X}', name="EsriUSATopo") | ||
EsriTerrain = lambda: Tiles('https://server.arcgisonline.com/ArcGIS/rest/services/World_Terrain_Base/MapServer/tile/{Z}/{Y}/{X}', name="EsriTerrain") | ||
EsriReference = lambda: Tiles('http://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Reference_Overlay/MapServer/tile/{Z}/{Y}/{X}', name="EsriReference") | ||
ESRI = EsriImagery # For backwards compatibility with gv 1.5 | ||
|
||
# Miscellaneous | ||
OSM = lambda: Tiles('http://c.tile.openstreetmap.org/{Z}/{X}/{Y}.png', name="OSM") | ||
Wikipedia = lambda: Tiles('https://maps.wikimedia.org/osm-intl/{Z}/{X}/{Y}@2x.png', name="Wikipedia") | ||
|
||
tile_sources = {k: v for k, v in locals().items() if isinstance(v, FunctionType) and k != 'ESRI'} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
from __future__ import absolute_import, division, unicode_literals | ||
|
||
import numpy as np | ||
|
||
from bokeh.models import WMTSTileSource, BBoxTileSource, QUADKEYTileSource, SaveTool | ||
|
||
from ...core import util | ||
from ...core.options import SkipRendering | ||
from ...element.tiles import _ATTRIBUTIONS | ||
from .element import ElementPlot | ||
|
||
|
||
class TilePlot(ElementPlot): | ||
|
||
style_opts = ['alpha', 'render_parents', 'level', 'smoothing', 'min_zoom', 'max_zoom'] | ||
|
||
def get_extents(self, element, ranges, range_type='combined'): | ||
extents = super(TilePlot, self).get_extents(element, ranges, range_type) | ||
if (not self.overlaid and all(e is None or not np.isfinite(e) for e in extents) | ||
and range_type in ('combined', 'data')): | ||
x0, x1 = (-20037508.342789244, 20037508.342789244) | ||
y0, y1 = (-20037508.342789255, 20037508.342789244) | ||
global_extent = (x0, y0, x1, y1) | ||
return global_extent | ||
return extents | ||
|
||
def get_data(self, element, ranges, style): | ||
if not isinstance(element.data, util.basestring): | ||
SkipRendering("WMTS element data must be a URL string, " | ||
"bokeh cannot render %r" % element.data) | ||
if '{Q}' in element.data: | ||
tile_source = QUADKEYTileSource | ||
elif all(kw in element.data for kw in ('{XMIN}', '{XMAX}', '{YMIN}', '{YMAX}')): | ||
tile_source = BBoxTileSource | ||
elif all(kw in element.data for kw in ('{X}', '{Y}', '{Z}')): | ||
tile_source = WMTSTileSource | ||
else: | ||
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 zoom in ('min_zoom', 'max_zoom'): | ||
if zoom in style: | ||
params[zoom] = style[zoom] | ||
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, source=None, data=None): | ||
glyph.url = mapping['tile_source'].url | ||
glyph.update(**{k: v for k, v in properties.items() | ||
if k in glyph.properties()}) | ||
renderer.update(**{k: v for k, v in properties.items() | ||
if k in renderer.properties()}) | ||
|
||
def _init_glyph(self, plot, mapping, properties): | ||
""" | ||
Returns a Bokeh glyph object. | ||
""" | ||
tile_source = mapping['tile_source'] | ||
level = properties.pop('level', 'glyph') | ||
renderer = plot.add_tile(tile_source, level=level) | ||
renderer.alpha = properties.get('alpha', 1) | ||
|
||
# Remove save tool | ||
plot.tools = [t for t in plot.tools if not isinstance(t, SaveTool)] | ||
return renderer, tile_source |
Oops, something went wrong.