Nhóm các linestrings được kết nối trong PostGIS?


12

Tôi có một bảng các đường phố tôi đã chọn dựa trên một tập hợp các thuộc tính (giả sử nó speed_limit < 25). Có những nhóm đường phố nằm liền kề nhau; Tôi muốn nhóm các nhóm linestrings được kết nối này vào GeometryCollections. Trong hình ảnh bên dưới, sẽ có hai GeometryCollections: một với các dòng màu đỏ và một với các dòng màu xanh.

nhập mô tả hình ảnh ở đây

Tôi đã thử chạy một vài truy vấn "hòa tan, hủy kết nối" dọc theo dòng:

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

Với tất cả mọi thứ tôi đã thử, tôi sẽ kết thúc bằng một tính năng ( ST_Union) hoặc hình học ban đầu ( ST_Dumpcủa ST_Union).

Có lẽ nó có thể làm điều này với một số loại WITH RECURSIVEphép thuật?


Một cái gì đó không phù hợp với "(ST_Dump (st_union)). Geom"
Martin F

Vì anh ta không bí danh ST_Union (geom), tên của geom mới được thừa hưởng tên của hàm để trở thành st_union. Đó là lý do tại sao nó trông hơi buồn cười
LR1234567

Câu trả lời:


19

Vì vậy, ví dụ. Đây là một bảng đơn giản với hai nhóm cạnh được kết nối:

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

Bây giờ, đây là một hàm đệ quy, với id của một cạnh, tích lũy tất cả các cạnh chạm vào:

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

Điều đó chỉ khiến chúng ta cần tìm, sau khi mỗi nhóm được tích lũy, id của một cạnh chưa phải là một phần của một nhóm. Mà, bi thảm thay, đòi hỏi một truy vấn đệ quy thứ hai.

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;   

Mà kết hợp lại trả về một tập hợp đẹp với id hạt giống và mỗi nhóm nó tích lũy. Tôi để nó như một bài tập cho người đọc để biến các mảng id trở lại thành một truy vấn để tạo hình học cho ánh xạ.

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

Tôi nghĩ mã này có thể đơn giản hơn, nếu loại hình học hỗ trợ băm trong PostgreSQL (khi bạn viết RCTE đơn giản hơn không liên quan đến việc tích lũy mảng id, bạn sẽ gặp lỗi "Tất cả các kiểu dữ liệu cột phải được băm"), vì vậy có một yêu cầu tăng cường ít cho tôi.
Paul Ramsey

Đây là một cách tiếp cận thực sự tuyệt vời. Tôi nhận thấy một số kết quả kỳ lạ khi tôi áp dụng nó cho một bộ thử nghiệm lớn hơn; Tôi sẽ xem liệu tôi có thể giảm vấn đề xuống một ví dụ đơn giản không. 100 dòng: 85 cụm, cụm lớn nhất = 3, 0,03 s //// 200 dòng: 144 cụm, cụm lớn nhất = 9, 0,08 s //// 300 dòng: 180 cụm, cụm lớn nhất = 51, 0,16 s /// / 400 dòng: 188 cụm, cụm lớn nhất = 41, 0,27 s //// 500 dòng: 176 cụm, cụm lớn nhất = 112, 0,56 s //// 600 dòng: 143 cụm, cụm lớn nhất = 449, 1,0 s // // 650 dòng: 133 cụm, cụm lớn nhất = 7601, 6,8 giây
dbaston

Thêm phần này vào dữ liệu kiểm tra sẽ gây ra các ID trùng lặp trong grouplistmảng : insert into lines (id, geom) values ( 15, 'LINESTRING(0 0, 10 10)');. Thay đổi array_agg(id)trong hàm trở lại array_agg(DISTINCT id)dường như để giải quyết vấn đề.
dbaston

Đây là một giải pháp tốt, vậy bây giờ làm thế nào chúng ta có thể lưu trữ hình học trong một bảng để chúng ta có thể nhìn thấy các đường được kết nối?
zakaria mouqcit

6

Đây là một cách tiếp cận sử dụng bảng tạm thời để tổng hợp các cụm lại với nhau. Tôi không thực sự quan tâm đến cách tiếp cận bảng tạm thời, nhưng điều này dường như hoạt động khá tốt khi số lượng dòng tăng lên (tôi có 1,2 M dòng trong đầu vào của mình).

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;

hoạt động hoàn hảo
zakaria mouqcit

@zakariamouqcit Rất vui vì điều này đã làm việc cho bạn! Tôi đã viết câu trả lời này trước khi tôi viết ST_ClusterIntersectingchức năng trong PostGIS. Nếu dữ liệu của bạn đủ nhỏ để vừa với bộ nhớ, tôi khuyên bạn nên kiểm tra xem có giải pháp hiệu quả hơn không.
dbaston

tìm kiếm câu hỏi này đã đưa tôi đến đây Đã thử lặp lại và st_clusterintersecting nhưng thấy st_clusterDBScan là apt nhất. Trong trường hợp bất cứ ai khác được đưa đến đây quá. postgis.net/docs/manual-dev/ST_ClusterDBSCAN.html
D_C

Đồng ý, ST_ClusterDBSCAN hầu như luôn là cách tốt nhất để sử dụng PostGIS 2.3+
dbaston
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.