拓扑学表示认识到,在现实中,几何特征很少彼此独立存在。当你从飞机上俯瞰大都市时,你会看到一个由街道勾勒的街区组成的迷宫,环环相扣。通过一个简单的几何模型,你可以用线绳来表示街道,用多边形来表示街区。但是一旦你铺设了街道,你就知道你的街区会在哪里。不得不为街区创建多边形是一种多余的练习。恭喜你,你已经发现了拓扑结构。
我们将用一组例子来工作。它将是非常简单的,在不加载数据的情况下创建。这组例子将让你感受到拓扑结构是如何创建和组织的。
我们已经将ch13_staging 模式打包为本章下载的一部分(www.postgis.us/chapter_13_edition_3),并加载了表cityboundary、neighbourhoods和 streetcentrelines。ch13 模式中包含了本章的拓扑几何表。
什么是拓扑学
地球的表面是有限的。我们有大约1.969亿平方英里(5.101亿平方公里)可以玩,包括水。人类是有领土的,所以我们把所有的土地分成了大大小小的国家。除了南极洲和一些有争议的地区外,移动一个国家的边界至少要涉及两个国家。地理学的铁律规定,当一个国家获得土地时,另一个国家必须割让土地。这种零和土地游戏是人类在地球上创造的国家集体穷尽和相互排斥的结果。
这种集体详尽和相互排斥的区域划分是拓扑学的要求。有了这个前提,在创建几何学时,你就不需要重述显而易见的事情了。例如,如果你在乡下有一块土地,并决定将北半部分用于耕种,另一半用于非耕种,那么就必须有某种分界线,将两半的土地分开。通过为可耕种区域创建一个多边形,你为非可耕种区域创建了另一个多边形,并且用一条线把这两个区域分开。
请考虑另一个例子。1790年,美国国会用相邻各州割让的土地创建了华盛顿特区。该区被划分为四个象限,有两条从国会大厦辐射出来的垂直轴。这四个象限的名称很恰当。西北、东北、西南和东南。假设本着平等的精神,国会决定让所有象限的面积相同。这将意味着将轴心移到北部和西部。如果你在模型中使用拓扑结构,这种重组只不过是移动一个点而已。通过移动这个点,你的线绳(轴线)会跟着移动,形成象限的多边形会放空或膨胀。你只需移动一个点就能实现这一切!这就是拓扑学的力量。
这就是拓扑学的威力:通过定义一套关于几何图形如何相互关联的规则,你可以省去每当你做最轻微的改动时必须重新调查整个景观的麻烦。
在PostGIS中,有三种矢量数据的表示方法。一种是比较标准的几何模型,每个几何体都是一个独立的单元。在几何模型中,共享的东西,如地块的边界,在每个几何体中都是重复的。还有地理学模型,它和几何学模型一样,把每块空间作为独立的单位,边界也是重复的,但在球状空间中看待这些单位。然后是拓扑模型,它借用了几何学的二维世界观,但有一个关键区别。在拓扑模型中,共享的边界和区域在数据库中存储一次,并与共享边界的几何体链接。这些有链接边界的几何体被称为topogeoms。
这有几个好处:
- 如果你简化了一个对象的分布,被简化的边线仍然是共享的,这样你就不会在以前没有的地方出现重叠或空隙。
- 如果你有一组对象,如建筑物、街区或地块,不应该重叠,那么在拓扑模型中就更容易发现和防止这些问题。
现在你对拓扑的概念有了新的认识,我们将继续使用PostGIS的拓扑功能来构建拓扑。
使用拓扑结构
拓扑是一种与几何学完全不同的空间特征。回想一下你的第一个几何课程。在欧几里得几何学中,点、线和多边形并没有坐标系作为背景。你并不关心事物的绝对测量,而是关心它们之间的关系。拓扑模型,在某种程度上,回到了经典几何学,你描述两个自由几何体如何相互作用,而不考虑坐标系。
因为GIS拓扑学是图论的产物,它认同一套不同的术语。就所有意图和目的而言,你可以把几何学中的一个点看作是拓扑学中的一个节点,把线绳看作是一条边,把多边形看作是一个面。总的来说,节点、边和面是拓扑学的基元,用来代替几何学。
注意:我们用拓扑学这个术语来指代拓扑学研究领域,也指代拓扑网络。
安装拓扑学扩展
在创建拓扑结构之前,你必须确保你已经安装了拓扑结构扩展。如果你不确定,在你的数据库中寻找一个名为topology 的模式。这个模式包含用于创建拓扑结构的函数以及拓扑目录表。如果它不见了,说明你还没有安装这个扩展。扩展必须逐个数据库地安装。
CREATE EXTENSION postgis_topology;
作为拓扑安装的一部分,PostGIS将topology 模式添加到你的数据库的search_path 。这意味着你可以引用拓扑结构的功能,而不需要明确地预加topology 。
在某些情况下,你所登录的角色可能有自己的自定义search_path ,它将覆盖数据库的search_path 。在你继续之前,通过运行这个SQL语句验证topology 是你的search_path 的一部分:
SHOW search_path;
如果你在搜索路径中没有看到topology 模式,请断开与数据库的连接并重新连接。
一旦完成,你就可以创建一个拓扑结构。
创建一个拓扑结构
在这一节中,你将以SRID为4326的科罗拉多州为基础,创建一个风格化的拓扑结构。下面的列表显示了如何创建拓扑结构:
SELECT CreateTopology('ch13a_topology',4326);
在你执行上述SQL语句后,你会注意到一个名为ch13a_topology 的新模式。在topology.topology目录表中会出现一个新条目,注册新的拓扑结构。当你偷看ch13a_topology 模式时,你会看到四个新的表在等待数据:节点、edge_data、face和relation。
PostGIS使用一个单独的模式来存放每个拓扑网络--在这里是ch13a_topology 。所选择的SRID适用于模式中的所有表和所有将使用ch13a_topology 模式的topogeometry列。因为拓扑学是关于几何学之间的关系,拥有不同的SRID是没有意义的。
在每个拓扑结构中,你总会发现四个表:节点、边缘数据、面和关系。前三个只是拓扑学上对点、线段和多边形的说法。在这三个用于存储基元的表中,edge_data是持有构建网络的所有信息的表。当你开始从拓扑基元构建空间对象时,这些空间对象中的每一个与拓扑结构的关系都会存在于该表中。
对于科罗拉多州,您可以先使用函数TopoGeo_AddLineString 添加构成该州四个边界的线绳,如清单 1 所示。
清单1.建立科罗拉多州的拓扑结构网络
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-109.05304 39.195013,
-109.05304 41.000889,
-104.897461 40.996484
)',
4326
)
);
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-104.897461 40.996484,
-102.051744 40.996484,
-102.051744 40.003029
)',
4326
)
);
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-102.051744 40.003029,
-102.04874 36.992682,
-104.48204 36.992682
)',
4326
)
);
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-104.48204 36.992682,
-109.045226 36.999077,
-109.05304 39.195013
)',
4326
)
);
为了确保你已经正确地输入或复制了所有的内容,执行下面的SQL:
SELECT ST_GetFaceGeometry('ch13a_topology',1);
如果你看一下pgAdmin中Geometry viewer的输出,你应该看到图1中的内容。
图1.pgAdmin中看到的科罗拉多州4
整个科罗拉多州是一个大面孔,是一个完美的矩形多边形几何体。
运行清单1中的代码后,看一下表格内部,你会看到四条新的边,四个新的节点,和一个新的面。TopoGeo_AddLineString 函数使用边的数据自动生成了拓扑网络,并填充了节点和面。现在你有一个科罗拉多州的矩形轮廓的拓扑结构。
两条主要的州际公路从边界到边界纵横交错:I-25贯穿南北,I-70贯穿西/东。你可以用下面的代码添加I-70。
清单2.添加高速公路I-70
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-109.05304 39.195013,
-108.555908 39.108751,
-105.021057 39.717751,
-102.051744 40.003029
)',
4326
)
);
成功添加I-70后,SELECT ,将返回新边缘的ID号码。你应该在输出中看到数字5 。
接下来,添加I-25。
清单3.添加高速公路I-25
SELECT TopoGeo_AddLineString(
'ch13a_topology',
ST_GeomFromText(
'LINESTRING(
-104.897461 40.996484,
-105.021057 39.717751,
-104.798584 38.814031,
-104.48204 36.992682
)',
4326
)
);
因为你先添加了I-70,然后再添加I-25,后者将把I-70一分为二,为自己创造两条边,并把I-70分成两条边。输出将返回I-25的两条新边的ID号:7 和8 。
在这一点上,一张图会有帮助。我们使用QGIS PostGIS Topology Viewer制作了图2,其中显示了四个面的ID、八个边的ID和五个节点(每个节点使用不同风格的数字)。
图2.科罗拉多州拓扑结构网络
I-25(边8,7与节点2,5,4)南北走向。I-70(边5、6,节点1、5、3)自西向东行驶。这两条高速公路在州府丹佛(节点5)相交。
高速公路的增加将原来单面的科罗拉多州分成了四面。再仔细看一下这些表格。PostGIS自动重新组织了你的拓扑结构。角点不再是节点,只是勾勒出边缘的顶点。PostGIS在两条高速公路边缘相交的地方为丹佛增加了一个节点。
我们对高速公路进行了建模,并在其中加入了扭结点。对于I-25公路来说,该节点位于科罗拉多泉。对于I-70公路来说,该节点位于Grand Junction。这些节点只是用来完善几何形状的顶点;它们在关系中没有发挥任何作用。因此,它们不是节点。棱线只在节点处相交。
你现在总共有八条边。两条高速公路将科罗拉多州切成四个不同的多边形或面。I-25公路的加入将我们原来的单边I-70(边5)分成了两条边(5和6)。
如果你使用下面的查询查看面表,你会看到列出的每个面以及它们的mbr(最小边界矩形),这只是该面的边界框。面表并不存储实际的多边形,因为得出这些多边形所需的所有数据都可以在edge_data表中找到。这种存储方法遵守了数据库的原则,即只将数据保存在一个地方。像以前一样,你使用ST_GetFaceGeometry 函数来查看实际的面的几何形状。
SELECT face_id, mbr,
ST_GetFaceGeometry('ch13a_topology',face_id) AS geom
❶ 返回面的几何形状
❷ 排除没有几何形状的通用面
上述查询中geom 列的输出如图 3 所示。该查询只考虑非通用面。通用面的面的id总是0,代表不属于拓扑结构的部分,所以总是空的。
图3.在pgAdmin4中看到的科罗拉多州在丹佛的四分法
边缘视图和边缘数据
edge 视图是一个包含edge_data表的列的子集的视图。edge_data表包含了OGC拓扑规范中没有定义的额外列,但PostGIS拓扑内部使用了这些列。在一般情况下,为了与OGC拓扑标准保持一致,应该使用edge 视图而不是直接查询edge_data表。
记住,拓扑学关注的不是描述几何图形,而是它们之间的关系。去掉科罗拉多州所有多余的顶点,就会产生一个骨架网络图,你可以在图4中看到。
图4.简化的网络拓扑结构
你可以通过使用清单4查询edge_data 表来看到构成拓扑结构的边。
清单4.查询edge_data
SELECT *
FROM ch13a_topology.edge_data
ORDER BY edge_id;
列表4的输出如图5所示,显示了基于简化拓扑结构的edge_data表的内容。
如果你的输出与下面的有些不同,请不要惊慌。不过,在这一点上你应该有8条边。
图5.简化的网络拓扑结构
拓扑结构类型
一旦你构建了你的拓扑结构,你就可以将你的基元分组,以构成topogeometries**(用拓扑学术语来说就是层)。
比方说,你想收集构成科罗拉多州模型中的高速公路的四条边。你可以先创建一个新的表来存储地形测量,如清单5所示。在这个表中,你可以使用PostGIS的AddTopoGeometryColumn 函数添加一个topogeometry 列。你应该始终使用AddTopoGeometryColumn 函数来创建新的列,因为它负责在topology.layer 表中注册新的topogeometry 列。
清单5.创建一个用于存储公路的表并定义一个地形测量列
CREATE TABLE ch13.highways_topo (highway varchar(20) PRIMARY KEY);
SELECT AddTopoGeometryColumn(
'ch13a_topology',
'ch13',
'highways_topo',
'topo',
'LINESTRING'
);
运行前面的代码后,你应该在topology.layer 表中看到一个新条目。AddTopoGeometryColumn 将返回新层的自动分配的ID。请记住,一个地形测量总是与一个图层相联系的。
一旦你有了你的topogeometry 列,你就可以使用CreateTopoGeom 函数添加I-70公路,如清单6所示。
清单6.定义I-70公路的地形测量,使用 CreateTopoGeom
INSERT INTO ch13.highways_topo (highway, topo)
VALUES (
'I70',
CreateTopoGeom(
❶ 为 I-70 定义条目,其中拓扑元素由 ch13a_topology 形成
❷ 拓扑结构的类型:2 = 线性的
❸ 这个topogeom所属层的ID。这是你定义 topogeom 列时返回的数字。
❹ 组成这个topogeom的元素。数组中的每个元素都由元素ID和元素类型组成(1=节点,2=边缘,3=面)。在这个例子中,所有元素都是边。
当定义一个新的地形测量列时,你需要用以下数字之一来表示地形测量类型。1=点,2=线,3=面。在topogeometries的情况下,多边形和多多边形被归入areal类型,点和多点是点的类型,而线绳和多线绳是线的类型。
一个地形几何学被实现为一个由4个元素组成的数据库域类型。你可以通过下面的查询看到这个编码:
SELECT highway, topo, (topo).*
FROM ch13.highways_topo WHERE highway = 'I70';
上述列表的输出是:
highway | topo | topology_id | layer_id | id | type
---------+-----------+-------------+----------+----+------
I70 | (1,1,1,2) | 1 | 1 | 1 | 2
(1 row)
正如你所看到的,扩大(topo).*作为列列出了构成地形测量的部分。第一项是topogeometry所属的拓扑结构。第二项是topogeometry所属的layer ,这是你在添加ch13.highways_topo.topo 列时分配给该列的层id。第三项是ch13a_topology schemarelation 表中列topogeo_id 中的topogeometry的id。最后,第四项是类型,这里是2 = lineal 。
如果你一开始就有几何体,你可以使用强大的toTopoGeom 函数将几何体转换为拓扑几何体,并在一步之内将新形成的拓扑几何体添加到你的表中,如清单7所演示。
清单7.使用拓扑几何学定义toTopoGeom
INSERT INTO ch13.highways_topo (highway, topo)
SELECT
'I25',
toTopoGeom(
❶ 使用 toTopoGeom 定义 I-25
❷ 几何体;如果不存在形成几何体所需的任何边或节点,将被创建
❸ 拓扑结构
❹ 图层
在列表 7 中,你使用toTopoGeom 函数添加 I-25 的拓扑几何。使用这个函数的风险和好处是,如果不存在形成新的地形几何的基元,它将根据需要创建新的基元边、结点和面。
在这个例子中,你已经在列表3中添加了原始边,所以toTopoGeometry ,不应该引入新的边。你包括你要添加到的拓扑结构的名称,以及这个新的拓扑几何将与之相关的层。这个层的ID必须与你在列表5中创建topogeometry列时返回的ID相同。
如果形成新的地形几何所需的节点或边不存在,toTopoGeom 函数将自动应用一个公差来寻找匹配的节点或边,然后再去创建它们。换句话说,如果一个现有的节点在线段几何的抓取距离内,toTopogeom ,将转移线段,把节点作为一个顶点纳入,而不是创建一个新的节点。如果你想覆盖默认的公差,你可以传递一个额外的最终参数给toTopogeom 来应用一个公差。toTopogeom 使用的默认公差是输入几何体的边界盒的函数。这个默认的公差是通过函数topology._ST_MinTolerance 在内部计算的。
为了确认你的新拓扑几何体的组成,你可以使用GetTopoGeomElements 函数,就像下一个清单中一样。
清单8.查询科罗拉多州公路的原始元素
SELECT highway, (topo).*, GetTopoGeomElements(topo) As el
FROM ch13.highways_topo
ORDER BY highway;
这个清单输出了用(topo).*) 访问的四个topogeometry子元素标识符和用GetTopoGeomElements 函数访问的一组topoelements。
highway | topology_id | layer_id | id | type | el
---------+-------------+----------+----+------+-------
I25 | 1 | 1 | 2 | 2 | {7,2}
I25 | 1 | 1 | 2 | 2 | {8,2}
I70 | 1 | 1 | 1 | 2 | {5,2}
I70 | 1 | 1 | 1 | 2 | {6,2}
列表8中的代码为每个topogeometry返回一组称为topoelements的对象。尽管你在highways_topo表中只有两行,但当你使用GetTopoGeomElements 函数时,你会得到四行,因为GetTopoGeomElements 为每条公路的每条边都返回一行。
Topoelement 对象是一个有两个元素的整数数组域类型。第一个是相应表格中元素的ID。因为边缘构成了高速公路,所以ID在ch13a_topology.edge中是edge_ids 。topoelement的第二个元素表示层/类的类型(1 =节点,2 =边,3 =面,更高的数字是层的ID)。
建立一个命名规则
PostGIS并没有明确区分描述拓扑网络的数据库对象和你自己在拓扑几何列中使用的拓扑。我们建议你建立一个命名惯例。支持拓扑结构的无数模式和表格可能会让人不知所措,尤其是对于负责维护底层网络的人来说。
使用拓扑结构的总结
PostGIS的拓扑模型为处理拓扑提供了以下功能:
- 启用拓扑扩展可以立即创建拓扑模式和功能。
topology.topology表记录了你数据库中的所有拓扑结构。topology.layer表记录了你的数据库中的所有拓扑几何列(层)。- 每个拓扑网络都有自己的网络模式。
- 基元(边、节点、面)在网络模式中有各自的表。
- 特定拓扑网络模式中的关系表(本例中为ch13a_topology.relation)记录了哪些拓扑基元和层元素属于哪个拓扑几何。
一旦你建立了你的拓扑结构,你就可以在数据库的任何地方自由使用它们。你可以通过从你的拓扑结构建立拓扑几何来在你的数据库中的其他地方使用它们。其过程如下:
- 在你自己的表中添加拓扑几何列(层)。
- 从基元或其他层创建拓扑几何,并将其添加到你的拓扑几何列。
- 从几何体中添加拓扑几何体,并使用
toTopoGeom功能在一个步骤中改变你的底层网络。但请记住,一旦你这样做,边、面和节点就会自动添加,而现有的则会被分割。一旦你的拓扑结构以这种方式被改变,简单地删除引入的拓扑结构并不足以恢复对拓扑结构的改变。