赞
踩
PncMap是Planning and Control Map的缩写,即用于规划控制使用的地图
pnc map的作用就是将routing的结果,进行一系列的操作处理,找到一个局部的(固定长度),可以供规划使用的,车辆能够正常驶入的局部地图(RouteSegments),后续用于生成规划中的ReferenceLine结构。
pnc map主要是将routing发送的轨迹规划转变为能够生成reference的中间数据
pnc_map封装在reference_line_provider下,主要供其在由routing结果到ReferenceLine的过程中做前期准备。
PncMap类负责对接Routing搜索结果的更新。它会根据车辆当前位置,提供车辆周边的RouteSegments信息供ReferenceLineProvider生成ReferenceLine。
“车辆周边”与车辆的纵向和横向相关,具体如下:
// modules/map/pnc_map/pnc_map.cc
DEFINE_double(
look_backward_distance, 30,
"look backward this distance when creating reference line from routing");
DEFINE_double(look_forward_short_distance, 180,
"short look forward this distance when creating reference line "
"from routing when ADC is slow");
DEFINE_double(
look_forward_long_distance, 250,
"look forward this distance when creating reference line from routing");
PncMap中的下面这个方法用来接收车辆的状态更新。当然,这其中很重要的就是位置状态。
bool PncMap::UpdateVehicleState(const VehicleState &vehicle_state)
此时PncMap会计算车辆已经经过了哪些Routing的途经点,以及下一个目标点等信息。
在每一个Planning计算循环中,ReferenceLineProvider都会传入车辆的VehicleState给PncMap,并从PncMap获取车辆周围的通路(RouteSegments)。
PncMap中的下面这个方法返回Routing途经点还有哪些没有达到:
std::vector<routing::LaneWaypoint> PncMap::FutureRouteWaypoints() const
modules/map/pnc_map/path.h
// 为路由模块的输入,车辆在全局地图中必须经过的点
// PointENU为东北天坐标系,apollo中被简化为二维横轴墨卡托坐标系
message LaneWaypoint {
optional string id = 1;
optional double s = 2;
optional apollo.common.PointENU pose = 3;
// When the developer selects a point on the dreamview route editing
// the direction can be specified by dragging the mouse
// dreamview calculates the heading based on this to support construct lane way point with heading
optional double heading = 4;
}
message RoutingRequest {
...
repeated LaneWaypoint waypoint = 2;
}
RoutingRequest为dreamview上发出的routing请求点,主要关注LaneWaypoint这个数据类型,表示routing算法导航出的路径需要经过这些LaneWaypoint点。
RoutingResponse表示的是routing的搜索结果信息,主要由多段RoadSegment组成。
RoadSegment:
pnc map中的passage:
hd map中的RouteSegments:
// 其中id存储的为全局唯一的车道id,即定义在map_lane.proto中的Lane的id
message LaneSegment {
optional string id = 1;
optional double start_s = 2;
optional double end_s = 3;
}
路由模块将道路结构分解为多个RoadSegment,每个RoadSegment又包括多个通道:Passage,每个Passage又由多个LaneSegment组成。
如图所示,为双车道中的一个例子。
这样做有什么好处呢?
RoadSegment,Passage,LaneSegment图解如下:
/**
* @brief class RouteSegments
*
* This class is a representation of the Passage type in routing.proto.
* It is extended from a passage region, but keeps some properties of the
* passage, such as the last end LaneWaypoint of the original passage region
* (route_end_waypoint), whether the passage can lead to another passage in
* routing (can_exit_).
* This class contains the original data that can be used to generate
* hdmap::Path.
**/
class RouteSegments : public std::vector<LaneSegment>
pnc_map封装在reference_line_provider下,主要供其在由routing结果到ReferenceLine的过程中做前期准备,而实现这一功能的主要步骤分为两步:
bool PncMap::GetRouteSegments(const VehicleState &vehicle_state,
const double backward_length,
const double forward_length,
std::list<RouteSegments> *const route_segments)
在这之后,reference_line_provider 里会将RouteSegments转换为hdmap::Path结构,再转成ReferenceLine结构,继而进行平滑
主要函数为:
bool PncMap::UpdateRoutingResponse(const routing::RoutingResponse &routing)
可以看到,其输入为RoutingResponse成员,即routing模块的输出。
其主要功能为初始化下面加粗的三个pnc_map类成员:
具体解析如下:
首先,每当有新的routing结果时,都需要用最新的RoutingResponse对PncMap里的数据进行更新。
从上述代码可以响应结果剥离工作比较简单,就是对这条完整路径进行RoadSegment,Passage,LaneSegment的存储。举个例子:
图中左上角蓝色方框为一个road的示例,包含road_index,passage_index,lane_index,左下角在生成的RoutingResponse上进行车道线编号。
在举个例子:
在UpdateRoutingResponse中,第二步是将routing中的request点信息转化后的索引数据中进行匹配,找到具体的LaneSegment信息:
举个例子,上图中,该次查询的查询点waypoint一共两个,起点(红色星星)和终点(蓝色星星)。这一步需要对这两个waypoint进行处理,计算这两个waypoint分别在上述的那些LaneSegment中,准确的说是上述all_lane_ids_中每个LaneSegment包含有哪些waypoint。
我们来看下WithinLaneSegment是怎么实现的。
/// file in apollo/modules/map/pnc_map/route_segments.cc
bool RouteSegments::WithinLaneSegment(const LaneSegment &lane_segment, const routing::LaneWaypoint &waypoint) {
return lane_segment.lane && lane_segment.lane->id().id() == waypoint.id() &&
lane_segment.start_s - kSegmentationEpsilon <= waypoint.s() &&
lane_segment.end_s + kSegmentationEpsilon >= waypoint.s();
}
从代码中可以看到,waypoint在lane_segment中需要满足条件:
最终生成的数据在routing_waypoint_index_
该函数为pnc_map的第二个步骤,也是其主要功能,即根据第一步骤转换的routing信息及自车位置来构造route_segments,这个我们从该函数的入参也可以很清晰的看出来。
简单来说,它的作用就是根据车辆当前位置生成短期通道
/// file in apollo/master/modules/map/pnc_map/pnc_map.cc
PncMap::GetRouteSegments(const VehicleState &vehicle_state,
const double backward_length,
const double forward_length,
std::list<RouteSegments> *const route_segments)
关于输出RouteSegments:
简单来说,我们在这一步主要是生成连接起来的可通行的Passage,从而为生成参考线做准备。
这里的更新并不是更新车辆的位置,速度等,而是根据传入的车辆状态信息,更新当前所在的LaneSegment位置以及下一必经过点的waypoint位置信息
这部分由UpdateVehicleState函数完成
UpdateVehicleState函数的签名如下:
PncMap::UpdateVehicleState(const VehicleState &vehicle_state)
这个函数完成的工作就是计算上图中的红色部分:
其主要逻辑根据自车的全局坐标系以及航向角,找到自车在哪一个LaneSegment中,或者说投影后距离那个LaneSegment最近,输出自车所在LaneSegment对应的index,并计算离自车最近的下一个必经点LaneWaypoint所在的LaneSegment对应的index。
我们来细细分析一下:
(1)根据自车状态,从routing中查找最近的道路点adc_waypoint_
/// file in apollo/modules/map/pnc_map/pnc_map.cc
bool PncMap::UpdateVehicleState(const VehicleState &vehicle_state) {
// Step 1. 计算当前车辆在对应车道上的投影adc_waypoint_
adc_state_ = vehicle_state;
if (!GetNearestPointFromRouting(vehicle_state, &adc_waypoint_)) {
return false;
}
...
}
GetNearestPointFromRouting的计算流程大致如下:
(2)计算索引adc_route_index_
(3)计算next_routing_waypoint_index_
/// file in apollo/modules/map/pnc_map/pnc_map.cc void PncMap::UpdateNextRoutingWaypointIndex(int cur_index) { // 情况1. 车道倒车,后向查找,下一个查询点waypoint对应的索引查找 // search backwards when the car is driven backward on the route. while (next_routing_waypoint_index_ != 0 && next_routing_waypoint_index_ < routing_waypoint_index_.size() && routing_waypoint_index_[next_routing_waypoint_index_].index > cur_index) { --next_routing_waypoint_index_; } while (next_routing_waypoint_index_ != 0 && next_routing_waypoint_index_ < routing_waypoint_index_.size() && routing_waypoint_index_[next_routing_waypoint_index_].index == cur_index && adc_waypoint_.s < routing_waypoint_index_[next_routing_waypoint_index_].waypoint.s) { --next_routing_waypoint_index_; } // 情况2. 车道前进,前向查找,下一个查询点waypoint对应的索引查找 // search forwards // 第1步,查找最近包含有waypoint的LaneSegment while (next_routing_waypoint_index_ < routing_waypoint_index_.size() && routing_waypoint_index_[next_routing_waypoint_index_].index < cur_index) { ++next_routing_waypoint_index_; } // 第2步,查找下一个最近的waypoint对应的索引 while (next_routing_waypoint_index_ < routing_waypoint_index_.size() && cur_index == routing_waypoint_index_[next_routing_waypoint_index_].index && adc_waypoint_.s >= routing_waypoint_index_[next_routing_waypoint_index_].waypoint.s) { ++next_routing_waypoint_index_; } if (next_routing_waypoint_index_ >= routing_waypoint_index_.size()) { next_routing_waypoint_index_ = routing_waypoint_index_.size() - 1; } }
举例1
现在有以下这么个情况:
|-wp0---------------------vp--------|-------wp1-----wp2---------------wp3-----|-------------------------wp4-----|
\ LaneSegment k /\ LaneSegment k+1 /\ LaneSegment k+2 /
其中vp为当前车辆在LaneSegment中的投影,wp0, wp1, wp2, wp3分别是路径查询的4个waypoint(必须经过的点。现在我们要查询车辆下一个最近的必经点waypoint,那么结果应该是wp1!假设车辆当前是前进状态,cur_index值为k,那么:
可以判断,函数执行前,next_routing_waypoint_index_值为0(查询wp0时得到)
第1步,查找最近包含有waypoint的LaneSegment,最终得到的结果next_routing_waypoint_index_仍然为0,因为routing_waypoint_index_[next_routing_waypoint_index_].index就是wp0.index,值为k,与cur_index相等。
第2步,查找下一个最近的waypoint对应的索引,最终的结果next_routing_waypoint_index_变为1,因为adc_waypoint_.s >= wp0.s。
举例2
现在有以下这么个情况(车辆行驶到了LaneSegment k+2,并且已经经过了wp1,wp2和wp3):
|-wp0-------------------------------|----wp1--------wp2---------------wp3-----|-----vp------------------wp4-----|
\ aneSegment k /\ LaneSegment k+1 /\ LaneSegment k+2 /
其中vp为当前车辆在LaneSegment中的投影,wp0, wp1, wp2, wp3分别是路径查询的4个waypoint(必须经过的点),其中wp0,wp1,wp2和wp3已经经过了,wp4待车辆经过。现在我们要查询车辆下一个最近的必经点waypoint,那么结果应该是wp4!假设车辆当前是前进状态,cur_index值为k+2,那么:
可以判断,函数执行前,next_routing_waypoint_index_值为3(查询wp3时得到)
第1步,查找最近包含有waypoint的LaneSegment,最终得到的结果next_routing_waypoint_index_变为4,因为routing_waypoint_index_[next_routing_waypoint_index_].index就是wp3.index,值为k+1,wp3.index < cur_index。
第2步,查找下一个最近的waypoint对应的索引,next_routing_waypoint_index_仍然为4,因为adc_waypoint_.s < wp4.s
(4)到达目的地标志
/// file in apollo/modules/map/pnc_map/pnc_map.cc
bool PncMap::UpdateVehicleState(const VehicleState &vehicle_state) {
// Step 1. 计算当前车辆在对应车道上的投影adc_waypoint_
...
// Step 2. 计算车辆投影点所在LaneSegment在route_indices_`的索引`dc_route_index_`
int last_index = GetWaypointIndex(routing_waypoint_index_.back().waypoint);
if (next_routing_waypoint_index_ == routing_waypoint_index_.size() - 1 ||
(!stop_for_destination_ && last_index == routing_waypoint_index_.back().index)) {
stop_for_destination_ = true;
}
}
如果next_routing_waypoint_index_
是终点的索引,表示已经到达目的地。
根据上一步得到的本车所处index信息,计算出当前车辆在轨迹规划中可能经过的所有相邻车道(passage)信息,作为将要生成的reference_line原始信息:
计算相邻可通行区域信息调用的是GetNeighborPassages,其入参:
这里我们简单地总结一下获取邻接可驶入通道的原则:
if (source_passage.change_lane_type() == routing::FORWARD) {
return result;
}
if (source_passage.can_exit()) { // no need to change lane
return result;
}
RouteSegments source_segments;
if (!PassageToSegments(source_passage, &source_segments)) {
return result;
}
if (next_routing_waypoint_index_ < routing_waypoint_index_.size() &&
source_segments.IsWaypointOnSegment(routing_waypoint_index_[next_routing_waypoint_index_].waypoint)) {
return result;
}
std::unordered_set<std::string> neighbor_lanes; if (source_passage.change_lane_type() == routing::LEFT) { // 当前passage是左转通道 for (const auto &segment : source_segments) { // 查询当前Passage中每个LaneSegment所在车道的邻接左车道 for (const auto &left_id : segment.lane->lane().left_neighbor_forward_lane_id()) { neighbor_lanes.insert(left_id.id()); } } } else if (source_passage.change_lane_type() == routing::RIGHT) { 当前passage是右转通道 for (const auto &segment : source_segments) { // 查询当前Passage中每个LaneSegment所在车道的邻接右车道 for (const auto &right_id : segment.lane->lane().right_neighbor_forward_lane_id()) { neighbor_lanes.insert(right_id.id()); } } } for (int i = 0; i < road.passage_size(); ++i) { if (i == start_passage) { continue; } const auto &target_passage = road.passage(i); for (const auto &segment : target_passage.segment()) { if (neighbor_lanes.count(segment.id())) { // 查询当前RoadSegment中所有Passage::LaneSegment的所属车道,有交集就添加到结果中 result.emplace_back(i); break; } } } return result; }
举例来说,蓝色车为自车,则该函数输出Passage1, 2。
若不为双车道,例如右边有Passage3,Passage4,则输出为Passage1,2,3,4。
这里需要关注一下PassageToSegments。从下面可以看出,passage表达的数据内容和RouteSegments其实是类似的:RouteSegments就是将Passage中一条条lane,存进其数组中,病记录起始终止的s值
bool PncMap::PassageToSegments(routing::Passage passage,
RouteSegments *segments) const {
CHECK_NOTNULL(segments);
segments->clear();
for (const auto &lane : passage.segment()) {
auto lane_ptr = hdmap_->GetLaneById(hdmap::MakeMapId(lane.id()));
if (!lane_ptr) {
AERROR << "Failed to find lane: " << lane.id();
return false;
}
segments->emplace_back(lane_ptr, std::max(0.0, lane.start_s()),
std::min(lane_ptr->total_length(), lane.end_s()));
}
return !segments->empty();
}
遍历上一步输出的所有邻接车道,
// Step 1. update vehicle state
// Step 2. compute neighbor passages
auto drive_passages = GetNeighborPassages(road, passage_index);
// Step 3. compute route segment
for (const int index : drive_passages) {
...
}
Passage生成对应的RouteSegments分为如下步骤,分别为:
将当前车辆的坐标投影到Passage
检查Passage是否可驶入:就是检查临界车道是否可以驶入
生成RouteSegmens:使用上面合法的passage生成道路段,并且使用backward_length和backward_length前后扩展道路段。
总的如下:
至此,根据每一次车辆位置状态更新值,都可以获得当前车辆位置依据发送RoutingResponse计算得到用于规划控制使用的前后预瞄距离范围内的车道索引,用于生成reference_line。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。