接続された線ストリングをPostGISでグループ化しますか?


12

一連の属性に基づいて選択した通りのテーブルがあります(それがであるとしましょうspeed_limit < 25)。ローカルに隣接した通りのグループがあります。これらの接続された線ストリングのセットをGeometryCollectionsにグループ化します。以下の画像では、2つのGeometryCollectionがあります。1つは赤い線で、もう1つは青い線です。

ここに画像の説明を入力してください

私は、次の行に沿っていくつかの「解決、分解」クエリを実行してみました。

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

私が試したすべてのことで、1つのフィーチャ(ST_Union)または元のジオメトリ(ST_Dumpof ST_Union)になります。

たぶん、何らかのWITH RECURSIVE魔法でこれを行うことは可能ですか?


「(ST_Dump(st_union))。geom」で何かが正しく見えない
マーティンF 14

彼はST_Union(geom)をエイリアスしなかったため、新しいgeomの名前は関数の名前を継承してst_unionになりました。それはちょっとおかしい理由です
LR1234567

回答:


19

したがって、例によって。これは、2つの連結されたエッジグループを持つ単純なテーブルです。

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を見つける必要があります。悲劇的なことに、2番目の再帰クエリが必要です。

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を記述すると、「すべての列のデータ型はハッシュ可能でなければならない」というエラーが発生する)私のための少しの強化リクエスト。
ポールラムジー14

これは本当に素晴らしいアプローチです。より大きなテストセットに適用すると、奇妙な結果に気付きます。問題を単純な例に減らすことができるかどうかを確認します。100行:85クラスター、最大クラスター= 3、0.03秒//// 200行:144クラスター、最大クラスター= 9、0.08秒//// 300行:180クラスター、最大クラスター= 51、0.16秒/// / 400行:188クラスター、最大クラスター= 41、0.27秒//// 500行:176クラスター、最大クラスター= 12、0.56秒//// 600行:143クラスター、最大クラスター= 449、1.0秒// // 650行:133個のクラスタ、最大クラスタ= 7601、6.8秒
dbaston

これをテストデータに追加すると、grouplist配列内でIDが重複します insert into lines (id, geom) values ( 15, 'LINESTRING(0 0, 10 10)');array_agg(id)関数の戻り値をに変更array_agg(DISTINCT id)すると、問題が解決したようです。
dbaston 14

これは良い解決策です。接続線を表示できるように、ジオメトリをテーブルに保存するにはどうすればよいですか?
ザカリア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;

完全に動作します
ザカリアmouqcit

@zakariamouqcitこれはあなたのために働いてくれてうれしいです!ST_ClusterIntersectingPostGISで関数を記述する前に、この回答を書きました。データがメモリに収まるほど小さい場合は、パフォーマンスの高いソリューションを確認することをお勧めします。
-dbaston

この質問を探してここに来ました。反復およびst_clusterintersectingを試しましたが、st_clusterDBScanが最も適切であることがわかりました。他の人がここに連れて来られた場合に備えて。postgis.net/docs/manual-dev/ST_ClusterDBSCAN.html
D_C

合意された、ST_ClusterDBSCANは2.3+ PostGISのために行くための最善の方法はほとんど常にある
dbaston
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.