3. 路网构建进阶

上一讲我们聊了怎么手搓一个简单路网。说实话,那种方式适合练手,真要搞一个真实城市的仿真,谁还一条条画路啊?

这一讲,我们来点硬核的。我会带你走一遍真实项目里的路网构建流程:从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数据中猜信号灯配置
注意: 如果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里,实现公交专用道有两种方式:

  1. 使用车道类型(lane type):给某条车道打上"bus"标签
  2. 使用禁止规则(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 本章知识体系

说了这么多,我画个图帮你理一下思路:

路网构建进阶知识体系 OSM地图数据 NETCONVERT转换 编辑道路属性 添加公交专用道 设置限速区域 可用的SUMO路网 (.net.xml) 核心流程:OSM → NETCONVERT → 属性编辑 → 专用道/限速 → 可用路网

3.7 避坑指南

最后,分享几个我踩过的坑:

  • OSM数据太旧:有些道路已经改了,但OSM没更新。我建议转完后用高德或百度地图对比一下。
  • 交叉口连接错误:NETCONVERT有时候会把不该连的路连起来。用SUMO-GUI打开,手动检查一下关键路口。
  • 限速单位搞混:SUMO里速度单位是m/s,但OSM和日常习惯用km/h。换算公式:km/h ÷ 3.6 = m/s。
  • 公交专用道没效果:检查一下你的车辆类型定义,确保公交车设置了 vClass="bus"。

好了,这一讲的内容就是这些。路网构建是仿真的地基,地基打不好,后面跑出来的数据全是废的。所以,多花点时间在这个环节,值得。

可可精品资料库