第09章-空间关系函数
第09章:空间关系函数
9.1 空间关系概述
空间关系函数用于判断两个几何对象之间的拓扑关系。这些函数返回布尔值,是空间查询的核心。PostGIS 基于 DE-9IM(Dimensionally Extended Nine-Intersection Model)模型实现了丰富的空间关系判断函数。
9.1.1 空间关系分类
┌─────────────────────────────────────────────────────────────┐
│ 空间关系函数分类 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 基本拓扑关系(OGC 标准) │
│ ├── ST_Equals - 相等 │
│ ├── ST_Disjoint - 相离 │
│ ├── ST_Intersects - 相交 │
│ ├── ST_Touches - 相接 │
│ ├── ST_Crosses - 穿越 │
│ ├── ST_Within - 在...内 │
│ ├── ST_Contains - 包含 │
│ ├── ST_Overlaps - 重叠 │
│ └── ST_Covers/CoveredBy - 覆盖 │
│ │
│ 距离关系 │
│ ├── ST_DWithin - 在指定距离内 │
│ ├── ST_DFullyWithin - 完全在指定距离内 │
│ └── ST_Distance - 计算距离 │
│ │
│ 高级关系 │
│ ├── ST_Relate - DE-9IM 关系判断 │
│ ├── ST_RelateMatch - DE-9IM 模式匹配 │
│ └── ST_OrderingEquals - 顺序相等 │
│ │
└─────────────────────────────────────────────────────────────┘
9.1.2 DE-9IM 模型介绍
DE-9IM(Dimensionally Extended Nine-Intersection Model)是描述两个几何对象空间关系的标准模型。
几何 B
Interior Boundary Exterior
┌─────────────────────────────────────┐
Interior │ II │ IB │ IE │
│─────────────────────────────────────│
Boundary │ BI │ BB │ BE │ 几何 A
│─────────────────────────────────────│
Exterior │ EI │ EB │ EE │
└─────────────────────────────────────┘
每个单元格的值可以是:
- T (true): 交集维度 >= 0 (存在交集)
- F (false): 交集为空
- 0: 交集是点 (0维)
- 1: 交集是线 (1维)
- 2: 交集是面 (2维)
- * (任意): 任意值
9.2 基本拓扑关系
9.2.1 ST_Equals(相等)
两个几何在空间上相等(顶点和结构完全相同,顺序可以不同)。
-- 基本用法
SELECT ST_Equals(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(0 0)')
); -- true
-- 顶点顺序不同但几何相等
SELECT ST_Equals(
ST_GeomFromText('LINESTRING(0 0, 1 1)'),
ST_GeomFromText('LINESTRING(1 1, 0 0)')
); -- true(方向不同但几何相等)
-- 多边形顺序不同
SELECT ST_Equals(
ST_GeomFromText('POLYGON((0 0, 1 0, 1 1, 0 1, 0 0))'),
ST_GeomFromText('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))')
); -- true(顺时针/逆时针)
-- 与 = 操作符不同
-- = 比较的是二进制表示,更严格
SELECT ST_GeomFromText('POINT(0 0)') = ST_GeomFromText('POINT(0 0)'); -- true
SELECT ST_GeomFromText('LINESTRING(0 0, 1 1)') = ST_GeomFromText('LINESTRING(1 1, 0 0)'); -- false(方向不同)
9.2.2 ST_Disjoint(相离)
两个几何没有任何公共点。
-- 基本用法
SELECT ST_Disjoint(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(1 1)')
); -- true
SELECT ST_Disjoint(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(2, 2, 3, 3)
); -- true
SELECT ST_Disjoint(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(1, 0, 2, 1)
); -- false(边界相接)
-- 注意:ST_Disjoint 不使用索引!
-- 应该使用 NOT ST_Intersects 代替
SELECT * FROM table_a a, table_b b
WHERE NOT ST_Intersects(a.geom, b.geom); -- 使用索引
9.2.3 ST_Intersects(相交)
两个几何有公共点(ST_Disjoint 的反义)。这是最常用的空间关系函数。
-- 基本用法
SELECT ST_Intersects(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('LINESTRING(-1 0, 1 0)')
); -- true
SELECT ST_Intersects(
ST_MakeEnvelope(0, 0, 2, 2),
ST_MakeEnvelope(1, 1, 3, 3)
); -- true(部分重叠)
-- 查询相交的要素
SELECT b.name
FROM districts a, poi b
WHERE ST_Intersects(a.geom, b.geom)
AND a.name = '东城区';
-- 边界相接也算相交
SELECT ST_Intersects(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(1, 0, 2, 1)
); -- true
-- 使用索引的查询模式
SELECT * FROM poi
WHERE ST_Intersects(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));
-- && 操作符(边界框相交,比 ST_Intersects 更快)
SELECT * FROM poi
WHERE geom && ST_MakeEnvelope(116, 39, 117, 40, 4326)
AND ST_Intersects(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));
9.2.4 ST_Touches(相接)
两个几何的边界相交,但内部不相交。
-- 多边形边界相接
SELECT ST_Touches(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(1, 0, 2, 1)
); -- true
-- 点在线上
SELECT ST_Touches(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('LINESTRING(0 0, 1 1)')
); -- true(点在端点上)
SELECT ST_Touches(
ST_GeomFromText('POINT(0.5 0.5)'),
ST_GeomFromText('LINESTRING(0 0, 1 1)')
); -- false(点在线内部)
-- 点在多边形边界上
SELECT ST_Touches(
ST_GeomFromText('POINT(0 0.5)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 查找相邻的行政区
SELECT a.name AS district_a, b.name AS district_b
FROM districts a, districts b
WHERE a.id < b.id
AND ST_Touches(a.geom, b.geom);
9.2.5 ST_Crosses(穿越)
两个几何相交,且交集的维度小于两者的最大维度。通常用于线与线、线与面的关系。
-- 两条线交叉
SELECT ST_Crosses(
ST_GeomFromText('LINESTRING(0 0, 2 2)'),
ST_GeomFromText('LINESTRING(0 2, 2 0)')
); -- true
-- 线穿过多边形
SELECT ST_Crosses(
ST_GeomFromText('LINESTRING(-1 0.5, 2 0.5)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 线完全在多边形内(不算 cross)
SELECT ST_Crosses(
ST_GeomFromText('LINESTRING(0.2 0.5, 0.8 0.5)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- false
-- 点与线(点是0维,交集是0维,不算 cross)
SELECT ST_Crosses(
ST_GeomFromText('POINT(0.5 0.5)'),
ST_GeomFromText('LINESTRING(0 0, 1 1)')
); -- false
-- 多点与线交叉
SELECT ST_Crosses(
ST_GeomFromText('MULTIPOINT((0 0), (1 1), (2 2))'),
ST_GeomFromText('LINESTRING(0 1, 2 1)')
); -- true(部分点在线的一侧,部分在另一侧)
-- 查找穿越河流的道路
SELECT r.name AS road_name
FROM roads r, rivers rv
WHERE ST_Crosses(r.geom, rv.geom);
9.2.6 ST_Within(在...内)
几何 A 完全在几何 B 内部(包括边界)。
-- 点在多边形内
SELECT ST_Within(
ST_GeomFromText('POINT(0.5 0.5)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 点在边界上也算 within
SELECT ST_Within(
ST_GeomFromText('POINT(0 0)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 小多边形在大多边形内
SELECT ST_Within(
ST_MakeEnvelope(0.2, 0.2, 0.8, 0.8),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 查找某区域内的所有 POI
SELECT p.*
FROM poi p, districts d
WHERE d.name = '东城区'
AND ST_Within(p.geom, d.geom);
-- ST_Within(A, B) 等价于 ST_Contains(B, A)
SELECT
ST_Within(point_geom, polygon_geom) AS within_result,
ST_Contains(polygon_geom, point_geom) AS contains_result
FROM test_data;
9.2.7 ST_Contains(包含)
几何 A 包含几何 B(B 在 A 内部)。ST_Within 的反向操作。
-- 多边形包含点
SELECT ST_Contains(
ST_MakeEnvelope(0, 0, 1, 1),
ST_GeomFromText('POINT(0.5 0.5)')
); -- true
-- 边界上的点
SELECT ST_Contains(
ST_MakeEnvelope(0, 0, 1, 1),
ST_GeomFromText('POINT(0 0)')
); -- true(边界上也算包含)
-- 查找包含特定点的行政区
SELECT name
FROM districts
WHERE ST_Contains(geom, ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326));
-- 查找完全包含某道路的区域
SELECT d.name
FROM districts d, roads r
WHERE r.name = '长安街'
AND ST_Contains(d.geom, r.geom);
9.2.8 ST_Overlaps(重叠)
两个几何有公共区域,但互不包含,且交集的维度与原几何相同。
-- 两个重叠的多边形
SELECT ST_Overlaps(
ST_MakeEnvelope(0, 0, 2, 2),
ST_MakeEnvelope(1, 1, 3, 3)
); -- true
-- 一个包含另一个(不算 overlap)
SELECT ST_Overlaps(
ST_MakeEnvelope(0, 0, 3, 3),
ST_MakeEnvelope(1, 1, 2, 2)
); -- false
-- 两条重叠的线
SELECT ST_Overlaps(
ST_GeomFromText('LINESTRING(0 0, 2 2)'),
ST_GeomFromText('LINESTRING(1 1, 3 3)')
); -- true
-- 查找与指定区域重叠的区域
SELECT b.name
FROM districts a, districts b
WHERE a.name = '东城区'
AND a.id != b.id
AND ST_Overlaps(a.geom, b.geom);
9.2.9 ST_Covers 和 ST_CoveredBy
ST_Covers:A 覆盖 B(B 的所有点都在 A 上)
ST_CoveredBy:A 被 B 覆盖
与 ST_Contains/ST_Within 的区别在于边界的处理。
-- 对于边界上的点,Covers 和 Contains 结果相同
SELECT ST_Covers(
ST_MakeEnvelope(0, 0, 1, 1),
ST_GeomFromText('POINT(0 0)')
); -- true
SELECT ST_Contains(
ST_MakeEnvelope(0, 0, 1, 1),
ST_GeomFromText('POINT(0 0)')
); -- true
-- 但对于空几何,行为可能不同
-- ST_Covers 在某些边界情况下更宽松
-- CoveredBy 是 Covers 的反向
SELECT ST_CoveredBy(
ST_GeomFromText('POINT(0.5 0.5)'),
ST_MakeEnvelope(0, 0, 1, 1)
); -- true
-- 查询被某区域完全覆盖的要素
SELECT * FROM features
WHERE ST_CoveredBy(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));
9.3 距离关系
9.3.1 ST_DWithin(在指定距离内)
判断两个几何的距离是否在指定范围内。这是空间查询中最重要的函数之一,支持索引加速。
-- 基本用法(Geometry,单位与 SRID 相关)
SELECT ST_DWithin(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(1 1)'),
2 -- 距离阈值
); -- true
-- 使用 Geography(单位是米)
SELECT ST_DWithin(
ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)::geography,
ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)::geography,
1000 -- 1000米
);
-- 查找某点 1 公里范围内的 POI
SELECT name, ST_Distance(geom::geography, query_point::geography) AS distance_m
FROM poi,
(SELECT ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326) AS query_point) q
WHERE ST_DWithin(geom::geography, query_point::geography, 1000)
ORDER BY distance_m;
-- 查找相互距离在 500 米内的 POI 对
SELECT a.name AS poi_a, b.name AS poi_b,
ST_Distance(a.geom::geography, b.geom::geography) AS distance_m
FROM poi a, poi b
WHERE a.id < b.id
AND ST_DWithin(a.geom::geography, b.geom::geography, 500);
-- 使用 use_spheroid 参数(Geography)
-- 默认 true 使用椭球体计算(更精确但慢)
-- false 使用球体计算(快但略有误差)
SELECT ST_DWithin(
point1::geography,
point2::geography,
1000,
false -- 使用球体计算
);
9.3.2 ST_DFullyWithin
判断 A 的所有点是否都在 B 的指定距离内。
-- 线完全在点的指定距离内
SELECT ST_DFullyWithin(
ST_GeomFromText('LINESTRING(0 0, 1 1)'),
ST_GeomFromText('POINT(0.5 0.5)'),
1
); -- true
-- 应用:查找完全在服务范围内的道路
SELECT r.name
FROM roads r, service_centers s
WHERE ST_DFullyWithin(r.geom, s.geom, 5000); -- 5000米服务半径
9.3.3 ST_Distance
计算两个几何之间的最短距离。
-- 基本用法
SELECT ST_Distance(
ST_GeomFromText('POINT(0 0)'),
ST_GeomFromText('POINT(3 4)')
); -- 5 (欧几里得距离)
-- Geography 类型(返回米)
SELECT ST_Distance(
ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)::geography,
ST_SetSRID(ST_MakePoint(121.4737, 31.2304), 4326)::geography
); -- 约 1067799 米(北京到上海)
-- 点到线的距离
SELECT ST_Distance(
ST_GeomFromText('POINT(1 1)'),
ST_GeomFromText('LINESTRING(0 0, 2 0)')
); -- 1
-- 查找最近的 POI
SELECT name, ST_Distance(geom::geography, query_point::geography) AS distance_m
FROM poi,
(SELECT ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326) AS query_point) q
ORDER BY geom::geography <-> query_point::geography
LIMIT 5;
9.4 DE-9IM 高级关系
9.4.1 ST_Relate
计算两个几何的 DE-9IM 矩阵,或判断是否满足特定模式。
-- 计算 DE-9IM 矩阵
SELECT ST_Relate(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(1, 0, 2, 1)
); -- FF2F11212 (表示相接关系)
-- 检查是否匹配特定模式
SELECT ST_Relate(
ST_MakeEnvelope(0, 0, 1, 1),
ST_MakeEnvelope(1, 0, 2, 1),
'FF2F11212'
); -- true
-- 使用通配符模式
-- T: 交集存在
-- F: 交集为空
-- *: 任意
-- 0, 1, 2: 特定维度
-- 检查相交关系 (T********, any intersection)
SELECT ST_Relate(
ST_MakeEnvelope(0, 0, 2, 2),
ST_MakeEnvelope(1, 1, 3, 3),
'T********'
); -- true
-- 检查包含关系 (T*F**FFF*)
SELECT ST_Relate(
ST_MakeEnvelope(0, 0, 2, 2),
ST_MakeEnvelope(0.5, 0.5, 1.5, 1.5),
'T*F**FFF*'
); -- true
9.4.2 常用 DE-9IM 模式
-- 等价于 ST_Equals
-- T*F**FFF*
SELECT ST_Relate(a, b, 'T*F**FFF*');
-- 等价于 ST_Disjoint
-- FF*FF****
SELECT ST_Relate(a, b, 'FF*FF****');
-- 等价于 ST_Intersects(Disjoint 的反义)
-- 不是 FF*FF****
SELECT NOT ST_Relate(a, b, 'FF*FF****');
-- 等价于 ST_Touches
-- FT*******, F**T*****, F***T****
SELECT ST_Relate(a, b, 'FT*******') OR
ST_Relate(a, b, 'F**T*****') OR
ST_Relate(a, b, 'F***T****');
-- 等价于 ST_Crosses(线与线)
-- 0********
SELECT ST_Relate(line_a, line_b, '0********');
-- 等价于 ST_Within
-- T*F**F***
SELECT ST_Relate(a, b, 'T*F**F***');
-- 等价于 ST_Contains
-- T*****FF*
SELECT ST_Relate(a, b, 'T*****FF*');
-- 等价于 ST_Overlaps(多边形与多边形)
-- T*T***T**
SELECT ST_Relate(poly_a, poly_b, 'T*T***T**');
9.4.3 ST_RelateMatch
直接使用 DE-9IM 矩阵字符串进行模式匹配。
-- 判断给定的 DE-9IM 矩阵是否匹配模式
SELECT ST_RelateMatch('101202FF2', 'TTTTTTFFF'); -- true
-- 实际应用
SELECT a.name, b.name,
ST_Relate(a.geom, b.geom) AS de9im,
ST_RelateMatch(ST_Relate(a.geom, b.geom), 'T********') AS intersects
FROM table_a a, table_b b;
9.5 空间关系查询优化
9.5.1 使用边界框预过滤
-- 推荐:先用 && 过滤,再精确判断
SELECT * FROM poi
WHERE geom && ST_MakeEnvelope(116, 39, 117, 40, 4326)
AND ST_Intersects(geom, some_polygon);
-- ST_Intersects 内部会自动先检查边界框
-- 但显式使用 && 可以更好地利用索引
-- 查看执行计划
EXPLAIN ANALYZE
SELECT * FROM poi
WHERE ST_Intersects(geom, ST_MakeEnvelope(116, 39, 117, 40, 4326));
9.5.2 避免使用 NOT 和 ST_Disjoint
-- 差:ST_Disjoint 不使用索引
SELECT * FROM table_a WHERE ST_Disjoint(geom, query_geom);
-- 好:使用 NOT ST_Intersects
SELECT * FROM table_a WHERE NOT ST_Intersects(geom, query_geom);
-- 更好:先用 && 判断边界框
SELECT * FROM table_a
WHERE NOT (geom && query_geom AND ST_Intersects(geom, query_geom));
9.5.3 空间连接优化
-- 基本空间连接
SELECT a.*, b.*
FROM table_a a, table_b b
WHERE ST_Intersects(a.geom, b.geom);
-- 优化:确保两边都有空间索引
CREATE INDEX idx_a_geom ON table_a USING GIST (geom);
CREATE INDEX idx_b_geom ON table_b USING GIST (geom);
-- 优化:使用 JOIN 语法更清晰
SELECT a.*, b.*
FROM table_a a
JOIN table_b b ON ST_Intersects(a.geom, b.geom);
-- 优化:限制结果数量
SELECT a.*, b.*
FROM table_a a
JOIN LATERAL (
SELECT * FROM table_b b
WHERE ST_Intersects(a.geom, b.geom)
LIMIT 10
) b ON true;
9.6 实际应用示例
9.6.1 点在面查询
-- 查找某点所在的行政区
SELECT d.name AS district_name
FROM districts d
WHERE ST_Contains(d.geom, ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326));
-- 批量查询每个 POI 所在的行政区
SELECT p.id, p.name AS poi_name, d.name AS district_name
FROM poi p
LEFT JOIN districts d ON ST_Contains(d.geom, p.geom);
9.6.2 邻近查询
-- 查找最近的 5 个医院
SELECT name, ST_Distance(geom::geography, query_point::geography) AS distance_m
FROM poi,
(SELECT ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326) AS query_point) q
WHERE category = '医院'
ORDER BY geom <-> query_point
LIMIT 5;
-- 查找 1 公里范围内的所有餐厅
SELECT name, ST_Distance(geom::geography, query_point::geography) AS distance_m
FROM poi,
(SELECT ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326) AS query_point) q
WHERE category = '餐厅'
AND ST_DWithin(geom::geography, query_point::geography, 1000)
ORDER BY distance_m;
9.6.3 相邻区域分析
-- 查找所有相邻的行政区对
SELECT a.name AS district_a, b.name AS district_b
FROM districts a, districts b
WHERE a.id < b.id
AND ST_Touches(a.geom, b.geom);
-- 查找某区域的所有相邻区域
SELECT b.name AS neighbor
FROM districts a, districts b
WHERE a.name = '东城区'
AND a.id != b.id
AND (ST_Touches(a.geom, b.geom) OR ST_Overlaps(a.geom, b.geom));
9.6.4 道路网络分析
-- 查找穿过某区域的道路
SELECT r.name, ST_Length(ST_Intersection(r.geom, d.geom)::geography) AS length_in_district
FROM roads r, districts d
WHERE d.name = '东城区'
AND ST_Intersects(r.geom, d.geom);
-- 查找道路交叉点
SELECT a.name AS road_a, b.name AS road_b,
ST_AsText(ST_Intersection(a.geom, b.geom)) AS intersection_point
FROM roads a, roads b
WHERE a.id < b.id
AND ST_Crosses(a.geom, b.geom);
9.7 本章小结
本章详细介绍了 PostGIS 的空间关系函数:
- 基本拓扑关系:ST_Equals、ST_Intersects、ST_Contains 等
- 距离关系:ST_DWithin、ST_Distance 等
- DE-9IM 模型:ST_Relate、模式匹配
- 查询优化:边界框预过滤、索引使用
- 实际应用:点在面、邻近查询、相邻分析
9.8 下一步
在下一章中,我们将学习空间分析函数,包括:
- 几何运算(缓冲区、交集、并集)
- 叠加分析
- 几何变换
- 网络分析
相关资源:

浙公网安备 33010602011771号