第 4 章 数据管理

目录
4.1. 空间数据模型
4.1.1. OGC 几何
4.1.2. SQL/MM 第 3 部分 - 曲线
4.1.3. WKT 和 WKB
4.2. 几何数据类型
4.2.1. PostGIS EWKB 和 EWKT
4.3. 地理数据类型
4.3.1. 创建地理表
4.3.2. 使用地理表
4.3.3. 何时使用地理数据类型
4.3.4. 地理高级常见问题解答
4.4. 几何验证
4.4.1. 简单几何
4.4.2. 有效几何
4.4.3. 管理有效性
4.5. 空间参考系统
4.5.1. SPATIAL_REF_SYS 表
4.5.2. 用户定义的空间参考系统
4.6. 空间表
4.6.1. 创建空间表
4.6.2. GEOMETRY_COLUMNS 视图
4.6.3. 手动注册几何列
4.7. 加载空间数据
4.7.1. 使用 SQL 加载数据
4.7.2. 使用 Shapefile 加载器
4.8. 提取空间数据
4.8.1. 使用 SQL 提取数据
4.8.2. 使用 Shapefile 倾卸器
4.9. 空间索引
4.9.1. GiST 索引
4.9.2. BRIN 索引
4.9.3. SP-GiST 索引
4.9.4. 调整索引使用

4.1. 空间数据模型

4.1.1. OGC 几何

开放地理空间联盟 (OGC) 制定了 简单要素访问 标准 (SFA) 来为地理空间数据提供模型。它定义了基本空间类型 几何,以及用于操纵和转换几何值以执行空间分析任务的操作。PostGIS 将 OGC 几何模型实现为 PostgreSQL 数据类型 geometrygeography

几何是一种抽象类型。几何值属于其具体子类型之一,表示各种类型和尺寸的几何形状。这些包括原子类型线线环多边形,以及集合类型多点多线多边形几何集合简单要素访问 - 第 1 部分:通用架构 v1.2.1多面体三角形TIN结构添加了子类型。

几何在二维笛卡尔平面上建模形状。多面体、三角形和 TIN 类型还可以在三维空间中表示形状。形状的大小和位置由其坐标指定。每个坐标都有一个 X 和 Y 纵坐标值,用于确定其在平面中的位置。形状由点或线段构成,点由单个坐标指定,线段由两个坐标指定。

坐标可能包含可选的 Z 和 M 纵坐标值。Z 纵坐标通常用于表示高度。M 纵坐标包含一个度量值,可以表示时间或距离。如果几何值中存在 Z 或 M 值,则必须为几何中的每个点定义这些值。如果几何具有 Z 或 M 纵坐标,则坐标维度为 3D;如果同时具有 Z 和 M,则坐标维度为 4D。

几何值与空间参考系统相关联,该系统指示其嵌入其中的坐标系。空间参考系统由几何 SRID 号码标识。X 和 Y 轴的单位由空间参考系统确定。在平面参考系中,X 和 Y 坐标通常表示东向和北向,而在大地系中,它们表示经度和纬度。SRID 0 表示一个无限的笛卡尔平面,其轴上未分配任何单位。请参阅第 4.5 节,“空间参考系统”

几何维度是几何类型的属性。点类型的维度为 0,线类型的维度为 1,多边形类型的维度为 2。集合的维度为最大元素维度的维度。

几何值可能为。空值不包含顶点(对于原子几何类型)或不包含元素(对于集合)。

几何值的一个重要属性是其空间范围边界框,OGC 模型称之为包络。这是包围几何坐标的 2 或 3 维框。它是一种有效的方式来表示几何在坐标空间中的范围,并检查两个几何是否相互作用。

几何模型允许评估拓扑空间关系,如第 5.1.1 节“维度扩展的 9 交集模型”中所述。为了支持这一点,为每种几何类型定义了内部边界外部的概念。几何在拓扑上是封闭的,因此它们始终包含其边界。边界是一个维度比几何本身低 1 的几何。

OGC 几何模型为每种几何类型定义了有效性规则。这些规则确保几何值表示现实情况(例如,可以指定一个孔洞位于壳体外部的多边形,但这在几何上没有意义,因此无效)。PostGIS 还允许存储和操作无效几何值。这允许在需要时检测并修复它们。请参阅第 4.4 节“几何验证”

4.1.1.1. 点

点是 0 维几何,表示坐标空间中的单个位置。

POINT (1 2)
POINT Z (1 2 3)
POINT ZM (1 2 3 4)

4.1.1.2. 线串

线串是由连续线段序列形成的 1 维线。每个线段由两个点定义,一个线段的端点形成下一个线段的起点。OGC 有效线串有两个或更多点,但 PostGIS 也允许单点线串。线串可以自相交(自相交)。如果起点和终点相同,则线串为闭合。如果线串不自相交,则线串为简单

LINESTRING (1 2, 3 4, 5 6)

4.1.1.3. 线性环

线性环既是闭合的又是简单的线串。第一个点和最后一个点必须相等,并且线不能自相交。

LINEARRING (0 0 0, 4 0 0, 4 4 0, 0 4 0, 0 0 0)

4.1.1.4. 多边形

多边形是一个 2 维平面区域,由外部边界(壳体)和零个或多个内部边界(孔洞)限定。每个边界都是线性环

POLYGON ((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0))

4.1.1.5. 多点

多点是多个点的集合。

MULTIPOINT ( (0 0), (1 2) )

4.1.1.6. 多线串

多线串是多个线串的集合。如果多线串的每个元素都是闭合的,则该多线串也是闭合的。

MULTILINESTRING ( (0 0,1 1,1 2), (2 3,3 2,5 4) )

4.1.1.7. 多面

多面是多个不重叠、不相邻的面集合。集合中的面可能仅在有限个点处相切。

MULTIPOLYGON (((1 5, 5 5, 5 1, 1 1, 1 5)), ((6 5, 9 1, 6 1, 6 5)))

4.1.1.8. 几何集合

几何集合是几何的异构(混合)集合。

GEOMETRYCOLLECTION ( POINT(2 3), LINESTRING(2 3, 3 4))

4.1.1.9. 多面体表面

多面体表面是共享一些边的斑块或小平面的连续集合。每个斑块都是一个平面多边形。如果多边形坐标具有 Z 坐标,则该表面是三维的。

POLYHEDRALSURFACE Z (
  ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)),
  ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)),
  ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)),
  ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)),
  ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)),
  ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )

4.1.1.10. 三角形

三角形是由三个不同的非共线顶点定义的多边形。由于三角形是多边形,因此它由四个坐标指定,第一个和第四个坐标相等。

TRIANGLE ((0 0, 0 9, 9 0, 0 0))

4.1.1.11. TIN

TIN 是多个不重叠的 三角形的集合,表示 三角不规则网

TIN Z ( ((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0)) )

4.1.2. SQL/MM 第 3 部分 - 曲线

ISO/IEC 13249-3 SQL 多媒体 - 空间 标准(SQL/MM)扩展了 OGC SFA,以定义包含圆弧曲线的几何子类型。SQL/MM 类型支持 3DM、3DZ 和 4D 坐标。

[Note]

SQL-MM 实现中的所有浮点比较都执行到指定的容差,当前为 1E-8。

4.1.2.1. 圆形串

圆形串是基本曲线类型,类似于线性世界中的线串。单个弧段由三个点指定:起点和终点(第一个和第三个)以及弧上的其他一些点。要指定一个闭合圆,则起点和终点相同,中点是圆直径上的对点(即圆心)。在弧序列中,前一个弧的终点是下一个弧的起点,就像线串的线段一样。这意味着圆形串必须具有大于 1 的奇数个点。

CIRCULARSTRING(0 0, 1 1, 1 0)

CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0)

4.1.2.2. 复合曲线

复合曲线是一个单一的连续曲线,可能包含圆弧线段和直线段。这意味着除了具有结构良好的组件之外,每个组件的端点(最后一个除外)必须与后续组件的起点重合。

COMPOUNDCURVE( CIRCULARSTRING(0 0, 1 1, 1 0),(1 0, 0 1))

4.1.2.3. 曲线多边形

曲线多边形类似于多边形,具有一个外环和零个或多个内环。区别在于,环可以是圆形字符串或复合曲线以及线字符串。

从 PostGIS 1.4 开始,PostGIS 在曲线多边形中支持复合曲线。

CURVEPOLYGON(
  CIRCULARSTRING(0 0, 4 0, 4 4, 0 4, 0 0),
  (1 1, 3 3, 3 1, 1 1) )

示例:一个曲线多边形,其壳由包含圆形字符串和线字符串的复合曲线定义,并且由圆形字符串定义的孔

CURVEPOLYGON(
  COMPOUNDCURVE( CIRCULARSTRING(0 0,2 0, 2 1, 2 3, 4 3),
                 (4 3, 4 5, 1 4, 0 0)),
  CIRCULARSTRING(1.7 1, 1.4 0.4, 1.6 0.4, 1.6 0.5, 1.7 1) )

4.1.2.4. 多曲线

多曲线是曲线集合,可以包括线字符串、圆形字符串或复合曲线。

MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4))

4.1.2.5. 多曲面

多曲面是曲面集合,可以是(线性)多边形或曲线多边形。

MULTISURFACE(
  CURVEPOLYGON(
    CIRCULARSTRING( 0 0, 4 0, 4 4, 0 4, 0 0),
    (1 1, 3 3, 3 1, 1 1)),
  ((10 10, 14 12, 11 10, 10 10), (11 11, 11.5 11, 11 11.5, 11 11)))

4.1.3. WKT 和 WKB

OGC SFA 规范定义了两种用于表示外部使用几何值的形式:众所周知的文本(WKT)和众所周知的二进制(WKB)。WKT 和 WKB 都包含有关对象类型和定义它的坐标的信息。

众所周知的文本(WKT)提供空间数据的标准文本表示形式。空间对象的 WKT 表示形式的示例为

  • POINT(0 0)

  • POINT Z (0 0 0)

  • POINT ZM (0 0 0 0)

  • POINT EMPTY

  • LINESTRING(0 0,1 1,1 2)

  • LINESTRING EMPTY

  • POLYGON((0 0,4 0,4 4,0 4,0 0),(1 1, 2 1, 2 2, 1 2,1 1))

  • MULTIPOINT((0 0),(1 2))

  • MULTIPOINT Z ((0 0 0),(1 2 3))

  • MULTIPOINT EMPTY

  • MULTILINESTRING((0 0,1 1,1 2),(2 3,3 2,5 4))

  • MULTIPOLYGON(((0 0,4 0,4 4,0 4,0 0),(1 1,2 1,2 2,1 2,1 1)), ((-1 -1,-1 -2,-2 -2,-2 -1,-1 -1)))

  • GEOMETRYCOLLECTION(POINT(2 3),LINESTRING(2 3,3 4))

  • GEOMETRYCOLLECTION EMPTY

WKT 的输入和输出由函数 ST_AsTextST_GeomFromText 提供

text WKT = ST_AsText(geometry);
geometry = ST_GeomFromText(text WKT, SRID);

例如,从 WKT 和 SRID 创建和插入空间对象的语句为

INSERT INTO geotable ( geom, name )
  VALUES ( ST_GeomFromText('POINT(-126.4 45.32)', 312), 'A Place');

众所周知的二进制 (WKB) 提供了空间数据作为二进制数据(字节数组)的可移植全精度表示。空间对象 WKB 表示的示例是

  • WKT: POINT(1 1)

    WKB: 0101000000000000000000F03F000000000000F03

  • WKT: LINESTRING (2 2, 9 9)

    WKB: 0102000000020000000000000000000040000000000000004000000000000022400000000000002240

WKB 的输入和输出由函数 ST_AsBinaryST_GeomFromWKB 提供

bytea WKB = ST_AsBinary(geometry);
geometry = ST_GeomFromWKB(bytea WKB, SRID);

例如,从 WKB 创建和插入空间对象的语句是

INSERT INTO geotable ( geom, name )
  VALUES ( ST_GeomFromWKB('\x0101000000000000000000f03f000000000000f03f', 312), 'A Place');

4.2. 几何数据类型

PostGIS 通过定义名为 geometry 的 PostgreSQL 数据类型来实现 OGC 简单要素模型。它使用内部类型代码(参见 GeometryTypeST_GeometryType)表示所有几何子类型。这允许将空间要素建模为使用类型 geometry 的列定义的表行。

geometry 数据类型是 不透明 的,这意味着所有访问都是通过调用几何值上的函数来完成的。函数允许创建几何对象、访问或更新所有内部字段以及计算新的几何值。PostGIS 支持 OGC 简单要素访问 - 第 2 部分:SQL 选项 (SFS) 规范中指定的所有函数,以及许多其他函数。有关函数的完整列表,请参见 第 7 章,PostGIS 参考

[Note]

PostGIS 通过在空间函数前加上“ST_”前缀来遵循 SFA 标准。这本意是代表“空间和时间”,但该标准的时间部分从未开发。相反,它可以解释为“空间类型”。

SFA 标准规定空间对象包括空间参考系统标识符 (SRID)。在创建空间对象以插入到数据库中时需要 SRID(它可以默认为 0)。参见 ST_SRID第 4.5 节,“空间参考系统”

为了使几何查询更有效,PostGIS 定义了各种空间索引以及使用它们的空間运算符。有关详细信息,请参见 第 4.9 节,“空间索引”第 5.2 节,“使用空间索引”

4.2.1. PostGIS EWKB 和 EWKT

OGC SFA 规范最初仅支持 2D 几何,并且几何 SRID 不包含在输入/输出表示中。OGC SFA 规范 1.2.1(与 ISO 19125 标准一致)增加了对 3D (ZYZ) 和测量 (XYM 和 XYZM) 坐标的支持,但仍然不包括 SRID 值。

由于这些限制,PostGIS 定义了扩展的 EWKB 和 EWKT 格式。它们提供 3D (XYZ 和 XYM) 和 4D (XYZM) 坐标支持,并包括 SRID 信息。包含所有几何信息允许 PostGIS 将 EWKB 用作记录格式(例如,在 DUMP 文件中)。

EWKB 和 EWKT 用于 PostGIS 数据对象的“规范形式”。对于输入,二进制数据的规范形式是 EWKB,对于文本数据,EWKB 或 EWKT 均可接受。这允许通过使用 ::geometry 将 HEXEWKB 或 EWKT 中的文本值强制转换为几何值来创建几何值。对于输出,二进制的规范形式是 EWKB,对于文本,它是 HEXEWKB(十六进制编码的 EWKB)。

例如,此语句通过从 EWKT 文本值强制转换来创建几何,并使用 HEXEWKB 的规范形式输出它

SELECT 'SRID=4;POINT(0 0)'::geometry;
  geometry
  ----------------------------------------------------
  01010000200400000000000000000000000000000000000000

PostGIS EWKT 输出与 OGC WKT 有些不同

  • 对于 3DZ 几何,Z 限定符被省略

    OGC:POINT Z (1 2 3)

    EWKT:POINT (1 2 3)

  • 对于 3DM 几何,包括 M 限定符

    OGC:POINT M (1 2 3)

    EWKT:POINTM (1 2 3)

  • 对于 4D 几何,ZM 限定符被省略

    OGC:POINT ZM (1 2 3 4)

    EWKT:POINT (1 2 3 4)

EWKT 避免过度指定维度以及 OGC/ISO 格式可能出现的差异,例如

  • POINT ZM (1 1)

  • POINT ZM (1 1 1)

  • POINT (1 1 1 1)

[Caution]

PostGIS 扩展格式目前是 OGC 格式的超集,因此每个有效的 OGC WKB/WKT 也是有效的 EWKB/EWKT。但是,如果 OGC 以与 PosGIS 定义冲突的方式扩展格式,则将来可能会发生变化。因此,您不应该依赖这种兼容性!

空间对象的 EWKT 文本表示的示例有

  • POINT(0 0 0) -- XYZ

  • SRID=32632;POINT(0 0) -- 带 SRID 的 XY

  • POINTM(0 0 0) -- XYM

  • POINT(0 0 0 0) -- XYZM

  • SRID=4326;MULTIPOINTM(0 0 0,1 2 1) -- 带 SRID 的 XYM

  • MULTILINESTRING((0 0 0,1 1 0,1 2 1),(2 3 1,3 2 1,5 4 1))

  • POLYGON((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0))

  • MULTIPOLYGON(((0 0 0,4 0 0,4 4 0,0 4 0,0 0 0),(1 1 0,2 1 0,2 2 0,1 2 0,1 1 0)),((-1 -1 0,-1 -2 0,-2 -2 0,-2 -1 0,-1 -1 0)))

  • GEOMETRYCOLLECTIONM( POINTM(2 3 9), LINESTRINGM(2 3 4, 3 4 5) )

  • MULTICURVE( (0 0, 5 5), CIRCULARSTRING(4 0, 4 4, 8 4) )

  • POLYHEDRALSURFACE( ((0 0 0, 0 0 1, 0 1 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 1 0 0, 0 0 0)), ((0 0 0, 1 0 0, 1 0 1, 0 0 1, 0 0 0)), ((1 1 0, 1 1 1, 1 0 1, 1 0 0, 1 1 0)), ((0 1 0, 0 1 1, 1 1 1, 1 1 0, 0 1 0)), ((0 0 1, 1 0 1, 1 1 1, 0 1 1, 0 0 1)) )

  • TRIANGLE ((0 0, 0 10, 10 0, 0 0))

  • TIN( ((0 0 0, 0 0 1, 0 1 0, 0 0 0)), ((0 0 0, 0 1 0, 1 1 0, 0 0 0)) )

可以使用以下函数使用这些格式进行输入和输出

bytea EWKB = ST_AsEWKB(geometry);
text EWKT = ST_AsEWKT(geometry);
geometry = ST_GeomFromEWKB(bytea EWKB);
geometry = ST_GeomFromEWKT(text EWKT);

例如,使用 EWKT 创建和插入 PostGIS 空间对象的语句是

INSERT INTO geotable ( geom, name )
  VALUES ( ST_GeomFromEWKT('SRID=312;POINTM(-126.4 45.32 15)'), 'A Place' )

4.3. 地理数据类型

PostGIS geography 数据类型为表示在“地理”坐标(有时称为“大地”坐标或“纬度/经度”或“经度/纬度”)上的空间要素提供本机支持。地理坐标是表示为角度单位(度)的球面坐标。

PostGIS 几何数据类型的基础是平面。平面上的两点之间的最短路径是一条直线。这意味着对几何(面积、距离、长度、交点等)的函数使用直线向量和笛卡尔数学进行计算。这使得它们更容易实现并且执行起来更快,但对于球形地球表面的数据来说,它们也不准确。

PostGIS 地理数据类型基于球形模型。球体上两点之间的最短路径是大圆弧。对地理(面积、距离、长度、交点等)的函数使用球体上的弧进行计算。通过考虑球形地球的形状,这些函数提供了更准确的结果。

由于底层数学更复杂,因此为地理类型定义的函数比几何类型少。随着时间的推移,随着新算法的添加,地理类型的功能将得到扩展。作为一种解决方法,可以在几何和地理类型之间进行转换。

与几何数据类型一样,地理数据通过空间参考系统标识符 (SRID) 与空间参考系统相关联。可以使用 spatial_ref_sys 表中定义的任何大地(基于经度/纬度)空间参考系统。(在 PostGIS 2.2 之前,地理类型仅支持 WGS 84 大地(SRID:4326))。您可以按照 第 4.5.2 节,“用户定义的空间参考系统” 中所述添加自己的自定义大地空间参考系统。

对于所有空间参考系统,测量函数(例如 ST_DistanceST_LengthST_PerimeterST_Area)和 ST_DWithin 的距离参数返回的单位为米。

4.3.1. 创建地理表

您可以使用带有 geography 类型列的 CREATE TABLE SQL 语句创建用于存储地理数据的表。以下示例创建了一个地理列的表,该列在 WGS84 大地坐标系(SRID 4326)中存储 2D 线串

CREATE TABLE global_points (
    id SERIAL PRIMARY KEY,
    name VARCHAR(64),
    location geography(POINT,4326)
  );

地理类型支持两个可选类型修饰符

  • 空间类型修饰符限制了列中允许的形状和尺寸类型。空间类型的允许值有:POINT、LINESTRING、POLYGON、MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION。地理类型不支持曲线、TIN 或 POLYHEDRALSURFACEs。修饰符通过添加后缀来支持坐标维数限制:Z、M 和 ZM。例如,'LINESTRINGM' 修饰符仅允许三维线串,并将第三维视为度量。类似地,“POINTZM”需要四维(XYZM)数据。

  • SRID 修饰符将空间参考系统 SRID 限制为特定数字。如果省略,则 SRID 默认为 4326(WGS84 大地),并且所有计算都使用 WGS84 执行。

创建具有地理列的表的示例

  • 使用默认 SRID 4326(WGS84 经度/纬度)创建具有 2D 点地理的表

    CREATE TABLE ptgeogwgs(gid serial PRIMARY KEY, geog geography(POINT) );
  • 在 NAD83 经纬度中创建具有 2D 点地理的表

    CREATE TABLE ptgeognad83(gid serial PRIMARY KEY, geog geography(POINT,4269) );
  • 创建具有 3D(XYZ)点和明确 SRID 4326 的表

    CREATE TABLE ptzgeogwgs84(gid serial PRIMARY KEY, geog geography(POINTZ,4326) );
  • 使用默认 SRID 4326 创建具有 2D 线地理的表

    CREATE TABLE lgeog(gid serial PRIMARY KEY, geog geography(LINESTRING) );
  • 使用 SRID 4267(NAD 1927 经度纬度)创建具有 2D 多边形地理的表

    CREATE TABLE lgeognad27(gid serial PRIMARY KEY, geog geography(POLYGON,4267) );

地理字段在 geography_columns 系统视图中注册。您可以查询 geography_columns 视图并查看该表是否已列出

SELECT * FROM geography_columns;

创建空间索引与几何列的工作方式相同。PostGIS 将注意列类型是 GEOGRAPHY,并创建一个适当的基于球体的索引,而不是用于 GEOMETRY 的常规平面索引。

-- Index the test table with a spherical index
CREATE INDEX global_points_gix ON global_points USING GIST ( location );

4.3.2. 使用地理表

您可以像几何一样将数据插入地理表中。如果几何数据具有 SRID 4326,则会自动转换为地理类型。还可以使用 EWKT 和 EWKB 格式来指定地理值。

-- Add some data into the test table
INSERT INTO global_points (name, location) VALUES ('Town', 'SRID=4326;POINT(-110 30)');
INSERT INTO global_points (name, location) VALUES ('Forest', 'SRID=4326;POINT(-109 29)');
INSERT INTO global_points (name, location) VALUES ('London', 'SRID=4326;POINT(0 49)');

spatial_ref_sys 表中列出的任何大地(经度/纬度)空间参考系统都可以指定为地理 SRID。如果使用非大地坐标系,则会引发错误。

-- NAD 83 lon/lat
SELECT 'SRID=4269;POINT(-123 34)'::geography;
                    geography
----------------------------------------------------
 0101000020AD1000000000000000C05EC00000000000004140
-- NAD27 lon/lat
SELECT 'SRID=4267;POINT(-123 34)'::geography;
                    geography
----------------------------------------------------
 0101000020AB1000000000000000C05EC00000000000004140
-- NAD83 UTM zone meters - gives an error since it is a meter-based planar projection
SELECT 'SRID=26910;POINT(-123 34)'::geography;

ERROR:  Only lon/lat coordinate systems are supported in geography.

查询和测量函数使用米为单位。因此,距离参数应以米为单位表示,并且返回值应以米为单位(或对于面积,以平方米为单位)。

-- A distance query using a 1000km tolerance
SELECT name FROM global_points WHERE ST_DWithin(location, 'SRID=4326;POINT(-110 29)'::geography, 1000000);

您可以通过计算一架飞机从西雅图到伦敦(LINESTRING(-122.33 47.606, 0.0 51.5)) 沿着大圆航线飞行的距离来了解地理的强大功能,看看它与雷克雅未克(POINT(-21.96 64.15)) 的距离有多近(绘制航线图)。

地理类型计算出雷克雅未克与西雅图和伦敦之间的大圆飞行路径之间的球体上真正的最短距离,为 122.235 公里。

-- Distance calculation using GEOGRAPHY
SELECT ST_Distance('LINESTRING(-122.33 47.606, 0.0 51.5)'::geography, 'POINT(-21.96 64.15)'::geography);
   st_distance
-----------------
 122235.23815667

几何类型计算出雷克雅未克与世界平面地图上从西雅图到伦敦的直线路径之间的无意义的笛卡尔距离。结果的名义单位为“度”,但结果与点之间的任何真实角度差都不对应,因此即使称它们为“度”也是不准确的。

-- Distance calculation using GEOMETRY
SELECT ST_Distance('LINESTRING(-122.33 47.606, 0.0 51.5)'::geometry, 'POINT(-21.96 64.15)'::geometry);
      st_distance
--------------------
 13.342271221453624

4.3.3. 何时使用地理数据类型

地理数据类型允许您以经度/纬度坐标存储数据,但代价是:在 GEOGRAPHY 上定义的函数比 GEOMETRY 上的函数少;定义的那些函数执行起来需要更多 CPU 时间。

您选择的的数据类型应由您正在构建的应用程序的预期工作区域决定。您的数据将跨越全球或大洲区域,还是仅限于州、县或市级?

  • 如果您的数据包含在较小区域中,您可能会发现选择适当的投影并使用 GEOMETRY 是最佳解决方案,就性能和可用功能而言。

  • 如果您的数据是全球性的或涵盖大陆区域,您可能会发现 GEOGRAPHY 允许您构建系统,而无需担心投影细节。您以经度/纬度存储数据,并使用在 GEOGRAPHY 上定义的函数。

  • 如果您不了解投影,并且不想了解它们,并且您准备接受 GEOGRAPHY 中可用功能的限制,那么对您来说使用 GEOGRAPHY 可能比使用 GEOMETRY 更容易。只需将您的数据加载为经度/纬度,然后从那里开始。

请参阅 第 12.11 节,“PostGIS 函数支持矩阵” 以比较地理与几何支持的内容。有关地理函数的简要列表和说明,请参阅 第 12.4 节,“PostGIS 地理支持函数”

4.3.4. 地理高级常见问题解答

4.3.4.1. 您是在球体还是球面上进行计算?
4.3.4.2. 日期线和极点呢?
4.3.4.3. 您可以处理的最长弧线是多少?
4.3.4.4. 为什么计算欧洲/俄罗斯/此处插入大型地理区域的面积如此慢?

4.3.4.1.

您是在球体还是球面上进行计算?

默认情况下,所有距离和面积计算都在球面上进行。您会发现局部区域的计算结果与良好的局部投影中的局部平面结果相符。在较大区域上,球面计算将比在投影平面上进行的任何计算更准确。

所有地理函数都可以选择使用球面计算,方法是将最终布尔参数设置为“FALSE”。这将在一定程度上加快计算速度,特别是对于几何形状非常简单的情况。

4.3.4.2.

日期线和极点呢?

所有计算都没有日期线或极点的概念,坐标是球形的(经度/纬度),因此从计算的角度来看,跨越日期线的形状与任何其他形状没有区别。

4.3.4.3.

你可以处理的最长弧线是什么?

我们使用大圆弧作为两点之间的“插值线”。这意味着实际上任何两点都以两种方式连接起来,具体取决于你沿大圆行进的方向。我们的所有代码都假设这些点通过大圆上的*较短*路径连接起来。因此,弧度超过 180 度的形状将无法正确建模。

4.3.4.4.

为什么计算欧洲/俄罗斯/在此插入大型地理区域的面积如此慢?

因为多边形非常巨大!大面积有两个缺点:它们的边界很大,因此无论你运行什么查询,索引都倾向于提取要素;顶点数目很大,并且测试(距离、包含)必须至少遍历一次顶点列表,有时还要遍历 N 次(其中 N 是其他候选要素中的顶点数)。

与 GEOMETRY 一样,我们建议当你拥有非常大的多边形,但在小区域中执行查询时,“反规范化”你的几何数据,将其划分为较小的块,以便索引可以有效地对对象的各个部分进行子查询,并且查询不必每次都提取整个对象。请查阅 ST_Subdivide 函数文档。仅仅因为你可以将整个欧洲存储在一个多边形中,并不意味着你*应该*这样做。

4.4. 几何验证

PostGIS 符合开放地理空间联盟 (OGC) 的简单要素规范。该标准定义了几何为 简单有效 的概念。这些定义允许简单要素几何模型以一致且明确的方式表示空间对象,从而支持高效计算。(注意:OGC SF 和 SQL/MM 对简单和有效有相同的定义。)

4.4.1. 简单几何

简单 几何是指没有异常几何点的几何,例如自相交或自相切。

POINT 作为 0 维几何对象本质上是 简单 的。

MULTIPOINT 如果没有两个坐标(POINT)相等(具有相同的坐标值),则为 简单 的。

LINESTRING 如果不经过同一点两次(端点除外),则为 简单 的。如果简单 LineString 的端点相同,则称其为 闭合,并称为线形环。

(a)(c) 是简单的 LINESTRING(b)(d) 不是简单的。 (c) 是一个闭合线形环。

(a)

(b)

(c)

(d)

一个 MULTILINESTRING 只有在它的所有元素都是简单的,并且任意两个元素之间的唯一交点出现在两个元素的边界上的时候才是 简单的

(e)(f) 是简单的 MULTILINESTRING(g) 不是简单的。

(e)

(f)

(g)

POLYGON 由线形环组成,所以有效的多边形几何始终是 简单的

要测试一个几何是否简单,请使用 ST_IsSimple 函数

SELECT
   ST_IsSimple('LINESTRING(0 0, 100 100)') AS straight,
   ST_IsSimple('LINESTRING(0 0, 100 100, 100 0, 0 100)') AS crossing;

 straight | crossing
----------+----------
 t        | f

通常,PostGIS 函数不要求几何参数是简单的。简单性主要用作定义几何有效性的基础。它也是某些类型的空间数据模型的要求(例如,线性网络通常不允许交叉的线)。可以使用 ST_UnaryUnion 使多点和线形几何变得简单。

4.4.2. 有效几何

几何有效性主要适用于 2 维几何(POLYGONMULTIPOLYGON)。有效性由允许多边形几何明确建模平面区域的规则定义。

如果一个 POLYGON 满足以下条件,则它是 有效的

  1. 多边形边界环(外部壳环和内部空洞环)是 简单的(不交叉或自相交)。因此,多边形不能有切割线、尖刺或环路。这意味着多边形空洞必须表示为内部环,而不是外部环自相交(所谓的“反向空洞”)。

  2. 边界环不交叉

  3. 边界环可以在点处相交,但只能相切(即不在一条线上)

  4. 内部环包含在外部环中

  5. 多边形内部是简单连接的(即环不能以将多边形分割成多个部分的方式相交)

(h)(i) 是有效的 POLYGON(j-m) 是无效的。 (j) 可以表示为有效的 MULTIPOLYGON

(h)

(i)

(j)

(k)

(l)

(m)

如果一个 MULTIPOLYGON 满足以下条件,则它是 有效的

  1. 它的元素 POLYGON 是有效的

  2. 元素不重叠(即它们的内部不能相交)

  3. 元素仅在点处相切(即不在线上)

(n) 是有效的 MULTIPOLYGON(o)(p) 无效。

(n)

(o)

(p)

这些规则意味着有效的多边形几何也是 简单的

对于线性几何,唯一的有效性规则是 LINESTRING 必须至少有两个点且长度不为零(或等效地,至少有两个不同的点)。请注意,非简单的(自相交的)线段是有效的。

SELECT
   ST_IsValid('LINESTRING(0 0, 1 1)') AS len_nonzero,
   ST_IsValid('LINESTRING(0 0, 0 0, 0 0)') AS len_zero,
   ST_IsValid('LINESTRING(10 10, 150 150, 180 50, 20 130)') AS self_int;

 len_nonzero | len_zero | self_int
-------------+----------+----------
 t           | f        | t

POINTMULTIPOINT 几何没有有效性规则。

4.4.3. 管理有效性

PostGIS 允许创建和存储有效和无效的几何。这允许检测和标记或修复无效几何。还有一些情况下,OGC 有效性规则比所需的更严格(例如零长度线段和带有反转孔的多边形)。

PostGIS 提供的许多函数都依赖于几何参数有效的假设。例如,计算多边形面积(其孔定义在多边形外部)或从非简单边界线构造多边形是没有意义的。假设几何输入有效允许函数更有效地运行,因为它们不需要检查拓扑正确性。(值得注意的例外是零长度线段和带有反转的多边形通常可以正确处理。)此外,如果输入有效,大多数 PostGIS 函数会生成有效的几何输出。这允许 PostGIS 函数安全地链接在一起。

如果您在调用 PostGIS 函数时遇到意外的错误消息(例如“GEOS Intersection() 抛出一个错误!”),您应该首先确认函数参数有效。如果不是,请考虑使用以下技术之一来确保您正在处理的数据有效。

[Note]

如果函数报告有效输入的错误,那么您可能在 PostGIS 或其使用的库之一中发现了错误,您应该向 PostGIS 项目报告此错误。如果 PostGIS 函数为有效输入返回无效几何,也是如此。

要测试几何是否有效,请使用 ST_IsValid 函数

SELECT ST_IsValid('POLYGON ((20 180, 180 180, 180 20, 20 20, 20 180))');
-----------------
 t

有关几何无效的性质和位置的信息由 ST_IsValidDetail 函数提供

SELECT valid, reason, ST_AsText(location) AS location
    FROM ST_IsValidDetail('POLYGON ((20 20, 120 190, 50 190, 170 50, 20 20))') AS t;

 valid |      reason       |                  location
-------+-------------------+---------------------------------------------
 f     | Self-intersection | POINT(91.51162790697674 141.56976744186045)

在某些情况下,需要自动更正无效几何。使用 ST_MakeValid 函数来执行此操作。(ST_MakeValid 是空间函数的一个案例,允许 无效输入!)

默认情况下,PostGIS 在加载几何时不会检查有效性,因为对于复杂几何,有效性测试可能需要大量 CPU 时间。如果您不信任您的数据源,可以通过添加检查约束在表上强制执行有效性检查

ALTER TABLE mytable
  ADD CONSTRAINT geometry_valid_check
	CHECK (ST_IsValid(geom));

4.5. 空间参考系统

空间参考系统(SRS)(也称为坐标参考系统 (CRS))定义了几何如何参考地球表面的位置。有三种类型的 SRS

  • 大地 SRS 使用直接映射到地球表面的角度坐标(经度和纬度)。

  • 投影 SRS 使用数学投影变换将球形地球的表面“压平”到平面上。它以允许直接测量距离、面积和角度等量的方式分配位置坐标。坐标系是笛卡尔坐标系,这意味着它有一个定义的原点和两个垂直轴(通常朝向北和东)。每个投影 SRS 都使用一个指定的长度单位(通常是米或英尺)。投影 SRS 的适用区域可能会受到限制,以避免失真并符合定义的坐标边界。

  • 局部 SRS 是一个笛卡尔坐标系,不参考地球表面。在 PostGIS 中,这由 SRID 值 0 指定。

有许多不同的空间参考系统正在使用。常见的 SRS 在欧洲石油勘测组 EPSG 数据库 中标准化。为了方便,PostGIS(和许多其他空间系统)使用称为 SRID 的整数标识符来引用 SRS 定义。

几何通过其 SRID 值与空间参考系统相关联,可通过 ST_SRID 访问该值。可使用 ST_SetSRID 分配几何的 SRID。一些几何构造函数允许提供 SRID(例如 ST_PointST_MakeEnvelope)。EWKT 格式支持带有 SRID=n; 前缀的 SRID。

处理几何对的空间函数(例如 叠加关系 函数)要求输入几何位于同一空间参考系统中(具有相同的 SRID)。可以使用 ST_TransformST_TransformPipeline 将几何数据转换为不同的空间参考系统。函数返回的几何与输入几何具有相同的 SRS。

4.5.1. SPATIAL_REF_SYS 表

PostGIS 使用的 SPATIAL_REF_SYS 表是符合 OGC 标准的数据库表,用于定义可用的空间参考系统。它保存了坐标系的数字 SRID 和文本描述。

spatial_ref_sys 表定义为

CREATE TABLE spatial_ref_sys (
  srid       INTEGER NOT NULL PRIMARY KEY,
  auth_name  VARCHAR(256),
  auth_srid  INTEGER,
  srtext     VARCHAR(2048),
  proj4text  VARCHAR(2048)
)

列为

srid

唯一标识数据库中 空间参考系统 (SRS) 的整数代码。

auth_name

为该参考系统引用的标准或标准机构的名称。例如,“EPSG”是有效的 auth_name

auth_srid

auth_name 中引用的机构定义的空间参考系统的 ID。对于 EPSG,这是 EPSG 代码。

srtext

空间参考系统的众所周知文本表示形式。WKT SRS 表示形式的一个示例是

PROJCS["NAD83 / UTM Zone 10N",
  GEOGCS["NAD83",
	DATUM["North_American_Datum_1983",
	  SPHEROID["GRS 1980",6378137,298.257222101]
	],
	PRIMEM["Greenwich",0],
	UNIT["degree",0.0174532925199433]
  ],
  PROJECTION["Transverse_Mercator"],
  PARAMETER["latitude_of_origin",0],
  PARAMETER["central_meridian",-123],
  PARAMETER["scale_factor",0.9996],
  PARAMETER["false_easting",500000],
  PARAMETER["false_northing",0],
  UNIT["metre",1]
]

有关 SRS WKT 的讨论,请参阅 OGC 标准 坐标参考系统的众所周知文本表示形式

proj4text

PostGIS 使用 PROJ 库提供坐标转换功能。proj4text 列包含特定 SRID 的 PROJ 坐标定义字符串。例如

+proj=utm +zone=10 +ellps=clrk66 +datum=NAD27 +units=m

有关更多信息,请参阅 PROJ 网站spatial_ref_sys.sql 文件包含所有 EPSG 投影的 srtextproj4text 定义。

在检索用于转换的空间参考系统定义时,PostGIS 使用以下策略

  • 如果 auth_nameauth_srid 存在(非 NULL),则使用基于这些项的 PROJ SRS(如果存在)。

  • 如果 srtext 存在,则尽可能使用它创建 SRS。

  • 如果 proj4text 存在,则尽可能使用它创建 SRS。

4.5.2. 用户定义的空间参考系统

PostGIS spatial_ref_sys 表包含 PROJ 投影库处理的最常见的 3000 多个空间参考系统定义。但它不包含许多坐标系。如果您拥有有关空间参考系统所需的信息,则可以将 SRS 定义添加到表中。或者,如果您熟悉 PROJ 构造,则可以定义自己的自定义空间参考系统。请记住,大多数空间参考系统都是区域性的,在超出其预期范围时使用时没有任何意义。

用于查找核心集中未定义的空间参考系统的资源是 http://spatialreference.org/

一些常用的空间参考系统包括:4326 - WGS 84 长纬度4269 - NAD 83 长纬度3395 - WGS 84 世界墨卡托2163 - 美国国家地图等面积 以及 60 个 WGS84 UTM 区域。UTM 区域是最理想的测量区域之一,但仅覆盖 6 度区域。(要确定为您的感兴趣区域使用哪个 UTM 区域,请参阅 utmzone PostGIS plpgsql 辅助函数。)

美国各州使用州平面空间参考系统(基于米或英尺)——每个州通常存在一个或两个。大多数基于米的系统都在核心集中,但许多基于英尺的系统或 ESRI 创建的系统需要从 spatialreference.org 复制。

您甚至可以定义非基于地球的坐标系,例如 火星 2000。此火星坐标系是非平面的(它以球面度为单位),但您可以将它与 geography 类型一起使用,以获取以米而不是度为单位的长度和邻近度量。

以下是使用未分配的 SRID 和 US 中心 Lambert 等角投影的 PROJ 定义加载自定义坐标系的一个示例

INSERT INTO spatial_ref_sys (srid, proj4text)
VALUES ( 990000,
  '+proj=lcc  +lon_0=-95 +lat_0=25 +lat_1=25 +lat_2=25 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs'
);

4.6. 空间表

4.6.1. 创建空间表

可以使用带有 geometry 类型列的 CREATE TABLE SQL 语句创建表来存储几何数据。以下示例创建了一个表,其中包含一个几何列,该列以 BC-Albers 坐标系(SRID 3005)存储 2D (XY) 线串

CREATE TABLE roads (
    id SERIAL PRIMARY KEY,
    name VARCHAR(64),
    geom geometry(LINESTRING,3005)
  );

geometry 类型支持两个可选的 类型修饰符

  • 空间类型修饰符 限制了列中允许的形状和维度类型。该值可以是任何受支持的 几何子类型(例如 POINT、LINESTRING、POLYGON、MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION 等)。该修饰符通过添加后缀来支持坐标维度限制:Z、M 和 ZM。例如,修饰符 'LINESTRINGM' 仅允许具有三个维度的线串,并将第三个维度视为度量。同样,'POINTZM' 要求四维 (XYZM) 数据。

  • SRID 修饰符空间参考系统 SRID 限制为特定数字。如果省略,SRID 将默认为 0。

创建带有几何列的表的示例

  • 创建包含任何类型几何且具有默认 SRID 的表

    CREATE TABLE geoms(gid serial PRIMARY KEY, geom geometry );
  • 创建带有 2D POINT 几何且具有默认 SRID 的表

    CREATE TABLE pts(gid serial PRIMARY KEY, geom geometry(POINT) );
  • 创建带有 3D (XYZ) POINT 且具有明确 SRID 3005 的表

    CREATE TABLE pts(gid serial PRIMARY KEY, geom geometry(POINTZ,3005) );
  • 创建带有 4D (XYZM) LINESTRING 几何且具有默认 SRID 的表

    CREATE TABLE lines(gid serial PRIMARY KEY, geom geometry(LINESTRINGZM) );
  • 创建带有 2D POLYGON 几何且具有 SRID 4267(NAD 1927 长纬度)的表

    CREATE TABLE polys(gid serial PRIMARY KEY, geom geometry(POLYGON,4267) );

表中可以有多个几何列。可以在创建表时指定这一点,也可以使用 ALTER TABLE SQL 语句添加列。此示例添加了一个可以容纳 3D 线串的列

ALTER TABLE roads ADD COLUMN geom2 geometry(LINESTRINGZ,4326);

4.6.2. GEOMETRY_COLUMNS 视图

OGC SQL 的简单要素规范 定义了 GEOMETRY_COLUMNS 元数据表来描述几何表结构。在 PostGIS 中,geometry_columns 是一个从数据库系统目录表读取的视图。这确保了空间元数据信息始终与当前定义的表和视图保持一致。视图结构是

\d geometry_columns
             View "public.geometry_columns"
      Column       |          Type          | Modifiers
-------------------+------------------------+-----------
 f_table_catalog   | character varying(256) |
 f_table_schema    | character varying(256) |
 f_table_name      | character varying(256) |
 f_geometry_column | character varying(256) |
 coord_dimension   | integer                |
 srid              | integer                |
 type              | character varying(30)  |

列为

f_table_catalog、f_table_schema、f_table_name

包含几何列的要素表的完全限定名称。PostgreSQL 没有“目录”的类似概念,因此该列留空。对于“架构”,使用 PostgreSQL 架构名称(public 为默认值)。

f_geometry_column

要素表中几何列的名称。

coord_dimension

列的坐标维度(2、3 或 4)。

srid

此表中坐标几何所用空间参考系统 ID。它是对 spatial_ref_sys 表的外键引用(请参阅 第 4.5.1 节,“SPATIAL_REF_SYS 表”)。

type

空间对象的类型。要将空间列限制为单一类型,请使用以下类型之一:POINT、LINESTRING、POLYGON、MULTIPOINT、MULTILINESTRING、MULTIPOLYGON、GEOMETRYCOLLECTION 或相应的 XYM 版本 POINTM、LINESTRINGM、POLYGONM、MULTIPOINTM、MULTILINESTRINGM、MULTIPOLYGONM、GEOMETRYCOLLECTIONM。对于异构(混合类型)集合,可以使用“GEOMETRY”作为类型。

4.6.3. 手动注册几何列

可能需要此操作的两种情况是 SQL 视图和批量插入。对于批量插入情况,可以通过约束列或执行 ALTER TABLE 来更正 geometry_columns 表中的注册信息。对于视图,可以使用 CAST 操作公开。请注意,如果列基于 typmod,则创建过程会正确注册它,因此无需执行任何操作。此外,未对几何应用空间函数的视图将与基础表几何列注册相同。

-- Lets say you have a view created like this
CREATE VIEW public.vwmytablemercator AS
	SELECT gid, ST_Transform(geom, 3395) As geom, f_name
	FROM public.mytable;

-- For it to register correctly
-- You need to cast the geometry
--
DROP VIEW public.vwmytablemercator;
CREATE VIEW  public.vwmytablemercator AS
	SELECT gid, ST_Transform(geom, 3395)::geometry(Geometry, 3395) As geom, f_name
	FROM public.mytable;

-- If you know the geometry type for sure is a 2D POLYGON then you could do
DROP VIEW public.vwmytablemercator;
CREATE VIEW  public.vwmytablemercator AS
	SELECT gid, ST_Transform(geom,3395)::geometry(Polygon, 3395) As geom, f_name
	FROM public.mytable;
--Lets say you created a derivative table by doing a bulk insert
SELECT poi.gid, poi.geom, citybounds.city_name
INTO myschema.my_special_pois
FROM poi INNER JOIN citybounds ON ST_Intersects(citybounds.geom, poi.geom);

-- Create 2D index on new table
CREATE INDEX idx_myschema_myspecialpois_geom_gist
  ON myschema.my_special_pois USING gist(geom);

-- If your points are 3D points or 3M points,
-- then you might want to create an nd index instead of a 2D index
CREATE INDEX my_special_pois_geom_gist_nd
	ON my_special_pois USING gist(geom gist_geometry_ops_nd);

-- To manually register this new table's geometry column in geometry_columns.
-- Note it will also change the underlying structure of the table to
-- to make the column typmod based.
SELECT populate_geometry_columns('myschema.my_special_pois'::regclass);

-- If you are using PostGIS 2.0 and for whatever reason, you
-- you need the constraint based definition behavior
-- (such as case of inherited tables where all children do not have the same type and srid)
-- set optional use_typmod argument to false
SELECT populate_geometry_columns('myschema.my_special_pois'::regclass, false); 

虽然基于旧约束的方法仍然受支持,但直接在视图中使用的基于约束的几何列将无法在 geometry_columns 中正确注册,而基于 typmod 的几何列则可以。在此示例中,我们使用 typmod 定义一列,并使用约束定义另一列。

CREATE TABLE pois_ny(gid SERIAL PRIMARY KEY, poi_name text, cat text, geom geometry(POINT,4326));
SELECT AddGeometryColumn('pois_ny', 'geom_2160', 2160, 'POINT', 2, false);

如果我们在 psql 中运行

\d pois_ny;

我们观察到它们被定义为不同的类型——一个是 typmod,一个是约束

                                  Table "public.pois_ny"
  Column   |         Type          |                       Modifiers

-----------+-----------------------+------------------------------------------------------
 gid       | integer               | not null default nextval('pois_ny_gid_seq'::regclass)
 poi_name  | text                  |
 cat       | character varying(20) |
 geom      | geometry(Point,4326)  |
 geom_2160 | geometry              |
Indexes:
    "pois_ny_pkey" PRIMARY KEY, btree (gid)
Check constraints:
    "enforce_dims_geom_2160" CHECK (st_ndims(geom_2160) = 2)
    "enforce_geotype_geom_2160" CHECK (geometrytype(geom_2160) = 'POINT'::text
        OR geom_2160 IS NULL)
    "enforce_srid_geom_2160" CHECK (st_srid(geom_2160) = 2160)

在 geometry_columns 中,它们都正确注册

SELECT f_table_name, f_geometry_column, srid, type
	FROM geometry_columns
	WHERE f_table_name = 'pois_ny';
f_table_name | f_geometry_column | srid | type
-------------+-------------------+------+-------
pois_ny      | geom              | 4326 | POINT
pois_ny      | geom_2160         | 2160 | POINT

但是——如果我们要创建这样的视图

CREATE VIEW vw_pois_ny_parks AS
SELECT *
  FROM pois_ny
  WHERE cat='park';

SELECT f_table_name, f_geometry_column, srid, type
	FROM geometry_columns
	WHERE f_table_name = 'vw_pois_ny_parks';

基于 typmod 的 geom 视图列注册正确,但基于约束的列注册不正确。

   f_table_name   | f_geometry_column | srid |   type
------------------+-------------------+------+----------
 vw_pois_ny_parks | geom              | 4326 | POINT
 vw_pois_ny_parks | geom_2160         |    0 | GEOMETRY

这可能会在 PostGIS 的未来版本中发生变化,但现在要强制基于约束的视图列正确注册,您需要执行此操作

DROP VIEW vw_pois_ny_parks;
CREATE VIEW vw_pois_ny_parks AS
SELECT gid, poi_name, cat,
  geom,
  geom_2160::geometry(POINT,2160) As geom_2160
  FROM pois_ny
  WHERE cat = 'park';
SELECT f_table_name, f_geometry_column, srid, type
	FROM geometry_columns
	WHERE f_table_name = 'vw_pois_ny_parks';
   f_table_name   | f_geometry_column | srid | type
------------------+-------------------+------+-------
 vw_pois_ny_parks | geom              | 4326 | POINT
 vw_pois_ny_parks | geom_2160         | 2160 | POINT

4.7. 加载空间数据

创建空间表后,即可将空间数据上传到数据库。有两种内置方法可以将空间数据导入 PostGIS/PostgreSQL 数据库:使用格式化的 SQL 语句或使用 Shapefile 加载器。

4.7.1. 使用 SQL 加载数据

如果空间数据可以转换为文本表示(作为 WKT 或 WKB),那么使用 SQL 可能是将数据导入 PostGIS 的最简单方法。可以使用 psql SQL 实用程序加载包含 SQL INSERT 语句的文本文件,将数据批量加载到 PostGIS/PostgreSQL 中。

SQL 加载文件(例如 roads.sql)可能如下所示

BEGIN;
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (1,'LINESTRING(191232 243118,191108 243242)','Jeff Rd');
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (2,'LINESTRING(189141 244158,189265 244817)','Geordie Rd');
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (3,'LINESTRING(192783 228138,192612 229814)','Paul St');
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (4,'LINESTRING(189412 252431,189631 259122)','Graeme Ave');
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (5,'LINESTRING(190131 224148,190871 228134)','Phil Tce');
INSERT INTO roads (road_id, roads_geom, road_name)
  VALUES (6,'LINESTRING(198231 263418,198213 268322)','Dave Cres');
COMMIT;

可以使用 psql 将 SQL 文件加载到 PostgreSQL 中

psql -d [database] -f roads.sql

4.7.2. 使用 Shapefile 加载器

数据加载器 shp2pgsql 将 Shapefile 转换为适合插入到 PostGIS/PostgreSQL 数据库(以几何或地理格式)的 SQL。加载器具有通过命令行标志选择的几种操作模式。

还有一个 shp2pgsql-gui 图形界面,其中包含与命令行加载器相同的大多数选项。对于一次性非脚本加载或您不熟悉 PostGIS 的情况,这可能更容易使用。它还可以配置为 PgAdminIII 的插件。

(c|a|d|p) 这些是互斥选项

-c

创建一个新表,并从 Shapefile 中填充数据。这是默认模式。

-a

将 Shapefile 中的数据追加到数据库表中。请注意,要使用此选项加载多个文件,文件必须具有相同的属性和相同的数据类型。

-d

在使用 Shapefile 中的数据创建新表之前,先删除数据库表。

-p

仅生成表创建 SQL 代码,而不添加任何实际数据。如果您需要完全分离表创建和数据加载步骤,可以使用此选项。

-?

显示帮助屏幕。

-D

对输出数据使用 PostgreSQL “转储”格式。这可以与 -a、-c 和 -d 结合使用。加载速度比默认的“插入”SQL 格式快得多。对于非常大的数据集,请使用此选项。

-s [<FROM_SRID>:]<SRID>

使用指定 SRID 创建和填充几何表。还可以选择指定输入 shapefile 使用给定的 FROM_SRID,在这种情况下,几何将重新投影到目标 SRID。

-k

保留标识符的大小写(列、架构和属性)。请注意,Shapefile 中的属性全部为大写。

-i

强制将所有整数转换为标准 32 位整数,即使 DBF 头签名似乎需要这样做,也不要创建 64 位 bigint。

-I

在几何列上创建 GiST 索引。

-m

-m a_file_name 指定一个文件,其中包含一组(长)列名到 10 个字符 DBF 列名的映射。该文件的内容是一行或多行,每行包含两个名称,由空格分隔,且没有前导或尾随空格。例如

COLUMNNAME DBFFIELD1
AVERYLONGCOLUMNNAME DBFFIELD2

-S

生成简单几何,而不是 MULTI 几何。仅当所有几何实际上都是单个几何时才会成功(即具有单个壳的 MULTIPOLYGON,或具有单个顶点的 MULTIPOINT)。

-t <dimensionality>

强制输出几何具有指定的维度。使用以下字符串表示维度:2D、3DZ、3DM、4D。

如果输入的维度少于指定的维度,则输出将用零填充这些维度。如果输入的维度多于指定的维度,则将去除不需要的维度。

-w

输出 WKT 格式,而不是 WKB。请注意,由于精度损失,这可能会导致坐标漂移。

-e

在不使用事务的情况下单独执行每个语句。当存在一些生成错误的不良几何时,这允许加载大部分正确的数据。请注意,这不能与 -D 标志一起使用,因为“转储”格式始终使用事务。

-W <encoding>

指定输入数据(dbf 文件)的编码。使用时,dbf 的所有属性都从指定的编码转换为 UTF8。生成的 SQL 输出将包含一个 SET CLIENT_ENCODING to UTF8 命令,以便后端能够从 UTF8 重新转换为数据库内部配置为使用的任何编码。

-N <policy>

NULL 几何处理策略(insert*、skip、abort)

-n

-n 仅导入 DBF 文件。如果你的数据没有相应的 shapefile,它将自动切换到此模式,仅加载 dbf。因此,只有在你拥有完整的 shapefile 集,并且你只需要属性数据而不需要几何时,才需要设置此标志。

-G

在 WGS84 长纬度(SRID=4326)中使用地理类型而不是几何(需要经度/纬度数据)

-T <表空间>

指定新表的表空间。除非同时使用 -X 参数,否则索引仍将使用默认表空间。PostgreSQL 文档对何时使用自定义表空间进行了很好的描述。

-X <表空间>

指定新表索引的表空间。这适用于主键索引,以及如果同时使用 -I,则适用于 GIST 空间索引。

-Z

使用此标志时,将阻止生成 ANALYZE 语句。如果不使用 -Z 标志(默认行为),则将生成 ANALYZE 语句。

使用加载器创建输入文件并加载它的示例会话可能如下所示

# shp2pgsql -c -D -s 4269 -i -I shaperoads.shp myschema.roadstable > roads.sql
# psql -d roadsdb -f roads.sql

可以使用 UNIX 管道一步完成转换和加载

# shp2pgsql shaperoads.shp myschema.roadstable | psql -d roadsdb

4.8. 提取空间数据

可以使用 SQL 或 Shapefile 转储器从数据库中提取空间数据。SQL 部分介绍了一些可用于对空间表进行比较和查询的函数。

4.8.1. 使用 SQL 提取数据

从数据库中提取空间数据最直接的方法是使用 SQL SELECT 查询来定义要提取的数据集,并将结果列转储到可解析的文本文件中

db=# SELECT road_id, ST_AsText(road_geom) AS geom, road_name FROM roads;

road_id | geom                                    | road_name
--------+-----------------------------------------+-----------
	  1 | LINESTRING(191232 243118,191108 243242) | Jeff Rd
	  2 | LINESTRING(189141 244158,189265 244817) | Geordie Rd
	  3 | LINESTRING(192783 228138,192612 229814) | Paul St
	  4 | LINESTRING(189412 252431,189631 259122) | Graeme Ave
	  5 | LINESTRING(190131 224148,190871 228134) | Phil Tce
	  6 | LINESTRING(198231 263418,198213 268322) | Dave Cres
	  7 | LINESTRING(218421 284121,224123 241231) | Chris Way
(6 rows)

有时需要某种限制来减少返回的记录数。对于基于属性的限制,使用与非空间表相同的 SQL 语法。对于空间限制,以下函数很有用

ST_Intersects

此函数说明两个几何是否共享任何空间。

=

这测试两个几何是否在几何上相同。例如,如果 'POLYGON((0 0,1 1,1 0,0 0))' 与 'POLYGON((0 0,1 1,1 0,0 0))' 相同(它相同)。

接下来,你可以在查询中使用这些运算符。请注意,在 SQL 命令行中指定几何和框时,你必须将字符串表示显式转换为几何函数。312 是与我们的数据匹配的虚构空间参考系统。因此,例如

SELECT road_id, road_name
  FROM roads
  WHERE roads_geom='SRID=312;LINESTRING(191232 243118,191108 243242)'::geometry;

上述查询将返回 "ROADS_GEOM" 表中几何等于该值的一个记录。

检查某些道路是否通过多边形定义的区域

SELECT road_id, road_name
FROM roads
WHERE ST_Intersects(roads_geom, 'SRID=312;POLYGON((...))');

最常见的空间查询可能是“基于框架”的查询,客户端软件(如数据浏览器和 Web 制图工具)使用该查询来获取“地图框架”的数据以供显示。

使用“&&”运算符时,您可以指定 BOX3D 作为比较要素或 GEOMETRY。但是,当您指定 GEOMETRY 时,其边界框将用于比较。

使用“BOX3D”对象作为框架,此类查询看起来像这样

SELECT ST_AsText(roads_geom) AS geom
FROM roads
WHERE
  roads_geom && ST_MakeEnvelope(191232, 243117,191232, 243119,312);

请注意使用 SRID 312 来指定包络的投影。

4.8.2. 使用 Shapefile 转储器

pgsql2shp 表转储器连接到数据库并将表(可能由查询定义)转换为 shape 文件。基本语法为

pgsql2shp [<options>] <database> [<schema>.]<table>
pgsql2shp [<options>] <database> <query>

命令行选项为

-f <filename>

将输出写入特定文件名。

-h <host>

要连接的数据库主机。

-p <port>

在数据库主机上要连接的端口。

-P <password>

连接到数据库时要使用的密码。

-u <user>

连接到数据库时要使用的用户名。

-g <geometry column>

对于具有多个几何列的表,在编写 shape 文件时要使用的几何列。

-b

使用二进制光标。这将使操作更快,但如果表中任何非几何属性缺少转换为文本的强制转换,则此方法将不起作用。

-r

原始模式。不要删除 gid 字段,也不要转义列名。

-m filename

将标识符重新映射为十个字符的名称。文件内容是两行符号,用一个空格分隔,没有尾随或前导空格:VERYLONGSYMBOL SHORTONE ANOTHERVERYLONGSYMBOL SHORTER 等。

4.9. 空间索引

空间索引使针对大型数据集使用空间数据库成为可能。如果没有索引,则搜索要素需要对数据库中的每条记录进行顺序扫描。索引通过将数据组织成一个结构来加快搜索速度,该结构可以快速遍历以查找匹配的记录。

通常用于属性数据的 B 树索引方法对于空间数据并不是非常有用,因为它仅支持在单个维度中存储和查询数据。诸如几何(具有 2 个或更多维度)之类的需要支持跨所有数据维度进行范围查询的索引方法。PostgreSQL 用于空间数据处理的一个主要优势在于它提供了多种适用于多维数据的索引方法:GiST、BRIN 和 SP-GiST 索引。

  • GiST(广义搜索树) 索引将数据分解为“一侧的事物”、“重叠的事物”、“内部的事物”,并且可用于各种数据类型,包括 GIS 数据。PostGIS 使用在 GiST 之上实现的 R 树索引来索引空间数据。GiST 是最常用的通用空间索引方法,并提供非常好的查询性能。

  • BRIN(块范围索引) 索引通过汇总表记录范围的空间范围来操作。通过范围扫描进行搜索。BRIN 仅适用于某些类型的数据(空间排序,更新不频繁或不更新)。但是,它提供了更快的索引创建速度和更小的索引大小。

  • SP-GiST(空间分区广义搜索树) 是一种通用索引方法,支持分区搜索树,例如四叉树、k-d 树和基数树(trie)。

空间索引仅存储几何图形的边界框。空间查询使用索引作为主过滤器,以快速确定一组可能与查询条件匹配的几何图形。大多数空间查询都需要一个辅助过滤器,该过滤器使用空间谓词函数来测试更具体的空间条件。有关使用空间谓词进行查询的详细信息,请参阅第 5.2 节“使用空间索引”

另请参阅PostGIS 工作坊关于空间索引的部分PostgreSQL 手册

4.9.1. GiST 索引

GiST 代表“广义搜索树”,是一种用于多维数据的通用索引形式。PostGIS 使用在 GiST 之上实现的 R 树索引来索引空间数据。GiST 是最常用且最通用的空间索引方法,并提供非常好的查询性能。GiST 的其他实现用于加速对所有类型的非规则数据结构(整数数组、光谱数据等)的搜索,这些数据结构不适用于正常的 B 树索引。有关详细信息,请参阅PostgreSQL 手册

一旦空间数据表超过几千行,你将需要构建一个索引来加速数据的空间搜索(除非你的所有搜索都基于属性,在这种情况下,你将需要在属性字段上构建一个普通索引)。

在“geometry”列上构建 GiST 索引的语法如下

CREATE INDEX [indexname] ON [tablename] USING GIST ( [geometryfield] ); 

上述语法将始终构建一个 2D 索引。要获得几何类型的 n 维索引,可以使用此语法创建一个索引

CREATE INDEX [indexname] ON [tablename] USING GIST ([geometryfield] gist_geometry_ops_nd);

构建空间索引是一项计算密集型操作。它还会在创建期间阻止对表的写访问,因此在生产系统上,您可能希望以较慢的 CONCURRENTLY 感知方式进行

CREATE INDEX CONCURRENTLY [indexname] ON [tablename] USING GIST ( [geometryfield] ); 

在构建索引后,有时强制 PostgreSQL 收集表统计信息(用于优化查询计划)会有所帮助

VACUUM ANALYZE [table_name] [(column_name)];

4.9.2. BRIN 索引

BRIN 代表“块范围索引”。它是一种在 PostgreSQL 9.5 中引入的通用索引方法。BRIN 是一种有损索引方法,这意味着需要进行二次检查以确认记录与给定的搜索条件匹配(所有提供的空间索引都是这种情况)。它提供了更快的索引创建和更小的索引大小,并具有合理的读取性能。其主要目的是支持在与表内物理位置具有相关性的列上对非常大的表进行索引。除了空间索引之外,BRIN 还可以加速对各种类型的属性数据结构(整数、数组等)的搜索。有关更多信息,请参阅PostgreSQL 手册

一旦空间表超过几千行,您将需要构建索引以加快数据的空间搜索。只要 GiST 索引的大小不超过数据库可用的 RAM 量,并且只要您能够负担索引存储大小和写入时索引更新的成本,GiST 索引的性能就非常高。否则,对于非常大的表,可以考虑将 BRIN 索引作为一种替代方案。

BRIN 索引存储一个边界框,该边界框包围了一组连续的表块中包含的所有几何图形,称为块范围。在使用索引执行查询时,将扫描块范围以查找与查询范围相交的块范围。仅当数据按物理方式排序,以便块范围的边界框具有最小的重叠(理想情况下是互斥的)时,此方法才有效。生成的索引大小非常小,但通常读取性能低于对相同数据的 GiST 索引。

构建 BRIN 索引的 CPU 密集程度远低于构建 GiST 索引。通常会发现,构建 BRIN 索引的速度比构建相同数据的 GiST 索引快十倍。而且,由于 BRIN 索引仅为每个表块范围存储一个边界框,因此通常使用的磁盘空间比 GiST 索引少一千倍。

您可以选择在一个范围内总结的块数。如果您减少此数字,索引将变大,但可能会提供更好的性能。

为了使 BRIN 生效,表数据应按物理顺序存储,以最大程度地减少块范围重叠的量。数据可能已经按适当的方式排序(例如,如果它从已经按空间顺序排序的另一个数据集加载)。否则,可以通过按一维空间键对数据进行排序来实现此目的。执行此操作的一种方法是创建一个按几何值排序的新表(在最近的 PostGIS 版本中,使用高效的希尔伯特曲线排序)

CREATE TABLE table_sorted AS
   SELECT * FROM table  ORDER BY geom;

或者,可以通过使用 GeoHash 作为(临时)索引并对该索引进行聚类,就地对数据进行排序

CREATE INDEX idx_temp_geohash ON table
    USING btree (ST_GeoHash( ST_Transform( geom, 4326 ), 20));
CLUSTER table USING idx_temp_geohash;

geometry 列上构建 BRIN 索引的语法为

CREATE INDEX [indexname] ON [tablename] USING BRIN ( [geome_col] ); 

以上语法构建一个 2D 索引。要构建一个 3D 维度索引,请使用此语法

CREATE INDEX [indexname] ON [tablename]
    USING BRIN ([geome_col] brin_geometry_inclusion_ops_3d);

您还可以使用 4D 运算符类获取 4D 维度索引

CREATE INDEX [indexname] ON [tablename]
    USING BRIN ([geome_col] brin_geometry_inclusion_ops_4d);

以上命令使用一个范围内的默认块数,即 128。要指定在一个范围内总结的块数,请使用此语法

CREATE INDEX [indexname] ON [tablename]
    USING BRIN ( [geome_col] ) WITH (pages_per_range = [number]); 

请记住,BRIN 索引仅为大量行存储一个索引条目。如果您的表存储具有混合维数的几何图形,则生成的索引很可能会性能不佳。您可以通过选择具有存储几何图形维数最少的运算符类来避免此性能损失

BRIN 索引支持 geography 数据类型。在 geography 列上构建 BRIN 索引的语法为

CREATE INDEX [indexname] ON [tablename] USING BRIN ( [geog_col] ); 

上述语法为球体上的地理空间对象构建一个 2D 索引。

目前,仅提供“包含支持”,这意味着对于 2D 情况(对于 &&~@ 运算符)仅可以使用 geometrygeography,而对于 3D 几何仅可以使用 &&& 运算符。目前不支持 kNN 搜索。

BRIN 和其他索引类型之间的一个重要区别在于,数据库不会动态维护索引。对表中空间数据所做的更改将简单地附加到索引的末尾。这将导致索引搜索性能随着时间推移而下降。可以通过执行 VACUUM 或使用特殊函数 brin_summarize_new_values(regclass) 来更新索引。因此,BRIN 可能最适合与只读或很少更改的数据一起使用。有关更多信息,请参阅 手册

总结使用 BRIN 处理空间数据

  • 索引构建时间非常快,索引大小非常小。

  • 索引查询时间比 GiST 慢,但仍然可以接受。

  • 需要按空间顺序对表数据进行排序。

  • 需要手动维护索引。

  • 最适合于非常大的表,其中重叠较少或没有重叠(例如点),并且是静态的或不经常更改的。

  • 对于返回相对大量数据记录的查询更有效。

4.9.3. SP-GiST 索引

SP-GiST 代表“空间分区广义搜索树”,是一种用于多维数据类型的索引通用形式,支持分区搜索树,例如四叉树、k-d 树和基数树(trie)。这些数据结构的共同特征是它们会反复将搜索空间划分为不必相等大小的分区。除了空间索引之外,SP-GiST 还用于加速对多种数据类型的搜索,例如电话路由、ip 路由、子字符串搜索等。有关更多信息,请参阅 PostgreSQL 手册

与 GiST 索引一样,SP-GiST 索引是有损的,因为它们存储包围空间对象的边界框。SP-GiST 索引可以被视为 GiST 索引的替代方案。

一旦 GIS 数据表超过几千行,就可以使用 SP-GiST 索引来加速数据的空间搜索。在“geometry”列上构建 SP-GiST 索引的语法如下

CREATE INDEX [indexname] ON [tablename] USING SPGIST ( [geometryfield] ); 

上述语法将构建一个 2 维索引。可以使用 3D 运算符类为几何类型创建 3 维索引

CREATE INDEX [indexname] ON [tablename] USING SPGIST ([geometryfield] spgist_geometry_ops_3d);

构建空间索引是一个计算密集型操作。它还会在创建期间阻止对表的写访问,因此在生产系统上,您可能希望以较慢的 CONCURRENTLY 兼容方式进行

CREATE INDEX CONCURRENTLY [indexname] ON [tablename] USING SPGIST ( [geometryfield] ); 

在构建索引后,有时强制 PostgreSQL 收集表统计信息(用于优化查询计划)会有所帮助

VACUUM ANALYZE [table_name] [(column_name)];

SP-GiST 索引可以加速涉及以下运算符的查询

  • <<, &<, &>, >>, <<|, &<|, |&>, |>>, &&, @>, <@ 和 ~=,适用于 2 维索引,

  • 对于 3 维索引,&/&、~==、@>> 和 <<@。

目前不支持 kNN 搜索。

4.9.4. 调整索引使用

通常,索引可以无形中加快数据访问速度:一旦建立索引,PostgreSQL 查询计划程序会自动决定何时使用索引来提高查询性能。但在某些情况下,计划程序不会选择使用现有索引,因此查询最终会使用缓慢的顺序扫描,而不是空间索引。

如果您发现您的空间索引没有被使用,您可以采取一些措施

  • 检查查询计划并检查您的查询是否实际计算了您需要的东西。错误的 JOIN,无论是忘记的还是连接到错误的表,都可能意外地多次检索表记录。要获取查询计划,请在查询前面执行 EXPLAIN

  • 确保收集有关表中值的数目和分布的统计信息,以便为查询计划程序提供更好的信息,以便围绕索引使用做出决策。 VACUUM ANALYZE 将计算这两者。

    无论如何,您都应该定期清理您的数据库。许多 PostgreSQL DBA 定期在非高峰期运行 VACUUM cron 作业。

  • 如果清理没有帮助,您可以使用命令 SET ENABLE_SEQSCAN TO OFF; 暂时强制计划程序使用索引信息。这样,您可以检查计划程序是否能够为您的查询生成索引加速的查询计划。您应该仅将此命令用于调试;一般来说,计划程序比您更了解何时使用索引。在运行查询后,别忘了运行 SET ENABLE_SEQSCAN TO ON;,以便计划程序可以正常运行其他查询。

  • 如果 SET ENABLE_SEQSCAN TO OFF; 帮助您的查询更快地运行,那么您的 Postgres 可能没有针对您的硬件进行调整。如果您发现计划程序对顺序扫描与索引扫描的成本判断错误,请尝试减少 postgresql.confRANDOM_PAGE_COST 的值,或使用 SET RANDOM_PAGE_COST TO 1.1;RANDOM_PAGE_COST 的默认值为 4.0。尝试将其设置为 1.1(对于 SSD)或 2.0(对于快速磁性磁盘)。减小该值会使计划程序更有可能使用索引扫描。

  • 如果 SET ENABLE_SEQSCAN TO OFF; 对您的查询没有帮助,则该查询可能正在使用 Postgres 规划器尚无法优化的 SQL 结构。可以将查询重写为规划器能够处理的形式。例如,带有内联 SELECT 的子查询可能不会生成有效的计划,但可以使用 LATERAL JOIN 进行重写。

有关详细信息,请参阅 Postgres 手册中的 查询规划 部分。