路网优化
gotrackit提供了以下几种方式来对标准路网做一些优化,你需要使用NetReverse类,该类包含了以下几个与路网优化有关的函数:
- 清洗路网线层数据 - clean_link_geo
- 修复联通性 - modify_conn
- ID重映射 - remapping_link_node_id
- 路段划分 - divide_links
- 环路处理 - circle_process
- 拓扑优化 - topology_optimization
- 路网重塑 - redivide_link_node
清洗路网线层数据
如果你已经有了路网线层数据(从OSM或者其他任何途径获取的), 使用create_node_from_link函数来生产拓扑关联可能会报错,因为你的路网线层数据可能存在以下问题:
- 包含了MultiLineString类型
- 带有Z坐标
- 有自相交的线对象
我们可以使用clean_link_geo函数对线层做一次清洗,示例代码如下:
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
# 读取数据
df = gpd.read_file(r'./data/output/request/0304/道路双线20230131_84.shp')
# 处理geometry
# l_threshold表示将线型中距离小于l_threshold米的折点进行合并,简化路网,同时消除重叠折点
# l_threshold推荐 1m ~ 5m,过大会导致线型细节失真
# plain_crs是要使用的平面投影坐标系
link_gdf = ng.NetReverse.clean_link_geo(gdf=df, plain_crs='EPSG:32649', l_threshold=1.0)
修复联通性
如果你已经有了标准路网,但是路网可能存在联通性问题,你可以使用modify_conn函数来修复路网的联通性,修复联通性示例代码如下:
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
link_gdf = gpd.read_file(r'./data/input/net/test/sz/FinalLink.shp')
node_gdf = gpd.read_file(r'./data/input/net/test/sz/FinalNode.shp')
# net_file_type指的是输出路网文件的类型
nv = ng.NetReverse(net_file_type='shp', conn_buffer=0.8, net_out_fldr=r'./data/input/net/test/sz/')
new_link_gdf, new_node_gdf = nv.modify_conn(link_gdf=link_gdf, node_gdf=node_gdf, book_mark_name='sz_conn_test', generate_mark=True)
print(new_link_gdf)
print(new_node_gdf)
在net_out_fldr下会生成联通性修复完成后的路网文件以及xml空间书签文件,将xml文件导入到QGIS可以查看修复的点位情况,以便排查是否所有修复都是合理的
ID重映射
如果你的线层文件的link_id或者点层文件的node_id是一个非常大的整数, 使用这样的路网存在风险,你可以使用静态方法remapping_link_node_id函数进行ID重映射
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
l = gpd.read_file(r'./data/input/net/xian/modifiedConn_link.shp')
n = gpd.read_file(r'./data/input/net/xian/modifiedConn_node.shp')
print(l[['link_id', 'from_node', 'to_node']])
print(n[['node_id']])
nv = ng.NetReverse()
nv.remapping_link_node_id(l, n)
print(l[['link_id', 'from_node', 'to_node']])
print(n[['node_id']])
路段划分
你已经有了一套标准路网, 你希望对link层进行路段划分,即将长度大于L(m)的路段都进打断,并且同步更新点层数据,你可以使用divide_links函数进行路段划分
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
link = gpd.read_file(r'./data/input/net/test/0317/link1.geojson')
node = gpd.read_file(r'./data/input/net/test/0317/node1.geojson')
nv = ng.NetReverse()
# 执行划分路网
# divide_l: 所有长度大于divide_l的路段都将按照divide_l进行划分
# min_l: 划分后如果剩下的路段长度小于min_l, 那么此次划分将不被允许
new_link, new_node = nv.divide_links(link_gdf=link, node_gdf=node, divide_l=50, min_l=5.0)
new_link.to_file(r'./data/input/net/test/0317/divide_link.geojson', driver='GeoJSON', encoding='gbk')
new_node.to_file(r'./data/input/net/test/0317/divide_node.geojson', driver='GeoJSON', encoding='gbk')
- 划分后,会生产一个新的字段:
_parent_link,用于记录此路段在划分前所属的link_id,如果为空值,说明该路段没有发生划分 - 关于路段划分函数的参数详解见:divide_links
处理环路
gotrackit不允许路网出现以下情况:
- 环路(
from_node == to_node) - 具有相同的拓扑起终点
(from_node,to_node)的link
在构建Net时会自动识别这些link并且进行删除, 如果你想保留这些路段请使用circle_process函数进行处理
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
l = gpd.read_file('./data/input/net/test/0506yg/link.shp')
n = gpd.read_file('./data/input/net/test/0506yg/node.shp')
# 处理环路和相同from_node - to_node的link
new_link, new_node = ng.NetReverse.circle_process(link_gdf=l, node_gdf=n)
# circle_process处理后输出的路网是平面投影坐标系, 需要转化为EPSG:4326
new_link = new_link.to_crs('EPSG:4326')
new_node = new_node.to_crs('EPSG:4326')
new_link.to_file('./data/input/net/test/0506yg/new_link.shp')
new_node.to_file('./data/input/net/test/0506yg/new_node.shp')
circle_process处理后如图:
拓扑优化
拓扑优化可以帮助你按照一定的规则对路段进行合并,我们以一个简单的示例来说明,下图是源路网:
合并后的长度限制
我们使用拓扑优化接口-topology_optimization对源路网进行路段合并,我们设置合并后的路段长度不能超过320米
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
执行上述代码后,会在net_out_fldr下生成新的路网文件,可视化后如下:
由合并后的路网可以发现:
节点15没有被合并,因为其连接的两条link的行车方向相反,ignore_dir=False表明了不能忽略行车方向节点5没有被合并,因为该节点一旦被合并,合并后的路段长度= 198 + 219 > 320,不满足长度限制条件节点10没有被合并,不满足长度限制条件节点12、13、4没有被合并,因为一旦合并后将形成环,不满足allow_ring=False的限制
路段转角限制
我们使用拓扑优化接口-topology_optimization对源路网进行路段合并,我们设置转角大于5°的节点不能被合并
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
执行上述代码后,会在net_out_fldr下生成新的路网文件,可视化后如下:
由合并后的路网可以发现:
节点15没有被合并,因为其连接的两条link的行车方向相反,ignore_dir=False表明了不能忽略行车方向节点5、3、8、7没有被合并,因为该节点的转向角超过了angle_threshold节点12、13、4没有被合并,因为一旦合并后将形成环,不满足allow_ring=False的限制
路段属性限制
我们使用拓扑优化接口-topology_optimization对源路网进行路段合并,我们设置限制字段为link层的road_name字段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | |
执行上述代码后,会在net_out_fldr下生成新的路网文件,可视化后如下:
由合并后的路网可以发现:
节点5没有被合并,因为其连接的两条link的road_name属性不一致
忽略路段行车方向
我们使用拓扑优化接口-topology_optimization对源路网进行路段合并,我们指定忽略路段的行车方向
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
执行上述代码后,会在net_out_fldr下生成新的路网文件,可视化后如下:
由合并后的路网可以发现:
节点12、13、4没有被合并,因为一旦合并后将形成环,不满足allow_ring=False的限制- 原路网中的
15节点已经被合并
允许合并后出现环
我们使用拓扑优化接口-topology_optimization对源路网进行路段合并,我们指定不忽略路段的行车方向、允许合并后出现环
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | |
执行上述代码后,会在net_out_fldr下生成新的路网文件,可视化后如下:
由合并后的路网可以发现:
节点12、13、4被合并形成环
路网重塑
你已经有了线层文件,但是其存在折点联通性问题,你可以使用redivide_link_node函数进行路网重塑:
import geopandas as gpd
import gotrackit.netreverse.NetGen as ng
if __name__ == '__main__':
# 读取数据
origin_link = gpd.read_file(r'./data/input/net/test/0402BUG/load/test_link.geojson')
print(origin_link)
# 为防止线层线型有重复点,先做清洗
origin_link = ng.NetReverse.clean_link_geo(gdf=origin_link, l_threshold=1.0, plain_crs='EPSG:32650')
# multi_core_merge=True表示启用多进程进行拓扑优化
# merge_core_num表示启用两个核
nv = ng.NetReverse(net_out_fldr=r'./data/input/net/test/0402BUG/redivide',
plain_crs='EPSG:32650', flag_name='new_divide', multi_core_merge=True,
merge_core_num=2)
# 路段、节点重新划分、联通性修复,新的网络文件在net_out_fldr下生成
nv.redivide_link_node(link_gdf=origin_link)
路网重塑实际上进行了以下几个步骤:
- 按照折点拆分线层
- 生产点层
- 进行拓扑优化
- 去除重叠路段
- 修复联通性