You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何修复Dash Leaflet标记点击无响应问题(动态标记场景)

问题描述

我希望Dash Leaflet地图能响应用户对标记的点击操作,但目前应用对标记点击无任何响应。我使用了模式匹配回调,因为标记会被动态添加/移除,而非启动时静态生成。以下是最小示例代码:

# import third party packages
from dash import Dash, html, Output, Input, ctx, ALL
import dash_bootstrap_components as dbc
import dash_leaflet as dl
import dash_leaflet.express as dlx

THEME_URL = dbc.themes.LUX
THEME_NAME = "lux" # must be a dbc theme name, lowercase
DBC_CSS = ("https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.2/dbc.min.css")
app = Dash(__name__, external_stylesheets=[THEME_URL, DBC_CSS])

# define app layout major components
navbar = dbc.Navbar([
    # Use row and col to control vertical alignment of logo / brand
    dbc.Row([
        dbc.Col(
            dbc.NavbarBrand("Leaflet Test", className="ms-2 text-white fs-2"),
            width={"size": 3},
            style={"marginLeft": 30}
        ),
    ], align="center"),
], color="primary")

map_page = dbc.Container([
    dbc.Container([
        dbc.Checklist(
            options=[
                {"label": "Place of Birth", "value": "birth"},
                {"label": "Place of Death", "value": "death"},
            ],
            value=["birth"],
            id="place-options",
            inline=True,
            className="mb-2",
        ),
    ], fluid=True),
    dbc.Container(id="map-container", fluid=True)
], fluid=True)

app.layout = html.Div([
    navbar,
    dbc.Tabs([
        dbc.Tab(
            dbc.Card(dbc.CardBody(map_page), style={"border": "none"}),
            label="Map Filter",
            activeTabClassName="fw-bold fst-italic",
            tabClassName="fs-5",
        ),
    ]),
    
    dbc.Modal("Hello", is_open=False, id="bio-modal"),
])


# return leaflet map so it renders properly when app is started
@app.callback(
    Output("map-container", "children"),
    Input("place-options", "value"),
)
def draw_map(_):
    markers = [{
        "lat": 0,
        "lon": 0,
        "name": "marker0",
        "id": {"type": "marker-click", "index": 0}
    }]
    markers = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in markers])
    children = [dl.TileLayer(), dl.GeoJSON(data=markers, cluster=True)] # zoomToBoundsOnClick=True
    return  dl.Map(children, center=[0,0], zoom=6, style={"height": "78vh", "width": "96vw"}, id="map")


@app.callback(
    Output("bio-modal", "is_open"),
    Input({"type": "marker-click", "index": ALL}, "n_clicks"),
    prevent_initial_call=True,
)
def show_modal(_):
    print(ctx.triggered_id)
    return True


if __name__ == '__main__':
    app.run(debug=True)
修复方案

问题核心在于你用GeoJSON组件批量渲染标记,但模式匹配回调监听的是单个dl.Marker组件的n_clicks事件,两者不兼容。以下提供两种适配动态标记场景的修复方式:

方案1:利用GeoJSON组件的点击事件(适合批量标记场景)

GeoJSON组件自带click属性,点击标记时会返回对应feature的完整数据,可通过该数据区分不同标记,无需模式匹配单个标记ID。

修改要点

  • 给GeoJSON组件设置固定ID,用于监听点击事件
  • 回调输入改为GeoJSON的click属性,通过返回的clickData获取标记信息

修改后的完整代码:

# import third party packages
from dash import Dash, html, Output, Input, ctx, ALL
import dash_bootstrap_components as dbc
import dash_leaflet as dl
import dash_leaflet.express as dlx

THEME_URL = dbc.themes.LUX
THEME_NAME = "lux" # must be a dbc theme name, lowercase
DBC_CSS = ("https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.2/dbc.min.css")
app = Dash(__name__, external_stylesheets=[THEME_URL, DBC_CSS])

# 布局部分与原代码一致
navbar = dbc.Navbar([
    dbc.Row([
        dbc.Col(
            dbc.NavbarBrand("Leaflet Test", className="ms-2 text-white fs-2"),
            width={"size": 3},
            style={"marginLeft": 30}
        ),
    ], align="center"),
], color="primary")

map_page = dbc.Container([
    dbc.Container([
        dbc.Checklist(
            options=[
                {"label": "Place of Birth", "value": "birth"},
                {"label": "Place of Death", "value": "death"},
            ],
            value=["birth"],
            id="place-options",
            inline=True,
            className="mb-2",
        ),
    ], fluid=True),
    dbc.Container(id="map-container", fluid=True)
], fluid=True)

app.layout = html.Div([
    navbar,
    dbc.Tabs([
        dbc.Tab(
            dbc.Card(dbc.CardBody(map_page), style={"border": "none"}),
            label="Map Filter",
            activeTabClassName="fw-bold fst-italic",
            tabClassName="fs-5",
        ),
    ]),
    
    dbc.Modal("Hello", is_open=False, id="bio-modal"),
])

# 绘制地图:给GeoJSON添加ID,自定义标记识别字段
@app.callback(
    Output("map-container", "children"),
    Input("place-options", "value"),
)
def draw_map(_):
    markers = [{
        "lat": 0,
        "lon": 0,
        "name": "marker0",
        "custom_id": 0  # 自定义字段用于区分标记
    },
    {
        "lat": 10,
        "lon": 10,
        "name": "marker1",
        "custom_id": 1
    }]
    markers = dlx.dicts_to_geojson([{**c, **dict(tooltip=c['name'])} for c in markers])
    # 给GeoJSON设置固定ID,用于监听点击
    children = [dl.TileLayer(), dl.GeoJSON(data=markers, cluster=True, id="geo-markers")]
    return  dl.Map(children, center=[0,0], zoom=6, style={"height": "78vh", "width": "96vw"}, id="map")

# 监听GeoJSON点击事件,打开模态框
@app.callback(
    Output("bio-modal", "is_open"),
    Input("geo-markers", "click"),
    prevent_initial_call=True,
)
def show_modal(click_data):
    if click_data:
        # 获取点击标记的信息
        marker_name = click_data["properties"]["name"]
        marker_id = click_data["properties"]["custom_id"]
        print(f"点击标记:{marker_name},ID:{marker_id}")
    return True

if __name__ == '__main__':
    app.run(debug=True)

方案2:动态生成单个Marker组件(适合模式匹配回调场景)

如果必须使用模式匹配回调(比如需要单独控制每个标记的属性),则需要遍历数据生成多个dl.Marker组件,每个组件设置模式匹配ID,而非用GeoJSON批量渲染。

修改要点

  • 遍历标记数据,为每个标记生成独立的dl.Marker组件,设置模式匹配格式的id
  • 回调监听所有标记的n_clicks事件,通过ctx.triggered_id获取点击的标记索引

修改后的完整代码:

# import third party packages
from dash import Dash, html, Output, Input, ctx, ALL
import dash_bootstrap_components as dbc
import dash_leaflet as dl
import dash_leaflet.express as dlx

THEME_URL = dbc.themes.LUX
THEME_NAME = "lux" # must be a dbc theme name, lowercase
DBC_CSS = ("https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates@V1.0.2/dbc.min.css")
app = Dash(__name__, external_stylesheets=[THEME_URL, DBC_CSS])

# 布局部分与原代码一致
navbar = dbc.Navbar([
    dbc.Row([
        dbc.Col(
            dbc.NavbarBrand("Leaflet Test", className="ms-2 text-white fs-2"),
            width={"size": 3},
            style={"marginLeft": 30}
        ),
    ], align="center"),
], color="primary")

map_page = dbc.Container([
    dbc.Container([
        dbc.Checklist(
            options=[
                {"label": "Place of Birth", "value": "birth"},
                {"label": "Place of Death", "value": "death"},
            ],
            value=["birth"],
            id="place-options",
            inline=True,
            className="mb-2",
        ),
    ], fluid=True),
    dbc.Container(id="map-container", fluid=True)
], fluid=True)

app.layout = html.Div([
    navbar,
    dbc.Tabs([
        dbc.Tab(
            dbc.Card(dbc.CardBody(map_page), style={"border": "none"}),
            label="Map Filter",
            activeTabClassName="fw-bold fst-italic",
            tabClassName="fs-5",
        ),
    ]),
    
    dbc.Modal("Hello", is_open=False, id="bio-modal"),
])

# 动态生成单个Marker组件,每个标记带模式匹配ID
@app.callback(
    Output("map-container", "children"),
    Input("place-options", "value"),
)
def draw_map(_):
    markers_data = [{
        "lat": 0,
        "lon": 0,
        "name": "marker0",
        "index": 0
    },
    {
        "lat": 10,
        "lon": 10,
        "name": "marker1",
        "index": 1
    }]
    # 生成每个Marker组件,设置模式匹配ID
    markers = [
        dl.Marker(
            position=[marker["lat"], marker["lon"]],
            children=[dl.Tooltip(marker["name"])],
            id={"type": "marker-click", "index": marker["index"]}
        ) for marker in markers_data
    ]
    children = [dl.TileLayer()] + markers
    return  dl.Map(children, center=[0,0], zoom=6, style={"height": "78vh", "width": "96vw"}, id="map")

# 模式匹配回调,监听所有标记的点击事件
@app.callback(
    Output("bio-modal", "is_open"),
    Input({"type": "marker-click", "index": ALL}, "n_clicks"),
    prevent_initial_call=True,
)
def show_modal(_):
    triggered_id = ctx.triggered_id
    print(f"点击标记索引:{triggered_id['index']}")
    return True

if __name__ == '__main__':
    app.run(debug=True)

内容的提问来源于stack exchange,提问作者Chris Raper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 14:47:34