百度Apollo学习:Routing模块结构和源码
附赠自动驾驶学习资料和量产经验:链接
Routing模块简介
Routing类似于现在开车时用到的导航模块,通常考虑的是起点到终点的最优路径(通常是最短路径),Routing考虑的是起点到终点的最短路径,而Planning则是行驶过程中,当前一小段时间如何行驶,需要考虑当前路况,是否有障碍物。Routing模块则不需要考虑这些信息,只需要做一个长期的规划路径即可,过程如下:
这也和我们开车类似,上车之后,首先搜索目的地,打开导航(Routing所做的事情),而开始驾车之后,则会根据当前路况,行人车辆信息来适当调整直到到达目的地(Planning所做的事情)。
-
Routing- 主要关注起点到终点的长期路径,根据起点到终点之间的道路,选择一条最优路径。
-
Planning- 主要关注几秒钟之内汽车的行驶路径,根据当前行驶过程中的交通规则,车辆行人等信息,规划一条短期路径。
基础知识
Demo
我们通过"OSM Pathfinding"作为例子,来详细讲解整个过程。项目地址,感谢@mplewis提供的展示。
首先我们通过如下的视频演示看下Routing寻找路径的过程,查找的是深圳南山区的地图:
路径规划演示
-
选择查找算法,有: A*, Breadth First Search, Greedy Best First Search, Uniform Cost Search, Depth First Search。
-
选择起点。
-
选择终点。
-
选择开始,开始寻找路径。
上面的项目是基于OSM(openstreetmap)获取的地图数据,如果需要自己制作地图,首先在OSM的官网导出地图,导出的文件格式为“map.osm”,可以通过浏览器打开查看,然后在项目的tools目录,把OSM地图转成项目用到的(Graph)图。制作demo的过程如下:
-
获取地图信息- 由于OSM的地图都是开源的,所以我们只需要找到对应的区域,并且选择导出,就可以导出地图的原始数据。地图的数据格式为OSM格式。
-
构建图- 根据上述的信息,构建有向图,下载的格式对渲染比较友好,但是对查找最短路径不友好,因此要转换成有向图的格式(apollo的routing模块也是经过了如下的转换)。
-
查找最短路径- 根据上述的信息,查找一条最短路径。
如果图的规模太大,以1000个举例,只算两个点之间互相有连接的情况,1000*1000就是100万个点,如果点的规模更大,那么就需要采用redis数据库来提高查找效率了。
下面我们先介绍上面的例子是如何工作的。
地图
我们以openstreetmap为例来介绍下地图是如何组成的。[开放街道地图](OpenStreetMap)(英语:OpenStreetMap,缩写为OSM)是一个建构自由内容之网上地图协作计划,目标是创造一个内容自由且能让所有人编辑的世界地图,并且让一般的移动设备有方便的导航方案。因为这个地图是一个开源地图,所以可以灵活和自由的获取地图资源。
接着看下openstreetmap的基本元素:
Node节点表示由其纬度和经度定义的地球表面上的特定点。每个节点至少包括id号和一对坐标。节点也可用于定义独立点功能。例如,节点可以代表公园长椅或水井。节点也可以定义道路(Way)的形状,节点是一切形状的基础。
<node id="25496583" lat="51.5173639" lon="-0.140043" version="1" changeset="203496" user="80n" uid="1238" visible="true" timestamp="2007-01-28T11:40:26Z">
<tag k="highway" v="traffic_signals"/>
</node>
Way道路是包含2到2,000个有序节点的折线组成,用于表示线性特征,例如河流和道路。道路也可以表示区域(实心多边形)的边界,例如建筑物或森林。在这种情况下,道路的第一个和最后一个节点将是相同的。这被称为“封闭的方式”。
<way id="5090250" visible="true" timestamp="2009-01-19T19:07:25Z" version="8" changeset="816806" user="Blumpsy" uid="64226">
<nd ref="822403"/>
<nd ref="21533912"/>
<nd ref="821601"/>
<nd ref="21533910"/>
<nd ref="135791608"/>
<nd ref="333725784"/>
<nd ref="333725781"/>
<nd ref="333725774"/>
<nd ref="333725776"/>
<nd ref="823771"/>
<tag k="highway" v="residential"/>
<tag k="name" v="Clipstone Street"/>
<tag k="oneway" v="yes"/>
</way>
Relation关系是记录两个或更多个数据元素(节点,方式和/或其他关系)之间的关系的多用途数据结构。例子包括:
-
路线关系,列出形成主要(编号)高速公路,自行车路线或公交路线的方式。
-
转弯限制,表示你无法从一种方式转向另一种方式。
-
描述具有孔的区域(其边界是“外部方式”)的多面体(“内部方式”)。
Tag所有类型的数据元素(节点,方式和关系)以及变更集都可以包含标签。标签描述了它们所附着的特定元素的含义。标签由两个自由格式文本字段组成; ‘Key’和’Vaule’。例如,“高速公路”=“住宅”定义了一条道路。元素不能有2个带有相同“key”的标签,“key”必须是唯一的。例如,您不能将元素标记为amenity = restaurant和amenity = bar。
我们看到的地图,实际上是由一些Node和Way组成,需要展示地图时候,通过读取地图中的Node和Way的数据实时画(渲染)出来,例如2个Node组成了一条道路,那么就在这两点之间画一条直线,并且标记为道路,如果是封闭区域,并且根据数据,画出一个多边形,并把它标记为湖泊或者公园。
有很多地图渲染引擎,以下是openstreet推荐的地图引擎:
-
Osmarender: 一个基于可扩展样式表语言转换 (XSLT) 的渲染器,能够创建可缩放矢量图形(SVG), SVG可以用浏览器观看或转换成位图.
-
Mapnik: 一个用C++写的非常快的渲染器,可以生成位图(png, jpeg)和矢量图形(pdf, svg, postscript)。
最短距离
我们先看一下经典的例子:最短路径。
在图论中,最短路径问题是在图中的两个顶点之间找到路径,使得其边的权重之和最小化的问题。而在地图上找到两个点之间最短路径的问题可以被建模为图中最短路径问题的特殊情况,其中顶点对应于交叉点并且边缘对应于路段,每个路段对应于路段的长度。
最短路径算法:
-
Dijkstra算法
-
A*算法
-
Bellman-Ford算法
-
SPFA算法(Bellman-Ford算法的改进版本)
-
Floyd-Warshall算法
-
Johnson算法
-
Bi-Direction BFS算法
在地图上查找两个点之间的最短距离,我们就可以把道路的长度当做边,路口当做节点,通过把道路抽象为一个有向图,然后通过上述算法,查找到当前2点之间的最短路径,并且输出,这就是每次我们查找起点和终点的过程。
所以要查找起点到终点之间的路径,需要经过以下几个步骤:
-
获取地图的原始数据,节点和道路信息。
-
通过上述信息,构建有向图。
-
采用最短路径算法,找到2点之间的最短距离。
1. 实际上,真实场景的地图导航,查找2点之间的路径,可能不是实时计算出来的,假设有100个人在查询“北京机场”到“天安门”的路线,第一个人的路线可能是实时计算得到的,而其他99个人都是用的缓存的数据,在第一个人查到之后,后面的99个人就不需要重复计算了。
2. 对于频繁查找的路线,也可以在晚上统一计算,然后保存起来,可以利用存储换速度,除非道路有变化(新修路或者道路维修,桥断了),然后再重新计算。
3. 多样化的需求,比如可以选择高速优先还是不走高速,地铁优先,少换乘等,这些都需要构建层次和结构化的信息。
4. 根据用户的反馈实时的更新路况,比如路上有10个人在用地图导航,发现在某一段大家都开的很慢,或者有用户反馈堵车,就更新当前路况。
5. 所以地图是一个强者越强的市场,用户越多,数据就更新的就越快,地图就越准确,用的人就越多;用的人越少,数据更新的就越慢,假设一条路上只有一个用户,一个人开的慢,并不能反馈当前道路拥堵,用该地图的其它用户过去之后,发现堵车,就导致用户体验很差,下次就不会再用这个地图了。所以地图必须需要一定的用户量才能活下去,而且强者越强。
分析Routing模块之前,我们只需要能够解决以下几个问题,就算是把routing模块掌握清楚了。
-
如何从A点到B点?
-
如何规避某些点? - 查找的时候发现是黑名单里的节点,则选择跳过
-
如何途径某些点?- 采用分段的形式,逐段导航(改进版的算法是不给定点的顺序,自动规划最优的线路)
-
如何设置固定线路,而且不会变?最后routing输出的结果是什么?<