Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Plotly map tiles support #4686

Merged
merged 18 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion examples/reference/elements/bokeh/Tiles.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"<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",
" <dt>Backends</dt>\n",
" <dd><a href='./Tiles.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='../plotly/Tiles.ipynb'>Plotly</a></dd>\n",
"</dl>\n",
"</div>"
]
Expand Down
130 changes: 130 additions & 0 deletions examples/reference/elements/plotly/Tiles.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
{
"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>Plotly</dd>\n",
" <dt>Backends</dt>\n",
" <dd><a href='../bokeh/Tiles.ipynb'>Bokeh</a></dd>\n",
" <dd><a href='Tiles.ipynb'>Plotly</a></dd>\n",
"</dl>\n",
"</div>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import holoviews as hv\n",
"from holoviews import opts\n",
"hv.extension('plotly')"
]
},
{
"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 that describe the location and zoom level. In the most common case of a WMTS tile source, the URL 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.\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 that return a ``Tiles`` element:"
]
},
{
"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": {
"pycharm": {
"name": "#%%\n"
}
},
"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),\n",
" opts.Layout(hspacing=10, vspacing=40)\n",
").cols(4)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"For full documentation and the available style and plot options, use ``hv.help(hv.Tiles).``"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Vector Mapbox Tiles\n",
"\n",
"In addition to displaying raster tiles loaded from a tile source URL, the Plotly backend can also display vector tiles provided by Mapbox. A vector tile style is specified using the `mapboxstyle` option, and requires a Mapbox\n",
"access token to be provided as the `accesstoken` option.\n",
"\n",
"```python\n",
"hv.Tiles('').opts(\n",
" mapboxstyle=\"dark\",\n",
" accesstoken=\"pk...\",\n",
" width=600,\n",
" height=600\n",
")\n",
"```"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.9"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
23 changes: 23 additions & 0 deletions holoviews/element/tiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from ..core import util
from ..core.dimension import Dimension
from ..core.element import Element2D
from ..util.transform import lon_lat_to_easting_northing, easting_northing_to_lon_lat


class Tiles(Element2D):
Expand Down Expand Up @@ -54,6 +55,28 @@ def range(self, dim, data_range=True, dimension_range=True):
def dimension_values(self, dimension, expanded=True, flat=True):
return np.array([])

@staticmethod
def lon_lat_to_easting_northing(longitude, latitude):
"""
Projects the given longitude, latitude values into Web Mercator
(aka Pseudo-Mercator or EPSG:3857) coordinates.

See docstring for holoviews.util.transform.lon_lat_to_easting_northing
for more information
"""
return lon_lat_to_easting_northing(longitude, latitude)

@staticmethod
def easting_northing_to_lon_lat(easting, northing):
"""
Projects the given easting, northing values into
longitude, latitude coordinates.

See docstring for holoviews.util.transform.easting_northing_to_lon_lat
for more information
"""
return easting_northing_to_lon_lat(easting, northing)


# Mapping between patterns to match specified as tuples and tuples containing attributions
_ATTRIBUTIONS = {
Expand Down
10 changes: 10 additions & 0 deletions holoviews/plotting/plotly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from .renderer import PlotlyRenderer

from .annotation import * # noqa (API import)
from .tiles import * # noqa (API import)
from .element import * # noqa (API import)
from .chart import * # noqa (API import)
from .chart3d import * # noqa (API import)
Expand Down Expand Up @@ -72,6 +73,7 @@

# Annotations
Labels: LabelPlot,
Tiles: TilePlot,

# Shapes
Box: PathShapePlot,
Expand Down Expand Up @@ -102,6 +104,7 @@
plot.padding = 0

dflt_cmap = 'fire'
dflt_shape_line_color = '#2a3f5f' # Line color of default plotly template

point_size = np.sqrt(6) # Matches matplotlib default
Cycle.default_cycles['default_colors'] = ['#30a2da', '#fc4f30', '#e5ae38',
Expand Down Expand Up @@ -129,3 +132,10 @@
# Annotations
options.VSpan = Options('style', fillcolor=Cycle(), opacity=0.5)
options.HSpan = Options('style', fillcolor=Cycle(), opacity=0.5)

# Shapes
options.Rectangles = Options('style', line_color=dflt_shape_line_color)
options.Bounds = Options('style', line_color=dflt_shape_line_color)
options.Path = Options('style', line_color=dflt_shape_line_color)
options.Segments = Options('style', line_color=dflt_shape_line_color)
options.Box = Options('style', line_color=dflt_shape_line_color)
21 changes: 16 additions & 5 deletions holoviews/plotting/plotly/annotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import param

from .chart import ScatterPlot
from ...element import Tiles


class LabelPlot(ScatterPlot):
Expand All @@ -17,12 +18,16 @@ class LabelPlot(ScatterPlot):

_nonvectorized_styles = []

trace_kwargs = {'type': 'scatter', 'mode': 'text'}

_style_key = 'textfont'

def get_data(self, element, ranges, style):
x, y = ('y', 'x') if self.invert_axes else ('x', 'y')
@classmethod
def trace_kwargs(cls, is_geo=False, **kwargs):
if is_geo:
return {'type': 'scattermapbox', 'mode': 'text'}
else:
return {'type': 'scatter', 'mode': 'text'}

def get_data(self, element, ranges, style, is_geo=False, **kwargs):
text_dim = element.vdims[0]
xs = element.dimension_values(0)
if self.xoffset:
Expand All @@ -31,4 +36,10 @@ def get_data(self, element, ranges, style):
if self.yoffset:
ys = ys + self.yoffset
text = [text_dim.pprint_value(v) for v in element.dimension_values(2)]
return [{x: xs, y: ys, 'text': text}]

if is_geo:
lon, lat = Tiles.easting_northing_to_lon_lat(xs, ys)
return [{"lon": lon, "lat": lat, 'text': text}]
else:
x, y = ('y', 'x') if self.invert_axes else ('x', 'y')
return [{x: xs, y: ys, 'text': text}]
Loading