Google S2 Geometry:多边形点包含检测结果不符合预期
问题分析与解决方案
这个问题的核心在于S2Loop对顶点环绕方向的严格要求——S2的球面多边形依赖顶点的逆时针顺序(从多边形内部向外看的视角)来定义“内部”区域,你的顶点顺序恰好搞反了,导致S2判定的“内部”和你预期的完全相反。
为什么你的断言会失败?
你定义的顶点顺序是:
(60°N, 118°W) → (23°N, 118°W) → (23°N, 34°E) → (60°N, 34°E)
在球面上,这个顺序是顺时针环绕的。S2会把这种方向的Loop的“内部”判定为整个球面减去你画的矩形区域,而不是矩形本身。所以你的点虽然确实在矩形范围内,但S2认为它不在Loop的“内部”(因为此时内部是矩形之外的所有区域)。
修复方案
有两种简单的方式解决这个问题:
1. 手动反转顶点顺序(推荐明确控制的场景)
把顶点调整为逆时针环绕的顺序,比如:
std::vector<S2Point> vertices = { S2LatLng::FromDegrees(60, -118).ToPoint(), S2LatLng::FromDegrees(60, 34).ToPoint(), S2LatLng::FromDegrees(23, 34).ToPoint(), S2LatLng::FromDegrees(23, -118).ToPoint(), }; auto loop = new S2Loop(vertices, S2Debug::DISABLE); S2Point p = S2LatLng::FromDegrees(42.716450, -67.079284).ToPoint(); ASSERT_TRUE(loop->Contains(p));
这个顺序从左上角出发,先向东到右上角,再向南到右下角,最后向西回到左上角,是标准的逆时针环绕,此时Loop的内部就是你预期的矩形区域。
2. 让S2自动修正方向(适合不确定顺序的场景)
可以调用S2Loop::Normalize()方法,它会自动把Loop调整为逆时针方向,并且确保内部是较小的那个区域(符合大多数场景的预期):
std::vector<S2Point> vertices = { S2LatLng::FromDegrees(60, -118).ToPoint(), S2LatLng::FromDegrees(23, -118).ToPoint(), S2LatLng::FromDegrees(23, 34).ToPoint(), S2LatLng::FromDegrees(60, 34).ToPoint(), }; auto loop = new S2Loop(vertices, S2Debug::DISABLE); // 检查并修正方向 if (!loop->IsNormalized()) { loop->Normalize(); } S2Point p = S2LatLng::FromDegrees(42.716450, -67.079284).ToPoint(); ASSERT_TRUE(loop->Contains(p));
额外提示
- 你可以用
loop->IsNormalized()来检查当前Loop的方向是否符合S2的标准要求; - 如果你的多边形需要覆盖较大的球面区域(比如超过半个球面),
Normalize()可能会把它调整为覆盖较小区域,这种情况需要手动控制顶点顺序。
内容的提问来源于stack exchange,提问作者jeffreyveon




