OpenDrive标准定义的道路网络数据包含道路参考线、车道属性、 junction连接关系等多个层级的信息,解析时需要按照XML的层级结构逐层读取对应节点内容。下面先介绍解析前的环境准备和核心思路。
解析环境准备
解析OpenDrive的XML数据可以选择多种C++ XML解析库,这里选用轻量且易用的tinyxml2库,首先需要完成库的安装和项目配置。
安装tinyxml2库
如果是Ubuntu系统,可以直接通过包管理器安装:
sudo apt-get install libtinyxml2-dev
如果是其他系统,也可以从tinyxml2的官方仓库下载源码,编译后引入项目即可。
项目配置
在C++项目中引入tinyxml2头文件,链接对应的库文件,示例CMake配置如下:
cmake_minimum_required(VERSION 3.10) project(open_drive_parser) set(CMAKE_CXX_STANDARD 11) # 引入tinyxml2 find_package(TinyXML2 REQUIRED) add_executable(parser main.cpp) target_link_libraries(parser TinyXML2::TinyXML2)
OpenDrive XML结构概览
OpenDrive的根节点是<OpenDRIVE>,下面主要包含<road>、<junction>、<controller>等核心子节点,其中<road>节点是道路数据的核心载体,每个<road>节点包含道路的ID、长度、参考线、车道段等信息。
一个简化的OpenDrive XML结构示例如下:
<OpenDRIVE>
<road id="1" name="road_1" length="100.0">
<link/>
<planView>
<geometry s="0" x="0" y="0" hdg="0" length="100">
<line/>
</geometry>
<planView>
<lanes>
<laneSection s="0">
<left/>
<center>
<lane id="0" type="none" level="false"/>
</center>
<right>
<lane id="1" type="driving" level="false"/>
</right>
</laneSection>
</lanes>
</road>
</OpenDRIVE>
核心解析代码实现
加载OpenDrive文件
首先使用tinyxml2的XMLDocument类加载XML文件,检查文件是否加载成功:
#include <iostream>
#include <tinyxml2.h>
using namespace tinyxml2;
using namespace std;
// 加载OpenDrive文件
bool loadOpenDriveFile(const char* filePath, XMLDocument& doc) {
XMLError error = doc.LoadFile(filePath);
if (error != XML_SUCCESS) {
cout << "加载文件失败,错误码:" << error << endl;
return false;
}
return true;
}
解析根节点与道路列表
加载成功后,先获取根节点<OpenDRIVE>,再遍历所有的<road>子节点:
// 解析所有道路节点
void parseRoads(XMLDocument& doc) {
// 获取根节点
XMLElement* root = doc.FirstChildElement("OpenDRIVE");
if (root == nullptr) {
cout << "未找到OpenDRIVE根节点" << endl;
return;
}
// 遍历所有road节点
XMLElement* roadNode = root->FirstChildElement("road");
while (roadNode != nullptr) {
// 获取道路基础属性
const char* roadId = roadNode->Attribute("id");
const char* roadName = roadNode->Attribute("name");
double roadLength = 0.0;
roadNode->QueryDoubleAttribute("length", &roadLength);
cout << "解析到道路 ID:" << roadId << " 名称:" << roadName << " 长度:" << roadLength << endl;
// 解析当前道路的参考线
parseRoadPlanView(roadNode);
// 解析当前道路的车道信息
parseRoadLanes(roadNode);
// 移动到下一个road节点
roadNode = roadNode->NextSiblingElement("road");
}
}
解析道路参考线数据
道路的参考线存储在<planView>节点的<geometry>子节点中,每个<geometry>代表一段参考线几何:
// 解析道路参考线
void parseRoadPlanView(XMLElement* roadNode) {
XMLElement* planViewNode = roadNode->FirstChildElement("planView");
if (planViewNode == nullptr) {
return;
}
// 遍历所有geometry节点
XMLElement* geometryNode = planViewNode->FirstChildElement("geometry");
while (geometryNode != nullptr) {
double s = 0.0, x = 0.0, y = 0.0, hdg = 0.0, length = 0.0;
geometryNode->QueryDoubleAttribute("s", &s);
geometryNode->QueryDoubleAttribute("x", &x);
geometryNode->QueryDoubleAttribute("y", &y);
geometryNode->QueryDoubleAttribute("hdg", &hdg);
geometryNode->QueryDoubleAttribute("length", &length);
cout << "参考线片段:s=" << s << " 起点(" << x << "," << y << ") 航向=" << hdg << " 长度=" << length << endl;
// 判断几何类型,这里示例直线类型
XMLElement* lineNode = geometryNode->FirstChildElement("line");
if (lineNode != nullptr) {
cout << "该段参考线为直线类型" << endl;
}
geometryNode = geometryNode->NextSiblingElement("geometry");
}
}
解析车道信息
车道信息存储在<lanes>节点的<laneSection>中,每个<laneSection>代表一段车道section,下面包含左、中、右三个车道组:
// 解析道路车道信息
void parseRoadLanes(XMLElement* roadNode) {
XMLElement* lanesNode = roadNode->FirstChildElement("lanes");
if (lanesNode == nullptr) {
return;
}
// 遍历车道段
XMLElement* laneSectionNode = lanesNode->FirstChildElement("laneSection");
while (laneSectionNode != nullptr) {
double sectionS = 0.0;
laneSectionNode->QueryDoubleAttribute("s", §ionS);
cout << "车道段起始s值:" << sectionS << endl;
// 解析右侧车道
XMLElement* rightNode = laneSectionNode->FirstChildElement("right");
if (rightNode != nullptr) {
XMLElement* laneNode = rightNode->FirstChildElement("lane");
while (laneNode != nullptr) {
int laneId = 0;
const char* laneType = laneNode->Attribute("type");
laneNode->QueryIntAttribute("id", &laneId);
cout << "右侧车道 ID:" << laneId << " 类型:" << laneType << endl;
laneNode = laneNode->NextSiblingElement("lane");
}
}
laneSectionNode = laneSectionNode->NextSiblingElement("laneSection");
}
}
主函数调用示例
将上述函数组合起来,即可完成基础的OpenDrive数据解析:
int main() {
XMLDocument doc;
// 替换为你的OpenDrive文件路径
const char* filePath = "test.xodr";
if (!loadOpenDriveFile(filePath, doc)) {
return -1;
}
parseRoads(doc);
return 0;
}
注意事项
- OpenDrive的XML节点属性类型多样,解析数值属性时优先使用
QueryDoubleAttribute、QueryIntAttribute等方法,避免直接转换导致的错误。 - 实际OpenDrive文件可能包含更复杂的节点,比如junction连接、信号、交通标志等,解析时可以按照上述逐层遍历的思路扩展对应节点的解析逻辑。
- 如果OpenDrive文件较大,解析时可以考虑按需加载对应的道路节点,避免一次性加载全部数据占用过多内存。