PostGIS是PostgreSQL数据库的一个开源扩展,安装之后,数据库就能像处理普通数字和字符串一样处理点、线、面等几何数据。它是地理信息行业事实上的标准方案,国内外大量地图服务、外卖配送、共享单车调度系统背后都有它的身影。这篇文章带你从零开始掌握PostGIS的核心概念和常用操作,不需要任何GIS专业背景,只要会写基础SQL就能看懂。

一、空间数据类型:geometry与geography的区别
安装PostGIS后,最常接触的是geometry和geography两种数据类型。很多初学者在这里就走错了路,先把这个概念理清楚非常有必要。
geometry类型把地球表面的数据投影到平面上来计算,所有计算都基于笛卡尔坐标系,速度快、函数丰富,是绝大多数场景的默认选择。geography类型则直接在球面上计算,基于大圆距离,结果更精确但计算成本更高。举个例子,计算北京到上海的直线距离,用geometry需要自己选择坐标系,而用geography直接就能得到米为单位的球面距离。
看一下建表和插入数据的实际写法:
-- 在数据库中启用扩展(只需执行一次)
CREATE EXTENSION postgis;
-- 创建店铺表,location字段存储经纬度点
CREATE TABLE shops (
id SERIAL PRIMARY KEY,
name TEXT,
location GEOGRAPHY(POINT, 4326)
);
-- 插入数据时用ST_GeomFromText或更简洁的快速写法
INSERT INTO shops (name, location)
VALUES ('中关村店', ST_SetSRID(ST_MakePoint(116.316, 39.984), 4326)::geography);
-- 直接计算距离,单位是米
SELECT name,
ST_Distance(location,
ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)::geography
) AS dist_m
FROM shops
ORDER BY dist_m
LIMIT 5;
上面这条查询就是典型的“查找附近”需求:以坐标116.4、39.9为圆心,返回距离最近的五家店铺。如果用geometry类型做同样的事,需要调用ST_DistanceSphere或者先做坐标投影转换,否则算出来的会是“度”而不是米。
一个实用的经验法则:数据范围较小(比如一个城市内部的门店)且需要精确球面距离时用geography;数据量大、计算复杂或需要做空间分析时用geometry并配合合适的投影坐标系,例如国内常用的CGCS2000相关投影。
二、空间索引:没有它查询会慢到怀疑人生
空间查询最大的特点是无法使用普通的B-tree索引。如果不建索引,查询附近门店时数据库会做全表扫描,逐行计算距离,数据量到几十万条时响应时间会从毫秒级飙升到数秒甚至更久。
PostGIS使用GIST索引来解决这一问题。GIST是一种通用搜索树,它不直接索引几何对象本身,而是索引每个几何对象的外接矩形。查询时先通过外接矩形快速排除掉大部分不相关的行,再对少量候选数据做精确计算。
-- 为location字段创建空间索引
CREATE INDEX idx_shops_location ON shops USING GIST (location);
-- 推荐使用 ST_DWithin 做范围查询,它能利用索引
SELECT name, ST_Distance(location, ST_SetSRID(
ST_MakePoint(116.4, 39.9), 4326)::geography) AS dist_m
FROM shops
WHERE ST_DWithin(location,
ST_SetSRID(ST_MakePoint(116.4, 39.9), 4326)::geography,
3000) -- 3000米范围内
ORDER BY dist_m;
这里有个非常关键的细节:ST_DWithin配合索引能高效完成范围查询,而如果写成ST_Distance(...) < 3000的形式,索引会完全失效,只能全表扫描。同样是查三公里内的门店,两种写法在百万级数据下的性能差距可能达到几百倍。
另外要注意ORDER BY distance部分依然需要排序,可以通过限制返回条数、先粗筛后精算等方式进一步优化。日常开发中养成习惯:凡是涉及空间条件的查询,先检查执行计划,确认索引真的被用上了。
三、常用空间函数与实战场景
PostGIS提供了上千个空间函数,日常开发中真正高频使用的其实只有十来个。下面按典型场景整理几个最常用的。
第一个场景是判断点是否在某个多边形区域内,比如判断用户下单地址是否在配送范围内:
-- 创建配送区域表
CREATE TABLE delivery_zones (
id SERIAL PRIMARY KEY,
zone_name TEXT,
geom GEOMETRY(POLYGON, 4326)
);
-- 判断点是否在区域内(边界也算在内)
SELECT zone_name
FROM delivery_zones
WHERE ST_Contains(geom, ST_SetSRID(ST_MakePoint(116.35, 39.95), 4326));
与ST_Contains类似的还有ST_Within(参数顺序相反)、ST_Intersects(相交判断,常用于判断路线是否穿越禁区)以及ST_DWithin(距离判断)。这四个函数覆盖了绝大多数包含、相交、邻近类需求。
第二个场景是缓冲区分析。ST_Buffer可以在几何对象周围生成一个指定半径的区域,例如给一条河流生成500米的保护范围带,或者给基站生成信号覆盖圈:
-- 为河流生成500米缓冲区
SELECT id, ST_AsText(
ST_Buffer(geom::geography, 500)
) AS buffer_500m
FROM rivers
WHERE id = 1;
-- 统计落在缓冲区内的污染源数量
SELECT COUNT(*)
FROM pollution_sources p
JOIN rivers r ON ST_Within(p.geom, ST_Buffer(r.geom::geography, 500));
第三个场景是面积和长度计算。统计一个行政区面积、一条管线的长度,分别用ST_Area和ST_Length。注意对geography类型这两个函数直接返回平方米和米,非常省心;对geometry类型则取决于坐标系单位,需要换算。
四、上手建议与常见坑
关于坐标系,最常见的坑是SRID不一致。所有数据都统一使用4326(WGS84经纬度)是最省心的做法,需要精确计算时再临时转换。一旦两张表的SRID不同,空间查询会直接报错或得到错误结果,排查起来很折磨人。
安装方面,Linux下可以通过包管理器直接安装,例如Debian系执行apt install postgresql-postgis相关包,Windows安装PostgreSQL时勾选Stack Builder里的PostGIS组件即可。装完后别忘了在目标数据库里执行CREATE EXTENSION postgis,很多人装完扩展包却发现类型不可用,原因就是漏了这一步。
数据导入推荐使用命令行工具shp2pgsql(导入Shapefile)或者QGIS的图形化导入功能。调试阶段可以配合ST_AsText把几何数据转成可读文本,或者用ST_AsGeoJSON输出GeoJSON格式,直接在前端地图上渲染验证。
最后给出一个学习路径建议:先在本地用小数据集跑通建表、插点、建索引、附近查询这条完整链路,再逐步接触空间连接、空间统计等进阶内容。PostGIS官方文档虽然篇幅庞大,但函数说明配合示例非常详细,遇到具体问题按函数名查文档往往比搜索引擎更高效。掌握它之后,你会发现地理位置类的需求不再需要引入额外的中间件,一条SQL就能优雅解决。
PostGISPostgreSQL地理空间数据库修改时间:2026-09-10 01:20:38