这篇文章的思路很离奇,但它完全是可行的!出现这种离奇的原因是,在我手里除了上海地铁站点名称数据(这也算数据?)以外没有任何可用数据的情况下,某项目要求我空手套白狼,获取上海地铁各线路的站间距和各站点间旅行速度数据。

当然,这种程度还是难不倒博主的。整体获取思路如下:

1 站点关键词查找

从第一步开始就需要利用高德API的服务了。这里使用的是关键词查找服务。

Python
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服务也得用上。这个的参数和上面会略有不同:

Python
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的公交路径规划服务,导出同一线路上两两站点间的路程及时耗。

Python
# 当前使用的公交路径规划服务
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的格式就头疼的毛病,所以文件读取和写入方法都自己重写了一遍,上面的代码可能会用到:

Python
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.txtmat_sum.txt其实就对应上述获取的两份数据,一个是相邻站点间的数据,一个是总体数据。计算旅行速度的方式如代码中所示,其实就是解一个二元方程,只不过列方程思路很难想到。

Python
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
<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>