概述
各大地图厂商都拥有自己的一套电子地图,也向一般用户、开发者提供了路径规划的接口,基于带起终点坐标的请求信息,接口会返回分段的路径坐标串信息。将这些路径信息规范为多段线然后使用GIS软件进行可视化后,可以清晰的看到路网的轮廓。从视觉效果上来看,这就是一套完整的导航路网,但是从底层数据来看,这不是一套合乎标准的可复用、可计算的路网系统,节点层缺失、拓扑不完善、线层重叠等等问题,是没法投入实际生产使用的...
是否有可能依据路径结果的坐标串信息来逆向构建一套拓扑正确、线型合理、点线关联的可计算路网图层呢?
所谓正确的路网,先从最基本的拓扑属性来说,即为每一个路段都标注上拓扑的起终点节点编号,标注其是否为双向行驶路段,明确其行车方向是否与拓扑方向一致,以及依据这些节点之间的连接关系构造出的有向图能否与真实世界匹配,除开拓扑的合理性,还有视觉感官上的合理性,比如两个路段应该是首尾相连的,但是在你的处理下,本应该在同一个位置的节点,却相隔数十米,尽管他在拓扑上是联通的,这样也是不可行的。
通过最近的一些开发尝试,发现基本可行,大概分为以下几个步骤:
- 构造尽可能多的OD对利用接口获取路径数据;
- 基于路径最小线段单元进行去重;
- 空间关联生成点层;
- 合并2度节点;
- 去除重叠路段,还原线型;
- 极小间隔节点优化。
开发步骤
使用的库主要是:geopandas、shapely、networkx
1.构造OD进行路径规划请求
这个,各大地图厂商都有自己的开发者接口,参照平台文档进行路径规划请求即可获得分段路径的坐标串信息,然后将其转化为多段线即可:
2.基于路径最小线段单元进行去重
基于返回的坐标串信息,我们将轨迹线中相邻的两个坐标点位看作一个路段,将其进行拆分,生成多个不包含折点的路段,标注该路段的唯一标识索引字段为起终点坐标(当作字符串处理),然后按照该索引字段进行暴力去重,即可减少大部分的重叠路段。
3.空间关联生成点层
提取路段的起终点坐标信息,构成集合,然后依然是依据坐标进行去重,然后分别赋予node_id,生成点层后与线层的起点、终点tag关联,为路段添加拓扑起终点的节点编号,即路段的from_node和to_node编号。这里需要注意的是,有部分点位,两个点位之间:经纬度坐标小数点前6~7位都是一样的,在小数点最后一位不一致,这种点位使用纯字符串去重的方式是无法去重的,这样会得到视觉上同一个位置有两个不同node_id的节点,还会导致该点处拓扑不连通:
这种情况处理的方法是:对所有的点层做一个半径为0.1~0.8m的buffer后进行空间自相交运算然后识别出这些几乎重叠的点的组别,这里的优化处理我放在最后一步去做。
还有需要注意的一点是:在为路段添加拓扑起终点的节点编号后,某些路段的起终点节点编号刚好相反,那么说明该处是一条可双向行驶的路段,我们需要将他们找出来,然后只保留其中一条,同时方向字段dir标记为0.
4.合并2度节点
1~3步骤较为简单,从这里开始,就要和复杂多变的路径线型斗智斗勇了,总会有意想不到的bug线型出现!由于现在的路网非常碎,一个一两百米的路段可能会有十多个节点,且这些节点都是度为2的节点,即这些节点大部分都没有存在必要,一般节点的出现是在不同道路的交叉位置处、路段属性的变化处。这块的功能之前在某个项目正好写过一次,所以算是复用了之前的模块。
大概的思路是:步骤3输出的线层和点层构建整个网络的无向图和有向图,基于无向图找出所有的度为2的节点,然后抽取子图,再遍历子图进行合并操作:
在分析每个子图时,更加精准的合并需要考虑方向限制(即行车方向不一致的link是否允许合并)、是否允许合并后出现环(上图中的子图6中的节点合并后就会形成一个环)、属性限制,找出可以合并的link组,然后在几何上对路段进行合并,同步也需要更新其拓扑关系,如上图子图2所关联的4个路段会变为1个路段:
合并前的拓扑关系:
| link_id | direction | from_node | to_node |
|---|---|---|---|
| 1 | 0 | 1 | 2 |
| 2 | 0 | 2 | 3 |
| 3 | 0 | 3 | 4 |
| 4 | 0 | 4 | 5 |
合并后的拓扑关系:
| link_id | direction | from_node | to_node |
|---|---|---|---|
| 1 | 0 | 1 | 5 |
这样可以删除很多的非必要节点,减少我们的网络规模,在路径搜索的时候会更快。
考虑方向限制的方法
我们一般使用dir来表示路段的实际行车方向,如果dir=1代表该路段的实际允许的行车方向与拓扑方向一致,如果dir=-1代表该路段的实际允许的行车方向与拓扑方向相反,如果dir=0代表该路段的实际允许的行车方向为双向,所以如果有两个相邻路段的行车方向不一致(几乎很少),那么这种合并是不被允许的: 在考虑这种情况的限制时,我们最好在数据预处理时将所有的dir为-1的路段转化为1,那么此时所有路段的dir值只可能为1或者0,那么对于初步筛选的可以合并的link组,只有dir值连续为1或者0的才有可能进行合并:
然后再考虑每个节点的不平衡度:
5. 去除重叠路段,还原线型
找到所有的重叠link组
做完步骤4,得到的路网还有部分问题,大多是一些相同位置的link重叠(没有完全重叠,相隔距离很近,通过QGIS检查发现,距离一般在0~0.2m左右,重叠形态非常多,这里仅列举一种情况):
一般来说这种情况,如果不放大看很难察觉,但是这会导致拓扑有问题,而且作为一个细节控!这种问题必须解决!消除重叠路段的思路是:对于每一条link做0.5m左右的缓冲区,然后去和其他每一条link(除开本身)做空间相交运算,然后计算相交面积占比自身面积的比率,我们设定一个阈值60%,当两条link的相交比率超过了这个阈值,我们即可认为他们是重叠的,此时我们把这两条link看做无向图的两个节点,认为他们是联通(重叠)的,重复以上步骤,我们得到了一个无向图,其中应该包含了多个相互不连通的子图,每个子图内是联通的,这就是我们要找的重叠link组别:
以上图路网为例,我们得到的无向图的节点(实际是link的编号)连接关系为:(2,4),(2,5),(4,6),(2,6),(3,7)
利用连通图算法找到其中有两个联通图:
图1:(2,4),(2,5),(4,6),(2,6),图2:(3,7)
这样一来我们就对重叠的link组进行了分组,然后遍历每一个组别,生成新的线型。
基于重叠link组生成新线型
(1).取出重叠组中最长的那条link作为base_link
(2).将重叠Link组所涉及的node信息取出来,分别沿着base_link的拓扑方向计算其与base_link的位置关系(在base_link上的投影点距离拓扑起点的距离),在起点外侧标记为head_beyond,为负数),在尾部外侧标记为tail_beyond(大于base_link的线型长度),其余标记为within内部
(3).利用节点在base_link上的投影切割base_link:
所有的节点都需要去对base_link做切割操作嘛?不是的。如果距离非常近的点,我们只保留度最大的那一个用于切割base_link,因为度最大的点他很大可能还连接了其他的link,我们不希望破坏原有的拓扑关系,如图,通过检测算法,如果节点1和节点9被识别为在一组内,他们的距离小于规定的距离阈值,我们会优先剔除节点1(度为1),保留节点9(度为2),节点2、3、4被识别为一组,三个节点的度都是1,随机保留一个节点,所以我们最终会使用节点9、节点(4).节点5在base_link上的投影点来打断base_link,生成的新节点和link:
(5).删除原有重叠link组,添加新线型
删除原来的重叠link组,添加新的link,这样一来这一组内的重叠现象就被消除,遍历所有的组做相同的操作即可:
但是从结果中可以看到,这样带来的问题是,也会生成一些2度节点,我们如果愿意,可以对当前的link图层再做一次步骤4(合并2度节点)
为何选择这样的方式更新呢?因为重叠路段的形态多种多样,远不止上图所示,这种更新机制几乎可以面对所有的重叠情形,且能最大程度的保持原有线型。
6. 极小间隔节点优化
在 3.空间关联生成点层 中,我们提到了有部分点位几乎重合(地理坐标只是小数点最后一位不一样),由于之前的暴力字符串去重,这种点位仍然被看成是两个点,这会导致路网的拓扑不连通,所以我们还是利用点缓冲区的方法计算点与点之间的相交百分比的方法来识别这种点集,然后任意保留其中一个即可,同时记录{删除点ID:保留点ID}的哈希表,用于更新线层的from_node和to_node,几何线型也可以刷新,但是可做可不做,因为肉眼几乎分辨不出来。
北京某片区逆向结果
只作了一次2度节点合并操作:
只要我们构造的OD对足够多,就能保证某区域范围内的路网完备性,一个较好的思路是:先基于一定量的OD逆向一个基础版本的路网,然后做一个路网增量更新的机制,当以后每进来一条新的路径,就将其和基础路网进行对比分析,增量变化,逐渐完善路网。