PostGIS'de bağlı linestler gruplandırılsın mı?


12

Bir dizi özelliğe göre seçtiğim bir sokak tablonuz var (diyelim ki speed_limit < 25). Yerel olarak bitişik olan sokak grupları vardır; Bu bağlı linestrings kümelerini GeometryCollections içine gruplandırmak istiyorum. Aşağıdaki resimde iki GeometryCollections olacaktır: biri kırmızı çizgili, diğeri mavi çizgili.

resim açıklamasını buraya girin

Birkaç satır boyunca "çözün, deaggregate" sorguları çalıştırmayı denedim:

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

Denediğim her şeyle, ya tek bir özellik ( ST_Union) ya da orijinal geometrim ( ST_Dump/ ST_Union) ile sonuçlanıyorum .

Belki bunu bir çeşit WITH RECURSIVEsihirle yapmak mümkündür ?


"(ST_Dump (st_union)). Geom" ile ilgili bir şey doğru görünmüyor
Martin F

ST_Union (geom) adını takmadığından, yeni geom adı st_union olmak için işlevin adını devraldı. Bu yüzden biraz komik görünüyor
LR1234567

Yanıtlar:


19

Örnek olarak. İşte bağlı iki kenar grubuna sahip basit bir tablo:

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);

Şimdi, bir kenarın kimliği göz önüne alındığında, dokunan tüm kenarları biriktiren özyinelemeli bir işlev:

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';

Bu, her grup biriktikten sonra, zaten bir grubun parçası olmayan bir kenarın kimliğini bulmamıza gerek bırakıyor. Bu, trajik bir şekilde, ikinci bir özyinelemeli sorgu gerektirir.

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;   

Birlikte alınan tohum kimliği ve birikmiş her grup ile güzel bir set döndürür. Eşleme için geometri oluşturmak üzere kimlik dizilerini bir sorguya dönüştürmek için okuyucuya bir alıştırma olarak bırakıyorum.

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

Ben PostgreSQL karma destekleyen geometri türü (ids dizileri biriktirmeyen daha basit bir RCTE yazdığınızda, bir hata "Tüm sütun veri türleri yıkanabilir olmalıdır"), bu kod daha basit olabileceğini düşünüyorum benim için küçük geliştirme isteği.
Paul Ramsey

Bu gerçekten harika bir yaklaşım. Daha büyük bir test setine uyguladığım için bazı garip sonuçlar fark ediyorum; Sorunu basit bir örneğe indirgeyebileceğimi göreceğim. 100 satır: 85 küme, en büyük küme = 3, 0,03 s //// 200 satır: 144 küme, en büyük küme = 9, 0,08 s //// 300 satır: 180 küme, en büyük küme = 51, 0,16 s /// / 400 satır: 188 küme, en büyük küme = 41, 0.27 s //// 500 satır: 176 küme, en büyük küme = 112, 0.56 s //// 600 satır: 143 küme, en büyük küme = 449, 1.0 s // // 650 satır: 133 küme, en büyük küme = 7601, 6.8 s
dbaston

Test verilerine bu ekleme yinelenen kimlikleri neden olacaktır grouplistdizisi: insert into lines (id, geom) values ( 15, 'LINESTRING(0 0, 10 10)');. Değişen array_agg(id)fonksiyon karşılığında etmek array_agg(DISTINCT id)sorunu çözmek gibi görünüyor.
dbaston

Bu iyi bir çözüm, şimdi bağlı çizgileri görebilmemiz için geometrileri bir tabloda nasıl saklayabiliriz?
zakaria mouqcit

6

Kümeleri kademeli olarak bir araya getirmek için geçici bir tablo kullanan bir yaklaşım. Geçici tablo yaklaşımını gerçekten umursamıyorum, ancak satır sayısı arttıkça oldukça iyi bir performans sergiliyor gibi görünüyor (girişimde 1.2 M hat var).

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;

mükemmel çalışıyor
zakaria mouqcit

@zakariamouqcit Bunun sizin için çalışmasına sevindim! Bu cevabı ST_ClusterIntersectingPostGIS'te yazmadan önce yazdım . Verileriniz belleğe sığacak kadar küçükse, daha performanslı bir çözüm olup olmadığını kontrol etmenizi öneririm.
dbaston

bu soruyu aramak beni buraya getirdi. Yinelemeli ve st_clusterintersecting'i denedim, ancak st_clusterDBScan'ı en uygun buldum. Başka biri de buraya getirilirse. postgis.net/docs/manual-dev/ST_ClusterDBSCAN.html
D_C

Katılıyorum, ST_ClusterDBSCAN neredeyse her zaman PostGIS 2.3+ için gitmek için en iyi yoldur
dbaston
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.