ポイントに基づいてラインを重複しないサブセットに分割します


10

ラインジオメトリのテーブルと、別のテーブルでこのラインにスナップされる1つまたは複数のポイントが与えられた場合、ラインがポイントと交差する各位置で1つまたは複数の交差するポイントを使用して各ラインを分割します。

たとえば、ラインLがあり、ラインジオメトリに沿って3つの交点A、B、Cが順番に並んでいます。Lを4つの異なるジオメトリとして返します。Lの最初からAまで、Lに沿ってAからBまで、Lに沿ってBからCまで、そしてCからLの終わりまでです。

過去に私はこのタスクに整形を使用しましたが、これは線形参照の問題です(http://sgillies.net/blog/1040/shapely-recipes/)。ただし、これは、何百万もの線と点があるこの場合には実用的ではありません。代わりに、PostgreSQL / PostGISを使用するソリューションを探しています。

ポイントは直線上にあるように制約されていることに注意してください。さらに、ポイントは有効にラインの開始または終了に置くことができます。その場合、ラインを分割する必要はありません(同じラインの開始または終了ポイントと一致しない他のポイントがない限り)。サブセットラインは方向と属性を保持する必要がありますが、ポイントフィーチャの属性は重要ではありません。

回答:


7

ST_Split PostGISの機能は、あなたが望むものと考えられます。

PostGIS 2.2以降では、ST_SplitでMulti *ジオメトリがサポートされるようになりました。

古いバージョンのPostGISについては、以下をお読みください。


1つのラインを複数のポイントで分割するには、このマルチポイントラッパー plpgsql関数のようなものを使用できます。以下の「(複数の)ポイントで(複数の)ラインを分割する」ケースだけに簡略化しました:

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 NOT ILIKE '%LINESTRING' THENIF blade_geometry_type ILIKE '%LINESTRING' THENに変更し、期待どおりの正しい「GEOMETRYCOLLECTION」結果を得ました。ただし、PostGISはまだかなり新しいので、その変更は賢明ですか?
alphabetasoup 14

申し訳ありませんが、そうであったはずですIF geom_geometry_type NOT ILIKE '%LINESTRING' THEN-私はそれを編集しました。
rcoup 2014

1
ああ、なるほど。おかげで、これは素晴らしいソリューションです。これがまだPostGISパイプラインにない場合、マルチラインとマルチポイントを処理できるように、これをST_Splitへの貢献として提案する必要があります。
alphabetasoup

3
ST_Splitpostgis.net/docs/ST_Split.htmlpostgis 2.2以上のマルチ*ブレードをサポート
raphael

3

ST_Splitが拡張され、マルチライン、マルチポイント、または(マルチ)ポリゴン境界による分割をサポートするように、PostGIS 2.2アップグレードします。

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


2

完全な答えはありませんが、ST_Line_Locate_Pointは線と点を引数として取り、点に最も近い位置までの線に沿った距離を表す0〜1の数値を返します。

ST_Line_Substringは、行と、それぞれ0から1までの2つの数値を引数として取ります。数字は、ライン上の位置を端数の距離として表します。この関数は、これら2つの位置の間を走る線分を返します。

これらの2つの関数を操作することで、やりたいことを実現できるはずです。


これをありがとう。私は実際にあなたのテクニックと@rcoupのテクニックを使ってこの問題を解決しました。他の人にとって簡単になるはずの機能により、私は彼に受け入れられた答えを与えました。他の人がこのパスをたどりたい場合は、ポイントを持つラインの一時テーブルを作成しました。各ラインの行とその上に1つのストップがあります。ST_Line_Locate_Point(line.geom、pt.geom)AS Lおよびウィンドウ関数の出力用の列を追加しました:rank()OVER PARTITION BY line.id ORDER BY LR)。次に、一時テーブルaをそれ自体bにLEFT OUTER JOINします。ここで、a.id = b.idおよびa.LR = b.LR + 1(続き)
alphabetasoup

(続き)外部結合では、結合フィールドがnullの場合にCASEが可能です。その場合、ポイントからラインの終わりまでのST_Line_Substring、それ以外の場合、最初のポイントの線形参照から2番目のポイントの線形参照までのST_Line_Substring (ランクが高い)。次に、[開始] LAセグメントの取得は、2番目のSELECTで実行され、ランクが1のセグメントを選択し、ラインのST_StartPointから交差ポイントの線形参照までのST_Line_Substringを計算します。これらをテーブルにポップし、line.idとvoilàを保持することを忘れないでください。乾杯。
alphabetasoup

この回答をコードの回答として投稿できますか?私はそのオプションを見てみたいし、SQLの初心者でもあります。
Phil Donovan

1
@PhilDonovan:完了。
alphabetasoup 14

2

私はこれを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属性があることを前提としています。行の一意のIDを表すために「column_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つしかない線を使用して、手順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) ;


最後に、以下を使用して3つのテーブルを結合します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;

バム!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.