基于高德地图车辆通行时间预测的python实践

一、 动机与意义

如今,随着人们生活水平的提升,私人汽车逐渐成为家庭中不可或缺的交通工具。而车辆的增多会直接导致城市道路的拥堵,大大提高车辆事故发生率。通过预测车辆在不同道路的通行时间,能够大致估计道路的拥挤状况,提前对司机进行行车预警,或是根据实际情况规划其他路径。同时,随着车联网的普及,当已知车辆在该条道路的预计通行时间,能够更好的进行通信资源分配和广告内容投放,提高通信网络效率。

二、高德地图中的到达时间预测

在城市交通中,道路基本信息是可以提前得到的,道路基本信息包括道路实际长度、道路的曲率、限速信息以及红绿灯信息、甚至当前路段的拥堵情况等。高德地图中对到达时间的预测是依靠两部分信息:实时路况信息和历史速度信息(历史速度信息指的是对应的平均通行时间)。
其中实时路况信息对短时(例如60分钟以内)路况预测帮助较大,因为道路上可能出现的突发交通事故仅对较短时间内的车辆通行造成影响。所以对预测较短行车距离的到达时间时,会受到实时路况信息的影响;而历史速度信息对长时(例如60分钟以上)路况预测帮助较大。对于未来2小时以上的路况预测而言,当前时刻的路况信息帮助十分有限,这时候历史速度信息基本处于绝对主导的地位,因此在长距离路线的预估到达时间计算中,历史速度至关重要,其预测的准确性直接影响预估到达时间。
对于实时路况信息有太多不可控因素,所以很难将其考虑至到达时间预测中来。高德地图基于时间卷积网络(TCN)模型,利用历史信息(某时间段&去年同期:同一段道路、确定特征日、确定时间批次)和道路属性来预测未来一周不同路段的通行时间。其中将近两年的道路历史信息通过TCN模型得到动态特征信息,然后和作为静态特征信息的道路属性进行特征拼接,之后通过全连接层和输出层得到预估到达时间。网络结构示意图如下:
网络结构示意图
动态特征信息是将今年和去年对应的平均旅行时间序列作为一个双通道序列放进TCN模型中学习,利用TCN强大的时序建模能力,同时结合今年和去年的走势特征,更加准确的预测未来一周的走势(上升、下降或震荡)。而作为静态特征信息的道路属性则包括:
道路属性特征 路长、路宽、车道数、车道宽度、最大限速等
时间属性特征 前三天对应时间批次旅行时间、前七天对应时间批次旅行时间均值、去年同期前后两个平均旅行时间(同一特征日&同一时间批次)

道路属性特征路长、路宽、车道数、车道宽度、最大限速等
时间属性特征前三天对应时间批次旅行时间、前七天对应时间批次旅行时间均值、去年同期前后两个平均旅行时间(同一特征日&同一时间批次)

道路属性特征主要考虑不同的道路通行能力会有所差别,会在一定程度上影响车辆通行速度。时间属性特征主要分三方面:前三天对应时刻道路车辆通行时间、前七天对应时刻道路车辆通行时间的平均值以及去年同期道路通行时间平均值。

三、总结

  1. 高德地图中的到达时间预测适用于城市交通网络,且预先已知行驶终点。
  2. 适合于城市场景中较长行驶距离的车辆通行时间预测。

四、代码实现

我们可以通过高德导航提供的API接口来得到某段道路的车辆通行时间,随着车辆的移动,只需要不断将当前车辆位置和目的地位置坐标作为参数不断调用该API接口,就能够不断刷新道路预计通行时间。
高德地图道路通行时间预测
这里可以在得到车辆当前位置信息的基础上利用python来不断爬虫该网页,从而获得该道路预测通行时间:

def get_html(url):
    headers = {
        'User-Agent':'Mozilla/5.0(Macintosh; Intel Mac OS X 10_11_4)\
        AppleWebKit/537.36(KHTML, like Gecko) Chrome/52 .0.2743. 116 Safari/537.36'
 
    }     #模拟浏览器访问
    response = requests.get(url,headers = headers)       #请求访问网站
    html = response.text       #获取网页源码
    return html                #返回网页源码

然后寻找想要得到的道路预测通行时间信息和道路距离:

        soup = BeautifulSoup(get_html('https://restapi.amap.com/v3/direction/driving?origin='+current+'&destination=113.···,23.···&extensions=all&output=xml&waypoints='+waypoint+'&key=···'), 'html.parser')   #初始化BeautifulSoup库,并设置解析器
        origin = soup.find('origin')
        destination = soup.find('destination')
        duration = soup.find_all('duration')
        distance = soup.find_all('distance')
        print(datetime.datetime.now())
        print(origin)
        print(destination)
        print(duration)
        print(distance)

其中当前位置信息是通过GPS模块获得的;目的地位置信息是实现确定好的;途径点waypoint也会随着车辆移动不断变化;密钥key可以在高德地图开发者平台免费获取。
我的GPS模块是淘宝上45块钱买的,它可以以10HZ的频率发射当前位置、速度以及其他信息,可以通过简单的串口通信来接收并获取当前位置和速度信息:

    #读取串口数据
    ser = serial.Serial( #下面这些参数根据情况修改
    port='COM5',
    baudrate=9600,
    parity=serial.PARITY_ODD,
    stopbits=serial.STOPBITS_TWO,
    bytesize=serial.SEVENBITS
    )

由于GPS获得的是wgs84坐标,需要转换为中国国测局在02年建立的坐标系才能供高德地图使用,所以,需要进一步进行坐标变换:

class GisTransform(object):
    """gis坐标转换类"""
 
    def __init__(self, old_gis_name, new_gis_name):
        """
        经纬度(谷歌高德):'wgs84'/  墨卡托:'webMercator'/ 火星坐标系(国测局):'gcj02'
        """
        self.pi = 3.1415926535897932384626  # π   精度比math.pi 还高一些
        self.ee = 0.00669342162296594323  # 偏心率平方
        self.a = 6378245.0  # 长半轴
 
        func_name = old_gis_name + '_to_' + new_gis_name
        if hasattr(self, func_name):
            self.transform_func = getattr(self, func_name)
 
    def _out_of_china(self, lng, lat):
        """
        判断是否在国内,不在国内不做偏移
        :param lng:
        :param lat:
        :return:
        """
        return not (lng > 73.66 and lng < 135.05 and lat > 3.86 and lat < 53.55)
 
    def _transformlat(self, lng, lat):
        ret = -100.0 + 2.0 * lng + 3.0 * lat + 0.2 * lat * lat + \
              0.1 * lng * lat + 0.2 * math.sqrt(math.fabs(lng))
        ret += (20.0 * math.sin(6.0 * lng * self.pi) + 20.0 *
                math.sin(2.0 * lng * self.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(lat * self.pi) + 40.0 *
                math.sin(lat / 3.0 * self.pi)) * 2.0 / 3.0
        ret += (160.0 * math.sin(lat / 12.0 * self.pi) + 320 *
                math.sin(lat * self.pi / 30.0)) * 2.0 / 3.0
        return ret
 
    def _transformlng(self, lng, lat):
        ret = 300.0 + lng + 2.0 * lat + 0.1 * lng * lng + \
              0.1 * lng * lat + 0.1 * math.sqrt(math.fabs(lng))
        ret += (20.0 * math.sin(6.0 * lng * self.pi) + 20.0 *
                math.sin(2.0 * lng * self.pi)) * 2.0 / 3.0
        ret += (20.0 * math.sin(lng * self.pi) + 40.0 *
                math.sin(lng / 3.0 * self.pi)) * 2.0 / 3.0
        ret += (150.0 * math.sin(lng / 12.0 * self.pi) + 300.0 *
                math.sin(lng / 30.0 * self.pi)) * 2.0 / 3.0
        return ret
 
    def wgs84_to_webMercator(self, lon, lat):
        """wgs84坐标 转 墨卡托坐标"""
        x = lon * 20037508.342789 / 180
        y = math.log(math.tan((90 + lat) * self.pi / 360)) / (self.pi / 180)
        y = y * 20037508.34789 / 180
        return x, y
 
    def gcj02_to_webMercator(self, x, y):
        """火星转墨卡托"""
        wgs84_x, wgs84_y = self.gcj02_to_wgs84(x, y)
        webMercator_x, webMercator_y = self.wgs84_to_webMercator(wgs84_x, wgs84_y)
        return webMercator_x, webMercator_y
 
    def webMercator_to_webMercator(self, x, y):
        return x, y
 
    def webMercator_to_wgs84(self, x, y):
        """墨卡托坐标 转 wgs84坐标"""
        lon = x / 20037508.34 * 180
        lat = y / 20037508.34 * 180
        lat = 180 / self.pi * (2 * math.atan(math.exp(lat * self.pi / 180)) - self.pi / 2)
        return lon, lat
 
    def gcj02_to_wgs84(self, lng, lat):
        """
        GCJ02(火星坐标系)转GPS84
        :param lng:火星坐标系的经度
        :param lat:火星坐标系纬度
        :return:
        """
        if self._out_of_china(lng, lat):
            return lng, lat
        dlat = self._transformlat(lng - 105.0, lat - 35.0)
        dlng = self._transformlng(lng - 105.0, lat - 35.0)
        radlat = lat / 180.0 * self.pi
        magic = math.sin(radlat)
        magic = 1 - self.ee * magic * magic
        sqrtmagic = math.sqrt(magic)
        dlat = (dlat * 180.0) / ((self.a * (1 - self.ee)) / (magic * sqrtmagic) * self.pi)
        dlng = (dlng * 180.0) / (self.a / sqrtmagic * math.cos(radlat) * self.pi)
        mglat = lat + dlat
        mglng = lng + dlng
        new_x = lng * 2 - mglng
        new_y = lat * 2 - mglat
        return new_x, new_y
 
    def wgs84_to_gcj02(self, lng, lat):
        """
        WGS84转GCJ02(火星坐标系)
        :param lng:WGS84坐标系的经度
        :param lat:WGS84坐标系的纬度
        :return:
        """
        if self._out_of_china(lng, lat):  # 判断是否在国内
            return lng, lat
        dlat = self._transformlat(lng - 105.0, lat - 35.0)
        dlng = self._transformlng(lng - 105.0, lat - 35.0)
        radlat = lat / 180.0 * self.pi
        magic = math.sin(radlat)
        magic = 1 - self.ee * magic * magic
        sqrtmagic = math.sqrt(magic)
        dlat = (dlat * 180.0) / ((self.a * (1 - self.ee)) / (magic * sqrtmagic) * self.pi)
        dlng = (dlng * 180.0) / (self.a / sqrtmagic * math.cos(radlat) * self.pi)
        mglat = lat + dlat
        mglng = lng + dlng
        return mglng, mglat
 
    def webMercator_to_gcj02(self, x, y):
        """墨卡托转火星"""
        wgs84_x, wgs84_y = self.webMercator_to_wgs84(x, y)
        gcj02_x, gcj02_y = self.wgs84_to_gcj02(wgs84_x, wgs84_y)
        return gcj02_x, gcj02_y

主函数使用时可以调用:

        gis = GisTransform('wgs84', 'gcj02')  # 经纬度: wgs84 墨卡托: webMercator 国测局: gcj02
        gcj02_coordinates = gis.transform_func(Target_current_coordinates_N,Target_current_coordinates_A)
        current = '{:.6f},{:.6f}'.format(gcj02_coordinates[0],gcj02_coordinates[1])
        print(current)

以上就是该方案代码的主要过程,接下来需要进一步做个GUI出来方便操作处理。

参考:【2019高德技术年刊】下载链接
python实现 经纬度的 各GIS坐标系转换

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值