จัดกลุ่มการเชื่อมต่อที่เชื่อมต่อกันใน PostGIS?


12

ฉันมีตารางถนนที่ฉันเลือกโดยอิงจากชุดของคุณลักษณะ (สมมติว่าเป็นspeed_limit < 25) มีกลุ่มถนนที่ต่อเนื่องกันในท้องถิ่น ฉันต้องการจัดกลุ่มชุดการเชื่อมต่อเหล่านี้เป็น GeometryCollections ในภาพด้านล่างจะมี GeometryCollections สองอัน: อันหนึ่งมีเส้นสีแดงและอีกเส้นที่มีเส้นสีฟ้า

ป้อนคำอธิบายรูปภาพที่นี่

ฉันพยายามเรียกใช้คำค้นหา "ละลายยุบรวม" สองสามบรรทัดตาม:

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

ด้วยทุกสิ่งที่ฉันได้ลองฉันจะลงเอยด้วยฟีเจอร์เดียว ( ST_Union) หรือเรขาคณิตดั้งเดิมของฉัน ( ST_DumpจากST_Union)

อาจเป็นไปได้ที่จะทำเช่นนี้ด้วยWITH RECURSIVEเวทมนตร์บางชนิด?


บางสิ่งดูไม่ถูกต้องด้วย "(ST_Dump (st_union)). geom"
Martin F

เนื่องจากเขาไม่ได้ใช้นามแฝง ST_Union (geom) ชื่อของ geom ใหม่จึงสืบทอดชื่อของฟังก์ชันเพื่อให้เป็น st_union นั่นเป็นเหตุผลที่มันดูตลกนิดหน่อย
LR1234567

คำตอบ:


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 |   grouplist   
----+---------------
  1 | {1,2,3,4}
 11 | {11,12,13,14}
(2 rows)

ฉันคิดว่ารหัสนี้อาจจะง่ายกว่านี้ถ้าประเภทรูปทรงเรขาคณิตที่สนับสนุนการแฮชใน PostgreSQL (เมื่อคุณเขียน RCTE ที่ง่ายกว่าซึ่งไม่เกี่ยวข้องกับการสะสมอาร์เรย์ของรหัสคุณจะได้รับข้อผิดพลาด "คอลัมน์ประเภทข้อมูลทั้งหมดจะต้อง hashable") คำขอปรับปรุงเล็กน้อยสำหรับฉัน
Paul Ramsey

นี่เป็นวิธีที่ยอดเยี่ยมจริงๆ ฉันสังเกตเห็นผลลัพธ์แปลก ๆ เมื่อฉันนำไปใช้กับชุดทดสอบที่ใหญ่ขึ้น ฉันจะดูว่าฉันสามารถลดปัญหาให้เป็นตัวอย่างง่ายๆ 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 s
dbaston

เพิ่มนี้ข้อมูลการทดสอบจะทำให้รหัสที่ซ้ำกันในอาร์เรย์:grouplist insert into lines (id, geom) values ( 15, 'LINESTRING(0 0, 10 10)');การเปลี่ยนarray_agg(id)ฟังก์ชั่นกลับเป็นarray_agg(DISTINCT id)ดูเหมือนว่าจะแก้ไขปัญหาได้
dbaston

นี่เป็นทางออกที่ดีดังนั้นตอนนี้เราจะเก็บรูปทรงเรขาคณิตไว้ในตารางเพื่อให้เห็นเส้นเชื่อมต่อได้อย่างไร
zakaria mouqcit

6

นี่คือวิธีการที่ใช้ตารางชั่วคราวเพื่อรวมกลุ่มที่เพิ่มขึ้นพร้อมกัน ฉันไม่สนใจวิธีการตารางชั่วคราว แต่ดูเหมือนว่าจะทำงานได้ดีเมื่อจำนวนบรรทัดเพิ่มขึ้น (ฉันมี 1.2 M บรรทัดในอินพุตของฉัน)

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;

ทำงานได้อย่างสมบูรณ์แบบ
zakaria mouqcit

@zakariamouqcit ดีใจที่ได้ทำงานกับคุณ! ฉันเขียนคำตอบนี้ก่อนที่ฉันจะเขียนST_ClusterIntersectingฟังก์ชันใน PostGIS หากข้อมูลของคุณมีขนาดเล็กพอที่จะใส่ลงในหน่วยความจำฉันขอแนะนำให้ตรวจสอบวิธีการแก้ปัญหาที่มีประสิทธิภาพมากขึ้น
dbaston

ค้นหาคำถามนี้พาฉันมาที่นี่ พยายามซ้ำแล้วซ้ำอีกและ st_cluster การตรวจพบ แต่พบว่า st_clusterDBScan เป็นคนที่ฉลาดที่สุด ในกรณีที่มีคนอื่นมาที่นี่ด้วย postgis.net/docs/manual-dev/ST_ClusterDBSCAN.html
D_C

เห็นด้วย ST_ClusterDBSCAN เกือบจะเป็นวิธีที่ดีที่สุดสำหรับ PostGIS 2.3+
dbaston
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.