内容介绍
梗概:求一个城市的公交的平均(非)直线系数,并结合前面博客所求得的公交线路的平均长度、平均站点数、平均站距,对比分析几个城市的公交路线基本数据。
准备知识
1、什么是“非直线系数”?(参考百度百科)
非直线系数是指道路起讫点间的实际交通距离与两点间空间直线距离之比。能评价不同的路网型式和客货流路线集散点之间联系的便捷程度。非直线系数:①棋盘式路网1.2〜1.4。②放射性路网一般在2.6左右。③环形加放射性路网一般为1.1〜1.2。
2、直线系数和非直线系数的区别:
本质上没区别,就是有的地方喜欢用"非直线系数"(大部分),有的地方称呼“直线系数”(少部分)。
3、求解难点——如何确定直线距离?
(1)先讲本博客认同的方法:求公交行车区间直线距离:
行车区间在代码中以"bounds"来表示,所谓行车区间不是始发站到终点站的距离!起码在高德地图的数据里不是,最开始我也搞错了,直到画出坐标对应图片才理解。个人感觉:所谓区间直线就是“对角线”的感觉,如图①把行车路径的最南,最西的点汇集成左下角一点,最北,最东一点汇集成右上角一点,然后将两点连线!
①青岛公交321路(环形线)路线和行车区间示意图
②青岛五条公交线路和对应区间直线
(2)求始发站到终点站的距离
这个是最主流的求公交直线的情况,很多政府性的报告中涉及到公交非直线系数也是以这种计算方法,许多关键指标的对照中的非直线系数仍然是以始发站到终点站的距离直线求出的。
但是,此方法不利于解决环形线路!!!!
倘若不处理环形线路,直接取始发站到终点站的距离(此时两者特别近),那么误差将会特别惊人。
本博客在处理的时候,采用的是判断线路中是否带有“环形”,来判断是否为环线,环形线路直线距离取值为始发站到中点站的距离*2
但效果并不理想!
因为,很多线路虽然不是严格意义上的环形线,但存在上下行!而且就算知道是环形线,但又不知道哪个两个站点之间位置距离最远!
总之,通过优化数据,排查错误,异常值检测,上下行、环形线路标定等办法,最后是能解决,但是,直接用获取到的区间来求直线距离不香吗?
(3)发散思维,多方法思考
①可以求所有相邻站点之间的直线距离,将它们累加起来。
缺点:这样求出的直线和路程长度差不多,参考意义并不是很大!
②求任意两个站点的之间的距离
站点数 | 2 | 3 | 4 | 5 | 6 |
---|---|---|---|---|---|
所求直线数 | 1 | 3 | 6 | 10 | 15 |
即所求直线数为:n(n-1)/2,n为站点数
而且这样方法也不用求解任意两站之间的距离,只需要累加n(n-1)/2直线之后,除以总路程*(n-1)即可!
③还是采用始发站到终点站距离
这次也不专门判断是不是环形线路了,存不存在上下行,始发站和终点站是不是在一起,直接判断两者之间的距离,小于1km就认为始发站和终点站是一起的,然后,取始发站到中间站点距离*2为公交路线直线长度。
大规模数据情况下,不容易直接判断出一条线路哪两个点之间直线距离最长(非要求的话,可以采用“树的直径”问题类似算法解决,或者把始发站到所有站点直线求一遍,取最大那个),但是没必要,直接取始发站到中点距离即可,通常情况下,误差不会太大,在可接受范围内!
PS:
小于1km的通常都是始发站和终点站在一起的,而且通常是小于0.5km的,但也有例外:青岛19路(合肥路深圳路–汽车东站),行车区间有10多公里的情况下,始发站和终点站距离只有1.0695公里,属于始发站和终点站不在一块,但挨得特别近!
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/0b8e72da61df250d900a585d9d993f4f.png)
该进入正文了
1、排除环形线路
如果是英文的话,应该这么写也就好了
if '环形线' in dt['line_name']:
print(dt['line_name'].find('环形'))
但是,中文的话,判断子字符串或许会麻烦一点。
我这里具体也没上网查,写了个下面的版本
#特判是不是环形线
tmp=[]
for i in dt['line_name']:
tmp.append(i)
for i in range(0,len(tmp)):
if tmp[i]=='环' and tmp[i+1]=='行':
print(dt['line_name'])
dt['straight']=dt['straight']*2 #环形线直线距离乘2
lng1,lat1 = rt['buslines'][0]['busstops'][0]['location'].split(',') #始发站坐标
lng2,lat2 = rt['buslines'][0]['busstops'][int(dt['station']/2)]['location'].split(',') #中间站坐标
dt['straight2'] = Geodistance(float(lng1),float(lat1),float(lng2),float(lat2))*2 #始发站到中间站点距离且需要*2
确实能判断出名字中带有环形的线路(两条321路,是因为一个是正常线路,一个是定时区间的车)
2、处理上下行
以上是早期处理直线的思路,后发现有大量的上下行问题没法解决,故采取
直接判断始发站到终点站之间的距离的办法,小于1km就认为始发站和终点站是一起的。
这个判断就比前面那个容易了,非要说说代码,求解始发站到中间站点直线距离思路挺巧妙的~
if dt['straight2']<1.0:
print(dt['line_name'])
dt['straight']=dt['straight']*2 #环形线直线距离乘2
lng1,lat1 = rt['buslines'][0]['busstops'][0]['location'].split(',') #始发站坐标
lng2,lat2 = rt['buslines'][0]['busstops'][int(dt['station']/2)]['location'].split(',') #中间站坐标
dt['straight2'] = Geodistance(float(lng1),float(lat1),float(lng2),float(lat2))*2 #始发站到中间站点距离且需要*2
3、直接可运行的汇总代码
改了挺多版本的,事后看起来挺简单的,但当时解决问题的过程确实走了不少弯路。
import requests
import json
import pandas as pd
import time
from math import sin, asin, cos, radians, sqrt
#python计算两点间直线距离
def Geodistance(lng1,lat1,lng2,lat2):
lng1, lat1, lng2, lat2 = map(radians, [lng1, lat1, lng2, lat2])
dlon=lng2-lng1
dlat=lat2-lat1
a=sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
dis=2*asin(sqrt(a))*6371*1000
return dis/1000 #换算为千米
#获取公交信息:线路名、行车区间(坐标)、路程、行车区间直线距离等
def Bus_inf(city,line):
global bus_num #全局变量,用于计算公交数目
try:
#获取数据
url = 'https://restapi.amap.com/v3/bus/linename?s=rsv3&extensions=all&key=a5b7479db5b24fd68cedcf24f482c156&output=json&city={}&offset=1&keywords={}&platform=JS'.format(city,line)
r = requests.get(url).text
rt = json.loads(r)
#读取当前公交线路基本信息
dt = {}
dt['line_name'] = rt['buslines'][0]['name'] #公交线路名字
dt['bounds'] = rt['buslines'][0]['bounds'] #行车区间(不是始发站,终点站坐标!!!)
dt['distance'] = float(rt['buslines'][0]['distance']) #全程长度
dt['station'] = int(rt['buslines'][0]['busstops'][-1]['sequence']) #全程站点数(包括始发站和终点站)
dt['station_dis'] = dt['distance']/(dt['station']-1) #该路线站距
#计算直线系数 和 非直线系数=1/直线系数
#以下为两套求直线的方法:(1)求行车区间 (2)求始发站到终点站距离
lng1,lat1 =rt['buslines'][0]['bounds'].split(';')[0].split(',')
lng2,lat2 =rt['buslines'][0]['bounds'].split(';')[1].split(',')
dt['straight'] = Geodistance(float(lng1),float(lat1),float(lng2),float(lat2)) #计算区间bounds的直线距离
lng1,lat1 = rt['buslines'][0]['busstops'][0]['location'].split(',') #始发站坐标
lng2,lat2 = rt['buslines'][0]['busstops'][-1]['location'].split(',') #终点站坐标
dt['straight2'] = Geodistance(float(lng1),float(lat1),float(lng2),float(lat2)) #始发站到终点站的直线距离
#首尾距离小于1km即认为存在上下行(环形线路)
if dt['straight2']<1.0:
print(dt['line_name'])
dt['straight']=dt['straight']*2 #环形线直线距离乘2
lng1,lat1 = rt['buslines'][0]['busstops'][0]['location'].split(',') #始发站坐标
lng2,lat2 = rt['buslines'][0]['busstops'][int(dt['station']/2)]['location'].split(',') #中间站坐标
dt['straight2'] = Geodistance(float(lng1),float(lat1),float(lng2),float(lat2))*2 #始发站到中间站点距离且需要*2
dt['straight_dis'] = dt['straight']/dt['distance'] #直线系数1=直线长度/路程长度 (0,1]
dt['non_straight_dis'] = dt['distance']/dt['straight'] #非直线系数1=路程长度/直线长度 [1,∞)
dt['straight_dis2'] = dt['straight2']/dt['distance'] #直线系数2=直线长度/路程长度 (0,1]
dt['non_straight_dis2'] = dt['distance']/dt['straight2'] #非直线系数2=路程长度/直线长度 [1,∞)
bus_num+=1 #有效公交数+1
return pd.DataFrame(dt,index=[bus_num]) #下标index为“第几条公交线”
except:
#print('没有{}公交'.format(line)) #正常情况下,这条语句不会执行
return pd.DataFrame() #读取数据失败,返回空的
def Bus_analysis(bus_info):
print('公交线路的平均长度:{:.6f} km'.format(bus_info["distance"].mean()))
print('公交线路的平均站点数:{:.6f} 个'.format(bus_info['station'].mean()))
print('公交线路的平均站距1为:{:.6f} km/站'.format(bus_info["distance"].sum()/(bus_info['station'].sum()-bus_num)))
print('公交线路的平均直线系数(区间距离):{:.6f}'.format(bus_info['straight'].sum()/bus_info["distance"].sum()))
print('公交线路的平均非直线系数(区间距离):{:.6f}'.format(bus_info["distance"].sum()/bus_info['straight'].sum()))
print('公交线路的平均直线系数(首位距离):{:.6f}'.format(bus_info['straight2'].sum()/bus_info["distance"].sum()))
print('公交线路的平均非直线系数(首位距离):{:.6f}'.format(bus_info["distance"].sum()/bus_info['straight2'].sum()))
if __name__=="__main__":
t0=time.time()
bus_num=0 #设置全局变量数值(通常默认就是0)
city='青岛' #需要查询公交信息的城市
for_num=1000 #遍历的线路数[1路,for_num路],通常公交线路数小于1000,具体可参考8684等网站
all_buslines=pd.DataFrame()
for i in range(1,for_num+1):
all_buslines=pd.concat([all_buslines,Bus_inf(city,str(i)+'路')]) #不加这个'路'可能优先获取地铁
print("Bus_info函数遍历{}前{}路公交,有效公交线路数为:{}个的情况下:".format(city,for_num,bus_num))
Bus_analysis(all_buslines)
all_buslines.to_csv("{}前{}路公交(有效线路数:{})基本信息.csv".format(city,for_num,bus_num),encoding='utf-8-sig')
t1=time.time()
print("用时:%.2fs"%(t1-t0))
查找始发站和终点站在一起的能力还挺不错的!
4、读取文本路线名的主函数
记得Bus_analysis(all_buslines,len(bus_name))要加一个bus_num变量,其他就和上面一样了
if __name__=="__main__":
record_time(0)
all_buslines=pd.DataFrame()
city='青岛市'
#获取已知线路的公交文本
with open("公交线路.txt", "r", encoding="utf-8") as f:
bus_name = f.readlines()
bus_name = bus_name[0].split(",")
for i in bus_name:
all_buslines=pd.concat([all_buslines,Bus_inf(city,i)])
Bus_analysis(all_buslines,len(bus_name))
print("通过文本获取青岛市区{}条路线基本信息成功!".format(len(bus_name)))
all_buslines.to_csv("通过文本获取的青岛市区{}条路线基本信息.csv".format(len(bus_name)),index=False,encoding='utf-8-sig')
record_time(1)
对比分析
在前面的实验内容中,我们通过处理各种问题,最终掌握了计算出一个城市公交线路平均长度、平均站点数、平均站距、平均直线系数的方法。下面通过循环一个城市前1000路公交,对比分析一下它们的平均参数,其中关于直线系数,选取了“区间直线”和“始发站到终点站”直线(首尾直线)两种情况
关于直接循环1000路这个方法的补充说明:这个方法确实存在会遗漏一些线路的情况,但我们可以理解为这是“随机取样”的结果,一个城市前1000路公交中存在的线路,已经基本能反映其公交特征,而且遗漏的线路往往都是一些不符合标准城市公交特征的专线。另外,一个城市前1000路公交的有效公交数,也能侧面反映其城市规模。
青岛市区线路和全市线路对比
下图分别为青岛全市获取的423条线路,及市区256条线路的实验结果。可见,市区线路点与点之间联系比较频繁,其平均站点数会较多一点,平均站点会更短一点。但由于所处地理环境等因素的影响,其非直线系数相差并不多。
一线城市公交线路基本特征分析
北京市前1000路有效公交数为768个,深圳市前1000路有效公交数为821个,足以侧面看出一线城市的交通体量之大。城市规模大,线路平均长度也随之变大,但考虑到深圳是城市化率100%的一线新兴城市,其公交平均站距会较北京少了不少。
其他城市对比
厦门市是五大经济特区之一,面积大约是青岛的八分之一,地方不大,平均公交线路自然不长,线路平均站点数也比较少。
三明市是一个地处福建丘陵,森林绿化率全国第一的一个小城市,其公交基本特征见下图,可知,其公交线路长度一般都不太长,平均站距也会比上面大城市短一些。
三明路线分布图,以河流两侧的长直线为主,所以直线系数还是比较高的
说明
因为求解直线系数中的“直线‘没法准确获得,且不能保证获取公交数据的完整性,上面所得的城市公交”非直线系数“等结果是存在误差的,请大家酌情参考。
这个系列博客原计划是还有公交换乘网络构建和分析的
但突然不想写了,复习考研去了ㄟ( ▔, ▔ )ㄏ
至此《城市公交网络分析与可视化》系列博客告一段落!