python地铁路线可视化
目录
- 事前准备
- 绘制地铁站点路线图
- 利用百度api获取地铁站点坐标[^1]
- 经纬度坐标系的转换[^2]
- 绘制地铁路线图[^3]
- 设置地图默认语言为中文[^4]
- 绘制精细地铁线路图
- 利用百度api获取精细地铁线路百度墨卡托(BD-09MC)坐标[^1]
- 提取地铁路线经纬度坐标序列
- 绘制地铁路线图
事前准备
- 需要安装的第三方库:plotly、requests、numpy
- 由于plotly使用的地图api是mapbox,我们需要先注册一个mapbox的账号,之后可在Account界面下获取一个token备用,也可直接使用我代码中所用的token而无需注册
绘制地铁站点路线图
利用百度api获取地铁站点坐标1
百度地图获取城市地铁站点信息的api为http://map.baidu.com/?qt=bsi&c={城市编号}&t={13位时间戳},这里附上百度地图城市编号文件:BaiduMap_cityCode
有了api之后,我们以广州为例,获取广州所有地铁站点信息,代码如下
import requests
import timenull = None #将json中的null定义为None
city_code = 257 #广州的城市编号
station_info = requests.get('http://map.baidu.com/?qt=bsi&c=%s&t=%s' % (city_code, int(time.time() * 1000))
)
station_info_json = eval(station_info.content) #将json字符串转为python对象
这样处理以后我们会得到json格式的字典,其中键名为content的内容是我们感兴趣的线路信息字典组成的列表,其中每一个字典为一条线路的信息,现给出线路字典的键值信息如下
键名 | 描述 | 数据类型 |
---|---|---|
line_name | 线路名 | str |
line_uid | 线路uid | str |
pair_line_uid | 反向线路uid | str |
stops | 站点信息 | dict |
而每一个站点信息字典的键值信息如下 | ||
键名 | 描述 | 数据类型 |
:-: | :-: | :-: |
is_practical | 未知 | int |
name | 站点名字 | str |
uid | 站点uid | str |
x | 站点百度墨卡托x坐标 | float |
y | 站点百度墨卡托y坐标 | float |
这样我们可提取出我们所需的各条地铁线路的站点名及百度墨卡托(BD-09MC)坐标,代码如下 |
for line in station_info_json['content']: plots = []plots_name = []for plot in line['stops']:plots.append([plot['x'], plot['y']])plots_name.append(plot['name'])plot_mercator = np.array(plots)#......
经纬度坐标系的转换2
由于mapbox采用WGS-84坐标系,我们还得将提取出的BD-09MC坐标转换为WGS-84坐标系下的经纬度,要做到这一点,我们先将BD-09MC坐标转换到BD-09坐标系(百度的另一个坐标系)下,再转到GCJ-02坐标系(国内常用的坐标系)下,之后才能转换到WGS-84坐标系下,关于这几个坐标系就不在此赘述了,下面提供几个转换函数
import numpy as np
import mathPI = math.pidef _transformlat(coordinates):lng = coordinates[ : , 0] - 105lat = coordinates[ : , 1] - 35ret = -100 + 2 * lng + 3 * lat + 0.2 * lat * lat + \0.1 * lng * lat + 0.2 * np.sqrt(np.fabs(lng))ret += (20 * np.sin(6 * lng * PI) + 20 *np.sin(2 * lng * PI)) * 2 / 3ret += (20 * np.sin(lat * PI) + 40 *np.sin(lat / 3 * PI)) * 2 / 3ret += (160 * np.sin(lat / 12 * PI) + 320 *np.sin(lat * PI / 30.0)) * 2 / 3return retdef _transformlng(coordinates):lng = coordinates[ : , 0] - 105lat = coordinates[ : , 1] - 35ret = 300 + lng + 2 * lat + 0.1 * lng * lng + \0.1 * lng * lat + 0.1 * np.sqrt(np.fabs(lng))ret += (20 * np.sin(6 * lng * PI) + 20 *np.sin(2 * lng * PI)) * 2 / 3ret += (20 * np.sin(lng * PI) + 40 *np.sin(lng / 3 * PI)) * 2 / 3ret += (150 * np.sin(lng / 12 * PI) + 300 *np.sin(lng / 30 * PI)) * 2 / 3return retdef gcj02_to_wgs84(coordinates):"""GCJ-02转WGS-84:param coordinates: GCJ-02坐标系的经度和纬度的numpy数组:returns: WGS-84坐标系的经度和纬度的numpy数组"""ee = 0.006693421622965943 # 偏心率平方a = 6378245 # 长半轴lng = coordinates[ : , 0]lat = coordinates[ : , 1]is_in_china= (lng > 73.66) & (lng < 135.05) & (lat > 3.86) & (lat < 53.55)_transform = coordinates[is_in_china] #只对国内的坐标做偏移dlat = _transformlat(_transform)dlng = _transformlng(_transform)radlat = _transform[ : , 1] / 180 * PImagic = np.sin(radlat)magic = 1 - ee * magic * magicsqrtmagic = np.sqrt(magic)dlat = (dlat * 180.0) / ((a * (1 - ee)) / (magic * sqrtmagic) * PI)dlng = (dlng * 180.0) / (a / sqrtmagic * np.cos(radlat) * PI)mglat = _transform[ : , 1] + dlatmglng = _transform[ : , 0] + dlngcoordinates[is_in_china] = np.array([_transform[ : , 0] * 2 - mglng, _transform[ : , 1] * 2 - mglat]).Treturn coordinatesdef bd09_to_gcj02(coordinates):"""BD-09转GCJ-02:param coordinates: BD-09坐标系的经度和纬度的numpy数组:returns: GCJ-02坐标系的经度和纬度的numpy数组"""x_pi = PI * 3000 / 180x = coordinates[ : , 0] - 0.0065y = coordinates[ : , 1] - 0.006z = np.sqrt(x * x + y * y) - 0.00002 * np.sin(y * x_pi)theta = np.arctan2(y, x) - 0.000003 * np.cos(x * x_pi)lng = z * np.cos(theta)lat = z * np.sin(theta)coordinates = np.array([lng, lat]).Treturn coordinatesdef bd09_to_wgs84(coordinates):"""BD-09转WGS-84:param coordinates: BD-09坐标系的经度和纬度的numpy数组:returns: WGS-84坐标系的经度和纬度的numpy数组"""return gcj02_to_wgs84(bd09_to_gcj02(coordinates))def mercator_to_bd09(mercator):"""BD-09MC转BD-09:param coordinates: GCJ-02坐标系的经度和纬度的numpy数组:returns: WGS-84坐标系的经度和纬度的numpy数组"""MCBAND = [12890594.86, 8362377.87, 5591021, 3481989.83, 1678043.12, 0]MC2LL = [[1.410526172116255e-08, 8.98305509648872e-06, -1.9939833816331, 200.9824383106796, -187.2403703815547, 91.6087516669843,-23.38765649603339, 2.57121317296198, -0.03801003308653,17337981.2],[-7.435856389565537e-09, 8.983055097726239e-06, -0.78625201886289,96.32687599759846, -1.85204757529826, -59.36935905485877,47.40033549296737, -16.50741931063887, 2.28786674699375,10260144.86],[-3.030883460898826e-08, 8.98305509983578e-06, 0.30071316287616,59.74293618442277, 7.357984074871, -25.38371002664745,13.45380521110908, -3.29883767235584, 0.32710905363475,6856817.37],[-1.981981304930552e-08, 8.983055099779535e-06, 0.03278182852591,40.31678527705744, 0.65659298677277, -4.44255534477492,0.85341911805263, 0.12923347998204, -0.04625736007561,4482777.06], [3.09191371068437e-09, 8.983055096812155e-06, 6.995724062e-05,23.10934304144901, -0.00023663490511, -0.6321817810242,-0.00663494467273, 0.03430082397953, -0.00466043876332,2555164.4], [2.890871144776878e-09, 8.983055095805407e-06, -3.068298e-08,7.47137025468032, -3.53937994e-06, -0.02145144861037,-1.234426596e-05, 0.00010322952773, -3.23890364e-06,826088.5]] x = np.abs(mercator[ : , 0])y = np.abs(mercator[ : , 1])coef = np.array([MC2LL[index] for index in (np.tile(y.reshape((-1, 1)), (1, 6)) < MCBAND).sum(axis=1)]) return converter(x, y, coef)def converter(x, y, coef):x_temp = coef[ : ,0] + coef[ : ,1] * np.abs(x)x_n = np.abs(y) / coef[ : ,9]y_temp = coef[ : ,2] + coef[ : ,3] * x_n + coef[ : ,4] * x_n ** 2 + \coef[ : ,5] * x_n ** 3 + coef[ : ,6] * x_n ** 4 + coef[ : ,7] * x_n ** 5 + \coef[ : ,8] * x_n ** 6x[x < 0] = -1x[x >= 0] = 1y[y < 0] = -1y[y >= 0] = 1 x_temp *= xy_temp *= ycoordinates = np.array([x_temp, y_temp]).Treturn coordinates
绘制地铁路线图3
接下来就可利用plotly绘制出地铁站点路线图了,代码如下
import plotly.offline as py
import plotly.graph_objs as go
from plotly.offline import init_notebook_modeinit_notebook_mode(connected=True) #如果使用jupyter需要加上这一句,把地图显示在页面上mapbox_access_token = ('pk.eyJ1IjoibHVrYXNtYXJ0aW5lbGxpIiwiYSI6ImNpem85dmhwazAy''ajIyd284dGxhN2VxYnYifQ.HQCmyhEXZUTz3S98FMrVAQ'
) # 此处的写法只是为了排版,结果为连接在一起的字符串
layout = go.Layout(autosize=True,mapbox=dict(accesstoken=mapbox_access_token,center=dict(lat=23.12864583, #广州市纬度lon= 113.2648325 #广州市经度),pitch=0,zoom=10,),
)color = ('blue', 'green', 'yellow', 'purple', 'orange', 'red', 'violet', 'navy', 'crimson', 'cyan', 'magenta', 'maroon', 'peru') #可按需增加
data = [] #绘制数据
marked = set()
cnt = 0
for line in station_info_json['content']:uid = line['line_uid']if uid in marked: #由于线路包括了来回两个方向,需要排除已绘制线路的反向线路continueplots = [] #站台BD-09MC坐标plots_name = [] #站台名称for plot in line['stops']:plots.append([plot['x'], plot['y']])plots_name.append(plot['name'])plot_mercator = np.array(plots)plot_coordinates = bd09_to_wgs84(mercator_to_bd09(plot_mercator)) #站台经纬度data.append(go.Scattermapbox(lon=plot_coordinates[:, 0], #站台经度lat=plot_coordinates[:, 1], #站台纬度mode='markers+lines',name=line['line_name'], #线路名称,显示在图例(legend)上text=plots_name, #各个点的名称,鼠标悬浮在点上时显示# 设置标记点的参数marker=go.scattermapbox.Marker(size=10,color=color[cnt]),))marked.add(uid) #添加已绘制线路的uidmarked.add(line['pair_line_uid']) #添加已绘制线路反向线路的uidcnt = (cnt + 1) % len(color)fig = dict(data=data, layout=layout)
#py.iplot(fig) #直接显示地图
py.plot(fig, filename='Guangzhou_railway.html') #生成html文件并打开
如想获取更多颜色可参考https://www.cnblogs.com/darkknightzh/p/6117528.html
完整的代码文件
设置地图默认语言为中文4
将地铁路线图绘制出来后,我们会发现里面的地点名是中英混合的,原因在于plotly使用的mapbox的默认语言是英文,而有些地点名没有英文名称就显示为中文了。为了解决这个问题,我去翻过了plotly的api,遗憾的是没有找到相关的配置,最后只能修改生成的html文件将默认语言设置为中文了(其实我对前端一窍不通),步骤如下
- 在工作目录下找到生成的html文件,把里面的代码用js相关工具格式化一下,我这里使用了在线格式化工具,将格式化的代码保存下来
- 在head标签内添加如下代码
<script src='https://api.mapbox.com/mapbox-gl-js/plugins/mapbox-gl-language/v0.10.0/mapbox-gl-language.js'></script>
如图所示
3. 在代码中搜索”Map({“(如果使用正则表达式要注意加上转义),找到定义Map的地方,在后面添加如下代码
p.addControl(new MapboxLanguage({defaultLanguage: 'zh'
})); //p是定义为Map类型的变量
如图所示
这样修改之后打开该html文件就能以中文地名显示了,可视化效果如下
这时候可能有挑剔的小伙伴会说:“这地铁路线图看上去好假啊喂(嫌弃)。“那么接下来我们再来绘制更加精细的地铁路线图。
绘制精细地铁线路图
利用百度api获取精细地铁线路百度墨卡托(BD-09MC)坐标1
百度地图获取城市精细地铁线路坐标的api为https://map.baidu.com/?qt=bsl&tps=&newmap=1&uid={地铁线路uid}&c={城市编号},获取广州精细地铁线路坐标的代码如下
import requests
import timenull = None #将json中的null定义为None
city_code = 257 #广州的城市编号
station_info = requests.get('http://map.baidu.com/?qt=bsi&c=%s&t=%s' % (city_code, int(time.time() * 1000))
)
station_info_json = eval(station_info.content) #将json字符串转为python对象
for railway in station_info_json['content']:uid = railway['line_uid']railway_json = requests.get('https://map.baidu.com/?qt=bsl&tps=&newmap=1&uid=%s&c=%s' % (uid, city_id))railway_json = eval(railway_json.content) #将json字符串转为python对象#......
这样做之后我们同样得到了一个字典,键名为content的内容是长度为1的保存了线路信息字典的列表,现给出线路信息字典的部分键值信息如下
键名 | 描述 | 数据类型 |
---|---|---|
geo |
整条线路坐标信息,以 ”|“ 为分隔符分为三部分,第三部分为该线路 有序的BD-09MC坐标,格式为"x1,y1,x2,y2,...,xn,yn;x_1,y_1,x_2,y_2,...,x_n,y_n;x1,y1,x2,y2,...,xn,yn;" |
str |
stations | 具体站点信息 | list |
uid | 地铁路线uid | str |
lineColor | 线路16进制RGB | str |
提取地铁路线经纬度坐标序列
有了上面获取到的字典,我们可以提取出里面的BD-09MC坐标并转换成WGS-84坐标,代码如下
trace_mercator = np.array(# 取出线路信息字典,以“|”划分后,取出第三部分信息,去掉末尾的“;”,获取BD-09MC坐标序列railway_json['content'][0]['geo'].split('|')[2][ : -1].split(','), dtype=float).reshape((-1, 2)) trace_coordinates = bd09_to_wgs84(mercator_to_bd09(trace_mercator))
绘制地铁路线图
与前面的绘制路线图的代码整合后,最新的绘制代码如下
import randommapbox_access_token = ('pk.eyJ1IjoibHVrYXNtYXJ0aW5lbGxpIiwiYSI6ImNpem85dmhwazAy''ajIyd284dGxhN2VxYnYifQ.HQCmyhEXZUTz3S98FMrVAQ'
) # 此处的写法只是为了排版,结果为连接在一起的字符串
layout = go.Layout(autosize=True,mapbox=dict(accesstoken=mapbox_access_token,bearing=0,center=dict(lat=23.12864583, #广州市纬度lon= 113.2648325 #广州市经度),pitch=0,zoom=10),
)
colors = ('blue', 'green', 'yellow', 'purple', 'orange', 'red', 'violet', 'navy', 'crimson', 'cyan', 'magenta', 'maroon', 'peru') #可按需增加
null = None #将json中的null定义为None
city_code = 257 #广州的城市编号
data = [] #绘制数据
marked = set()
for railway in station_info_json['content']:uid = railway['line_uid']if uid in marked: #由于线路包括了来回两个方向,需要排除已绘制线路的反向线路continuerailway_json = requests.get('https://map.baidu.com/?qt=bsl&tps=&newmap=1&uid=%s&c=%s' % (uid, city_code))railway_json = eval(railway_json.content) #将json字符串转为python对象trace_mercator = np.array(# 取出线路信息字典,以“|”划分后,取出第三部分信息,去掉末尾的“;”,获取BD-09MC坐标序列railway_json['content'][0]['geo'].split('|')[2][ : -1].split(','), dtype=float).reshape((-1, 2)) trace_coordinates = bd09_to_wgs84(mercator_to_bd09(trace_mercator))plots = [] #站台BD-09MC坐标plots_name = [] #站台名称for plot in railway['stops']:plots.append([plot['x'], plot['y']])plots_name.append(plot['name'])plot_mercator = np.array(plots)plot_coordinates = bd09_to_wgs84(mercator_to_bd09(plot_mercator)) #站台经纬度color = railway_json['content'][0]['lineColor'] #利用json所给线路的颜色if color == '':color = random.choice(colors)data.extend([# 地铁路线go.Scattermapbox(lon=trace_coordinates[:, 0], #路线点经度lat=trace_coordinates[:, 1], #路线点纬度mode='lines',# 设置路线的参数line=go.scattermapbox.Line(width=2,color=color), name=railway['line_name'], #线路名称,显示在图例(legend)上legendgroup=railway['line_name']),# 地铁站台go.Scattermapbox(lon=plot_coordinates[:, 0], #站台经度lat=plot_coordinates[:, 1], #站台纬度mode='markers', text=plots_name,# 设置标记点的参数marker=go.scattermapbox.Marker(size=10,color=color),name=railway['line_name'], #线路名称,显示在图例(legend)及鼠标悬浮在标记点时的路线名上legendgroup=railway['line_name'], #设置与路线同组,当隐藏该路线时隐藏标记点showlegend=False #不显示图例(legend))])marked.add(uid) #添加已绘制线路的uidmarked.add(railway['pair_line_uid']) #添加已绘制线路反向线路的uidfig = dict(data=data, layout=layout)
#py.iplot(fig) #直接显示地图
py.plot(fig, filename='Guangzhou_railway.html') #生成html文件并打开
最后,再通过上述同样的步骤完成地图默认语言的设置,即可得到最终效果,如图
放大后我们能够看出地铁路线与地图道路的匹配程度较好,如图
完整的代码文件
2019.05.22更新:坐标系转换代码第57行bool_out_of_china更正为is_in_china
2019.07.07更新:更改错误描述“墨卡托”为“百度墨卡托(BD-09MC)”,更改参考2中非百度摩卡托坐标转换参考链接。
2019.07.30更新:更改原代码中mapbox_access_token的赋值操作,避免因缩进导致字符串中出现空格,进而导致认证失败,地图加载不出来
2019.09.25更新:新增城市公交百度api如下
百度地图获取城市公交信息的api为https://map.baidu.com/?qt=spot&c={城市编号}&wd=XX市公交&rn=50&t={13位时间戳},如https://map.baidu.com/?qt=spot&c=257&wd=广州市公交&rn=50&t=1563956728633
百度地图获取城市公交详细线路信息的api为https://map.baidu.com/?qt=bsl&uid={公交线路uid}&c={城市编号}&t={13位时间戳},如https://map.baidu.com/?qt=bsl&uid=89426be2781c4681f70e7757&c=257&t=1564015808798
2019.12.08更新:改正精细线路绘制代码中由于查询接口返回的颜色值可能为空导致的错误,具体修改如下
import random
station_info_json = eval(station_info.content) #将json字符串转为python对象
colors = ('blue', 'green', 'yellow', 'purple', 'orange', 'red', 'violet',
'navy', 'crimson', 'cyan', 'magenta', 'maroon', 'peru') #可按需增加
data = [] #绘制数据
marked = set()color = railway_json['content'][0]['lineColor'] #利用json所给线路的颜色
if color == '':
color = random.choice(colors)
地图地铁信息api参考http://www.yanweijia.cn/2016/07/24/subway_info_api/ ↩︎ ↩︎
非百度墨卡托坐标转换参考https://blog.csdn.net/pashine/article/details/81283390
百度墨卡托坐标转换参考https://www.jianshu.com/p/063bee79507a ↩︎plotly官方文档参考https://plot.ly/python/maps/
plotly python版api参考https://plot.ly/python/reference/ ↩︎html文件设置mapbox的默认语言参考https://blog.csdn.net/hyzhang6/article/details/79760166 ↩︎
python地铁路线可视化相关推荐
- Python爬虫实战,pyecharts模块,Python实现中国地铁数据可视化
前言 利用Python实现中国地铁数据可视化.废话不多说. 让我们愉快地开始吧~ 开发工具 Python 版本:3.6.4 相关模块: requests模块; wordcloud模块; 熊猫模块; n ...
- python自学路线-零基础Python学习路线,小白的进阶之路!
近几年Python的受欢迎程度可谓是扶摇直上,当然了学习的人也是愈来愈多.一些学习Python的小白在学习初期,总希望能够得到一份Python学习路线图,小编经过多方汇总为大家汇总了一份Python学 ...
- 【Python】Matplotlib 可视化必备神书,附pdf下载
出品:Python数据之道 大家知道,在利用Python进行数据可视化过程中,基本上是很难绕开 Matplotlib 的,因为 不少其他的可视化库多多少少是建立在 Matplotlib 的基础上的. ...
- 【Python】数据可视化教程来了!
来自:Datawhale????数据可视化小组 开源初衷 Matplotlib可以说是python数据可视化最重要且常见的工具之一,几乎每个和数据打交道的人都不可避免,还有大量可视化工具是基于它的二次 ...
- Python学习入门2:Python学习路线(课程大纲+Python视频教程+下载地址)
Python学习路线(课程大纲+Python视频教程+下载地址) 目前Python已经成为最受欢迎的程序设计语言之一.Python的设计哲学是"优雅"."明确" ...
- 自学python到什么程度就可以工作-零基础学到什么程度可以找一份python工作?Python学习路线规划...
目前信息化产业发展势头很好,互联网就成为了很多普通人想要涉及的行业,因为相比于传统行业,互联网行业涨薪幅度大,机会也多,所以就会大批的人想要转行来学习Python开发.目前来讲市场上需要的Python ...
- 从零基础到精通的Python学习路线(附教程)
首先,我们先普及一下编程语言的基础知识.其实无论用任何编程语言来开发程序,都是为了让计算机干活,比如编写一篇文章,下载一首MP3等,而计算机干活的CPU只认识机器的指令,所以,尽管不同的编程语言差异极 ...
- 视频教程-爬虫微课5小时 Python学习路线-Python
爬虫微课5小时 Python学习路线 十多年的Java和大数据开发和培训,精通Hadoop.Spark和Android开发,对于数据分析和挖掘有比较深的研究.曾在公司独立开发O2O项目e小时.网店项目 ...
- 爬虫微课5小时 python学习路线-余强-专题视频课程
爬虫微课5小时 python学习路线-1992557人已学习 课程介绍 此课程每堂课都有一个作业,包含的项目案例有 家长帮142600条数据爬取与分析,全球天气数据抓取与分析,淘宝商品 ...
最新文章
- rest-framework之解析器
- 【CVPR 2021】首次实现将GAN压缩22倍,性能比原始模型还高!
- 【Java源码分析】ArrayList源码分析
- 【转】每天读一遍,思考一下:我是否浮躁?
- word公式编辑器快捷键_科研利器|编辑公式,就用这款免费软件,一键搞定!
- C语言查看队头元素,C语言实现循环队列的初始化进队出队读取队头元素判空-1...
- [模板]欧几里得算法/扩展欧几里得
- OPENSSL的基础使用
- 解读:云原生下的可观察性发展方向
- .net core精彩实例分享 -- 依赖注入和中间件
- Elasticsearch--高级-映射_修改映射 数据迁移---全文检索引擎ElasticSearch工作笔记019
- cordova android ios
- 重定向telnet方法
- epoint:TreeView
- 计算机毕业生怎么参加清华暑校,上完清华暑校后,怎么才能进清华?
- Qt 股票资金流向评估
- GAM做interaction时常见的报错及解决办法
- 游戏版署版本心得(一)
- Acala 全球征文精选
- Android之SeekBar(0在中间)
热门文章
- 四只头雁:5G to B规模商用的开启节点
- 人生就是不停的战斗————九把刀北大演讲 转载自豆瓣网友“此间的少年”
- linux 终端分屏命令vsp
- 母婴类网站的前景与国内市场环境
- 约瑟夫问题与魔术(九)——魔术《五重巧合之皇家同花顺》
- 【Stable Diffusion/NovelAI Diffusion的AMD GPU加速推理探索】
- .什么是ECharts
- linux用户登录报密码不对,Linux用户登录密码的生成
- matlab函数im2bw_im2bw(matlab中im2bw用法)
- 【倡议】表达哀思,博客更换为素装