NSDT工具推荐: Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 可编程3D场景编辑器 - REVIT导出3D模型插件 - 3D模型语义搜索引擎 - AI模型在线查看 - Three.js虚拟轴心开发包 - 3D模型在线减面 - STL模型在线切割 - 3D道路快速建模
在本文中,我们将介绍 PostGIS 的一些基础知识及其功能,以及一些可用于简化解决方案或提高性能的提示和技巧。 简而言之 - PostGIS 是一个 Postgres 扩展,增加了对存储和操作空间数据类型的支持。 当我们构建在地图上存储、操作和可视化数据的软件应用程序时,我们通常需要使用空间数据存储。 我们可以使用谷歌地图或类似应用程序作为典型地理空间可视化软件功能的一个很好的例子。
在我们使用 PostGIS 功能之前,我们需要在 Postgres 中安装扩展:
CREATE EXTENSION IF NOT EXISTS postgis;
1、空间数据类型
PostGIS 支持几种不同类型的(地理)空间数据类型。 他们中的大多数人在平面设计领域都有“表亲”。 但与对象坐标相对于屏幕或一张纸的图形软件不同,地理空间坐标参考地球表面的点。 这使得在地图上呈现此类对象成为可能,而且还可以分析它们之间的交互。 当我们开始使用空间对象和操作来解决现实世界的问题时,这将派上用场。
1.1 矢量 - Vectors
与图形设计软件类似,空间矢量数据支持基本的几何形状,如点、线串和多边形。 除了基本的几何图形,PostGIS 还支持一些更高级的几何图形:
- 多版本的基本几何图形 - 点、线串或多边形的同质集合
- 基本几何图形的 3D 版本 - 与添加了 Z 坐标的基本几何图形相同
- 几何集合——任意几何的集合,同质的或异质的
- 多面体曲面 - 复杂的 3D 曲面
地图和导航应用程序严重依赖矢量对象来模拟地图的特征。 查看下面的屏幕截图,Google 地图上的大多数对象都可以表示为多边形(例如建筑物)或点(例如企业)或线(例如道路)。 在 3D 模式下查看地图时,建筑物通常表示为多面体表面。
要使用“geometry”数据类型创建一个表,我们可以运行下面的语句:
CREATE TABLE building (
id UUID PRIMARY KEY,
geom geometry
);
这将创建一个表,其中包含几何类型的“geom”列,这是所有矢量对象的通用类型。 将其视为 OOP 世界中的基类。 这意味着我们可以在同一列中组合点、线、多边形和其他矢量对象。 如果我们事先知道我们将处理哪些几何图形,我们可以将其指定为列类型定义的一部分。 在这种情况下,PostGIS 将不允许在同一列中插入其他几何类型。 这始终是存储数据的首选方式,因为某些操作希望几何图形具有相同的类型。
CREATE TABLE building (
id UUID PRIMARY KEY,
geom geometry(Polygon)
);
此外,我们还可以在列类型定义中包含 SRID(空间参考标识符),强制所有值都符合相同的 SRID。 稍后将更详细地介绍 SRID。
CREATE TABLE building (
id UUID PRIMARY KEY,
geom geometry(Polygon,4326)
);
1.2 栅格 - Rasters
空间栅格数据类型也类似于它的图形设计表亲(JPEG、PNG、TIFF 和我们日常生活中使用的其他栅格文件),但存在一些差异。
与一个像素是屏幕或纸上的一个点的常规栅格不同,空间栅格具有定义像素宽度和高度的空间分辨率。 因此,空间栅格的每个像素都覆盖了地图上大小一致的矩形。
空间栅格有一个或多个波段,每个波段都有一个所有“像素”值的矩阵。 每个波段的数据类型是单独设置的,几乎可以是任何数字类型——二进制(对屏蔽有用)、整数或浮点值。 在某种程度上,它是我们在图形设计领域中习惯使用的 24 位 RGB 光栅的概括。 24 位 RGB 栅格的空间等效项是 3 波段栅格,其中每个波段都定义为无符号 8 位整数。 然而,由于可以灵活地存储除颜色之外的任何数值,我们可以利用栅格来存储各种信息——地形高程、人口密度、植被信息或指标等。
栅格数据支持包含在一个单独的 postgis 扩展中,在我们可以使用之前需要安装它:
CREATE EXTENSION IF NOT EXISTS postgis_raster;
然后我们可以使用栅格类型创建一个表:
CREATE TABLE satellite_image (
id UUID PRIMARY KEY,
rast raster
);
1.3 点云
点云数据格式可以看作是栅格和矢量之间的混合。 它在某种程度上类似于栅格,表示离散数据集,由单个点而不是形状组成。 然而,与光栅不同的是,它没有分辨率或密度,因此点可以位于 3D 空间中的任何位置。 将点云与矢量类型进行比较——它类似于 3D 矢量点的集合。
点云数据通常是从 LiDAR、3D 扫描仪或测量 3D 空间中物体物理特性的类似设备获得的。 可视化时,它看起来类似于下图。 树木(或任何其他物体)看起来像连续的 3D 物体,但它们都是由空间中的离散点组成的。
点云支持包含在一个单独的 postgis 扩展中,在我们可以使用栅格之前需要安装它:
CREATE EXTENSION pointcloud;
CREATE EXTENSION pointcloud_postgis;
2、空间操作
在处理“常规”非空间数据时,我们通常根据包含表示对象标识符(整数、字符串或可能是 UUID)的原始值的列中的精确值来连接和过滤表。 这是由于我们通常在关系数据库中解决的问题的性质。 对非空间数据集的典型查询可能看起来像这样:
SELECT *
FROM book b
INNER JOIN publisher p ON p.id = b.publisher_id;
或者这样:
SELECT *
FROM book b
WHERE b.publisher_id = 12345;
然而,对于空间数据,我们通常没有真实世界的用例要求我们通过相等性过滤空间对象或通过使用相等性比较器匹配空间对象来连接表。 如果我们在使用 Google 地图应用程序时想一想它是如何工作的——缩放、平移、单击对象,我们可以推断出对空间数据最常用的操作是交集。 每当我们平移或缩放地图时,系统都需要确定应从存储中获取哪些对象并在屏幕上呈现。 这通常是通过将对象与代表地图可见部分的矩形相交来完成的。 下面的查询查找与地图上给定矩形相交的建筑物:
SELECT *
FROM building b
WHERE ST_Intersects(b.geom, ST_MakeEnvelope(24, 47, 25, 48, 4326));
另一个常用的操作是距离计算,它通常用于确定哪些对象位于地图上给定点的附近。
3、空间索引
在对原始值进行索引时,数据库通常使用 Hash 或 B-Tree 来构建索引。 由于通常用于空间数据的操作有所不同,因此不能在此处应用此方法。 空间索引需要以一种允许我们从与给定空间对象相交的空间对象集合中有效地找到空间对象的方式构建。
为了解决这个问题,空间索引使用 R-Tree(“Rectangle”中的“R”)结构,它构建了一个矩形树,其中每个子节点矩形都包含在父节点矩形中。 树的叶子是代表 PostGIS 列中空间对象边界框的矩形。
这样,我们可以快速遍历树以找到哪些对象与给定对象相交,而不是检查每个对象是否相交。 这将过滤操作的时间复杂度从 O(N) 降低到 O(logN)。
创建空间索引的 SQL 命令与“常规”索引创建非常相似:
CREATE INDEX building_geom_idx ON building USING GIST(geom);
这里唯一的区别是“GIST”部分,它向 PostGis 发出信号,表明我们需要为此索引使用“通用索引结构”。 PostGIS 支持三种空间索引(GIST、SPGIST 和 BRIN),但在大多数情况下,GIST 是一个不错的选择。
值得注意的是,空间索引也可以用于栅格数据,因为我们经常需要快速找到相关栅格。 相同的语法可以应用于栅格列,但在这种情况下,我们在栅格图像周围索引边界框,因此该语句需要包含 ST_ConvexHull 函数。
CREATE INDEX satellite_image_rast_idx ON satellite_image USING GIST(ST_ConvexHull(rast));
与任何索引一样,在将对象插入数据库时存在性能折衷,因为 PostGIS 需要将新对象插入 R-Tree 索引。 但是每当我们计划使用空间操作时,我们应该考虑为查询中使用的列添加索引,因为它会显着提高性能。
4、坐标系标识 - SRID
SRID(spatial reference identifier)是我们需要给每个空间对象赋予属性的重要信息。 它包括有关坐标系的信息、(0, 0) 点在地球上的位置、坐标的分辨率以及地图上的坐标如何与地球上的实际点相对应。
最常用的 SRID 是 WGS84 — SRID 4326 用于 GPS 跟踪、谷歌地图和许多其他应用程序,但是还有更多的 SRID 很受欢迎,有些在全球某些地区提供比 WGS84 更高的精度。 所以我们总是需要知道进入系统的数据的 SRID。
PostGIS 在涉及 SRID 时非常灵活。 在上面的示例中,我们创建了一个表“building”,其中包含一个没有指定 SRID 的几何列。 这意味着 PostGIS 将允许插入具有任何 SRID 的多边形。 在我们无法预测或更改传入数据的 SRID 的情况下,这有时很有用,甚至是必要的,但应尽可能避免。
空间列也可以有一个预定义的 SRID,它强制该列中的所有对象使用指定的 SRID。
CREATE TABLE building (
id UUID PRIMARY KEY,
geom geometry(Polygon, 4326)
);
在所有对象上使用统一的 SRID 的第一个原因是空间查询需要相同 SRID 的对象,如果我们尝试与不同 SRID 的对象相交,将会失败:
SELECT ST_Intersects(
ST_MakeEnvelope(24, 47, 25, 48, 4979),
ST_MakeEnvelope(24, 47, 25, 48, 4326)
);
将提示如下错误:
ERROR: ST_Intersects: Operation on mixed SRID geometries (Polygon, 4979) != (Polygon, 4326)
这个问题有一个解决方法,但它会导致下一个缺点。 每当我们有不匹配的 SRID 时,我们可以将一个空间对象转换为另一个对象的 SRID。
SELECT ST_Intersects(
ST_Transform(ST_MakeEnvelope(24, 47, 25, 48, 4979), 4326),
ST_MakeEnvelope(24, 47, 25, 48, 4326)
);
在此查询中,ST_Transform 将所有坐标从源 SRID 转换为目标 SRID,并输出一个 SRID 为 4326 的多边形,该多边形可以与另一个多边形相交而不会出现错误。
但是,这种方法会带来性能损失。 首先,这种变换需要一些时间。 更重要的是,我们将无法使用空间索引来提高 ST_Intersects 操作的性能,因为空间索引适用于原始 SRID 中的几何图形,而不适用于目标 SRID 中的转换几何图形。 查询执行计划将需要在第一个表上执行表扫描,以确定哪些对象与第二个表中的对象相交,在转换为目标 SRID 之后。
处理此问题的一种方法是在将所有对象插入数据库时对所有对象执行 ST_Transform,并始终保持 SRID 之间的一致性。 这有很多好处,但值得注意的是,对象转换并不总是精确的,从一个 SRID 转换到另一个 SRID 时我们会损失一些精度。 如果精度对软件至关重要,那么将原始对象和转换后的对象都存储在数据库中并交替使用它们可能是个好主意。
5、结束语
本文简要介绍了 PostGIS、它是什么、它支持的一些空间数据类型和操作以及可以利用 PostGIS 解决的一些现实问题。 我们还介绍了空间索引,这是获得最佳性能的第一站。 希望它有助于攀登陡峭的学习曲线进入 GIS 世界。
BimAnt翻译整理,转载请标明出处