强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

PostGIS 完全指南 / 第 7 章:坐标系统与投影

第 7 章:坐标系统与投影

7.1 为什么坐标系统很重要

地球是一个近似椭球体,将三维曲面映射到二维平面必然产生变形。不同的坐标系在不同区域、不同用途下各有优劣。

坐标系选择错误的后果

问题原因影响
距离计算偏差 10-50%用度值当米计算配送范围失准
位置偏移 100-500 米坐标系不匹配(如 GCJ-02 当 WGS84)门店定位错误
面积误差达 100%+高纬度地区用等距投影规划面积失真
几何操作失败混合使用不同 SRIDSQL 报错

7.2 坐标系分类

地理坐标系 (Geographic CRS)

使用经纬度(度)表示位置,基于椭球体模型。

坐标系SRID基准面适用范围
WGS 844326WGS 84全球(GPS 标准)
CGCS 20004490CGCS 2000中国国家标准
NAD 834269NAD 83北美
GRS 804019GRS 80欧洲

投影坐标系 (Projected CRS)

将经纬度投影到平面,单位通常为米。

投影类型特点适用场景
UTM (通用横轴墨卡托)6°分带,变形小中小范围工程测量
高斯-克吕格3°或6°分带中国测绘标准
Lambert 等角圆锥适合中纬度东西延伸区域省级/区域级
Web Mercator球面墨卡托Web 地图

7.3 中国常用坐标系详解

坐标系对比

坐标系来源SRID精度法律地位
WGS 84GPS/国际4326厘米级国际标准
GCJ-02国测局无官方 SRID米级偏移中国地图法规要求
BD-09百度无官方 SRID米级偏移百度地图专用
CGCS 2000中国国标4490厘米级中国法定坐标系

GCJ-02 / BD-09 注意事项

-- ⚠️ GCJ-02 和 BD-09 不是标准坐标系
-- PostGIS 没有内置的 SRID,不能直接用 ST_Transform 转换
-- 需要使用第三方算法或偏移修正

-- 偏移量示意(北京地区,约 300-600 米)
-- WGS84: (116.4074, 39.9042)
-- GCJ02: (116.4139, 39.9087)  -- 约偏移 500 米
-- BD09:  (116.4203, 39.9142)  -- 在 GCJ02 基础上再偏移

重要: 国内地图数据来源(高德、腾讯)使用 GCJ-02,百度使用 BD-09,与 WGS84 有数百米偏移。混合使用前必须统一坐标系。


7.4 SRID 与 spatial_ref_sys

查询坐标系信息

-- 查看 SRID 4326 的详细定义
SELECT srid, auth_name, auth_srid, srtext
FROM spatial_ref_sys WHERE srid = 4326;

-- 搜索 UTM Zone 50N(覆盖中国东部沿海 114°-120°E)
SELECT srid, auth_name, auth_srid, srtext
FROM spatial_ref_sys
WHERE srtext ILIKE '%utm%zone%50%'
  AND srtext ILIKE '%north%';

-- 搜索 CGCS2000 三度带
SELECT srid, srtext
FROM spatial_ref_sys
WHERE srtext ILIKE '%CGCS%2000%3%degree%'
LIMIT 10;

常用中国 SRID 速查

-- 速查表
WITH china_sr (srid, name, type, unit, coverage) AS (
    VALUES
        (4326,  'WGS 84',                              '地理', '度',   '全球'),
        (4490,  'CGCS 2000',                           '地理', '度',   '中国'),
        (4547,  'CGCS 2000 / 3-degree GK zone 39',    '投影', '米',   '117°带 (北京)'),
        (4548,  'CGCS 2000 / 3-degree GK zone 40',    '投影', '米',   '120°带 (上海)'),
        (4549,  'CGCS 2000 / 3-degree GK zone 41',    '投影', '米',   '123°带'),
        (4538,  'CGCS 2000 / 3-degree GK CM 117E',    '投影', '米',   '中央经线117°'),
        (4539,  'CGCS 2000 / 3-degree GK CM 120E',    '投影', '米',   '中央经线120°'),
        (32650, 'WGS 84 / UTM zone 50N',               '投影', '米',   '114°-120°E'),
        (32651, 'WGS 84 / UTM zone 51N',               '投影', '米',   '120°-126°E'),
        (3857,  'Web Mercator',                         '投影', '米',   '全球 Web 地图')
)
SELECT * FROM china_sr;

7.5 ST_Transform 投影转换

ST_Transform 是 PostGIS 中最核心的坐标转换函数,底层使用 PROJ 库。

基本转换

-- 将北京坐标从 WGS84 (4326) 转换为 CGCS2000 三度带 39 带 (4547)
SELECT
    ST_X(geom) AS wgs84_lng,
    ST_Y(geom) AS wgs84_lat,
    ST_X(ST_Transform(geom, 4547)) AS cgcs_x,
    ST_Y(ST_Transform(geom, 4547)) AS cgcs_y
FROM (SELECT ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326) AS geom) t;

-- 结果:
-- wgs84_lng: 116.4074, wgs84_lat: 39.9042
-- cgcs_x: 447963.89,  cgcs_y: 4418554.07 (米)

批量转换

-- 将整个表的数据从 4326 转换到 4547
UPDATE stores
SET geom = ST_Transform(geom, 4547);

-- ⚠️ 转换后记得更新 SRID
-- 方式 1: 用 ST_SetSRID 包装
UPDATE stores
SET geom = ST_SetSRID(ST_Transform(geom, 4547), 4547);

-- 方式 2: 使用 UpdateGeometrySRID(更新元数据)
SELECT UpdateGeometrySRID('stores', 'geom', 4547);
-- 注意: 这只更新元数据,不转换坐标!

创建投影视图(不修改原数据)

-- 创建投影后的视图,原表保持 WGS84
CREATE VIEW stores_projected AS
SELECT
    id, name, address, store_type,
    ST_Transform(geom, 4547) AS geom
FROM stores;

-- 查询投影后的视图
SELECT name, ST_X(geom) AS x_m, ST_Y(geom) AS y_m
FROM stores_projected;

7.6 投影转换精度问题

精度损失

-- 反复转换可能导致精度损失
WITH point AS (
    SELECT ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326) AS geom
)
SELECT
    ST_X(geom) AS original_lng,
    ST_X(ST_Transform(ST_Transform(geom, 4547), 4326)) AS roundtrip_lng,
    ST_X(geom) - ST_X(ST_Transform(ST_Transform(geom, 4547), 4326)) AS error_degrees
FROM point;
-- 误差通常在 1e-10 度以内,可忽略

避免转换错误

-- ❌ 错误: 对已经是投影坐标的几何再次设置 4326
UPDATE table SET geom = ST_SetSRID(ST_MakePoint(447963, 4418554), 4326);
-- 这会创建一个经度 447963° 的"坐标",完全错误!

-- ✅ 正确: 明确数据的实际 SRID
UPDATE table SET geom = ST_SetSRID(ST_MakePoint(447963, 4418554), 4547);

7.7 选择合适的投影

选择指南

数据范围                    推荐投影
─────────                   ─────────
全球 Web 地图               SRID 3857 (Web Mercator)
中国全国                    SRID 4490 (CGCS2000 地理坐标)
省级/区域 (<600km)          CGCS2000 三度带 (4547-4554)
城市级 (<100km)             CGCS2000 三度带或 UTM
距离/面积精确计算           投影坐标系 (米)

按经度选择三度带号

-- 计算指定经度对应的 CGCS2000 三度带号
-- 带号 = FLOOR((经度 - 1.5) / 3) + 25
-- SRID = 4500 + 带号

-- 示例:北京 (116.4074°E)
-- 带号 = FLOOR((116.4074 - 1.5) / 3) + 25 = FLOOR(38.3) + 25 = 63
-- SRID = 4500 + 63 - 16 = 4547

-- 创建函数自动计算
CREATE OR REPLACE FUNCTION get_cgcs2000_srid(lng DOUBLE PRECISION)
RETURNS INTEGER AS $$
BEGIN
    RETURN 4500 + FLOOR((lng - 1.5) / 3)::INTEGER + 25 - 16;
END;
$$ LANGUAGE plpgsql IMMUTABLE;

-- 测试
SELECT get_cgcs2000_srid(116.4074);  -- 北京: 4547
SELECT get_cgcs2000_srid(121.4737);  -- 上海: 4548
SELECT get_cgcs2000_srid(104.0668);  -- 成都: 4545

7.8 距离和面积的正确计算

不同方法的精度对比

-- 创建测试: 北京到上海的距离
WITH points AS (
    SELECT
        ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326) AS beijing,
        ST_SetSRID(ST_MakePoint(121.4737, 31.2304), 4326) AS shanghai
)
SELECT
    -- 方法 1: Geometry 距离 (度) — 无意义
    ROUND(ST_Distance(beijing, shanghai)::numeric, 4) AS dist_degrees,

    -- 方法 2: Geography 距离 (米) — 精确球面距离
    ROUND((ST_Distance(beijing::geography, shanghai::geography) / 1000)::numeric, 1) AS dist_geo_km,

    -- 方法 3: 投影后距离 (米) — 精确但取决于投影
    ROUND((ST_Distance(
        ST_Transform(beijing, 4547),
        ST_Transform(shanghai, 4548)
    ) / 1000)::numeric, 1) AS dist_proj_km,

    -- 方法 4: Haversine 公式 — 参考值
    ROUND((6371 * acos(
        cos(radians(39.9042)) * cos(radians(31.2304)) *
        cos(radians(121.4737) - radians(116.4074)) +
        sin(radians(39.9042)) * sin(radians(31.2304))
    ))::numeric, 1) AS dist_haversine_km
FROM points;

-- 结果对比:
-- dist_degrees:  ~10.77 (无意义)
-- dist_geo_km:   ~1068.1 (精确)
-- dist_proj_km:  ~1070.5 (投影误差)
-- dist_haversine: ~1068.0 (参考)

推荐做法

场景推荐方法原因
点对点距离ST_Distance(geom::geography)最简单精确
范围过滤ST_DWithin(geom::geography, ...)索引加速
面积计算ST_Area(geom::geography)球面面积(米²)
精确测量先投影再计算局部高精度
线路长度ST_Length(geom::geography)球面长度(米)

7.9 自定义坐标系

-- 添加自定义投影坐标系
-- 示例: 某城市的地方坐标系
INSERT INTO spatial_ref_sys (srid, auth_name, auth_srid, proj4text, srtext)
VALUES (
    920001,
    'CUSTOM',
    920001,
    '+proj=tmerc +lat_0=39.9042 +lon_0=116.4074 +k=1 +x_0=500000 +y_0=0 +datum=WGS84 +units=m +no_defs',
    'PROJCS["Beijing Local",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",39.9042],PARAMETER["central_meridian",116.4074],PARAMETER["scale_factor",1],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1]]'
);

-- 使用自定义坐标系
SELECT ST_Transform(
    ST_SetSRID(ST_MakePoint(116.4074, 39.9042), 4326),
    920001
);

7.10 坐标系诊断脚本

-- 诊断表中几何的 SRID 分布
SELECT
    ST_SRID(geom) AS srid,
    count(*) AS count,
    ST_XMin(ST_Extent(geom)) AS xmin,
    ST_YMin(ST_Extent(geom)) AS ymin,
    ST_XMax(ST_Extent(geom)) AS xmax,
    ST_YMax(ST_Extent(geom)) AS ymax
FROM my_table
GROUP BY ST_SRID(geom);

-- 检查坐标范围是否合理
-- WGS84 经度: -180 ~ 180, 纬度: -90 ~ 90
SELECT
    CASE
        WHEN ST_XMin(ext) < -180 OR ST_XMax(ext) > 180 THEN '经度超出范围!'
        WHEN ST_YMin(ext) < -90 OR ST_YMax(ext) > 90 THEN '纬度超出范围!'
        ELSE '坐标范围正常'
    END AS diagnosis,
    ext
FROM (SELECT ST_Extent(geom) AS ext FROM my_table) t;

7.11 本章小结

要点说明
SRID每个空间数据都必须有明确的 SRID
WGS84 (4326)GPS/国际标准,Web 地图通用
CGCS2000 (4490)中国法定坐标系
ST_Transform投影转换的核心函数
Geography 类型不需要投影就能精确计算距离/面积
GCJ-02 / BD-09国内地图偏移,需手动处理

扩展阅读