18. 地理学

在数据中,坐标通常是“地理坐标”或“经纬度”。

与墨卡托、UTM 或州平面中的坐标不同,地理坐标**不是笛卡尔坐标**。地理坐标不代表平面图上从原点绘制的线性距离。相反,这些**球面坐标**描述了地球仪上的角度坐标。在球面坐标中,点由从参考子午线(经度)旋转的角度和从赤道(纬度)的角度指定。

_images/cartesian_spherical.jpg

您可以将地理坐标视为近似的笛卡尔坐标,并继续进行空间计算。但是,距离、长度和面积的测量将是**无意义的**。由于球面坐标测量**角度**距离,因此单位为“度”。此外,来自索引和真/假测试(如 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 的功能非常有限。该函数仅适用于点,并且不支持跨越两极或国际日期变更线的索引。

当提出诸如“从洛杉矶飞往巴黎的航班将离冰岛多近?”这样的问题时,对非点几何的支持变得非常明显。

_images/lax_cdg.jpg

在笛卡尔平面(紫色线)上处理地理坐标会导致一个非常错误的答案!使用大圆路线(红色线)可以得到正确的答案。如果我们将 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 公里。

对于跨越国际日期变更线的要素,处理地理坐标的笛卡尔方法完全失效。从洛杉矶到东京的最短大圆路线穿过太平洋。最短的笛卡尔路线穿过大西洋和印度洋。

_images/lax_nrt.png
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 1

  • ST_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。

脚注

1(1,2)

缓冲区和交集函数实际上是基于转换为几何体的包装器,而不是在球面坐标系中本地执行。因此,它们可能无法对范围很大的对象返回正确的结果,这些对象无法干净地转换为平面表示。

例如,ST_Buffer(geography,distance) 函数将地理对象转换为“最佳”投影,对其进行缓冲,然后将其转换回地理坐标。如果没有“最佳”投影(对象太大),则操作可能会失败或返回格式错误的缓冲区。