23. 有效性¶
在 90% 的情況下,「為什麼我的查詢會傳回『TopologyException』錯誤」這個問題的解答為「有一個以上的輸入無效」。這引發了一個疑問:所謂無效是什麼意思,為什麼我們要關心它呢?
23.1. 什麼是有效性¶
有效性對多邊形來說最重要,多邊形定義了有界的區域,需要大量結構。線非常簡單,不會無效,點也不會無效。
多邊形有效性的一些規則感覺很明顯,而且其他規則的感覺很主觀(事實上,是主觀的)。
多邊形環必須閉合。
用於定義孔洞的環應該在用於定義外部邊界的環的內部。
環不得自相交(環不得觸及或穿過它們自己)。
環不得觸及其他環,除非在一個點上。
多邊形的元素不能互相觸及。
最後三條規則屬於主觀分類。有其他方式定義多邊形,這些方式都同樣自我一致,但是以上這些規則是 OGC SFSQL 標準使用的規則,PostGIS 符合此標準。
這些規則之所以重要的原因,是因為幾何計算演算法依賴輸入中的一致結構。可以建立不具有任何結構假設的演算法,但是這些例程往往很慢,因為任何無結構例程的第一個步驟就是分析輸入並在其中建立結構。
以下是一個結構很重要的範例。此多邊形無效
POLYGON((0 0, 0 1, 2 1, 2 2, 1 2, 1 0, 0 0));
您可以從這個圖示更清楚地看到其無效性

外環實際上是一個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))

在這個多邊形上執行 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):傳回重新建構的幾何形狀,以遵循有效性規則。