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

在IPython Notebook中实现地图点击交互式时间序列可视化

嘿,这个需求用Plotly就能完美搞定!它自带的交互能力刚好能满足你点击地图点位查看对应时间序列的需求,而且处理NaN缺失值也很顺手。我给你准备了两种场景的实现方案,一种适合在Jupyter Notebook里快速验证,另一种可以部署成网页应用,你按需选择就行~

实现方案:交互式地图+点击触发时间序列

1. 先安装依赖库

根据你的使用场景安装对应的包:

# Jupyter Notebook环境(快速验证)
pip install plotly pandas ipywidgets

# 网页部署场景(Dash框架)
pip install dash plotly pandas

2. 数据预处理

首先把你的宽格式矩阵(行=经纬度,列=日期)转成长格式,这样Plotly更容易处理。我先模拟一份数据给你参考:

import pandas as pd
import numpy as np

# 模拟你的(Long, Lat)点位集合
locations = [
    (116.4074, 39.9042),   # 北京
    (121.4737, 31.2304),   # 上海
    (113.2644, 23.1291),   # 广州
    (104.0668, 30.5728)    # 成都
]
# 模拟日期序列
dates = pd.date_range(start='2023-01-01', periods=30, freq='D')

# 生成带随机NaN的数值矩阵(模拟你的数据)
np.random.seed(42)
data = np.random.randn(len(locations), len(dates))
data[np.random.choice(data.size, size=15, replace=False)] = np.nan

# 转换成宽格式DataFrame(和你的原始数据结构一致)
df_wide = pd.DataFrame(
    data,
    index=pd.MultiIndex.from_tuples(locations, names=['Long', 'Lat']),
    columns=dates
)

# 转成长格式,方便后续绘图
df_long = df_wide.reset_index().melt(
    id_vars=['Long', 'Lat'],
    var_name='Date',
    value_name='Value'
)

3. 场景1:Jupyter Notebook快速实现

这个版本适合本地调试,交互响应快:

import plotly.graph_objects as go
from ipywidgets import Output, VBox

# 创建交互式地图
map_fig = go.FigureWidget(go.Scattermapbox(
    lat=df_long['Lat'].unique(),
    lon=df_long['Long'].unique(),
    mode='markers',
    marker=dict(size=10, color='royalblue'),
    text=[f"({lon:.2f}, {lat:.2f})" for lon, lat in zip(df_long['Long'].unique(), df_long['Lat'].unique())],
    hoverinfo='text'
))
map_fig.update_layout(
    mapbox_style='carto-positron',  # 可选:'open-street-map'/'stamen-terrain'
    mapbox_zoom=3,
    mapbox_center={'lat': 35, 'lon': 110},
    margin={'l':0, 'r':0, 't':0, 'b':0}
)

# 创建空的时间序列图
ts_fig = go.FigureWidget()
ts_fig.update_layout(
    title='点击地图点位查看时间序列',
    xaxis_title='日期',
    yaxis_title='数值'
)

# 定义点击回调函数
def on_map_click(trace, points, state):
    if points.point_inds:
        # 获取点击点位的经纬度
        ind = points.point_inds[0]
        lon = df_long['Long'].unique()[ind]
        lat = df_long['Lat'].unique()[ind]
        
        # 筛选该点位的时间序列,自动跳过NaN
        ts_data = df_long[(df_long['Long'] == lon) & (df_long['Lat'] == lat)].dropna()
        
        # 更新时间序列图
        with ts_fig.batch_update():
            ts_fig.data = []
            ts_fig.add_scatter(
                x=ts_data['Date'],
                y=ts_data['Value'],
                mode='lines+markers',
                name=f"({lon:.2f}, {lat:.2f})"
            )
            ts_fig.update_layout(title=f"点位 ({lon:.2f}, {lat:.2f}) 的时间序列")

# 绑定点击事件
map_fig.data[0].on_click(on_map_click)

# 组合显示地图和时间序列图
VBox([map_fig, ts_fig])

4. 场景2:Dash网页部署版本

这个版本可以部署成公共网页,适合多人使用:

import dash
from dash import dcc, html, Input, Output, State
import plotly.graph_objects as go

app = dash.Dash(__name__)

# 网页布局:标题+地图+时间序列图
app.layout = html.Div([
    html.H1("交互式地图-时间序列查看器", style={'textAlign': 'center'}),
    dcc.Graph(id='map-graph', style={'height': '60vh'}),
    dcc.Graph(id='ts-graph', style={'height': '35vh'})
])

# 初始化地图
@app.callback(
    Output('map-graph', 'figure'),
    Input('map-graph', 'id')
)
def init_map(_):
    return go.Figure(go.Scattermapbox(
        lat=df_long['Lat'].unique(),
        lon=df_long['Long'].unique(),
        mode='markers',
        marker=dict(size=10, color='royalblue'),
        text=[f"({lon:.2f}, {lat:.2f})" for lon, lat in zip(df_long['Long'].unique(), df_long['Lat'].unique())],
        hoverinfo='text'
    )).update_layout(
        mapbox_style='carto-positron',
        mapbox_zoom=3,
        mapbox_center={'lat': 35, 'lon': 110},
        margin={'l':0, 'r':0, 't':0, 'b':0}
    )

# 点击地图更新时间序列
@app.callback(
    Output('ts-graph', 'figure'),
    Input('map-graph', 'clickData'),
    State('map-graph', 'figure')
)
def update_time_series(clickData, map_figure):
    # 默认显示提示图
    if not clickData:
        return go.Figure(layout=dict(
            title='点击地图上的点位查看对应时间序列',
            xaxis_title='日期',
            yaxis_title='数值'
        ))
    
    # 获取点击点位的经纬度
    point_ind = clickData['points'][0]['pointIndex']
    lon = map_figure['data'][0]['lon'][point_ind]
    lat = map_figure['data'][0]['lat'][point_ind]
    
    # 筛选数据并跳过NaN
    ts_data = df_long[(df_long['Long'] == lon) & (df_long['Lat'] == lat)].dropna()
    
    # 返回时间序列图
    return go.Figure(
        data=[go.Scatter(x=ts_data['Date'], y=ts_data['Value'], mode='lines+markers')],
        layout=dict(
            title=f"点位 ({lon:.2f}, {lat:.2f}) 的时间序列",
            xaxis_title='日期',
            yaxis_title='数值'
        )
    )

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

一些优化小提示

  • NaN处理:代码里用dropna()跳过缺失值,如果你想保留NaN的位置,直接去掉这个方法即可——Plotly的折线图遇到NaN会自动断开,标记点会跳过缺失值。
  • 性能优化:如果你的点位超过上千个,可以开启Plotly的WebGL渲染(在marker里加opacity=0.8),或者对点位做聚合,提升交互流畅度。
  • 样式自定义:可以修改点位的颜色、大小,时间序列的线条样式、颜色,甚至替换地图瓦片,完全适配你的可视化需求。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 08:56:46