2、SUMO网络构建:从零开始搭路网
做自动驾驶仿真,路网就是舞台。舞台搭不好,戏就没法唱。今天咱们聊聊SUMO网络构建这件事。我个人习惯把路网构建分成三种方式:手绘、导入、手动改。三种方式各有各的用处,咱们一个一个说。
2.1 用netedit手绘路网
netedit是SUMO自带的图形化编辑器。说实话,刚开始用的时候我觉得它有点丑,但用久了发现真香。启动方式很简单:
netedit
或者你想直接打开一个已有的网络文件:
netedit mynetwork.net.xml
打开后你会看到一个空白画布。嗯,这里要注意,画布上的网格是SUMO的坐标系,单位是米。我刚开始做项目时,有次把坐标单位搞混了,结果路网缩放了10倍,车跑起来像蚂蚁爬。从那以后我每次都会先确认网格间距。
手绘路网的基本操作:
- 创建边(Edge):双击画布,按住Shift再点一下,就能拉出一条路
- 添加节点(Junction):两条路交叉的地方自动生成节点
- 设置车道数:选中边,在右侧属性面板改lane数
- 调整形状:拖拽边的中间点可以弯曲道路
2.2 导入OSM地图
手绘适合小场景,但做自动驾驶仿真,你往往需要真实道路。这时候OSM(OpenStreetMap)就派上用场了。我在项目中遇到过最头疼的事,就是导入的OSM地图车道数不对。后来发现,OSM的数据质量参差不齐,有些小路根本没标车道数。
导入OSM的步骤:
- 去openstreetmap.org下载区域地图(.osm格式)
- 用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
这些参数什么意思?说白了就是:去掉多余几何形状、识别环岛、识别匝道、合并过近的节点、检测信号灯。我刚开始做的时候没加这些参数,结果路网里全是碎小的路段,仿真跑起来卡得要命。
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崩溃了。后来我养成了习惯,每次手动编辑后必跑一遍验证。
2.5 本章知识体系
说了这么多,咱们用一张图来总结一下SUMO网络构建的核心逻辑:
这张图把咱们今天讲的内容串起来了。三种方式殊途同归,最终都生成.net.xml文件。而理解这个文件的结构,是后续做仿真脚本的基础。你想想看,不管是手绘还是导入,最后不都得落到这个文件上吗?
好了,路网构建这块就聊到这儿。记住一句话:路网是仿真的地基,地基稳了,上面盖什么楼都行。下一节咱们聊聊怎么在路网上跑车,那才是真正有意思的部分。