18. 地理学¶
在数据中,坐标通常是“地理坐标”或“经纬度”。
与墨卡托、UTM 或州平面中的坐标不同,地理坐标**不是笛卡尔坐标**。地理坐标不代表平面图上从原点绘制的线性距离。相反,这些**球面坐标**描述了地球仪上的角度坐标。在球面坐标中,点由从参考子午线(经度)旋转的角度和从赤道(纬度)的角度指定。
您可以将地理坐标视为近似的笛卡尔坐标,并继续进行空间计算。但是,距离、长度和面积的测量将是**无意义的**。由于球面坐标测量**角度**距离,因此单位为“度”。此外,来自索引和真/假测试(如 intersects 和 contains)的近似结果可能变得非常错误。当接近极点或国际日期变更线等问题区域时,点之间的距离会变大。
例如,以下是洛杉矶和巴黎的坐标。
洛杉矶:
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
类型提供了此功能。
注意
不同的空间数据库对“处理地理信息”有不同的方法
Oracle 试图通过在 SRID 为地理信息时透明地进行地理计算来掩盖差异。
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 的功能非常有限。该函数仅适用于点,并且不支持跨越两极或国际日期变更线的索引。
当提出诸如“从洛杉矶飞往巴黎的航班将离冰岛多近?”这样的问题时,对非点几何的支持变得非常明显。
在笛卡尔平面(紫色线)上处理地理坐标会导致一个非常错误的答案!使用大圆路线(红色线)可以得到正确的答案。如果我们将 LAX-CDG 航班转换为线字符串,并使用 geography
计算到冰岛某个点的距离,我们将得到正确的答案(回忆)以米为单位。
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
中工作所节省的应用程序复杂性将抵消任何性能问题。将数据转换为 geometry
可以抵消大多数功能限制。
18.5. 函数列表¶
ST_Distance(geometry, geometry): 用于几何类型,返回两个几何体在投影单位下基于空间参考的二维笛卡尔最小距离。对于地理类型,默认情况下返回两个地理体之间的球面最小距离,单位为米。
ST_GeographyFromText(text): 从 Well-Known Text 表示或扩展 (WKT) 返回指定的地理值。
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。
脚注