18. 地理¶
数据坐标为“地理”或“纬度/经度”的情况非常常见。
与墨卡托投影、UTM 或州平面坐标不同,地理坐标**不是笛卡尔坐标**。地理坐标不表示在平面上绘制的与原点的线性距离。相反,这些**球面坐标**描述了地球上的角度坐标。在球面坐标中,一个点由参考子午线(经度)的旋转角度和与赤道(纬度)的角度指定。
您可以将地理坐标视为近似的笛卡尔坐标,并继续进行空间计算。但是,距离、长度和面积的测量结果将是**无意义的**。由于球面坐标测量的是**角度**距离,因此单位为“度”。此外,来自索引的近似结果以及诸如相交和包含之类的 true/false 测试可能变得非常错误。随着接近诸如极点或国际日期变更线等问题区域,点之间的距离会越来越大。
例如,以下是洛杉矶和巴黎的坐标。
洛杉矶:
POINT(-118.4079 33.9434)
巴黎:
POINT(2.3490 48.8533)
以下使用标准的 PostGIS 笛卡尔 ST_Distance(geometry, geometry) 计算洛杉矶和巴黎之间的距离。请注意,SRID 为 4326 声明了一个地理空间参考系统。
SELECT ST_Distance(
'SRID=4326;POINT(-118.4079 33.9434)'::geometry, -- Los Angeles (LAX)
'SRID=4326;POINT(2.5559 49.0083)'::geometry -- Paris (CDG)
);
121.898285970107
啊哈!122!但是,这意味着什么?
空间参考 4326 的单位是度。因此我们的答案是 122 度。但是(再次),这意味着什么?
在一个球体上,“一度的正方形”的大小变化很大,随着您远离赤道而变小。想想地球上的子午线(垂直线),当您走向两极时它们会越来越靠近。因此,122 度的距离没有任何意义。这是一个无意义的数字。
为了计算有意义的距离,我们必须将地理坐标视为真正的球面坐标,而不是近似的笛卡尔坐标。我们必须将点之间的距离测量为球体上的真实路径——大圆的一部分。
PostGIS 通过 geography
类型提供此功能。
注意
不同的空间数据库有不同的“处理地理数据”的方法
当 SRID 是地理坐标时,Oracle 会尝试通过透明地进行地理计算来掩盖差异。
SQL Server 使用两种空间类型,“STGeometry”用于笛卡尔数据,“STGeography”用于地理数据。
Informix Spatial 是 Informix 的纯笛卡尔扩展,而 Informix Geodetic 是纯地理扩展。
与 SQL Server 类似,PostGIS 使用两种类型,“geometry”和“geography”。
使用 geography
而不是 geometry
类型,让我们再次尝试测量洛杉矶和巴黎之间的距离。
SELECT ST_Distance(
'SRID=4326;POINT(-118.4079 33.9434)'::geography, -- Los Angeles (LAX)
'SRID=4326;POINT(2.5559 49.0083)'::geography -- Paris (CDG)
);
9124665.27317673
一个很大的数字!所有来自 geography
计算的返回值都以**米**为单位,因此我们的答案是 9125 公里。
PostGIS 的旧版本使用 ST_Distance_Spheroid(point, point, measurement) 函数支持球体上的非常基本的计算。但是,ST_Distance_Spheroid 有很大的局限性。该函数仅适用于点,并且不支持跨越极点或国际日期变更线的索引。
当提出诸如“从洛杉矶到巴黎的航班将与冰岛的距离有多近?”之类的问题时,支持非点几何形状的需求变得非常明确。
在笛卡尔平面(紫色线)上处理地理坐标会产生一个非常错误的答案!使用大圆路线(红线)会给出正确的答案。如果我们使用 geography
将我们的 LAX-CDG 航班转换为线字符串并计算到冰岛某点的距离,我们将得到正确的答案(回想一下)以米为单位。
SELECT ST_Distance(
ST_GeographyFromText('LINESTRING(-118.4079 33.9434, 2.5559 49.0083)'), -- LAX-CDG
ST_GeographyFromText('POINT(-22.6056 63.9850)') -- Iceland (KEF)
);
502454.906643729
因此,LAX-CDG 航线与冰岛的最近距离(从其国际机场测量)相对较小,为 502 公里。
对于跨越国际日期变更线的要素,笛卡尔方法处理地理坐标完全失效。从洛杉矶到东京的最短大圆航线跨越太平洋。最短的笛卡尔航线跨越大西洋和印度洋。
SELECT ST_Distance(
ST_GeometryFromText('Point(-118.4079 33.9434)'), -- LAX
ST_GeometryFromText('Point(139.733 35.567)')) -- NRT (Tokyo/Narita)
AS geometry_distance,
ST_Distance(
ST_GeographyFromText('Point(-118.4079 33.9434)'), -- LAX
ST_GeographyFromText('Point(139.733 35.567)')) -- NRT (Tokyo/Narita)
AS geography_distance;
geometry_distance | geography_distance
-------------------+--------------------
258.146005837336 | 8833954.76996256
18.1. 使用地理¶
为了将几何数据加载到地理表中,首先需要将几何图形投影到 EPSG:4326(经度/纬度),然后需要将其更改为地理。 ST_Transform(geometry,srid) 函数将坐标转换为地理坐标,Geography(geometry) 函数或 ::geography
后缀“转换”为地理坐标。
CREATE TABLE nyc_subway_stations_geog AS
SELECT
ST_Transform(geom,4326)::geography AS geog,
name,
routes
FROM nyc_subway_stations;
在地理表上构建空间索引与几何图形完全相同
CREATE INDEX nyc_subway_stations_geog_gix
ON nyc_subway_stations_geog USING GIST (geog);
区别在于底层:地理索引将正确处理覆盖极点或国际日期变更线的查询,而几何索引则不会。
以下是查找帝国大厦 500 米内所有地铁站的查询。
地理类型只有少量原生函数
ST_AsText(geography) 返回
text
ST_GeographyFromText(text) 返回
geography
ST_AsBinary(geography) 返回
bytea
ST_GeogFromWKB(bytea) 返回
geography
ST_AsSVG(geography) 返回
text
ST_AsGML(geography) 返回
text
ST_AsKML(geography) 返回
text
ST_AsGeoJson(geography) 返回
text
ST_Distance(geography, geography) 返回
double
ST_DWithin(geography, geography, float8) 返回
boolean
ST_Area(geography) 返回
double
ST_Length(geography) 返回
double
ST_Covers(geography, geography) 返回
boolean
ST_CoveredBy(geography, geography) 返回
boolean
ST_Intersects(geography, geography) 返回
boolean
ST_Buffer(geography, float8) 返回
geography
1ST_Intersection(geography, geography) 返回
geography
1
18.2. 创建地理表¶
使用地理列创建新表的 SQL 与创建几何表非常相似。但是,地理坐标包括在表创建时直接指定对象类型的功能。例如
CREATE TABLE airports (
code VARCHAR(3),
geog GEOGRAPHY(Point)
);
INSERT INTO airports
VALUES ('LAX', 'POINT(-118.4079 33.9434)');
INSERT INTO airports
VALUES ('CDG', 'POINT(2.5559 49.0083)');
INSERT INTO airports
VALUES ('KEF', 'POINT(-22.6056 63.9850)');
在表定义中,GEOGRAPHY(Point)
将我们的机场数据类型指定为点。新的地理字段不会在 geometry_columns
视图中注册。相反,它们注册在名为 geography_columns
的视图中。
SELECT * FROM geography_columns;
f_table_name | f_geography_column | srid | type
--------------------------+--------------------+------+----------
nyc_subway_stations_geog | geog | 0 | Geometry
airports | geog | 4326 | Point
注意
上面的输出中省略了一些列。
18.3. 转换为几何¶
虽然地理类型的基础函数可以处理许多用例,但有时您可能需要访问仅由几何类型支持的其他函数。幸运的是,您可以将对象在地理和几何之间来回转换。
PostgreSQL 的转换语法约定是在要转换的值的末尾附加 ::typename
。因此,2::text
会将数字 2 转换为文本字符串“2”。并且 'POINT(0 0)'::geometry
会将点的文本表示形式转换为几何点。
ST_X(point) 函数仅支持几何类型。我们如何从地理坐标中读取 X 坐标?
SELECT code, ST_X(geog::geometry) AS longitude FROM airports;
code | longitude
------+-----------
LAX | -118.4079
CDG | 2.5559
KEF | -21.8628
通过在地理值后附加 ::geometry
,我们将对象转换为 SRID 为 4326 的几何图形。从那里我们可以使用任意多的几何函数。但是,请记住 - 现在我们的对象是一个几何图形,坐标将被解释为笛卡尔坐标,而不是球面坐标。
18.4. 为什么(不)使用地理¶
地理坐标是普遍接受的坐标——每个人都理解纬度/经度的含义,但很少有人理解 UTM 坐标的含义。为什么不一直使用地理坐标?
首先,如前所述,(现在)直接支持地理类型的函数要少得多。您可能需要花费大量时间来解决地理类型限制。
其次,球体上的计算在计算上比笛卡尔计算昂贵得多。例如,距离的笛卡尔公式(毕达哥拉斯)涉及对 sqrt() 的一次调用。距离的球面公式(Haversine)涉及两次 sqrt() 调用、一次 arctan() 调用、四次 sin() 调用和两次 cos() 调用。三角函数非常耗费资源,而球面计算涉及很多三角函数。
结论是什么?
如果您的数据在地理上是紧凑的(包含在一个州、县或市内),请使用几何类型和对您的数据有意义的笛卡尔投影。请参阅 http://epsg.io 站点,并输入您所在区域的名称以选择可能的参考系统。
如果您需要使用地理上分散的数据集(覆盖世界大部分地区)来测量距离,请使用 geography 类型。 通过使用 geography
可以节省应用程序的复杂性,这将抵消任何性能问题。而且,转换为 geometry
可以弥补大多数功能上的限制。
18.5. 函数列表¶
ST_Distance(geometry, geometry): 对于 geometry 类型,返回两个几何图形在投影单位中基于空间参考的二维笛卡尔最小距离。 对于 geography 类型,默认返回两个地理位置之间以米为单位的球面最小距离。
ST_GeographyFromText(text): 从 Well-Known Text 表示或扩展 (WKT) 返回指定的 geography 值。
ST_Transform(geometry, srid): 返回一个新的几何图形,其坐标已转换为整数参数引用的 SRID。
ST_X(point): 返回点的 X 坐标,如果不可用则返回 NULL。输入必须是一个点。
ST_Azimuth(geography_A, geography_B): 返回从 A 到 B 的方向(以弧度为单位)。
ST_DWithin(geography_A, geography_B, R): 如果 A 与 B 的距离在 R 米之内,则返回 true。
脚注