根据点将线划分为不重叠的子集


10

给定一张具有线几何形状的表,并在单独的表中捕捉到该线的一个或多个点,我想在每条线与一个点相交的位置处使用一个或多个相交点来分割每条线。

例如,一条直线L沿直线几何顺序依次具有三个相交点A,B和C。我想将L作为四个不同的几何返回:从L的起点到A,沿着L的从A到B,沿着L的从B到C,以及从C到L的终点。

过去,我经常使用shape来完成此任务,这是一个线性引用问题(http://sgillies.net/blog/1040/shapely-recipes/)。但是,在这种情况下这是不切实际的,因为它有数百万条线和点。相反,我正在寻找使用PostgreSQL / PostGIS的解决方案。

注意,点被限制在一条线上。此外,一个点可以有效地位于一条线的起点或终点,在这种情况下,无需拆分该线(除非存在其他与同一条线的起点或终点不一致的点)。子线需要保留其方向和属性,但是点要素的属性无关紧要。

Answers:


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'THEN更改为IF blade_geometry_type ILIKE'%LINESTRING'THEN,并获得了预期且正确的“ GEOMETRYCOLLECTION”结果。但是,我仍然对PostGIS还是很陌生,那么修改是否明智?
alphabetasoup

抱歉,应该已经IF geom_geometry_type NOT ILIKE '%LINESTRING' THEN-我已对其进行编辑。
rcoup 2014年

1
知道了 谢谢,这是一个很好的解决方案。您应该建议将此作为对ST_Split的贡献,以便它可以处理多行和多点(如果PostGIS管道中还没有的话)。
alphabetasoup

3
ST_Splitpostgis.net/docs/ST_Split.htmlpostgis 2.2及以上支持多刀片服务器
拉斐尔

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

这不适用于我的复杂几何图形
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 BY line.id ORDER BY LR)。然后LEFT OUTER JOIN临时表中,a,其自身,b,其中a.id = b.id和a.LR = b.LR + 1(续)
alphabetasoup

(续)当连接字段为null时,外部连接允许使用CASE,在这种情况下,从点到行尾的ST_Line_Substring,否则从第一个点的线性参考到第二点的线性参考的ST_Line_Substring (排名较高)。然后,使用第二个SELECT来执行[start] LA片段的获取,只需挑选等级为1的片段,然后计算从线的ST_StartPoint到相交点的线性参考的ST_Line_Substring。将它们弹出到表中,记住保留line.id和voilà。干杯。
alphabetasoup

您可以将此答案作为代码中的答案发布吗?我想看看这个选项,因为我还是SQL的新手。
Phil Donovan 2014年

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;

AM!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.