PostGIS空间查询语句怎么写?常用函数有哪些?
引言
“PostGIS空间查询语句怎么写?常用函数有哪些?”是很多 GIS 初学者、空间数据分析师和后端开发者第一次接触 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 的
WHERE、JOIN、GROUP 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空间查询语句前先看这几项
- 确认几何字段:表中是否有
geometry或geography字段。 - 确认 SRID:参与查询的图层坐标系是否一致。
- 确认单位:距离、面积、长度计算是否使用了正确的投影坐标系。
- 确认索引:大表的
geom字段是否创建了 GiST 空间索引。 - 确认函数:是判断相交、包含、距离,还是要生成新的几何结果。
- 确认参数顺序:
ST_Within和ST_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_Intersects、ST_Within、ST_Contains、ST_DWithin、ST_Distance、ST_Buffer、ST_Intersection 即可。真正项目中,先明确空间关系,再查对应函数会更可靠。
结论
PostGIS空间查询语句 的关键不是死记函数,而是先判断业务问题属于哪一种空间关系:相交、包含、距离、裁剪还是统计。确定关系之后,再选择合适的 PostGIS常用空间函数,并检查坐标系、单位、空间索引和几何有效性。
对于日常 GIS 项目,建议从 ST_Intersects、ST_Within、ST_Contains 和 ST_DWithin 开始练习。掌握这些基础写法后,点面匹配、范围筛选、空间连接和区域统计等常见任务基本都可以独立完成。