Skip to content
1,111 changes: 1,088 additions & 23 deletions examples/Features.ipynb

Large diffs are not rendered by default.

9 changes: 4 additions & 5 deletions folium/features.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 }});
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
-------
Expand Down
37 changes: 24 additions & 13 deletions folium/map.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
TypeJsonValue,
camelize,
escape_backticks,
get_and_assert_figure_root,
parse_options,
validate_location,
)
Expand All @@ -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__(
Expand All @@ -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):
"""
Expand All @@ -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
Expand All @@ -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 %}
"""
)
Expand Down Expand Up @@ -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 %}
"""
)
Expand All @@ -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."""
Expand All @@ -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()


Expand Down
2 changes: 1 addition & 1 deletion folium/plugins/fast_marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 1 addition & 2 deletions folium/plugins/feature_group_sub_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------
Expand Down Expand Up @@ -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 %}
"""
)
Expand Down
4 changes: 2 additions & 2 deletions folium/plugins/heat_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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 %}
"""
)
Expand Down
5 changes: 2 additions & 3 deletions folium/plugins/heat_map_withtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

"""

Expand Down Expand Up @@ -102,8 +102,7 @@ class HeatMapWithTime(JSCSSMixin, Layer):
defaultWeight: 1,
{% if this.gradient %}gradient: {{ this.gradient }}{% endif %}
}
})
.addTo({{this._parent.get_name()}});
});

{% endmacro %}
"""
Expand Down
3 changes: 1 addition & 2 deletions folium/plugins/marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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 %}
"""
)
Expand Down
6 changes: 5 additions & 1 deletion folium/plugins/time_slider_choropleth.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 %}
"""
)
Expand Down
2 changes: 1 addition & 1 deletion folium/plugins/vectorgrid_protobuf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 %}
"""
Expand Down
18 changes: 10 additions & 8 deletions folium/raster_layers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 %}
"""
)
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand All @@ -293,7 +295,7 @@ class ImageOverlay(Layer):
{{ this.url|tojson }},
{{ this.bounds|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }});
);
{% endmacro %}
"""
)
Expand Down Expand Up @@ -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
Expand All @@ -392,7 +394,7 @@ class VideoOverlay(Layer):
{{ this.video_url|tojson }},
{{ this.bounds|tojson }},
{{ this.options|tojson }}
).addTo({{ this._parent.get_name() }});
);
{% endmacro %}
"""
)
Expand Down
11 changes: 10 additions & 1 deletion folium/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
5 changes: 4 additions & 1 deletion tests/plugins/test_feature_group_sub_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion tests/plugins/test_grouped_layer_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions tests/plugins/test_heat_map_withtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -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() }});
"""
)

Expand Down
3 changes: 2 additions & 1 deletion tests/plugins/test_marker_cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,14 +49,15 @@ 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(
{{ marker.location|tojson }},
{}
).addTo({{this.get_name()}});
{% endfor %}

{{ this.get_name() }}.addTo({{ this._parent.get_name() }});
"""
)
expected = normalize(tmpl.render(this=mc))
Expand Down
Loading