PostGIS 是 PostgreSQL 最强大的开源空间扩展模块,它为传统关系型数据库注入了地理空间数据处理能力,可轻松实现地理位置存储、空间关系查询、缓冲区分析等核心功能,广泛应用于城市规划、物流配送、环境监测等领域。本文将通过"概念讲解+实操示例"的方式,从环境搭建到进阶分析,全方位带大家掌握 PostGIS 的核心用法,所有示例代码均可直接运行。
一、PostGIS 核心概念速览
在开始实战前,先明确几个关键概念,帮大家建立基础认知:
- 核心定位:PostGIS 是 PostgreSQL 的空间扩展,无需独立部署,安装后即可让 PostgreSQL 支持地理空间数据的存储、查询与分析,完美继承 PostgreSQL 的事务、权限管理等企业级特性。
- 支持数据类型 :核心分为几何类型(
GEOMETRY,基于平面坐标)和地理类型(GEOGRAPHY,基于球面坐标,适合远距离高精度计算),具体包括点(Point)、线(LineString)、面(Polygon)等基础类型,以及多点(MultiPoint)、多线(MultiLineString)等复合类型。 - 核心能力:提供超过 500 种空间函数,涵盖空间关系判断(如包含、相交)、距离计算、缓冲区分析、坐标系转换等功能,同时支持 GiST 等空间索引,大幅提升海量空间数据查询效率。
二、PostGIS 常用函数详解
PostGIS 提供了丰富的空间函数,是实现地理数据处理与分析的核心。以下按"数据构建""格式转换""空间关系判断""距离与面积计算""坐标转换"五大类,梳理入门必备的常用函数,为后续实战示例奠定基础。
2.1 数据构建函数(创建空间几何对象)
用于构建点、线、面等基础空间几何对象,是插入空间数据的核心函数。
ST_MakePoint(x, y):创建二维点对象,x 为经度,y 为纬度。示例:ST_MakePoint(116.4074, 39.9042)(创建北京坐标点)。ST_MakeLine(geom1, geom2, ...):通过多个点对象创建线对象(LineString)。示例:ST_MakeLine(ST_MakePoint(113.9147, 22.5429), ST_MakePoint(114.0896, 22.5438))(创建深南大道简化线段)。ST_MakePolygon(linestring):通过闭合的线对象创建面对象(Polygon)。要求线对象的起点和终点必须重合(闭合)。示例:ST_MakePolygon(ST_GeomFromText('LINESTRING(113.8174 22.4426, 113.9741 22.4436, 113.9817 22.5717, 113.8249 22.5707, 113.8174 22.4426)'))(创建南山区简化边界)。ST_SetSRID(geom, srid):为几何对象指定坐标系(SRID 为坐标系编号,如 4326 对应 WGS84 坐标系)。示例:ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)(为北京坐标点指定 WGS84 坐标系)。
2.2 格式转换函数(几何对象与文本/其他格式互转)
用于在空间几何对象与可读文本(如 WKT 格式)之间转换,方便数据查看与导入。
ST_AsText(geom):将空间几何对象转换为 WKT(Well-Known Text)文本格式。示例:ST_AsText(geom)可将点对象转为POINT (116.4074 39.9042)。ST_GeomFromText(wkt, srid):将 WKT 文本转换为空间几何对象,可选指定坐标系。示例:ST_GeomFromText('POINT (116.4074 39.9042)', 4326)。ST_AsGeoJSON(geom):将空间几何对象转换为 GeoJSON 格式,适合前端地图框架(如 Leaflet、ECharts)展示。示例:ST_AsGeoJSON(geom)可生成{"type":"Point","coordinates":[116.4074,39.9042]}。
2.3 空间关系判断函数(判断几何对象间的位置关系)
用于判断两个空间对象是否存在相交、包含、相邻等关系,是空间查询的核心函数。
ST_Intersects(geom1, geom2):判断两个几何对象是否相交(有重叠部分)。返回布尔值(true/false)。示例:判断深南大道是否穿过南山区。ST_Contains(geom1, geom2):判断 geom1 是否完全包含 geom2。示例:判断某点位是否在南山区范围内。ST_Touches(geom1, geom2):判断两个几何对象是否相邻(边界接触,但内部不重叠)。示例:判断两个行政区是否接壤。ST_DWithin(geom1, geom2, distance):判断两个几何对象的距离是否小于等于指定值(distance 单位为米,需与坐标系匹配)。核心应用:查找距某地点n公里内的兴趣点(如银行、商场),是LBS场景的核心函数。示例:查询广州周边 100 公里内的城市、查找天安门周边 3 公里内的银行。
2.4 距离与面积计算函数(量化空间对象的度量属性)
用于计算空间对象间的距离、空间对象的面积等量化指标。
ST_Distance(geom1, geom2):计算两个几何对象间的最短距离。GEOMETRY 类型返回平面距离,GEOGRAPHY 类型返回球面距离,单位均为米。示例:计算北京到上海的直线距离。ST_Area(geom):计算面对象(Polygon)的面积。单位由坐标系决定,平面坐标系返回平面面积,投影坐标系返回实际面积(需提前转换)。示例:计算南山区的面积。ST_Length(geom):计算线对象(LineString)的长度。单位为米,示例:计算深南大道简化线段的长度。
2.5 坐标转换函数(不同坐标系间的转换)
由于不同场景使用的坐标系不同(如 WGS84 用于 GPS,UTM 用于局部区域测量),需通过转换函数统一坐标系。
ST_Transform(geom, target_srid):将几何对象从当前坐标系转换为目标坐标系(target_srid 为目标坐标系编号)。示例:ST_Transform(geom, 32650)将 WGS84 坐标系(4326)的南山区边界转换为 UTM 50N 坐标系(32650),用于准确计算面积。ST_SRID(geom):查看几何对象的坐标系编号(SRID)。示例:ST_SRID(geom)可返回 4326,代表该对象使用 WGS84 坐标系。
三、坐标系统核心知识:规范与转换场景
坐标系统是地理空间数据的基础,不同场景下选择合适的坐标系直接影响数据准确性(如距离、面积计算结果)。本节将补充核心坐标知识,明确常用规范及转换时机,解决实战中"选什么坐标系""什么时候需要转换"的核心问题。
3.1 常用坐标系统规范
PostGIS 支持多种坐标系统,实际开发中最常用的是以下两类,需重点掌握其规范和适用场景:
3.1.1 地理坐标系(GCS):基于球面的经纬度坐标
地理坐标系以地球球面为基准,用经纬度( longitude, latitude )描述位置,核心规范如下:
- 核心参数:单位为"度"(°),经度范围 -180°~180°(东经为正,西经为负),纬度范围 -90°~90°(北纬为正,南纬为负)。
- 最常用规范:WGS84(SRID=4326):全球通用的地理坐标系,由 GPS 系统采用,是互联网地图(百度地图、高德地图、Google 地图)的基础坐标系。实战中绝大多数 LBS 场景(如手机定位、周边兴趣点查询)均基于 WGS84 采集数据。
- 其他常见规范: - 火星坐标系(GCJ-02):中国国内地图服务商(百度、高德)对 WGS84 加密后的坐标系,直接使用 WGS84 数据在国内地图上会出现偏移; - 北京54(SRID=4214)、西安80(SRID=4610):我国早期测绘常用的地理坐标系,主要用于国土、规划等传统测绘场景。
3.1.2 投影坐标系(PCS):基于平面的笛卡尔坐标
投影坐标系通过"投影"将球面坐标转换为平面坐标(x, y),核心规范如下:
- 核心参数:单位为"米"(部分为千米),可直接用于距离、面积的精确计算(避免球面到平面的误差)。
- 最常用规范:UTM 投影(分带):全球分为 60 个投影带(每带 6° 经度),每个带对应唯一 SRID。例如: - 深圳、香港地区属于 UTM 50N 带,SRID=32650; - 北京地区属于 UTM 50N 带,SRID=32650; - 上海地区属于 UTM 51N 带,SRID=32651。 UTM 投影在局部区域(单投影带内)的距离、面积计算误差极小,适合城市规划、工程测绘、物流路径优化等需要精确度量的场景。
3.2 什么时候需要进行坐标转换?
坐标转换的核心目的是"适配场景需求"或"统一数据基准",以下 4 种核心场景必须进行转换,否则会导致数据偏差或计算错误:
3.2.1 场景 1:需要精确计算距离/面积时(地理坐标 → 投影坐标)
地理坐标系(如 WGS84 )的单位是"度",直接用 ST_Distance(距离)、ST_Area(面积)计算会产生较大误差(本质是球面距离到平面距离的近似值)。例如:在 WGS84 坐标系下计算北京到上海的距离,结果会比实际公路/航空距离小 2%~5% ;计算城市行政区面积时误差可能超过 10%。
解决方案 :将地理坐标(SRID=4326)转换为对应区域的 UTM 投影坐标(如深圳用 32650 ),再进行计算。这也是实战示例中"计算南山区面积"时使用 ST_Transform(geom, 32650) 的核心原因。
3.2.2 场景 2:数据来源不统一时(多坐标系 → 统一坐标系)
实际项目中常遇到多源数据融合场景:例如"将国土部门提供的西安80坐标系数据,与互联网采集的 WGS84 坐标系POI数据叠加分析"。若坐标系不统一,数据会出现明显偏移(如两个图层的同一地点相差几百米),无法正常分析。
解决方案 :选择一个目标坐标系(如项目统一用 WGS84 ),将其他坐标系的数据通过 ST_Transform 转换为目标坐标系。
3.2.3 场景 3:对接国内地图服务商时(WGS84 → 火星坐标系)
国内百度地图、高德地图等服务商为遵守相关规定,采用火星坐标系(GCJ-02)展示数据。若直接将 WGS84 坐标系的定位数据(如手机 GPS 原始数据)叠加到国内地图上,会出现"漂移"现象(偏差几十米到几百米)。
解决方案:将 WGS84 坐标转换为火星坐标系(GCJ-02)。注意:PostGIS 默认不提供 WGS84 到 GCJ-02 的转换函数,需通过自定义函数实现(可参考地图服务商官方 SDK 提供的转换算法)。
3.2.4 场景 4:传统测绘数据对接时(地方坐标系 → 通用坐标系)
部分城市规划、国土测绘项目会使用地方独立坐标系(如北京地方坐标系、上海地方坐标系)。若需将这类数据对接互联网平台或进行跨区域分析,必须转换为通用坐标系(如 WGS84 或 UTM )。
解决方案 :获取地方坐标系与通用坐标系的转换参数(由当地测绘部门提供),通过 PostGIS 的 ST_Transform 结合自定义转换参数实现转换。
3.3 坐标转换核心注意事项
- 转换不可逆性与精度损失:球面坐标与平面坐标的转换属于"近似转换",会存在微小精度损失(通常在厘米级到米级,不影响绝大多数业务场景),但多次转换会累积误差,应尽量减少转换次数。
- SRID 必须明确 :所有空间数据必须指定 SRID(通过
ST_SetSRID),否则 PostGIS 无法识别坐标系,无法完成转换和准确计算。 - 优先统一坐标系再处理数据 :多源数据融合分析前,应先将所有数据转换为同一坐标系,再进行查询、分析(如
ST_Intersects、ST_DWithin),避免跨坐标系计算导致的错误结果。
四、环境搭建:PostgreSQL + PostGIS 安装
PostGIS 作为 PostgreSQL 的扩展,需先安装 PostgreSQL,再通过官方工具安装 PostGIS 扩展。以下是 Windows 和 macOS 系统的简化安装步骤(Linux 可通过 apt/yum 直接安装):
3.1 基础环境准备
- Windows:下载 PostgreSQL 安装包(推荐 15+ 版本),安装过程中勾选"Stack Builder",完成后通过 Stack Builder 选择" Spatial Extensions ",安装最新版 PostGIS 捆绑包。
- macOS :下载 Postgres.app(自带 PostgreSQL 环境),拖入应用程序并启动,通过终端执行
sudo mkdir -p /etc/paths.d && echo /Applications/Postgres.app/Contents/Versions/latest/bin | sudo tee /etc/paths.d/postgres-app配置环境变量,再通过brew install gdal补充依赖。
3.2 启用 PostGIS 扩展
打开 pgAdmin(PostgreSQL 图形化工具),连接数据库后,执行以下 SQL 语句启用 PostGIS 扩展:
sql
-- 1. 创建测试数据库(若已有数据库可跳过)
CREATE DATABASE postgis_demo;
-- 2. 切换到测试数据库
\c postgis_demo;
-- 3. 启用 PostGIS 核心扩展
CREATE EXTENSION postgis;
-- 4. (可选)启用拓扑扩展(用于复杂拓扑分析)
CREATE EXTENSION postgis_topology;
-- 5. 验证安装成功
SELECT PostGIS_Full_Version();
-- 成功输出示例(版本号可能不同):
-- POSTGIS="3.3.2" (EXTENSION) PGSQL="150" GEOS="3.11.1" PROJ="9.1.1"
五、核心实战:PostGIS 基础操作示例
本节将通过"城市点位管理"和"道路与区域分析"两个基础场景,演示 PostGIS 的核心用法。
4.1 示例 1:城市点位数据管理(Point 类型)
场景:存储国内主要城市的经纬度坐标,实现"查询城市间距""查找指定范围内的城市"等功能。
3.1.1 步骤 1:创建带空间字段的表
使用 GEOMETRY(Point, 4326) 定义空间字段,其中 4326 是 WGS84 坐标系(GPS 标准,经纬度范围:经度 -180~180,纬度 -90~90):
sql
CREATE TABLE cities (
id SERIAL PRIMARY KEY, -- 主键ID
city_name VARCHAR(64) NOT NULL, -- 城市名称
province VARCHAR(64) NOT NULL, -- 所属省份
geom GEOMETRY(Point, 4326) -- 空间字段(WGS84坐标系的点)
);
3.1.2 步骤 2:插入城市点位数据
使用 ST_MakePoint(经度, 纬度) 构建点几何,通过 ST_SetSRID 指定坐标系:
sql
INSERT INTO cities (city_name, province, geom)
VALUES
('北京', '北京市', ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326)),
('上海', '上海市', ST_SetSRID(ST_MakePoint(121.4737, 31.2304), 4326)),
('广州', '广东省', ST_SetSRID(ST_MakePoint(113.2644, 23.1291), 4326)),
('深圳', '广东省', ST_SetSRID(ST_MakePoint(114.0579, 22.5431), 4326)),
('成都', '四川省', ST_SetSRID(ST_MakePoint(104.0679, 30.6799), 4326));
-- 查看插入的数据(将空间数据转为文本格式)
SELECT city_name, province, ST_AsText(geom) AS lon_lat FROM cities;
输出结果:
text
city_name | province | lon_lat
-----------+----------+----------------------
北京 | 北京市 | POINT (116.4074 39.9042)
上海 | 上海市 | POINT (121.4737 31.2304)
广州 | 广东省 | POINT (113.2644 23.1291)
深圳 | 广东省 | POINT (114.0579 22.5431)
成都 | 四川省 | POINT (104.0679 30.6799)
3.1.3 步骤 3:基础空间查询
- 查询两个城市的直线距离 :使用
ST_Distance函数,返回结果单位为米(因使用 4326 坐标系,需注意:GEOMETRY类型计算的是平面距离,远距离有误差;若需高精度球面距离,建议使用GEOGRAPHY类型):
sql
-- 计算北京到上海的直线距离(单位:公里)
SELECT
a.city_name AS city_a,
b.city_name AS city_b,
ROUND(ST_Distance(a.geom, b.geom) / 1000, 2) AS distance_km
FROM cities a, cities b
WHERE a.city_name = '北京' AND b.city_name = '上海';
-- 输出结果:
-- city_a | city_b | distance_km
-- -------+--------+-------------
-- 北京 | 上海 | 1067.28
- 查找指定范围内的城市 :使用
ST_DWithin函数,查询"广州周边 100 公里内的城市":
sql
SELECT city_name, province
FROM cities
WHERE ST_DWithin(
geom,
(SELECT geom FROM cities WHERE city_name = '广州'),
100000 -- 距离单位:米(100000米=100公里)
) AND city_name != '广州';
-- 输出结果(深圳在广州100公里范围内):
-- city_name | province
-- ----------+----------
-- 深圳 | 广东省
4.2 示例 2:道路与区域分析(LineString + Polygon 类型)
场景:存储城市道路(线)和行政区(面)数据,实现"判断道路是否穿过行政区""计算行政区面积"等功能。
3.2.1 步骤 1:创建道路表和行政区表
sql
-- 1. 道路表(LineString 类型)
CREATE TABLE roads (
id SERIAL PRIMARY KEY,
road_name VARCHAR(64) NOT NULL,
road_level VARCHAR(32), -- 道路等级:高速/国道/省道
geom GEOMETRY(LineString, 4326)
);
-- 2. 行政区表(Polygon 类型)
CREATE TABLE districts (
id SERIAL PRIMARY KEY,
district_name VARCHAR(64) NOT NULL,
city_name VARCHAR(64) NOT NULL,
geom GEOMETRY(Polygon, 4326)
);
3.2.2 步骤 2:插入示例数据
sql
-- 插入深圳某道路数据(简化的直线段)
INSERT INTO roads (road_name, road_level, geom)
VALUES (
'深南大道',
'城市主干道',
ST_SetSRID(
ST_MakeLine(
ST_MakePoint(113.9147, 22.5429),
ST_MakePoint(114.0896, 22.5438)
),
4326
)
);
-- 插入深圳南山区边界数据(简化的多边形)
INSERT INTO districts (district_name, city_name, geom)
VALUES (
'南山区',
'深圳市',
ST_SetSRID(
ST_MakePolygon(
ST_GeomFromText(
'LINESTRING(
113.8174 22.4426,
113.9741 22.4436,
113.9817 22.5717,
113.8249 22.5707,
113.8174 22.4426 -- 闭合多边形(起点=终点)
)'
)
),
4326
)
);
3.2.3 步骤 3:进阶空间查询
- 判断道路是否穿过行政区 :使用
ST_Intersects函数,判断深南大道是否穿过南山区:
sql
SELECT
r.road_name,
d.district_name,
CASE WHEN ST_Intersects(r.geom, d.geom) THEN '是' ELSE '否' END AS is_intersect
FROM roads r, districts d
WHERE r.road_name = '深南大道' AND d.district_name = '南山区';
-- 输出结果:
-- road_name | district_name | is_intersect
-- ----------+---------------+--------------
-- 深南大道 | 南山区 | 是
- 计算行政区面积 :使用
ST_Area函数,注意:4326 坐标系下GEOMETRY类型计算的是平面面积,需转换为局部坐标系(如 UTM 坐标系)才能得到准确的实际面积:
sql
-- 转换为 UTM 50N 坐标系(适合深圳区域),计算南山区面积(单位:平方公里)
SELECT
district_name,
ROUND(
ST_Area(ST_Transform(geom, 32650)) / 1000000, 2 -- 1平方公里=1000000平方米
) AS area_sqkm
FROM districts
WHERE district_name = '南山区';
-- 输出结果(简化边界计算值,实际南山区面积约 187 平方公里):
-- district_name | area_sqkm
-- ---------------+-----------
-- 南山区 | 185.67
六、实战扩展:查找指定地点周边兴趣点
场景:查找"天安门周边 3 公里内的银行",这是 LBS 应用中最典型的"周边兴趣点查询"需求,核心通过 ST_DWithin 函数实现。
5.1 步骤 1:创建兴趣点表(银行)
Plain
-- 创建银行表,包含名称、地址、坐标(WGS84坐标系)
CREATE TABLE banks (
id SERIAL PRIMARY KEY,
bank_name VARCHAR(64) NOT NULL, -- 银行名称
address VARCHAR(128) NOT NULL, -- 详细地址
geom GEOMETRY(Point, 4326) -- 空间坐标字段
);
5.2 步骤 2:插入示例银行数据
当空间数据量达到万级以上时,查询效率会显著下降。PostGIS 支持 GiST(通用搜索树)索引,可大幅提升空间查询速度(加速比可达 100 倍以上)。
sql
-- 为 cities 表的 geom 字段创建 GiST 索引
CREATE INDEX idx_cities_geom ON cities USING GIST (geom);
-- 为 roads 表和 districts 表的 geom 字段创建 GiST 索引
CREATE INDEX idx_roads_geom ON roads USING GIST (geom);
CREATE INDEX idx_districts_geom ON districts USING GIST (geom);
-- 查看索引(验证创建成功)
\di -- 列出当前数据库的所有索引
注意:空间索引仅对空间查询(如 ST_DWithin、ST_Intersects)有效,对普通属性查询(如按城市名称查询)无效,需单独创建普通索引。
七、性能优化:空间索引创建
PostGIS 凭借其强大的空间处理能力,在多个领域有广泛应用:
- 城市规划:分析土地利用情况、计算基础设施(学校、医院)的服务覆盖范围,辅助城市发展决策。
- 物流配送:优化配送路线,查询配送点周边的客户分布,计算最短配送距离。
- 环境监测:存储气象站点、水质监测点数据,分析污染扩散范围,模拟洪水等灾害的影响区域。
- 位置服务(LBS):为打车软件、外卖平台提供"附近的服务"查询,如"查找周边 3 公里内的餐厅"。
九、总结
PostGIS 作为 PostgreSQL 的空间扩展,以"开源免费、无缝集成、功能全面"的优势,成为开源空间数据库的行业标准。本文通过两个实战示例,带大家掌握了 PostGIS 的核心用法:从环境搭建、空间表创建、数据插入,到基础的距离查询、范围查询,再到进阶的空间关系判断和性能优化,所有示例代码均可直接复用。
入门后,大家可进一步探索 PostGIS 的高级功能,如栅格数据处理(遥感影像分析)、拓扑关系管理、3D 空间数据支持等。同时,PostGIS 可与 QGIS(桌面 GIS 工具)、GeoServer(地图服务发布工具)、Python GeoPandas 库等生态工具无缝集成,构建完整的 GIS 解决方案。如果需要处理地理空间数据,PostGIS + PostgreSQL 绝对是值得优先选择的技术组合!
参考资源:
- PostGIS 官方文档:https://postgis.net/documentation/
- PostGIS 官方教程:https://postgis.net/workshops/
- PostgreSQL 官方文档:https://www.postgresql.org/docs/