Apache Spark SQL 地理空间数据类型详解:GEOMETRY 与 GEOGRAPHY 的选型、语法与实践
【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark
本文全面解析 Apache Spark SQL 中基于 OGC Simple Feature Access 规范的GEOMETRY(平面笛卡尔坐标系)与GEOGRAPHY(经纬度地理坐标系)两大地理空间数据类型。你将从本文掌握:两类类型的适用场景与选型原则、带 SRID 的类型声明语法、基于 WKB 的值构造与校验规则、内置 ST 函数的用法、SRID 注册表与存储兼容性(Parquet/Delta/Iceberg),并结合当前仓库源码理解其底层实现与错误处理机制。
一、GEOMETRY 与 GEOGRAPHY:两套坐标体系,两种计算模型
Spark SQL 支持GEOMETRY与GEOGRAPHY两种地理空间类型,其定义遵循 Open Geospatial Consortium (OGC) Simple Feature Access 规范。运行时,值以Well-Known Binary (WKB)二进制形式表示,并关联一个Spatial Reference Identifier (SRID)以确定其坐标系统;值最终如何持久化则由具体数据源决定。
| 类型 | 坐标系 | 典型用途与说明 |
|---|---|---|
| GEOMETRY | Cartesian(平面) | 投影坐标或局部坐标下的平面计算。以平面坐标系表示点、线、多边形,适合 Web Mercator(SRID 3857)、UTM 或局部网格(如工程/CAD)。可接受注册表中任意 SRID,包括 SRID 0(未指定 CRS)。 |
| GEOGRAPHY | Geographic(经纬度) | 基于地球的数据;在球面/椭球面上计算距离与面积。坐标以经度、纬度(度)表示。边插值始终为SPHERICAL(球形)。仅接受地理类 SRID,最常见为 4326(WGS 84)。 |
在 SQL 中,GEOMETRY和GEOGRAPHY列必须显式声明 SRID(或ANY)。当通过ST_GeomFromWKB(wkb)(不带显式 SRID)构造值时,该值 SRID 为0(未指定);而ST_GeogFromWKB(wkb)始终返回 SRID 为 4326 的值。
何时选 GEOMETRY
- 数据为局部或投影坐标(例如以米为单位的工程/CAD 数据,或 Web Mercator 地图瓦片);
- 需要在小范围/区域级进行平面运算:求交、并集、裁剪、包含关系、叠加分析,且把地表当作平面处理误差可接受;
- 顶点间距近或范围足够小,地球曲率可忽略不计。
何时选 GEOGRAPHY
- 数据为全球范围或跨越较大范围(例如国界、全球兴趣点);
- 距离或面积的计算必须尊重地球曲率(例如两城市间最短路径、球面上多边形面积);
- 典型场景包括航空、航海、全球移动出行等依赖大圆(great-circle)或测地线(geodesic)行为的使用场景。
选错类型可能得到误导性结果:例如球面上伦敦到纽约的最短路径会穿越加拿大,而平面 GEOMETRY 计算可能给出不经过加拿大的路径。
二、SQL 类型语法:SRID 与 ANY
在 SQL 中,类型必须携带 SRID 或ANY:
- 固定 SRID(列内所有值共享同一个 SRID):
GEOMETRY(srid)—— 例如GEOMETRY(4326)、GEOMETRY(3857)GEOGRAPHY(srid)—— 例如GEOGRAPHY(4326)
- 混合 SRID(列内各值可以有不同的 SRID):
GEOMETRY(ANY)GEOGRAPHY(ANY)
不带参数、未声明(srid)或(ANY)的裸GEOMETRY/GEOGRAPHY在 SQL 中不受支持。
从当前仓库源码看,类型层面对「固定/混合」的区分做了显式建模:GeometryType.scala 中isMixedSrid通过判断 SRID 是否为MIXED_SRID实现;typeName在混合 SRID 时显示为"geometry(any)",固定 SRID 时显示为"geometry($srid)"。此外,acceptsGeometryType规定了类型兼容规则:混合 SRID 类型可接受任意其他 GeometryType(固定或混合);固定 SRID 类型只接受同 SRID 的 GeometryType,且不接受混合 SRID。
三、创建带地理空间列的表格
-- 固定 SRID:所有值必须使用给定 SRID(例如 WGS 84) CREATE TABLE points ( id BIGINT, pt GEOMETRY(4326) ); CREATE TABLE locations ( id BIGINT, loc GEOGRAPHY(4326) ); -- 混合 SRID:每一行可以有不同 SRID CREATE TABLE mixed_geoms ( id BIGINT, geom GEOMETRY(ANY) );四、构造 GEOMETRY 与 GEOGRAPHY 值
值通过内置函数从Well-Known Binary (WKB)构造。WKB 是点、线、多边形等空间形状的标准二进制编码格式。
从 WKB(二进制)构造:
ST_GeomFromWKB(wkb)—— 返回 SRID 默认值为 0 的 GEOMETRYST_GeomFromWKB(wkb, srid)—— 返回给定 SRID 的 GEOMETRYST_GeogFromWKB(wkb)—— 返回 SRID 为 4326 的 GEOGRAPHY
示例(WKB 格式的点,随后写入表):
-- Point (1, 2) 的 WKB(小端序、2D 点) SELECT ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040'); SELECT ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040', 4326); SELECT ST_GeogFromWKB(X'0101000000000000000000F03F0000000000000040'); INSERT INTO points (id, pt) VALUES (1, ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040', 4326));在源码层面,stExpressions.scala 中ST_GeomFromWKB、ST_GeogFromWKB、ST_AsBinary、ST_Srid、ST_SetSrid均实现为RuntimeReplaceable表达式:ST_GeomFromWKB(wkb)与ST_GeogFromWKB(wkb)是一元表达式、输入类型为BinaryType;ST_GeomFromWKB(wkb, srid)与ST_SetSrid(geo, srid)为二元表达式;ST_Srid、ST_AsBinary的输入限定为GeographyType/GeometryType集合。这意味着这些 ST 函数在 SQL 中可直接与二进制字面量(X'...')组合使用,并被规约到统一的内部实现。
五、WKB 坐标处理规则
解析 WKB 时 Spark 应用以下规则,违反任一规则都会产生解析错误:
- 空点:对于Point几何(包括 MultiPoint 内部的点),允许NaN(Not a Number)坐标值,表示一个空点(如 Well-Known Text 中的
POINT EMPTY)。LineString与Polygon(及其内部的点)不允许坐标值为 NaN。 - 非点坐标:LineString、Polygon环以及属于这些结构的点,坐标值必须为有限值(不允许 NaN、正负无穷)。
- 无穷大:任何坐标值都不接受正无穷或负无穷。
- 多边形环:每个环必须闭合(首尾点相等)且至少 4 个点;LineString至少 2 个点。
- GEOGRAPHY 边界:当 WKB 以GEOGRAPHY解析(例如通过
ST_GeogFromWKB)时,经度必须在[-180, 180](含端点)、纬度必须在[-90, 90](含端点)。GEOMETRY 不强制这些边界。 - 无效 WKB:Null 或空输入、字节被截断、无效的几何类型或字节序,以及其他格式错误的 WKB。
六、内置地理空间(ST)函数
Spark SQL 提供一组标量函数处理 GEOMETRY 与 GEOGRAPHY 值。完整函数列表及详细参数说明和示例,见 Built-in Functions 页面的Geospatial ST Functions一节。当前版本提供的函数汇总如下:
| 函数 | 说明 |
|---|---|
ST_AsBinary(geo[, endianness]) | 将 GEOMETRY 或 GEOGRAPHY 值以 WKB(BINARY)形式返回。可选endianness参数为'NDR'(小端,默认)或'XDR'(大端)。 |
ST_GeomFromWKB(wkb[, srid]) | 解析 WKB 并返回 GEOMETRY。可选srid参数设置 SRID;省略时 SRID 为0。 |
ST_GeogFromWKB(wkb) | 解析 WKB 并返回 SRID 为 4326 的 GEOGRAPHY。 |
ST_Srid(geo) | 返回 GEOMETRY 或 GEOGRAPHY 值的 SRID(输入为 NULL 时返回 NULL)。 |
ST_SetSrid(geo, srid) | 返回带给定 SRID 的新 GEOMETRY 或 GEOGRAPHY。新 SRID 必须对该值类型有效。 |
关于字节序,源码注释明确:ST_AsBinary未指定字节序时默认使用小端编码("NDR")——这正是上面 WKB 示例(X'01010000...',小端点)能直接读写回环的原因。
示例:
SELECT hex(ST_AsBinary(ST_GeogFromWKB(X'0101000000000000000000F03F0000000000000040'))); -- 0101000000000000000000F03F0000000000000040 SELECT hex(ST_AsBinary(ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040'), 'XDR')); -- 00000000013FF00000000000004000000000000000 SELECT ST_Srid(ST_GeogFromWKB(X'0101000000000000000000F03F0000000000000040')); -- 4326 SELECT ST_Srid(ST_SetSrid(ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040'), 3857)); -- 3857这些函数均在仓库测试中得到了验证,例如 st-functions.sql.out 中记录了ST_GeomFromWKB、ST_SetSrid、ST_GeogFromWKB以及CAST(... AS GEOMETRY(4326))等查询的分析结果,可直接作为复现参考。
七、SRID 与存储值
- 固定 SRID 列:列内每个值的 SRID 必须与列类型一致。插入不同 SRID 的值会抛出
GEO_ENCODER_SRID_MISMATCH_ERROR。可用ST_SetSrid将值的 SRID 改为与列匹配。 - 混合 SRID 列(
GEOMETRY(ANY)或GEOGRAPHY(ANY)):每行值可以有不同的 SRID。每个值仍须对该类型有效;无效 SRID 会抛出ST_INVALID_SRID_VALUE。 - 存储:Parquet、Delta、Iceberg 以「每列固定 SRID」方式存储 geometry/geography。它们不支持持久化
GEOMETRY(ANY)或GEOGRAPHY(ANY);混合 SRID 类型仅用于内存/查询场景。
源码对上述错误与兼容性做了精确实现:
- GeometryType.scala 的
assertSridAllowedForType方法:固定 SRID 类型下值 SRID 与列 SRID 不一致时抛出GEO_ENCODER_SRID_MISMATCH_ERROR(错误参数包含 type、valueSrid、typeSrid);混合 SRID 类型下值 SRID 不在支持范围内时抛出ST_INVALID_SRID_VALUE。 - 同一文件中的
GeometryType.apply(srid)在构造类型时即通过CartesianSpatialReferenceSystemMapper查注册表,查不到即抛ST_INVALID_SRID_VALUE;toSrid对无法识别的 CRS 字符串抛ST_INVALID_CRS_VALUE。 ST_INVALID_SRID_VALUE错误类在 QueryExecutionErrors.scala 中统一定义,并附带发生错误的 srid 参数。- 测试层面,GeometryDataFrameSuite.scala 与 GeographyDataFrameSuite.scala 分别对
GEO_ENCODER_SRID_MISMATCH_ERROR和ST_INVALID_SRID_VALUE做了断言,是理解「列 SRID 与值 SRID 校验」行为的最直接参考。
另外从源码可以确认类型的一些实现细节:GeometryType的defaultSize为 2048 字节(约可存储 120 个 2D 点);其 JSON 表示使用 CRS 字符串(如geometry(OGC:CRS84)),这与 Parquet、Delta、Iceberg 的存储规范保持一致;类型相等性由 SRID 唯一决定(equals/hashCode基于 SRID)。
八、支持的 SRID
Spark 内置一份预构建的 SRID 注册表,由 PROJ 数据库中的坐标系与 OGC 标准覆盖项组合而成,用于坐标系的校验与正确处理。
SRID 兼容规则:
- GEOMETRY接受注册表中的所有 SRID(地理 + 投影 + SRID 0)
- GEOGRAPHY仅接受地理类 SRID(经纬度坐标系)
PROJ 版本与 Spark 版本对照
| Spark 版本 | PROJ 版本 |
|---|---|
| 4.2.0 | 9.8.1 |
SRID 注册表固定绑定上表中的 PROJ 版本,不会与外部数据库实时同步。
OGC 标准覆盖项
Spark 对 PROJ 数据库中的部分 SRID 应用了 OGC 标准覆盖:
| SRID | PROJ CRS 标识 | OGC CRS 标识 | 说明 |
|---|---|---|---|
| 4326 | EPSG:4326 | OGC:CRS84 | WGS 84(按 OGC 标准为经度/纬度顺序) |
| 4267 | EPSG:4267 | OGC:CRS27 | NAD27 |
| 4269 | EPSG:4269 | OGC:CRS83 | NAD83 |
常用 SRID
| SRID | CRS 标识 | 名称 | CRS 类型 | 说明 |
|---|---|---|---|---|
| 0 | SRID:0 | Unspecified | Cartesian | 未定义 CRS 的坐标(ST_GeomFromWKB(wkb)的默认值) |
| 4326 | OGC:CRS84 | WGS 84 | Geographic | 1984 世界大地坐标系(经度/纬度),GPS 坐标、全球数据(GEOGRAPHY 默认) |
| 4267 | OGC:CRS27 | NAD27 | Geographic | 北美 1927 大地基准 |
| 4269 | OGC:CRS83 | NAD83 | Geographic | 北美 1983 大地基准 |
| 3857 | EPSG:3857 | Web Mercator | Projected | Web 地图服务使用的伪墨卡托投影 |
注意事项:
GEOMETRY(0)表示固定 SRID 为 0。若需要每行不同 SRID,请使用GEOMETRY(ANY)。- Parquet 与 Iceberg 的地理空间规范要求每列固定 SRID,因此不支持持久化
GEOMETRY(ANY)或GEOGRAPHY(ANY)。
SRID 校验示例
无效 SRID(不在注册表中):
SELECT ST_GeomFromWKB(X'0101000000000000000000F03F0000000000000040', 99999); -- Throws [ST_INVALID_SRID_VALUE]投影 SRID 用于 GEOGRAPHY 类型:
CREATE TABLE invalid_geo (id BIGINT, loc GEOGRAPHY(3857)); -- Throws [ST_INVALID_SRID_VALUE] (3857 is projected, not geographic)九、数据类型的 API 参考
关于 Scala、Java、Python、SQL 中完整的数据类型列表与 API 用法,参见 Data Types 文档;全部 ST 函数的签名、参数与示例参见 Built-in Functions 中 Geospatial ST Functions 章节。本文所涉及的 Geo 类型与函数实现集中位于 sql/api 的 types 包 与 catalyst 的 expressions/st 包,测试与示例可进一步参阅 GeometryDataFrameSuite.scala、GeographyDataFrameSuite.scala 及 st-functions.sql.out。
【免费下载链接】sparkApache Spark - A unified analytics engine for large-scale data processing项目地址: https://gitcode.com/gh_mirrors/sp/spark
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考