在PostGIS中对连接的线串进行分组?


12

我有一张基于一组属性(假设是speed_limit < 25)选择的街道表。有些街道在本地是连续的。我想将这些连接的线串集合归为GeometryCollections。在下图中,将有两个GeometryCollections:一个带有红线,一个带有蓝线。

在此处输入图片说明

我尝试按照以下方式运行几个“分解,分解”查询:

SELECT (ST_Dump(st_union)).geom
FROM 
    (SELECT ST_Union(geom) FROM roads) sq

在尝试了所有方法后,我要么获得了单个特征(ST_Union),要么得到了原始几何形状(ST_Dumpof ST_Union)。

也许可以用某种WITH RECURSIVE魔术来做到这一点?


“(ST_Dump(st_union))。geom”看起来不正确
Martin F

由于他没有为ST_Union(geom)别名,因此新geom的名称继承了该函数的名称,成为st_union。这就是为什么它看起来有点有趣的原因
LR1234567

Answers:


19

因此,举例来说。这是一个简单的表,具有两组相连的边线:

drop table lines;
create table lines ( id integer primary key, geom geometry(linestring) );
insert into lines (id, geom) values ( 1, 'LINESTRING(0 0, 0 1)');
insert into lines (id, geom) values ( 2, 'LINESTRING(0 1, 1 1)');
insert into lines (id, geom) values ( 3, 'LINESTRING(1 1, 1 2)');
insert into lines (id, geom) values ( 4, 'LINESTRING(1 2, 2 2)');
insert into lines (id, geom) values ( 11, 'LINESTRING(10 10, 10 11)');
insert into lines (id, geom) values ( 12, 'LINESTRING(10 11, 11 11)');
insert into lines (id, geom) values ( 13, 'LINESTRING(11 11, 11 12)');
insert into lines (id, geom) values ( 14, 'LINESTRING(11 12, 12 12)');
create index lines_gix on lines using gist(geom);

现在,这是一个递归函数,给定一条边的id,它会累加所有接触的边:

CREATE OR REPLACE FUNCTION find_connected(integer) returns integer[] AS
$$
WITH RECURSIVE lines_r AS (
  SELECT ARRAY[id] AS idlist, geom, id
  FROM lines 
  WHERE id = $1
  UNION ALL
  SELECT array_append(lines_r.idlist, lines.id) AS idlist, 
         lines.geom AS geom, 
         lines.id AS id
  FROM lines, lines_r
  WHERE ST_Touches(lines.geom, lines_r.geom)
  AND NOT lines_r.idlist @> ARRAY[lines.id]
)
SELECT 
  array_agg(id) AS idlist
  FROM lines_r
$$ 
LANGUAGE 'sql';

这样一来,我们便需要在每个组累加后查找尚未属于组的边的id。不幸的是,这需要第二个递归查询。

WITH RECURSIVE groups_r AS (
  (SELECT find_connected(id) AS idlist, 
          find_connected(id) AS grouplist, 
          id FROM lines WHERE id = 1)
  UNION ALL
  (SELECT array_cat(groups_r.idlist,find_connected(lines.id)) AS idlist,
         find_connected(lines.id) AS grouplist,
         lines.id
  FROM lines, groups_r
  WHERE NOT idlist @> ARRAY[lines.id]
  LIMIT 1)
)
SELECT id, grouplist
FROM groups_r;   

将它们合在一起会返回一个不错的集合,其中包含种子ID及其累积的每个组。我将其作为练习留给读者,以将id的数组转换回查询以创建用于映射的几何。

 id |   grouplist   
----+---------------
  1 | {1,2,3,4}
 11 | {11,12,13,14}
(2 rows)

我认为这段代码可能会更简单,如果几何类型支持PostgreSQL中的哈希(当您编写不涉及累积ID数组的更简单的RCTE时,会出现错误“所有列数据类型必须是可哈希的”),所以有一个对我的增强要求很少。
保罗·拉姆西

这是一种非常棒的方法。当我将其应用于更大的测试集时,我注意到一些奇怪的结果。我将看看是否可以将问题简化为一个简单的示例。100行:85个簇,最大簇= 3,0.03 s ///// 200行:144个簇,最大簇= 9,0.08 s //// 300行:180个簇,最大簇= 51,0.16 s /// / 400行:188个簇,最大簇= 41,0.27 s ///// 500行:176个簇,最大簇= 112,0.56 s //// 600行:143个簇,最大簇= 449,1.0 s // // 650行:133个群集,最大群集=
7601,6.8

将其添加到测试数据将导致grouplist数组 ID重复insert into lines (id, geom) values ( 15, 'LINESTRING(0 0, 10 10)');array_agg(id)在函数返回中更改array_agg(DISTINCT id)似乎可以解决问题。
dbaston 2014年

这是一个很好的解决方案,因此现在如何将几何存储在表格中,以便可以看到连接的线?
zakaria mouqcit

6

这是一种使用临时表将群集增量聚合在一起的方法。我不太在意临时表方法,但是随着行数的增加,这似乎表现很好(我的输入中有120万行)。

DO
$$
DECLARE
this_id bigint;
this_geom geometry;
cluster_id_match integer;

id_a bigint;
id_b bigint;

BEGIN
DROP TABLE IF EXISTS clusters;
CREATE TABLE clusters (cluster_id serial, ids bigint[], geom geometry);
CREATE INDEX ON clusters USING GIST(geom);

-- Iterate through linestrings, assigning each to a cluster (if there is an intersection)
-- or creating a new cluster (if there is not)
FOR this_id, this_geom IN SELECT id, geom FROM lines LOOP
  -- Look for an intersecting cluster.  (There may be more than one.)
  SELECT cluster_id FROM clusters WHERE ST_Intersects(this_geom, clusters.geom)
     LIMIT 1 INTO cluster_id_match;

  IF cluster_id_match IS NULL THEN
     -- Create a new cluster
     INSERT INTO clusters (ids, geom) VALUES (ARRAY[this_id], this_geom);
  ELSE
     -- Append line to existing cluster
     UPDATE clusters SET geom = ST_Union(this_geom, geom),
                          ids = array_prepend(this_id, ids)
      WHERE clusters.cluster_id = cluster_id_match;
  END IF;
END LOOP;

-- Iterate through the clusters, combining clusters that intersect each other
LOOP
    SELECT a.cluster_id, b.cluster_id FROM clusters a, clusters b 
     WHERE ST_Intersects(a.geom, b.geom)
       AND a.cluster_id < b.cluster_id
      INTO id_a, id_b;

    EXIT WHEN id_a IS NULL;
    -- Merge cluster A into cluster B
    UPDATE clusters a SET geom = ST_Union(a.geom, b.geom), ids = array_cat(a.ids, b.ids)
      FROM clusters b
     WHERE a.cluster_id = id_a AND b.cluster_id = id_b;

    -- Remove cluster B
    DELETE FROM clusters WHERE cluster_id = id_b;
END LOOP;
END;
$$ language plpgsql;


@zakariamouqcit很高兴为您服务!我ST_ClusterIntersecting在PostGIS中编写函数之前就写了这个答案。如果您的数据足够小以适合内存,我建议您检查一下以寻求更高性能的解决方案。
dbaston

寻找这个问题把我带到这里。尝试了迭代和st_clusterintersecting,但发现st_clusterDBScan最合适。万一其他人也被带到这里。postgis.net/docs/manual-dev/ST_ClusterDBSCAN.html
D_C

同意,ST_ClusterDBSCAN几乎始终是PostGIS 2.3+的最佳途径
dbaston
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.