Tách các dòng thành các tập con không chồng chéo dựa trên các điểm


10

Đưa ra một bảng có hình dạng đường và một hoặc nhiều điểm được ngắt vào dòng này trong một bảng riêng biệt, tôi muốn chia mỗi dòng với một hoặc nhiều điểm giao nhau tại mỗi vị trí mà đường thẳng giao nhau với một điểm.

Ví dụ: có một đường thẳng, L, với ba điểm giao nhau, A, B và C theo thứ tự dọc theo hình dạng đường. Tôi muốn trả lại L là bốn hình học riêng biệt: từ đầu L đến A, từ A đến B dọc theo L, từ B đến C dọc theo L và từ C đến cuối của L

Trong quá khứ tôi đã sử dụng shapely cho nhiệm vụ này, đó là một vấn đề tham chiếu tuyến tính ( http://sgillies.net/blog/1040/shapely-recipes/ ). Tuy nhiên, điều này sẽ không thể thực hiện được trong trường hợp này, vốn có nhiều triệu dòng và điểm. Thay vào đó, tôi đang tìm kiếm một giải pháp bằng PostgreSQL / PostGIS.

Lưu ý rằng các điểm bị hạn chế nằm trên một dòng. Hơn nữa, một điểm có thể nằm ở đầu hoặc cuối của dòng một cách hợp lệ, trong trường hợp đó, dòng không cần phải phân tách (trừ khi có những điểm khác không trùng với điểm bắt đầu hoặc điểm kết thúc của cùng dòng). Các dòng tập hợp con cần giữ lại hướng và thuộc tính của chúng, nhưng các thuộc tính của các tính năng điểm không quan trọng.

Câu trả lời:


7

Hàm ST_Split PostGIS có lẽ là những gì bạn muốn.

PostGIS 2.2+ hiện hỗ trợ nhiều hình học * trong ST_Split.

Đối với các phiên bản cũ hơn của PostGIS, hãy đọc tiếp:


Để có được một dòng chia cho nhiều điểm, bạn có thể sử dụng một cái gì đó giống như hàm plpgsql đa điểm này . Tôi đã đơn giản hóa nó chỉ trong trường hợp "chia (nhiều) dòng với (nhiều) điểm" bên dưới:

DROP FUNCTION IF EXISTS split_line_multipoint(input_geom geometry, blade geometry);
CREATE FUNCTION split_line_multipoint(input_geom geometry, blade geometry)
  RETURNS geometry AS
$BODY$
    -- this function is a wrapper around the function ST_Split 
    -- to allow splitting multilines with multipoints
    --
    DECLARE
        result geometry;
        simple_blade geometry;
        blade_geometry_type text := GeometryType(blade);
        geom_geometry_type text := GeometryType(input_geom);
    BEGIN
        IF blade_geometry_type NOT ILIKE 'MULTI%' THEN
            RETURN ST_Split(input_geom, blade);
        ELSIF blade_geometry_type NOT ILIKE '%POINT' THEN
            RAISE NOTICE 'Need a Point/MultiPoint blade';
            RETURN NULL;
        END IF;

        IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN
            RAISE NOTICE 'Need a LineString/MultiLineString input_geom';
            RETURN NULL;
        END IF;

        result := input_geom;           
        -- Loop on all the points in the blade
        FOR simple_blade IN SELECT (ST_Dump(ST_CollectionExtract(blade, 1))).geom
        LOOP
            -- keep splitting the previous result
            result := ST_CollectionExtract(ST_Split(result, simple_blade), 2);
        END LOOP;
        RETURN result;
    END;
$BODY$
LANGUAGE plpgsql IMMUTABLE;

-- testing
SELECT ST_AsText(split_line_multipoint(geom, blade))
    FROM (
        SELECT ST_GeomFromText('Multilinestring((-3 0, 3 0),(-1 0, 1 0))') AS geom,
        ST_GeomFromText('MULTIPOINT((-0.5 0),(0.5 0))') AS blade
        --ST_GeomFromText('POINT(-0.5 0)') AS blade
    ) AS T;

Sau đó, để tạo một hình dạng đa điểm để cắt, sử dụng ST_Collect và tạo thủ công từ đầu vào:

SELECT ST_AsText(ST_Collect(
  ST_GeomFromText('POINT(1 2)'),
  ST_GeomFromText('POINT(-2 3)')
));

st_astext
----------
MULTIPOINT(1 2,-2 3)

Hoặc tổng hợp nó từ một truy vấn con:

SELECT stusps,
  ST_Multi(ST_Collect(f.the_geom)) as singlegeom
FROM (SELECT stusps, (ST_Dump(the_geom)).geom As the_geom
      FROM somestatetable ) As f
GROUP BY stusps

Tôi đã thử ST_Split để bắt đầu và rất ngạc nhiên khi tôi thấy rằng nó không chấp nhận hình học đa điểm. Hàm của bạn dường như lấp đầy khoảng trống đó, nhưng thật không may, nó đang trả về NULL cho trường hợp đa điểm mẫu. (Nó hoạt động tốt trên các đơn) dấu chấm (.) Tuy nhiên, tôi đã thay đổi NẾU KHÔNG blade_geometry_type iLike '% LineString' THEN để NẾU blade_geometry_type iLike '% LineString' THEN trong chức năng của bạn và nhận được kết quả 'GEOMETRYCOLLECTION' dự kiến và chính xác. Tuy nhiên, tôi vẫn còn khá mới đối với PostGIS, vậy sửa đổi đó có hợp lý không?
alphabetasoup

Xin lỗi, đáng lẽ ra IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN- tôi đã chỉnh sửa nó.
RCoup

1
Ah tôi thấy. Cảm ơn, đây là một giải pháp tuyệt vời. Bạn nên đề xuất đây là một đóng góp cho ST_Split để nó có thể xử lý đa dòng và đa điểm, nếu điều này chưa có trong đường ống PostGIS.
alphabetasoup

3
ST_Splithỗ trợ nhiều lưỡi * trong postgis 2.2và trên postgis.net/docs/ST_Split.html
raphael

3

Nâng cấp lên PostGIS 2.2 , trong đó ST_Split đã được mở rộng để hỗ trợ phân tách bởi một ranh giới đa tuyến, đa điểm hoặc (đa).

postgis=# SELECT postgis_version(),
                  ST_AsText(ST_Split('LINESTRING(0 0, 2 0)', 'MULTIPOINT(0 0, 1 0)'));
-[ RECORD 1 ]---+------------------------------------------------------------
postgis_version | 2.2 USE_GEOS=1 USE_PROJ=1 USE_STATS=1
st_astext       | GEOMETRYCOLLECTION(LINESTRING(1 0,2 0),LINESTRING(0 0,1 0))

Điều này thật tuyệt vời.
alphabetasoup

Điều này không hoạt động đối với geom phức tạp của tôi: gist.github.com/ideamotor/7bd7cdee15f410ce12f3aa14ebf70177
ideamotor 18/07/2016

nó hoạt động với ST_Snap, ala trac.osgeo.org/postgis/ticket/2192
ideamotor

2

Tôi chưa có toàn bộ câu trả lời cho bạn, nhưng ST_Line_Locate_Point lấy một dòng và một điểm làm đối số và trả về một số từ 0 đến 1 biểu thị khoảng cách dọc theo đường thẳng đến vị trí gần điểm nhất.

ST_Line_Subopes lấy một dòng và hai số, mỗi số từ 0 đến 1, làm đối số. Các số đại diện cho các vị trí trên dòng dưới dạng khoảng cách phân số. Hàm trả về đoạn đường chạy giữa hai vị trí đó.

Bằng cách làm việc với hai chức năng này, bạn sẽ có thể đạt được những gì bạn muốn làm.


Cảm ơn vì điều đó. Tôi thực sự đã giải quyết vấn đề này bằng cách sử dụng kỹ thuật của bạn cũng như từ @rcoup. Tôi đã cho anh ta câu trả lời được chấp nhận do chức năng sẽ giúp người khác dễ dàng. Nếu những người khác muốn đi theo con đường này, tôi đã tạo một bảng tạm thời gồm các dòng có điểm trên đó, với một hàng cho mỗi dòng và một điểm dừng trên đó. Tôi đã thêm các cột cho đầu ra của ST_Line_Locate_Point (line.geom, pt.geom) NHƯ L và một chức năng cửa sổ: xếp hạng () TRÊN THAM GIA B lineNG dòng.id ORDER BY LR). Sau đó LEFT OUTER JOIN bảng tạm thời, một, với chính nó, b, nơi a.id = b.id và a.LR = b.LR + 1 (tiếp theo)
alphabetasoup

. (với thứ hạng cao hơn). Bắt đầu phân đoạn [bắt đầu] LA sau đó được thực hiện với CHỌN thứ hai, chỉ cần chọn ra các phân đoạn có thứ hạng 1 và tính ST_Line_Sub chuỗi từ ST_StartPoint của đường thẳng đến tham chiếu tuyến tính của điểm giao nhau. Đặt chúng trong bảng, nhớ giữ dòng.id và voilà. Chúc mừng.
alphabetasoup

Bạn có thể gửi câu trả lời này như một câu trả lời trong mã không? Tôi muốn xem xét tùy chọn đó cũng như tôi là một người mới làm quen với SQL.
Phil Donovan

1
@PhilDonovan: xong.
alphasoup

2

Tôi đã được yêu cầu điều này hai lần bây giờ, rất xin lỗi vì sự chậm trễ. Đây không chắc được coi là một giải pháp ngắn gọn; Tôi đã viết nó khi xa hơn một chút về đường cong học tập so với hiện tại. Bất kỳ lời khuyên chào mừng, ngay cả những người phong cách.

--Inputs:
--walkingNetwork = Line features representing edges pedestrians can walk on
--stops = Bus stops
--NOTE: stops.geom is already constrained to be coincident with line features
--from walkingNetwork. They may be on a vertex or between two vertices.

--This series of queries returns a version of walkingNetwork, with edges split
--into separate features where they intersect stops.

CREATE TABLE tmp_lineswithstops AS (
    WITH subq AS (
        SELECT
        ST_Line_Locate_Point(
            roads.geom,
            ST_ClosestPoint(roads.geom, stops.geom)
        ) AS LR,
        rank() OVER (
            PARTITION BY roads.gid
            ORDER BY ST_Line_Locate_Point(
                roads.geom,
                ST_ClosestPoint(roads.geom, stops.geom)
            )
        ) AS LRRank,
        ST_ClosestPoint(roads.geom, stops.geom),
        roads.*
        FROM walkingNetwork AS roads
        LEFT OUTER JOIN stops
        ON ST_Distance(roads.geom, stops.geom) < 0.01
        WHERE ST_Equals(ST_StartPoint(roads.geom), stops.geom) IS false
        AND ST_Equals(ST_EndPoint(roads.geom), stops.geom) IS false
        ORDER BY gid, LRRank
    )
    SELECT * FROM subq
);

-- Calculate the interior edges with a join
--If the match is null, calculate the line to the end
CREATE TABLE tmp_testsplit AS (
    SELECT
    l1.gid,
    l1.geom,
    l1.lr AS LR1,
    l1.st_closestpoint AS LR1geom,
    l1.lrrank AS lr1rank,
    l2.lr AS LR2,
    l2.st_closestpoint AS LR2geom,
    l2.lrrank AS lr2rank,
    CASE WHEN l2.lrrank IS NULL -- When the point is the last along the line
        THEN ST_Line_Substring(l1.geom, l1.lr, 1) --get the substring line to the end
        ELSE ST_Line_Substring(l1.geom, l1.lr, l2.lr) --get the substring between the two points
    END AS sublinegeom
    FROM tmp_lineswithstops AS l1
    LEFT OUTER JOIN tmp_lineswithstops AS l2
    ON l1.gid = l2.gid
    AND l2.lrrank = (l1.lrrank + 1)
);

--Calculate the start to first stop edge
INSERT INTO tmp_testsplit (gid, geom, lr1, lr1geom, lr1rank, lr2, lr2geom, lr2rank, sublinegeom)
SELECT gid, geom,
0 as lr1,
ST_StartPoint(geom) as lr1geom,
0 as lr1rank,
lr AS lr2,
st_closestpoint AS lr2geom,
lrrank AS lr2rank,
ST_Line_Substring(l1.geom, 0, lr) AS sublinegeom --Start to point
FROM tmp_lineswithstops AS l1
WHERE l1.lrrank = 1;

--Now match back to the original road features, both modified and unmodified
CREATE TABLE walkingNetwork_split AS (
    SELECT
    roadssplit.sublinegeom,
    roadssplit.gid AS sgid, --split-gid
    roads.*
    FROM tmp_testsplit AS roadssplit
    JOIN walkingNetwork AS r
    ON r.gid = roadssplit.gid
    RIGHT OUTER JOIN walkingNetwork AS roads --Original edges with null if unchanged, original edges with split geom otherwise
    ON roads.gid = roadssplit.gid
);

--Now update the necessary columns, and drop the temporary columns
--You'll probably need to work on your own length and cost functions
--Here I assume it's valid to just multiply the old cost by the fraction of
--the length the now-split line represents of the non-split line
UPDATE walkingNetwork_split
SET geom = sublinegeom,
lengthz = lengthz*(ST_Length(sublinegeom)/ST_Length(geom)),
walk_seconds_ft = walk_seconds_ft*(ST_Length(sublinegeom)/ST_Length(geom)),
walk_seconds_tf = walk_seconds_tf*(ST_Length(sublinegeom)/ST_Length(geom))
WHERE sublinegeom IS NOT NULL
AND ST_Length(sublinegeom) > 0;
ALTER TABLE walkingNetwork_split
DROP COLUMN sublinegeom,
DROP COLUMN sgid;

--Drop intermediate tables
--You probably could use actual temporary tables;
--I prefer to have a sanity check at each stage
DROP TABLE IF EXISTS tmp_testsplit;
DROP TABLE IF EXISTS tmp_lineswithstops;

--Assign the edges a new unique id, so we can use this as source/target columns in pgRouting
ALTER TABLE walkingNetwork_split
DROP COLUMN IF EXISTS fid;
ALTER TABLE walkingNetwork_split
ADD COLUMN fid INTEGER;
CREATE SEQUENCE roads_seq;
UPDATE walkingNetwork_split
SET fid = nextval('roads_seq');
ALTER TABLE walkingNetwork_split
ADD PRIMARY KEY ("fid");

0

Tôi muốn mở rộng các câu trả lời ở trên từ quan điểm của người mới bắt đầu. Trong kịch bản này, bạn có một loạt các điểm và bạn xem để sử dụng chúng như một "lưỡi kiếm" để cắt một đường thành các đoạn. Toàn bộ ví dụ này giả định rằng trước tiên bạn đã chộp các điểm của mình vào dòng và các điểm đó có thuộc tính ID duy nhất từ ​​dòng được chụp. Tôi sử dụng 'cột_id "để thể hiện ID duy nhất của dòng.

Đầu tiên , bạn muốn nhóm các điểm của mình thành nhiều điểm khi nhiều hơn một lưỡi rơi trên một đường. Mặt khác, hàm split_line_multipoint hoạt động giống như hàm ST_Split, đây không phải là kết quả bạn muốn.

CREATE TABLE multple_terminal_lines AS
SELECT ST_Multi(ST_Union(the_geom)) as the_geom, a.matched_alid
FROM    point_table a
        INNER JOIN
        (
            SELECT  column_id
            FROM    point_table
            GROUP   BY column_id
            HAVING  COUNT(*) > 1
        ) b ON a.column_id = b.column_id
GROUP BY a.column_id;

Sau đó , bạn muốn phân chia mạng của mình dựa trên các đa điểm này.

CREATE TABLE split_multi AS
SELECT (ST_Dump(split_line_multipoint(ST_Snap(a.the_geometry, b.the_geom, 0.00001),b.the_geom))).geom as the_geom
FROM line_table a
JOIN multple_terminal_lines b 
ON a.column_id = b.column_id;


Lặp lại bước 1 và 2 với các dòng của bạn chỉ có một điểm giao nhau. Để thực hiện việc này, bạn nên cập nhật mã từ bước 1 thành 'HAVING COUNT (*) = 1'. Đổi tên bảng cho phù hợp.


Tiếp theo , tạo một bảng dòng trùng lặp và xóa các mục có điểm trên chúng.

CREATE TABLE line_dup AS
SELECT * FROM line_table;
-- Delete shared entries
DELETE FROM line_dup
WHERE column_id in (SELECT DISTINCT column_id FROM split_single) OR column_id in (SELECT DISTINCT column_id FROM split_multi) ;


Cuối cùng , kết hợp ba bảng của bạn với nhau bằng cách sử dụng UNION ALL:

CREATE TABLE line_filtered AS 
SELECT the_geom
FROM split_single
UNION ALL 
SELECT the_geom
FROM split_multi
UNION ALL 
SELECT the_geom
FROM line_dup;

BAM!

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.