Boost.Geometry
介绍
Boost.Geometry
(又名通用几何库,GGL)是Boost C++库的一部分,它定义了解决几何问题的概念,原语和算法。
Boost.Geometry包含基于概念,元函数和标签分发的不可知尺寸,可坐标系不可伸缩的可扩展内核。在该内核之上,构建了算法:面积,长度,周长,质心,凸包,交点(剪切),内部(多边形中的点),距离,包络(边界框),简化,变换等等。该库支持高精度算术数,例如ttmath。
Boost.Geometry包含可实例化的几何类,但库用户也可以使用自己的类。使用注册宏或特征类,可以调整其几何形状以实现Boost.Geometry概念。
Boost.Geometry可能会在所有几何都起作用的领域中使用:制图和GIS,游戏开发,计算机图形和widgets
,机器人技术,天文学等。核心被设计为尽可能通用并支持那些域。目前,开发主要是面向GIS的。
该库遵循现有约定:
- boost惯例
- std库中的约定
- 规范和名称来自OGC的一种几何标准,更具体地说是来自OGC简单特征规范
该库最初与Boost 1.47.0一起发布,从那时起,它正式成为Boost C ++库的一部分。
Boost打包的发行版中包含源代码的最新稳定版本。也可以从Boost GitHub存储库(主分支)下载。
上游的库开发可从Boost.Geometry(开发分支)获得。
请注意,库扩展名并未在Boost的正式发行版中分发,而仅在Boost.Geometry(开发分支)中可用,并且它们可能会发生变化。
Boost.Geometry在2009年11月28日被Boost接受(审查报告)。
有一个Boost.Geometry邮件列表。邮件列表及其消息也可以从Nabble作为Boost Geometry访问。还在Boost开发人员列表和Boost用户列表中讨论了Boost.Geometry。
汇编
设计原理
假设您需要一个C++程序来计算两点之间的距离。 您可以定义一个结构:
struct mypoint {
double x, y;
};
和一个包含该算法的函数:
double distance(mypoint const& a, mypoint const& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return sqrt(dx * dx + dy * dy);
}
很简单,它是可用的,但不是通用的。 对于库,必须进一步设计。上面的设计仅可用于笛卡尔坐标系中的2D点,mypoint
结构体。 通用库应该能够计算距离:
- 对于任何点类或结构,不仅限于此
mypoint
类型 - 在两个以上的维度
- 用于其他坐标系,例如在地球上或在球体上
- 点和线之间或其他几何组合之间
- 比双精度更高
- 避免平方根:通常我们不想这样做,因为它是一个相对开销比较大的函数,并且比较距离来说,它不重要。
在本节及以下各节中,我们将逐步使设计更加通用。
使用模板
distance
函数可以更改为模板函数。 这很简单,可以计算除mypoint
以外的其他点类型之间的距离。 我们添加了两个模板参数,允许输入两种不同的点类型。
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return std::sqrt(dx * dx + dy * dy);
}
此模板版本稍好一些,但不多。
考虑一个成员变量受保护的C++类。此类不允许直接访问x和y成员。 因此,本段很短,我们继续。
使用Traits
我们需要采用通用方法,并允许将任何点类型用作distance
函数的输入。 代替访问x和y成员,我们将使用traits system
添加一些levels of indirection
。 该函数将变为:
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b) {
double dx = get<0>(a) - get<0>(b);
double dy = get<1>(a) - get<1>(b);
return std::sqrt(dx * dx + dy * dy);
}
这种经过调整的distance
函数使用通用的get
函数(将维度作为模板参数)来访问点的坐标。 这将转至traits system
,定义如下:
namespace traits {
template <typename P, int D>
struct access {};
}
然后专门用于我们的mypoint
类型,实现一个称为get
的静态方法:
namespace traits {
template <>
struct access<mypoint, 0> {
static double get(mypoint const& p) {
return p.x;
}
};
// same for 1: p.y
...
}
调用traits::access<mypoint,0>::get(a)
现在返回我们的x坐标。 很好,不是吗? 对于库中经常使用的此类函数而言,它太冗长了。 我们可以通过添加一个额外的free函数来缩短语法:
template <int D, typename P>
inline double get(P const& p) {
return traits::access<P, D>::get(p);
}
这使我们能够为具有trait::access
特化的任何point
调用get<0>(a)
,如本段开头的distance
算法所示。 因此,我们希望使用x()
之类的方法来启用类,并且只要访问结构具有特殊化,该类具有静态get函数(对于维度0,返回x()
,对于1和y()
类似),就可以支持它们。
维度不可知论
现在我们可以计算2D点,任何结构或类的点之间的距离。 但是,我们也希望拥有3D。 因此,我们必须使其与尺寸无关。 这使我们的距离函数变得复杂。 我们可以使用for循环遍历维度,但是for循环具有比最初存在的简单坐标添加功能更高的性能。 但是,我们可以更多地使用模板,并使distance
算法如下所示,虽然更复杂,但对模板迷很有吸引力:
template <typename P1, typename P2, int D>
struct pythagoras {
static double apply(P1 const& a, P2 const& b) {
double d = get<D-1>(a) - get<D-1>(b);
return d * d + pythagoras<P1, P2, D-1>::apply(a, b);
}
};
template <typename P1, typename P2 >
struct pythagoras<P1, P2, 0> {
static double apply(P1 const&, P2 const&) {
return 0;
}
};
distance
函数正在调用pythagoras
结构,并指定维数:
template <typename P1, typename P2>
double distance(P1 const& a, P2 const& b)
{
BOOST_STATIC_ASSERT(( dimension<P1>::value == dimension<P2>::value ));
return sqrt(pythagoras<P1, P2, dimension<P1>::value>::apply(a, b));
}
引用的维度是使用另一个特征类定义的:
namespace traits
{
template <typename P>
struct dimension {};
}
总结
在此设计原理中,Boost.Geometry是使用tag dispatching
,概念,特征(traits)和元编程逐步设计的。 我们使用了众所周知的distance
函数来展示设计。
Boost.Geometry的设计如此处所述,具有更多的技术,例如自动反转模板参数,标记强制转换以及将实现类或调度类重用作为其他调度类中的策略。
快速开始
本“快速入门”部分以带注释的相对简单的代码片段的形式展示了Boost.Geometry的某些功能。
下面的代码假定包含boost/geometry.hpp
,并使用命名空间boost::geometry
。 Boost.Geometry仅是头文件,因此包含头文件就足够了。 没有与任何库的链接。
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
using namespace boost::geometry;
笛卡尔坐标系(直角坐标系)
仅使用库的一小部分。 例如:两点之间的距离是一个常见的用例。 Boost.Geometry可以根据各种类型进行计算。 使用自己的一种类型:
model::d2::point_xy<int> p1(1, 1), p2(2, 2);
std::cout << "Distance p1-p2 is: " << distance(p1, p2) << std::endl;
如果包含正确的头文件并且类型绑定到坐标系,则可以将其他各种类型用作点:普通C数组,Boost.Array,Boost.Tuple,Boost.Fusion导入的结构,您自己的类...
注册和使用C数组:
#include <boost/geometry/geometries/adapted/c_array.hpp>
BOOST_GEOMETRY_REGISTER_C_ARRAY_CS(cs::cartesian)
int a[2] = {1,1};
int b[2] = {2,3};
double d = distance(a, b);
std::cout << "Distance a-b is: " << d << std::endl;
另一个常用的算法是多边形中点。 它在Boost.Geometry中的内部名称下实现。 我们在这里通过检查位于填充有C数组点对的多边形内的Boost.Tuple(作为点)来显示其用法。
但是首先需要注册一个Boost.Tuple,就像注册C数组那样:
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
double points[][2] = {{2.0, 1.3}, {4.1, 3.0}, {5.3, 2.6}, {2.9, 0.7}, {2.0, 1.3}};
model::polygon<model::d2::point_xy<double> > poly;
append(poly, points);
boost::tuple<double, double> p = boost::make_tuple(3.7, 2.0);
std::cout << "Point p is in polygon? " << std::boolalpha << within(p, poly) << std::endl;
我们可以计算出多边形的面积:
std::cout << "Area: " << area(poly) << std::endl;
通过模板库的性质,可以混合point
类型。 我们再次使用C数组点和Boost.Tuple点来计算距离:
double d2 = distance(a, p);
std::cout << "Distance a-p is: " << d2 << std::endl;
上面列出的snippets
生成以下输出:
Distance p1-p2 is: 1.41421
Distance a-b is: 2.23607
Point p is in polygon? true
Area: 3.015
Distance a-p is: 2.87924
非笛卡尔坐标系
也可以使用非笛卡尔点。 例如:球体上的点。 然后,使用距离之类的算法时,库会“检查”它正在处理球形点,并计算球体上的距离,而不是应用勾股定理。
Boost.Geometry支持地理坐标系(geographical coordinate system),但这是扩展,不在当前的Boost版本中发布。
我们将地球近似为一个球体,并计算阿姆斯特丹和巴黎之间的距离:
typedef boost::geometry::model::point
<
double, 2, boost::geometry::cs::spherical_equatorial<boost::geometry::degree>
> spherical_point;
spherical_point amsterdam(4.90, 52.37);
spherical_point paris(2.35, 48.86);
double const earth_radius = 3959; // miles
std::cout << "Distance in miles: " << distance(amsterdam, paris) * earth_radius << std::endl;
它输出:
Distance in miles: 267.02
Adapted 结构体
最后是一个完全不同的领域的示例:开发基于窗口的应用程序,例如使用QtWidgets。 一旦在Boost.Geometry
中注册了Qt类,我们就可以使用它们。 例如,我们可以检查两个矩形是否重叠,如果重叠,则将第二个矩形移到另一个位置:
QRect r1(100, 200, 15, 15);
QRect r2(110, 210, 20, 20);
if (overlaps(r1, r2)) {
assign_values(r2, 200, 300, 220, 320);
}
空间索引
介绍
Boost.Geometry.Index旨在收集称为空间索引的数据结构,该数据结构可用于加速在空间中搜索对象。 通常,空间索引存储几何对象的表示,并允许搜索占据某个空间或接近空间中某个点的对象。
当前,仅实现一个空间索引-R树。
R-tree
R-tree是用于空间搜索的树数据结构。 它是由Antonin Guttman在1984年提出的,它是B树在多维数据上的扩展。 它可以用于存储点或体积数据,以执行空间查询。 例如,此查询可能返回位于某个区域内或接近空间中某个点的对象。 可以插入新对象或删除已存储的对象。
下图显示了R-tree结构。 每个R-tree的节点都存储一个框,描述其子节点所占用的空间。 在结构的底部,有包含值(几何对象表示形式)的叶节点。
R-tree是一种自平衡数据结构。平衡算法的关键部分是节点拆分算法。每种算法都会产生不同的拆分,因此对于每个拆分树,其内部结构可能会有所不同。通常,更复杂的算法可以更好地分析元素,并产生更少的重叠节点。在搜索过程中,必须遍历较少的节点才能找到所需的对象。另一方面,更复杂的分析需要更多时间。通常,更快的插入将导致搜索变慢,反之亦然。 R树的性能取决于平衡算法,参数和插入到容器中的数据。
此外,还有创建包含一些对象的R-tree的算法。这项技术称为批量加载,是通过使用打包算法[5] [6]完成的。此方法速度更快,并导致R树具有更好的内部结构。这意味着查询性能得到提高。
下面介绍了使用不同算法和示例性操作时间创建的树的结构示例。
快速开始
本快速入门部分显示了创建典型R-tree和执行空间查询的简单方法。
下面的代码假定包含以下文件并使用了命名空间。
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/index/rtree.hpp>
// to store queries results
#include <vector>
// just for output
#include <iostream>
#include <boost/foreach.hpp>
namespace bg = boost::geometry;
namespace bgi = boost::geometry::index;
通常,您将存储例如std::pair<Box,MyGeometryId>
在R-tree中。 MyGeometryId将是存储在其他容器中的复杂Geometry的某些标识符,例如 存储在vector中或环列表的迭代器中的多边形的索引类型。 为了使定义值变得简单,我们将使用预定义的Box和unsigned int。
typedef bg::model::point<float, 2, bg::cs::cartesian> point;
typedef bg::model::box<point> box;
typedef std::pair<box, unsigned> value;
可以使用各种算法和参数来创建R-tree。 您应该选择最适合自己目的的算法。 在此示例中,我们将使用二次算法。 参数作为模板参数传递。 节点中的最大元素数设置为16。
// create the rtree using default constructor
bgi::rtree< value, bgi::quadratic<16> > rtree;
通常,将例如从循环生成值。 存储在其他容器中的多边形。 在这种情况下,可能会使用geometry::envelope()
函数创建Box对象。 但是为了简单起见,我们只需要手动生成一些框,然后使用insert()
方法将它们插入R树即可。
// create some values
for ( unsigned i = 0 ; i < 10 ; ++i ) {
// create a box
box b(point(i + 0.0f, i + 0.0f), point(i + 0.5f, i + 0.5f));
// insert new value
rtree.insert(std::make_pair(b, i));
}
可以执行各种类型的空间查询,甚至可以在一个呼叫中将它们组合在一起。 为了简单起见,我们使用默认值。 以下查询返回与框相交的值。 未指定结果中的值顺序。
// find values intersecting some area defined by a box
box query_box(point(0, 0), point(5, 5));
std::vector<value> result_s;
rtree.query(bgi::intersects(query_box), std::back_inserter(result_s));
其他类型的查询是k最近邻居搜索。 它返回最接近空间中某个点的一些值。 默认的knn查询可以如下执行。 未指定结果中的值顺序。
// find 5 nearest values to a point
std::vector<value> result_n;
rtree.query(bgi::nearest(point(0, 0), 5), std::back_inserter(result_n));
最后,我们将打印结果。
// display results
std::cout << "spatial query box:" << std::endl;
std::cout << bg::wkt<box>(query_box) << std::endl;
std::cout << "spatial query result:" << std::endl;
BOOST_FOREACH(value const& v, result_s)
std::cout << bg::wkt<box>(v.first) << " - " << v.second << std::endl;
std::cout << "knn query point:" << std::endl;
std::cout << bg::wkt<point>(point(0, 0)) << std::endl;
std::cout << "knn query result:" << std::endl;
BOOST_FOREACH(value const& v, result_n)
std::cout << bg::wkt<box>(v.first) << " - " << v.second << std::endl;
有关更多
有关R-tree实现,其他算法和查询的更多信息,可以在本文档的其他部分找到。