23. 有效性

在 90% 的情况下,对于“为什么我的查询会返回‘TopologyException’错误”这个问题的答案是“一个或多个输入无效”。这引出了一个问题:无效意味着什么,我们为什么要关心它?

23.1. 什么是有效性

有效性对于多边形最为重要,多边形定义了有界区域,需要大量的结构。线非常简单,不能无效,点也不能无效。

多边形有效性的一些规则感觉很明显,而另一些则感觉很随意(事实上,它们确实是随意的)。

  • 多边形环必须闭合。

  • 定义孔洞的环应该位于定义外部边界的环的内部。

  • 环不能自相交(它们既不能接触也不能交叉)。

  • 环不能接触其他环,除非在一点上。

  • 多边形集合的元素不能互相接触。

最后三条规则属于随意类别。还有其他方法可以定义同样自洽的多边形,但上面的规则是 PostGIS 符合的 OGC SFSQL 标准所使用的规则。

这些规则之所以重要,是因为几何计算算法依赖于输入的一致结构。可以构建没有结构假设的算法,但这些例程往往非常慢,因为任何无结构例程的第一步都是分析输入并在其中构建结构

以下是一个结构为何重要的示例。这个多边形无效

POLYGON((0 0, 0 1, 2 1, 2 2, 1 2, 1 0, 0 0));

您可以在此图中更清楚地看到无效性

_images/figure_eight.png

外环实际上是一个“8”字形,中间有一个自相交点。请注意,图形例程成功地渲染了多边形填充,因此在视觉上它看起来像一个“区域”:两个单位正方形,因此总面积为两个面积单位。

让我们看看数据库认为我们多边形的面积是多少。

SELECT ST_Area(ST_GeometryFromText(
         'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
       ));
 st_area
---------
       0

这里发生了什么?计算面积的算法假设环不自我相交。一个表现良好的环将始终具有边界(内部)位于边界线一侧的面积(哪一侧无关紧要,只要它位于侧)。但是,在我们(行为不佳的)“8”字形中,边界区域在一条叶瓣的右侧,而在另一条叶瓣的左侧。这会导致为每个叶瓣计算的面积抵消(一个结果为 1,另一个结果为 -1),因此出现“零面积”结果。

23.2. 检测有效性

在前面的示例中,我们有一个我们知道无效的多边形。我们如何在一个包含数百万个几何图形的表中检测无效性?使用ST_IsValid(geometry)函数。针对我们的“8”字形使用它,我们可以快速得到答案。

SELECT ST_IsValid(ST_GeometryFromText(
         'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
       ));
f

现在我们知道该要素无效,但我们不知道原因。我们可以使用ST_IsValidReason(geometry)函数来找出无效性的来源。

SELECT ST_IsValidReason(ST_GeometryFromText(
         'POLYGON((0 0, 0 1, 1 1, 2 1, 2 2, 1 2, 1 1, 1 0, 0 0))'
       ));
Self-intersection[1 1]

请注意,除了原因(自相交)之外,还返回了无效性的位置(坐标 (1 1))。

我们也可以使用ST_IsValid(geometry)函数来测试我们的表。

-- Find all the invalid polygons and what their problem is
SELECT name, boroname, ST_IsValidReason(geom)
FROM nyc_neighborhoods
WHERE NOT ST_IsValid(geom);
          name           |   boroname    |          st_isvalidreason
-------------------------+---------------+-----------------------------------------
 Howard Beach            | Queens        | Self-intersection[597264.08 4499924.54]
 Corona                  | Queens        | Self-intersection[595483.05 4513817.95]
 Steinway                | Queens        | Self-intersection[593545.57 4514735.20]
 Red Hook                | Brooklyn      | Self-intersection[584306.82 4502360.51]

23.3. 修复无效性

修复无效性涉及将多边形分解为最简单的结构(环),确保环遵循有效性规则,然后构建遵循环封闭规则的新多边形。结果通常是直观的,但在输入极度不规范的情况下,有效输出可能不符合您对它们外观的直觉。最新版本的 PostGIS 包含不同的几何修复算法:仔细阅读手册页并选择您最喜欢的算法。

例如,这里有一个经典的无效性——“香蕉多边形”——一个封闭区域但弯曲到自身并接触的单个环,留下一个实际上不是洞的“洞”。

POLYGON((0 0, 2 0, 1 1, 2 2, 3 1, 2 0, 4 0, 4 4, 0 4, 0 0))
_images/banana.png

在多边形上运行ST_MakeValid将返回一个有效的OGC多边形,它由一个外环和一个内环组成,它们在一个点处接触。

SELECT ST_AsText(
         ST_MakeValid(
           ST_GeometryFromText('POLYGON((0 0, 2 0, 1 1, 2 2, 3 1, 2 0, 4 0, 4 4, 0 4, 0 0))')
         )
       );
POLYGON((0 0,0 4,4 4,4 0,2 0,0 0),(2 0,3 1,2 2,1 1,2 0))

注意

“香蕉多边形”(或“倒置外壳”)是一个案例,其中OGC的有效几何拓扑模型与 ESRI 内部使用的模型不同。ESRI 模型认为相接的环无效,并更倾向于这种形状的香蕉形式。OGC 模型则相反。两者都没有“正确”,它们只是对同一情况的不同建模方式。

23.4. 批量有效性修复

以下是一个 SQL 示例,用于在将修复后的版本添加到表中时标记无效几何以供审查。

-- Column for old invalid form
ALTER TABLE nyc_neighborhoods
  ADD COLUMN geom_invalid geometry
  DEFAULT NULL;

-- Fix invalid and save the original
UPDATE nyc_neighborhoods
  SET geom = ST_MakeValid(geom),
      geom_invalid = geom
  WHERE NOT ST_IsValid(geom);

-- Review the invalid cases
SELECT geom, ST_IsValidReason(geom_invalid)
  FROM nyc_neighborhoods
  WHERE geom_invalid IS NOT NULL;

OpenJump (http://openjump.org) 是一个用于直观修复无效几何的良好工具,它在 **工具->质量保证->验证所选图层** 下包含一个验证例程。

23.5. 函数列表

ST_IsValid(geometry A): 返回一个布尔值,指示几何是否有效。

ST_IsValidReason(geometry A): 返回一个文本字符串,其中包含无效的原因和无效的坐标。

ST_MakeValid(geometry A): 返回一个重新构建以符合有效性规则的几何。