From f3ca260c88e5a89a7afe5543e8adfed3ef991b1e Mon Sep 17 00:00:00 2001 From: adarshsm <24850536+adarshsm@users.noreply.github.com> Date: Sat, 17 Oct 2026 13:03:26 +0530 Subject: [PATCH] Support base layers in GroupedLayerControl GroupedLayerControl always passed `null` as the base layers argument of `L.control.groupedLayers`, so base layers could only go in a separate regular LayerControl (#1876). An unknown `base_layers` keyword was also silently forwarded into the JS options. Add a `base_layers` argument: the given layers are rendered as the base layer radio buttons of the grouped control and get `control = False`, so they no longer also show up in a regular LayerControl. Which base layer is visible on opening still follows each layer's `show`, as with LayerControl. Co-Authored-By: Claude Opus 5.5 --- folium/plugins/groupedlayercontrol.py | 18 +++++++++++- tests/plugins/test_grouped_layer_control.py | 32 +++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/folium/plugins/groupedlayercontrol.py b/folium/plugins/groupedlayercontrol.py index c30400932d..0fda5e2a05 100644 --- a/folium/plugins/groupedlayercontrol.py +++ b/folium/plugins/groupedlayercontrol.py @@ -23,6 +23,10 @@ class GroupedLayerControl(JSCSSMixin, MacroElement): exclusive_groups : bool, default True Whether to use radio buttons (default) or checkboxes. If you want to use both, use two separate instances of this class. + base_layers : list, default None + Layer objects, typically tile layers, to show as base layers in this + control. These are removed from the regular LayerControl. Use the + layers' `show` argument to choose which one is shown on opening. **kwargs Additional (possibly inherited) options. See https://leafletjs.com/reference.html#control-layers @@ -45,7 +49,15 @@ class GroupedLayerControl(JSCSSMixin, MacroElement): {% macro script(this,kwargs) %} L.control.groupedLayers( + {%- if this.base_layers %} + { + {%- for key, val in this.base_layers.items() %} + {{ key|tojson }} : {{val}}, + {%- endfor %} + }, + {%- else %} null, + {%- endif %} { {%- for group_name, overlays in this.grouped_overlays.items() %} {{ group_name|tojson }} : { @@ -65,12 +77,16 @@ class GroupedLayerControl(JSCSSMixin, MacroElement): {% endmacro %} """) - def __init__(self, groups, exclusive_groups=True, **kwargs): + def __init__(self, groups, exclusive_groups=True, base_layers=None, **kwargs): super().__init__() self._name = "GroupedLayerControl" self.options = remove_empty(**kwargs) if exclusive_groups: self.options["exclusiveGroups"] = list(groups.keys()) + self.base_layers = {} + for element in base_layers or []: + self.base_layers[element.layer_name] = element.get_name() + element.control = False self.layers_untoggle = set() self.grouped_overlays = {} for group_name, sublist in groups.items(): diff --git a/tests/plugins/test_grouped_layer_control.py b/tests/plugins/test_grouped_layer_control.py index c7003f3af3..b3fb824705 100644 --- a/tests/plugins/test_grouped_layer_control.py +++ b/tests/plugins/test_grouped_layer_control.py @@ -39,3 +39,35 @@ def test_grouped_layer_control(): {fg2.get_name()}.remove(); """) assert expected in out + + +def test_grouped_layer_control_base_layers(): + m = folium.Map([40.0, 70.0], zoom_start=6, tiles=None) + osm = folium.TileLayer("OpenStreetMap", name="osm").add_to(m) + topo = folium.TileLayer("OpenTopoMap", name="topo", show=False).add_to(m) + fg1 = folium.FeatureGroup(name="g1").add_to(m) + fg2 = folium.FeatureGroup(name="g2").add_to(m) + groupedlayercontrol.GroupedLayerControl( + groups={"groups1": [fg1, fg2]}, base_layers=[osm, topo] + ).add_to(m) + lc = folium.LayerControl().add_to(m) + out = normalize(m._parent.render()) + + expected = normalize(f""" + L.control.groupedLayers( + {{ + "osm" : {osm.get_name()}, + "topo" : {topo.get_name()}, + }}, + {{ + "groups1" : {{ + "g1" : {fg1.get_name()}, + "g2" : {fg2.get_name()}, + }}, + }}, + {{"exclusiveGroups": ["groups1",],}}, + ).addTo({m.get_name()}); + """) + assert expected in out + # The base layers are no longer listed in the regular LayerControl. + assert not lc.base_layers