LayerGroup & FeatureGroup
Bundle N layers so they can be added, removed, toggled, or measured as one — wraps Leaflet 2's LayerGroup / FeatureGroup.
Overview
dl2.LayerGroup and dl2.FeatureGroup mirror Leaflet's container primitives:
| Component | When to use it |
|---|---|
LayerGroup | Bundle any layers so a single addTo / remove shows or hides the whole set. Pair with dl2.Overlay inside a LayersControl to toggle the entire group as one entry. |
FeatureGroup | Like LayerGroup, but extends leaflet.FeatureGroup — also emits a combined geojson (vector children), a single n_clicks no matter which child was clicked, and an n_layers counter that bumps on add/remove. Pair with EditControl when you want to ship the user's drawings out of the map as one piece. |
Both components also accept any layer as a child via the same React context that <Map> uses — children attach to the group via a proxy map instead of the real map directly.
Live demo
A switch toggles a LayerGroup of three markers on/off. Below it, a FeatureGroup aggregates four shapes and reports its combined geojson and the bumping n_clicks counter.
Source
# File: docs/layer-group/example.py
"""
LayerGroup & FeatureGroup — limited working example.
Top map: a LayerGroup containing three markers — toggle them all on/off with one
switch (the group itself is conditionally rendered, so all children come and go
together).
Bottom map: a FeatureGroup wrapping three vector layers + a marker. Click any
of them and FeatureGroup's `n_clicks` bumps; the readout shows the combined
GeoJSON it emits.
"""
import json
import dash_leaflet2 as dl2
import dash_mantine_components as dmc
from dash import Input, Output, callback, html
from dl2_tiles import ESRI_CANVAS, register_theme_swap
from dl2_locations import PHILADELPHIA
from dl2_shared import code_panel, header, info_panel
# Basemap pair for this page — dl2_tiles owns the light/dark wiring.
TILES = ESRI_CANVAS
CODE_LG = """dl2.Map(children=[
dl2.TileLayer(),
dl2.LayerGroup(children=[
dl2.Marker(position=PHILADELPHIA.center),
dl2.Marker(position=PHILADELPHIA.at(2.2, 2.9)),
dl2.Marker(position=PHILADELPHIA.at(-2.2, -2.9)),
]) if show_markers else None,
])"""
CODE_FG = """dl2.Map(children=[
dl2.TileLayer(),
dl2.FeatureGroup(id="fg", children=[
dl2.Polygon(positions=[...]),
dl2.Polyline(positions=[...]),
dl2.Circle(center=[...], radius=400),
dl2.Marker(position=[...]),
]),
])
@callback(Output("fg-readout","children"), Input("fg","geojson"))
def show(geojson): ..."""
component = dmc.Stack(
[
header(
"LayerGroup & FeatureGroup",
"LayerGroup bundles N layers so one switch hides them all. "
"FeatureGroup adds aggregate event + GeoJSON output — click any child and "
"n_clicks bumps; add/remove children and n_layers bumps too.",
badge="dl2.LayerGroup / FeatureGroup",
),
dmc.Title("1. LayerGroup", order=3, mt="md"),
dmc.Grid(
[
dmc.GridCol(
dmc.Paper(
dl2.Map(
id="lg-map",
center=PHILADELPHIA.center,
zoom=12,
style={"height": "45vh"},
children=[
dl2.TileLayer(id="lg-tile", **TILES.kwargs("light")),
html.Div(id="lg-container"),
],
),
shadow="sm",
radius="md",
withBorder=True,
style={"overflow": "hidden", "height": "45vh"},
),
span=8,
),
dmc.GridCol(
info_panel(
"Toggle the whole group",
dmc.Switch(
id="lg-toggle",
checked=True,
label="Show three markers (all in one LayerGroup)",
),
),
span=4,
),
],
gutter="md",
),
code_panel("LayerGroup", CODE_LG),
dmc.Title("2. FeatureGroup", order=3, mt="md"),
dmc.Grid(
[
dmc.GridCol(
dmc.Paper(
dl2.Map(
id="fg-map",
center=PHILADELPHIA.center,
zoom=12,
style={"height": "45vh"},
children=[
dl2.TileLayer(),
dl2.FeatureGroup(
id="fg",
children=[
dl2.Polygon(
# (north_km, east_km) from the
# city centre — the shape keeps its
# real-world size at any latitude.
positions=PHILADELPHIA.ring([
(2.2, -4.9),
(4.5, 1.0),
(2.2, 5.9),
(0.0, 0.0),
]),
color="#228be6",
fillOpacity=0.35,
),
dl2.Polyline(
positions=PHILADELPHIA.ring([
(-3.3, -4.9),
(-3.3, 1.0),
(-3.3, 6.9),
]),
color="#fa5252",
weight=3,
),
dl2.Circle(
center=PHILADELPHIA.at(1.1, 2.9),
radius=600,
color="#40c057",
fillOpacity=0.25,
),
dl2.Marker(position=PHILADELPHIA.at(-1.1, -1.0)),
],
),
],
),
shadow="sm",
radius="md",
withBorder=True,
style={"overflow": "hidden", "height": "45vh"},
),
span=8,
),
dmc.GridCol(
dmc.Stack(
[
info_panel(
"Group n_clicks",
dmc.Badge(id="fg-clicks", color="blue", variant="light", children="0"),
),
info_panel(
"Combined geojson (vector children)",
dmc.Code(
id="fg-geojson-readout",
block=True,
children="(click a shape)",
style={"maxHeight": "20vh", "overflow": "auto"},
),
),
],
gap="md",
),
span=4,
),
],
gutter="md",
),
code_panel("FeatureGroup", CODE_FG),
],
gap="md",
)
@callback(Output("lg-container", "children"), Input("lg-toggle", "checked"))
def render_group(show):
if not show:
return []
return dl2.LayerGroup(
children=[
dl2.Marker(position=PHILADELPHIA.center),
dl2.Marker(position=PHILADELPHIA.at(2.2, 2.9)),
dl2.Marker(position=PHILADELPHIA.at(-2.2, -2.9)),
]
)
@callback(Output("fg-clicks", "children"), Input("fg", "n_clicks"))
def show_clicks(n):
return str(n or 0)
@callback(Output("fg-geojson-readout", "children"), Input("fg", "geojson"))
def show_geojson(gj):
if not gj:
return "(no children yet)"
return json.dumps(gj, indent=2)[:2000]
# Light/dark basemap, driven off the color-scheme store.
register_theme_swap("lg-tile", TILES)
:defaultExpanded: false :withExpandedButton: true
Source: /layer-group