Pisahkan garis menjadi himpunan bagian yang tidak tumpang tindih berdasarkan poin

10

Diberikan tabel dengan geometri garis, dan satu atau lebih titik yang terpotong ke garis ini dalam tabel terpisah, saya ingin membagi setiap garis dengan satu atau lebih titik berpotongan di setiap lokasi di mana garis memotong titik.

Misalnya, ada garis, L, dengan tiga titik berpotongan, A, B, dan C dalam urutan sepanjang garis geometri. Saya ingin mengembalikan L sebagai empat geometri yang berbeda: dari awal L ke A, dari A ke B di sepanjang L, dari B ke C di sepanjang L, dan dari C ke ujung L.

Di masa lalu saya telah menggunakan rupawan untuk tugas ini, yang merupakan masalah referensi linear ( http://sgillies.net/blog/1040/shapely-recipes/ ). Namun, ini tidak dapat dipraktikkan dalam kasus ini, yang memiliki jutaan garis dan titik. Sebagai gantinya, saya mencari solusi menggunakan PostgreSQL / PostGIS.

Perhatikan bahwa poin dibatasi pada garis. Lebih jauh, suatu titik dapat secara sah berada pada awal atau akhir suatu garis, dalam hal mana garis tersebut tidak perlu dipisah (kecuali ada titik lain yang tidak bersamaan dengan titik awal atau titik akhir garis yang sama). Garis subset perlu mempertahankan arah dan atributnya, tetapi atribut fitur titik tidak menjadi masalah.

alphabetasoup
sumber

Jawaban:

7

Fungsi ST_Split PostGIS mungkin adalah yang Anda inginkan.

PostGIS 2.2+ sekarang mendukung geometri Multi * di ST_Split.

Untuk versi PostGIS yang lebih lama, baca terus:


Untuk mendapatkan satu baris garis dengan beberapa titik, Anda bisa menggunakan sesuatu seperti fungsi plpgsql bungkus multipoint ini . Saya telah menyederhanakannya hanya untuk kasus "split (multi) lines dengan (multi)" di bawah ini:

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;

Kemudian untuk membuat geometri multipoint untuk dipotong, gunakan ST_Collect dan buat secara manual dari input:

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

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

Atau agregat dari subquery:

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
rcoup
sumber
Saya mencoba ST_Split untuk memulai, dan terkejut ketika saya menemukan bahwa itu tidak menerima geometri multipoint. Fungsi Anda tampaknya mengisi celah itu, tetapi sayangnya itu mengembalikan NULL untuk contoh kasus multi-titik. (Ia bekerja baik di tunggal) titik (.) Namun, saya berubah JIKA blade_geometry_type TIDAK ILIKE '% LineString' THEN ke IF blade_geometry_type ILIKE '% LineString' THEN dalam fungsi dan mendapat diharapkan dan benar GEOMETRYCOLLECTION' hasilnya `. Saya masih cukup baru untuk PostGIS, jadi apakah modifikasi itu masuk akal?
alphabetasoup
Maaf, seharusnya IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN- saya sudah mengeditnya.
rcoup
1
Ah, begitu. Terima kasih, ini solusi hebat. Anda harus menyarankan ini sebagai kontribusi untuk ST_Split sehingga dapat menangani multiline dan multipoint, jika ini belum ada dalam pipa PostGIS.
alphabetasoup
3
ST_Splitmendukung multi * blade di dalam postgis 2.2dan di atas postgis.net/docs/ST_Split.html
raphael
3

Tingkatkan ke PostGIS 2.2 , di mana ST_Split diperluas untuk mendukung pemisahan oleh multiline, multipoint atau (multi) batas poligon.

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))
Mike T
sumber
Ini brilian.
alphabetasoup
Ini tidak berfungsi untuk geom
ideamotor
ini bekerja dengan ST_Snap, ala trac.osgeo.org/postgis/ticket/2192
ideamotor
2

Saya belum memiliki seluruh jawaban untuk Anda, tetapi ST_Line_Locate_Point mengambil garis dan titik sebagai argumen, dan mengembalikan angka antara 0 dan 1 yang mewakili jarak di sepanjang garis ke posisi yang paling dekat dengan titik.

ST_Line_Substring mengambil garis dan dua angka, masing-masing antara 0 dan 1, sebagai argumen. Angka-angka mewakili posisi pada garis sebagai jarak fraksional. Fungsi mengembalikan segmen garis yang berjalan di antara dua posisi itu.

Dengan bekerja dengan dua fungsi ini, Anda harus dapat mencapai apa yang ingin Anda lakukan.

edward
sumber
Terima kasih untuk ini. Saya sebenarnya telah memecahkan masalah ini menggunakan teknik Anda dan juga dari @rcoup. Saya sudah memberinya jawaban yang diterima karena fungsi yang seharusnya memudahkan orang lain. Jika orang lain ingin menyusuri jalan ini, saya membuat tabel sementara dari garis-garis yang memiliki titik pada mereka, dengan satu baris untuk setiap baris dan satu perhentian yang ada di sana. Saya menambahkan kolom untuk output ST_Line_Locate_Point (line.geom, pt.geom) AS L dan fungsi jendela: peringkat () OVER PARTISI OLEH line.id ORDER BY LR). Kemudian LEFT OUTER GABUNG tabel sementara, a, untuk dirinya sendiri, b, di mana a.id = b.id dan a.LR = b.LR + 1 (lanjutan)
alphabetasoup
(lanjutan) Gabung luar memungkinkan untuk KASUS KETIKA bidang gabung adalah nol, dalam hal ini ST_Line_Substring dari titik ke akhir baris, jika tidak ST_Line_Substring dari referensi linear dari titik pertama, ke referensi linear dari titik kedua (dengan pangkat lebih tinggi). Mendapatkan [mulai] segmen LA kemudian dilakukan dengan SELECT kedua, hanya memilih yang dengan peringkat 1 dan menghitung ST_Line_Substring dari ST_StartPoint garis ke referensi linier dari titik berpotongan. Pop ini di tabel, ingat untuk menjaga line.id, dan voilà. Bersulang.
alphabetasoup
Bisakah Anda memposting jawaban ini sebagai jawaban dalam kode? Saya ingin melihat opsi itu dan saya juga seorang pemula untuk SQL.
Phil Donovan
1
@ Philip Donovan: selesai.
alphabetasoup
2

Saya telah diminta untuk ini dua kali sekarang, maaf atas keterlambatannya. Ini tidak mungkin dianggap solusi singkat; Saya menulisnya ketika sedikit lebih jauh ke bawah kurva belajar daripada saya saat ini. Setiap tips diterima, bahkan yang bergaya.

--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");
alphabetasoup
sumber
0

Saya ingin memperluas jawaban di atas dari sudut pandang pemula. Dalam skenario ini, Anda memiliki serangkaian poin dan Anda menonton menggunakannya sebagai "bilah" untuk memotong garis menjadi segmen. Seluruh contoh ini mengasumsikan bahwa Anda pertama kali mengambil poin Anda ke garis dan bahwa poin memiliki atribut ID unik dari garis terpotong mereka. Saya menggunakan 'column_id "untuk mewakili ID unik baris.

Pertama , Anda ingin mengelompokkan poin menjadi beberapa titik ketika lebih dari satu bilah jatuh pada satu garis. Jika tidak, fungsi split_line_multipoint bertindak seperti fungsi ST_Split, yang bukan hasil yang Anda inginkan.

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;

Kemudian , Anda ingin membagi jaringan Anda berdasarkan multipoints ini.

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;


Ulangi Langkah 1 dan 2 dengan garis yang hanya memiliki satu titik berpotongan. Untuk melakukan ini, Anda harus memperbarui kode dari langkah 1 ke 'HAVING COUNT (*) = 1'. Ganti nama tabel sesuai dengan itu.


Selanjutnya , buat tabel garis duplikat dan hapus entri dengan poin di atasnya.

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


Terakhir , gabungkan tiga tabel Anda bersama-sama menggunakan 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!

Mtap1
sumber