PostGIS系列课程之空间约束(三)

33 篇文章 8 订阅

延迟约束和完整的表级数据结构。

线性网络约束

从一个简单的道路网络开始。

CREATE TABLE roads (
  pk bigint PRIMARY KEY,
  name text NOT NULL,
  geom geometry(LineString, 3005) NOT NULL
    CONSTRAINT geom_no_zero_length CHECK (ST_Length(geom) > 0)
    CONSTRAINT geom_no_self_intersection CHECK (ST_IsSimple(geom))
);
检查约束

我们在表定义中添加了几个简单的检查约束:

  • 我们希望路段的长度不为零; 和,
  • 我们希望我们的路段没有自动交叉路口,变得“简单”。

毫无疑问,PostgreSQL可以轻松管理这些约束。

INSERT INTO roads VALUES (0, 'Main', 
    'LINESTRING(0 0, 0 0)');
------------------------------
ERROR:  new row for relation "roads" 
    violates check constraint "geom_no_zero_length"
DETAIL:  Failing row contains (0, Main,
    0102000020BD0B00000200000000000000000
    000000000000000000000000000...).
-----------------------------
INSERT INTO roads VALUES (1, 'Elm', 
    'LINESTRING(0 0, 5 5, 5 3, 0 5)');
-----------------------------
ERROR:  new row for relation "roads" 
    violates check constraint "geom_no_self_intersection"
DETAIL:  Failing row contains (1, Elm,
    0102000020BD0B00000400000000000000000
    000000000000000000000000000...).
触发器约束

但是,大多数空间用户想要对线性网络数据实施的真正限制是连通性。 特别是在以下情况下,他们会喜欢它:

  • 它们的网络是“节点”的,因此每个段都在另一个段的起点或终点处结束,而不是在一个段的中间。
  • 他们的网络是“连接的”,因此每个其他网段都可以访问每个网段。

这两个条件不仅需要了解要插入的段,还需要超越 CHECK 约束来触发。

CREATE OR REPLACE FUNCTION noded_road_trigger()
  RETURNS trigger AS
  $$
    DECLARE
      c bigint;
    BEGIN

      -- How many roads does the end point of this 
      -- road touch at the ends? Touches is not true
      -- when end points touch middles.
      SELECT Count(*)
      INTO c
      FROM roads
      WHERE ST_Touches(roads.geom, NEW.geom);

      -- Have to touch at least one other segment in
      -- the network.
      IF c < 1 THEN
        RAISE EXCEPTION 'road % is not connected to the network', NEW.pk;
      END IF;

      RETURN NEW;
    END;
  $$
  LANGUAGE 'plpgsql';

-- Run for data additions and changes,
-- and allow deferal to end of transaction.
CREATE CONSTRAINT TRIGGER noded_road 
  AFTER INSERT OR UPDATE ON roads DEFERRABLE
  FOR EACH ROW EXECUTE FUNCTION noded_road_trigger();

从一个小的种子网络开始,如下所示:

INSERT INTO roads VALUES (2, 'Grand', 'LINESTRING(0 0, 2 2)');
INSERT INTO roads VALUES (3, 'Grand', 'LINESTRING(2 2, 4 2)');
INSERT INTO roads VALUES (4, 'Oak', 'LINESTRING(0 2, 2 2)');

网络一

向网络添加两个新的细分:

INSERT INTO roads VALUES (5, 'Larch', 'LINESTRING(4 0, 6 0)');
-----------------------------
ERROR:  road 5 is not connected to the network
CONTEXT:  PL/pgSQL function noded_road_trigger() line 13 at RAISE
-----------------------------
INSERT INTO roads VALUES (6, 'Oak', 'LINESTRING(2 2, 4 0)');
-----------------------------
INSERT 0 1

这虽然有趣的。一个失败了,另一个成功了,为什么?因为当它们都需要连接的时候, “Oak” 段 需要在插入"Larch" 段之前插入到表中才能判断它们连接有效↓。

网络二

延缓约束

为了判断连接性,必须同时将这两个部分都放在适当的位置。 也许如果将它们放在事务块中,情况会好一些吗?

-- 删除数据进行新的测试
DELETE FROM roads WHERE pk = 6;
-- Does using a transaction block work?
BEGIN;
    INSERT INTO roads VALUES (5, 'Larch', 'LINESTRING(4 0, 6 0)');
    INSERT INTO roads VALUES (6, 'Oak', 'LINESTRING(2 2, 4 0)');
COMMIT;

不行?!

ERROR:  road 5 is not connected to the network
CONTEXT:  PL/pgSQL function noded_road_trigger() line 13 at RAISE

但是,当创建此约束触发器时,我们将其标记为 DEFERRABLE 。 如果我们可以让数据库等到所有记录都加载后再检查约束,那么我们的插入语句就可以了。

延迟约束校验,使用 SET CONSTRAINTS 命令(在事物块开始前)。

BEGIN;
SET CONSTRAINTS noded_road DEFERRED;
INSERT INTO roads VALUES (5, 'Larch', 'LINESTRING(4 0, 6 0)');
INSERT INTO roads VALUES (6, 'Oak', 'LINESTRING(2 2, 4 0)');
COMMIT;

成功! 因此,我们可以确保连接线。

强制约束

但是我们还能确保连通性吗? 如果每个网段都必须连接到另一个网段,则看起来是这样,但是实际上断开连接很容易:只需在一个延迟的事务中插入一组自连接的网段即可。

BEGIN;
SET CONSTRAINTS noded_road DEFERRED;
-- These aren't connected, do they fail on commit?
INSERT INTO roads VALUES (7, 'Sea', 'LINESTRING(6 1, 6 2)');
INSERT INTO roads VALUES (8, 'Isle', 'LINESTRING(6 2, 5 2)');
-- They do not! :(
COMMIT;
-- Clean up.
DELETE FROM roads WHERE pk IN (7, 8);

网络三

哦不,这很容易在表中插入单独的节点段的孤“岛”。 我们需要一个新的约束来检查所有段是否全局连接在一起。

表范围约束

我们已经使用``noded_road_trigger()` 保证了节点相连,因此我们只需要确保所有节点段都形成一个图,就可以使用 空间聚类 ,且公差为零。

CREATE OR REPLACE FUNCTION single_network_trigger()
  RETURNS trigger AS
  $$
    DECLARE
      c bigint;
    BEGIN

      -- All the segments are noded (touching)
      -- so clustering with zero distance will find
      -- all connected clusters.
      WITH clusters AS (
        SELECT ST_ClusterDBScan(geom, 0.0, 1) over () AS cluster
        FROM roads
      )
      SELECT Count(DISTINCT cluster)
      INTO c
      FROM clusters;

      -- If there's more than one cluster, we have
      -- separate islands in graph, and that's a no-no.
      IF c > 1 THEN
        RAISE EXCEPTION 'network is not fully connected';
      END IF;

      RETURN NEW;
    END;
  $$
  LANGUAGE 'plpgsql';

CREATE CONSTRAINT TRIGGER single_network 
  AFTER INSERT OR UPDATE ON roads DEFERRABLE
  FOR EACH ROW EXECUTE FUNCTION single_network_trigger();

现在,当尝试批量插入节点但未连接的线组时,系统捕获到异常了!

BEGIN;
-- 延迟约束起作用了
SET CONSTRAINTS ALL DEFERRED;
-- These aren't connected, do they fail on commit?
INSERT INTO roads VALUES (7, 'Sea', 'LINESTRING(6 1, 6 2)');
INSERT INTO roads VALUES (8, 'Isle', 'LINESTRING(6 2, 5 2)');
-- They do!
COMMIT;
-----------------------------
ERROR:  network is not fully connected
CONTEXT:  PL/pgSQL function single_network_trigger() line 15 at RAISE

这种方法的缺点很明显:对于较大的表,在每次数据更新时检查群集的成本将对性能造成影响。 但是,对于成千上万条记录中的较小表,它应该可以正常工作。

排除约束?

排除约束 由于PostGIS空间算子的实现方式,它对PostGIS并不是很有用。

排除约束严格限于执行使用索引运算符定义的条件,不幸的是,PostGIS运算符都使用“边界框逻辑”而不是“几何逻辑”。 例如:

SELECT 'LINESTRING(0 0 10 10)'::geometry && 'POINT(5 4)';

返回 TRUE , 因为它测试线串的边界框和点的边界框是否相交。

排除约束条件“仅允许不与现有几何图形相交的新几何图形”可能有用。 但是无法使用PostGIS && 运算符来实现。

若海软件科技

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

丷丩

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值