利⽤Python的folium包绘制城市道路图
写在前⾯
很长⼀段时间内,我都在研究在线地图的开发者⽂档,百度地图和⾼德地图的开发者中⼼提供了丰富的在线地图服务,虽然有⼀定的权限限制,但不得不说,还是给我的科研⼯作提供了特别⽅便的⼯具,在博客前⾯我先放上这两个在线地图开放平台的web API的地址链接:
基于这两个平台,博主进⾏了⼀系列的开发研究⼯作,本⽂介绍其中⼀项技术,如何⽤folium包绘制城市道路图,当然,也可绘制⾮城市道路图,只要提供正确的路名就⾏了。
开发⼯具:
Python3.7
Spyder编译器(也可以⽤pycharm,不过建议⽤Spyder,因为编译过程中产⽣的变量太多,基本上都是json数据,我都是⼀边看⼀边写,这⾥Spyder优势明显)
chrome浏览器
folium介绍及相关设置
folium基础功能
folium参数设置
先看两⾏代码:
import folium
line_road = folium.Map(location=[31.596730,120.233516],zoom_start=15,
tiles ='webrd02.is.autonavi/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}',
attr ='default')
location参数,设置展⽰地图的中⼼坐标点,就是说,⽐如你想看⽆锡市,可以设置成⽆锡市市中⼼的经纬度坐标
zoom_start是地图缩放等级,最⾼差不多可以到19还是20,如果想看⼤场景,就设⼩⼀点,想看局部地图就设⼤⼀点
tiles这个参数很重要,设置的是你的地图格式,默认的是OpenStreetMap,我这⾥把它改成
了’webrd02.is.autonavi/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}’,表⽰我⽤的是⾼德地图作为底图。为啥要改呢,因为每个不同的地图公司,⽤的坐标系不⼀样,⾼德地图和google地图、soso地图、aliyun地图、mapabc地图所⽤坐标相同,都是国测局(GCJ02)坐标,和百度地图⽤的坐标系不⼀样,如果直接拿百度坐标系下的经纬度画在⾼德地图上,那就会整体偏移,使⽤之前必须进⾏坐标转换
其他⽤默认参数
获取道路参数
本博客的⽬的是画道路的轮廓图,⾸先必须得有数据才能画图。博主知道,⽬前这些地图公司,都是⾃⼰把车在开在路上去采集路上的经纬度,只要我获取到了这些经纬度,那我不就能绘制道路了吗?本着这个想法,我就到处搜索资料,开发者的潜能是⽆限了,同样在CSDN上我到了⼀篇博客, ,真的很棒,不过他是⽤js写的,⽆所谓,⽅法是通的,⽤这篇博⽂提供的接⼝,真的实现了在地图上绘制道路的功能。
但是,但是,但是,,,
⽤了⼀段时间后,这个功能被封了,为此,我特意联系了⾼德地图开发者中⼼,他们的解释如下:
意思就是,这个功能⽤不了了,花钱也别想⽤。
其实很正常,这个功能太⽜逼了。
虽然⽤不了了,但我还是介绍⼀下怎么实现的,万⼀以后⼜能⽤了呢。
⾼德地图获取道路经纬度的API介绍
先看接⼝:
restapi.amap/v3/road/roadname?parameters
这个接⼝和⾼德地图其他功能的接⼝⼀样,后⾯的parameters是需要写的参数,每个参数之间⽤&隔开,其中keywords是道路名,这个参数必填,当然还有key也是必填的。现在来看看这⼀段的具体怎么写,⽐如我想获取的是⽆锡市钱荣路的经纬度:
# -*- coding: utf-8 -*-
"""
Created on Mon Mar 30 16:54:32 2020
@author: HP
"""
import json
import pandas as pd
quest import urlopen, quote
import folium
import numpy as np
road = quote('钱荣路')
key = YourKey  # 换成你⾃⼰申请的key
url ='restapi.amap/v3/road/roadname?city=0510&key=%s&keywords=%s'%(key, road)
req = urlopen(url)
res = ad().decode()
temp = json.loads(res)
roads = temp['roads']
pos =[]
# 由于道路可能分段,⽐如钱荣路会分成钱荣路普通段和钱荣路⾼架,这都属于钱荣路的路段,因此必须要都取出来
for p in range(len(roads)):
pos_cal =[]
line_qrroad = folium.Map(location=[31.596730,120.233516],zoom_start=15,
tiles ='webrd02.is.autonavi/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}',
attr ='default')
for i in range(len(pos)):
m = pos[i].split(';')
lat_lon =[]
for j in range(len(m)):
n = m[j].split(',')
n =list(map(float, n))
n[0],n[1]= n[1],n[0]
lat_lon.append(n)
pos_cal.append(n)
folium.PolyLine(lat_lon,weight =5, color ='red',opacity =0.8).add_to(line_qrroad)
line_qrroad.save('lineqrroad.html')
map_qrroad = folium.Map(location=[31.596730,120.233516],zoom_start=15,
tiles ='webrd02.is.autonavi/appmaptile?lang=zh_cn&size=1&scale=1&style=7&x={x}&y={y}&z={z}',
attr ='default')
for point in range(len(pos_cal)):
folium.CircleMarker(location=[pos_cal[point][0],pos_cal[point][1]],
radius=4,popup='popup',
color='red',fill=True,
fill_color='red').add_to(map_qrroad)
map_qrroad.save('render.html')
现在这段程序,已经没法解析出经纬度了,运⾏的话,会报如下错误:
runfile('D:/python/folium/qianrongroad.py', wdir='D:/python/folium')
Traceback (most recent call last):
File "D:\python\folium\qianrongroad.py", line 26,in<module>
roads = temp['roads']
KeyError:'roads'
意思就是说,没有‘road’这个key,我试图把请求串输⼊浏览器,返回的结果如下:
{"info":"INSUFFICIENT_PRIVILEGES","infocode":"10012","status":"0","sec_code_debug":"d41d8cd98f00b204e9800998ecf8427e","key":"ea12ed719e4ed1 3862dd0876384c6512","sec_code":"d41d8cd98f00b204e9800998ecf8427e"}
说我没有⾜够的权限。
好了,暂且不说了,看看代码的意思
前⾯是常规的json数据解析,没啥好说的,只要接⼝正常,就能取出数据来。
# 由于道路可能分段,⽐如钱荣路会分成钱荣路普通段和钱荣路⾼架,这都属于钱荣路的路段,因此必须要都取出来
for p in range(len(roads)):
上⾯这个循环,注释已经解释清楚了,⼀条路可能会被⾼德分成好⼏部分,当然这是科学的,⽐如完整的钱荣路是分成了钱荣路普通路段和⾼架路段的。也就是说解析出来的roads的长度是2,分别是roads[0]和roads[1],⽽经纬度数据则在roads[p][‘polylines’]⾥⾯。
for i in range(len(pos)):
m = pos[i].split(';')
lat_lon =[]
for j in range(len(m)):
n = m[j].split(',')
n =list(map(float, n))
n[0],n[1]= n[1],n[0]
spyder python下载lat_lon.append(n)
pos_cal.append(n)
这⾥是数据的分析,看起来写的很简单,其实很复杂,可惜没有数据来配套解释了。⾸先,经度和纬度之间⽤的是’,‘分割,每⼀⼩段路之间⽤的是’;‘来分割,这个’;'分割我理解为⾼德对数据的⼀种加密⽅式,完整的⼀条路被⾼德划分成了很多⼩段,我把数据取出来后,⾃⼰⽤matplotlib演⽰了⼀下完整的路的绘制过程,看下⾯⼏张动图(没法插视频):
为什么我这⾥要这么做,因为只有这样,我才是真正的理解了这些解析出来的数据是怎么连成⼀条完整的道路的,这样才好到folium中去绘制道路,实际上就是循环绘制,每⼀⼩段⼀⼩段的画,最后会连成⼀条完整的道路,过程就是下⾯这段代码:
for point in range(len(pos_cal)):
folium.CircleMarker(location=[pos_cal[point][0],pos_cal[point][1]],
radius=4,popup='popup',
color='red',fill=True,
fill_color='red').add_to(map_qrroad)
循环可以简化,博主习惯了写range(len)这种⽅式
渲染成⽹页,就可以打开了,看下结果:
忽略图中的圆圈标记,是我添加的其他信息。
放⼤看细节:
很良⼼有⽊有,双向车道、辅道、⽀路全部都有了,可惜当初没有把数据保存下来,只保存了这么个图。
这样就完事⼉了。
⾃从⾼德把这个接⼝封了之后,博主神伤了好久,想了各种办法,连付费使⽤都想出来了,但是⾼德⼀个字,不给⽤、没权限、有钱也不⾏。没办法,项⽬还要继续,功能还要继续实现。想到之前百度地图
事业部某年轻有为的负责⼈来咱们单位交流过,⼀番交涉,发现百度地图API也没有公开这个功能,但是离线地图可以。于是,,,博主⼜开始忙活了。
因涉及相关隐私,博主不具体介绍。总之,⼀番操作,获取到了百度地图坐标系下的道路经纬度数据,但前⾯说了,folium不⽀持百度地图,强⾏⽤百度地图坐标系下的经纬度坐标数据是会出乱⼦的,但这点⼩问题难不倒博主,⾼德地图API有坐标转换的接⼝呢。
百度坐标系下的坐标点转换成⾼德坐标系下的坐标点
接⼝在
接着上代码
import json
quest import urlopen, quote
import folium
import os
def BaiduMap2AMap(data):
polylines =[]
for i in range(len(data)):
poly =[]
for j in range(len(data[i])):
url ='restapi.amap/v3/assistant'\
'/coordinate/convert?locations=%f,%f&coordsys=baidu'\
'&key=YourKey'%(data[i][j][1], data[i][j][0])
req = urlopen(url)
res = ad().decode()
temp = json.loads(res)
location = temp['locations'].split(',')
location =list(map(float,location))
location[0], location[1]= location[1], location[0]
poly.append(location)
polylines.append(poly)
return polylines
常规的接⼝访问和数据分析代码,不做过多解释,不过给⼤家看⼀下函数的输⼊格式:
结合数据格式,⼤家应该能看明⽩这段代码
⽤相同的⽅法来画地图,看看结果
不错哦,再看看细节:
细节不如之前丰富,不过也很不错了。
再给⼤家看看,如果直接⽤百度坐标系下的经纬度点画到⾼德地图上是个啥效果:
看到没,整体偏了不少,所以坐标转换很重要。。
结语
写这篇博客,也是为了记录最近⼀段时间的⼯作。这篇博客实际上没解决任何问题,根本没有取到任何有⽤的数据。但是,其中坐标转换那个函数还是有点⽤的。⾥⾯的数据分析⽅法,也是博主花了⼀些⼼⾎才写出来的。
不断学习,不断进步吧。