3、路网文件解析:XML格式详解、节点(nodes)与边(edges)的定义规则
好,咱们进入第三讲。这一讲,我打算把SUMO路网文件的底裤扒开给你看。
说白了,路网文件就是一张地图的「骨架」。你想想看,没有骨架,车往哪儿跑?我刚开始接触SUMO时,总觉得这玩意儿就是个黑盒子,直到有一次自己手写了一个路网文件,才真正搞明白它的脾气。
3.1 XML格式:SUMO的通用语言
SUMO的路网文件,后缀名是.net.xml。它本质上就是一个XML文件。嗯,这里要注意:SUMO的XML和普通的HTML不一样,它有自己的标签规范。
一个典型的.net.xml文件长这样:
<?xml version="1.0" encoding="UTF-8"?>
<net version="1.16" junctionCornerDetail="5" limitTurnSpeed="5.50">
<location netOffset="0.00,0.00" convBoundary="0.00,0.00,1000.00,1000.00" projParameter="!"/>
<edge id="gneE0" from="gneJ0" to="gneJ1" priority="-1" length="500.00">
<lane id="gneE0_0" index="0" speed="13.89" length="500.00" shape="0.00,-1.60 500.00,-1.60"/>
</edge>
<edge id="gneE1" from="gneJ1" to="gneJ2" priority="-1" length="500.00">
<lane id="gneE1_0" index="0" speed="13.89" length="500.00" shape="500.00,-1.60 1000.00,-1.60"/>
</edge>
<junction id="gneJ0" type="dead_end" x="0.00" y="0.00" incLanes="" intLanes="" shape="0.00,0.00 0.00,-3.20 5.00,-3.20 5.00,0.00"/>
<junction id="gneJ1" type="priority" x="500.00" y="0.00" incLanes="gneE0_0" intLanes="" shape="495.00,0.00 495.00,-3.20 505.00,-3.20 505.00,0.00"/>
<junction id="gneJ2" type="dead_end" x="1000.00" y="0.00" incLanes="gneE1_0" intLanes="" shape="995.00,0.00 995.00,-3.20 1000.00,-3.20 1000.00,0.00"/>
</net>
看到没?整个文件就三个核心标签:<net>、<edge>、<junction>。我个人习惯把<net>看作「画布」,<edge>是「路」,<junction>是「路口」。
核心要点:SUMO的路网文件是「节点-边」模型。没有节点,边就不知道连到哪儿;没有边,节点就是个孤岛。
3.2 节点(Nodes):路网的「关节」
节点,在SUMO里叫junction。它代表道路的交叉点、端点或者变道点。我在项目中遇到过最坑的事,就是节点坐标写错了,结果车直接开到地图外面去了。
节点的定义格式:
<junction id="gneJ0" type="dead_end" x="0.00" y="0.00" incLanes="" intLanes="" shape="0.00,0.00 0.00,-3.20 5.00,-3.20 5.00,0.00"/>
每个节点必须包含以下属性:
| 属性名 | 含义 | 我的经验 |
|---|---|---|
id |
节点唯一标识 | 建议用gneJ0、gneJ1这种命名,别用中文 |
type |
节点类型 | 常见的有dead_end(死胡同)、priority(优先路口)、traffic_light(信号灯) |
x, y |
节点坐标 | 单位是米。我曾经把x和y写反了,结果车横着跑 |
incLanes |
进入该节点的车道 | 多个车道用空格分隔 |
intLanes |
节点内部车道 | 一般由SUMO自动生成,手写时留空即可 |
shape |
节点形状 | 定义了路口在视觉上的多边形轮廓 |
小技巧:如果你不确定节点类型该用哪个,就用priority。这是最通用的类型,SUMO会自动处理路权。
3.3 边(Edges):路网的「肌肉」
边,就是道路本身。它连接两个节点,车辆就在边上跑。我记得有一次做项目,需要模拟一条双向六车道的主干道,结果我忘了设置车道数,默认只有一条车道,仿真结果完全不对。
边的定义格式:
<edge id="gneE0" from="gneJ0" to="gneJ1" priority="-1" length="500.00">
<lane id="gneE0_0" index="0" speed="13.89" length="500.00" shape="0.00,-1.60 500.00,-1.60"/>
</edge>
边的核心属性:
| 属性名 | 含义 | 我的经验 |
|---|---|---|
id |
边唯一标识 | 建议用gneE0、gneE1,和节点区分开 |
from |
起始节点ID | 必须是一个已定义的节点 |
to |
终止节点ID | 同样必须是已定义的节点 |
priority |
道路优先级 | 数值越大优先级越高。我一般主干道设3,支路设1 |
length |
道路长度(米) | 这个值要和节点坐标算出来的距离一致,否则车会「瞬移」 |
每个边下面可以包含多个<lane>子标签,代表车道。车道属性包括:
id:车道ID,格式为边ID_车道索引,比如gneE0_0index:车道索引,从0开始speed:限速,单位m/s。13.89 m/s ≈ 50 km/hlength:车道长度,一般和边长度一致shape:车道形状,定义了车道的几何路径
避坑指南:我曾经手写路网时,把from和to写反了,结果车从终点往起点开。SUMO不会报错,但仿真结果完全反了。所以,写完后一定要用sumo-gui打开看看方向对不对。
3.4 节点与边的「连接规则」
为什么节点和边要分开定义?说白了,这是图论的基本思想。节点是顶点,边是连接。你想想看,如果路网是一张网,节点就是网上的「结」,边就是「线」。
连接规则其实很简单:
- 每条边必须连接两个节点(
from和to) - 节点必须先定义,边才能引用它
- 一个节点可以连接多条边(十字路口就是4条边)
- 边的方向是从
from指向to,车辆只能沿这个方向行驶
举个例子,一个十字路口需要:
- 4个节点(四个方向的路口)
- 4条边(每个方向一条)
- 每个节点连接2条边(一条进入,一条离开)
嗯,这里要注意:如果你需要双向行驶,必须定义两条方向相反的边。比如从A到B一条,从B到A另一条。SUMO不会自动生成反向车道。
3.5 手写一个简单路网
光说不练假把式。咱们手写一个最简单的路网:一条直路,两个端点。
<?xml version="1.0" encoding="UTF-8"?>
<net version="1.16" junctionCornerDetail="5" limitTurnSpeed="5.50">
<location netOffset="0.00,0.00" convBoundary="0.00,0.00,200.00,0.00" projParameter="!"/>
<junction id="J0" type="dead_end" x="0.00" y="0.00" incLanes="" intLanes="" shape="0.00,0.00 0.00,-3.20 5.00,-3.20 5.00,0.00"/>
<junction id="J1" type="dead_end" x="200.00" y="0.00" incLanes="" intLanes="" shape="195.00,0.00 195.00,-3.20 200.00,-3.20 200.00,0.00"/>
<edge id="E0" from="J0" to="J1" priority="1" length="200.00">
<lane id="E0_0" index="0" speed="13.89" length="200.00" shape="0.00,-1.60 200.00,-1.60"/>
</edge>
</net>
这个路网只有两个节点(J0和J1),一条边(E0)。车辆从J0出发,沿E0行驶到J1。就这么简单。
我的习惯:手写路网时,我会先用纸笔画个草图,标出所有节点坐标和边的连接关系。这样写XML时就不容易出错。
3.6 知识体系结构图
下面这张图,帮你理清路网文件的核心逻辑:
这张图很直白:<net>是根,下面分<junction>和<edge>两大块。节点定义位置和类型,边定义连接关系和车道。两者缺一不可。
3.7 常见错误与避坑
最后,我总结几个手写路网时容易踩的坑:
- 节点坐标不连续:比如节点A在(0,0),节点B在(100,0),但边的长度写成了50米。车会「瞬移」到终点。
- 忘记定义反向边:如果你需要双向行驶,必须手动定义两条边。SUMO不会自动生成。
- 节点类型用错:
dead_end类型的节点只能连接一条边,如果你连了两条边,SUMO会报错。 - shape属性写错:shape定义了路口的视觉形状,写错了不影响仿真,但看着别扭。
我曾经踩过的坑:有一次我手写了一个包含20个节点、30条边的路网,结果忘了写<location>标签的convBoundary属性。SUMO直接崩溃,报了个莫名其妙的错误。查了半天才发现是边界没定义。所以,<location>一定要写对。
好了,这一讲就到这里。路网文件说白了就是「节点+边」的组合,搞懂了这两个核心概念,后面的内容就顺了。