Skip to content

Commit b97053b

Browse files
committed
Fix for issue 1359
This includes a structural change. I added a Class object with a include method. This follows leaflet's `L.Class.include` statement. This allows users to override Leaflet class behavior. The motivating example for this can be found in the added `test_include` in `test_map.py`. Using an include, users can override the `createTile` method of `L.TileLayer` and add a headers.
1 parent e3ca7ba commit b97053b

File tree

4 files changed

+111
-6
lines changed

4 files changed

+111
-6
lines changed

folium/elements.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,23 @@ def __init__(self, element_name: str, element_parent_name: str):
148148
super().__init__()
149149
self.element_name = element_name
150150
self.element_parent_name = element_parent_name
151+
152+
153+
class IncludeStatement(MacroElement):
154+
"""Generate an include statement on a class."""
155+
156+
_template = Template(
157+
"""
158+
L.{{ this.class_name }}.include(
159+
{{ this.options | tojavascript }}
160+
)
161+
"""
162+
)
163+
164+
def __init__(self, class_name: str, **kwargs):
165+
super().__init__()
166+
self.class_name = class_name
167+
self.options = kwargs
168+
169+
def render(self, *args, **kwargs):
170+
return super().render(*args, **kwargs)

folium/features.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636

3737
from folium.elements import JSCSSMixin
3838
from folium.folium import Map
39-
from folium.map import FeatureGroup, Icon, Layer, Marker, Popup, Tooltip
39+
from folium.map import Class, FeatureGroup, Icon, Layer, Marker, Popup, Tooltip
4040
from folium.template import Template
4141
from folium.utilities import (
4242
JsCode,
@@ -2023,7 +2023,7 @@ def __init__(
20232023
self.add_child(PolyLine(val, color=key, weight=weight, opacity=opacity))
20242024

20252025

2026-
class Control(JSCSSMixin, MacroElement):
2026+
class Control(JSCSSMixin, Class):
20272027
"""
20282028
Add a Leaflet Control object to the map
20292029

folium/map.py

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
"""
55

66
import warnings
7-
from collections import OrderedDict
7+
from collections import OrderedDict, defaultdict
88
from typing import TYPE_CHECKING, Optional, Sequence, Union, cast
99

1010
from branca.element import Element, Figure, Html, MacroElement
1111

12-
from folium.elements import ElementAddToElement, EventHandler
12+
from folium.elements import ElementAddToElement, EventHandler, IncludeStatement
1313
from folium.template import Template
1414
from folium.utilities import (
1515
JsCode,
@@ -22,11 +22,47 @@
2222
validate_location,
2323
)
2424

25+
26+
class classproperty:
27+
def __init__(self, f):
28+
self.f = f
29+
30+
def __get__(self, obj, owner):
31+
return self.f(owner)
32+
33+
2534
if TYPE_CHECKING:
2635
from folium.features import CustomIcon, DivIcon
2736

2837

29-
class Evented(MacroElement):
38+
class Class(MacroElement):
39+
"""The root class of the leaflet class hierarchy"""
40+
41+
_includes = defaultdict(dict)
42+
43+
@classmethod
44+
def include(cls, **kwargs):
45+
cls._includes[cls].update(**kwargs)
46+
47+
@classproperty
48+
def includes(cls):
49+
return cls._includes[cls]
50+
51+
def render(self, **kwargs):
52+
figure = self.get_root()
53+
assert isinstance(
54+
figure, Figure
55+
), "You cannot render this Element if it is not in a Figure."
56+
if self.includes:
57+
stmt = IncludeStatement(self._name, **self.includes)
58+
figure.script.add_child(
59+
Element(stmt._template.render(this=stmt, kwargs=self.includes)),
60+
index=-1,
61+
)
62+
super().render(**kwargs)
63+
64+
65+
class Evented(Class):
3066
"""The base class for Layer and Map
3167
3268
Adds the `on` and `once` methods for event handling capabilities.

tests/test_map.py

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from folium import GeoJson, Map, TileLayer
1313
from folium.map import CustomPane, Icon, LayerControl, Marker, Popup
14-
from folium.utilities import normalize
14+
from folium.utilities import JsCode, normalize
1515

1616
tmpl = """
1717
<div id="{id}"
@@ -148,6 +148,55 @@ def test_popup_show():
148148
assert normalize(rendered) == normalize(expected)
149149

150150

151+
def test_include():
152+
create_tile = """
153+
function(coords, done) {
154+
const url = this.getTileUrl(coords);
155+
const img = document.createElement('img');
156+
fetch(url, {
157+
headers: {
158+
"Authorization": "Bearer <Token>"
159+
},
160+
})
161+
.then((response) => {
162+
img.src = URL.createObjectURL(response.body);
163+
done(null, img);
164+
})
165+
return img;
166+
}
167+
"""
168+
TileLayer.include(create_tile=JsCode(create_tile))
169+
tiles = TileLayer(
170+
tiles="OpenStreetMap",
171+
)
172+
m = Map(
173+
tiles=tiles,
174+
)
175+
rendered = m.get_root().render()
176+
expected = """
177+
L.TileLayer.include({
178+
"createTile":
179+
function(coords, done) {
180+
console.log("creating tile");
181+
const url = this.getTileUrl(coords);
182+
const img = document.createElement('img');
183+
fetch(url, {
184+
headers: {
185+
"Authorization": "Bearer <Token>"
186+
},
187+
})
188+
.then((response) => {
189+
img.src = URL.createObjectURL(response.body);
190+
done(null, img);
191+
})
192+
return img;
193+
},
194+
})
195+
"""
196+
197+
assert normalize(expected) in normalize(rendered)
198+
199+
151200
def test_popup_backticks():
152201
m = Map()
153202
popup = Popup("back`tick`tick").add_to(m)

0 commit comments

Comments
 (0)