第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 的空间关系函数:

  1. 基本拓扑关系:ST_Equals、ST_Intersects、ST_Contains 等
  2. 距离关系:ST_DWithin、ST_Distance 等
  3. DE-9IM 模型:ST_Relate、模式匹配
  4. 查询优化:边界框预过滤、索引使用
  5. 实际应用:点在面、邻近查询、相邻分析

9.8 下一步

在下一章中,我们将学习空间分析函数,包括:

  • 几何运算(缓冲区、交集、并集)
  • 叠加分析
  • 几何变换
  • 网络分析

相关资源

posted @ 2025-12-29 10:53  我才是银古  阅读(3)  评论(0)    收藏  举报