แบ่งบรรทัดออกเป็นชุดย่อยที่ไม่ทับซ้อนกันตามคะแนน


10

กำหนดตารางที่มีเรขาคณิตเส้นและหนึ่งจุดขึ้นไปที่จัดชิดกับบรรทัดนี้ในตารางแยกกันฉันต้องการแยกแต่ละบรรทัดด้วยจุดตัดกันอย่างน้อยหนึ่งจุดในแต่ละตำแหน่งที่เส้นตัดกับจุด

ตัวอย่างเช่นมีเส้น L ซึ่งมีจุดตัดกันสามจุดคือ A, B และ C ตามลำดับตามเรขาคณิตของเส้น ฉันต้องการคืน L เป็นรูปทรงที่แตกต่างกันสี่แบบ: จากจุดเริ่มต้นของ L ถึง A, จาก A ถึง B ตาม L, จาก B ถึง C ตาม L และจาก C ถึงจุดสิ้นสุดของ L

ในอดีตฉันเคยใช้หุ่นดีสำหรับงานนี้ซึ่งเป็นปัญหาการอ้างอิงเชิงเส้น ( http://sgillies.net/blog/1040/shapely-recipes/ ) อย่างไรก็ตามการทำเช่นนี้จะไม่สามารถทำได้ในกรณีนี้ซึ่งมีหลายล้านบรรทัดและคะแนน ฉันกำลังมองหาวิธีแก้ปัญหาด้วยการใช้ PostgreSQL / PostGIS แทน

โปรดทราบว่าคะแนนจะถูก จำกัด ให้อยู่ในบรรทัด นอกจากนี้จุดที่ถูกต้องสามารถอยู่ในจุดเริ่มต้นหรือจุดสิ้นสุดของบรรทัดซึ่งในกรณีที่ไม่จำเป็นต้องแยกบรรทัด (เว้นแต่มีจุดอื่น ๆ ที่ไม่สอดคล้องกับจุดเริ่มต้นหรือจุดสิ้นสุดของบรรทัดเดียวกัน) บรรทัดย่อยต้องรักษาทิศทางและคุณลักษณะของตนเองไว้ แต่คุณลักษณะของคุณลักษณะจุดไม่สำคัญ

คำตอบ:


7

ST_Splitฟังก์ชั่น PostGIS น่าจะเป็นสิ่งที่คุณต้องการ

ขณะนี้ PostGIS 2.2+ สนับสนุนรูปทรงเรขาคณิตหลายรายการใน ST_Split

สำหรับ PostGIS เวอร์ชันเก่าอ่านได้ที่:


หากต้องการแยกบรรทัดเดียวด้วยหลาย ๆ จุดคุณสามารถใช้ฟังก์ชัน plpgsql wrapper แบบหลายจุดนี้ ฉันได้ลดความซับซ้อนลงไปเพียงแค่ในกรณี "เส้นแบ่ง (หลาย) กับจุด (หลายจุด)" ด้านล่าง:

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;

จากนั้นเมื่อต้องการสร้างรูปทรงเรขาคณิตแบบหลายจุดเพื่อตัดใช้ST_Collectและสร้างด้วยตนเองจากอินพุต:

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

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

หรือรวมจากแบบสอบถามย่อย:

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

ฉันพยายาม ST_Split เพื่อเริ่มต้นและรู้สึกประหลาดใจเมื่อพบว่ามันไม่ได้รับเรขาคณิตหลายจุด ฟังก์ชั่นของคุณดูเหมือนจะเติมเต็มช่องว่างนั้น แต่น่าเสียดายที่มันคืนค่า NULL สำหรับกรณีตัวอย่างหลายจุด (มันทำงานได้ดีบนจุด (เดี่ยว)) อย่างไรก็ตามฉันเปลี่ยนIF blade_geometry_type ไม่ใช่ ILIKE '% LINESTRING' จากนั้นเป็นIF blade_geometry_type ILIKE '% LINESTRING' แล้วในฟังก์ชันของคุณและได้รับผลลัพธ์ 'GEOMETRYCOLECTION' ที่แน่นอน ฉันยังคงใหม่พอสมควรกับ PostGIS อย่างไรก็ตามการดัดแปลงนั้นสมเหตุสมผลหรือไม่
alphabetasoup

ขออภัยควรได้รับIF geom_geometry_type NOT ILIKE '%LINESTRING' THEN- ฉันได้แก้ไขแล้ว
rcoup

1
อ่าฉันเข้าใจแล้ว ขอบคุณนี่เป็นทางออกที่ดี คุณควรแนะนำสิ่งนี้เป็นผลงานให้กับ ST_Split เพื่อให้สามารถจัดการกับหลายบรรทัดและหลายจุดได้หากนี่ไม่ได้อยู่ในขั้นตอนของ PostGIS
alphabetasoup

3
ST_Splitสนับสนุนใบ * หลายในpostgis 2.2และเหนือpostgis.net/docs/ST_Split.html
ราฟาเอล

3

อัปเกรดเป็น PostGIS 2.2ซึ่งST_Splitถูกขยายเพื่อรองรับการแยกโดยMultiline , Multipoint หรือขอบเขตรูปหลายเหลี่ยม (หลาย)

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

อันนี้ยอดเยี่ยม
alphabetasoup

สิ่งนี้ใช้ไม่ได้กับ geom ที่ซับซ้อนของฉัน: gist.github.com/ideamotor/7bd7cdee15f410ce12f3aa14ebf70177
ideamotor

มันทำงานกับ ST_Snap, ala trac.osgeo.org/postgis/ticket/2192
ideamotor

2

ฉันยังไม่ได้คำตอบทั้งหมดสำหรับคุณ แต่ ST_Line_Locate_Point ใช้เส้นและจุดเป็นข้อโต้แย้งและส่งกลับตัวเลขระหว่าง 0 ถึง 1 แสดงระยะทางตามเส้นไปยังตำแหน่งที่ใกล้กับจุดมากที่สุด

ST_Line_Substring รับบรรทัดและตัวเลขสองตัวโดยมีค่าระหว่าง 0 ถึง 1 เป็นอาร์กิวเมนต์ ตัวเลขแสดงตำแหน่งในบรรทัดเป็นระยะทางแบบเศษส่วน ฟังก์ชันจะคืนค่าเซกเมนต์ของบรรทัดที่รันระหว่างสองตำแหน่งเหล่านั้น

เมื่อทำงานกับทั้งสองฟังก์ชั่นคุณควรจะสามารถบรรลุสิ่งที่คุณต้องการได้


ขอบคุณสำหรับสิ่งนี้. ฉันได้แก้ไขปัญหานี้โดยใช้เทคนิคของคุณเช่นเดียวกับที่จาก @rcoup ฉันให้คำตอบที่ได้รับการยอมรับกับเขาเนื่องจากฟังก์ชั่นที่ควรทำให้มันง่ายสำหรับคนอื่น หากคนอื่น ๆ ต้องการลงเส้นทางนี้ฉันสร้างตารางชั่วคราวของบรรทัดที่มีคะแนนอยู่กับพวกเขาโดยมีแถวสำหรับแต่ละบรรทัดและหยุดหนึ่งที่อยู่บนนั้น ฉันเพิ่มคอลัมน์สำหรับเอาต์พุตของ ST_Line_Locate_Point (line.geom, pt.geom) AS L และฟังก์ชั่นหน้าต่าง: rank () OVER PARTITION โดย line.id ORDER BY LR) จากนั้นให้ออกจากด้านซ้ายเข้าร่วมตารางชั่วคราว a, กับตัวเอง, b, โดยที่ a.id = b.id และ a.LR = b.LR + 1 (ต่อ)
ตัวอักษร

(ต่อ) การรวมภายนอกอนุญาตให้ CASE เมื่อฟิลด์การรวมเป็น null ซึ่งในกรณีนี้ ST_Line_Substring จากจุดหนึ่งไปยังจุดสิ้นสุดของบรรทัดมิฉะนั้น ST_Line_Substring จากการอ้างอิงเชิงเส้นของจุดแรกไปยังการอ้างอิงเชิงเส้นของจุดที่สอง (มีอันดับสูงกว่า) การรับ [start] LA เซ็กเมนต์จะถูกดำเนินการด้วย SELECT ที่สองเพียงแค่เลือกกลุ่มที่มีอันดับ 1 และคำนวณ ST_Line_Substring จาก ST_StartPoint ของบรรทัดไปยังการอ้างอิงเชิงเส้นของจุดตัดกัน แสดงป๊อปอัพเหล่านี้ในตารางโดยจดจำที่จะให้บรรทัด. id และvoilà ไชโย
alphabetasoup

คุณช่วยโพสต์คำตอบนี้เป็นคำตอบในรหัสได้ไหม? ฉันต้องการดูตัวเลือกนั้นรวมถึงฉันเพิ่งสมัครใหม่กับ SQL
Phil Donovan

1
@PhilDonovan: เสร็จแล้ว
alphabetasoup

2

ฉันถูกขอมาสองครั้งแล้วขออภัยในความล่าช้า สิ่งนี้ไม่น่าจะถือได้ว่าเป็นคำตอบสั้น ๆ ; ฉันเขียนมันลงไปเล็กน้อยเมื่อถึงช่วงโค้งการเรียนรู้มากกว่าที่ฉันเป็นอยู่ในปัจจุบัน เคล็ดลับใด ๆ ยินดีต้อนรับแม้โวหาร

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

ฉันต้องการขยายคำตอบข้างต้นจากมุมมองของผู้เริ่มต้น ในสถานการณ์สมมตินี้คุณมีชุดของคะแนนและคุณดูเพื่อใช้เป็น "เบลด" เพื่อตัดบรรทัดเป็นเซ็กเมนต์ ตัวอย่างทั้งหมดนี้อนุมานว่าคุณหักคะแนนของคุณเป็นบรรทัดแรกและคะแนนนั้นมีแอตทริบิวต์ ID ที่ไม่ซ้ำกันจากบรรทัดที่จัดชิด ฉันใช้ 'column_id "เพื่อแสดง ID เฉพาะของบรรทัด

ก่อนอื่นคุณต้องการจัดกลุ่มคะแนนของคุณเป็นหลายจุดเมื่อมีมากกว่าหนึ่งเบลดลงบนเส้น มิฉะนั้นฟังก์ชันsplit_line_multipointจะทำหน้าที่เหมือนฟังก์ชัน ST_Split ซึ่งไม่ใช่ผลลัพธ์ที่คุณต้องการ

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;

จากนั้นคุณต้องการแยกเครือข่ายของคุณตามหลายจุดเหล่านี้

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;


ทำซ้ำขั้นตอนที่ 1 และ 2 ด้วยเส้นของคุณที่มีจุดตัดกันเพียงจุดเดียว ในการทำเช่นนี้คุณควรอัปเดตรหัสจากขั้นตอนที่ 1 ถึง 'HAVING COUNT (*) = 1' เปลี่ยนชื่อตารางตาม


จากนั้นสร้างตารางเส้นที่ซ้ำกันและลบรายการที่มีคะแนนอยู่

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


สุดท้ายเข้าร่วมสามตารางของคุณด้วยกันโดยใช้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!

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