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文件,提取车道线数据。