自动驾驶---打造自动驾驶系统之导航模块开发(三)
各位读者朋友,大家好。本次打造的自动驾驶仿真系统,涉及感知,预测,规控等多个模块(以规控算法为主,包括Polynomial预测,MCTS决策算法,通行走廊Corridor构建,QP/CILQR轨迹生成求解器,LQR+PID的控制器等 ),同时也支持其它相关规控算法的扩展(部署&开发自身感兴趣的算法),非常便捷。笔者在该系列中开发的规控算法主要依据专栏《自动驾驶Planning决策规划** 》中的章节逐步搭建,后续实践系列涉及的博客包括但不局限于以下内容**:
《自动驾驶---构建自动驾驶系统的基础框架(一)》---已发布
《构建自动驾驶系统的定位模块(二)》现发布
《自动驾驶---打造自动驾驶系统之导航模块开发(三)》
《自动驾驶---打造自动驾驶系统之参考线平滑开发(四)》
《自动驾驶---打造自动驾驶系统之感知环境开发(五)》
《自动驾驶---打造自动驾驶系统之预测模块开发(六)》
《自动驾驶---打造自动驾驶系统之决策模块开发(七)》
《自动驾驶---打造自动驾驶系统之轨迹生成模块开发(八) 》
《自动驾驶---打造自动驾驶系统之控制模块开发(九)》
最终呈现的静态效果(无法直接贴视频)如下:

1 系统环境
笔者的环境是:Ubuntu 18.04 + ROS Melodic,当然18.04以上的环境也支持。
在本节中介绍了各类辅助库的安装方法,请您参考上一篇博客中的相关内容:《自动驾驶---打造自动驾驶系统之环境准备(一)》。如有遗漏之处,请及时补充完善。
当然,
若各位读者有意愿了解或在操作过程中遇到任何问题,
如需进一步咨询,请随时通过私信联系!
诚然,
若各位读者有意愿了解或在操作过程中遇到任何问题,
如需进一步咨询,请随时通过私信联系!
2 OpenStreeMap
我们缺少获取高级地图数据的途径(因高昂的费用),例如像谷歌、百度、高德这样的平台的地图数据。依赖于地图车道信息进行路径规划的相关内容可以参考之前的一篇文章《自动驾驶---运动学规划之参考线生成》。个人开发者由于缺乏必要的资源,因此选择开源的地图——OpenStreetMap。经过调研发现,在仿真系统中使用OpenStreetMap已经足够满足需求。
很多同学对此可能不够熟悉,笔者下面对OSM作个简单介绍:
OpenStreetMap(OSM) 是一个全球范围内的开放获取地图数据资源项目, 其主要目标是创建并提供免费且可编辑的地图数据库. 该系统由全球志愿者共同维护, 类比于地图领域的维基百科项目.
2.1 OpenStreetMap 概述
(1)背景
创立时间 :2004 年由 Steve Coast 发起。
目标 :提供一个免费、开放、可编辑的全球地图数据库。
特点 :
开放数据 :所有地图数据以开放许可(ODbL)发布,允许自由使用、修改和共享。
社区驱动 :全球志愿者共同贡献和维护数据。
多样化应用 :从导航到地理分析,适用于多种场景。
(2)核心概念
节点(Node) :地图上的点,用于表示位置(如路灯、商店)。
路径(Way) :由多个节点组成的线,用于表示道路、河流等。
关系(Relation) :描述节点和路径之间的关系(如公交路线、建筑物轮廓)。
标签(Tag) :描述地理对象的属性(如道路类型、建筑物名称)。
2.2 OpenStreetMap 数据
(1)数据来源
志愿者贡献 :用户通过 GPS 设备、卫星影像或本地知识添加和编辑数据。
引入开放数据:将来自官方或第三方发布的公共数据(包括道路网络和行政区划信息)进行汇集。
(2)数据格式
原始数据 :以 XML 格式存储,包含节点、路径、关系和标签。
数据库 :使用 PostgreSQL 和 PostGIS 存储和管理数据。
导出格式 :支持多种格式(如 .osm、.pbf、GeoJSON)。
(3)数据质量
实时更新 :志愿者可以随时编辑和更新数据。
区域差异 :发达地区数据较详细,偏远地区可能不完整。
验证机制 :通过社区审核和工具(如 OSMCha)确保数据准确性。
2.3 OpenStreetMap 工具
(1)编辑工具
iD 编辑器 :基于浏览器的可视化编辑器,适合初学者。
JOSM :功能强大的桌面编辑器,适合高级用户。
Vespucci :Android 平台的移动编辑器。
(2)数据下载工具
Overpass Turbo :基于 Overpass API 的交互式查询工具。
Geofabrik :提供按国家或地区分片下载 OSM 数据。
BBBiKE :支持大范围的城市级 OSM 数据提取服务。(支持任意选择矩形区域内完整的地理数据,并操作便捷)
(3)可视化工具
Mapnik :用于渲染 OSM 地图的默认样式。
OpenLayers :开源的 Web 地图库,支持 OSM 数据。
Leaflet :轻量级的 Web 地图库,常用于展示 OSM 地图。
3 地图格式转换
此外还有一个OSM数据格式转换工具OSMTools 主要用于处理地图文件的格式转换
由于BBBBike官方不直接支持以.osm格式存在的文件,在使用该平台时必须将.pbf文件转换为.osm格式才能继续操作


(1)源码安装
git clone https://github.com/ramunasd/osmctools.git
cd osmctools
autoreconf --install
./configure
make install
(2)apt安装
sudo apt install osmctools
输入如下指令,可以看到相关信息输出。
osmctools

osmconvert map.osm --out-statistics
可以看到,map中包括最大/最小经纬度,节点数,路的数量等道路信息。

常用的格式转换命令:
osmconvert map.osm -o=map.pbf
osmconvert map.pbf -o=map.osm
4 Routing开发
整个Routing模块大概分为以下几个模块:
- roscpp核心节点:负责接收和发送消息,并解析目标点数据以生成规划基础。
- 解析器模块:主要处理包含节点、道路等地图信息的XML文件,并据此构建完善的路网架构。
- pugixml.cpp源代码库(无改动版本):负责将OSM格式的地图数据转换为XML格式的标准组件。
- 路网模型构建器:专注于建立完善的路网模型架构,并描述各节点间相互连接关系的管理逻辑。
- 导航路径计算模块:A*算法实现核心模块, 负责计算最优导航路径的关键逻辑模块
4.1 osm数据读取
std::string osm_data_file = "";
osm_data_file = "./map.osm";
std::vector<std::byte> osm_data;
if( osm_data.empty() && !osm_data_file.empty() ) {
AINFO << "Reading data from the file: " << osm_data_file << std::endl;
auto data = ReadFile(osm_data_file);
if( !data )
AINFO << "Failed to read." << std::endl;
else
osm_data = std::move(*data);
}
if(osm_data.empty()){
AINFO << "Reading data error!!!" << std::endl;
return -1;
}
// Build routing Model.
RoutingModel model{osm_data};
void Model::LoadXmlData(const std::vector<std::byte> &xml) {
using namespace pugi;
xml_document doc;
if( !doc.load_buffer(xml.data(), xml.size()) )
AERROR << "Failed to parse the xml file!";
if( auto bounds = doc.select_nodes("/osm/bounds"); !bounds.empty() ) {
auto node = bounds.first().node();
m_MinLat = atof(node.attribute("minlat").as_string());
m_MaxLat = atof(node.attribute("maxlat").as_string());
m_MinLon = atof(node.attribute("minlon").as_string());
m_MaxLon = atof(node.attribute("maxlon").as_string());
} else {
AERROR << "Map's bounds are not defined!";
}
std::unordered_map<std::string, int> node_id_to_num;
for( const auto &node: doc.select_nodes("/osm/node") ) {
node_id_to_num[node.node().attribute("id").as_string()] = (int)m_Nodes.size();
m_Nodes.emplace_back();
m_Nodes.back().y = atof(node.node().attribute("lat").as_string());
m_Nodes.back().x = atof(node.node().attribute("lon").as_string());
}
}
4.2 构建路网
void RoutingModel::CreateNodeToRoadHashmap() {
for (const Model::Road &road : Roads()) {
if (road.type != Model::Road::Type::Footway) {
for (int node_idx : Ways()[road.way].nodes) {
if (node_to_road.find(node_idx) == node_to_road.end()) {
node_to_road[node_idx] = std::vector<const Model::Road *> ();
}
node_to_road[node_idx].push_back(&road);
}
}
}
}
4.3 A*算法搜索
void Route::AStarSearch() {
/** * Implementation of the A* Search algorithm.
*/
RoutingModel::Node *current_node = nullptr;
start_node->visited = true;
open_list.push_back(start_node);
while(open_list.size() > 0) {
current_node = NextNode();
float distance_current_node_and_end_node = current_node->distance(*end_node);
if(std::fabs(distance_current_node_and_end_node) < 0.001) {
// The final path is stored in the m_Model.path attribute
m_Model.path = ConstructFinalPath(current_node);
return;
} else {
// Otherwise, we have to repeat the loop adding the neighbors of that node until we find
// the end node.
AddNeighbors(current_node);
}
}
}
4.4 图坐标和gps坐标对齐
笛卡尔坐标系和地理坐標系之間的轉換方式有多種多樣性,在確保前后一致性的情況下即可。采用的是墨卡托投影。
// for coordinate transform
const auto pi = 3.14159265358979323846264338327950288;
const auto deg_to_rad = 2. * pi / 360.;
const auto earth_radius = 6378137.;
const auto latToym = [&](double lat) { return log(tan(lat * deg_to_rad / 2 + pi/4)) / 2 * earth_radius; };
const auto lonToxm = [&](double lon) { return lon * deg_to_rad / 2 * earth_radius; };
const auto ymTolat = [&](double ym) { return (2 * std::atan(std::exp(ym / earth_radius)) - pi / 2) * 180.0 / pi; };
const auto xmTolon = [&](double xm) { return (2 * xm / (earth_radius * deg_to_rad)); };
5 示例
在上一节中,我们得到了车辆的定位坐标,如下所示:

该矩形地图的经、纬度范围明确确定,并且其中各节点均带有经、纬度数据。
假设车辆起始位置位于地图中的某一个特定节点上。
输入目标经、纬度值。
现有两种解决方案可供采用:
(1)直接使用经纬度匹配节点,最终得到的节点统一转到odom坐标系下;
通过将输入的经纬度数据转换为地图坐标系,并对该系统进行数据检索操作后,在地图坐标系的基础上完成数据输出并切换至odom坐标系统
最终可以得到routing的点列信息,效果如下所示:

