第 5 章 空间查询

目录
5.1. 确定空间关系
5.1.1. 维度扩展的 9 交集模型
5.1.2. 命名空间关系
5.1.3. 一般空间关系
5.2. 使用空间索引
5.3. 空间 SQL 示例

空间数据库的 raison d'etre 是在数据库内执行通常需要桌面 GIS 功能的查询。有效地使用 PostGIS 需要了解可用的空间函数、如何在查询中使用它们以及确保适当的索引到位以提供良好的性能。

5.1. 确定空间关系

空间关系表示两个几何体如何相互作用。它们是查询几何体的基本功能。

5.1.1. 维度扩展的 9 交集模型

根据 OpenGIS 简单要素实现规范 for SQL,“比较两个几何体的基本方法是对两个几何体的内部、边界和外部之间的交集进行成对测试,并根据结果“交集”矩阵中的条目对两个几何体之间的关系进行分类。”

在点集拓扑理论中,嵌入在二维空间中的几何体中的点被归类为三个集合

边界

几何体的边界是下一维几何体的集合。对于维度为 0 的 POINT,边界为空集。LINESTRING 的边界是两个端点。对于 POLYGON,边界是外部和内部环的线段。

内部

几何图形的内部是指几何图形中不属于边界的所有点。对于 POINT,内部就是点本身。 LINESTRING 的内部是端点之间的所有点。对于 POLYGON,内部是多边形内的面积表面。

外部

几何图形的外部是指嵌入几何图形的空间的其余部分;换句话说,所有不在几何图形的内部或边界上的点。它是一个二维的非封闭表面。

维扩展九交集模型 (DE-9IM) 通过指定每个几何图形的上述集合之间的 9 个交集的维度来描述两个几何图形之间的空间关系。交集维度可以用 3x3 的 交集矩阵 正式表示。

对于几何图形 g内部边界外部 分别用符号 I(g)B(g)E(g) 表示。此外,dim(s) 表示集合 s 的维度,其域为 {0,1,2,F}

  • 0 => 点

  • 1 => 线

  • 2 => 面积

  • F => 空集

使用此符号,两个几何图形 ab 的交集矩阵为

 内部边界外部
内部dim( I(a) ∩ I(b) )dim( I(a) ∩ B(b) )dim( I(a) ∩ E(b) )
边界dim( B(a) ∩ I(b) )dim( B(a) ∩ B(b) )dim( B(a) ∩ E(b) )
外部dim( E(a) ∩ I(b) )dim( E(a) ∩ B(b) )dim( E(a) ∩ E(b) )

直观地,对于两个重叠的多边形几何图形,它看起来像

 

 内部边界外部
内部

dim( I(a) ∩ I(b) ) = 2

dim( I(a) ∩ B(b) = 1

dim( I(a) ∩ E(b) ) = 2

边界

dim( B(a) ∩ I(b) ) = 1

dim( B(a) ∩ B(b) ) = 0

dim( B(a) ∩ E(b) ) = 1

外部

dim( E(a) ∩ I(b) ) = 2

dim( E(a) ∩ B(b) ) = 1

dim( E(a) ∩ E(b) = 2

从左到右,从上到下读取,交集矩阵表示为文本字符串 '212101212'。

有关更多信息,请参阅

5.1.2. 命名空间关系

为了便于确定常见的空间关系,OGC SFS 定义了一组命名空间关系谓词。PostGIS 将这些谓词提供为函数 ST_ContainsST_CrossesST_DisjointST_EqualsST_IntersectsST_OverlapsST_TouchesST_Within。它还定义了非标准关系谓词 ST_CoversST_CoveredByST_ContainsProperly

空间谓词通常用作 SQL WHEREJOIN 子句中的条件。命名空间谓词在可用时会自动使用空间索引,因此无需使用边界框运算符 &&。例如

SELECT city.name, state.name, city.geom
FROM city JOIN state ON ST_Intersects(city.geom, state.geom);

有关更多详细信息和说明,请参阅 PostGIS 工作坊。

5.1.3. 一般空间关系

在某些情况下,命名空间关系不足以提供所需的空間筛选条件。

例如,考虑一个表示道路网络的线性数据集。可能需要识别所有相互交叉的道路段,但不是在一点上交叉,而是在一条线上交叉(也许是为了验证某些业务规则)。在这种情况下,ST_Crosses 不能提供必要的空间过滤器,因为对于线性要素,它只在它们在一点上交叉时返回 true

一个两步解决方案是首先计算道路线对的空间相交 (ST_Intersects) 的实际交点 (ST_Intersection),然后检查交点的 ST_GeometryType 是否为 'LINESTRING'(正确处理返回 GEOMETRYCOLLECTION[MULTI]POINT[MULTI]LINESTRING 等情况)。

显然,需要一个更简单、更快的解决方案。

第二个例子是定位与湖泊边界在一条线上相交的码头,并且码头的一端位于岸上。换句话说,码头位于湖泊内但没有完全包含在湖泊内,与湖泊边界在一条线上相交,并且码头的一个端点正好位于湖泊边界内或边界上。可以使用空间谓词的组合来查找所需的要素

这些需求可以通过计算完整的 DE-9IM 交点矩阵来满足。PostGIS 提供了 ST_Relate 函数来执行此操作

SELECT ST_Relate( 'LINESTRING (1 1, 5 5)',
                  'POLYGON ((3 3, 3 7, 7 7, 7 3, 3 3))' );
st_relate
-----------
1010F0212

为了测试特定的空间关系,使用一个交集矩阵模式。这是矩阵表示,并添加了额外的符号{T,*}

  • T => 交集维度非空;即在{0,1,2}

  • * => 不关心

使用交集矩阵模式,可以更简洁地评估特定的空间关系。可以使用ST_RelateST_RelateMatch函数来测试交集矩阵模式。对于上面的第一个示例,指定两条线在一条线上相交的交集矩阵模式是'1*1***1**'

-- Find road segments that intersect in a line
SELECT a.id
FROM roads a, roads b
WHERE a.id != b.id
      AND a.geom && b.geom
      AND ST_Relate(a.geom, b.geom, '1*1***1**');

对于第二个示例,指定一条线部分在多边形内,部分在多边形外的交集矩阵模式是'102101FF2'

-- Find wharves partly on a lake's shoreline
SELECT a.lake_id, b.wharf_id
FROM lakes a, wharfs b
WHERE a.geom && b.geom
      AND ST_Relate(a.geom, b.geom, '102101FF2');

5.2. 使用空间索引

在构建使用空间条件的查询时,为了获得最佳性能,重要的是要确保使用空间索引(如果存在)(参见第 4.9 节,“空间索引”)。为此,必须在查询的WHEREON子句中使用空间运算符或索引感知函数。

空间运算符包括边界框运算符(其中最常用的运算符是&&;有关完整列表,请参见第 7.10.1 节,“边界框运算符”)和用于最近邻查询的距离运算符(最常见的是<->;有关完整列表,请参见第 7.10.2 节,“距离运算符”)。

索引感知函数会自动将边界框运算符添加到空间条件中。索引感知函数包括命名空间关系谓词ST_ContainsST_ContainsProperlyST_CoveredByST_CoversST_CrossesST_IntersectsST_OverlapsST_TouchesST_WithinST_WithinST_3DIntersects,以及距离谓词ST_DWithinST_DFullyWithinST_3DDFullyWithinST_3DDWithin

诸如ST_Distance之类的函数使用索引来优化其操作。例如,以下查询在大型表上会非常慢

SELECT geom
FROM geom_table
WHERE ST_Distance( geom, 'SRID=312;POINT(100000 200000)' ) < 100

此查询选择geom_table中所有距离点 (100000, 200000) 100 个单位内的几何图形。它会很慢,因为它正在计算表中每个点与指定点之间的距离,即为表中的每一行计算一个ST_Distance()计算。

通过使用索引感知函数 ST_DWithin,可以大幅减少处理的行数。

SELECT geom
FROM geom_table
WHERE ST_DWithin( geom, 'SRID=312;POINT(100000 200000)', 100 )

此查询选择相同的几何图形,但以更有效的方式进行。这是通过 ST_DWithin() 在查询几何图形的扩展边界框上内部使用 && 运算符来实现的。如果 geom 上存在空间索引,查询计划器将识别出它可以使用索引来减少在计算距离之前扫描的行数。空间索引允许仅检索几何图形的边界框与扩展范围重叠的记录,因此这些记录可能在所需的距离内。然后计算实际距离以确认是否将记录包含在结果集中。

有关更多信息和示例,请参阅 PostGIS Workshop

5.3. 空间 SQL 示例

本节中的示例使用线性道路表和多边形市政边界表。 bc_roads 表的定义为

Column    | Type              | Description
----------+-------------------+-------------------
gid       | integer           | Unique ID
name      | character varying | Road Name
geom      | geometry          | Location Geometry (Linestring)

bc_municipality 表的定义为

Column   | Type              | Description
---------+-------------------+-------------------
gid      | integer           | Unique ID
code     | integer           | Unique ID
name     | character varying | City / Town Name
geom     | geometry          | Location Geometry (Polygon)
5.3.1. 所有道路的总长度是多少,以公里为单位?
5.3.2. 王子乔治市的面积有多大,以公顷为单位?
5.3.3. 该省面积最大的市政区域是什么?
5.3.4. 完全包含在每个市政区域内的道路长度是多少?
5.3.5. 创建一个包含王子乔治市所有道路的新表。
5.3.6. 维多利亚的“道格拉斯街”的长度是多少公里?
5.3.7. 具有孔洞的最大市政区域多边形是什么?

5.3.1.

所有道路的总长度是多少,以公里为单位?

您可以使用非常简单的 SQL 语句来回答这个问题

SELECT sum(ST_Length(geom))/1000 AS km_roads FROM bc_roads;

km_roads
------------------
70842.1243039643

5.3.2.

王子乔治市的面积有多大,以公顷为单位?

此查询将属性条件(在市政区域名称上)与空间计算(多边形面积)结合起来。

SELECT
  ST_Area(geom)/10000 AS hectares
FROM bc_municipality
WHERE name = 'PRINCE GEORGE';

hectares
------------------
32657.9103824927

5.3.3.

该省面积最大的市政区域是什么?

此查询使用空间度量作为排序值。解决此问题的方法有很多,但最有效的方法如下

SELECT
  name,
  ST_Area(geom)/10000 AS hectares
FROM bc_municipality
ORDER BY hectares DESC
LIMIT 1;

name           | hectares
---------------+-----------------
TUMBLER RIDGE  | 155020.02556131

请注意,为了回答此查询,我们必须计算每个多边形的面积。如果我们经常这样做,那么将面积列添加到表中以用于索引性能将是有意义的。通过以降序排列结果,然后使用 PostgreSQL 的“LIMIT”命令,我们可以轻松地选择最大值,而无需使用像 MAX() 这样的聚合函数。

5.3.4.

每个市区内完全包含的道路长度是多少?

这是一个“空间连接”的例子,它使用空间交互(“包含”)作为连接条件(而不是通常基于共同键的关联方法)将来自两个表的数据连接在一起。

SELECT
  m.name,
  sum(ST_Length(r.geom))/1000 as roads_km
FROM bc_roads AS r
JOIN bc_municipality AS m
  ON ST_Contains(m.geom, r.geom)
GROUP BY m.name
ORDER BY roads_km;

name                        | roads_km
----------------------------+------------------
SURREY                      | 1539.47553551242
VANCOUVER                   | 1450.33093486576
LANGLEY DISTRICT            | 833.793392535662
BURNABY                     | 773.769091404338
PRINCE GEORGE               | 694.37554369147
...

此查询需要一段时间,因为表中的每条道路都会汇总到最终结果中(示例表中约有 250,000 条道路)。对于较小的数据集(几千条记录,几百条记录),响应速度非常快。

5.3.5.

创建一个包含王子乔治市所有道路的新表。

这是一个“叠加”的例子,它接收两个表并输出一个新的表,该表包含空间裁剪或切割的结果。与上面演示的“空间连接”不同,此查询创建新的几何图形。叠加就像一个增强的空间连接,对于更精确的分析工作很有用。

CREATE TABLE pg_roads as
SELECT
  ST_Intersection(r.geom, m.geom) AS intersection_geom,
  ST_Length(r.geom) AS rd_orig_length,
  r.*
FROM bc_roads AS r
JOIN bc_municipality AS m
  ON ST_Intersects(r.geom, m.geom)
WHERE
  m.name = 'PRINCE GEORGE';

5.3.6.

维多利亚的“道格拉斯街”的长度是多少公里?

SELECT
  sum(ST_Length(r.geom))/1000 AS kilometers
FROM bc_roads r
JOIN bc_municipality m
  ON ST_Intersects(m.geom, r.geom
WHERE
  r.name = 'Douglas St'
  AND m.name = 'VICTORIA';

kilometers
------------------
4.89151904172838

5.3.7.

哪个市区多边形最大,并且包含一个洞?

SELECT gid, name, ST_Area(geom) AS area
FROM bc_municipality
WHERE ST_NRings(geom) > 1
ORDER BY area DESC LIMIT 1;

gid  | name         | area
-----+--------------+------------------
12   | SPALLUMCHEEN | 257374619.430216