Chapter 5. 空间查询

Table of Contents

空间数据库存在的理由是在数据库内执行查询,这通常需要桌面 GIS 功能。有效使用 PostGIS 需要了解哪些空间函数可用、如何在查询中使用它们,并确保适当的索引以提供良好的性能。

5.1. 空间关系的确定

空间关系显示两个几何图形如何相互关联。用于查询几何的基本功能。

5.1.1. 维度扩展九交模型(Dimensionally Extended 9-Intersection Model)

根据OpenGIS 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的交集矩阵为:

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

从视觉上看,对于两个重叠的多边形几何图形,如下所示:

 
  内部(Interior) 边界(Boundary) 外部(Exterior)
内部(Interior)

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

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

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

边界(Boundary)

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

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

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

外部(Exterior)

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_Contains, ST_Crosses, ST_Disjoint, ST_Equals, ST_Intersects, ST_Overlaps, ST_Touches, ST_Within。 它还定义了非标准关系谓词 ST_Covers, ST_CoveredBy, 和ST_ContainsProperly

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

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

有关更多详细信息和插图,请参阅PostGIS Workshop.

5.1.3. 一般空间关系

在某些情况下,指定的空间关系不足以提供所需的空间过滤条件。

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

两步解决方案是首先计算空间相交 (ST_Intersection)的道路线对的实际交叉点 (ST_Intersects),然后检查交叉点的 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. 使用空间索引

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

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

索引感知函数会自动将边界框运算符添加到空间条件中。 索引感知函数包括命名空间关系谓词 ST_Contains, ST_ContainsProperly, ST_CoveredBy, ST_Covers, ST_Crosses, ST_Intersects, ST_Overlaps, ST_Touches, ST_Within, ST_Within, 和ST_3DIntersects 以及距离谓词ST_DWithin, ST_DFullyWithin, ST_3DDFullyWithin, 和 ST_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.

道路的总长度是多少公里?

这个问题可以用非常简单的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
...

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

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