1、Boost.Geometry 的核心概念
Boost.Geometry(又称 Generic Geometry Library, GGL)支持多种几何类型:
- Point(点)
- Linestring(折线)
- Polygon(多边形,支持外环 + 内环/孔洞)
- MultiPolygon
面积计算统一通过:
boost::geometry::area(geometry)
2、Polygon 的基本结构
2.1 多边形类型定义的设计含义
using Point = bg::model::d2::point_xy<double>;
using Polygon = bg::model::polygon<Point>;
这两行代码背后隐含了 Boost.Geometry 的三层抽象设计:
① Point:坐标 + 坐标系
bg::model::d2::point_xy<double>
等价于:
p=(x,y)∈R2 \mathbf{p} = (x, y) \in \mathbb{R}^2 p=(x,y)∈R2
并且显式声明:
- 维度:2D
- 坐标系:Cartesian
- 存储类型:
double
重要:坐标系信息会直接影响:
- 面积计算方式
- 使用的 strategy(Cartesian vs Geographic)
② Polygon:拓扑结构,而非点集
bg::model::polygon<Point>
在 Boost.Geometry 中:
Polygon 是一个 拓扑对象(Topological Object),而不是简单的顶点集合。
其数学定义为:
P=(R0,R1,…,Rk)
\mathcal{P} =
\left(
\mathcal{R}_0,
{\mathcal{R}_1, \dots, \mathcal{R}_k}
\right)
P=(R0,R1,…,Rk)
其中:
- R0\mathcal{R}_0R0:外环(outer ring)
- Ri\mathcal{R}_iRi:内环(inner ring / hole)
2.2 Polygon 的组成结构(数据层面)
Polygon
├── outer() // 外边界(必须,闭合)
└── inners() // 内环(孔洞,可选)
2.2.1 Outer Ring(外环)
外环满足以下几何 + 拓扑约束:
-
简单闭合曲线
p[0] == p[n-1] -
无自交
-
默认方向:逆时针(CCW)
在 Cartesian 坐标系下:
- CCW ⇒ 正面积
- CW ⇒ 负面积
数学上:
Area(R0)>0 \mathrm{Area}(\mathcal{R}_0) > 0 Area(R0)>0
2.2.2 Inner Rings(内环 / 孔洞)
每个 inner ring:
- 代表一个“被挖空”的区域
- 必须 完全包含在外环内部
- 方向与外环相反(CW)
因此:
Area(Ri)<0 \mathrm{Area}(\mathcal{R}_i) < 0 Area(Ri)<0
Boost.Geometry 在计算面积时直接做代数求和:
Area(P)Area(R0)+∑iArea(Ri) \mathrm{Area}(\mathcal{P}) \mathrm{Area}(\mathcal{R}_0) + \sum_i \mathrm{Area}(\mathcal{R}_i) Area(P)Area(R0)+i∑Area(Ri)
不需要手动“减去孔洞面积”
2.3 为什么方向(Orientation)是“结构的一部分”?
在 Boost.Geometry 中:
Ring 的方向 ≠ 视觉约定,而是数学定义的一部分
原因是:
- 面积计算基于 有符号 Shoelace Formula
- 方向决定符号
Shoelace 公式:
A=12∑i=0n−1(xiyi+1−xi+1yi) A = \frac{1}{2} \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i) A=21i=0∑n−1(xiyi+1−xi+1yi)
| 方向 | 面积符号 |
|---|---|
| CCW | 正 |
| CW | 负 |
因此:
- 外环 CCW ⇒ 正贡献
- 内环 CW ⇒ 负贡献(孔洞)
2.4 自动修正机制:bg::correct
Boost.Geometry 提供了 结构规范化工具:
bg::correct(polygon);
该函数会:
- 自动闭合所有 ring
- 统一外环 / 内环方向
- 修正轻微的不规范输入
最佳实践:
所有 Polygon 在参与
area()、union_、intersection之前,都应调用correct()
2.5 常见错误与真实工程后果
错误 1:只存点,不存方向
poly.outer() = {{0,0}, {1,0}, {1,1}, {0,1}};
- 未闭合
- 方向未知
面积不可靠
正确:
bg::correct(poly);
错误 2:孔洞方向错误
poly.inners()[0] = {{1,1},{2,1},{2,2},{1,2},{1,1}}; // CCW
面积会 增加而不是减少
2.6 与 SLAM / 地图构建的关系(应用视角)
- 占据栅格 → 边界提取 → Polygon
- 局部地图裁剪 / Mask
- 车辆 footprint + union → 覆盖面积
这些应用都高度依赖 Polygon 拓扑正确性
2.7 小结
在 Boost.Geometry 中,Polygon 是“有方向的拓扑结构”,而不是简单点序列。
理解这一点,才能正确使用:
areaunion_differenceintersection
3、多边形面积计算
3.1 基本用法背后的完整逻辑
double area = bg::area(poly);
这一行代码表面上很简单,但内部隐含了 多层判定与算法分发:
Polygon
→ 坐标系(Cartesian)
→ 几何类型(Polygon)
→ strategy::area::cartesian
→ ring-based shoelace accumulation
3.2 示例代码逐行深度解析
Polygon poly;
// 外环
poly.outer().push_back({0, 0});
poly.outer().push_back({4, 0});
poly.outer().push_back({4, 3});
poly.outer().push_back({0, 3});
poly.outer().push_back({0, 0}); // 必须闭合
几何解释
该多边形是一个 轴对齐矩形:
- 左下角:(0,0)(0,0)(0,0)
- 右上角:(4,3)(4,3)(4,3)
- 宽度:4
- 高度:3
拓扑性质
- 顶点数(含闭合点):5
- 简单多边形(无自交)
- 顶点顺序:逆时针(CCW)
3.3 Boost.Geometry 中面积的数学本质
3.3.1 Shoelace(高斯面积)公式
Boost.Geometry 在 Cartesian 2D 下采用 有符号面积公式:
A12∑i=0n−1(xiyi+1−xi+1yi) A \frac{1}{2} \sum_{i=0}^{n-1} \left( x_i y_{i+1} - x_{i+1} y_i \right) A21i=0∑n−1(xiyi+1−xi+1yi)
- nnn 为 ring 顶点数(不含重复的最后一个点)
- 顺序决定符号
3.3.2 将示例代入公式
顶点序列:
(0,0),(4,0),(4,3),(0,3),(0,0) (0,0), (4,0), (4,3), (0,3), (0,0) (0,0),(4,0),(4,3),(0,3),(0,0)
计算:
A=12[(0⋅0+4⋅3+4⋅3+0⋅0) −(0⋅4+0⋅4+3⋅0+3⋅0)] =12 \begin{aligned} A &= \frac{1}{2}[ (0\cdot0 + 4\cdot3 + 4\cdot3 + 0\cdot0) \ &\quad - (0\cdot4 + 0\cdot4 + 3\cdot0 + 3\cdot0) ] \ &= 12 \end{aligned} A=21[(0⋅0+4⋅3+4⋅3+0⋅0) −(0⋅4+0⋅4+3⋅0+3⋅0)] =12
因此:
area = 12.0
3.4 为什么 area() 返回的是“有符号值”?
3.4.1 数学原因
Boost.Geometry 不人为取绝对值,而是保留:
orientation information \text{orientation information} orientation information
这在以下操作中非常关键:
- 多边形布尔运算(union / difference)
- 孔洞判定
- winding number 判断
3.4.2 工程后果
| 顶点顺序 | bg::area(poly) |
|---|---|
| CCW | +12 |
| CW | -12 |
若你只关心“物理面积”:
double area = std::abs(bg::area(poly));
3.5 bg::correct() 在面积计算中的关键作用
问题:如果用户没闭合?
poly.outer() = {
{0,0},{4,0},{4,3},{0,3}
};
行为 未定义或错误
正确姿势:
bg::correct(poly);
double area = bg::area(poly);
correct() 会:
- 自动闭合 ring
- 修正方向(outer CCW,inner CW)
- 修复轻微输入不规范
3.6 含孔洞的面积计算机制
若存在 inner rings:
AreaArea∗outer+∑iArea∗inner,i \mathrm{Area} \mathrm{Area}*{outer} + \sum_i \mathrm{Area}*{inner,i} AreaArea∗outer+i∑Area∗inner,i
由于 inner 为 CW:
Areainner,i<0 \mathrm{Area}_{inner,i} < 0 Areainner,i<0
孔洞自然被扣除
3.7 面积计算前的有效性检查
std::string reason;
if (!bg::is_valid(poly, reason)) {
throw std::runtime_error(reason);
}
检查内容包括:
- 是否闭合
- 是否自交
- 孔洞是否在外环内部
- 方向是否可修复
3.8 常见工程陷阱总结
自交多边形
- 蝴蝶形、多叉边
area()结果不可依赖
经纬度坐标直接算面积
bg::model::point<double,2,bg::cs::geographic<bg::degree>>
必须指定 geographic strategy
3.9 工程级推荐模板
bg::correct(poly);
std::string msg;
if (!bg::is_valid(poly, msg)) {
throw std::runtime_error(msg);
}
double area = std::abs(bg::area(poly));
4、环方向(Orientation)与面积符号
Boost.Geometry 使用有符号面积:
| 顶点顺序 | area(poly) |
|---|---|
| CCW | 正值 |
| CW | 负值 |
4.1 自动修正方向
bg::correct(poly);
- 自动闭合
- 自动调整外环/内环方向
工程中强烈建议在 area 之前调用
5、含孔洞(Inner Ring)的多边形面积示例
// 外环
poly.outer() = {
{0,0}, {5,0}, {5,5}, {0,5}, {0,0}
};
// 内环(孔)
poly.inners().resize(1);
poly.inners()[0] = {
{1,1}, {1,2}, {2,2}, {2,1}, {1,1}
};
bg::correct(poly);
double area = bg::area(poly);
计算结果:
area = 25 - 1 = 24
Boost.Geometry 会自动:
- 计算外环面积
- 减去所有内环面积
6、多多边形(MultiPolygon)面积
using MultiPolygon = bg::model::multi_polygon<Polygon>;
MultiPolygon mp;
mp.push_back(poly1);
mp.push_back(poly2);
double area = bg::area(mp);
等价于:
area = area(poly1) + area(poly2)
7、使用的数学原理(Shoelace Formula)
Boost.Geometry 的多边形面积基于 高斯面积公式(鞋带公式):
A=12∣∑i=0n−1(xiyi+1−xi+1yi)∣ A = \frac{1}{2} \left| \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i) \right| A=21i=0∑n−1(xiyi+1−xi+1yi)
- O(n) 时间复杂度
- 对非自交简单多边形严格成立
自交多边形 → 行为未定义(需 bg::is_valid() 检查)
8、面积计算前的有效性检查
bool valid = bg::is_valid(poly);
或者输出错误原因:
std::string msg;
bool valid = bg::is_valid(poly, msg);
9、常见坑总结
| 问题 | 后果 |
|---|---|
| 多边形未闭合 | 面积错误 |
| 环方向错误 | 面积为负 |
| 自交 | 未定义行为 |
| 经纬度坐标 | 不能直接用 area(需 geographic strategy) |
10、地理坐标(WGS84)下的面积计算
10.1 为什么“经纬度”不能直接用 Cartesian 面积?
在地理坐标系中:
bg::cs::geographic<bg::degree>
点的含义是:
[
\mathbf{p} = (\lambda, \varphi)
]
其中:
- λ\lambdaλ:经度(longitude)
- φ\varphiφ:纬度(latitude)
- 单位:角度(degree)
这 不是欧氏空间坐标:
- 经度线不是平行线
- 纬度方向的实际距离随纬度变化
- 地球是椭球体(WGS84),不是平面
Shoelace 公式在这里是错误的
10.2 Boost.Geometry 的解决方案:Strategy 分离
Boost.Geometry 的核心设计之一是:
几何类型 × 坐标系 × Strategy → 算法
对于 geographic 坐标:
bg::strategy::area::geographic<>
Boost.Geometry 自动切换到:
- 椭球测地线积分面积算法
- 基于 WGS84 椭球参数
10.3 geographic<> strategy 的数学原理
10.3.1 地球模型(WGS84)
Boost.Geometry 默认使用:
- 长半轴:
a=6378137.0;m a = 6378137.0;\text{m} a=6378137.0;m - 扁率:
f=1298.257223563 f = \frac{1}{298.257223563} f=298.2572235631
这是标准 WGS84 椭球模型。
10.3.2 面积计算的本质
地理多边形面积定义为:
A=∬ΩdS A = \iint_{\Omega} \mathrm{d}S A=∬ΩdS
其中:
- Ω\OmegaΩ:地表多边形区域
- dS\mathrm{d}SdS:椭球表面面积元
Boost.Geometry 内部采用:
- 测地线(Geodesic)边界
- 椭球面面积积分
- 数值稳定的累积算法(Karney / GeographicLib 思想)
10.4 示例:经纬度多边形面积
using GeoPoint = bg::model::point<
double, 2, bg::cs::geographic<bg::degree>
>;
bg::model::polygon<GeoPoint> poly;
poly.outer() = {
{116.0, 39.0},
{116.1, 39.0},
{116.1, 39.1},
{116.0, 39.1},
{116.0, 39.0}
};
bg::correct(poly);
double area = bg::area(
poly,
bg::strategy::area::geographic<>()
);
返回值单位
平方米(m²)
不是“平方度”
10.5 为什么必须显式指定 Strategy?
bg::area(poly); // 潜在错误
Boost.Geometry 无法自动判断:
-
你是否希望:
- 平面近似?
- 椭球精确?
-
geographic 坐标 没有默认 area 策略
显式指定是强制设计选择
10.6 常见误区(非常重要)
把经纬度当平面坐标
bg::model::point_xy<double> // 错误
面积误差可达 数十甚至上百倍
忽略单位
- 输入:degree
- 输出:m²
很多人误以为结果仍是“角度平方”
10.7 地理坐标下的方向(Orientation)问题
即使在 geographic 坐标下:
- Polygon 仍有 方向
- 外环仍应为 CCW(从地表法向看)
但:
- geographic strategy 内部会自动处理方向
- 推荐仍调用:
bg::correct(poly);
10.8 何时应使用 geographic 面积?
| 场景 | 是否使用 |
|---|---|
| 城市级地图 | ✅ |
| 国家/洲级 | ✅ |
| SLAM 局部地图(<1km) | ❌(投影即可) |
| 室内 | ❌ |
10.9 与投影坐标的对比
替代方案:先投影再算面积
WGS84 → UTM → Cartesian → area
优点:
- 速度快
- 算法简单
缺点:
- 投影误差
- 跨 UTM zone 复杂
10.10 工程级推荐写法
bg::correct(poly);
std::string reason;
if (!bg::is_valid(poly, reason)) {
throw std::runtime_error(reason);
}
double area = bg::area(
poly,
bg::strategy::area::geographic<>()
);
本节一句话总结
经纬度坐标下的面积计算,本质是“椭球曲面上的积分问题”,必须使用 geographic strategy。

9080

被折叠的 条评论
为什么被折叠?



