Skip to content

Commit

Permalink
use xyzservices instead of templates (python-visualization#1827)
Browse files Browse the repository at this point in the history
* use xyzservices instead of templates

* fix minimap test

* more robust check for openstreetmap

* lint

* rm ENV
  • Loading branch information
martinfleis authored Nov 7, 2023
1 parent 937693f commit 84459ba
Show file tree
Hide file tree
Showing 17 changed files with 65 additions and 80 deletions.
13 changes: 6 additions & 7 deletions folium/folium.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
74 changes: 34 additions & 40 deletions folium/raster_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -16,12 +17,6 @@
parse_options,
)

if TYPE_CHECKING:
import xyzservices


ENV = Environment(loader=PackageLoader("folium", "templates"))


class TileLayer(Layer):
"""
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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 = (
Expand All @@ -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,
Expand All @@ -154,7 +148,7 @@ def __init__(
detect_retina=detect_retina,
tms=tms,
opacity=opacity,
**kwargs
**kwargs,
)


Expand Down Expand Up @@ -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
Expand All @@ -231,7 +225,7 @@ def __init__(
transparent=transparent,
version=version,
attribution=attr,
**kwargs
**kwargs,
)
if cql_filter:
# special parameter that shouldn't be camelized
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbdark_matter/attr.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbdark_matter/tiles.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositron/attr.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositron/tiles.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositronnolabels/attr.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositronnolabels/tiles.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositrononlylabels/attr.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/cartodbpositrononlylabels/tiles.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/openstreetmap/attr.txt

This file was deleted.

1 change: 0 additions & 1 deletion folium/templates/tiles/openstreetmap/tiles.txt

This file was deleted.

1 change: 0 additions & 1 deletion requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,3 @@ types-requests
vega_datasets
vincent
wheel
xyzservices
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ branca>=0.6.0
jinja2>=2.9
numpy
requests
xyzservices
2 changes: 1 addition & 1 deletion tests/plugins/test_minimap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
34 changes: 17 additions & 17 deletions tests/test_folium.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__))

Expand Down Expand Up @@ -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
Expand Down
10 changes: 6 additions & 4 deletions tests/test_raster_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
------------------
"""
import pytest
import xyzservices
from jinja2 import Template

Expand Down Expand Up @@ -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,
Expand Down

0 comments on commit 84459ba

Please sign in to comment.