3、OpenDRIVE格式入门:XML基础回顾、OpenDRIVE文件结构、道路(Road)元素解析

好,咱们正式开始啃OpenDRIVE这块硬骨头。

说实话,我第一次接触OpenDRIVE的时候,看着那一堆XML标签,头都大了。但干高精地图这行,这东西绕不开。它是目前行业里最通用的路网描述标准,ASAM组织定的。你想想看,如果每家自动驾驶公司都用自己的地图格式,那高精地图的生态就彻底完蛋了。OpenDRIVE就是那个「通用语言」。

3.1 XML基础回顾——别嫌啰嗦,这是基本功

OpenDRIVE本质上是XML。所以你得先懂点XML的规矩。

XML就三个核心要素:标签属性层级

  • 标签:用尖括号包起来,比如 <road><lane>。有开标签就得有关闭标签 </road>
  • 属性:写在开标签里,比如 <road name="测试道路">,name就是属性。
  • 层级:标签套标签,父节点包着子节点。这决定了数据的树状结构。

我个人的习惯:拿到一个OpenDRIVE文件,第一件事不是看内容,而是先检查XML结构是否完整。缺个关闭标签,整个文件就废了。我在项目里遇到过,一个合作方给的文件,就因为少了个斜杠,解析器直接报错,排查了整整一下午。

来看个最简单的XML片段:

<?xml version="1.0" encoding="UTF-8"?>
<OpenDRIVE>
    <header revMajor="1" revMinor="4" />
    <road name="测试道路" length="100.0">
        <lanes>
            <lane id="0" type="driving" />
        </lanes>
    </road>
</OpenDRIVE>

看到了吗?<OpenDRIVE>是根节点,下面挂着<header><road><road>下面又挂着<lanes>。这就是层级关系。

避坑指南:我曾经在解析时忽略了自闭合标签。比如 <header /> 这种,它没有子节点,但很多新手会误以为它没闭合。实际上,那个斜杠就是闭合的意思。别搞混了。

3.2 OpenDRIVE文件结构——从根到叶

一个标准的OpenDRIVE文件,结构是固定的。说白了,就是一棵树。根节点永远是 <OpenDRIVE>

它的直接子节点包括:

节点名称 说明 出现次数
<header> 文件头,包含版本号、坐标系、日期等元信息 1次
<road> 道路元素,核心中的核心 1次或多次
<controller> 控制器,用于交通信号等 0次或多次
<junction> 路口连接关系 0次或多次
<junctionGroup> 路口组 0次或多次
<station> 站点信息 0次或多次

嗯,这里要注意。虽然节点很多,但咱们做车道线提取,90%的时间都在跟 <road> 打交道。其他的像 <controller><station>,说实话,我做了这么多年项目,用到的时候屈指可数。

再看一下 <header> 里有什么:

<header revMajor="1" revMinor="4" name="我的地图" version="1.0" date="2024-01-01" north="0.0" south="0.0" east="0.0" west="0.0" vendor="蓝海科技">
    <geoReference>+proj=utm +zone=50 +ellps=WGS84 +datum=WGS84 +units=m +no_defs</geoReference>
</header>

这里有个关键点:<geoReference>。它定义了地图的投影坐标系。我见过太多人忽略这个,结果坐标转换全错了。你想想看,如果坐标系不对,提取出来的车道线位置偏差几十米,那还玩什么?

警告:解析OpenDRIVE时,务必先读取 <header> 中的版本号和坐标系信息。版本号决定了后续解析规则。比如1.4版本和1.6版本,在车道线描述上就有细微差别。我曾经因为没注意版本号,用1.4的解析器去读1.6的文件,结果车道线类型全识别错了。

3.3 道路(Road)元素解析——核心中的核心

好,终于到重点了。<road> 元素是OpenDRIVE的灵魂。一条道路,在OpenDRIVE里就是一个 <road> 节点。

它的基本属性如下:

属性名 类型 说明
id 整数 道路唯一标识,必须唯一
name 字符串 道路名称,可重复
length 浮点数 道路长度,单位米
junction 整数 关联路口ID,-1表示非路口路段

一个 <road> 节点内部,又包含多个子节点。我列一下最重要的几个:

  • <link>:道路连接关系,前驱后继是谁。
  • <planView>:道路的几何形状。这是提取车道线的基础。
  • <elevationProfile>:道路高程变化。
  • <lateralProfile>:道路横向坡度。
  • <lanes>:车道信息。车道线提取的核心数据就在这里。
  • <objects>:道路上的物体,比如交通标志、杆子。
  • <signals>:交通信号灯。

我个人觉得,<planView><lanes> 是最关键的。一个描述道路的「骨架」,一个描述车道的「血肉」。

来看一个完整的道路示例:

<road id="1" name="朝阳路" length="500.0" junction="-1">
    <link>
        <predecessor elementType="road" elementId="2" contactPoint="end"/>
        <successor elementType="road" elementId="3" contactPoint="start"/>
    </link>
    <planView>
        <geometry s="0.0" x="0.0" y="0.0" hdg="0.0" length="500.0">
            <line/>
        </geometry>
    </planView>
    <lanes>
        <laneSection s="0.0">
            <left>
                <lane id="-1" type="driving" level="false">
                    <width sOffset="0.0" a="3.5" b="0.0" c="0.0" d="0.0"/>
                    <roadMark sOffset="0.0" type="solid" weight="standard" color="white" width="0.15"/>
                </lane>
            </left>
            <center>
                <lane id="0" type="none" level="false">
                    <roadMark sOffset="0.0" type="broken" weight="standard" color="white" width="0.15"/>
                </lane>
            </center>
            <right>
                <lane id="1" type="driving" level="false">
                    <width sOffset="0.0" a="3.5" b="0.0" c="0.0" d="0.0"/>
                    <roadMark sOffset="0.0" type="solid" weight="standard" color="white" width="0.15"/>
                </lane>
            </right>
        </laneSection>
    </lanes>
</road>

这段代码,我解释一下:

  • 道路ID是1,长度500米,不是路口路段(junction=-1)。
  • 它连接了道路2和道路3。
  • 几何形状是一条直线(<line/>),从(0,0)出发,方向0度。
  • 车道部分:有一个车道段(laneSection),包含左侧车道(id=-1)、中心线(id=0)、右侧车道(id=1)。
  • 每个车道都有宽度和车道线类型。比如左侧车道线是实线(solid),中心线是虚线(broken)。

关键点:车道ID的规则。中心线永远是0。左侧车道用负数(-1, -2...),右侧车道用正数(1, 2...)。这个规则是固定的,别记反了。我刚开始做的时候,就搞混了左右车道的正负号,结果提取出来的车道线全反了。

另外,<roadMark> 里的 type 属性,决定了车道线的样式。常见的类型有:

type值 含义 常见场景
none 无线 中心线有时用这个表示无标线
solid 实线 车道边界、禁止变道区域
broken 虚线 允许变道的车道分隔线
solid solid 双实线 严格禁止变道
broken broken 双虚线 双向都可变道
solid broken 左实右虚 左侧禁止变道,右侧允许

嗯,这里要注意。双实线和左实右虚这种组合类型,在OpenDRIVE 1.4版本里是用空格分隔的字符串表示的。解析的时候别当成一个整体去匹配。

我的经验:在实际项目中,提取车道线时,我一般会先解析 <planView> 得到道路的几何参考线,然后结合 <lanes> 里的车道宽度和偏移量,计算出每条车道线的精确位置。说白了,就是沿着参考线,往左右两侧偏移。这个计算过程,我会在后面的章节详细讲。

最后,提一下 <laneSection>。一条道路可能包含多个车道段。比如一段路,前面是双向两车道,后面变成了双向四车道。这时候就会有多个 <laneSection>,每个段有自己的起始位置(s值)。

好了,这一章的内容就到这。XML基础、OpenDRIVE文件结构、道路元素解析,这三块是后续所有操作的地基。地基不稳,后面盖的楼再漂亮也得塌。下一章,咱们开始真正动手,写代码解析OpenDRIVE文件,提取车道线数据。