第一次打开 SUMO 的人,多半会和当年的我一样:对着一个灰网格、几条绿线段发愣。Simulation of Urban MObility,这个德国航空航天中心维护了十几年的开源微观交通仿真器,在车路协同、自动驾驶决策验证、城市信号优化这些领域已经被用得很广。它也确实强大,但入门门槛真实存在:net.xml、rou.xml、netconvert、duarouter、TraCI 这一连串名词砸过来,很容易让人在一周内放弃。这篇就想把“从一个完全不认识 SUMO 的状态,到能跑通自己的第一个仿真场景”这条路上最关键的节点讲透。适合刚接触 SUMO 的交通专业学生、做自动驾驶仿真的工程师,以及所有需要在虚拟路网上验证车流方案的人。
1. 先搞懂 SUMO 的底层逻辑:路网、需求与仿真器
1.1 SUMO 是什么,为什么值得花时间
SUMO 是微观交通流仿真软件。“微观”意味着它把每一辆车当作独立个体来模拟:每辆车有自己的跟驰行为、换道倾向、出发时间和可以感知的信号灯状态。这和一整条车流被压缩成几个流量参数的宏观模型完全不同,所以它能回答“某个路口改完相位之后,平均排队长度变化多少”“自动驾驶车辆在混行环境下会不会导致上下游交通振荡”这类问题。
它免费、开源、跨平台,而且不是玩具。SUMO 的文档里能看到几十种跟驰模型、换道模型,还有一堆面向研究者的分析工具。更重要的是,它提供 TraCI 接口,外部程序可以在仿真运行过程中实时读取数据、修改信号灯状态、注入新车辆。做 V2X 仿真或者控制算法验证时,这个能力几乎是刚需。
当然,免费开源的代价是学习曲线比较陡。不是功能难,而是信息太分散,文档是管用的,但你得先知道该查哪一个文件。这篇相当于先把最核心的那几条线索给你捋顺。
1.2 路网、需求、仿真器三个概念必须分清
SUMO 把一次仿真拆成三个独立部分,这是理解一切的基础。
路网文件是静态的舞台,通常叫 xxx.net.xml。它定义了节点坐标、路段几何、车道数量、限速、交叉口连接关系、信号灯位置。路网不是人画的 CAD 图纸,它是机器可读的数据结构。你可以在 netedit 里画路网,也可以通过命令行工具从 OpenStreetMap 数据自动转换,或者直接手写 XML。
需求文件是动态的演员,一般是 xxx.rou.xml 或 xxx.trips.xml。它定义哪些车辆出发、从哪到哪、什么时候出发、用什么车型、最大速度多少。注意,需求数据和路网数据是分开维护的。同一个路网,你可以跑早高峰需求,也可以跑平峰需求,只需要替换需求文件。
仿真器把舞台和演员放到一起逐秒推进,这就是 sumo 和 sumo-gui 做的事。sumo 是无界面版本,适合批量跑实验;sumo-gui 带可视化窗口,适合调试路网和观察现象。
没有需求和路网中的任何一部分,仿真都跑不起来。在实践中我见过不少新人把路线错误地写在路网文件里,或者把信号灯配时写进需求文件,最后报错。区分这三个角色之后,绝大多数配置错误都能一眼定位。
1.3 组件工具箱:netconvert、netedit、duarouter、sumo 的分工
SUMO 装好之后,你会得到一堆可执行文件,但常用的是四个。
netconvert 负责格式转换,最常见的用途是把 OpenStreetMap 的 .osm 数据转换成 SUMO 的 .net.xml 路网,也可以在文本格式和二进制格式之间转换。netedit 是图形编辑器,可以直接画路段、设置信号灯、编辑交通需求,适合小范围精细修改。duarouter 负责路径计算,输入是一堆只写了起终点的 trip 或 flow,输出是每辆车实际走哪条路线的 route 文件。sumo 和 sumo-gui 是运行器,一个无头一个带界面。
一句话理解:netconvert 管转格式,netedit 管动手改,duarouter 管算路,sumo 管跑仿真。后面所有操作基本都在这些工具之间打转。
2. 安装与环境准备:别在起跑线卡住
2.1 Windows / Linux / macOS 三条安装路线
如果你用 Ubuntu 或 Debian 系的 Linux,可以直接sudo apt install sumo sumo-tools,装完版本可能不是最新,但足够入门。用 Windows 的话,去官网下载 zip 包解压到某个目录,比如C:\SUMO,然后把C:\SUMO\bin加入 PATH,再建一个环境变量SUMO_HOME=C:\SUMO。macOS 用户最简单,brew install sumo就能装完。
还有一条通用路线是 conda:conda install -c conda-forge sumo。好处是你可以在一个隔离环境里装,不会污染系统;坏处是 conda 的包更新频率有时候滞后。我个人的建议:新手阶段哪个顺手用哪个,不要为了版本纠结。真正影响你学不学得下去的,是习惯养成和概念理解,不是版本差那么 0.1。
2.2 验证安装和准备 Python 环境
装完先别急着画路网,确认三件事。
sumo --version netconvert --version python --version如果都能正常输出,说明基本环境没问题。接下来设置 Python 的PYTHONPATH,这一步很多人会漏。SUMO 的 Python 工具集放在 tools 目录下,包括 randomTrips.py、netcheck.py 这些脚本,以及 TraCI 的 Python 绑定库。如果PYTHONPATH没设置,你会在import traci时直接碰到红色报错。
Linux 下可以这样写进~/.bashrc:
export SUMO_HOME=/usr/share/sumo export PATH=$PATH:$SUMO_HOME/bin export PYTHONPATH=$SUMO_HOME/tools:$PYTHONPATH前提是你的包管理器把 sumo 装到了/usr/share/sumo。不同发行版路径可能有差异,可以用dpkg -L sumo | grep tools查一下实际位置。Windows 用户则在环境变量里配置一样的变量名。
2.3 一个值得坚持的工作目录习惯
SUMO 项目文件多,建议从一开始就给每个仿真场景一个独立目录,并且按类型分开。比如net放路网相关,demand放需求文件,config放 sumocfg 配置文件,output放仿真结果。听起来很基础,但实际项目里,一个路网改改弄弄就能产生十几个版本,需求文件更多,没有目录管理会在三天后彻底混乱。
我自己的习惯是把整个场景目录丢进 Git 仓库,每次改完路网或配置就提交一次。好处是改崩了可以随时回退,而且能清楚地看到某个参数是哪个版本引进来的。SUMO 场景文件都是纯文本,Git 做 diff 虽然不如代码文件直观,但至少能帮你定位修改时间点。
3. 一小时跑通第一个 SUMO 仿真
3.1 用手写 XML 的方式快速建一个小路网
不要急着导入真实地图,先手工写一个三角环线路网。这个路网很小,但包含三个节点、两条以上路段和交叉口连接关系,足够跑通完整流程。
先建node.xml,定义节点。SUMO 使用平面直角坐标,单位是米。
<nodes> <node id="A" x="0" y="0" type="priority"/> <node id="B" x="1000" y="0" type="priority"/> <node id="C" x="500" y="500" type="priority"/> </nodes>再建edge.xml,定义路段。这里有个特别容易踩的坑:SUMO 里的 edge 是单向的。你要想实现双向通行,必须写两条方向相反的 edge。比如 A 到 B 有了AB,B 到 A 还得有BA,别指望系统自动给你补反向。
<edges> <edge id="AB" from="A" to="B" numLanes="2" speed="13.89"/> <edge id="BA" from="B" to="A" numLanes="2" speed="13.89"/> <edge id="BC" from="B" to="C" numLanes="1" speed="10"/> <edge id="CB" from="C" to="B" numLanes="1" speed="10"/> <edge id="CA" from="C" to="A" numLanes="1" speed="10"/> <edge id="AC" from="A" to="C" numLanes="1" speed="10"/> </edges>注意speed的单位是米每秒,不是公里每小时。13.89正好是 50 km/h,10是 36 km/h。这是新手最容易忽略的细节,后面看仿真速度不对,先回去查这个。
然后用 netconvert 生成路网文件:
netconvert -n node.xml -e edge.xml -o simple.net.xml生成后可以打开simple.net.xml看内容,你会发现里面多了大量细节:junction 定义、connection 连接关系、车道编号、内部边 internal lanes 等。这些都是 netconvert 自动算出来的,你不需要手写。
3.2 定义车辆需求:从 flow 到 route
路网只是舞台,现在加演员。新建routes.rou.xml,先定义一个车辆类型,再定义一个车流。
<routes> <vType id="car" accel="2.6" decel="4.5" sigma="0.5" length="5" maxSpeed="50" minGap="2.5" color="1,0,0"/> <flow id="flow1" type="car" from="A" to="C" begin="0" end="3600" number="600"/> </routes>begin和end的单位是秒,number="600"表示这一小时内总共发出 600 辆车,车辆会在时间段内比较均匀地分布。maxSpeed单位是米每秒,虽然我这里填的 50 实际上是米/秒,但真实场景里如果限速 50 km/h,记得填 13.89。minGap表示车距,单位米。
flow指定的是 OD 需求,也就是出发地和目的地。具体走哪条路,SUMO 运行时内部会调用路径搜索算法算出最短路。你也可以直接给一辆车写死路线,方便调试:
<vehicle id="v1" depart="0" type="car"> <route edges="A AB B BC C"/> </vehicle>这种写法的好处是你能精确控制车辆经过的每条边,适合验证路网连通性和观察单车行为。批量实验时通常用 flow 或 trip,然后丢给 duarouter 展开成具体 route。
3.3 写配置文件,让 sumo 跑起来
有了路网和需求,还需要一个配置告诉 sumo 加载哪些文件、仿真多长时间。新建simple.sumocfg:
<configuration> <input> <net-file value="simple.net.xml"/> <route-files value="routes.rou.xml"/> </input> <time> <begin value="0"/> <end value="3600"/> <step-length value="0.1"/> </time> </configuration>注意step-length,它决定仿真步长,单位秒。默认是 1 秒,对大多数宏观信号优化研究已经够用。如果你做的是碰撞、避让这类精细化场景,可以把步长调到 0.1 秒,但计算开销会高很多。新手统一先跑 1 秒就行。
然后执行:
sumo -c simple.sumocfg如果配置正确,你会看到一系列仿真进度日志,最后显示仿真完成。没有图形界面,跑完就结束,但至少证明链路是通的。
要看车辆跑起来的样子,用带界面的版本:
sumo-gui -c simple.sumocfg打开之后按左下角的播放按钮,车辆就会出现在路网上动起来。
3.4 sumo-gui 的基本操作和调试技巧
窗口打开后,鼠标滚轮是缩放,右键拖拽是平移。左侧面板可以列出当前路网中的所有车辆,点击车辆能看到它的速度、位置、当前边。想按速度看车流颜色分布,可以在工具栏的着色模式里选“Speed”。
调整仿真速度有两个变量:一个是最上面的 delay 滑块,它控制每步之间人为等待的毫秒数,调成 0 表示系统尽量快地跑;另一个是左下角的步进按钮,可以单步推进,观察某个瞬间的状态。
我建议你第一次跑 GUI 的时候,故意把 delay 调大,让车速放慢,盯着几辆车看它们在交叉口怎么让行、怎么换道。这个过程能帮你建立对“微观仿真”的直观感受,也会在之后排查问题时帮上大忙。
4. 从演示到实战:OSM 真实地图导入与自动化工具
4.1 用 netconvert 把 OpenStreetMap 数据变成路网
手工路网只能用来练手,真项目要的是真实路网。最常用的数据源是 OpenStreetMap,你自己在网站上框选一个区域导出.osm文件,然后执行:
netconvert --osm-files map.osm -o city.net.xml这行命令会把 OSM 里的 highway 数据转成 SUMO 路网。但有个常见问题:OSM 数据里什么路都有,从高速公路到小区内部小路,不筛选的话转出来的路网会非常碎。所以一般会加一个类型限制:
netconvert --osm-files map.osm --osm-type street -o city.net.xml--osm-type street表示只转换街道等级的路网。不同 SUMO 版本支持的 type 可能略有差异,用之前可以netconvert --help osm看一下具体选项。
转出来的路网通常都是几千个节点起步,不可能手工检查每个交叉口。这时你应该打开 sumo-gui 快速浏览一圈,重点看有没有奇怪的断头路、飞线、大面积空白区域,有的话再回去查原始 OSM 数据。
4.2 坐标投影问题:绕不过去的坑
OSM 原始数据用的是经纬度,而 SUMO 内部用的是米制平面坐标。netconvert 默认会自动选一个投影方式把经纬度转成平面坐标,大多数情况下它能选对,但也经常失灵。
失灵的表现有两种:一种是一打开 sumo-gui,发现路网距离尺度完全不对,比如本来几百米的道路变成了几十万米;另一种是报错,说无法计算投影或者坐标超范围。
这时候需要手动指定投影参数。常见做法是显式传一个 PROJ 字符串,例如:
netconvert --proj-string "+proj=utm +zone=49 +datum=WGS84 +units=m" --osm-files map.osm -o city.net.xmlUTM 投影分带号,zone 可以根据中心经度计算:zone = int((lon + 180) / 6) + 1。这步看似麻烦,但它决定了路网坐标是否合理。如果你觉得投影概念很唬人,可以暂时理解成“把地图无缝平铺到地面上的一块小区域内”,选错区域就等于铺歪了。
有个经验:不要一上来就导入整个城市。先导一个小片区,验证坐标正确,再逐步扩大。否则你会面对一个巨大且错位的路网,根本无法定位问题。
4.3 自动生成交通需求:randomTrips 与流量标定
真实路网有了,需求怎么来?如果只是概念验证,可以用 SUMO 自带的随机出行生成脚本:
python $SUMO_HOME/tools/randomTrips.py -n city.net.xml -o trips.trips.xml -r trips.rou.xml -e 3600 -p 2-p 2表示平均每两秒生成一辆车。这个脚本会在路网上随机选取起终点,生成一批 trip 数据。trip 只写了出发地、目的地和发车时间,还没具体到路线,所以要再用 duarouter 算一遍:
duarouter -n city.net.xml -t trips.trips.xml -o routes.rou.xml跑完的routes.rou.xml就是每辆车带上具体路径的最终需求文件。
如果你做的是真实项目,光靠 randomTrips 是不够的。正式做法是用卡口数据或调查数据先构建 OD 矩阵,再根据路网和路径选择模型生成流量。SUMO 里有不少辅助脚本和工具可以导入 OD 矩阵,但那是另一个深度的话题。新手阶段先用 randomTrips 把流程跑通,理解需求生成的两步结构,后面再谈标定。
5. 进阶实操:信号灯、公交线与 TraCI 动态控制
5.1 信号灯配置:从自动生成到自定义相位
如果你把路网里某个节点的type从priority改成traffic_light,netconvert 会自动生成一套默认的信号灯配时。在 sumo-gui 里你会看到红绿灯按周期切换,足够应付演示。
但真实场景没有这么省心,你需要自定义绿信比、相位顺序甚至感应控制。信号灯配置写在 additional 文件里,然后在 sumocfg 的<additional-files>里加载。一个完整的两相位示例大概长这样:
<additional> <tlLogic id="C" type="static" programID="mProg" offset="0"> <phase duration="31" state="GGrr"/> <phase duration="6" state="yyyr"/> <phase duration="31" state="rrGG"/> <phase duration="6" state="rrrr"/> </tlLogic> </additional>这里的state字符串,每一位对应交叉口的一个连接。字母含义不复杂:大写G表示绿灯、y表示黄灯、r表示红灯。关键是顺序要和该节点实际连接列表保持一致,否则会出现“这方向亮黄灯、那方向也亮黄灯”的对不上号问题。
最稳妥的做法是先用 netedit 打开路网,在信号灯编辑器里直观地改相位和时长,改完之后它帮你生成正确的 XML。手写 state 容易错,新手尤其容易在连接顺序上翻车。
5.2 公交站、定线公交与多方式仿真
SUMO 不只管小汽车,公交也很容易加进去。先把公交站定义在附加文件里:
<additional> <busStop id="bs1" lane="AB_0" startPos="10" endPos="60" name="BusA"/> </additional>注意lane不能只写 edge,必须写到具体车道。比如AB_0表示 edge AB 的第 0 条车道,SUMO 的车道编号从右往左从 0 开始,这是很多人的知识盲区。
定义公交车时,给它配一个公交类型,然后在路线中加入停车动作:
<vehicle id="bus1" type="bus" depart="0" line="L1"> <route edges="A AB B BC C"/> <stop busStop="bs1" duration="20"/> </vehicle>duration是停站时长的秒数。这样这辆公交车在通过bs1时会停车 20 秒,然后继续行驶,看起来就是一班标准公交。
也可以实现行人仿真,但复杂度会跃升:需要给行人定义独立路网空间,比如人行横道、步行通道,不能直接让行人走在车行道上。如果项目需求里不涉及行人,我建议先跳过,等主流程跑稳了再碰。
5.3 TraCI:用 Python 在仿真运行中实时控制
TraCI 是 SUMO 对外提供的实时接口。它允许你启动一个仿真,然后在 Python 里一步一步推进,每一步之间都可以读写内部状态。
先写一个最简例子:
import libsumo as traci traci.start(["sumo", "-c", "simple.sumocfg"]) for step in range(3600): traci.simulationStep() ids = traci.vehicle.getIDList() if len(ids) == 0: continue speeds = [traci.vehicle.getSpeed(v) for v in ids] avg_speed = sum(speeds) / len(speeds) if step % 600 == 0: print(step, len(ids), avg_speed) traci.close()有些较老的 SUMO 版本不支持import libsumo,你只需要改成import traci,其余 API 基本一样。
TraCI 真正厉害的地方是动态干预。你可以在每一仿真步里判断当前某个路口的排队长度,如果超过阈值就延长绿灯时间;你也可以用traci.vehicle.add()在任意时刻向路网中注入车辆,模拟突发车流。这些能力让 SUMO 从“预先设定好的动画”变成了“可以做控制实验的沙盒”,也是很多论文实验的基础设施。
6. 常见问题与排查技巧实录
6.1 车辆不出现或瞬间消失
这是新手最常遇到的问题。车不出现,先查需求文件和仿真时间有没有重叠。比如 flow 里定的begin="0" end="3600",但 sumocfg 里begin="100" end="200",那前 100 秒车还没开始发,后 100 秒发完又结束,自然一辆都看不见。
还有一种情况是车辆出现了,但瞬间就“消失”了。这通常不是真消失,而是车辆在仿真结束前已经到达目的地,被移出路网。确认办法是开启 tripinfo 输出,在 sumocfg 中加入:
<output> <tripinfo-output value="tripinfo.xml"/> </output>这样每辆车的出发时间、到达时间、总旅行时间都会写进文件,一查就知道车到底跑没跑。
6.2 车辆不动或堵死在交叉口
如果车卡在某个交叉口一直不动,大概率是连接关系有问题,或者某条路根本走不通。用 sumo-gui 打开路网,切到连接显示模式,检查每个节电的 incoming、outgoing 边是否正确。
还有一种情况是路网中存在孤岛,也就是某些路段和其他路网没有物理连接。SUMO 提供了一个检查工具:
python $SUMO_HOME/tools/net/netcheck.py -n simple.net.xml -o report.txt报告里如果出现“disconnected subnetwork”之类的提示,说明路网内部有断点,需要回去修 OSM 数据或用 netedit 补上。
6.3 导入 OSM 后地图错位或尺度离谱
之前讲过的投影问题是主要元凶。如果出现坐标数量级明显不对,比如一条路长达 80 万米,别怀疑路网数据本身,先怀疑投影。用--proj-string重新指定一次,再对比路网的坐标范围,问题基本能解决。
另外,有些 OSM 导出文件包含大量小线段和冗余节点,转换出的路网会特别“碎”。常用的清理手段是在 netconvert 命令里加--geometry.remove和--geometry.min-dist,去掉过密的多余节点,保持几何形状不变。
6.4 仿真跑得太慢
路网大了、车辆多了,仿真速度自然下来。最快的办法是改用无界面的 sumo,去掉 GUI 渲染开销。还不够就用多线程选项:
sumo -c config.sumocfg --threads 4threads参数默认是 1,调高之后大路网会明显快一些。另外检查一下是不是把step-length设成了 0.1 秒,如果是,先改回 1 秒,大多数场景其实够用。输出文件也别乱开,fcd 输出每步全量记录车辆轨迹,写盘压力非常大。
6.5 环境变量和 Python 导入报错
ModuleNotFoundError: No module named 'traci'大概率是PYTHONPATH没配好,或者你换了终端之后没有重新加载配置。先执行echo $PYTHONPATH看是否为空。
如果之前能用,某次更新系统后突然失灵,检查 SUMO 版本和 Python 版本的兼容性。别急着升级 SUMO,有时候反而是系统 pip 版本和新版 traci 冲突。
还有一个隐藏问题:Windows 下路径包含中文或空格时,Python 脚本可能找不到文件。别在这种地方浪费时间,把场景目录放到纯英文路径下,能避开一大批诡异报错。
最后分享两个我自己的习惯。第一,每个实验场景都用 Git 管理,路网文件、需求文件、配置文件全部入库。改坏了就回退,实验做完了还能翻出来对照当初到底改了什么参数,这在仿真调参时比什么文档都可靠。第二,不要一上来就导入整个大区域,先手工做一个小三角形路网,把最简单的仿真跑通,再逐步叠加 OSM、信号灯、公交、TraCI。这个三角形路网就像编程里的 Hello World,跑通它,你就已经迈过 SUMO 最艰难的那道门槛了。