在Dash App中实现Plotly Scattermapbox地图点缩放聚类功能
在Dash的Scattermapbox中实现缩放动态聚类
要实现地图缩放时点自动聚类,可通过监听地图缩放层级,结合聚类算法动态生成单个点或聚合点,具体实现如下:
核心实现逻辑
- 监听地图的
relayoutData获取当前缩放层级(zoom) - 根据缩放层级动态调整聚类算法的邻域半径:缩放越大,邻域半径越小,聚类拆分越精细
- 用DBSCAN算法对坐标点聚类,根据聚类结果分别渲染原始点或聚合点
修改后的完整代码
import dash from dash import dcc, html, Input, Output, State import pandas as pd from sklearn.cluster import DBSCAN import numpy as np # 示例数据 df = pd.DataFrame({ 'x': [1, 2, 3, 37.774, 37.775], 'Lat': [37.774322, 37.777035, 37.773033, 37.7745, 37.7746], 'Long': [-122.489761, -122.485555, -122.491220, -122.4898, -122.4899] }) MAPBOX_KEY = "你的Mapbox密钥" cd_cols = ['x'] # 自定义数据列 app = dash.Dash(__name__) app.layout = html.Div([ dcc.Graph(id="map"), dcc.Input(id="inp") ]) @app.callback( Output('map','figure'), Input('inp','value'), Input('map', 'relayoutData'), State('map', 'figure') ) def update_map(val, relayout_data, current_fig): # 获取当前缩放层级,初始默认设为13 zoom = relayout_data.get('mapbox.zoom', 13) if relayout_data else 13 # 根据缩放层级调整DBSCAN邻域半径(单位:度) eps = 0.001 / (2 ** (zoom - 13)) # 执行聚类 coords = df[['Lat', 'Long']].values db = DBSCAN(eps=eps, min_samples=1).fit(coords) df['cluster'] = db.labels_ data = [] # 遍历每个聚类结果 for cluster_id in df['cluster'].unique(): cluster_points = df[df['cluster'] == cluster_id] if len(cluster_points) == 1: # 单个点,保留原始标记样式 data.append({ "type": "scattermapbox", "lat": cluster_points["Lat"], "lon": cluster_points["Long"], "name": "Location", "showlegend": False, "hoverinfo": "text", "hovertext": f"x: {cluster_points['x'].iloc[0]}", "mode": "markers", "clickmode": "event+select", "customdata": cluster_points.loc[:, cd_cols].values, "marker": { "symbol": "circle", "size": 8, "opacity": 0.7, "color": "black" } }) else: # 聚合点,用更大的红色标记展示,hover显示点数 cluster_lat = cluster_points['Lat'].mean() cluster_lon = cluster_points['Long'].mean() data.append({ "type": "scattermapbox", "lat": [cluster_lat], "lon": [cluster_lon], "name": "Cluster", "showlegend": False, "hoverinfo": "text", "hovertext": f"聚合点数: {len(cluster_points)}", "mode": "markers", "marker": { "symbol": "circle", "size": 12 + len(cluster_points)*2, "opacity": 0.8, "color": "red" } }) # 保留当前地图中心,避免回调触发后位置重置 if current_fig and 'layout' in current_fig and 'mapbox' in current_fig['layout']: center = current_fig['layout']['mapbox']['center'] else: center = {"lat": df["Lat"].mean(), "lon": df["Long"].mean()} layout = { "autosize": True, "hovermode": "closest", "mapbox": { "accesstoken": MAPBOX_KEY, "bearing": 0, "center": center, "pitch": 0, "zoom": zoom, "style": "satellite-streets", }, } return {'data': data, 'layout': layout} if __name__ == '__main__': app.run_server(debug=True)
关键细节说明
- 缩放监听:通过
relayoutData捕获地图缩放变化,每次缩放都会触发回调更新聚类结果 - 聚类参数调整:
eps值随缩放层级动态变化,确保不同缩放比例下聚类效果符合预期 - 视觉区分:单个点保持原有黑色小标记,聚合点用红色大标记,hover时展示聚合数量,便于识别
- 地图状态保留:利用
State获取当前地图中心位置,避免回调触发后地图跳回初始坐标
内容的提问来源于stack exchange,提问作者kms
相关产品推荐
相关产品推荐

