PostGIS空间查询语句怎么写?常用函数有哪些?

GIS基础理论
Dr.GIS
wowwwai GIS研习社 · 工具流程与项目排障

引言

“PostGIS空间查询语句怎么写?常用函数有哪些?”是很多 GIS 初学者、空间数据分析师和后端开发者第一次接触 PostGIS 时最常遇到的问题。PostGIS 的空间查询并不只是把经纬度字段拿出来比较,而是通过几何对象、空间关系函数和空间索引来完成点、线、面之间的判断、筛选和统计。

本文以实际项目中最常见的查询场景为主线,讲清楚 PostGIS空间查询语句 的基本写法、常用空间函数、索引使用方式,以及容易导致查询慢或结果不准的常见坑。

PostGIS空间查询语句和PostGIS常用空间函数流程示意图
PostGIS 空间查询通常由空间数据表、空间索引和空间关系函数共同完成。

背景:PostGIS空间查询到底在查什么

在普通 SQL 中,我们经常查询的是数字、文本、日期等字段,例如“人口大于 10000 的城市”。而在 PostGIS 中,查询对象变成了空间几何对象,例如点、线、面、多边形、缓冲区和行政区边界。

常见的 PostGIS空间查询语句 包括:

  • 查询某个点落在哪个行政区内。
  • 查询一条道路经过哪些地块。
  • 查询距离河流 500 米以内的村庄。
  • 统计每个区县内的 POI 数量。
  • 筛选与规划红线相交的建设用地。

这些问题本质上都是在判断几何对象之间的空间关系。PostGIS 提供了大量以 ST_ 开头的函数,用来表达“相交、包含、距离、邻接、缓冲、合并、裁剪”等空间操作。

原理:PostGIS空间查询语句的基本结构

一条典型的 PostGIS空间查询语句 通常由三部分组成:普通 SQL 条件、空间函数、空间索引友好的过滤条件。

SELECT a.*
FROM table_a AS a
JOIN table_b AS b
ON ST_Intersects(a.geom, b.geom)
WHERE b.name = '某个区域';

这里的 geom 是空间字段,通常保存点、线或面几何对象。ST_Intersects 是空间关系函数,用于判断两个几何对象是否相交。

PostGIS 查询的核心逻辑可以理解为:

  • 先确定要查询的空间表和几何字段。
  • 再选择合适的空间关系函数。
  • 如果数据量较大,需要确保几何字段已经创建空间索引。
  • 最后用 SQL 的 WHEREJOINGROUP BY 完成筛选和统计。

步骤:PostGIS空间查询语句怎么写

步骤一:确认空间字段和坐标系

在写 PostGIS空间查询语句 之前,先确认表中是否存在几何字段,以及几何字段的坐标系 SRID。

SELECT 
  f_table_name,
  f_geometry_column,
  type,
  srid
FROM geometry_columns
WHERE f_table_name = 'poi';

也可以直接查看某一条数据的 SRID:

SELECT ST_SRID(geom)
FROM poi
LIMIT 1;

如果两个表的 SRID 不一致,空间关系判断可能失败或结果不准确。常见做法是使用 ST_Transform 统一坐标系。

SELECT *
FROM poi
WHERE ST_Intersects(
  ST_Transform(poi.geom, 3857),
  ST_Transform(ST_GeomFromText('POLYGON((...))', 4326), 3857)
);

步骤二:创建空间索引

如果空间表数据量较大,必须为几何字段创建空间索引。PostGIS 常用的是 GiST 索引。

CREATE INDEX idx_poi_geom
ON poi
USING GIST (geom);

创建索引后,建议执行统计信息更新:

ANALYZE poi;

空间索引不是让所有空间函数都自动变快,而是让 PostGIS 能先用外包矩形快速过滤候选对象,再进行精确空间判断。

步骤三:查询点落在哪个面内

这是 PostGIS 最常见的空间查询之一。例如,查询每个 POI 点位所在的行政区。

SELECT 
  p.id,
  p.name AS poi_name,
  a.name AS district_name
FROM poi AS p
JOIN district AS a
ON ST_Within(p.geom, a.geom);

ST_Within(point, polygon) 表示第一个几何对象是否完全位于第二个几何对象内部。对于“点在面内”问题,也可以使用 ST_Contains(a.geom, p.geom),但参数顺序相反。

SELECT 
  p.id,
  p.name AS poi_name,
  a.name AS district_name
FROM poi AS p
JOIN district AS a
ON ST_Contains(a.geom, p.geom);

步骤四:查询两个图层是否相交

如果要查询道路穿过哪些地块,可以使用 ST_Intersects

SELECT 
  r.id AS road_id,
  r.name AS road_name,
  l.id AS land_id,
  l.type AS land_type
FROM roads AS r
JOIN land_parcels AS l
ON ST_Intersects(r.geom, l.geom);

ST_Intersects 是 PostGIS常用空间函数 中使用频率非常高的一个,适合判断点、线、面之间是否存在任意空间接触或重叠。

步骤五:查询一定距离范围内的对象

例如,查询距离学校 1000 米以内的公交站点。推荐使用 ST_DWithin,而不是先做缓冲区再相交。

SELECT 
  s.id,
  s.name
FROM bus_stops AS s
JOIN schools AS c
ON ST_DWithin(s.geom, c.geom, 1000)
WHERE c.name = '第一中学';

需要注意:这里的 1000 使用的是当前坐标系单位。如果数据是 EPSG:4326 经纬度坐标,单位是度,不是米。距离查询建议使用投影坐标系,或者使用 geography 类型。

SELECT 
  s.id,
  s.name
FROM bus_stops AS s
JOIN schools AS c
ON ST_DWithin(
  s.geom::geography,
  c.geom::geography,
  1000
)
WHERE c.name = '第一中学';

步骤六:按行政区统计点数量

空间查询经常和统计分析结合使用。例如,统计每个区县内有多少个 POI。

SELECT 
  a.name AS district_name,
  COUNT(p.id) AS poi_count
FROM district AS a
LEFT JOIN poi AS p
ON ST_Contains(a.geom, p.geom)
GROUP BY a.name
ORDER BY poi_count DESC;

这里使用 LEFT JOIN 是为了保留没有 POI 的行政区。如果使用 JOIN,没有匹配点位的行政区会被过滤掉。

步骤七:裁剪相交区域

如果不仅要判断是否相交,还要得到实际重叠部分,可以使用 ST_Intersection

SELECT 
  l.id,
  ST_Intersection(l.geom, p.geom) AS clipped_geom
FROM land_parcels AS l
JOIN planning_area AS p
ON ST_Intersects(l.geom, p.geom);

ST_Intersection 会生成新的几何对象,计算成本通常比 ST_Intersects 高。因此建议先用 ST_Intersects 过滤,再计算交集。

常见坑:PostGIS空间查询结果不对或查询很慢

坑一:经纬度坐标直接按米计算距离

很多新手会在 EPSG:4326 数据上直接写:

SELECT *
FROM poi
WHERE ST_DWithin(geom, ST_SetSRID(ST_MakePoint(116.39, 39.90), 4326), 1000);

这通常是错误的,因为 EPSG:4326 的单位是度,不是米。正确做法是转换到合适的投影坐标系,或使用 geography。

坑二:没有创建空间索引

如果表中有几十万或几百万条记录,却没有为 geom 字段创建 GiST 索引,空间查询会非常慢。检查索引可以使用:

SELECT indexname, indexdef
FROM pg_indexes
WHERE tablename = 'poi';

坑三:几何对象无效

自相交、多边形环方向异常、重复节点等问题可能导致空间函数返回异常结果。可以用 ST_IsValid 检查。

SELECT id, ST_IsValidReason(geom)
FROM land_parcels
WHERE NOT ST_IsValid(geom);

简单修复可以尝试:

UPDATE land_parcels
SET geom = ST_MakeValid(geom)
WHERE NOT ST_IsValid(geom);

坑四:ST_Within 和 ST_Contains 参数顺序写反

ST_Within(a, b) 表示 a 在 b 内部;ST_Contains(a, b) 表示 a 包含 b。写点在面内查询时,下面两种写法含义相近但参数顺序不同:

ST_Within(point_geom, polygon_geom)
ST_Contains(polygon_geom, point_geom)

如果把顺序写反,通常会查不到结果。

坑五:在索引字段外层套太多函数

如果在查询条件中频繁对表字段做 ST_Transform,可能影响索引利用。对于高频查询,建议提前生成统一坐标系的数据列,或建立表达式索引。

CREATE INDEX idx_poi_geom_3857
ON poi
USING GIST (ST_Transform(geom, 3857));

方法比较:PostGIS常用空间函数有哪些

函数 用途 典型场景 注意事项
ST_Intersects 判断两个几何对象是否相交 道路与地块相交、面与面叠加筛选 只判断是否有交集,不返回交集结果
ST_Within 判断一个几何对象是否在另一个对象内部 点落入行政区、建筑物位于地块内 参数顺序是小对象在前、大范围在后
ST_Contains 判断一个几何对象是否包含另一个对象 行政区包含 POI、地块包含建筑物 参数顺序与 ST_Within 相反
ST_DWithin 判断两个几何对象是否在指定距离内 查询 500 米范围内的设施 注意坐标系单位,推荐用于距离筛选
ST_Distance 计算两个几何对象之间的距离 计算最近设施距离 大量数据筛选时不要单独依赖它做全表计算
ST_Buffer 生成缓冲区 道路红线缓冲、服务范围示意 结果受坐标系单位和几何复杂度影响
ST_Intersection 返回两个几何对象的交集部分 裁剪地块、叠加分析 计算成本较高,建议先用 ST_Intersects 过滤
ST_Union 合并多个几何对象 行政区合并、斑块融合 大数据量时可能较慢,需分批或简化
ST_Area 计算面面积 统计地块面积、建设用地面积 经纬度坐标下结果不是平方米
ST_Length 计算线长度 道路长度、河流长度统计 同样要注意坐标系单位

检查清单:写PostGIS空间查询语句前先看这几项

  • 确认几何字段:表中是否有 geometrygeography 字段。
  • 确认 SRID:参与查询的图层坐标系是否一致。
  • 确认单位:距离、面积、长度计算是否使用了正确的投影坐标系。
  • 确认索引:大表的 geom 字段是否创建了 GiST 空间索引。
  • 确认函数:是判断相交、包含、距离,还是要生成新的几何结果。
  • 确认参数顺序:ST_WithinST_Contains 不要写反。
  • 确认几何有效性:复杂面数据在叠加前建议检查 ST_IsValid
  • 确认执行计划:慢查询可以用 EXPLAIN ANALYZE 查看是否使用索引。

FAQ:PostGIS空间查询常见问题

PostGIS空间查询语句一定要用空间索引吗?

小数据量测试时不一定需要,但正式项目中强烈建议创建空间索引。尤其是点面叠加、范围查询、距离查询和多表空间连接,一旦数据量变大,没有索引会明显拖慢查询。

ST_Intersects 和 ST_Intersection 有什么区别?

ST_Intersects 返回布尔值,用来判断两个几何对象是否相交;ST_Intersection 返回新的几何对象,用来得到实际重叠部分。前者常用于筛选,后者常用于裁剪和叠加分析。

PostGIS查询一定范围内的数据应该用哪个函数?

通常优先使用 ST_DWithin。它适合表达“距离某对象多少米以内”的查询,并且比先计算所有距离再筛选更适合空间索引优化。前提是要处理好坐标系单位。

为什么我的 ST_DWithin 查询结果不对?

最常见原因是数据使用 EPSG:4326 经纬度坐标,但距离参数按米填写。解决方法是把数据转换到米制投影坐标系,或使用 geography 类型进行距离判断。

点在边界线上时 ST_Within 能查到吗?

ST_Within 对边界情况比较严格,点落在多边形边界上时可能不符合“内部”判断。如果业务上需要包含边界,可以考虑 ST_Covers 或结合具体规则处理。

PostGIS常用空间函数需要全部记住吗?

不需要。入门阶段重点掌握 ST_IntersectsST_WithinST_ContainsST_DWithinST_DistanceST_BufferST_Intersection 即可。真正项目中,先明确空间关系,再查对应函数会更可靠。

结论

PostGIS空间查询语句 的关键不是死记函数,而是先判断业务问题属于哪一种空间关系:相交、包含、距离、裁剪还是统计。确定关系之后,再选择合适的 PostGIS常用空间函数,并检查坐标系、单位、空间索引和几何有效性。

对于日常 GIS 项目,建议从 ST_IntersectsST_WithinST_ContainsST_DWithin 开始练习。掌握这些基础写法后,点面匹配、范围筛选、空间连接和区域统计等常见任务基本都可以独立完成。