Skip to content

Commit 1a9bed8

Browse files
authored
Add layers in Layer (#1690)
* Add layers to map in Layer * SideBySideLayers is not a Layer * TimestampedWmsTileLayers is not a Layer * Remove experiment with not adding base layers * fix wrong test name * Update tests * Fix TimeSliderChoropleth * Update docstring of `show` argument * code style * docstring on `show` for `TileLayer` * Update LayerControl example in notebook
1 parent 1e9d4fe commit 1a9bed8

17 files changed

+1160
-68
lines changed

examples/Features.ipynb

Lines changed: 1088 additions & 23 deletions
Large diffs are not rendered by default.

folium/features.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ class GeoJson(Layer):
479479
control : bool, default True
480480
Whether the Layer will be included in LayerControls
481481
show: bool, default True
482-
Whether the layer will be shown on opening (only for overlays).
482+
Whether the layer will be shown on opening.
483483
smooth_factor: float, default None
484484
How much to simplify the polyline on each zoom level. More means
485485
better performance and smoother look, and less means more accurate
@@ -608,8 +608,7 @@ class GeoJson(Layer):
608608
609609
function {{ this.get_name() }}_add (data) {
610610
{{ this.get_name() }}
611-
.addData(data)
612-
.addTo({{ this._parent.get_name() }});
611+
.addData(data);
613612
}
614613
{%- if this.embed %}
615614
{{ this.get_name() }}_add({{ this.data|tojson }});
@@ -890,7 +889,7 @@ class TopoJson(JSCSSMixin, Layer):
890889
control : bool, default True
891890
Whether the Layer will be included in LayerControls.
892891
show: bool, default True
893-
Whether the layer will be shown on opening (only for overlays).
892+
Whether the layer will be shown on opening.
894893
smooth_factor: float, default None
895894
How much to simplify the polyline on each zoom level. More means
896895
better performance and smoother look, and less means more accurate
@@ -1410,7 +1409,7 @@ class Choropleth(FeatureGroup):
14101409
control : bool, default True
14111410
Whether the Layer will be included in LayerControls.
14121411
show: bool, default True
1413-
Whether the layer will be shown on opening (only for overlays).
1412+
Whether the layer will be shown on opening.
14141413
14151414
Returns
14161415
-------

folium/map.py

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
TypeJsonValue,
1515
camelize,
1616
escape_backticks,
17+
get_and_assert_figure_root,
1718
parse_options,
1819
validate_location,
1920
)
@@ -33,7 +34,7 @@ class Layer(MacroElement):
3334
control : bool, default True
3435
Whether the Layer will be included in LayerControls.
3536
show: bool, default True
36-
Whether the layer will be shown on opening (only for overlays).
37+
Whether the layer will be shown on opening.
3738
"""
3839

3940
def __init__(
@@ -49,6 +50,26 @@ def __init__(
4950
self.control = control
5051
self.show = show
5152

53+
def render(self, **kwargs):
54+
super().render(**kwargs)
55+
if self.show:
56+
self._add_layer_to_map()
57+
58+
def _add_layer_to_map(self, **kwargs):
59+
"""Show the layer on the map by adding it to its parent in JS."""
60+
template = Template(
61+
"""
62+
{%- macro script(this, kwargs) %}
63+
{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
64+
{%- endmacro %}
65+
"""
66+
)
67+
script = template.module.__dict__["script"]
68+
figure = get_and_assert_figure_root(self)
69+
figure.script.add_child(
70+
Element(script(self, kwargs)), name=self.get_name() + "_add"
71+
)
72+
5273

5374
class FeatureGroup(Layer):
5475
"""
@@ -68,7 +89,7 @@ class FeatureGroup(Layer):
6889
control: bool, default True
6990
Whether the layer will be included in LayerControls.
7091
show: bool, default True
71-
Whether the layer will be shown on opening (only for overlays).
92+
Whether the layer will be shown on opening.
7293
**kwargs
7394
Additional (possibly inherited) options. See
7495
https://leafletjs.com/reference.html#featuregroup
@@ -80,7 +101,7 @@ class FeatureGroup(Layer):
80101
{% macro script(this, kwargs) %}
81102
var {{ this.get_name() }} = L.featureGroup(
82103
{{ this.options|tojson }}
83-
).addTo({{ this._parent.get_name() }});
104+
);
84105
{% endmacro %}
85106
"""
86107
)
@@ -150,10 +171,6 @@ class LayerControl(MacroElement):
150171
{{ this.options|tojson }}
151172
).addTo({{this._parent.get_name()}});
152173
153-
{%- for val in this.layers_untoggle.values() %}
154-
{{ val }}.remove();
155-
{%- endfor %}
156-
157174
{% endmacro %}
158175
"""
159176
)
@@ -172,12 +189,10 @@ def __init__(
172189
)
173190
self.base_layers: OrderedDict[str, str] = OrderedDict()
174191
self.overlays: OrderedDict[str, str] = OrderedDict()
175-
self.layers_untoggle: OrderedDict[str, str] = OrderedDict()
176192

177193
def reset(self) -> None:
178194
self.base_layers = OrderedDict()
179195
self.overlays = OrderedDict()
180-
self.layers_untoggle = OrderedDict()
181196

182197
def render(self, **kwargs) -> None:
183198
"""Renders the HTML representation of the element."""
@@ -188,12 +203,8 @@ def render(self, **kwargs) -> None:
188203
key = item.layer_name
189204
if not item.overlay:
190205
self.base_layers[key] = item.get_name()
191-
if len(self.base_layers) > 1:
192-
self.layers_untoggle[key] = item.get_name()
193206
else:
194207
self.overlays[key] = item.get_name()
195-
if not item.show:
196-
self.layers_untoggle[key] = item.get_name()
197208
super().render()
198209

199210

folium/plugins/fast_marker_cluster.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class FastMarkerCluster(MarkerCluster):
3333
control : bool, default True
3434
Whether the Layer will be included in LayerControls.
3535
show: bool, default True
36-
Whether the layer will be shown on opening (only for overlays).
36+
Whether the layer will be shown on opening.
3737
icon_create_function : string, default None
3838
Override the default behaviour, making possible to customize
3939
markers colors and sizes.

folium/plugins/feature_group_sub_group.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class FeatureGroupSubGroup(JSCSSMixin, Layer):
2323
control : bool, default True
2424
Whether the Layer will be included in LayerControls.
2525
show: bool, default True
26-
Whether the layer will be shown on opening (only for overlays).
26+
Whether the layer will be shown on opening.
2727
2828
Examples
2929
-------
@@ -61,7 +61,6 @@ class FeatureGroupSubGroup(JSCSSMixin, Layer):
6161
var {{ this.get_name() }} = L.featureGroup.subGroup(
6262
{{ this._group.get_name() }}
6363
);
64-
{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
6564
{% endmacro %}
6665
"""
6766
)

folium/plugins/heat_map.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ class HeatMap(JSCSSMixin, Layer):
4141
control : bool, default True
4242
Whether the Layer will be included in LayerControls.
4343
show: bool, default True
44-
Whether the layer will be shown on opening (only for overlays).
44+
Whether the layer will be shown on opening.
4545
"""
4646

4747
_template = Template(
@@ -50,7 +50,7 @@ class HeatMap(JSCSSMixin, Layer):
5050
var {{ this.get_name() }} = L.heatLayer(
5151
{{ this.data|tojson }},
5252
{{ this.options|tojson }}
53-
).addTo({{ this._parent.get_name() }});
53+
);
5454
{% endmacro %}
5555
"""
5656
)

folium/plugins/heat_map_withtime.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ class HeatMapWithTime(JSCSSMixin, Layer):
5555
control : bool, default True
5656
Whether the Layer will be included in LayerControls.
5757
show: bool, default True
58-
Whether the layer will be shown on opening (only for overlays).
58+
Whether the layer will be shown on opening.
5959
6060
"""
6161

@@ -102,8 +102,7 @@ class HeatMapWithTime(JSCSSMixin, Layer):
102102
defaultWeight: 1,
103103
{% if this.gradient %}gradient: {{ this.gradient }}{% endif %}
104104
}
105-
})
106-
.addTo({{this._parent.get_name()}});
105+
});
107106
108107
{% endmacro %}
109108
"""

folium/plugins/marker_cluster.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class MarkerCluster(JSCSSMixin, Layer):
2424
control : bool, default True
2525
Whether the Layer will be included in LayerControls.
2626
show: bool, default True
27-
Whether the layer will be shown on opening (only for overlays).
27+
Whether the layer will be shown on opening.
2828
icon_create_function : string, default None
2929
Override the default behaviour, making possible to customize
3030
markers colors and sizes.
@@ -54,7 +54,6 @@ class MarkerCluster(JSCSSMixin, Layer):
5454
{{ this.get_name() }}.options.iconCreateFunction =
5555
{{ this.icon_create_function.strip() }};
5656
{%- endif %}
57-
{{ this._parent.get_name() }}.addLayer({{ this.get_name() }});
5857
{% endmacro %}
5958
"""
6059
)

folium/plugins/time_slider_choropleth.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
2323
control : bool, default True
2424
Whether the Layer will be included in LayerControls.
2525
show: bool, default True
26-
Whether the layer will be shown on opening (only for overlays).
26+
Whether the layer will be shown on opening.
2727
init_timestamp: int, default 0
2828
Initial time-stamp index on the slider. Must be in the range
2929
`[-L, L-1]`, where `L` is the maximum number of time stamps in
@@ -131,6 +131,10 @@ class TimeSliderChoropleth(JSCSSMixin, Layer):
131131
{{ this._parent.get_name() }}.on('overlayadd', onOverlayAdd);
132132
133133
onOverlayAdd(); // fill map as layer is loaded
134+
135+
{%- if not this.show %}
136+
{{ this.get_name() }}.remove();
137+
{%- endif %}
134138
{% endmacro %}
135139
"""
136140
)

folium/plugins/vectorgrid_protobuf.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ class VectorGridProtobuf(JSCSSMixin, Layer):
104104
{{ this.options if this.options is string else this.options|tojson }})
105105
.addTo({{ this._parent.get_name() }});
106106
{% else %}
107-
{{ this.options }}).addTo({{ this._parent.get_name() }});
107+
{{ this.options }});
108108
{% endif %}
109109
{%- endmacro %}
110110
"""

folium/raster_layers.py

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ class TileLayer(Layer):
6363
control : bool, default True
6464
Whether the Layer will be included in LayerControls.
6565
show: bool, default True
66-
Whether the layer will be shown on opening (only for overlays).
66+
Whether the layer will be shown on opening.
67+
When adding multiple base layers, use this parameter to select which one
68+
should be shown when opening the map, by not showing the others.
6769
subdomains: list of strings, default ['abc']
6870
Subdomains of the tile service.
6971
tms: bool, default False
@@ -82,7 +84,7 @@ class TileLayer(Layer):
8284
var {{ this.get_name() }} = L.tileLayer(
8385
{{ this.tiles|tojson }},
8486
{{ this.options|tojson }}
85-
).addTo({{ this._parent.get_name() }});
87+
);
8688
{% endmacro %}
8789
"""
8890
)
@@ -187,7 +189,7 @@ class WmsTileLayer(Layer):
187189
control : bool, default True
188190
Whether the Layer will be included in LayerControls.
189191
show: bool, default True
190-
Whether the layer will be shown on opening (only for overlays).
192+
Whether the layer will be shown on opening.
191193
**kwargs : additional keyword arguments
192194
Passed through to the underlying tileLayer.wms object and can be used
193195
for setting extra tileLayer.wms parameters or as extra parameters in
@@ -202,7 +204,7 @@ class WmsTileLayer(Layer):
202204
var {{ this.get_name() }} = L.tileLayer.wms(
203205
{{ this.url|tojson }},
204206
{{ this.options|tojson }}
205-
).addTo({{ this._parent.get_name() }});
207+
);
206208
{% endmacro %}
207209
"""
208210
) # noqa
@@ -279,7 +281,7 @@ class ImageOverlay(Layer):
279281
control : bool, default True
280282
Whether the Layer will be included in LayerControls.
281283
show: bool, default True
282-
Whether the layer will be shown on opening (only for overlays).
284+
Whether the layer will be shown on opening.
283285
284286
See https://leafletjs.com/reference.html#imageoverlay for more
285287
options.
@@ -293,7 +295,7 @@ class ImageOverlay(Layer):
293295
{{ this.url|tojson }},
294296
{{ this.bounds|tojson }},
295297
{{ this.options|tojson }}
296-
).addTo({{ this._parent.get_name() }});
298+
);
297299
{% endmacro %}
298300
"""
299301
)
@@ -378,7 +380,7 @@ class VideoOverlay(Layer):
378380
control : bool, default True
379381
Whether the Layer will be included in LayerControls.
380382
show: bool, default True
381-
Whether the layer will be shown on opening (only for overlays).
383+
Whether the layer will be shown on opening.
382384
**kwargs:
383385
Other valid (possibly inherited) options. See:
384386
https://leafletjs.com/reference.html#videooverlay
@@ -392,7 +394,7 @@ class VideoOverlay(Layer):
392394
{{ this.video_url|tojson }},
393395
{{ this.bounds|tojson }},
394396
{{ this.options|tojson }}
395-
).addTo({{ this._parent.get_name() }});
397+
);
396398
{% endmacro %}
397399
"""
398400
)

folium/utilities.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
from urllib.parse import urlparse, uses_netloc, uses_params, uses_relative
2727

2828
import numpy as np
29-
from branca.element import Element
29+
from branca.element import Element, Figure
3030

3131
# import here for backwards compatibility
3232
from branca.utilities import ( # noqa F401
@@ -499,3 +499,12 @@ def escape_double_quotes(text: str) -> str:
499499
def javascript_identifier_path_to_array_notation(path: str) -> str:
500500
"""Convert a path like obj1.obj2 to array notation: ["obj1"]["obj2"]."""
501501
return "".join(f'["{escape_double_quotes(x)}"]' for x in path.split("."))
502+
503+
504+
def get_and_assert_figure_root(obj: Element) -> Figure:
505+
"""Return the root element of the tree and assert it's a Figure."""
506+
figure = obj.get_root()
507+
assert isinstance(
508+
figure, Figure
509+
), "You cannot render this Element if it is not in a Figure."
510+
return figure

tests/plugins/test_feature_group_sub_group.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,11 @@ def test_feature_group_sub_group():
3838
var {{ this.get_name() }} = L.featureGroup.subGroup(
3939
{{ this._group.get_name() }}
4040
);
41-
{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
4241
"""
4342
)
4443
assert normalize(tmpl.render(this=g1)) in out
4544
assert normalize(tmpl.render(this=g2)) in out
45+
46+
tmpl = Template("{{ this.get_name() }}.addTo({{ this._parent.get_name() }});")
47+
assert normalize(tmpl.render(this=g1)) in out
48+
assert normalize(tmpl.render(this=g2)) in out

tests/plugins/test_grouped_layer_control.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
from folium.utilities import normalize
44

55

6-
def test_feature_group_sub_group():
6+
def test_grouped_layer_control():
77
m = folium.Map([40.0, 70.0], zoom_start=6)
88
fg1 = folium.FeatureGroup(name="g1")
99
fg2 = folium.FeatureGroup(name="g2")

tests/plugins/test_heat_map_withtime.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,8 +75,8 @@ def test_heat_map_with_time():
7575
defaultWeight: 1,
7676
{% if this.gradient %}gradient: {{ this.gradient }}{% endif %}
7777
}
78-
})
79-
.addTo({{this._parent.get_name()}});
78+
});
79+
{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
8080
"""
8181
)
8282

tests/plugins/test_marker_cluster.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,15 @@ def test_marker_cluster():
4949
{{ this.get_name() }}.options.iconCreateFunction =
5050
{{ this.icon_create_function.strip() }};
5151
{%- endif %}
52-
{{this._parent.get_name()}}.addLayer({{this.get_name()}});
5352
5453
{% for marker in this._children.values() %}
5554
var {{marker.get_name()}} = L.marker(
5655
{{ marker.location|tojson }},
5756
{}
5857
).addTo({{this.get_name()}});
5958
{% endfor %}
59+
60+
{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
6061
"""
6162
)
6263
expected = normalize(tmpl.render(this=mc))

0 commit comments

Comments
 (0)