引言
近期博主在做地理数据可视化方面的研究,pyecharts提供了较好的工具,里面提供了很多图形,尤其是Map和Geo这两种图,一种是地图,一种是地理信息图。pyecharts的基础还是echart,echart是百度地图开源的一个数据可视化 JS 库,从我个人使用的情况来看,目前pyecharts(博主pyecharts版本是0.5.11)有这两个问题:
- 地图精度不够。目前pyecharts提供的地图层级有世界地图、各个国家的地图、中国各省地图、中国各地市图、区县图,到区县后,就不再细分,如果我想看某条道路的具体信息,就难以实现。
- 经纬度定位“不准确”。这不属于pyecharts是问题,应该属于百度地图在地理编码上的特点,当然,并不是说百度地图地理编码不准确,而是博主的目前在做的项目,地理位置都比较奇特,不管是BaiduMap还是AMap(高德地图)都很难定位到特别准确。由于pyecharts的底层地图是百度地图,所以看起来是pyecharts定位“不准确”。
要解决第一个问题,就是说想要更高层级的地图,这个就需要换可视化包了,也就是说,将pyecharts改成具有其他更高层级地图的可视化包。博主使用的是folium,这个包也很强大,pyecharts的强大之处在与echart的可视化功能,folium则体现在地图及地图的扩展应用上。folium目前支持OpenStreetMap、AMap等几种地图,但不支持百度地图。一度吐血,为啥我用的这两个包底层地图就不能通用下。但其实也好,为了用好这两个包,博主对百度、高德这两大地图大佬的开发文档进行了研究,现在将二者地理编码的方式记录下来。
开发环境:
- Python3.7;
- pyecharts0.5.11;
- pycharm
百度地图地理编码
开发文档看这里,使用前需要去申请AK,同时最好申请开发者认证,能提升访问限制。开发文档其实写的很详细,无非是爬虫get和json解析那一套,我直接上代码。
import json
import pandas as pd
import numpy as np
from urllib.request import urlopen, quote
import requests
def Scene2CoordinateBaiduMap(filename):
data = pd.read_csv(filename)
result = []
i = 1
j = 1
for k in data['地点']:
address = quote(k) # 1
url = 'http://api.map.baidu.com/geocoder/v2/?address=' + address + '&output=json&ak=yourAK' # 2
req = urlopen(url)
res = req.read().decode()
temp = json.loads(res)
try: # 3
result.append(temp['result']['location'])
print('catch %d \n' % i)
i = i + 1
except:
result.append({'lat': 'null', 'lng': 'null'})
print('fail %d \n' % j)
j = j + 1
print('成功定位%d个,成功率%.2f%%' % (i - 1, (i - 1) * 100 / (i + j - 2)))
df = pd.DataFrame(result)
df.to_csv('经纬度原始数据-百度地图.csv', index=False)
if __name__ == '__main__':
main()
先看导入的包,访问百度地图API返回的都是json格式的数据,要对数据进行解析,自然少不了json包,pandas和numpy常规包,基本上很难不用到,后面两个是网页数据获取的包。高德地图地理编码也是这几个包,后面不再重复写。
几点说明:
#1quote函数是为了得到GBK的url编码,国内的网站编码都是GBK编码的;
#2要把url里面的AK改成你自己申请的AK,而不是yourAK这个字符串;
#3为啥要用try来处理,因为不是所有的地理编码都能成功,如果不用try处理,容易报错,程序会中断。
这个函数写的很详细了,你提供一个CSV文件,其中一列命名为‘地点’,就能将你的所有填写的地点都能转换成经纬度,当然,也有不成功的,总体成功率在95%左右。总体来说,定位效果还是可以的,但是如果地点数据很多填写的不规范,地理编码虽然解析出经纬度,但很多都是有问题的,比如解析出来的点不在正确的地点,如下图。
这些点本来应该都出现在无锡市地图上,但还有不少点在无锡市外,也就是说,解析的不正确。问题出在哪里?博主又回过头去看文档,文档里说,请求的参数除了address外,还有city,也就是城市,这个参数不是必须要写的,如果是写了这个,会不会就正确了呢?于是,我找了一个落在无锡地图之外的点,将请求网址写成这样:
http://api.map.baidu.com/geocoding/v3/?address=南湖大道双庆桥公交站前&city=无锡市&output=json&ak=您的ak&callback=showLocation
返回的数据如下:
showLocation&&showLocation({"status":0,"result":{"location":{"lng":120.31700801956372,"lat":31.532789952709309},"precise":0,"confidence":50,"comprehension":24,"level":"桥"}})
根据经纬度去百度地图拾取坐标系统中看看,巧了,点还对了。。。
博主懵逼了,上午的时候,明明是不对的,所以才想到要写一篇博客来解决这个问题,回过头来一想,因为我上午写的请求网址是这样的:
http://api.map.baidu.com/geocoding/v3/?address=南湖大道双庆桥公交站前&city='无锡市'&output=json&ak=您的ak&callback=showLocation
无锡市我打了引号,其实是没必要打引号的。。。
所以,实际上,在请求网址里面加上城市名,就能保证经纬度转换的时候,所有点都落在你想要的城市里面。。。
持续吐血,这篇博客还要写吗,我上午已经按照我的解决思路,高德地图地理编码→高德地图转百度地图→pyecharts地点上图,这个套路,解析了3000+的地点。
都写了3000多字了,还是继续写完吧。。。
高德地图地理编码
开发文档看这里 ,技术细节与百度地图基本一致,请求格式上我换了一种写法,让读者可以更灵活的使用爬虫的技巧。
上代码
def Scene2CoordinateAMap(filename):
data = pd.read_csv(filename)
sgdd = data['事故地点'].tolist()
geo = []
key = 'yourKEY'
base = 'https://restapi.amap.com/v3/geocode/geo'
j = 1
k = 1
for i in range(len(sgdd)):
parameters = {'address': sgdd[i], 'key': key, 'city':'无锡'}
response = requests.get(base, parameters)
answer = response.json()
try:
pos = answer['geocodes'][0]['location']
pos = pos.split(',')
pos[0], pos[1] = pos[1], pos[0]
geo.append(pos)
print('catch %d \n'%j)
j = j + 1
except:
geo.append([ 'null','null'])
print('fail %d \n'%k)
k = k + 1
points = pd.DataFrame(geo)
points.to_csv('经纬度填充-高德地图.csv', index=False, columns = ['lng', 'lat'])
print('成功定位%d个,成功率%.2f%%' % (j - 1, (j - 1) * 100 / (j + k - 2)))
为啥我会写成‘无锡’,能理解了吧,我高德就是这么写的。。。
可以看出来,和百度地图请求方式有一点不同,我用的是requests.get去请求的,其实网上更多的推荐是用这种方式去请求,而不是用urlopen,不过我个人习惯用urlopen,两者都行吧。
高德地图经纬度转百度地图经纬度
其实已经没啥必要写了,因为二者都差不多。
文档看这里,上代码
def AMap2BaiduMap(filename):
data = pd.read_csv(filename)
base = 'http://api.map.baidu.com/geoconv/v1/?coords='
tail = '&from=3&to=5&ak=yourAK'
transpos = []
j = 1
k = 1
for i in range(len(data)):
url = base + str(data.iloc[i]['lng_Amap']) + ',' + str(data.iloc[i]['lat_Amap']) + tail
req = urlopen(url)
res = req.read().decode()
temp = json.loads(res)
try:
pos = list(temp['result'][0].values()) # 字典转列表
transpos.append(pos)
print('catch %d \n'%j)
j = j + 1
except:
transpos.append(['null','null'])
print('fail %d \n'%k)
k = k + 1
tran = pd.DataFrame(transpos)
tran.to_csv('高德转百度.csv', index=False, columns = ['lng', 'lat'])
print('成功转换%d个,成功率%.2f%%' % (j - 1, (j - 1) * 100 / (j + k - 2)))
注意下,链接里有这么句代码
from=3&to=5
官方文档解释如下:
源坐标类型:
1:GPS设备获取的角度坐标,WGS84坐标;
2:GPS获取的米制坐标、sogou地图所用坐标;
3:google地图、soso地图、aliyun地图、mapabc地图和amap地图所用坐标,国测局(GCJ02)坐标;
4:3中列表地图坐标对应的米制坐标;
5:百度地图采用的经纬度坐标;
6:百度地图采用的米制坐标;
7:mapbar地图坐标;
8:51地图坐标
目标坐标类型:
5:bd09ll(百度经纬度坐标);
6:bd09mc(百度米制经纬度坐标)
我是高德经纬度转百度经纬度,自然是from=3to=5。
看看转换经纬度后的pyecharts-Geo图吧
全都在无锡市内了,大功告成,虽然没啥用了(╥╯^╰╥)
完