Noktaları temel alarak çizgileri çakışan olmayan alt kümelere ayırma


10

Çizgi geometrisi olan bir tablo ve bu çizgiye ayrı bir tabloda yakalanan bir veya daha fazla nokta göz önüne alındığında, her bir çizginin, çizginin bir nokta ile kesiştiği konumların her birinde bir veya daha fazla kesişen nokta ile ayrılmasını istiyorum.

Örneğin, çizgi geometrisi boyunca sırayla üç kesişen nokta, A, B ve C olan bir çizgi vardır. L'yi dört ayrı geometri olarak döndürmek istiyorum: L'nin başlangıcından A'ya, L boyunca A'dan B'ye, L boyunca B'den C'ye ve C'den L'nin sonuna.

Geçmişte doğrusal bir referans problemi olan bu görev için düzgün bir şekilde kullandım ( http://sgillies.net/blog/1040/shapely-recipes/ ). Bununla birlikte, milyonlarca çizgi ve noktaya sahip olan bu durumda bu mümkün olmayacaktır. Bunun yerine, PostgreSQL / PostGIS kullanarak bir çözüm arıyorum.

Noktaların bir çizgi üzerinde olması gerektiğini unutmayın. Ayrıca, bir çizgi geçerli olarak çizginin başlangıcında veya sonunda olabilir, bu durumda çizginin bölünmesi gerekmez (aynı çizginin başlangıç ​​veya bitiş noktalarına rastlamayan başka noktalar yoksa). Altküme satırlarının yönlerini ve niteliklerini koruması gerekir, ancak nokta özelliklerinin nitelikleri önemli değildir.

Yanıtlar:


7

ST_Split PostGIS işlevi ne istediğinizi muhtemelen.

PostGIS 2.2+ artık ST_Split'te Multi * geometrilerini destekliyor.

PostGIS'in eski sürümleri için okumaya devam edin:


Tek bir satırı birden çok noktaya bölmek için, bu çok noktalı sarmalayıcı plpgsql işlevi gibi bir şey kullanabilirsiniz . Ben sadece aşağıdaki "(çoklu) noktaları ile bölünmüş (çoklu) satırları basitleştirilmiş:

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;

Ardından, kesilecek çok noktalı bir geometri oluşturmak için ST_Collect kullanın ve girişlerden manuel olarak oluşturun:

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

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

Veya bir alt sorgudan toplayın:

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

Başlamak için ST_Split denedim ve çok noktalı geometri kabul etmediğini bulduğumda şaşırdım. İşleviniz bu boşluğu dolduruyor gibi görünüyor, ancak maalesef çok noktalı örnek durum için NULL döndürüyor. ((Tek) noktada iyi çalışır.) Ancak, blade_geometry_type EĞER ILIKE '% LINESTRING' O zaman IF blade_geometry_type ILIKE '% LINESTRING' ON işlevini değiştirdim ve beklenen ve doğru `GEOMETRYCOLLECTION 'sonucu aldım. Yine de PostGIS için oldukça yeniyim, bu değişiklik mantıklı mı?
alphabetasoup

Üzgünüm, olmalıydı IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN- Ben düzenledim.
rcoup

1
Ah, anlıyorum. Teşekkürler, bu harika bir çözüm. PostGIS kanalında yoksa çok satırlı ve çok noktalı işleyebilmesi için bunu ST_Split'e bir katkı olarak önermelisiniz.
alphabetasoup

3
ST_Splitpostgis.net/docs/ST_Split.htmlpostgis 2.2 ve üstü çoklu * bıçakları destekler
raphael

3

PostGIS 2.2'ye yükseltin , burada ST_Split çok satırlı, çok noktalı veya (çok) çokgen bir sınırla bölünmeyi desteklemek üzere genişletildi.

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

Bu harika.
alphabetasoup

Bu benim karmaşık geom için işe yaramaz: gist.github.com/ideamotor/7bd7cdee15f410ce12f3aa14ebf70177
ideamotor


2

Sizin için tüm cevabı bulamadım, ancak ST_Line_Locate_Point bir çizgi ve bir noktayı bağımsız değişken olarak alır ve 0 ile 1 arasında, çizgiye en yakın konuma kadar olan mesafeyi temsil eden bir sayı döndürür.

ST_Line_Substring, bir satır ve her biri 0 ile 1 arasında iki sayı bağımsız değişken olarak alır. Sayılar, çizgi üzerindeki konumları kesirli mesafeler olarak gösterir. İşlev, bu iki konum arasında çalışan çizgi parçasını döndürür.

Bu iki işlevle çalışarak yapmak istediğiniz şeyi başarabilmelisiniz.


Bunun için teşekkürler. Aslında bu sorunu tekniğinizin yanı sıra @rcoup'tan da çözdüm. Başkaları için kolaylaştıracak işlev nedeniyle ona kabul edilen cevabı verdim. Diğerleri bu yoldan gitmek isterse, üzerinde her nokta için bir satır ve üzerinde bir durak bulunan, üzerinde noktaları olan satırların geçici bir tablosunu oluşturdum. ST_Line_Locate_Point (line.geom, pt.geom) AS L ve bir pencere işlevi çıktısı için sütunlar ekledim: rank () OVER PARTITION BY line.id ORDER BY LR). Sonra SOL DIŞ geçici tabloya a, kendi kendine, b, burada a.id = b.id ve a.LR = b.LR + 1 (devam)
alphabetasoup

(devam) Dış birleştirme, birleştirme alanları null olduğunda BİR DURUMA izin verir, bu durumda ST_Line_Substring noktadan satırın sonuna, diğer bir deyişle ST_Line_Substring ilk noktanın doğrusal referansından ikinci noktanın doğrusal referansına (daha yüksek sıralama ile). [Start] LA segmentinin elde edilmesi daha sonra ikinci bir SELECT ile gerçekleştirilir, sadece 1 rütbesi olanları seçer ve ST_Line_Substring'i çizginin ST_StartPoint'inden kesişen noktanın doğrusal referansına hesaplar. Çizgiyi tutmayı hatırlayarak bunları masaya koyun. İd ve ​​voilà. Şerefe.
alphabetasoup

Bu yanıtı kodda bir cevap olarak gönderebilir misiniz? Bu seçeneğe bakmak istiyorum, ayrıca SQL'e yeni başlayan biriyim.
Phil Donovan

1
@PhilDonovan: bitti.
alphabetasoup

2

Bunu iki kez istedim, gecikme için çok üzgünüm. Bunun kısa bir çözüm olarak görülmesi olası değildir; Öğrenim eğrisinden şu anda olduğumdan biraz daha ileride yazdım. Herhangi bir ipucu hoş, stilistik olanlar bile.

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

Yukarıdaki cevapları yeni başlayanların bakış açısıyla genişletmek istiyorum. Bu senaryoda, bir dizi noktanız var ve bunları bir kesiti parçalara ayırmak için "bıçak" olarak kullanmayı izliyorsunuz. Bu örnek, noktalarınızı ilk önce çizgiye yapıştırdığınızı ve noktaların, kesik çizgilerinden benzersiz kimlik özelliğine sahip olduğunu varsayar. Satırın benzersiz kimliğini temsil etmek için 'column_id "kullanıyorum.

İlk olarak , bir bıçak birden fazla çizgiye düştüğünde puanlarınızı çoklu noktalara gruplamak istiyorsunuz. Aksi takdirde, split_line_multipoint işlevi istediğiniz sonuç olmayan ST_Split işlevi gibi davranır.

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;

Ardından , ağınızı bu çoklu noktalara göre bölmek istiyorsunuz.

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;


Yalnızca bir kesişme noktası olan çizgilerle Adım 1 ve 2'yi tekrarlayın. Bunu yapmak için, 1. adımdaki kodu 'HAVUN SAYISI (*) = 1' olarak güncellemelisiniz. Tabloları buna göre yeniden adlandırın.


Ardından , yinelenen bir çizgi tablosu yapın ve üzerinde nokta bulunan girişleri silin.

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


Son olarak , aşağıdakileri kullanarak üç masanıza katılın 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!

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.