diff --git a/examples/Features.ipynb b/examples/Features.ipynb index 1755605469..a0f75880b1 100644 --- a/examples/Features.ipynb +++ b/examples/Features.ipynb @@ -30,10 +30,159 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 2, @@ -75,10 +224,107 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 3, @@ -114,10 +360,123 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 4, @@ -168,17 +527,126 @@ "name": "stderr", "output_type": "stream", "text": [ - "/home/filipe/miniconda3/envs/FOLIUM/lib/python3.9/site-packages/altair/utils/deprecation.py:65: AltairDeprecationWarning: load_dataset is deprecated. Use the vega_datasets package instead.\n", + "C:\\Users\\frank\\anaconda3\\envs\\folium\\lib\\site-packages\\altair\\utils\\deprecation.py:65: AltairDeprecationWarning: load_dataset is deprecated. Use the vega_datasets package instead.\n", " warnings.warn(message, AltairDeprecationWarning)\n" ] }, { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 5, @@ -237,10 +705,154 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 6, @@ -315,10 +927,157 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 7, @@ -398,10 +1157,100 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 8, @@ -450,10 +1299,134 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 9, @@ -498,10 +1471,102 @@ { "data": { "text/html": [ - "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" ], "text/plain": [ - "" + "" ] }, "execution_count": 10, @@ -513,9 +1578,9 @@ "m = folium.Map(tiles=None)\n", "\n", "folium.raster_layers.TileLayer(\"OpenStreetMap\").add_to(m)\n", - "folium.raster_layers.TileLayer(\"stamentoner\").add_to(m)\n", + "folium.raster_layers.TileLayer(\"stamentoner\", show=False).add_to(m)\n", "\n", - "folium.LayerControl().add_to(m)\n", + "folium.LayerControl(collapsed=False).add_to(m)\n", "\n", "m" ] @@ -523,7 +1588,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -537,7 +1602,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.0" + "version": "3.9.13" } }, "nbformat": 4, diff --git a/folium/features.py b/folium/features.py index 748b631db9..b4468dfae1 100644 --- a/folium/features.py +++ b/folium/features.py @@ -479,7 +479,7 @@ class GeoJson(Layer): control : bool, default True Whether the Layer will be included in LayerControls show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. smooth_factor: float, default None How much to simplify the polyline on each zoom level. More means better performance and smoother look, and less means more accurate @@ -608,8 +608,7 @@ class GeoJson(Layer): function {{ this.get_name() }}_add (data) { {{ this.get_name() }} - .addData(data) - .addTo({{ this._parent.get_name() }}); + .addData(data); } {%- if this.embed %} {{ this.get_name() }}_add({{ this.data|tojson }}); @@ -890,7 +889,7 @@ class TopoJson(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. smooth_factor: float, default None How much to simplify the polyline on each zoom level. More means better performance and smoother look, and less means more accurate @@ -1410,7 +1409,7 @@ class Choropleth(FeatureGroup): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. Returns ------- diff --git a/folium/map.py b/folium/map.py index ce640c99fb..b091a064f3 100644 --- a/folium/map.py +++ b/folium/map.py @@ -14,6 +14,7 @@ TypeJsonValue, camelize, escape_backticks, + get_and_assert_figure_root, parse_options, validate_location, ) @@ -33,7 +34,7 @@ class Layer(MacroElement): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. """ def __init__( @@ -49,6 +50,26 @@ def __init__( self.control = control self.show = show + def render(self, **kwargs): + super().render(**kwargs) + if self.show: + self._add_layer_to_map() + + def _add_layer_to_map(self, **kwargs): + """Show the layer on the map by adding it to its parent in JS.""" + template = Template( + """ + {%- macro script(this, kwargs) %} + {{ this.get_name() }}.addTo({{ this._parent.get_name() }}); + {%- endmacro %} + """ + ) + script = template.module.__dict__["script"] + figure = get_and_assert_figure_root(self) + figure.script.add_child( + Element(script(self, kwargs)), name=self.get_name() + "_add" + ) + class FeatureGroup(Layer): """ @@ -68,7 +89,7 @@ class FeatureGroup(Layer): control: bool, default True Whether the layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. **kwargs Additional (possibly inherited) options. See https://leafletjs.com/reference.html#featuregroup @@ -80,7 +101,7 @@ class FeatureGroup(Layer): {% macro script(this, kwargs) %} var {{ this.get_name() }} = L.featureGroup( {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) @@ -150,10 +171,6 @@ class LayerControl(MacroElement): {{ this.options|tojson }} ).addTo({{this._parent.get_name()}}); - {%- for val in this.layers_untoggle.values() %} - {{ val }}.remove(); - {%- endfor %} - {% endmacro %} """ ) @@ -172,12 +189,10 @@ def __init__( ) self.base_layers: OrderedDict[str, str] = OrderedDict() self.overlays: OrderedDict[str, str] = OrderedDict() - self.layers_untoggle: OrderedDict[str, str] = OrderedDict() def reset(self) -> None: self.base_layers = OrderedDict() self.overlays = OrderedDict() - self.layers_untoggle = OrderedDict() def render(self, **kwargs) -> None: """Renders the HTML representation of the element.""" @@ -188,12 +203,8 @@ def render(self, **kwargs) -> None: key = item.layer_name if not item.overlay: self.base_layers[key] = item.get_name() - if len(self.base_layers) > 1: - self.layers_untoggle[key] = item.get_name() else: self.overlays[key] = item.get_name() - if not item.show: - self.layers_untoggle[key] = item.get_name() super().render() diff --git a/folium/plugins/fast_marker_cluster.py b/folium/plugins/fast_marker_cluster.py index 7b5fa3f68e..0a55ab37cc 100644 --- a/folium/plugins/fast_marker_cluster.py +++ b/folium/plugins/fast_marker_cluster.py @@ -33,7 +33,7 @@ class FastMarkerCluster(MarkerCluster): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. icon_create_function : string, default None Override the default behaviour, making possible to customize markers colors and sizes. diff --git a/folium/plugins/feature_group_sub_group.py b/folium/plugins/feature_group_sub_group.py index 994cc96792..ae07e03200 100644 --- a/folium/plugins/feature_group_sub_group.py +++ b/folium/plugins/feature_group_sub_group.py @@ -23,7 +23,7 @@ class FeatureGroupSubGroup(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. Examples ------- @@ -61,7 +61,6 @@ class FeatureGroupSubGroup(JSCSSMixin, Layer): var {{ this.get_name() }} = L.featureGroup.subGroup( {{ this._group.get_name() }} ); - {{ this.get_name() }}.addTo({{ this._parent.get_name() }}); {% endmacro %} """ ) diff --git a/folium/plugins/heat_map.py b/folium/plugins/heat_map.py index f17c4a0c03..cc6a981d5c 100644 --- a/folium/plugins/heat_map.py +++ b/folium/plugins/heat_map.py @@ -41,7 +41,7 @@ class HeatMap(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. """ _template = Template( @@ -50,7 +50,7 @@ class HeatMap(JSCSSMixin, Layer): var {{ this.get_name() }} = L.heatLayer( {{ this.data|tojson }}, {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) diff --git a/folium/plugins/heat_map_withtime.py b/folium/plugins/heat_map_withtime.py index 876b6139f0..9a556773c3 100644 --- a/folium/plugins/heat_map_withtime.py +++ b/folium/plugins/heat_map_withtime.py @@ -55,7 +55,7 @@ class HeatMapWithTime(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. """ @@ -102,8 +102,7 @@ class HeatMapWithTime(JSCSSMixin, Layer): defaultWeight: 1, {% if this.gradient %}gradient: {{ this.gradient }}{% endif %} } - }) - .addTo({{this._parent.get_name()}}); + }); {% endmacro %} """ diff --git a/folium/plugins/marker_cluster.py b/folium/plugins/marker_cluster.py index 664e0c50ed..b6460e0882 100644 --- a/folium/plugins/marker_cluster.py +++ b/folium/plugins/marker_cluster.py @@ -24,7 +24,7 @@ class MarkerCluster(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. icon_create_function : string, default None Override the default behaviour, making possible to customize markers colors and sizes. @@ -54,7 +54,6 @@ class MarkerCluster(JSCSSMixin, Layer): {{ this.get_name() }}.options.iconCreateFunction = {{ this.icon_create_function.strip() }}; {%- endif %} - {{ this._parent.get_name() }}.addLayer({{ this.get_name() }}); {% endmacro %} """ ) diff --git a/folium/plugins/time_slider_choropleth.py b/folium/plugins/time_slider_choropleth.py index 141920163f..c8ee4ec945 100644 --- a/folium/plugins/time_slider_choropleth.py +++ b/folium/plugins/time_slider_choropleth.py @@ -23,7 +23,7 @@ class TimeSliderChoropleth(JSCSSMixin, Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. init_timestamp: int, default 0 Initial time-stamp index on the slider. Must be in the range `[-L, L-1]`, where `L` is the maximum number of time stamps in @@ -131,6 +131,10 @@ class TimeSliderChoropleth(JSCSSMixin, Layer): {{ this._parent.get_name() }}.on('overlayadd', onOverlayAdd); onOverlayAdd(); // fill map as layer is loaded + + {%- if not this.show %} + {{ this.get_name() }}.remove(); + {%- endif %} {% endmacro %} """ ) diff --git a/folium/plugins/vectorgrid_protobuf.py b/folium/plugins/vectorgrid_protobuf.py index 78fdbfff94..a26472323b 100644 --- a/folium/plugins/vectorgrid_protobuf.py +++ b/folium/plugins/vectorgrid_protobuf.py @@ -104,7 +104,7 @@ class VectorGridProtobuf(JSCSSMixin, Layer): {{ this.options if this.options is string else this.options|tojson }}) .addTo({{ this._parent.get_name() }}); {% else %} - {{ this.options }}).addTo({{ this._parent.get_name() }}); + {{ this.options }}); {% endif %} {%- endmacro %} """ diff --git a/folium/raster_layers.py b/folium/raster_layers.py index fff7e8fc42..71120b28f3 100644 --- a/folium/raster_layers.py +++ b/folium/raster_layers.py @@ -63,7 +63,9 @@ class TileLayer(Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. + When adding multiple base layers, use this parameter to select which one + should be shown when opening the map, by not showing the others. subdomains: list of strings, default ['abc'] Subdomains of the tile service. tms: bool, default False @@ -82,7 +84,7 @@ class TileLayer(Layer): var {{ this.get_name() }} = L.tileLayer( {{ this.tiles|tojson }}, {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) @@ -187,7 +189,7 @@ class WmsTileLayer(Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. **kwargs : additional keyword arguments Passed through to the underlying tileLayer.wms object and can be used for setting extra tileLayer.wms parameters or as extra parameters in @@ -202,7 +204,7 @@ class WmsTileLayer(Layer): var {{ this.get_name() }} = L.tileLayer.wms( {{ this.url|tojson }}, {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) # noqa @@ -279,7 +281,7 @@ class ImageOverlay(Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. See https://leafletjs.com/reference.html#imageoverlay for more options. @@ -293,7 +295,7 @@ class ImageOverlay(Layer): {{ this.url|tojson }}, {{ this.bounds|tojson }}, {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) @@ -378,7 +380,7 @@ class VideoOverlay(Layer): control : bool, default True Whether the Layer will be included in LayerControls. show: bool, default True - Whether the layer will be shown on opening (only for overlays). + Whether the layer will be shown on opening. **kwargs: Other valid (possibly inherited) options. See: https://leafletjs.com/reference.html#videooverlay @@ -392,7 +394,7 @@ class VideoOverlay(Layer): {{ this.video_url|tojson }}, {{ this.bounds|tojson }}, {{ this.options|tojson }} - ).addTo({{ this._parent.get_name() }}); + ); {% endmacro %} """ ) diff --git a/folium/utilities.py b/folium/utilities.py index 9b8d6b8763..cc65134f93 100644 --- a/folium/utilities.py +++ b/folium/utilities.py @@ -26,7 +26,7 @@ from urllib.parse import urlparse, uses_netloc, uses_params, uses_relative import numpy as np -from branca.element import Element +from branca.element import Element, Figure # import here for backwards compatibility from branca.utilities import ( # noqa F401 @@ -499,3 +499,12 @@ def escape_double_quotes(text: str) -> str: def javascript_identifier_path_to_array_notation(path: str) -> str: """Convert a path like obj1.obj2 to array notation: ["obj1"]["obj2"].""" return "".join(f'["{escape_double_quotes(x)}"]' for x in path.split(".")) + + +def get_and_assert_figure_root(obj: Element) -> Figure: + """Return the root element of the tree and assert it's a Figure.""" + figure = obj.get_root() + assert isinstance( + figure, Figure + ), "You cannot render this Element if it is not in a Figure." + return figure diff --git a/tests/plugins/test_feature_group_sub_group.py b/tests/plugins/test_feature_group_sub_group.py index 61522ee606..6a97ca7f8c 100644 --- a/tests/plugins/test_feature_group_sub_group.py +++ b/tests/plugins/test_feature_group_sub_group.py @@ -38,8 +38,11 @@ def test_feature_group_sub_group(): var {{ this.get_name() }} = L.featureGroup.subGroup( {{ this._group.get_name() }} ); - {{ this.get_name() }}.addTo({{ this._parent.get_name() }}); """ ) assert normalize(tmpl.render(this=g1)) in out assert normalize(tmpl.render(this=g2)) in out + + tmpl = Template("{{ this.get_name() }}.addTo({{ this._parent.get_name() }});") + assert normalize(tmpl.render(this=g1)) in out + assert normalize(tmpl.render(this=g2)) in out diff --git a/tests/plugins/test_grouped_layer_control.py b/tests/plugins/test_grouped_layer_control.py index 0e80a7c029..18166e1739 100644 --- a/tests/plugins/test_grouped_layer_control.py +++ b/tests/plugins/test_grouped_layer_control.py @@ -3,7 +3,7 @@ from folium.utilities import normalize -def test_feature_group_sub_group(): +def test_grouped_layer_control(): m = folium.Map([40.0, 70.0], zoom_start=6) fg1 = folium.FeatureGroup(name="g1") fg2 = folium.FeatureGroup(name="g2") diff --git a/tests/plugins/test_heat_map_withtime.py b/tests/plugins/test_heat_map_withtime.py index e72e712a12..6d6e7bddde 100644 --- a/tests/plugins/test_heat_map_withtime.py +++ b/tests/plugins/test_heat_map_withtime.py @@ -75,8 +75,8 @@ def test_heat_map_with_time(): defaultWeight: 1, {% if this.gradient %}gradient: {{ this.gradient }}{% endif %} } - }) - .addTo({{this._parent.get_name()}}); + }); + {{ this.get_name() }}.addTo({{ this._parent.get_name() }}); """ ) diff --git a/tests/plugins/test_marker_cluster.py b/tests/plugins/test_marker_cluster.py index 99f8343a20..5eb586ac23 100644 --- a/tests/plugins/test_marker_cluster.py +++ b/tests/plugins/test_marker_cluster.py @@ -49,7 +49,6 @@ def test_marker_cluster(): {{ this.get_name() }}.options.iconCreateFunction = {{ this.icon_create_function.strip() }}; {%- endif %} - {{this._parent.get_name()}}.addLayer({{this.get_name()}}); {% for marker in this._children.values() %} var {{marker.get_name()}} = L.marker( @@ -57,6 +56,8 @@ def test_marker_cluster(): {} ).addTo({{this.get_name()}}); {% endfor %} + + {{ this.get_name() }}.addTo({{ this._parent.get_name() }}); """ ) expected = normalize(tmpl.render(this=mc)) diff --git a/tests/test_raster_layers.py b/tests/test_raster_layers.py index 1419b30ccf..d369fa9118 100644 --- a/tests/test_raster_layers.py +++ b/tests/test_raster_layers.py @@ -103,7 +103,8 @@ def test_image_overlay(): "{{ this.url }}", {{ this.bounds }}, {{ this.options }} - ).addTo({{this._parent.get_name()}}); + ); + {{ this.get_name() }}.addTo({{this._parent.get_name()}}); """ ) assert normalize(tmpl.render(this=io)) in normalize(out)