空间数据库存在的理由是在数据库内执行查询,这通常需要桌面 GIS 功能。有效使用 PostGIS 需要了解哪些空间函数可用、如何在查询中使用它们,并确保适当的索引以提供良好的性能。
空间关系显示两个几何图形如何相互关联。用于查询几何的基本功能。
根据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
=>空集合
使用此表示法,两个几何a和b的交集矩阵为:
内部(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) ) |
从视觉上看,对于两个重叠的多边形几何图形,如下所示:
|
||||||||||||||||||
|
|
从左到右,从上到下阅读。交集矩阵的字符串表示形式为“212101212”。
欲了解更多信息,请访问:
适用于 SQL 的 OpenGIS 简单功能实现规范 (1.1版本, 第 2.1.13.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 WHERE
或JOIN
子句中的条件。如果空间索引可用,命名空间谓词会自动使用空间索引,因此无需使用边界框运算符&&
。例如:
SELECT city.name, state.name, city.geom FROM city JOIN state ON ST_Intersects(city.geom, state.geom);
有关更多详细信息和插图,请参阅PostGIS Workshop.
在某些情况下,指定的空间关系不足以提供所需的空间过滤条件。
例如,考虑表示道路网络的线性数据集。 可能需要识别所有相互交叉的路段,不是在一个点,而是在一条线上(也许是为了验证某些业务规则)。 在这种情况下,ST_Crosses不提供必要的空间过滤器,因为对于线性要素,它仅在它们在某个点交叉的地方返回 两步解决方案是首先计算空间相交 (ST_Intersection)的道路线对的实际交叉点 (ST_Intersects),然后检查交叉点的 ST_GeometryType 是否为“ 显然,更简单、更快捷的解决方案更可取。 |
第二个例子是定位与湖泊边界相交的码头,并且码头的一端位于岸上。换句话说,如果码头位于湖泊内但未完全被湖泊包围,则与湖泊边界相交于一条线,并且码头的端点之一恰好位于湖泊边界内或湖泊边界上。 可以使用空间谓词的组合来查找所需的特征:
|
这些要求可以通过计算完整的 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_Relate和ST_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');
使用空间条件构建查询时,为了获得最佳性能,确保使用空间索引(如果存在)非常重要(请参阅Section 4.9, “空间索引”)。为此,必须在查询的WHERE
或ON
子句中使用空间运算符或索引感知函数。
空间运算符包括边界框运算符(其中最常用的是&&; 有关完整列表,请参见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。
本节中的示例使用线性道路表和多边形城市边界表。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 |