점을 기준으로 선을 겹치지 않는 부분 집합으로 분할


10

선 형상이있는 테이블과 별도의 테이블 에서이 선에 스냅되는 하나 이상의 점이 주어지면 선이 점과 교차하는 각 위치에서 하나 이상의 교차 점으로 각 선을 분할하고 싶습니다.

예를 들어, 선 지오메트리를 따라 3 개의 교차점 A, B 및 C가있는 선 L이 있습니다. 나는 L을 A에서 A로, A에서 B를 따라 L로, B를 C에서 L을 따라, C에서 L의 끝으로 네 가지 별개의 기하학으로 L을 반환하고 싶습니다.

과거에는 선형 참조 문제 ( http://sgillies.net/blog/1040/shapely-recipes/ ) 인 이 작업에 매끄럽게 사용했습니다 . 그러나이 경우에는 수백만 개의 선과 점이있는 실용적이지 않습니다. 대신 PostgreSQL / PostGIS를 사용하는 솔루션을 찾고 있습니다.

점은 선 위에있는 것으로 제한됩니다. 또한, 점은 선의 시작 또는 끝에 유효 할 수 있으며,이 경우 선을 분할 할 필요가 없습니다 (동일한 선의 시작 또는 끝 점과 일치하지 않는 다른 점이없는 경우). 서브 세트 선은 방향과 속성을 유지해야하지만 점 피쳐의 속성은 중요하지 않습니다.

답변:


7

ST_Split PostGIS와 기능은 당신이 원하는 아마.

PostGIS 2.2+는 이제 ST_Split에서 Multi * 형상을 지원합니다.

이전 버전의 PostGIS는 다음을 참조하십시오.


단일 포인트를 여러 포인트로 나누려면이 멀티 포인트 래퍼 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

죄송합니다 IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN. 수정했습니다.
rcoup

1
아, 알겠습니다 고마워, 이것은 훌륭한 솔루션입니다. 이것이 PostGIS 파이프 라인에 있지 않은 경우 멀티 라인 및 멀티 포인트를 처리 할 수 ​​있도록 ST_Split에 대한 기여로이를 제안해야합니다.
alphabetasoup

3
ST_Splitpostgis.net/docs/ST_Split.htmlpostgis 2.2 이상 에서 멀티 * 블레이드 지원
raphael

3

PostGIS와 2.2로 업그레이드 , ST_Split이 여러 줄, 지점 또는 (다) 다각형 경계에 의해 지원 분할로 확대되었다.

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



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 BY line.id ORDER BY LR)에 열을 추가했습니다. 그런 다음 왼쪽 외부 임시 테이블 a, 자체에 b를 결합합니다. 여기서 a.id = b.id 및 a.LR = b.LR + 1 (계속)
alphabetasoup

(계속) 외부 조인은 조인 필드가 널인 경우 CASE를 허용합니다.이 경우 ST_Line_Substring은 점에서 끝까지, 그렇지 않으면 ST_Line_Substring은 첫 번째 점의 선형 참조에서 두 번째 점의 선형 참조로 이어집니다. (더 높은 순위로). 그런 다음 [start] LA 세그먼트를 가져 오는 것은 두 번째 SELECT로 수행되며, 순위가 1 인 세그먼트를 선택하고 ST_StartPoint에서 교차 지점의 선형 참조까지 ST_Line_Substring을 계산하면됩니다. line.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;

밤!

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.