2、SUMO网络构建:从零开始搭路网

做自动驾驶仿真,路网就是舞台。舞台搭不好,戏就没法唱。今天咱们聊聊SUMO网络构建这件事。我个人习惯把路网构建分成三种方式:手绘、导入、手动改。三种方式各有各的用处,咱们一个一个说。

2.1 用netedit手绘路网

netedit是SUMO自带的图形化编辑器。说实话,刚开始用的时候我觉得它有点丑,但用久了发现真香。启动方式很简单:

netedit

或者你想直接打开一个已有的网络文件:

netedit mynetwork.net.xml

打开后你会看到一个空白画布。嗯,这里要注意,画布上的网格是SUMO的坐标系,单位是米。我刚开始做项目时,有次把坐标单位搞混了,结果路网缩放了10倍,车跑起来像蚂蚁爬。从那以后我每次都会先确认网格间距。

手绘路网的基本操作:

  • 创建边(Edge):双击画布,按住Shift再点一下,就能拉出一条路
  • 添加节点(Junction):两条路交叉的地方自动生成节点
  • 设置车道数:选中边,在右侧属性面板改lane数
  • 调整形状:拖拽边的中间点可以弯曲道路
小技巧:我习惯先画主干道,再画支路。就像盖房子先搭框架,再填细节。画完主干道后,用"Split"工具把长路切成几段,方便后续设置不同限速。

2.2 导入OSM地图

手绘适合小场景,但做自动驾驶仿真,你往往需要真实道路。这时候OSM(OpenStreetMap)就派上用场了。我在项目中遇到过最头疼的事,就是导入的OSM地图车道数不对。后来发现,OSM的数据质量参差不齐,有些小路根本没标车道数。

导入OSM的步骤:

  1. 去openstreetmap.org下载区域地图(.osm格式)
  2. 用netconvert工具转换:
netconvert --osm-files mymap.osm -o mymap.net.xml

就这么简单?其实没那么简单。你想想看,OSM里包含的信息太多了——人行道、自行车道、建筑轮廓、甚至树的位置。这些对车辆仿真来说都是噪音。我建议加上过滤参数:

netconvert --osm-files mymap.osm -o mymap.net.xml \
  --geometry.remove \
  --roundabouts.guess \
  --ramps.guess \
  --junctions.join \
  --tls.detect-signals \
  --tls.default-type actuated

这些参数什么意思?说白了就是:去掉多余几何形状、识别环岛、识别匝道、合并过近的节点、检测信号灯。我刚开始做的时候没加这些参数,结果路网里全是碎小的路段,仿真跑起来卡得要命。

避坑指南:我曾经导入一个城市中心的路网,结果生成了3000多个节点,仿真帧率掉到个位数。后来加了--junctions.join参数,节点数直接砍半。记住,路网不是越精细越好,够用就行。

2.3 理解.net.xml文件结构

别看.net.xml是个XML文件,它其实挺有规律的。我刚开始学SUMO时,觉得这文件又臭又长,后来发现核心就几个部分。咱们直接看一个简化版的结构:

<?xml version="1.0" encoding="UTF-8"?>
<net version="1.16" junctionCornerDetail="5" limitTurnSpeed="5.00">
  
  <!-- 位置信息 -->
  <location netOffset="0.00,0.00" convBoundary="0.00,0.00,1000.00,1000.00"/>
  
  <!-- 边(道路)定义 -->
  <edge id="gneE0" from="gneJ0" to="gneJ1" priority="1">
    <lane id="gneE0_0" index="0" speed="13.89" length="100.00" shape="0.00,0.00 100.00,0.00"/>
    <lane id="gneE0_1" index="1" speed="13.89" length="100.00" shape="0.00,3.20 100.00,3.20"/>
  </edge>
  
  <!-- 节点(交叉口)定义 -->
  <junction id="gneJ0" type="priority" x="0.00" y="0.00" incLanes="" intLanes="" shape="0.00,0.00 0.00,6.40 3.20,6.40 3.20,0.00"/>
  
  <!-- 连接关系 -->
  <connection from="gneE0" to="gneE1" fromLane="0" toLane="0" dir="s" state="M"/>
  
</net>

核心元素就四个:

元素 作用 关键属性
<edge> 定义一条道路 id, from, to, priority
<lane> 道路上的车道 id, index, speed, length, shape
<junction> 交叉口/节点 id, type, x, y, shape
<connection> 车道间的连接 from, to, fromLane, toLane, dir, state

这里有个容易搞混的地方:edge和lane的关系。一条edge可以包含多个lane,每个lane有自己的id。比如edge "gneE0"有两条lane,分别叫"gneE0_0"和"gneE0_1"。我见过有人直接在edge上设速度,结果所有lane都跟着变。其实速度是lane级别的属性,每个lane可以单独设置。

2.4 手动编辑网络文件

有些时候,图形界面搞不定的事,就得手动改文件。比如你想让某条路限速从60km/h降到30km/h,或者想加一条只有公交车能走的专用道。这些在netedit里操作起来很麻烦,但直接改XML就快多了。

我常用的手动编辑场景:

  • 批量改限速:用文本编辑器打开.net.xml,搜索speed="13.89"(对应50km/h),替换成你需要的值
  • 添加禁行规则:在lane里加<allow>标签,比如只允许passenger和bus通过
  • 调整交叉口相位:修改junction里的traffic light定义

举个例子,我想让某条路变成公交专用道:

<lane id="busLane_0" index="0" speed="13.89" length="200.00">
  <allow>bus</allow>
</lane>

就这么简单。但要注意,改了文件后一定要用netconvert验证一下:

netconvert -s mynetwork.net.xml -o mynetwork_checked.net.xml

如果文件有语法错误,netconvert会报错。我吃过这个亏,有次改完文件没验证,直接跑仿真,结果SUMO崩溃了。后来我养成了习惯,每次手动编辑后必跑一遍验证。

核心要点:手动编辑.net.xml是进阶技能,但也是最灵活的。你可以在不打开任何图形界面的情况下,用脚本批量生成或修改路网。我做过一个项目,需要生成100个不同拓扑的路网,就是用Python脚本直接写XML,效率比手动画高太多了。

2.5 本章知识体系

说了这么多,咱们用一张图来总结一下SUMO网络构建的核心逻辑:

SUMO网络构建核心流程 方式一:netedit手绘 方式二:导入OSM 方式三:手动编辑 生成 .net.xml 网络文件 .net.xml 核心结构 <edge> 道路定义 <lane> 车道属性 <junction> 节点 <connection> 连接关系 <location> 位置偏移 <type> 道路类型

这张图把咱们今天讲的内容串起来了。三种方式殊途同归,最终都生成.net.xml文件。而理解这个文件的结构,是后续做仿真脚本的基础。你想想看,不管是手绘还是导入,最后不都得落到这个文件上吗?

好了,路网构建这块就聊到这儿。记住一句话:路网是仿真的地基,地基稳了,上面盖什么楼都行。下一节咱们聊聊怎么在路网上跑车,那才是真正有意思的部分。

可可精品资料库