8

I have a line network and I am placing points at equal intervals along each line. These points will be used to cut the lines into segments. I have found a way to create the points along the lines:

CREATE TABLE split_pt as

WITH line AS 
    (SELECT
        id,
        geom 
    FROM line_table),
linemeasure AS
    (SELECT
        ST_AddMeasure(line.geom, 0, ST_Length(line.geom)) AS linem,
        generate_series(0, ST_Length(line.geom)::int, 160.9) AS i
    FROM line),
geometries AS (
    SELECT
        i,
        (ST_Dump(ST_GeometryN(ST_LocateAlong(linem, i), 1))).geom AS geom 
    FROM linemeasure)

SELECT
    row_number() over() as id,
    i,
    ST_SetSRID(ST_MakePoint(ST_X(geom), ST_Y(geom)), 3857) AS geom
FROM geometries;

However, I have two problems:

  1. I'm getting around 400 duplicate points
  2. About 60% of the points are not actually on the line so running the subsequent ST_Split() with these points against the lines doesn't work in most instances.

I am looking for help to eliminate duplicate points and to ensure that the points I create in the above query will return True on ST_Intersects(). I'd like to be able to incorporate these two aspects in the the above query and not in subsequent steps, if possible.

geozelot
  • 30,050
  • 4
  • 32
  • 56
pdavis
  • 827
  • 1
  • 8
  • 18
  • 2
    could you add a simple input geometry and the result of the intermediate results – Ian Turton Oct 08 '19 at 16:43
  • 3
    Due to the limitations of IEEE floating-point values, it might not be possible for a point to land exactly on a line. – Vince Oct 08 '19 at 17:36
  • Why don't you just segment the lines into the required segments? – Cyril Mikhalchenko Oct 08 '19 at 18:07
  • @Cyril unfortunately segmentizing the lines also keeps all segments that are shorter than the specified length. My lines have quite a lot of vertices as-is (so already lots of short segments), and I do need to keep most of them, to retain the shape – pdavis Oct 08 '19 at 18:24
  • and how does the ST_ClosestPoint() function behave - does it return "bad points" exactly on the line or not? – Cyril Mikhalchenko Oct 08 '19 at 18:38
  • 1
    doubling point removal: DELETE from geometries WHERE NOT id IN( SELECT min(id) as idpnt from geometries group by geom); – Cyril Mikhalchenko Oct 08 '19 at 19:06
  • and what about : https://postgis.net/docs/ST_LineLocatePoint.html and his counter part https://postgis.net/docs/ST_LineInterpolatePoint.html ? it should garantee position on the line and provide a easier workaround – Maximilien jaffrès Oct 09 '19 at 13:24
  • @cyril ST_ClosestPoint() does not return most of the points exactly on the lines. In fact, it doesn't result in any more points intersecting the lines than the query I provided in the question. Thinking comment from Vince is the limitation. – pdavis Oct 09 '19 at 15:00
  • @IanTurton can you expand on your comment? – pdavis Oct 09 '19 at 15:01
  • 1
    It's a pity that this is actually the case, because users assume that the point should be exactly on the line, and that's a lie, it would still be a lie to understand what the concept of a line in postgis is - it's hair, it's nano hair, how thick the line is, etc., etc. ? I've seen the JGH answer, but it doesn't solve your problem... – Cyril Mikhalchenko Oct 09 '19 at 16:36
  • @cyril yes I agree that the point should be on the line. My goal was to use those points to split the lines at those places. I found that if I buffer the points by a very small amount (0.0001 or so), I can split the lines as expected. But then have to delete the infinitesimally small line segments left over. Not ideal, but it seems to work for now. – pdavis Oct 09 '19 at 18:25
  • 2
    PostGIS interpolates between vertices, it's functional minimal unit for most geometries (e.g. a line) is a point array, and trigonometry; floating point error is the issue here. ST_QuantizeCoordinates can help, but really you should be looking at ST_LineSubstring. – geozelot Oct 09 '19 at 21:43
  • @pdavis point buffering is like a workaround or hack, I think it's worth checking out the ThingumaBob. The ST_Split and ST_Difference behavioral functions are similar, it feels like they were developed by the same programmer and work well when the user is dealing with the same object, but become a headache when the number of objects increases...I think developers understand the problem and will try to solve it in the future... – Cyril Mikhalchenko Oct 10 '19 at 16:23
  • https://gis.stackexchange.com/a/192526/120129... – Cyril Mikhalchenko Oct 16 '19 at 10:55
  • @ThingumaBob, ST_LineSubstring was I should have been looking at all along. If you'd like, please add your comment as an answer and I'll accept it. – pdavis Oct 23 '19 at 22:44

1 Answers1

14

Better look at using ST_LineSubstring; no issues with coordinate precision, but somewhat tricky to set up...


Edit: I then packed it into a more sophisticated set of functions a while ago; there's a C function add-on as well, for those who like to build from source.

I packed that functionality into a (naive) function a few years ago:

CREATE OR REPLACE FUNCTION ST_LineSubstringByLength(

geom GEOMETRY(LINESTRING), length FLOAT8, use_meter BOOLEAN DEFAULT TRUE

) RETURNS SETOF geometry_dump AS

$$ DECLARE

len_frac  FLOAT8;
s_frac    FLOAT8;
e_frac    FLOAT8;

BEGIN

IF ($3)
  THEN  len_frac := $2 / ST_Length(geom::GEOGRAPHY);
  ELSE  len_frac := $2 / ST_Length(geom);
END IF;

FOR n IN 0..CEIL(1.0 / len_frac)
  LOOP
    s_frac := len_frac * n;
    IF (s_frac >= 1.0)
      THEN
        EXIT;
    END IF;
    e_frac := len_frac * (n + 1);
    IF (e_frac > 1.0)
      THEN
        e_frac := 1.0;
    END IF;
    RETURN NEXT (ARRAY[n + 1], ST_LineSubstring($1, s_frac, e_frac));
  END LOOP;

RETURN;

END $$ LANGUAGE plpgsql;


The function accepts:

  • a LineString GEOMETRY
  • a Length value by which the given LineString should be divided
    (until the last element which will be of the rest length)
  • a Switch to set the given length to be interpreted as meter; defaults to TRUE
    (input geometry needs to be in EPSG:4326; it really just casts to GEOGAPHY for the fraction calculation)

The function returns a SETOF geometry_dump (a RECORD just like e.g. ST_Dump) having:

  • a path value (INTEGER[]) indicating the position of the current row in the extraction starting from the beginning of the input LineString
  • a geom member (GEOMETRY)

A record looks like:

({1}, 01020000000200000000000000000000000000000000000000A65F8237663FC13F0000000000000000)
({2}, 010200000002000000A65F8237663FC13F0000000000000000A65F8237663FD13F0000000000000000)

Usage:

SELECT * FROM ST_LineSubstringByLength('LINESTRING(0 0, 5 0)'::GEOMETRY, 15000);
SELECT * FROM ST_LineSubstringByLength('LINESTRING(0 0, 5 0)'::GEOMETRY, 1, FALSE);

SELECT id, (dmp).path[1], (dmp).geom FROM <table_with_geom_and_id_columns>, LATERAL ST_LineSubstringByLength(geom, <length_in_meter?>[, TRUE|FALSE]) AS dmp ;

geozelot
  • 30,050
  • 4
  • 32
  • 56
  • 1
    It's trivial btw. to alter this function to accept an integer value of segments it should divide the input LineString into. I use a customized PostGIS version on my machine where I add all those things that I can directly into the C function base; it's trivial to have these functions then to return segments for any geometry (using e.g. the boundaries)...and it's about 10 to 100 times faster. if you don't mind building from source...? – geozelot Oct 24 '19 at 13:48