7

I want to create a buffer along a line where the buffer width correspond to a fraction of the length of the line, something like that:

tg

To do that my idea is to compute for each node of the line (except for the first and last node) the bisector angle between the previous and the next segment, example for the node, bisector angle in red #3:

enter image description here

At the moment I'm just trying to create one side (a line) of the buffer:

enter image description here

Here's my attempt:

WITH POINT_LINE AS
(
WITH SUB AS
(
                -- Extract all the segments and calculate for each segment the cumulative length
        WITH segments AS (
        SELECT gid, (pt).path[1]-1 as path, ST_MakeLine(lag((pt).geom, 1, NULL) OVER (PARTITION BY gid ORDER BY gid, (pt).path), (pt).geom) AS geom
          FROM (SELECT gid, ST_DumpPoints(geom) AS pt FROM MYLINE) as dumps
        )
        SELECT 
            gid,
            sum(st_length(geom)) over (order by path asc rows between unbounded preceding and current row)-st_length(geom)  as l_start,
            sum(st_length(geom)) over (order by path asc rows between unbounded preceding and current row)          as l_end,
            ST_StartPoint(geom)                                             as g_start,
            ST_EndPoint(geom)                                               as g_end,
            path,
            geom
        FROM segments WHERE geom IS NOT NULL
)
SELECT 
a.gid, 
a.path,
a.l_end, 
b.g_start, 
-- HERE I try to calculate the bisector angle:
atan((ST_Y(a.g_start)-ST_Y(a.g_end))/(ST_X(a.g_start)-ST_X(a.g_end))) + asin((ST_X(a.g_end)-ST_X(a.g_start))/SQRT((ST_X(a.g_end)-ST_X(a.g_start))^2+(ST_Y(a.g_end)-ST_Y(a.g_start))^2)) as phi, 
a.geom
FROM SUB a, SUB b
WHERE a.path = b.path-1
AND a.gid = b.gid
)
-- Intermediate node
SELECT gid, path+1 as path, ST_SetSRID(ST_MakePoint(ST_X(g_start)+cos(phi)*l_end,ST_Y(g_start)+sin(phi)*l_end),2056) as geom
FROM POINT_LINE
UNION 
-- First node of the line
SELECT gid, path as path, ST_StartPoint(geom) as geom
FROM POINT_LINE
WHERE path = 1

Infortunately the node are not always at the right position, so I guess that my angle is sometime wrong.

Do you have an idea how to solve this problem.

  • I think you can do this by using ST_Angle, to get the angle between successive linestring, and then add/subtract half of this to ST_Azimuth of one of the linestrings. – John Powell Nov 6 at 16:49
  • Ok, I'm out of my office right now, so I will give you an update tomorrow – obchardon Nov 6 at 17:00
  • 2
    It would be useful to understand the higher-level problem you're trying to solve as well. This question reminded me of: gis.stackexchange.com/questions/326681/… – alphabetasoup Nov 6 at 20:09
  • The easiest way is from the line to create the equidistant points, then on the points run the generator series in the order of their increase, then combine the buffers, and possibly smooth them out... – Cyril Nov 11 at 14:32
  • @Cyril that is indeed my goal, I will try to implement the JohnPowell's advice and post an answer if my attempt is sucessful. – obchardon Nov 11 at 15:11
2

My answer is not very elegant from the programming point of view but perhaps it is also one of the variants of its solution, actually the idea and only then the tools are important.

So the source data is a line of the type-multilinestring, its trajectory should be the same, it's important to adjust the size of buffers and density of points on the line, I played with the data in EPSG:4326.

Run the script:

WITH ta AS (WITH at AS (SELECT id, (((ST_Dump(geom)).geom)) as geom FROM line),
    intervals as (SELECT generate_series (0, 999) as steps,  generate_series (1, 1000) as gid)
    SELECT steps AS stp, gid, ST_LineInterpolatePoint(geom, steps/(SELECT count(steps)::float-1 FROM intervals)) as geom FROM at, intervals GROUP BY id, gid, intervals.steps, geom),
    tb as (SELECT generate_series (1, 1000) as gs),
    tc as (SELECT * FROM ta, tb where ta.gid=tb.gs),
    td as (SELECT DISTINCT b.gid, ST_Buffer((ST_DumpPoints(b.geom)).geom, b.gid*0.003) geom FROM ta a, tc b GROUP BY b.gid, b.geom),
    te as (SELECT ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(ORDER BY gid))) As geom FROM td) 
    SELECT ST_Union(geom) as geom from te;

The result is shown in the figure.

enter image description here

The script is called ST_VariableBufferFromLine.

3

Thanks to @Cyril I've solved my question (Thank you !).

I've improved the answer given by @Cyril, it is way faster, here is the solution:

WITH 
step1 AS 
    (SELECT gid, 
        ST_DumpPoints(geom) AS dump, 
        ST_Length(geom) AS len, 
        geom 
     FROM mylines),
step2 AS
    (SELECT gid, 
        (dump).path[1], 
        ST_Buffer((dump).geom, ST_LineLocatePoint(geom, (dump).geom)*len/10 + 0.01) AS geom 
     FROM step1),
step3 AS
    (SELECT gid, 
        ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(PARTITION BY gid ORDER BY gid, path))) AS geom 
    FROM step2)

SELECT gid, ST_Union(geom) AS geom FROM step3 GROUP BY gid

What this request does ?

Step 1:

SELECT gid, ST_DumpPoints(geom) AS dump, ST_Length(geom) AS len, geom FROM mylines

We extract every points of each line. I keep the GID so I will be able to perform the same operation on several line at the same time.

Step 2:

SELECT gid, (dump).path[1], st_buffer((dump).geom, ST_LineLocatePoint(geom, (dump).geom)*len/10 + 0.01) AS geom FROM step1

We apply a buffer to each point, the buffer size correspond to the corresponding length of the line at this point divided by ten (ten is arbitrary). I need to add a small constant to the buffer size (here 0.01) so the first buffer size is not 0. We keep the path (order) of each point.

Step 3:

SELECT gid, ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(PARTITION BY gid ORDER BY gid, path))) AS geom FROM step2

We use ST_ConvexHull for each consecutive pair of circular buffer.

SQL FUNCTION:

CREATE OR REPLACE FUNCTION ST_VariableBufferFromLine(
    geom GEOMETRY, 
    Length_BufferSize_Ratio NUMERIC
)

RETURNS GEOMETRY AS

$BODY$

WITH 
    step1 AS 
        (SELECT ST_DumpPoints(geom) AS dump, 
        ST_Length(geom) AS len, 
        geom),
    step2 AS
        (SELECT (dump).path[1], 
        ST_Buffer((dump).geom, GREATEST(ST_LineLocatePoint(geom, (dump).geom)*len/Length_BufferSize_Ratio,0.001)) AS geom 
         FROM step1),
    step3 AS
        (SELECT 
        ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(ORDER BY path))) AS geom 
        FROM step2)
SELECT ST_Union(geom) AS geom FROM step3

$BODY$

LANGUAGE SQL;

So now we can simply use:

SELECT gid, ST_VariableBufferFromLine(geom,10.0) AS geom FROM mylines

If you need a fixed buffer size (for example the end of the line should have of buffer of 100m) we can simply replace this part in the function:

GREATEST(ST_LineLocatePoint(geom, (dump).geom)*len/Length_BufferSize_Ratio,0.001))

by this one:

GREATEST(ST_LineLocatePoint(geom, (dump).geom)*end_width,0.001))

With end_width = the buffer size at the end of the line. Don't forget to adapt the variable name.

Example result with 2 lines:

enter image description here

  • 2
    Nice solution! I posted a slightly generalized version of this which allows specifying start and end distance here: trac.osgeo.org/postgis/wiki/UserWikiVariableBuffer – dr_jts Nov 25 at 19:19
  • @dr_jts cool ! Just noticed that in your function start_dist should be strictly bigger than 0. If start_dist = 0 the first segment won't be created. – obchardon Nov 26 at 12:52
  • Thanks, will check that out. – dr_jts Nov 27 at 1:38
1

There is now a VariableBuffer class in JTS here. It could serve as the basis for a PGPL/SQL implementation. And perhaps it will be ported to the PostGIS codebase (likely via GEOS).

0

There is also a relatively simple implementation in this post. Should be straightforward to port to PostGIS. Although I'm not sure how it avoids creating small artifacts where the segment buffers meet the vertex buffers.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.