From 84459ba089da13f2a699670a44fbe6e88e662e7b Mon Sep 17 00:00:00 2001 From: Martin Fleischmann Date: Tue, 7 Nov 2023 13:53:58 +0100 Subject: [PATCH] use xyzservices instead of templates (#1827) * use xyzservices instead of templates * fix minimap test * more robust check for openstreetmap * lint * rm ENV --- folium/folium.py | 13 ++-- folium/raster_layers.py | 74 +++++++++---------- .../tiles/cartodbdark_matter/attr.txt | 1 - .../tiles/cartodbdark_matter/tiles.txt | 1 - .../templates/tiles/cartodbpositron/attr.txt | 1 - .../templates/tiles/cartodbpositron/tiles.txt | 1 - .../tiles/cartodbpositronnolabels/attr.txt | 1 - .../tiles/cartodbpositronnolabels/tiles.txt | 1 - .../tiles/cartodbpositrononlylabels/attr.txt | 1 - .../tiles/cartodbpositrononlylabels/tiles.txt | 1 - folium/templates/tiles/openstreetmap/attr.txt | 1 - .../templates/tiles/openstreetmap/tiles.txt | 1 - requirements-dev.txt | 1 - requirements.txt | 1 + tests/plugins/test_minimap.py | 2 +- tests/test_folium.py | 34 ++++----- tests/test_raster_layers.py | 10 ++- 17 files changed, 65 insertions(+), 80 deletions(-) delete mode 100644 folium/templates/tiles/cartodbdark_matter/attr.txt delete mode 100644 folium/templates/tiles/cartodbdark_matter/tiles.txt delete mode 100644 folium/templates/tiles/cartodbpositron/attr.txt delete mode 100644 folium/templates/tiles/cartodbpositron/tiles.txt delete mode 100644 folium/templates/tiles/cartodbpositronnolabels/attr.txt delete mode 100644 folium/templates/tiles/cartodbpositronnolabels/tiles.txt delete mode 100644 folium/templates/tiles/cartodbpositrononlylabels/attr.txt delete mode 100644 folium/templates/tiles/cartodbpositrononlylabels/tiles.txt delete mode 100644 folium/templates/tiles/openstreetmap/attr.txt delete mode 100644 folium/templates/tiles/openstreetmap/tiles.txt diff --git a/folium/folium.py b/folium/folium.py index 4cf889244..60e4f905a 100644 --- a/folium/folium.py +++ b/folium/folium.py @@ -82,15 +82,14 @@ class Map(JSCSSMixin, MacroElement): """Create a Map with Folium and Leaflet.js Generate a base map of given width and height with either default - tilesets or a custom tileset URL. The following tilesets are built-in - to Folium. Pass any of the following to the "tiles" keyword: + tilesets or a custom tileset URL. Folium has built-in all tilesets + available in the ``xyzservices`` package. For example, you can pass + any of the following to the "tiles" keyword: - "OpenStreetMap" - - "Mapbox Bright" (Limited levels of zoom for free tiles) - - "Mapbox Control Room" (Limited levels of zoom for free tiles) - - "Cloudmade" (Must pass API key) - - "Mapbox" (Must pass API key) - - "CartoDB" (positron and dark_matter) + - "CartoDB Positron" + - "CartoBD Voyager" + - "NASAGIBS Blue Marble" You can pass a custom tileset to Folium by passing a :class:`xyzservices.TileProvider` or a Leaflet-style diff --git a/folium/raster_layers.py b/folium/raster_layers.py index fe3079b72..ea0e6d180 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -2,10 +2,11 @@ Wraps leaflet TileLayer, WmsTileLayer (TileLayer.WMS), ImageOverlay, and VideoOverlay """ -from typing import TYPE_CHECKING, Any, Callable, Optional, Union +from typing import Any, Callable, Optional, Union +import xyzservices from branca.element import Element, Figure -from jinja2 import Environment, PackageLoader, Template +from jinja2 import Template from folium.map import Layer from folium.utilities import ( @@ -16,12 +17,6 @@ parse_options, ) -if TYPE_CHECKING: - import xyzservices - - -ENV = Environment(loader=PackageLoader("folium", "templates")) - class TileLayer(Layer): """ @@ -30,9 +25,14 @@ class TileLayer(Layer): Parameters ---------- tiles: str or :class:`xyzservices.TileProvider`, default 'OpenStreetMap' - Map tileset to use. Can choose from this list of built-in tiles: + Map tileset to use. Folium has built-in all tilesets + available in the ``xyzservices`` package. For example, you can pass + any of the following to the "tiles" keyword: + - "OpenStreetMap" - - "CartoDB positron", "CartoDB dark_matter" + - "CartoDB Positron" + - "CartoBD Voyager" + - "NASAGIBS Blue Marble" You can pass a custom tileset to Folium by passing a :class:`xyzservices.TileProvider` or a Leaflet-style @@ -90,7 +90,7 @@ class TileLayer(Layer): def __init__( self, - tiles: Union[str, "xyzservices.TileProvider"] = "OpenStreetMap", + tiles: Union[str, xyzservices.TileProvider] = "OpenStreetMap", min_zoom: int = 0, max_zoom: int = 18, max_native_zoom: Optional[int] = None, @@ -104,14 +104,26 @@ def __init__( subdomains: str = "abc", tms: bool = False, opacity: float = 1, - **kwargs + **kwargs, ): - # check for xyzservices.TileProvider without importing it - if isinstance(tiles, dict): + if isinstance(tiles, str): + if tiles.lower() == "openstreetmap": + tiles = "OpenStreetMap Mapnik" + if name is None: + name = "openstreetmap" + try: + tiles = xyzservices.providers.query_name(tiles) + except ValueError: + # no match, likely a custom URL + pass + + if isinstance(tiles, xyzservices.TileProvider): attr = attr if attr else tiles.html_attribution # type: ignore min_zoom = tiles.get("min_zoom", min_zoom) max_zoom = tiles.get("max_zoom", max_zoom) subdomains = tiles.get("subdomains", subdomains) + if name is None: + name = tiles.name.replace(".", "").lower() tiles = tiles.build_url(fill_subdomain=False, scale_factor="{r}") # type: ignore self.tile_name = ( @@ -122,27 +134,9 @@ def __init__( ) self._name = "TileLayer" - tiles_flat = "".join(tiles.lower().strip().split()) - if tiles_flat in {"cloudmade", "mapbox", "mapboxbright", "mapboxcontrolroom"}: - # added in May 2020 after v0.11.0, remove in a future release - raise ValueError( - "Built-in templates for Mapbox and Cloudmade have been removed. " - "You can still use these providers by passing a URL to the `tiles` " - "argument. See the documentation of the `TileLayer` class." - ) - templates = list( - ENV.list_templates(filter_func=lambda x: x.startswith("tiles/")) - ) - tile_template = "tiles/" + tiles_flat + "/tiles.txt" - attr_template = "tiles/" + tiles_flat + "/attr.txt" - - if tile_template in templates and attr_template in templates: - self.tiles = ENV.get_template(tile_template).render() - attr = ENV.get_template(attr_template).render() - else: - self.tiles = tiles - if not attr: - raise ValueError("Custom tiles must have an attribution.") + self.tiles = tiles + if not attr: + raise ValueError("Custom tiles must have an attribution.") self.options = parse_options( min_zoom=min_zoom, @@ -154,7 +148,7 @@ def __init__( detect_retina=detect_retina, tms=tms, opacity=opacity, - **kwargs + **kwargs, ) @@ -219,7 +213,7 @@ def __init__( overlay: bool = True, control: bool = True, show: bool = True, - **kwargs + **kwargs, ): super().__init__(name=name, overlay=overlay, control=control, show=show) self.url = url @@ -231,7 +225,7 @@ def __init__( transparent=transparent, version=version, attribution=attr, - **kwargs + **kwargs, ) if cql_filter: # special parameter that shouldn't be camelized @@ -309,7 +303,7 @@ def __init__( overlay: bool = True, control: bool = True, show: bool = True, - **kwargs + **kwargs, ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "ImageOverlay" @@ -406,7 +400,7 @@ def __init__( overlay: bool = True, control: bool = True, show: bool = True, - **kwargs: TypeJsonValue + **kwargs: TypeJsonValue, ): super().__init__(name=name, overlay=overlay, control=control, show=show) self._name = "VideoOverlay" diff --git a/folium/templates/tiles/cartodbdark_matter/attr.txt b/folium/templates/tiles/cartodbdark_matter/attr.txt deleted file mode 100644 index 0892c0cc3..000000000 --- a/folium/templates/tiles/cartodbdark_matter/attr.txt +++ /dev/null @@ -1 +0,0 @@ -© OpenStreetMap contributors © CartoDB, CartoDB attributions diff --git a/folium/templates/tiles/cartodbdark_matter/tiles.txt b/folium/templates/tiles/cartodbdark_matter/tiles.txt deleted file mode 100644 index ebf805b3e..000000000 --- a/folium/templates/tiles/cartodbdark_matter/tiles.txt +++ /dev/null @@ -1 +0,0 @@ -https://cartodb-basemaps-{s}.global.ssl.fastly.net/dark_all/{z}/{x}/{y}.png diff --git a/folium/templates/tiles/cartodbpositron/attr.txt b/folium/templates/tiles/cartodbpositron/attr.txt deleted file mode 100644 index 0892c0cc3..000000000 --- a/folium/templates/tiles/cartodbpositron/attr.txt +++ /dev/null @@ -1 +0,0 @@ -© OpenStreetMap contributors © CartoDB, CartoDB attributions diff --git a/folium/templates/tiles/cartodbpositron/tiles.txt b/folium/templates/tiles/cartodbpositron/tiles.txt deleted file mode 100644 index 316884cc5..000000000 --- a/folium/templates/tiles/cartodbpositron/tiles.txt +++ /dev/null @@ -1 +0,0 @@ -https://cartodb-basemaps-{s}.global.ssl.fastly.net/light_all/{z}/{x}/{y}.png diff --git a/folium/templates/tiles/cartodbpositronnolabels/attr.txt b/folium/templates/tiles/cartodbpositronnolabels/attr.txt deleted file mode 100644 index 43c55c25a..000000000 --- a/folium/templates/tiles/cartodbpositronnolabels/attr.txt +++ /dev/null @@ -1 +0,0 @@ -(c) OpenStreetMap contributors (c) CartoDB, CartoDB attributions diff --git a/folium/templates/tiles/cartodbpositronnolabels/tiles.txt b/folium/templates/tiles/cartodbpositronnolabels/tiles.txt deleted file mode 100644 index b92e71915..000000000 --- a/folium/templates/tiles/cartodbpositronnolabels/tiles.txt +++ /dev/null @@ -1 +0,0 @@ -https://{s}.basemaps.cartocdn.com/light_nolabels/{z}/{x}/{y}{r}.png diff --git a/folium/templates/tiles/cartodbpositrononlylabels/attr.txt b/folium/templates/tiles/cartodbpositrononlylabels/attr.txt deleted file mode 100644 index 43c55c25a..000000000 --- a/folium/templates/tiles/cartodbpositrononlylabels/attr.txt +++ /dev/null @@ -1 +0,0 @@ -(c) OpenStreetMap contributors (c) CartoDB, CartoDB attributions diff --git a/folium/templates/tiles/cartodbpositrononlylabels/tiles.txt b/folium/templates/tiles/cartodbpositrononlylabels/tiles.txt deleted file mode 100644 index f5e0b0c85..000000000 --- a/folium/templates/tiles/cartodbpositrononlylabels/tiles.txt +++ /dev/null @@ -1 +0,0 @@ -https://{s}.basemaps.cartocdn.com/light_only_labels/{z}/{x}/{y}{r}.png diff --git a/folium/templates/tiles/openstreetmap/attr.txt b/folium/templates/tiles/openstreetmap/attr.txt deleted file mode 100644 index bfabc61be..000000000 --- a/folium/templates/tiles/openstreetmap/attr.txt +++ /dev/null @@ -1 +0,0 @@ -Data by © OpenStreetMap, under ODbL. diff --git a/folium/templates/tiles/openstreetmap/tiles.txt b/folium/templates/tiles/openstreetmap/tiles.txt deleted file mode 100644 index 26261ed37..000000000 --- a/folium/templates/tiles/openstreetmap/tiles.txt +++ /dev/null @@ -1 +0,0 @@ -https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png diff --git a/requirements-dev.txt b/requirements-dev.txt index 61d92d317..c01cd6f49 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -36,4 +36,3 @@ types-requests vega_datasets vincent wheel -xyzservices diff --git a/requirements.txt b/requirements.txt index b06947b5f..dff8e41f4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ branca>=0.6.0 jinja2>=2.9 numpy requests +xyzservices diff --git a/tests/plugins/test_minimap.py b/tests/plugins/test_minimap.py index 5919c9cde..f80f31152 100644 --- a/tests/plugins/test_minimap.py +++ b/tests/plugins/test_minimap.py @@ -25,4 +25,4 @@ def test_minimap(): out = normalize(m._parent.render()) # verify that tiles are being used - assert r"https://{s}.tile.openstreetmap.org" in out + assert r"https://tile.openstreetmap.org" in out diff --git a/tests/test_folium.py b/tests/test_folium.py index 4d119176f..36533e193 100644 --- a/tests/test_folium.py +++ b/tests/test_folium.py @@ -13,13 +13,13 @@ import numpy as np import pandas as pd import pytest +import xyzservices.providers as xyz from jinja2 import Environment, PackageLoader from jinja2.utils import htmlsafe_json_dumps import folium from folium import TileLayer from folium.features import Choropleth, GeoJson -from folium.raster_layers import ENV rootpath = os.path.abspath(os.path.dirname(__file__)) @@ -106,24 +106,24 @@ def test_init(self): }, } - def test_builtin_tile(self): + @pytest.mark.parametrize( + "tiles,provider", + [ + ("OpenStreetMap", xyz.OpenStreetMap.Mapnik), + ("CartoDB positron", xyz.CartoDB.Positron), + ("CartoDB dark_matter", xyz.CartoDB.DarkMatter), + ], + ) + def test_builtin_tile(self, tiles, provider): """Test custom maptiles.""" - default_tiles = [ - "OpenStreetMap", - "CartoDB positron", - "CartoDB dark_matter", - ] - for tiles in default_tiles: - m = folium.Map(location=[45.5236, -122.6750], tiles=tiles) - tiles = "".join(tiles.lower().strip().split()) - url = "tiles/{}/tiles.txt".format - attr = "tiles/{}/attr.txt".format - url = ENV.get_template(url(tiles)).render() - attr = ENV.get_template(attr(tiles)).render() - - assert m._children[tiles].tiles == url - assert htmlsafe_json_dumps(attr) in m._parent.render() + m = folium.Map(location=[45.5236, -122.6750], tiles=tiles) + tiles = "".join(tiles.lower().strip().split()) + url = provider.build_url(fill_subdomain=False, scale_factor="{r}") + attr = provider.html_attribution + + assert m._children[tiles.replace("_", "")].tiles == url + assert htmlsafe_json_dumps(attr) in m._parent.render() bounds = m.get_bounds() assert bounds == [[None, None], [None, None]], bounds diff --git a/tests/test_raster_layers.py b/tests/test_raster_layers.py index c4cefec09..600d87664 100644 --- a/tests/test_raster_layers.py +++ b/tests/test_raster_layers.py @@ -3,6 +3,7 @@ ------------------ """ +import pytest import xyzservices from jinja2 import Template @@ -113,10 +114,11 @@ def test_image_overlay(): assert bounds == [[0, -180], [90, 180]], bounds -def test_xyzservices(): - m = folium.Map( - [48.0, 5.0], tiles=xyzservices.providers.CartoDB.DarkMatter, zoom_start=6 - ) +@pytest.mark.parametrize( + "tiles", ["CartoDB DarkMatter", xyzservices.providers.CartoDB.DarkMatter] +) +def test_xyzservices(tiles): + m = folium.Map([48.0, 5.0], tiles=tiles, zoom_start=6) folium.raster_layers.TileLayer( tiles=xyzservices.providers.CartoDB.Positron,