Grid_map总结
Gridmap是由苏黎世理工自动驾驶实验室ASL发布的一款地图,Grid_map也是一种高程图,和Octomap等空间占有地图有着明显的区别。其底层的存储和运算使用Eigen。
1 地图的构造、存储和索引
Grid_map是由多层layer组成的一种复合地图,每一层地图可以表达不同的信息,不同的layer由各自的属性的cell组成,但是所有的cell对应的空间(2D)坐标是统一的。例如A层layer表达地图每个cell在空间中的高度,B层layer表达地图每个cell的颜色,C层layer表达每个cell的可遍历性等等。其多层特性,可以用图1来描述。
Grid_map不是Global map,其属于一种局部的可以移动的地图,在机器人运动的过程中,地图会随着机器人而发生整体移动,如地图是以机器人为中心,长宽各为 (L)的局部地图。如图3所示。
2 Gridmap中的迭代器
为了方便地图中cell单元的遍历,作者在Grid_map中加入了7个迭代器,涉及到的内容主要是计算机图形学方面的内容。
2.1 Grid map迭代器
Grid map迭代器是地图中最常见的一个迭代器,其迭代原理和大多数二维地图的迭代原理类似,比较简单。其原理可以描述为:
设地图大小(Cell个数)为 (m*n),其乘积也为所有的cell数目。设定一个起始的一维索引值 (l),在迭代的过程中 (l)不断的变换,且在 (0leqslant l leqslant m*n) 范围内有且对应一个cell单元。那么该cell单元在在索引坐标系下对应的二维索引坐标为 ((l/n,l \% n )) Grid map迭代器的迭代效果,如图4所示。
为了使算法适应不同直线,减少浮点运算,将算法的核心思想做进一步的改进之后,伪代码如下。如需进一步了解该算法可以参考上面的wiki。
function line(x0, x1, y0, y1)
boolean steep := abs(y1 - y0) > abs(x1 - x0)
if steep then
swap(x0, y0)
swap(x1, y1)
if x0 > x1 then
swap(x0, x1)
swap(y0, y1)
int deltax := x1 - x0
int deltay := abs(y1 - y0)
int error := deltax / 2
int ystep
int y := y0
if y0 < y1 then ystep := 1 else ystep := -1
for x from x0 to x1
if steep then plot(y,x) else plot(x,y)
error := error - deltay
if error < 0 then
y := y + ystep
error := error + deltax
2.5 Polygon(多边形) 迭代器
多边形迭代器是Gridmap迭代器中最复杂的一个迭代器。多边形迭代器在构造的过程中需要提前设定多边形的各个顶点,其中顶点的坐标以Position坐标系表示。
下面主要对多边形迭代器的迭代过程做总结。
2.5.1 获取起始索引和索引区
对起始索引点和索引区的获取还是老思路,即求取多边形 (n) 个顶点中,(x) 方向和 (y) 方向的最大值和最小值,然后将两个轴的最大值作为左上角点,即起始点,两个轴向的最小值作为右下角点,即终止点(在Position坐标系下),相当于做了一个外接矩形。
2.5.2 判断点是否在多边形内
点与多边形的关系有内部、外部和多边形上,判断方法也有很多种。
引射线法,即由点向多边形的左侧引射线,如果射线与多边形的交点个数为奇数,则点在多边形内或多边形上,反之点在多边形外。或者将其理解为光源照射后与梯形区域的关系,都是一样的原理。如图6所示。
面积法。若点在多边形上或者点在多边形内部,那么点与多边形各个边构成的三角形的面积之和是等于多边形面积,反之在外部则不相等。此种方法会因为计算精度会带来一定的误差。关于多边形面积求取,多边形的面积等于由组成多边形三角形的面积之和。可用式(2)取
关于点与多边形关系判断的其他方法可以参考博客。
2.5.3 求取多边形的质心
多边形质心在求取,可以参考wiki中多边形部分。利用式(3)求取。
2.5.3 多边形的缩放
如图7所示,多边形的缩放可以看做是将多边形的顶点向内做一个偏移 (s) 而多边形的形状不发生变化,所以最直接的方法就是做多边形任意两条边的平行线,平行线之之间的距离是 (s),两条平行线的交点就是新的顶点。新的顶点求取方法也比较简单,如图7所示。
其中, $ heta $为两条边的夹角。
2.6 Ellipse(椭圆) 迭代器
椭圆迭代器也比较简单,其构造需要提供椭圆的圆心,长短轴和椭圆的旋转角度等变量。
EllipseIterator::EllipseIterator(const GridMap& gridMap, const Position& center, const Length& length, const double rotation)
椭圆迭代器的迭代过程。椭圆迭代器是Submap迭代器的继承,所以仅需要找到起始和终止迭代点,然后按照子地图的迭代方式迭代即可,只不过在迭代过程中需要判断index对应的cell是否在地图之内。具体的过程可以描述为:
1).求取旋转之后的长短轴。
2).以圆形为中心,长短轴平方和的根作为长,求取椭圆的外接矩形,主要是求取外接矩形的左上角点和右下角点。
3).将矩形的左上角点作为起始点,右下角点作为终止点。
4).按子地图迭代方式,迭代地图,并判断cell是否在椭圆内。判断的方法也比较简单,即 (frac{x^{2}}{a_{2}}+frac{y^{2}}{b_{2}}leq 1)其中 ((x,y)) 是cell单元相对于圆心的坐标。
2.7 Spiral(螺旋) 迭代器
&esmp; 由螺旋迭代器可以看出,螺旋迭代器是圆形子地图除了线性迭代的另一种迭代方式。在迭代器初始化的时候需要迭代器所属的父地图、子地图的圆形和半径等变量。
SpiralIterator::SpiralIterator(const grid_map::GridMap& gridMap, const Eigen::Vector2d& center, const double radius)
螺旋迭代器的迭代过程。螺旋迭代器的迭代原理比较简单,应该可以算是参加工作笔试编程题的难度吧!!!!具体过程可以描述为
- 以子地图圆心为中心,(r) 为半径,安顺时针方向取cell单元存放到
vector
中。- 每迭代器一次,就从
vector
中弹一次,若vector
为空了,执行1)即可。
所以整个螺旋迭代器的原理还是比较简单的,迭代的代码如下:
/**
* @function [generateRing]
* @description [获取距圆心距离为distance的环上的点
* 按照顺时针的顺序,绕圆心,以distance_为半径,求取圆上的点存放到pointsRing_中]
*
*
*/
void SpiralIterator::generateRing()
{
distance_++;
Index point(distance_, 0);
Index pointInMap;
Index normal;
do
{
// 1.增加mappoint点
pointInMap.x() = point.x() + indexCenter_.x();
pointInMap.y() = point.y() + indexCenter_.y();
// 判断增加了坐标值的点是否还在map内
if (checkIfIndexWithinRange(pointInMap, bufferSize_))
{
// 当距离值等于或者接近半径时,要判断该点是否还在圆内
if (distance_ == nRings_ || distance_ == nRings_ - 1)
{
if (isInside(pointInMap))
pointsRing_.push_back(pointInMap);
}
else
{
pointsRing_.push_back(pointInMap);
}
}
// 2. 螺旋式递增算法
// 2.1 获取下一个迭代点的方向:0,不动 1,向右或者向下 -1,向左或者向下
normal.x() = -signum(point.y());
normal.y() = signum(point.x());
// 2.1 在x轴上判断,移动后的点是否满足要求
if (normal.x() != 0
&& (int) Vector(point.x() + normal.x(), point.y()).norm() == distance_)
point.x() += normal.x();
// 2.2 在y轴上判断,移动后的点是否满足要求
else if (normal.y() != 0
&& (int) Vector(point.x(), point.y() + normal.y()).norm() == distance_)
point.y() += normal.y();
// 2.3 如果上面两个条件都不满足,则在两个轴上同时移动
else
{
point.x() += normal.x();
point.y() += normal.y();
}
// 当且仅当point.x()等于当前距离且y的值等于0的时候跳出
} while (point.x() != distance_ || point.y() != 0);
}
const Eigen::Array2i& SpiralIterator::operator *() const
{
return pointsRing_.back();
}
/**
* @function [++]
* @description [迭代重载运算符]
* @return
****/
SpiralIterator& SpiralIterator::operator ++()
{
// 先把最后一个index给删了,相当于return一次弹一次
pointsRing_.pop_back();
// 当这个环空的时候,再增加环
if (pointsRing_.empty() && !isPastEnd())
generateRing();
return * this;
}