地图可视化-交互式地图

  • 地图可视化第二篇,交互式地图

  • 资料来源:

    https://github.com/cyang-kth/fmm-examples/blob/master/2-map_match_interactive.ipynb
    https://ipyleaflet.readthedocs.io/en/latest/
    https://ipywidgets.readthedocs.io/en/latest/

  • 更新

    1
    2022.04.18 初始

导语

前一篇的 Folium 固然展示好用,但是涉及调试相关,需要交互时非常吃力.

恰好现在是整理数据关键时刻,心一横开始重写界面. 其实主要思路来自 2-map_match_interactive.ipynb,这里仅仅是使用的记录.

  • 使用 ipyleaflet 制作交互式底图
  • 使用 ipywidgets 制作 weight,ipyleaflet 的 WidgetControl 提供了 ipywidgets 的容器.

交互式地图

一个 fmm 的官方示例,就以这个为基础,来简述相关内容了.

fmm_draw.gif

  • 整个地图匹配参数可调
  • 可以在地图上直接选取轨迹

底图

ipyleaflet 在使用中与 Folium 非常相似,内置了 openstreemap 的底图,也可以传入自定义瓦片底图.

  • 内置的 Basemaps 包括范围多详情见 -> Basemaps
  • 默认地图缩放是固定的,无法随鼠标滚轮移动 -> scroll_wheel_zoom=True 更加符合习惯
1
m = Map(center=(59.341884644077, 18.06016188114882),zoom=14,scroll_wheel_zoom=True,basemap=basemaps.OpenStreetMap.Mapnik)

轨迹选取

ipyleaflet 支持 N 多的 Controls,这里使用的是 Draw Control.其支持直接从底图取点连城线,然后返回 GEOJSON 格式的轨迹数据.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
draw_control = DrawControl(circlemarker={}, polygon={}, edit=False,
remove=False) # 新的 draw_control

draw_control.polyline = {
"shapeOptions": {
"color": "#fca45d",
"weight": 6,
"opacity": 1
}
}

m.add_control(draw_control)

def handle_draw(target, action, geo_json):
global test_json
if (action=="deleted"): # 清空曲线
m.remove_layer(mr_layer)
m.remove_layer(traj_layer)
mr_layer = None
traj_layer = None
if (action=="created"): # 选取完毕
draw_control.clear()
test_json =geo_json
update_map(geo_json)

draw_control.on_draw(handle_draw) # 绑定方法

weight

这里使用的是 ipywidgets 这是一个 jupyter 专用的小组件库,ipyleaflet 提供了 WidgetControl 的容器,两者组合就能完成丰富的交互.

原图的 k r e 都是 ipywidgets 再组合到 WidgetControl 容器中.

1
2
3
4
5
6
7
8
9
10
11
k_slider = IntSlider(description='k:', min=4, max=32, value=20,step=4)
k_widget_control = WidgetControl(widget=k_slider, position='topright')
m.add_control(k_widget_control)

r_slider = IntSlider(description='r (m):', min=50, max=500, value=300, step =50)
r_widget_control = WidgetControl(widget=r_slider, position='topright')
m.add_control(r_widget_control)

e_slider = IntSlider(description='e (m):', min=50, max=200, value=140, step = 30)
e_widget_control = WidgetControl(widget=e_slider, position='topright')
m.add_control(e_widget_control)

需要读取小部件值时,直接 实例变量名.value

1
match_geojson_network(data,k_slider.value,r_slider.value,e_slider.value)

值变动时,绑定观察者操作 -> Traitlet events

1
2
3
4
5
def on_value_change(change):# 操作
update_map(None)
k_slider.observe(on_value_change, names='value')
r_slider.observe(on_value_change, names='value')
e_slider.observe(on_value_change, names='value')

ipywidgets 操作 dataframe

这里是追加的方便操作 dataframe 单个值的功能

  • 传入的 df 有 4 列, time lat lon 和 filter,每次改变 checkbox 时更改 filter 的 0 1 值.
  • df 一行对应 3 个 box,组成一个 items,多个 items 组 boxs 列表,最后装入 VBox.
  • 仅调试用,so 非常粗糙,不过就需要核对轨迹,修改少量点是够用了.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
def item(item_filter):
@throttle(0.2)
def on_value_change2(change):# 改变值
index = int(item3.description)
flag = cells.iloc[index, -3]
cells.iloc[index, -3] = 1 if flag == 0 else 0
# xx do something with map


itme1 = widgets.Label(item_filter.time.strftime('%Y-%m-%d %H:%M:%S'))
itme2 = widgets.Label(str(item_filter.clid))
item3 = widgets.Checkbox(
value=False if item_filter["filter"] == 0 else True,
description=str(item_filter.name),
disabled=False,
indent=False)
itme_Null = widgets.Label(" ")
item3.observe(on_value_change2, names='value')

return [itme1, itme_Null, itme2, itme_Null, itme_Null, item3]


def box(item_filter):
items = item(item_filter)
return widgets.HBox(items)

boxs = [box(cells.iloc[i]) for i in range(len(cells))]

box_layout = Layout(display='flex',
flex_flow='column',
align_items='stretch',
border='solid')

widgets.VBox(children=boxs, layout=box_layout)

延时操作

小部件值的变动非常频繁,可能引起大量的更新,官方文档 -> DebouncingThrottling 提供了两种延时调用限制频率的方法.个人偏向 Throttling.

使用时就是一个装饰器应用,原文档代码+示例很全了,不加赘述.

其他

有一点的坑是 vscode + jupyter 里面经常遇到 ipyleaflet 无响应问题

  • 此时没有错误信息,只要重启一下就正常.
  • 出现频率随机,相同的流程下有时一直没问题,有时连续 2 3 次卡住.

诡异的是单独使用 jupyter ,从浏览器访问,则完全没有问题.详细查询无果后,此问题搁置.

  • 启用 jupyter notebook --config "" --ip 0.0.0.0 --no-browser "$@"