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


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:


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


Here's my attempt:

                -- 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
            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,
        FROM segments WHERE geom IS NOT NULL
-- 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, 
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
-- First node of the line
SELECT gid, path as path, ST_StartPoint(geom) as geom
WHERE path = 1

Unfortunately, the node are not always in the right position, so I guess that my angle is sometimes wrong.

Do you have an idea on 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 '19 at 16:49
  • Ok, I'm out of my office right now, so I will give you an update tomorrow – obchardon Nov 6 '19 at 17:00
  • 3
    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 '19 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 M Nov 11 '19 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 '19 at 15:11

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 tbla AS (WITH atbl AS (SELECT id, (((ST_Dump(geom)).geom)) 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 atbl, intervals GROUP BY id, gid, intervals.steps, geom),
    tblb AS (SELECT generate_series (1, 1000) as gs),
    tblc AS (SELECT * FROM tbla, tblb where tbla.gid=tblb.gs),
    tbld AS (SELECT DISTINCT b.gid, ST_Buffer((ST_DumpPoints(b.geom)).geom, b.gid*0.003) geom FROM tbla a, tblc b GROUP BY b.gid, b.geom),
    tble AS (SELECT ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(ORDER BY gid))) geom FROM tbld) 
    SELECT ST_Union(geom) geom FROM tble;

The result is shown in the figure.

enter image description here

Original solutions ...

The script is called - ST_VariableBufferFromLine.


There is now a VariableBuffer class in JTS.

It could serve as the basis for a PGPL/SQL implementation. And perhaps it will be ported to the PostGIS codebase (likely via GEOS).


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

step1 AS 
    (SELECT gid, 
        ST_DumpPoints(geom) AS dump, 
        ST_Length(geom) AS len, 
     FROM mylines),
step2 AS
    (SELECT gid, 
        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.


    geom GEOMETRY, 
    Length_BufferSize_Ratio NUMERIC



    step1 AS 
        (SELECT ST_DumpPoints(geom) AS dump, 
        ST_Length(geom) AS len, 
    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
        ST_ConvexHull(ST_Union(geom, LEAD(geom) OVER(ORDER BY path))) AS geom 
        FROM step2)
SELECT ST_Union(geom) AS geom FROM step3



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


There is a relatively simple implementation in a GeoNet thread on Can you taper a buffer?

It 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.