第三讲:路网构建进阶——从OSM到可用的仿真路网
上一讲我们聊了怎么手搓一个简单路网。说实话,那种方式适合练手,真要搞一个真实城市的仿真,谁还一条条画路啊?
这一讲,我们来点硬核的。我会带你走一遍真实项目里的路网构建流程:从OSM下载地图,用NETCONVERT转成SUMO格式,再动手改属性、加公交专用道、设限速区。嗯,这些活儿我几乎每个项目都要干一遍。
3.1 从OSM获取地图数据
OpenStreetMap,简称OSM,是个好东西。它就像交通领域的维基百科,全世界的地图数据都有。我个人习惯用 overpass-api 或者直接去 download.geofabrik.de 下载区域地图。
举个例子,你想仿真北京中关村附近的路网:
# 下载某个区域的OSM数据(.osm格式)
wget https://download.geofabrik.de/asia/china-latest.osm.pbf
# 或者用osmconvert转成.osm格式
osmconvert china-latest.osm.pbf -o=beijing.osm
这里有个坑——OSM文件通常很大。我曾经下载整个北京市的OSM文件,结果有2个多G。你想想看,一个路网文件2个G,SUMO根本跑不动。所以我的建议是:先用 osmfilter 或者 osmium 工具,只提取你需要的区域。
osmium extract -b 116.30,39.90,116.40,40.00 beijing.osm.pbf -o zhongguancun.osm.pbf 可以按边界框裁剪地图。边界框的经纬度可以去 openstreetmap.org 的导出页面查看。
3.2 使用NETCONVERT转换OSM到SUMO路网
拿到OSM文件后,主角登场了——netconvert。它是SUMO自带的转换工具,能把OSM的原始数据翻译成SUMO能识别的 .net.xml 文件。
最基本的转换命令长这样:
netconvert --osm-files zhongguancun.osm --output-file zhongguancun.net.xml
跑完这条命令,你会得到一个路网文件。但说实话,直接转出来的路网往往不太完美。为什么?因为OSM的数据是志愿者画的,有些道路属性不完整,有些交叉口连接不对。
我在项目中遇到过最典型的问题:OSM里的道路是双黄线,但SUMO默认当成普通道路处理。结果仿真出来的车流乱窜,跟实际完全不符。
所以,我们需要加一些参数来优化转换:
netconvert --osm-files zhongguancun.osm \
--output-file zhongguancun.net.xml \
--geometry.max-segment-length 50 \
--junctions.join \
--ramps.guess \
--tls.guess-signals
这几个参数什么意思?我解释一下:
--geometry.max-segment-length 50:把太长的路段切成小段,方便后续编辑--junctions.join:合并距离很近的交叉口,避免出现微型路口--ramps.guess:自动识别匝道,这个对快速路仿真很重要--tls.guess-signals:从OSM数据中猜信号灯配置
--tls.guess-signals 可能会猜错。我曾经在仿真一个欧洲小镇时,它把环岛也当成了信号灯路口,结果车流全堵死了。所以转完后一定要用SUMO-GUI打开看看。
3.3 编辑道路属性
转完路网后,你会发现有些道路的属性不对。比如,OSM里标注的限速是60km/h,但实际那条路限速80km/h。或者,有些路明明是单行道,OSM里却标成了双向。
这时候,我们需要手动编辑 .net.xml 文件。嗯,直接改XML有点痛苦,但掌握了规律其实还好。
一个典型的道路(edge)定义长这样:
<edge id="12345" from="node1" to="node2" priority="1" speed="13.89" numLanes="2">
<lane id="12345_0" index="0" speed="13.89" length="200.0" shape="0,0 200,0"/>
<lane id="12345_1" index="1" speed="13.89" length="200.0" shape="0,3.5 200,3.5"/>
</edge>
注意看 speed 属性,单位是 m/s。13.89 m/s 换算过来就是 50 km/h。如果你想改成60 km/h,就改成 16.67。
我个人习惯用Python写个脚本批量改,而不是手动一个个改。比如:
import xml.etree.ElementTree as ET
tree = ET.parse('zhongguancun.net.xml')
root = tree.getroot()
for edge in root.findall('edge'):
for lane in edge.findall('lane'):
# 把限速改成60 km/h (16.67 m/s)
lane.set('speed', '16.67')
tree.write('zhongguancun_modified.net.xml')
你看,几行代码就搞定了。当然,实际项目中你可能需要根据道路ID或者类型来选择性修改,而不是一刀切。
3.4 添加公交专用道
公交专用道是城市交通仿真的常见需求。在SUMO里,实现公交专用道有两种方式:
- 使用车道类型(lane type):给某条车道打上"bus"标签
- 使用禁止规则(vClass):禁止非公交车使用该车道
我推荐第二种方式,更灵活。具体做法是在 .net.xml 里找到目标车道,加上 allow 或 disallow 属性:
<lane id="12345_0" index="0" speed="13.89" length="200.0" allow="bus" shape="0,0 200,0"/>
这样,只有 vClass="bus" 的车辆才能走这条车道。其他车辆会被强制变道到旁边的车道。
我曾经在深圳的一个BRT项目里用过这个功能。当时有个问题:公交车到了路口需要左转,但公交专用道在最右侧。这就需要在路口前设置一段过渡区,让公交车能变道到左转车道。解决办法是在路口前50米取消公交专用道的限制。
3.5 设置限速区域
限速区域,说白了就是给某一段路或者某个区域设定不同的速度限制。比如学校门口限速30 km/h,或者施工区域限速20 km/h。
在SUMO里,限速区域可以通过两种方式实现:
- 修改车道属性:直接改
speed值 - 使用Variable Speed Sign (VSS):动态限速,适合模拟可变限速牌
对于静态限速,直接改车道属性就够了。比如把学校门口的一段路限速改成30 km/h:
<lane id="67890_0" index="0" speed="8.33" length="100.0" shape="..."/>
8.33 m/s 就是 30 km/h。
对于动态限速,需要添加VSS元素。这个稍微复杂一点,但很实用。比如模拟雨天限速:
<additional>
<variableSpeedSign id="vss_1" lanes="12345_0 12345_1">
<step time="0" speed="13.89"/>
<step time="3600" speed="8.33"/> <!-- 一小时后限速30 km/h -->
</variableSpeedSign>
</additional>
嗯,这里要注意:VSS需要在仿真运行时通过 --additional-files 参数加载。
3.6 本章知识体系
说了这么多,我画个图帮你理一下思路:
3.7 避坑指南
最后,分享几个我踩过的坑:
- OSM数据太旧:有些道路已经改了,但OSM没更新。我建议转完后用高德或百度地图对比一下。
- 交叉口连接错误:NETCONVERT有时候会把不该连的路连起来。用SUMO-GUI打开,手动检查一下关键路口。
- 限速单位搞混:SUMO里速度单位是m/s,但OSM和日常习惯用km/h。换算公式:km/h ÷ 3.6 = m/s。
- 公交专用道没效果:检查一下你的车辆类型定义,确保公交车设置了
vClass="bus"。
好了,这一讲的内容就是这些。路网构建是仿真的地基,地基打不好,后面跑出来的数据全是废的。所以,多花点时间在这个环节,值得。
可可精品资料库