4. OpenDRIVE文件结构
OpenDRIVE 文件本质上是一个遵循 XML 规范的文本文件,其扩展名通常为 .xodr。理解它的结构,不能只停留在“它是一棵树”这种抽象描述上,而应把它看作一张由道路几何、车道拓扑、交叉口逻辑和信号控制共同构成的分层数据模型。XML 只是承载形式,真正决定文件能否被仿真器、规划模块或地图引擎正确解析的,是各核心节点之间的引用关系与组织顺序。
一、根元素与整体骨架
每个 OpenDRIVE 文件的根元素都是 <OpenDRIVE>,它通常带有 xmlns 命名空间和 rev 版本号。根元素之下,按约定顺序排列若干一级子节点,最常见的是 <header>、<road>、<junction>、<controller>,以及可选的 <station>、<userData> 等。这个顺序并非随意:header 提供全局元信息,road 定义道路实体,junction 描述道路之间的连接关系,controller 则把信号灯、匝道控制等逻辑挂接到具体道路上。
核心组织原则:OpenDRIVE 采用“先定义实体,再建立引用”的方式。道路、车道、信号、控制器都有唯一 ID,后续节点通过 id 属性进行跨层级引用。因此,ID 的稳定性和唯一性直接决定文件的可维护性。
二、header:全局元信息与坐标基准
<header> 是文件的“身份证”和“坐标系声明”。它通常包含 revMajor、revMinor、name、version、date、north、south、east、west 等属性,用于描述地图版本、地理范围和方向基准。更重要的是,header 中会定义 <geoReference>,说明地图采用的投影方式和坐标原点。如果这一层缺失或错误,后续所有道路几何都会失去绝对位置意义。
此外,header 还可以包含 <offset>,用于把局部坐标平移到全局坐标。工程实践中,常见误区是只修改了道路坐标却忘记同步 offset,导致仿真中车辆整体偏移。因此,header 应被视为“全局约束层”,而不是可有可无的注释。
三、road:道路几何与车道拓扑的核心载体
<road> 是 OpenDRIVE 中出现频率最高、结构最复杂的节点。每条 road 通过 id、name、length、junction 等属性标识自身。其内部通常包含 <link>、<planView>、<elevationProfile>、<lateralProfile>、<lanes>、<signals>、<objects> 等子节点。
<planView>:定义道路平面线形,由geometry组成,支持直线、螺旋线、圆弧、多项式等类型。<elevationProfile>:定义纵断面高程,常用elevation和shape描述。<lanes>:定义车道数量、宽度、类型和连接关系,是横向拓扑的核心。<link>:声明当前 road 的前驱和后继,用于构建道路级拓扑。
其中,<lanes> 又分为 laneSection,每个 section 内再分 left、center、right。中心车道 center 的 ID 固定为 0,左侧为正、右侧为负,这是 OpenDRIVE 的硬性约定。若把左右车道 ID 写反,仿真器会直接报错或产生逆行车辆。
四、junction 与 controller:连接关系与控制逻辑
<junction> 用于描述多条道路交汇形成的交叉口。它本身不直接定义几何,而是通过 <connection> 和 <laneLink> 说明“哪条路的哪条车道连接到哪条路的哪条车道”。因此,junction 是道路级拓扑的“粘合剂”。一个常见错误是只写了 connection 却没有写 laneLink,导致车辆进入路口后无法选择正确出口。
<controller> 则负责信号控制逻辑。它通常与 <signal> 配合使用,通过 control 子节点把信号灯与控制器关联起来。controller 可以定义相位、周期和信号组,是 OpenDRIVE 从“静态地图”走向“动态交通场景”的关键节点。若地图只用于路径规划,controller 可以省略;但若用于交通仿真或车路协同,它必须与 junction 中的连接关系保持一致。
五、案例:一个典型交叉口文件如何组织
假设要制作一个十字交叉口。首先在 header 中声明地图范围与投影;然后创建四条 road,分别表示东西南北四个进口道,每条 road 内用 planView 定义直线段,用 lanes 定义左转、直行、右转车道;接着创建 junction,把四条 road 的进口车道与出口车道通过 connection 和 laneLink 连接起来;最后创建 controller,把信号灯组与各进口道的 signal 关联。这样,仿真器读取文件后,既能知道车辆可以走哪条车道,也能知道红灯时应该停在哪里。
<OpenDRIVE>
<header revMajor="1" revMinor="7" name="cross" version="1.0">
<geoReference><![CDATA[+proj=tmerc ...]]></geoReference>
</header>
<road id="1" name="east" length="120.0" junction="10">
<link><predecessor elementType="junction" elementId="10"/></link>
<planView><geometry s="0" x="0" y="0" hdg="0" length="120"><line/></geometry></planView>
<lanes><laneSection s="0"><left><lane id="1" type="driving"/></left>
<center><lane id="0" type="none"/></center>
<right><lane id="-1" type="driving"/></right></laneSection></lanes>
</road>
<junction id="10">
<connection id="1" incomingRoad="1" connectingRoad="2" contactPoint="start">
<laneLink from="1" to="-1"/>
</connection>
</junction>
<controller id="20">
<control signalId="100" type="0"/>
</controller>
</OpenDRIVE>
六、常见误区与复盘要点
| 误区 | 表现 | 修正方式 |
|---|---|---|
| ID 重复或缺失 | 解析器报 duplicate id 或引用失败 | 建立全局 ID 分配表,road、junction、controller 分段编号 |
| laneLink 缺失 | 车辆进入路口后无法选择出口 | 逐条检查 connection 下的 laneLink 是否覆盖所有可行车道 |
| header 与 road 坐标不一致 | 地图整体偏移或旋转 | 统一使用 geoReference 和 offset,避免局部坐标混用 |
| controller 与 signal 脱节 | 信号灯不亮或相位错乱 | 确保 control 中的 signalId 与 signals 中的 id 一致 |
工程建议:在编写或生成 OpenDRIVE 文件时,先画一张“节点引用关系草图”,明确 header、road、junction、controller 之间的 ID 对应关系,再落笔写 XML。这样比事后逐行排查引用错误要高效得多。
总结来看,OpenDRIVE 的文件结构可以概括为:根元素定框架,header 定基准,road 定几何与车道,junction 定连接,controller 定控制。掌握这五层关系,就掌握了 OpenDRIVE 从静态地图到动态场景的完整组织逻辑。