跳至内容
16.3 空间类型与坐标参考

16.3 空间类型与坐标参考

PostGIS 不只是“经纬度函数包”。它把空间对象、坐标参考、拓扑关系、距离 模型、索引操作符和目录元数据带进 PostgreSQL。

学习顺序应当是:

业务对象
  -> 坐标参考与单位
  -> geometry / geography
  -> 合法性与边界规则
  -> 谓词
  -> 索引

若从“建一个 GiST”开始,很容易得到能够执行却单位错误的查询。

16.3.1 geometry、geography 与测量语义

两种类型回答不同计算问题

PostGIS 的核心空间类型:

类型计算表面距离/面积单位典型用途
geometry指定 CRS 的平面坐标CRS 单位拓扑、局部投影、丰富函数、空间索引
geography地球曲面模型米/平方米全球经纬度上的距离、半径、面积

geometry(Point, 4326) 的坐标是经纬度角度。下面这条距离:

ST_Distance(point_a_4326, point_b_4326)

返回的是坐标系单位,也就是角度,不是米。把结果乘一个固定“每度米数”只在 非常有限的局部近似下成立,且经度尺度随纬度变化。

将同一点作为 geography

ST_Distance(
  point_a_4326::geography,
  point_b_4326::geography
)

才得到以米为单位的地表距离语义。PostGIS 官方 空间查询章节 说明 geometry 的平面计算与 geography 的大地计算差异。

不是所有列都要存两份

本章将规范值保存在 geometry:

location geometry(Point, 4326) NOT NULL

再生成 geography:

location_geog geography(Point, 4326)
  GENERATED ALWAYS AS (
    location::geography
  ) STORED

好处:

  • 只有一个可写坐标事实;
  • geography 与 geometry 不会因应用漏更新而漂移;
  • geography 可建独立 GiST,米制查询不必每次临时转换;
  • 生成表达式和类型可以从目录审计。

代价:

  • 多一列存储;
  • 写入要计算生成值;
  • 多一个索引意味着更多 WAL、磁盘和缓存;
  • schema 将业务允许的 CRS 固定为 4326。

若米制查询很少,可以只存 geometry 并在查询中转换;若大多数查询都在一个 适当局部投影内,也可以统一使用投影 geometry。要由查询和单位合同决定, 不是机械地“双列最保险”。

typmod 把对象类型与 SRID 放进 schema

比较:

location geometry

和:

location geometry(Point, 4326)

后者让数据库拒绝非 Point 或非 4326 的值,使表结构本身表达坐标合同。本章 围栏同样固定:

zone_geom geometry(Polygon, 4326)

如果业务允许 MultiPolygon,应明确写:

geometry(MultiPolygon, 4326)

或在接入时将 Polygon 规范化为 MultiPolygon。不要直到某个区域含离岛才临时 修改客户端。

X/Y 是坐标轴,不是自动的“纬/经”

EPSG:4326 常见 WKT/GeoJSON 使用:

X = longitude
Y = latitude

本章点:

ST_SetSRID(
  ST_MakePoint(-74.00000, 40.71000),
  4326
)

即经度 -74、纬度 40.71。把两者颠倒仍可能落在各自合法数值范围内, 数据库不一定能发现。

接入合同应写清:

format: longitude,latitude
x: longitude
y: latitude
srid: 4326
longitude_range: [-180, 180]
latitude_range: [-90, 90]

并用已知控制点做端到端验证,而不是只做数值范围检查。

geography 不是“更准确”的万能开关

geography 很适合:

  • “距离配送中心 1 km 内”;
  • 跨较大区域的地表距离;
  • 用经纬度数据直接得到米制结果。

但 geometry 仍常用于:

  • ST_CoversST_Intersects 等拓扑;
  • 本地工程坐标和高精度投影;
  • 更广的 PostGIS 函数集合;
  • 需要明确平面模型的地图与分析。

问题不是哪种类型高级,而是哪种计算模型与业务问题一致。

本章距离证据

distance-semantics.sql 对三个中心寻找最近 事件,并用 geography 计算米:

hub最近事件距离(四舍五入米)
airporte0070
centrale0010
easte004423

三者都在 1 km 内。这里的 423 只验证单位与查询链,不是测绘级距离承诺。

16.3.2 SRID、投影、单位与坐标转换

SRID 是坐标参考身份

同样一对数:

(500000, 4500000)

在不同 CRS 中代表完全不同位置和单位。SRID 让 PostGIS 知道坐标属于哪个 参考系统,并能查找转换定义。

检查:

SELECT
  ST_SRID(location),
  GeometryType(location)
FROM shop_ch16.delivery_event;

本章所有事件、中心和围栏都是 4326。

ST_SetSRID 只贴标签

ST_SetSRID(geom, 4326)

不会改变任何坐标数值。它适用于“这些数本来就是 4326,只是对象没有声明” 的场景。

真正转换:

ST_Transform(geom, target_srid)

会依据源 CRS 与目标 CRS 重新计算坐标。PostGIS ST_Transform 文档 明确区分转换坐标与仅修改 SRID 标签。

危险反例:

-- 原始数值其实是 Web Mercator,却被错误贴成 WGS84
ST_SetSRID(mercator_numbers, 4326)

这不是近似误差,而是数据身份损坏。以后再 ST_Transform 只会把错误输入 转换得更复杂。

混合 SRID 应当显式失败

本章故意执行:

SELECT ST_Intersects(
  ST_SetSRID(ST_MakePoint(0, 0), 4326),
  ST_SetSRID(ST_MakePoint(0, 0), 3857)
);

固定结果:

SQLSTATE XX000
Operation on mixed SRID geometries

srid-mismatch.sql 把错误作为验收证据。不要 捕获这个错误后自动 ST_SetSRID 到另一边;系统无法仅凭数值知道哪边身份 正确。

CRS 决定单位与失真

常见选择:

选择优点风险
EPSG:4326 geometry交换广泛,保存经纬度自然平面距离是角度
EPSG:4326 geography米制地表距离直接函数/性能模型与 geometry 不同
本地投影 geometry局部距离、面积和形状可控适用区域有限,需转换治理
EPSG:3857 geometryWeb 地图显示生态常见距离/面积失真,非通用测量 CRS

Web Mercator 适合瓦片显示,不应仅因为前端地图使用它,就把业务距离也定义在 3857 平面上。

选投影需要:

  • 业务覆盖区域;
  • 容许失真;
  • 距离、面积、方向还是拓扑;
  • 数据供应者的 CRS;
  • 跨区/跨国查询;
  • 权威测绘要求。

这通常要由 GIS 专业人员与业务共同评审,而不是数据库管理员猜一个 EPSG 编号。

转换表达式与索引要一致

若查询反复写:

WHERE ST_DWithin(
  ST_Transform(location, :local_srid),
  :query_point,
  1000
)

原始 location GiST 通常不能直接服务这个转换表达式。选项包括:

  • 存储/生成规范投影列并建索引;
  • 建表达式索引;
  • 先用原 CRS 的安全包围盒缩小候选,再精确转换;
  • 使用 geography 的米制谓词。

表达式索引必须与查询表达式结构、SRID 和函数可索引条件一致。每行临时转换 还会增加 CPU。不能只验证结果,不看计划。

坐标转换也要版本化

CRS 定义、网格文件和转换路径可能随 PROJ/PostGIS/操作系统包变化。高精度 业务应在证据中保存:

PostGIS_Full_Version()
PROJ version
source and target SRID
transformation method/grid availability
control points and expected tolerance

本章没有外部权威坐标或网格文件,因此只固定 PostGIS 版本、SRID 和合成 控制点,不声称测绘精度。

16.3.3 点、线、面、边界与有效几何

对象维度决定问题

对象本章/配送中的例子典型问题
Point配送事件、中心在哪里、离多远
LineString轨迹、道路长度、相交、沿线位置
Polygon围栏覆盖、包含、面积
Multi*多片区域、分段轨迹多个几何组成一个业务对象

用两列经纬度只能表示 Point,且无法让数据库知道它们共同构成一个空间对象。 PostGIS 类型让约束、函数和索引围绕整个对象工作。

Polygon 有内部、边界和外部

点对面的关系至少有三种:

interior
boundary
exterior

所以:

ST_Contains(polygon, point)
ST_Covers(polygon, point)

不是同义词。本章 e003 在边界:

ST_Covers   = true
ST_Contains = false
ST_Touches  = true

PostGIS ST_Covers 说明它允许 B 的点位于 A 的内部或边界,并会自动利用包围盒比较。

业务要先决定:

  • 边界订单属于两个区、任一区还是某个优先区;
  • 边界容差如何处理 GPS 噪声;
  • 围栏之间是否允许重叠;
  • 点恰好在洞边界如何解释;
  • 版本换挡时边界与时间边界谁先判定。

SQL 只能实现已选规则。

有效几何是谓词的前提

自相交 Polygon、未闭合 ring、错误洞关系等无效几何可能让拓扑谓词产生意外 结果。写入时至少验证:

CHECK (ST_IsValid(zone_geom))

本章还固定:

CHECK (NOT ST_IsEmpty(zone_geom))
CHECK (ST_SRID(zone_geom) = 4326)

必要时用:

ST_IsValidReason(geom)

定位原因。ST_MakeValid 可以尝试修复,但修复可能改变对象类型、拆成多个面 或改变业务边界,不能在生产接入中无审计地自动覆盖原值。

空、NULL 与未知不同

NULL geometry  -> 未提供/未知
EMPTY geometry -> 已知为空的空间对象

它们在函数和聚合中的行为不同。本章业务事件和围栏都要求 NOT NULL 且 非空,避免把“没有位置”误当成“位于任何区域之外”。如果设备可能不上传定位, 应另设质量状态:

location_status = missing / invalid / approximate / verified

不要用 (0,0) 作为缺失哨兵;它是几内亚湾中的真实坐标。

轨迹不是无序点集

若把多个 Point 组成 LineString,顺序必须来自稳定合同:

ST_MakeLine(location ORDER BY occurred_at, event_id)

还应分段:

  • courier/session;
  • 最大时间间隔;
  • 设备重启;
  • 不合理速度跳变;
  • SRID/质量状态。

跨越长间隔直接连线会制造从 A 到 B 的虚假直线。轨迹的 LineString 是一种 派生产品,应可从原始事件和版本化规则重建。

经纬度数据也有许可证与版本

真实边界、道路、地址或兴趣点通常来自外部数据源。上线前要保存:

provider and dataset
version / snapshot date
license and attribution
allowed redistribution/use
source CRS
transformation chain
import checksum
update and rollback policy

本章坐标全部为项目自造的合成数据, fixture-manifest.json 明确 external_geodata=false。这让实验离线可复现,也意味着它不能证明真实地图 数据质量。

建表前的空间合同

business_object: delivery event
geometry_type: Point
canonical_srid: 4326
coordinate_order: longitude, latitude
geometry_role: topology and index
geography_role: meter distance
null_policy: forbidden for canonical event
validity_policy: ST_IsValid and non-empty
boundary_policy: ST_Covers
external_dataset: none

这份合同完整后,下一节的谓词与索引才有确定含义。

本节验收

psql "service=pg36-admin" \
  -f static/labs/ch16/boundary-semantics.sql

psql "service=pg36-admin" \
  -f static/labs/ch16/distance-semantics.sql

再检查生成列:

SELECT
  attname,
  attgenerated,
  format_type(atttypid, atttypmod)
FROM pg_attribute
WHERE attrelid =
      'shop_ch16.delivery_event'::regclass
  AND attname IN ('location', 'location_geog');

verify.sql 要求 location_geog.attgenerated = 's',所有 geometry 与 geography 的 SRID 都是 4326,围栏全部非空且有效。任一项漂移,时空查询 即使返回“看起来正确”的五行也不能通过。


上一节:时序表与时间分区 · 返回本章目录 · 下一节:空间谓词与索引 · 查看全书目录 · 查看索引中心

最后更新于