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

Plotly地图分支与客户点异色显示问题求助

问题描述

我需要在地图上可视化分支机构及其指定半径范围内的客户,要求分支点与客户点使用不同颜色。已为分支设置下拉菜单,选中后地图会居中并缩放至该分支,但目前无法实现分支与客户点异色显示,相关代码如下:

import plotly.express as px
import pandas as pd
import geopandas as gpd
from shapely.geometry import Polygon
from geopy.distance import geodesic

# Sample data
branches = pd.DataFrame({
    'name': ['Branch A', 'Branch B'],
    'lat': [39.647868, 41.06212],
    'lon': [27.882012, 28.98888]
})

customers = pd.DataFrame({
    'name': ['Customer 1', 'Customer 2', 'Customer 3'],
    'lat': [39.65113, 41.01879, 39.606046],
    'lon': [27.89559, 29.01249, 27.784534]
})

# Create a GeoDataFrame
branches_gdf = gpd.GeoDataFrame(branches, geometry=gpd.points_from_xy(branches.lon, branches.lat))
customers_gdf = gpd.GeoDataFrame(customers, geometry=gpd.points_from_xy(customers.lon, customers.lat))

# Function to create a circle
def create_circle(center, radius_km):
    angle_step = 10  # degrees
    angles = range(0, 360, angle_step)
    circle_points = [
        geodesic(kilometers=radius_km).destination((center.y, center.x), angle)
        for angle in angles
    ]
    return Polygon([(point.longitude, point.latitude) for point in circle_points])

# Define a perimeter in kilometers
perimeter_radius_km = 5

# Plot initial setup
fig = px.scatter_mapbox(
    lat=customers_gdf.geometry.y,
    lon=customers_gdf.geometry.x,
    hover_name=customers_gdf['name'],
    zoom=4,
    mapbox_style='carto-positron'
)

# Add all branch locations
fig.add_scattermapbox(
    lat=branches_gdf.geometry.y,
    lon=branches_gdf.geometry.x,
    mode='markers',
    marker=dict(size=12, color='red'),
    name='Branches',
    hovertext=branches_gdf['name']
)

# Add dropdown for selecting branches
buttons = []
for branch in branches_gdf.itertuples():
    branch_location = branch.geometry
    perimeter = create_circle(branch_location, perimeter_radius_km)
    customers_within = customers_gdf[customers_gdf.within(perimeter)]

    buttons.append(dict(
        args=[{
            'lat': [[branch_location.y] + customers_within.geometry.y.tolist(), [point[1] for point in perimeter.exterior.coords]],
            'lon': [[branch_location.x] + customers_within.geometry.x.tolist(), [point[0] for point in perimeter.exterior.coords]],
            'mode': ['markers', 'lines'],
            'marker': [dict(size=12, color='red')] + [dict(size=8, color='blue')] * len(customers_within) + [dict(size=0, color='blue')],
            'hovertext': [[branch.name] + customers_within['name'].tolist(), ['Perimeter']],
            'name': ['Branch and Customers', 'Perimeter']
        }],
        label=branch.name,
        method='update'
    ))
    buttons.append(dict(
        args=[{
            'lat': [customers_within.geometry.y.tolist() + [branch_location.y]],
            'lon': [customers_within.geometry.x.tolist() + [branch_location.x]],
            'marker': dict(size=8, color='blue'),  # Customer color
            'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x},
            'mapbox.zoom': 12
        }],
        label=branch.name,
        method='update'
    ))
    # Update the map center and zoom
    buttons[-1]['args'].append({'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x}, 'mapbox.zoom': 12})

fig.update_layout(
    updatemenus=[{
        'buttons': buttons,
        'direction': 'down',
        'showactive': True
    }]
)

# Save the interactive map to an HTML file
fig.write_html("interactive_map.html")
问题分析与修正方案

原代码的核心问题是下拉按钮的更新逻辑错误:

  • 单个Trace无法为不同点设置不同颜色,必须将分支和客户拆分为独立Trace
  • 第二个按钮错误地将所有点统一设置为蓝色,覆盖了分支的红色样式
  • 更新参数结构混乱,导致图层属性冲突

以下是修正后的代码,确保分支点(红色)、客户点(蓝色)、周长(灰色线条)清晰区分,且下拉选中后正确居中缩放:

import plotly.express as px
import pandas as pd
import geopandas as gpd
from shapely.geometry import Polygon
from geopy.distance import geodesic

# 样本数据
branches = pd.DataFrame({
    'name': ['Branch A', 'Branch B'],
    'lat': [39.647868, 41.06212],
    'lon': [27.882012, 28.98888]
})

customers = pd.DataFrame({
    'name': ['Customer 1', 'Customer 2', 'Customer 3'],
    'lat': [39.65113, 41.01879, 39.606046],
    'lon': [27.89559, 29.01249, 27.784534]
})

# 创建GeoDataFrame
branches_gdf = gpd.GeoDataFrame(branches, geometry=gpd.points_from_xy(branches.lon, branches.lat))
customers_gdf = gpd.GeoDataFrame(customers, geometry=gpd.points_from_xy(customers.lon, customers.lat))

# 创建圆形范围的函数
def create_circle(center, radius_km):
    angle_step = 10  # 角度步长
    angles = range(0, 360, angle_step)
    circle_points = [
        geodesic(kilometers=radius_km).destination((center.y, center.x), angle)
        for angle in angles
    ]
    return Polygon([(point.longitude, point.latitude) for point in circle_points])

# 定义半径(公里)
perimeter_radius_km = 5

# 初始地图设置:显示所有客户和分支
fig = px.scatter_mapbox(
    lat=customers_gdf.geometry.y,
    lon=customers_gdf.geometry.x,
    hover_name=customers_gdf['name'],
    zoom=4,
    mapbox_style='carto-positron',
    color_discrete_sequence=['blue'],
    marker={'size':8},
    title='All Customers & Branches'
)

# 添加所有分支点
fig.add_scattermapbox(
    lat=branches_gdf.geometry.y,
    lon=branches_gdf.geometry.x,
    mode='markers',
    marker=dict(size=12, color='red'),
    name='Branches',
    hovertext=branches_gdf['name']
)

# 构建下拉按钮
buttons = []
# 添加"显示全部"按钮
buttons.append(dict(
    args=[{
        'data': [
            # 客户点
            {
                'lat': customers_gdf.geometry.y.tolist(),
                'lon': customers_gdf.geometry.x.tolist(),
                'mode': 'markers',
                'marker': dict(size=8, color='blue'),
                'hovertext': customers_gdf['name'].tolist(),
                'name': 'Customers'
            },
            # 分支点
            {
                'lat': branches_gdf.geometry.y.tolist(),
                'lon': branches_gdf.geometry.x.tolist(),
                'mode': 'markers',
                'marker': dict(size=12, color='red'),
                'hovertext': branches_gdf['name'].tolist(),
                'name': 'Branches'
            }
        ],
        'layout': {
            'mapbox.center': {'lat': (branches.lat.mean()), 'lon': (branches.lon.mean())},
            'mapbox.zoom': 4,
            'title': 'All Customers & Branches'
        }
    }],
    label='Show All',
    method='update'
))

# 为每个分支添加按钮
for branch in branches_gdf.itertuples():
    branch_location = branch.geometry
    perimeter = create_circle(branch_location, perimeter_radius_km)
    customers_within = customers_gdf[customers_gdf.within(perimeter)]
    
    # 提取周长的坐标
    perimeter_lats = [point[1] for point in perimeter.exterior.coords]
    perimeter_lons = [point[0] for point in perimeter.exterior.coords]
    
    buttons.append(dict(
        args=[{
            'data': [
                # 当前分支点
                {
                    'lat': [branch_location.y],
                    'lon': [branch_location.x],
                    'mode': 'markers',
                    'marker': dict(size=12, color='red'),
                    'hovertext': [branch.name],
                    'name': 'Selected Branch'
                },
                # 范围内的客户点
                {
                    'lat': customers_within.geometry.y.tolist(),
                    'lon': customers_within.geometry.x.tolist(),
                    'mode': 'markers',
                    'marker': dict(size=8, color='blue'),
                    'hovertext': customers_within['name'].tolist(),
                    'name': 'Customers in Range'
                },
                # 周长线条
                {
                    'lat': perimeter_lats,
                    'lon': perimeter_lons,
                    'mode': 'lines',
                    'line': dict(color='gray', width=2),
                    'hovertext': ['Perimeter (5km)'],
                    'name': 'Perimeter'
                }
            ],
            'layout': {
                'mapbox.center': {'lat': branch_location.y, 'lon': branch_location.x},
                'mapbox.zoom': 12,
                'title': f'{branch.name} & Customers in 5km Range'
            }
        }],
        label=branch.name,
        method='update'
    ))

# 更新布局添加下拉菜单
fig.update_layout(
    updatemenus=[{
        'buttons': buttons,
        'direction': 'down',
        'showactive': True,
        'x': 0.1,
        'y': 1.1
    }],
    margin={'r':0, 't':50, 'l':0, 'b':0}
)

# 保存为HTML文件
fig.write_html("interactive_map.html")
核心修改点
  • 将分支、客户、周长拆分为独立的Trace,每个Trace单独设置颜色和样式
  • 统一下拉按钮的更新逻辑,通过data参数完整替换所有图层,避免冲突
  • 添加"显示全部"按钮,方便返回初始视图
  • 明确设置每个Trace的name属性,图例显示更清晰
  • 优化地图标题,当前视图信息一目了然

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 23:37:04