这篇文章的思路很离奇,但它完全是可行的!出现这种离奇的原因是,在我手里除了上海地铁站点名称数据(这也算数据?)以外没有任何可用数据的情况下,某项目要求我空手套白狼,获取上海地铁各线路的站间距和各站点间旅行速度数据。
当然,这种程度还是难不倒博主的。整体获取思路如下:
1 站点关键词查找
从第一步开始就需要利用高德API的服务了。这里使用的是关键词查找服务。
def poi_get(keywords):
url = 'https://restapi.amap.com/v3/place/text?parameters'
parameters = {
'key': '你自己的key',
'keywords': keywords,
'types': '150500',
'city': '上海',
'citylimit': 'true',
}
response = requests.get(url, parameters)
txt = response.text
pattern_loc = re.compile(r'"type":"交通设施服务;地铁站;地铁站".*?"location":"(.*?)","tel"')
try:
loc_result = pattern_loc.findall(txt)[0]
return loc_result
except:
return 'error'
上面poi_get(keywords)
传入的参数是站点的中文名,这个方法导出的是站点的经纬度坐标(或者查询失败导出'error')。
然而这个功能普通账号一天只能请求100次,所以对于总共517个站点(上海现在有多少个站点了?),这点额度明显是不够看的,因此关键词查找2.0服务也得用上。这个的参数和上面会略有不同:
def poi_get2(keywords):
url = 'https://restapi.amap.com/v5/place/text?parameters'
parameters = {
'key': '你自己的key',
'keywords': keywords,
'types': '150500',
'region': '上海市',
'city_limit': 'true'
}
response = requests.get(url, parameters)
txt = response.text
pattern_loc = re.compile(r'"type":"交通设施服务;地铁站;地铁站".*?"location":"(.*?)","id"')
try:
loc_result = pattern_loc.findall(txt)[0]
return loc_result
except:
return 'error'
拿了别人的账号一起来用,花了两天的额度终于导出了上海所有站点的经纬度坐标。有些导出失败的可以自己去高德地图终端查找。利用终端导出所选点经纬度的方法,可以在网上查到,比较简单。
整理好的导出数据长这样(取前6行):
2 公交路径规划
这一步就是关键步骤了,利用上述操作得到的站点经纬度坐标,以及高德API的公交路径规划服务,导出同一线路上两两站点间的路程及时耗。
# 当前使用的公交路径规划服务
def rp(org, dst):
url = ' https://restapi.amap.com/v3/direction/transit/integrated?parameters'
parameters = {
'key': '你自己的key',
'origin': org, # 起点经纬度
'destination': dst, # 终点经纬度
'city': '上海',
'strategy': 0, # 策略:智能模式
}
response = requests.get(url, parameters)
txt = response.text
return txt
def json_load(txt):
pattern_msg = re.compile(
r'"buslines".*?"departure_stop":{"name":"(.*?)","id".*?"arrival_stop":{(.*?)lyline"')
pattern_msg_sub = re.compile(r'"name":"(.*?)","id".*?"name":"(.*?)","id".*?"type":"地铁线路","distance":"(.*?)","duration":"(.*?)","po')
try:
result = pattern_msg.findall(txt)
result_sub = pattern_msg_sub.findall(result[0][1])
return [result[0][0]] + [result_sub[0][i] for i in range(4)]
except:
return ['error' for i in range(5)]
def api_get(loc_mat):
msg_mat_single = []
for i in range(len(loc_mat) - 1):
print(i)
if loc_mat[i + 1][1] == loc_mat[i][1]:
loc_mat[i][2] = loc_mat[i][2].strip('"')
json_text = rp(str(loc_mat[i][2].strip('"')), str(loc_mat[i + 1][2]).strip('"'))
# 末尾加上起点坐标,方便定位
loc_ele = loc_mat[i][2].split(',')
try:
single_ele = json_load(json_text) + [loc_ele[0], loc_ele[1]]
except:
single_ele = json_load(json_text) + ['error', 'error']
print(single_ele)
msg_mat_single.append(single_ele)
re_write(msg_mat_single, 'mat_single.txt')
备注:由于博主那段时间得了看见DataFrame的格式就头疼的毛病,所以文件读取和写入方法都自己重写了一遍,上面的代码可能会用到:
def load(text, mode=0):
if mode == 0:
file = open(text, 'r')
else:
file = open(text, 'r', encoding='gbk')
mat = []
for aline in file:
item = aline.split('\t')
item[len(item) - 1] = item[len(item) - 1][:-1]
mat.append([item[i] for i in range(len(item))])
file.close()
return mat
def re_write(mat, text):
file = open(text, 'w')
item_len = len(mat[0])
for item in mat:
for i in range(item_len - 1):
file.write(str(item[i]) + '\t')
file.write(str(item[-1]) + '\n')
file.close()
总之,最后导出的数据应该长这样(取前6行):
列元素从左到右分别为:起点、讫点、所属线路、两站间距离(米)、两站间时耗(秒)、起点经度、起点纬度。
当然,除了每条线路上邻近站点的此类数据外,还需要线路起点站和终点站之间的总体数据,因为后面会用到。对于环线4号线,建议将整条线路拆成两半,最后汇总为一个结果。
3 数据处理与分析
下述mat_single.txt
和mat_sum.txt
其实就对应上述获取的两份数据,一个是相邻站点间的数据,一个是总体数据。计算旅行速度的方式如代码中所示,其实就是解一个二元方程,只不过列方程思路很难想到。
def pre_analysis():
single_mat = load('mat_single.txt')
sum_mat = load('mat_sum.txt')
result_single_mat = []
result_sum_mat = []
for item in sum_mat:
distance_list = []
time_cost_list = []
for ele in single_mat:
# 如果是同一线路的元素
if ele[2] == item[2]:
distance_list.append(int(ele[3]))
time_cost_list.append(int(ele[4]))
sum_with_several_wait = sum(time_cost_list) # 总时间附带n次间隔时间
wait_interval = len(time_cost_list) # 间隔数目n
sum_with_one_wait = int(item[4]) # 总时间附带一次间隔时间
sum_distance = int(item[3]) # 总距离
# 由此可以计算每次的间隔时间(即列车在停靠站点时静止不动的时间均值)
wait_time = (sum_with_several_wait - sum_with_one_wait) / (wait_interval - 1)
# 由此可以得出旅行速度均值
travel_speed_avg = sum_distance / (sum_with_one_wait - wait_time) * 3.6
# 以及邻近站点间的旅行速度值
travel_speed = [(distance_list[i] / (time_cost_list[i] - wait_time) * 3.6) for i in range(len(distance_list))]
result_sum_mat.append([travel_speed_avg])
for item in travel_speed:
result_single_mat.append([item])
re_write(result_sum_mat, 'result_sum.txt')
re_write(result_single_mat, 'result_single.txt')
当然,毕竟是项目,导出结果还是需要作图的。
附上Echarts代码(这个花的时间也不少):
<html lang="zh-CN" style="height: 100%">
<head>
<meta charset="utf-8">
</head>
<body style="height: 100%; margin: 0">
<div id="myChart" style="height: 500px;"></div>
<script type="text/javascript" src="https://fastly.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script type="text/javascript">
var myChart = echarts.init(document.getElementById('myChart'), null, {height: 500});
var app = {};
option = {
textStyle: {
color: '#212424',
fontFamily: 'Microsoft YaHei, sans-serif',
fontWeight: ''
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'cross',
crossStyle: {
color: '#999'
}
}
},
toolbox: {
feature: {
dataView: { show: true, readOnly: false },
magicType: { show: true, type: ['line', 'bar'] },
restore: { show: true },
saveAsImage: { show: true }
}
},
legend: {
data: ['旅行速度', '站间距'],
textStyle: {
fontSize: 16
}
},
xAxis: [
{
type: 'category',
data: [
'1\n号线',
'2\n号线',
'3\n号线',
'4\n号线',
'5\n号线',
'6\n号线',
'7\n号线',
'8\n号线',
'9\n号线',
'10\n号线',
'11\n号线\n主线',
'11\n号线\n支线',
'12\n号线',
'13\n号线',
'14\n号线',
'15\n号线',
'16\n号线',
'17\n号线',
'18\n号线'
],
axisLabel: {
textStyle: {
fontSize: 13
}
},
axisPointer: {
type: 'shadow'
}
}
],
yAxis: [
{
type: 'value',
name: '旅行速度',
nameTextStyle: {
fontSize: 14
},
min: 0,
max: 60,
interval: 12,
axisLabel: {
formatter: '{value} km/h',
textStyle: {
fontSize: 14
}
}
},
{
type: 'value',
name: '站间距',
nameTextStyle: {
fontSize: 14
},
min: 0,
max: 5,
interval: 1,
axisLabel: {
formatter: '{value} km',
textStyle: {
fontSize: 14
}
}
}
],
series: [
{
name: '旅行速度',
type: 'bar',
label: {
show: true,
position: 'top',
fontSize: 14
},
itemStyle: {
//柱状颜色和圆角
color: '#97B1D0',
barBorderRadius: [5, 5, 0, 0] // (顺时针左上,右上,右下,左下)
},
tooltip: {
valueFormatter: function (value) {
return value + ' km/h';
}
},
markLine: {
silent: true,
lineStyle: {
normal: {
color: '#296DC1' // 这儿设置安全基线颜色
}
},
data: [
{
yAxis: 34.5,
lineStyle: {
type: 'dashed',
width: 2
},
label: {
formatter: '均值34.5km/h',
fontSize: 14,
position: 'start',
color: '#296DC1'
}
}
]
},
data: [
31.8, 37.7, 30.2, 34.3, 34.5, 30.2, 29.0, 29.6, 37.3, 29.8, 38.6, 41.9,
30.6, 28.4, 32.3, 32.1, 47.3, 49.0, 39.8
]
},
{
name: '站间距',
type: 'line',
itemStyle: {
normal: {
label: {
show: true,
fontSize: 14
}
}
},
markLine: {
silent: true,
lineStyle: {
normal: {
color: '#FF818C' // 这儿设置安全基线颜色
}
},
data: [
{
yAxis: 1.64,
lineStyle: {
type: 'dashed',
width: 2
},
label: {
formatter: '均值1.64km',
fontSize: 14,
position: 'end',
color: '#FF818C'
}
}
]
},
yAxisIndex: 1,
tooltip: {
valueFormatter: function (value) {
return value + ' km';
}
},
color: '#F02233',
data: [
1.36, 2.08, 1.43, 1.33, 1.96, 1.21, 1.36, 1.28, 1.88, 1.23, 1.94, 2.88,
1.29, 1.27, 1.3, 1.44, 4.89, 2.88, 1.44
]
}
]
};
if (option && typeof option === 'object') {
myChart.setOption(option);
}
window.addEventListener('resize', myChart.resize);
</script>
</body>
</html>