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 中的连接关系保持一致。

OpenDRIVE 文件结构:核心节点与引用关系 根元素统一承载,header 定基准,road 定几何,junction 定连接,controller 定控制 <OpenDRIVE> XML 根元素 · 命名空间 · rev 版本 <header> 版本 / 范围 / geoReference <road> 几何 / 车道 / 信号 / 对象 <junction> connection / laneLink <controller> control / signal 关联 road 内部结构 planView / geometry elevationProfile lanes / laneSection link / signals / objects laneSection 内:left(正 ID) · center(0) · right(负 ID) geometry 支持 line / arc / spiral / poly3 / paramPoly3 junction 与 controller 的引用链 junction id connection id controller id control / signal id road 通过 junction 属性声明所属交叉口 laneLink 把 incoming lane 映射到 outgoing lane

五、案例:一个典型交叉口文件如何组织

假设要制作一个十字交叉口。首先在 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 从静态地图到动态场景的完整组织逻辑。