导语 gis 项目绝对逃不过地图可视化…
python 的地图可视化包有很多,不一而足,仅总结项目涉及到的几个库,一些示例代码及坑..
folium : 项目主力使用,官方文档 官方例程
散点图 热力图 静态轨迹图 轨迹时序图 轨迹动图 结果对比图 plotly-express : 代码一般简写 px
前排提醒
Folium 默认底图是 osm 如果展示访问涉及国家边界,会是很大的风险点.如果可能务必更换底图,个人推荐是 高德地图 几乎无缝使用. 多图杀猫 堆叠,同一数据的各种炫酷展示,提供几套方案备选,汇报用得上. 3D + 交互 = 300% 通过概率.不是folium folium 使用的结果很满意,基本 cover 了项目需要的地图数据展示.
学习folium
中文介绍+入门 私以为这一篇比较合个人口味 Python地图可视化之Folium ,如果是简单使用,这一篇就够了. 插件的使用几乎都在 官方例程 中有体现,下载下来一个一个看,总用时不超过 2 个小时.根据需要的效果挑挑拣拣,忽哟 验收的图就出来了. Folium 默认是 osm 底图,如果可能尽量更换底图.
瓦片底图有高德/百度/腾讯,高德最方便,除了 gcj坐标 基本无缝使用.百度还有 bd 坐标系问题,腾讯地图也不是那么完美. 1 2 3 4 5 6 7 8 9 10 m = folium.Map( location=[39.917834 , 116.397036 ], zoom_start=11 , width='100%' , height='100%' , zoom_control='False' , control_scale=True , tiles= 'http://webrd02.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}<ype=6' , attr='AutoNavi' )
folium 画图基本上都是在 folium.Map
实例 m
上添加新对象,最后调用 m.save()
保存或者直接在 jupyter 显示.
下面是项目中常见的类型,只总结了几个.给出了示例代码.
散点图 单个点是 folium.Marker
实例.
popup 是点击后展示的文本 tooltip 是鼠标滑过时展示文本 1 2 3 4 5 6 7 8 9 10 11 folium.Marker( [39.917834 , 116.397036 ], popup=(f'点击: {1 :.0 f} <br>\ 点击2: {2 :.0 f} \ 点击3: {3 :.0 f} <br>' ), tooltip='滑动' , icon=DivIcon( icon_size=(150 , 36 ), icon_anchor=(7 , 20 ), html=f'<div style="font-size: 13pt;">点</div>' , )).add_to(m)
以上只是把文字+事件嵌到了地图上,还不够,还需在地图上对应位置标明一个点.配色随便选的.
1 2 3 4 5 6 7 8 9 10 incidents = folium.FeatureGroup() incidents.add_child( folium.CircleMarker( [39.917834 , 116.397036 ], radius=7 , color='yellow' , fill=True , fill_color='red' , fill_opacity=0.4 )) m.add_child(incidents)
真实的散点图,需要将上面的过程重复若干次.当然颜色也需要重新调整.
热力图 热力图用来展示分布变化屡试不爽,官方提供了两个示例,足矣.
Heatmap : 直接传入经纬度的 list 绘制静态热力图HeatMapWithTime : 随时间变化的热力图,展示变化数据时更加实用.轨迹图 轨迹图实际用到了 3 种
轨迹图 传入经纬度的 list 之后将点连接.
1 2 3 4 5 6 7 locals = [[39.917834 , 116.397036 ],[39.937834 , 116.397036 ],[39.937834 , 116.377036 ]]folium.PolyLine( locals , weight=4 , color='red' , opacity=0.8 , ).add_to(m)
轨迹动图 借助 AntPath 插件,可以将轨迹的方向标出来.
1 2 3 4 5 6 7 8 9 locals = [[39.917834 , 116.397036 ],[39.937834 , 116.397036 ],[39.937834 , 116.377036 ]]folium.plugins.AntPath( locations=locals , paused=False , color='red' , dash_array=[20 , 30 ], delay=800 , popup=id , tooltip=id ).add_to(m)
轨迹时序图 来自 How to display a time series of folium maps?
轨迹时序图:
实际上是合成了带时间戳的 GeoJSONs
借助 TimestampedGeoJson 插件显示 假设源数据是 datas
[[lat,lon,time],xx]
时间单位必须是字符串 最大的一个坑是 GeoJson 描述点时是 lon 在前..
实际上这样的数据转换定义一个模板文件比较好,时间有限直接强制转换了,请不要直接在生产环境使用,会被同事喷死的.
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 35 36 37 38 for i in range (len (datas) - 1 ): lines.append({ 'coordinates' : [ [datas[i][1 ], datas[i][0 ]], [datas[i + 1 ][1 ], datas[i + 1 ][0 ]], ], 'dates' : [datas[i][2 ], datas[i + 1 ][2 ]], 'weight' : 5 , 'color' : '#3388ff' , 'opacity' : 0.5 , }) features = [{ 'type' : 'Feature' , 'geometry' : { 'type' : 'LineString' , 'coordinates' : line['coordinates' ], }, 'properties' : { 'times' : line['dates' ], 'style' : { 'color' : line['color' ], 'weight' : line['weight' ] if 'weight' in line else 5 , 'opacity' : line['opacity' ] if 'opacity' in line else 0.5 , } } } for line in lines] plugins.TimestampedGeoJson( { 'type' : 'FeatureCollection' , 'features' : features, }, period='PT1M' , add_last_point=False ).add_to(m)
结果对比图 显示两个上下/左右同步显示的地图,可以加载不同数据源以对比.
官方示例 -> plugin-DualMap.ipynb
其他小点 翻阅官方文档时的小点
鼠标位置追踪 MousePosition
插件
1 2 3 4 5 6 7 8 9 10 11 12 13 from folium.plugins import MousePositionformatter = "function(num) {return L.Util.formatNum(num, 3) + ' º ';};" MousePosition( position="topright" , separator=" | " , empty_string="NaN" , lng_first=True , num_digits=20 , prefix="Coordinates:" , lat_formatter=formatter, lng_formatter=formatter, ).add_to(m)
图例 添加一个可拖动图例
issue # How can I add a legend to a folium map? 源码来自 How does one add a legend (categorical) to a folium map ,效果也见这个页面. 不太懂 html 的描述语言,仅调整到自己能用的状态. 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 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 template = """ {% macro html(this, kwargs) %} <!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>jQuery UI Draggable - Default functionality</title> <link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css"> <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> <script> $( function() { $( "#maplegend" ).draggable({ start: function (event, ui) { $(this).css({ right: "auto", top: "auto", bottom: "auto", }); } }); }); </script> </head> <body> <div id='maplegend' class='maplegend' style='position: absolute; z-index:9999; border:2px solid grey; background-color:rgba(255, 255, 255, 0.8); border-radius:6px; padding: 10px; font-size:14px; right: 20px; top: 40px;'> <div class='legend-title'>图例</div> <div class='legend-scale'> <ul class='legend-labels'> <li><span style='background:red;opacity:0.7;'></span>图例1</li> <li><span style='background:darkblue;opacity:0.7;'></span>图例2</li> <li><span style='background:green;opacity:0.7;'></span>图例3</li> </ul> </div> </div> </body> </html> <style type='text/css'> .maplegend .legend-title { text-align: left; margin-bottom: 5px; font-weight: bold; font-size: 90%; } .maplegend .legend-scale ul { margin: 0; margin-bottom: 5px; padding: 0; float: left; list-style: none; } .maplegend .legend-scale ul li { font-size: 80%; list-style: none; margin-left: 0; line-height: 18px; margin-bottom: 2px; } .maplegend ul.legend-labels li span { display: block; float: left; height: 16px; width: 30px; margin-right: 5px; margin-left: 0; border: 1px solid #999; } .maplegend .legend-source { font-size: 80%; color: #777; clear: both; } .maplegend a { color: #777; } </style> {% endmacro %}""" macro = MacroElement() macro._template = Template(template) m.get_root().add_child(macro)
显示表格数据 同理不太懂 html 描述语言,仅调整到项目能用状态.
data 是 pandas.dataframe 整个过程貌似是转换成 html 再插入地图. 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 import brancahtml = data[['id' , 'normal' , 'abnormal' ]].to_html( classes= "table table-striped table-hover table-condensed table-responsive" , index=False ) legend_html = """ {% macro html(this, kwargs) %} <div style=" position: fixed; bottom: 20px; right: 20px; width: auto; height: auto; z-index:9999; font-size:14px; background-color: #ffffff; opacity: 0.8; "> """ + \html + \ """ </div> {% endmacro %} """ legend = branca.element.MacroElement() legend._template = branca.element.Template(legend_html) m.get_root().add_child(legend)
动态调整缩放 最开始的自定义底图时就有 zoom_start
这个缩放等级,但是随着数据不同,缩放等级最好还是动态调整.
1 m.fit_bounds(m.get_bounds())
但是在 DualMap
的对比图时不可用,会时好时坏.
plotly-express plotly-express 貌似是 plotly 的使用太繁琐,然后出的一个简化使用的版本.能直接传入 pandas 的 dataframe ,似乎值得深度了解.
3d 轨迹图 实际上是使用了 px.scatter_3d()
这一个方法绘制 时间-经纬度的 3d 图.
支持直接传入 dataframe ..实在是不用都对不起编写这个库的人.. 官方文档 + 示例 df 是传入的 dataframe 1 2 3 4 5 6 7 8 9 10 fig = px.scatter_3d( data_frame=df, x=df[lat], y=df[lon], z=df[timekey], size='lat' , size_max=size, color='color' , ) fig.show()
最后保存与 folium
略有不同
1 plotly.offline.plot(fig, filename='./3d.html' )