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_0
  • index:车道索引,从0开始
  • speed:限速,单位m/s。13.89 m/s ≈ 50 km/h
  • length:车道长度,一般和边长度一致
  • shape:车道形状,定义了车道的几何路径

避坑指南:我曾经手写路网时,把from和to写反了,结果车从终点往起点开。SUMO不会报错,但仿真结果完全反了。所以,写完后一定要用sumo-gui打开看看方向对不对。

3.4 节点与边的「连接规则」

为什么节点和边要分开定义?说白了,这是图论的基本思想。节点是顶点,边是连接。你想想看,如果路网是一张网,节点就是网上的「结」,边就是「线」。

连接规则其实很简单:

  1. 每条边必须连接两个节点(from和to)
  2. 节点必须先定义,边才能引用它
  3. 一个节点可以连接多条边(十字路口就是4条边)
  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 知识体系结构图

下面这张图,帮你理清路网文件的核心逻辑:

SUMO路网文件核心结构 <net> 路网根标签 <junction> 节点 <edge> 边 节点属性 • id:唯一标识 • type:类型(dead_end/priority) • x, y:坐标位置 边属性 • id:唯一标识 • from/to:连接节点 • length:道路长度 节点是「点」,边是「线」,点线结合构成路网

这张图很直白:<net>是根,下面分<junction>和<edge>两大块。节点定义位置和类型,边定义连接关系和车道。两者缺一不可。

3.7 常见错误与避坑

最后,我总结几个手写路网时容易踩的坑:

  • 节点坐标不连续:比如节点A在(0,0),节点B在(100,0),但边的长度写成了50米。车会「瞬移」到终点。
  • 忘记定义反向边:如果你需要双向行驶,必须手动定义两条边。SUMO不会自动生成。
  • 节点类型用错:dead_end类型的节点只能连接一条边,如果你连了两条边,SUMO会报错。
  • shape属性写错:shape定义了路口的视觉形状,写错了不影响仿真,但看着别扭。

我曾经踩过的坑:有一次我手写了一个包含20个节点、30条边的路网,结果忘了写<location>标签的convBoundary属性。SUMO直接崩溃,报了个莫名其妙的错误。查了半天才发现是边界没定义。所以,<location>一定要写对。

好了,这一讲就到这里。路网文件说白了就是「节点+边」的组合,搞懂了这两个核心概念,后面的内容就顺了。

可可精品资料库