基于PostGIS的高级应用(4)-- 空间查询

简介: 一 空间关系   数据库中判定数据之间的关系,使用的是比较操作符,如下: 操作符 描述 < 小于 | 大于<= |小于等于= |大于等于 = |等于<>或!=|不等于  但是在空间数据库中,由于空间数据的多维属性及其不同的几何特征,其判定关系与数值型字符型这些常用数据有非常大的概念性差异。

一 空间关系

  数据库中判定数据之间的关系,使用的是比较操作符,如下:

操作符 描述
< 小于

| 大于
<= |小于等于
= |大于等于

= |等于
<>或!=|不等于
  但是在空间数据库中,由于空间数据的多维属性及其不同的几何特征,其判定关系与数值型字符型这些常用数据有非常大的概念性差异。对于GIS来说,空间数据库是核心,GIS开发人员对常用的基于sql比较操作符查询关系表的方式叫“属性查询”,对基于图形空间关系的判定查询叫“空间查询”。所以在说空间查询时,一定要写理清什么是空间关系。
空间关系简介图.png

  任何涉及地理位置的数据,都具备如下关系:

空间关系 描述
ST_Intersects 相交关系,两个图形之间存在公共部分,比如公共点,公共线,公共面
ST_Disjoint 相离关系,两个图形无丝毫公共部分,与ST_Intersects完全相反
ST_Contains 包含关系,图形A包含图形B:ST_Contains(A,B),如点在面内,线在面内。
ST_Within 被包含关系,图形A被B包含:ST_Within(A,B),与ST_Contains完全相反。
ST_Covers 覆盖关系,图形A完全覆盖住了图形B:ST_Covers (A,B),部分关系与ST_Contains重叠,但不是完全一样。
ST_Crosses 穿越关系,图形A与图形B有一部分公共内点,但不是全部。
ST_Equals 相等关系,两个图形完全相等。
ST_Overlaps 压盖关系
ST_Touches 相连关系,两个图形只有边界存在公共连接关系。

1-1 ST_Within(A,B)=ST_Contains(B,A)
1-2 ST_Touches
1-3 ST_Overlaps
1-4 ST_Crosses
1-5 ST_Intersects
1-6 ST_Disjoint
空间关系并非非此即彼的关系,不同空间关系之间肯能存在重叠部分,但又有些许差异,具体需要用户在实际应用时体会。实际应用中,St_Intersects是最常用的一个。
更专业解释参考维基百科详情:https://en.wikipedia.org/wiki/DE-9IM

二 分析优化

  正如第一节所说,空间关系之间,既可能是完全互斥的,如相离和相交,包含和被包含,又有似乎重叠的关系,如 包含与压盖,相交与相连。实际应用为了解决一个业务场景,可能有很多种空间方法可以解决问题,但不同的方法之间实际效率需要使用者测试和选择。

2.1 案例说明

  某规划公司有两类点数据,一类是公交站点,地铁站点等交通站点数据,数据量约6200条,一类是居民小区房屋点位置数据,数据量约25000条。规划部门希望快速检索站点为中心约200米以外的所有房屋点数据。
房屋测试数据:

--创建房屋测试表
create table house(
    gid serial primary key,
    name text,
    geom geometry(Point,4326)
);
create index house_geom_idx on house using gist(geom);
--插入约25000的测试数据
insert into house(geom) SELECT st_setsrid((ST_Dump(p_geom)).geom,4326)
from (select ST_GeneratePoints(
ST_GeomFromText('Polygon((118.357442 31.231278,119.235188 31.231278,119.235188 32.614617,118.357442 32.614617,118.357442 31.231278))'
), 25000) as p_geom) as b;

站点测试数据:

--创建站点测试表
create table station(
    gid serial primary key,
    name text,
    geom geometry(Point,4326)
);
create index station_geom_idx on station using gist(geom);
--插入测试数据
insert into station(geom) SELECT st_setsrid((ST_Dump(p_geom)).geom,4326)
from (select ST_GeneratePoints(
ST_GeomFromText('Polygon((118.357442 31.231278,119.235188 31.231278,119.235188 32.614617,118.357442 32.614617,118.357442 31.231278))'
), 6200) as p_geom) as b;

创建一个临时的站点缓冲数据表

create table station_buffer(gid serial primary key,geom geometry(Polygon,4326));
create index station_buffer_geom_idx on station_buffer using gist(geom);

insert into station_buffer(geom) select st_buffer(geom,0.002) geom from station;

2.2 空间分析

2.2.1 相离算法

  以每个站点为中心,200米为半径,建立缓冲区,合并所有的缓冲区,查询房屋不在这个合并缓冲区范围内的数据,即房屋点与合并缓冲区点相离,这个逻辑是最简单的:

select count(a.*) from house a,(
select st_union(geom) geom from station_buffer
) b where ST_Disjoint(a.geom,b.geom);

但是直接卡死了。。。。

2.2.1 相交反算

另外一个思路是把缓冲区内相交的房屋计算出来,然后根据结果反算不在相交数据集中的数据:
方法一:把缓冲区union成一个大的图形,计算这个图形相交的house,然后反算。

select count(a.*) from house a where gid not in 
(select distinct(a.gid) from house a,(select st_union(geom) geom from station_buffer) b where ST_intersects(a.geom,b.geom));
 count 
-------
 23434
(1 row)

Time: 17857.083 ms (00:17.857)

方法一虽然花费了17s,但是比直接相离那个逻辑,也是快了不知道几百倍了。。。但还是很卡。
方法二:与方法一基本一致,但是不合并缓冲区。

select count(a.*) from house a where gid not in (
select distinct(a.gid) from house a,station_buffer b where ST_intersects(a.geom,b.geom)
);
 count 
-------
 23434
(1 row)

Time: 274.454 ms

方法二直接从16s优化到了274 ms了,质的飞越!
但方法二这里使用了not in,只是为了表达逻辑性的,但not in其实也是很影响性能的,我们试着修改修改看看:
方法三:与方法二一致,只是将not in优化成左连接了。。。

select count(*) from (
select a.*,b.gid as gid2 from house a left join (
select distinct(a.gid) from house a,station_buffer b where ST_intersects(a.geom,b.geom)
) as b on a.gid=b.gid) as c where gid2 is null;
 count 
-------
 23434
(1 row)

Time: 62.825 ms

PS:not in可以用not exists和左连接去优化,直接not in是很影响性能的。
我们可以把执行计划贴下:

explain select a.* from house a where gid not in (
select distinct(a.gid) from house a,station_buffer b where ST_intersects(a.geom,b.geom)
);
                                                         QUERY PLAN                                                         
----------------------------------------------------------------------------------------------------------------------------
 Seq Scan on house a  (cost=5864.43..6385.93 rows=12500 width=68)
   Filter: (NOT (hashed SubPlan 1))
   SubPlan 1
     ->  Unique  (cost=0.46..5801.93 rows=25000 width=4)
           ->  Gather Merge  (cost=0.46..5692.71 rows=43688 width=4)
                 Workers Planned: 2
                 ->  Nested Loop  (cost=0.43..5237.25 rows=18203 width=4)
                       ->  Parallel Index Scan using house_pkey on house a_1  (cost=0.29..531.05 rows=10417 width=36)
                       ->  Index Scan using station_buffer_geom_idx on station_buffer b  (cost=0.15..0.44 rows=1 width=568)
                             Index Cond: (a_1.geom && geom)
                             Filter: _st_intersects(a_1.geom, geom)
(11 rows)

Time: 1.813 ms

不使用not in:

explain select count(*) from (
select a.*,b.gid as gid2 from house a left join (
select distinct(a.gid) from house a,station_buffer b where ST_intersects(a.geom,b.geom)
) as b on a.gid=b.gid) as c where gid2 is null;
                                                     QUERY PLAN                                                      
---------------------------------------------------------------------------------------------------------------------
 Aggregate  (cost=4148.80..4148.81 rows=1 width=8)
   ->  Hash Anti Join  (cost=3741.88..4148.80 rows=1 width=0)
         Hash Cond: (a.gid = a_1.gid)
         ->  Gather  (cost=0.00..313.17 rows=25000 width=4)
               Workers Planned: 2
               ->  Parallel Seq Scan on house a  (cost=0.00..313.17 rows=10417 width=4)
         ->  Hash  (cost=3429.38..3429.38 rows=25000 width=4)
               ->  HashAggregate  (cost=2929.38..3179.38 rows=25000 width=4)
                     Group Key: a_1.gid
                     ->  Gather  (cost=0.28..2820.16 rows=43688 width=4)
                           Workers Planned: 2
                           ->  Nested Loop  (cost=0.28..2820.16 rows=18203 width=4)
                                 ->  Parallel Seq Scan on station_buffer b  (cost=0.00..502.83 rows=2583 width=568)
                                 ->  Index Scan using house_geom_idx on house a_1  (cost=0.28..0.89 rows=1 width=36)
                                       Index Cond: (geom && b.geom)
                                       Filter: _st_intersects(geom, b.geom)
(16 rows)

这个案例的优化,将相离改成了相交,not in改成了左连接,都起到了优化查询案例。

2.3 优化解释

  • 相离为什么那么慢?
    房屋点与红色缓冲区.png

站点200米缓冲区是红色部分,200米是很小的范围,受影响的house是很少的。数据库查询计划,对有索引的,只检索少部分数据才走索引。而相离运算,基本起不到过滤的作用,几乎100%的数据都是相离的,那么会全表查询,全表计算,所以非常卡。

  • 为什么union缓冲区这么慢?
    相交反算的方法一,把缓冲区合并了一个图形,通过上图可知,红色的缓冲区几乎各个地方都有,合并后的图形的extent(外接矩形)基本是全局的,而无效部分非常大(红色区域之间都是无效部分),gist索引,首先也还是根据这个外接矩形去筛选的。实际应用中,对图形的extent中,无效面积过大的,反而还要去切割去优化io和扫描放大。

具体参考德哥的:
《PostgreSQL 空间st_contains,st_within空间包含搜索优化 - 降IO和降CPU(bound box) (多边形GiST优化)》
《PostgreSQL 空间切割(st_split, ST_Subdivide)功能扩展 - 空间对象网格化 (多边形GiST优化)》
在空间分析中,合理使用gist索引,图形越简单,图形面积占外接矩形面积越大,检索效果越好。反之,这种union图形,图形变复杂,extent变大,图形面积占外接矩形面积越小,效果越差。

  • 为啥不使用not in?
    这个是sql优化中常用的,使用not exists和左连接,的确起到了优化作用。(案例中gid是主键,有索引)。

  空间查询在PostGIS中,也是sql查询,各种优化需要根据实际情况,如分割复杂图形,减少gist索引无效面积,合理使用空间分析的匹配关系,虽然很多逻辑都对,但是高效还是要开发者根据实际情况去调测的,另外其他基本的sql优化也是通用的。本文作者,水平一般般,但是稍微学了点皮毛就尝试了下,验证了一句话:“实践是检验真理的唯一标准”。

相关文章
|
定位技术 Windows 关系型数据库
PostgreSQL GUI pgadmin4 v3.3 支持 gis geometry 数据编辑、显示
标签 PostgreSQL , pgadmin , gis , 编辑 背景 pgadmin 4 v3.3 开始支持geometry 类型的展示。 https://www.postgresql.org/ftp/pgadmin/pgadmin4/v3.3/windows/ 如果geometry使用的是SRID 4326 (WGS 84 lon/lat)坐标系,则pgadmin会自动从OpenStreetMap 加载图层,作为背景。
2017 0
|
2月前
|
SQL 人工智能 关系型数据库
【DDIA笔记】【ch2】 数据模型和查询语言 -- 文档模型中Schema的灵活性
【6月更文挑战第8天】网状模型是层次模型的扩展,允许节点有多重父节点,但导航复杂,需要预知数据库结构。关系模型将数据组织为元组和关系,强调声明式查询,解耦查询语句与执行路径,简化了访问并通过查询优化器提高效率。文档型数据库适合树形结构数据,提供弱模式灵活性,但在Join支持和访问局部性上不如关系型。关系型数据库通过外键和Join处理多对多关系,适合高度关联数据。文档型数据库的模式灵活性体现在schema-on-read,写入时不校验,读取时解析,牺牲性能换取灵活性。适用于不同类型或结构变化的数据场景。
28 0
|
存储 SQL 数据可视化
Ganos矢量快显功能上手
本文介绍了由阿里巴巴独立研发的多模态时空数据库组件Ganos的2D和3D矢量快显功能。其中,2D矢量快显功能可用于对亿级规模2D矢量数据的高效可视化。Ganos的2D矢量快显快显功能解决了传统切片方案切片时间长和切片存储开销大两大痛点,并且支持局部更新,相较于现有系统,在效率、存储开销以及功能丰富性上都有很大提升。本文以实例的形式介绍了如何创建和更新2D矢量金字塔,以及如何返回切片等功能。通过参考本文,用户可以很快上手体验这一功能。Ganos的3D矢量可视化功能通过对2D矢量切片进行扩展,使其能够支持Geometry3D数据的可视化,可用于可视化大范围3D场景。
|
存储 Oracle 前端开发
详细讲解orcale空间数据库以及空间数据库内置函数
详细讲解orcale空间数据库以及空间数据库内置函数
442 0
|
存储 SQL 关系型数据库
一篇文章带你玩转PostGIS空间数据库 1
一篇文章带你玩转PostGIS空间数据库
|
SQL 存储 算法
一篇文章带你玩转PostGIS空间数据库 2
一篇文章带你玩转PostGIS空间数据库
|
关系型数据库 数据库连接 数据库
空间数据库系列:PostGIS和qgis的数据连接
如何使用QGIS连接带postGIS扩展的postgresql数据库
728 0
|
存储 SQL 关系型数据库
【MySQL从入门到精通】【高级篇】(二十九)覆盖索引的使用&索引下推
上一篇文章我们介绍了 【MySQL从入门到精通】【高级篇】(二十八)子查询优化,排序优化,GROUP BY优化和分页查询优化。这篇文章我们接着来介绍覆盖索引。
176 0
|
SQL 存储 关系型数据库
MySQL基础教程8——DQL—高级数据查询
可以查询到一共有1条数据,注意如果count(字段列表)中字段有空值是不会被记录到该条数据。
|
SQL 关系型数据库 Unix