小巧. 快速. 可靠.
三选二.
Geopoly 接口到 SQLite R*Tree 模块

1. 概述

Geopoly 模块是 R-Tree 扩展 的替代接口,它使用 GeoJSON 符号 (RFC-7946) 来描述二维多边形。Geopoly 包含用于检测一个多边形是否包含在另一个多边形内或与另一个多边形重叠、计算多边形围成的面积、对多边形进行线性变换、将多边形渲染为 SVG 以及其他类似操作的函数。

Geopoly 的源代码包含在 amalgamation 中。但是,根据配置选项和您使用的特定 SQLite 版本,Geopoly 扩展可能默认启用或禁用。要确保为您的构建启用 Geopoly,请添加 -DSQLITE_ENABLE_GEOPOLY=1 编译时选项。

Geopoly 操作的是“简单”多边形 - 也就是说,边界不与自身相交的多边形。因此,Geopoly 扩展了 R-Tree 扩展 的功能,而 R-Tree 扩展 只能处理矩形区域。另一方面,R-Tree 扩展 能够处理 1 到 5 个坐标维度,而 Geopoly 仅限于二维形状。

Geopoly 模块中的每个多边形都可以与任意数量的辅助数据字段相关联。

1.1. GeoJSON

GeoJSON 标准 是使用 JSON 交换地理空间信息的语法。GeoJSON 是一个丰富的标准,可以描述几乎任何类型的地理空间内容。

Geopoly 模块只理解 GeoJSON 的一小部分,但这是一个关键子集。特别是,GeoJSON 理解描述简单多边形的顶点 JSON 数组。

多边形由其顶点定义。每个顶点都是一个 JSON 数组,包含两个数值,它们是顶点的 X 和 Y 坐标。多边形是一个 JSON 数组,至少包含四个这样的顶点,因此是一个数组的数组。数组中第一个和最后一个顶点必须相同。多边形遵循右手规则:当从一个顶点到下一个顶点跟踪一条线时,线右侧的区域位于多边形外部,左侧的区域位于多边形内部。换句话说,顶点的净旋转是逆时针方向的。

例如,以下 JSON 描述了一个等腰三角形,它位于 X 轴上,面积为 0.5

[[0,0],[1,0],[0.5,1],[0,0]]

三角形有三个顶点,但三角形的 GeoJSON 描述有 4 个顶点,因为第一个和最后一个顶点是重复的。

1.2. 二进制存储格式

在内部,Geopoly 以二进制格式存储多边形 - 一个 SQL BLOB。二进制格式的详细信息将在下面给出。所有 Geopoly 接口都可以接受 GeoJSON 格式或二进制格式的多边形。

2. 使用 Geopoly 扩展

创建 geopoly 表如下

CREATE VIRTUAL TABLE newtab USING geopoly(a,b,c);

上面的语句创建了一个名为“newtab”的新 geopoly 表。每个 geopoly 表都包含一个内置的整数“rowid”列和一个“_shape”列,该列包含与表该行相关联的多边形。上面的示例还定义了三个名为“a”、“b”和“c”的辅助数据列,它们可以存储应用程序需要与每个多边形相关联的任何其他信息。如果没有必要存储辅助信息,则可以省略辅助列列表。

使用普通的 INSERT 语句将新的多边形存储在表中

INSERT INTO newtab(_shape) VALUES('[[0,0],[1,0],[0.5,1],[0,0]]');

UPDATE 和 DELETE 语句的工作方式类似。

2.1. 查询

要使用索引的地理空间搜索查询 geopoly 表,请在 WHERE 子句中使用 geopoly_overlap() 或 geopoly_within() 函数之一作为布尔函数,并将“_shape”列作为函数的第一个参数。例如

SELECT * FROM newtab WHERE geopoly_overlap(_shape, $query_polygon);

前面的示例将返回 _shape 与 $query_polygon 参数中的多边形重叠的每一行。geopoly_within() 函数的工作方式类似,但只返回 _shape 完全包含在 $query_polygon 中的那些行。

WHERE 子句中包含裸 geopoly_overlap() 或 geopoly_within() 函数的查询(以及 DELETE 和 UPDATE 语句)利用底层的 R*Tree 数据结构进行快速查找,该查找只需要检查表中的一小部分行。检查的行数当然取决于 $query_polygon 的大小。大的 $query_polygon 通常需要查看比小的 $query_polygon 更多的行。

针对 geopoly 表的 rowid 的查询也非常快,即使对于包含大量行的表也是如此。但是,辅助数据列中没有任何索引,因此针对辅助数据列的查询将涉及全表扫描。

3. 特殊函数

geopoly 模块定义了几个新的 SQL 函数,这些函数对于处理多边形很有用。这些函数的所有多边形参数都可以是 GeoJSON 格式或内部二进制格式。

3.1. geopoly_overlap(P1,P2) 函数

如果 P1 和 P2 都是多边形,则 geopoly_overlap(P1,P2) 函数在 P1 和 P2 之间存在任何重叠时返回一个非零整数,或者如果 P1 和 P2 完全不相交时返回零。如果 P1 或 P2 不是多边形,则此例程返回 NULL。

geopoly_overlap(P1,P2) 函数很特殊,因为 geopoly 虚拟表知道如何使用 R*Tree 索引来优化 WHERE 子句使用 geopoly_overlap() 作为布尔函数的查询。只有 geopoly_overlap(P1,P2) 和 geopoly_within(P1,P2) 函数具有此功能。

3.2. geopoly_within(P1,P2) 函数

如果 P1 和 P2 都是多边形,则 geopoly_within(P1,P2) 函数在 P1 完全包含在 P2 内时返回一个非零整数,或者如果 P1 的任何部分位于 P2 外部时返回零。如果 P1 和 P2 是同一个多边形,则此例程返回非零。如果 P1 或 P2 不是多边形,则此例程返回 NULL。

geopoly_within(P1,P2) 函数很特殊,因为 geopoly 虚拟表知道如何使用 R*Tree 索引来优化 WHERE 子句使用 geopoly_within() 作为布尔函数的查询。只有 geopoly_within(P1,P2) 和 geopoly_overlap(P1,P2) 函数具有此功能。

3.3. geopoly_area(P) 函数

如果 P 是一个多边形,则 geopoly_area(P) 返回该多边形围成的面积。如果 P 不是多边形,则 geopoly_area(P) 返回 NULL。

3.4. geopoly_blob(P) 函数

如果 P 是一个多边形,则 geopoly_blob(P) 返回该多边形的二进制编码作为 BLOB。如果 P 不是多边形,则 geopoly_blob(P) 返回 NULL。

3.5. geopoly_json(P) 函数

如果 P 是一个多边形,则 geopoly_json(P) 返回该多边形的 GeoJSON 表示形式作为 TEXT 字符串。如果 P 不是多边形,则 geopoly_json(P) 返回 NULL。

3.6. geopoly_svg(P,...) 函数

如果 P 是一个多边形,则 geopoly_svg(P,...) 返回一个文本字符串,该字符串是该多边形的 可缩放矢量图形 (SVG) 表示形式。如果有多个参数,则第二个及后续参数将作为属性添加到每个 SVG 图形中。例如

SELECT geopoly_svg($polygon,'class="poly"','style="fill:blue;"');

如果 P 不是多边形,则 geopoly_svg(P,...) 返回 NULL。

请注意,geopoly 使用传统的右手笛卡尔坐标系,原点位于左下角,而 SVG 使用左手坐标系,原点位于左上角。geopoly_svg() 例程不会尝试转换坐标系,因此显示的图像以镜像形式显示并旋转。如果这是不可取的,则可以使用 geopoly_xform() 例程将输出从笛卡尔坐标系转换为 SVG 坐标系,然后再将多边形传递到 geopoly_svg() 中。

3.7. geopoly_bbox(P) 和 geopoly_group_bbox(P) 函数

如果 P 是一个多边形,则 geopoly_bbox(P) 返回一个新的多边形,该多边形是完全包围 P 的最小(轴对齐)矩形。如果 P 不是多边形,则 geopoly_bbox(P) 返回 NULL。

geopoly_group_bbox(P) 函数是 geopoly_bbox(P) 的聚合版本。geopoly_group_bbox(P) 函数返回能够包围聚合期间看到的全部 P 值的最小矩形。

3.8. geopoly_contains_point(P,X,Y) 函数

如果 P 是一个多边形,则 geopoly_contains_point(P,X,Y) 在且仅当坐标 X,Y 位于多边形 P 的内部或边界上时返回一个非零整数。如果 P 不是多边形,则 geopoly_contains_point(P,X,Y) 返回 NULL。

3.9. geopoly_xform(P,A,B,C,D,E,F) 函数

geopoly_xform(P,A,B,C,D,E,F) 函数返回一个新的多边形,该多边形是对多边形 P 的仿射变换,其中变换由值 A,B,C,D,E,F 定义。如果 P 不是有效多边形,则此例程返回 NULL。

变换根据以下公式转换多边形的每个顶点

x1 = A*x0 + B*y0 + E
y1 = C*x0 + D*y0 + F

因此,例如,要将多边形移动某个量 DX, DY 而不改变其形状,请使用

geopoly_xform($polygon, 1, 0, 0, 1, $DX, $DY)

要围绕点 0, 0 将多边形旋转 R 弧度

geopoly_xform($polygon, cos($R), sin($R), -sin($R), cos($R), 0, 0)

请注意,翻转多边形的变换可能会导致顶点的顺序反转。换句话说,变换可能会导致顶点以顺时针方向循环,而不是逆时针方向。这可以通过在变换后将结果发送到 geopoly_ccw() 函数来纠正。

3.10. geopoly_regular(X,Y,R,N) 函数

geopoly_regular(X,Y,R,N) 函数返回一个具有 N 个边的凸、简单、规则、等边、等角的多边形,以 X,Y 为中心,外接圆半径为 R。或者,如果 R 为负数或 N 小于 3,则该函数返回 NULL。N 值上限为 1000,因此即使 N 值大于 1000,该例程也不会渲染一个具有超过 1000 个边的多边形。

例如,以下图形

3 4 5 6 7 8 10 12 16 20

是由以下脚本生成的

SELECT '<svg width="600" height="300">';
WITH t1(x,y,n,color) AS (VALUES
   (100,100,3,'red'),
   (200,100,4,'orange'),
   (300,100,5,'green'),
   (400,100,6,'blue'),
   (500,100,7,'purple'),
   (100,200,8,'red'),
   (200,200,10,'orange'),
   (300,200,12,'green'),
   (400,200,16,'blue'),
   (500,200,20,'purple')
)
SELECT
   geopoly_svg(geopoly_regular(x,y,40,n),
        printf('style="fill:none;stroke:%s;stroke-width:2"',color))
   || printf(' <text x="%d" y="%d" alignment-baseline="central" text-anchor="middle">%d</text>',x,y+6,n)
  FROM t1;
SELECT '</svg>';

3.11. geopoly_ccw(J) 函数

geopoly_ccw(J) 函数返回具有逆时针 (CCW) 旋转的多边形 J。

RFC-7946 要求多边形使用 CCW 旋转。但该规范还注意到,许多旧版 GeoJSON 文件不遵循该规范,并且包含具有顺时针 (CW) 旋转的多边形。geopoly_ccw() 函数对于读取旧版 GeoJSON 脚本的应用程序很有用。如果 geopoly_ccw() 的输入是一个格式正确的多边形,则不会进行任何更改。但是,如果输入多边形的循环方向相反,则 geopoly_ccw() 会反转循环方向,使其符合规范,并使其与 Geopoly 模块一起正常工作。

4. 实现细节

geopoly 模块是 R-Tree 扩展 的扩展。Geopoly 使用与 R-Tree 扩展 相同的底层逻辑和阴影表。Geopoly 只是提供了一个不同的接口,并提供了一些额外的逻辑来计算多边形解码、重叠和包含。

4.1. 多边形的二进制编码

Geopoly 在内部使用二进制格式存储所有多边形。二进制多边形由一个 4 字节的标头组成,后面跟一个坐标对数组,其中每个坐标的每个维度都是一个 32 位浮点数。

标头中的第一个字节是标志字节。标志字节的最低有效位决定了紧随标头的坐标对的存储方式是大端序还是小端序。最低有效位的值为 0 表示大端序,值为 1 表示小端序。标头中第一个字节的其他位保留供将来扩展使用。

标头中的接下来的三个字节以大端序整数的形式记录多边形中顶点的数量。因此,每个多边形中顶点的最大数量约为 1600 万个。

在标头之后是坐标对数组。每个坐标都是一个 32 位浮点数。使用 32 位浮点数表示坐标意味着地球表面上的任何点都可以以大约 2.5 米的分辨率进行映射。当然,如果地图限制在一个大陆或国家,则可以实现更高的分辨率。请注意,geopoly 模块中坐标的分辨率与地球表面上由于潮汐力而产生的点每天移动的距离相近。

二进制格式中的坐标列表不包含冗余。最后一个坐标不像 GeoJSON 那样是第一个坐标的重复。因此,多边形的二进制表示形式中的坐标对始终比 GeoJSON 表示形式少一个。

4.2. 阴影表

geopoly 模块建立在 R 树扩展 之上,并使用相同的底层阴影表和算法。出于索引目的,每个多边形在阴影表中表示为一个矩形边界框。底层的 R 树实现使用边界框来限制搜索空间。然后,geoploy_overlap() 和/或 geopoly_within() 例程进一步细化搜索,以获得精确的结果。

此页面最后修改于 2023-12-05 14:43:20 UTC