Boost.Geometry 是 C++ Boost 库中用于处理几何对象的核心组件,它提供了一套丰富且高效的算法接口,用于执行空间关系判断、距离计算、边界提取及几何遍历等操作。本文将深入解析 disjoint、distance、envelope、equals、expand 和 for_each 六大类算法,帮助开发者更好地理解其原理与应用场景。
1. disjoint:空间不相交判断
功能描述
disjoint 算法用于判断两个几何对象是否在空间上完全分离,即它们没有任何公共点(交集为空)。该函数严格遵循 OGC 简单要素规范 (Simple Feature Specification) 中的 "Disjoint" 谓词定义。
接口形式
- 基础接口:接受两个几何对象,自动根据坐标系选择默认策略进行判断。
核心要点
- 返回值 :若两几何体不相交返回
true,否则返回false。 - 支持类型:广泛支持点、线段、框、线串、环、多边形及其多重几何类型之间的组合判断。
- 应用场景:常用于碰撞检测的初步筛选(Broad-phase collision detection),快速排除不可能发生交互的对象对,从而优化计算性能。
示例代码
cpp
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
namespace bg = boost::geometry;
int main() {
typedef bg::model::d2::point_xy<double> point_type;
typedef bg::model::polygon<point_type> polygon_type;
typedef bg::model::box<point_type> box_type;
// --- 基础用法: disjoint(Geometry1, Geometry2) ---
polygon_type poly1, poly2, poly3;
bg::read_wkt("POLYGON((0 2,-2 0,-4 2,-2 4,0 2))", poly1);
bg::read_wkt("POLYGON((2 2,4 4,6 2,4 0,2 2))", poly2);
bg::read_wkt("POLYGON((0 2,2 4,4 2,2 0,0 2))", poly3);
bool check1 = bg::disjoint(poly1, poly2);
std::cout << "Disjoint (poly1, poly2): " << (check1 ? "Yes" : "No") << std::endl;
bool check2 = bg::disjoint(poly1, poly3);
std::cout << "Disjoint (poly1, poly3): " << (check2 ? "Yes" : "No") << std::endl;
return 0;
}
输出
Disjoint (poly1, poly2): Yes
Disjoint (poly1, poly3): No
2. distance:空间距离计算
功能描述
distance 系列算法用于计算两个几何对象之间的最短欧几里得距离或其他度量距离。此外,还提供了 comparable_distance 用于高效比较距离大小。
主要变体
-
distance (标准距离)
- 计算两点、点与线、点与面等之间的实际物理距离。
- 策略支持:支持多种策略以适应不同坐标系,如笛卡尔坐标系下的勾股定理、球面坐标系下的 Haversine 公式、以及地理坐标系下的 Vincenty 或 Andoyer 算法。这使得它不仅能处理平面几何,还能精确计算地球表面的大圆距离。
-
comparable_distance (可比距离)
- 设计目的:为了优化性能。在某些场景下(如寻找最近邻),我们只需要比较距离的相对大小,而不需要精确的距离值。
- 原理 :在笛卡尔坐标系中,该函数通常返回距离的平方,避免了耗时的开方运算 (
sqrt),同时保持了距离比较的正确性(若 d1<d2d_1 < d_2d1<d2,则 d12<d22d_1^2 < d_2^2d12<d22)。
复杂度分析
- 点到几何体:线性复杂度。
- 多点到多边形/环:当前实现为二次复杂度。
- 其他组合:线性对数复杂度。
示例代码
cpp
#include <iostream>
#include <vector>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/numeric/conversion/bounds.hpp>
#include <boost/foreach.hpp>
// 引入策略头文件
#include <boost/geometry/strategies/agnostic/haversine.hpp>
namespace bg = boost::geometry;
int main() {
typedef bg::model::d2::point_xy<double> point_type;
typedef bg::model::polygon<point_type> polygon_type;
typedef bg::model::linestring<point_type> linestring_type;
// --- Comparable Distance (可比距离,用于快速比较,不开方) ---
point_type p(1.4, 2.6);
std::vector<point_type> v;
// 生成一些测试点
for (double x = 0.0; x <= 4.0; x += 1.0) {
for (double y = 0.0; y <= 4.0; y += 1.0) {
v.push_back(point_type(x, y));
}
}
point_type min_p;
double min_d = boost::numeric::bounds<double>::highest();
BOOST_FOREACH(point_type const& pv, v) {
// 使用 comparable_distance 避免昂贵的开方运算进行循环比较
double d = bg::comparable_distance(p, pv);
if (d < min_d) {
min_d = d;
min_p = pv;
}
}
std::cout << "Closest point (comparable): " << bg::dsv(min_p) << std::endl;
std::cout << "Actual distance to closest: " << bg::distance(p, min_p) << std::endl;
// --- 基础 Distance (distance(Geometry1, Geometry2)) ---
point_type p_dist(1, 2);
polygon_type poly;
linestring_type line;
bg::read_wkt("POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3)(4.0 2.0, 4.2 1.4, 4.8 1.9, 4.4 2.2, 4.0 2.0))", poly);
line.push_back(point_type(0, 0));
line.push_back(point_type(0, 3));
std::cout << "Distance Point-Poly: " << bg::distance(p_dist, poly) << std::endl;
std::cout << "Distance Point-Line: " << bg::distance(p_dist, line) << std::endl;
// --- 策略 Distance (distance(Geometry1, Geometry2, Strategy)) ---
// 场景:计算球面上两点之间的距离 (Haversine 公式)
// 定义地理坐标点 (经度,纬度)
typedef bg::model::point<double, 2, bg::cs::geographic<bg::degree>> geo_point;
geo_point location1(5.0, 52.0); // Amsterdam approx
geo_point location2(10.0, 52.0); // East of Amsterdam
// 实例化 Haversine 策略 (地球半径单位:米)
bg::strategy::distance::haversine<double> haversine_strategy(6371000.0);
double dist_sphere = bg::distance(location1, location2, haversine_strategy);
std::cout << "Distance (Haversine strategy) in meters: " << dist_sphere << std::endl;
return 0;
}
输出
Closest point (comparable): (1, 3)
Actual distance to closest: 0.565685
Distance Point-Poly: 1.22066
Distance Point-Line: 1
Distance (Haversine strategy) in meters: 342225
3. envelope:最小外接矩形计算
功能描述
envelope 算法用于计算几何对象的 最小外接矩形 (Minimum Bounding Rectangle, MBR),也称为轴对齐包围盒 (AABB)。这是空间索引(如 R-Tree)构建的基础。
接口形式
- void envelope(Geometry, Box&):将计算结果填入传入的 Box 对象中。
- Box return_envelope(Geometry):直接返回计算后的 Box 对象,使用更便捷,但需指定模板参数。
核心要点
- 符合标准:遵循 OGC 规范的 "Envelope" 函数。
- 应用价值:通过计算简单的矩形边界,可以将复杂的几何相交判断转化为简单的矩形重叠判断,极大地加速空间查询效率。
示例代码
cpp
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/ring.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/assign.hpp>
namespace bg = boost::geometry;
int main() {
typedef bg::model::d2::point_xy<double> point_type;
typedef bg::model::polygon<point_type> polygon_type;
typedef bg::model::ring<point_type> ring_type;
typedef bg::model::box<point_type> box_type;
// --- 基础 Envelope (envelope(Geometry, Box)) ---
polygon_type poly;
bg::read_wkt("POLYGON((2 1.3,2.4 1.7,2.8 1.8,3.4 1.2,3.7 1.6,3.4 2,4.1 3,5.3 2.6,5.4 1.2,4.9 0.8,2.9 0.7,2 1.3)(4.0 2.0, 4.2 1.4, 4.8 1.9, 4.4 2.2, 4.0 2.0))", poly);
box_type env_box;
bg::envelope(poly, env_box);
std::cout << "Envelope (void version): " << bg::dsv(env_box) << std::endl;
// --- 基础 Return Envelope (return_envelope<Box>(Geometry)) ---
ring_type ring;
using namespace boost::assign;
ring += point_type(4.0, -0.5), point_type(3.5, 1.0), point_type(2.0, 1.5),
point_type(3.5, 2.0), point_type(4.0, 3.5), point_type(4.5, 2.0),
point_type(6.0, 1.5), point_type(4.5, 1.0), point_type(4.0, -0.5);
box_type ret_box = bg::return_envelope<box_type>(ring);
std::cout << "Return Envelope: " << bg::dsv(ret_box) << std::endl;
return 0;
}
输出
Envelope (void version): ((2, 0.7), (5.4, 3))
Return Envelope: ((2, -0.5), (6, 3.5))
4. equals:空间相等性判断
功能描述
equals 算法用于判断两个几何对象在空间上是否"相等"。这里的相等指的是 空间等价 (Spatially Equal),即两个几何体覆盖完全相同的点集。
关键特性
- 拓扑相等而非结构相等 :即使两个几何对象的顶点顺序不同、起点不同或数据结构类型不同(例如一个
Box和一个表示相同区域的Polygon),只要它们覆盖的空间区域一致,equals就会返回true。 - 复杂性:线性复杂度,效率较高。
- 符合标准:遵循 OGC 规范的 "Equals" 谓词。
应用场景
- 数据去重:识别并移除空间位置重复的几何记录。
- 验证操作:检查几何变换(如简化、缓冲)后是否保持了原有的空间范围(在特定容差内)。
示例代码
cpp
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/adapted/boost_tuple.hpp>
#include <boost/assign.hpp>
namespace bg = boost::geometry;
// 注册 boost::tuple 作为点类型
BOOST_GEOMETRY_REGISTER_BOOST_TUPLE_CS(cs::cartesian)
int main() {
typedef boost::tuple<int, int> point_type;
typedef bg::model::polygon<point_type> polygon_type;
typedef bg::model::box<point_type> box_type;
using boost::assign::tuple_list_of;
// --- 基础 Equals (equals(Geometry1, Geometry2)) ---
polygon_type poly1, poly2;
// 定义两个顶点顺序不同但形状相同的多边形
bg::exterior_ring(poly1) = tuple_list_of(0, 0)(0, 5)(5, 5)(5, 0)(0, 0);
bg::exterior_ring(poly2) = tuple_list_of(5, 0)(0, 0)(0, 5)(5, 5)(5, 0);
std::cout << "Polygons are spatially " << (bg::equals(poly1, poly2) ? "equal" : "not equal") << std::endl;
box_type box;
bg::assign_values(box, 0, 0, 5, 5);
std::cout << "Polygon and Box are spatially " << (bg::equals(box, poly2) ? "equal" : "not equal") << std::endl;
return 0;
}
输出
Polygons are spatially equal
Polygon and Box are spatially equal
5. expand:包围盒动态扩展
功能描述
expand 算法用于将一个现有的包围盒 (Box) 扩展,使其能够包含另一个几何对象(点、盒或其他几何体的包络)。
工作机制
- 输入:一个可变的 Box 对象和一个待加入的几何对象。
- 行为 :
- 若加入的是点:Box 边界向外延伸以包含该点。
- 若加入的是盒:Box 边界延伸以包含该盒。
- 若加入的是其他几何体:通常先计算其
envelope,再进行扩展。
- 初始化注意 :在使用
expand之前,通常需要将初始 Box 设置为"逆无穷大" (inverse infinite),以便第一个加入的几何体能正确初始化 Box 的范围。
核心价值
- 增量构建:非常适合用于动态构建空间索引或计算一组离散几何体的总包围盒,无需预先存储所有对象。
- 非 OGC 标准:这是 Boost.Geometry 特有的实用工具函数。
示例代码
cpp
#include <iostream>
#include <list>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
namespace bg = boost::geometry;
int main() {
typedef bg::model::d2::point_xy<short int> point_type;
typedef bg::model::box<point_type> box_type;
using bg::expand;
// 初始化一个反向盒体 (最小值最大,最大值最小),以便第一次 expand 能直接赋值
box_type box = bg::make_inverse<box_type>();
expand(box, point_type(0, 0));
expand(box, point_type(1, 2));
expand(box, point_type(5, 4));
// 也可以用一个现有的 box 来扩展
expand(box, bg::make<box_type>(3, 3, 5, 5));
std::cout << bg::dsv(box) << std::endl;
return 0;
}
输出
((0, 0), (5, 5))
6. for_each:几何元素遍历
功能描述
for_each 系列算法借鉴了 C++ STL 的设计思想,允许用户对几何对象内部的组成元素(点或线段)应用自定义函数或仿函数 (Functor)。
主要变体
-
for_each_point
- 遍历几何体中的每一个点。
- 用途:坐标转换、数据清洗(如四舍五入)、特征提取或可视化渲染前的预处理。
-
for_each_segment
- 遍历几何体中的每一条线段(由相邻两点构成)。
- 用途:计算每段长度、斜率分析、线段滤波或特定的几何校验。
设计优势
- 灵活性:通过传入自定义仿函数,用户可以轻松实现各种复杂的遍历逻辑,而无需关心几何体内部的具体存储结构。
- 兼容性 :虽然不属于 OGC 标准,但其行为与
std::for_each高度一致,符合 C++ 程序员的直觉。
示例代码
cpp
#include <iostream>
#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/segment.hpp>
#include <cmath>
namespace bg = boost::geometry;
// --- For Each Point 辅助类 ---
template <typename Point>
class round_coordinates {
private:
typedef typename bg::coordinate_type<Point>::type coordinate_type;
coordinate_type factor;
inline coordinate_type round(coordinate_type value) {
return floor(0.5 + (value / factor)) * factor;
}
public:
round_coordinates(coordinate_type f) : factor(f) {}
inline void operator()(Point& p) {
using bg::get;
using bg::set;
set<0>(p, round(get<0>(p)));
set<1>(p, round(get<1>(p)));
}
};
// --- For Each Point 辅助函数 (用于打印) ---
template <typename Point>
void list_coordinates(Point const& p) {
using bg::get;
std::cout << "x = " << get<0>(p) << " y = " << get<1>(p) << std::endl;
}
// --- For Each Segment 辅助类 ---
template <typename Segment>
struct gather_segment_statistics {
//接收多个数值类型(如 int, float, double, long double),并返回其中精度最高、范围最大的那个类型
typedef typename bg::select_most_precise<
typename bg::coordinate_type<Segment>::type,
double
>::type type;
type min_length, max_length;
gather_segment_statistics() : min_length(1e38), max_length(-1) {}
inline void operator()(Segment const& s) {
type length = bg::length(s);
if (length < min_length) min_length = length;
if (length > max_length) max_length = length;
}
};
int main() {
typedef bg::model::d2::point_xy<double> point_type;
typedef bg::model::polygon<point_type> polygon_type;
typedef bg::model::linestring<point_type> linestring_type;
// --- For Each Point ---
polygon_type poly;
bg::read_wkt("POLYGON((0 0,1.123 9.987,8.876 2.234,0 0),(3.345 4.456,7.654 8.765,9.123 5.432,3.345 4.456))", poly);
// 使用 Functor 类进行坐标舍入
bg::for_each_point(poly, round_coordinates<point_type>(0.1));
std::cout << "Rounded Polygon WKT: " << bg::wkt(poly) << std::endl;
// 使用函数指针/模板函数列出坐标
std::cout << "Listing coordinates of a simple triangle:" << std::endl;
polygon_type tri;
bg::read_wkt("POLYGON((0 0,0 4,4 0,0 0))", tri);
bg::for_each_point(tri, list_coordinates<point_type>);
// --- For Each Segment ---
linestring_type polyline;
// 初始化折线
polyline.push_back(point_type(0, 0));
polyline.push_back(point_type(3, 3));
polyline.push_back(point_type(5, 1));
polyline.push_back(point_type(6, 2));
polyline.push_back(point_type(8, 0));
polyline.push_back(point_type(4, -4));
polyline.push_back(point_type(1, -1));
polyline.push_back(point_type(3, 2));
// 统计线段长度
gather_segment_statistics<bg::model::referring_segment<point_type>> functor;
functor = bg::for_each_segment(polyline, functor);
std::cout << "Min segment length: " << functor.min_length << std::endl;
std::cout << "Max segment length: " << functor.max_length << std::endl;
return 0;
}
输出
Rounded Polygon WKT: POLYGON((0 0,1.1 10,8.9 2.2,0 0),(3.3 4.5,7.7 8.8,9.1 5.4,3.3 4.5))
Listing coordinates of a simple triangle:
x = 0 y = 0
x = 0 y = 4
x = 4 y = 0
x = 0 y = 0
Min segment length: 1.41421
Max segment length: 5.65685
总结
Boost.Geometry 的这套算法接口设计精良,既遵循了国际通用的 OGC 标准以确保互操作性,又提供了丰富的策略机制和工具函数以满足高性能和特殊场景的需求。
- 使用 disjoint 和 equals 进行高效的空间关系谓词判断。
- 利用 distance 和 comparable_distance 在精度与性能之间找到平衡。
- 借助 envelope 和 expand 构建高效的空间索引结构。
- 通过 for_each 实现对几何数据的细粒度控制和处理。