2

We have a single LineString (a route on a map) made of multiple points (blue line) which we are then buffering to produce an area around it. We have found that everything looks as expected unless the route intersects itself, at which point the buffered area then gains considerable longitudinal curvature.

An example shows the differing results: http://geojson.io/#id=gist:jgwconsulting/1e2a6e8bad9f018f2c6321016a527bef&map=6/55.621/-3.618

  • Grey: entire route is buffered as a single LineString, resulting in curvature of the buffered area.
    SELECT ST_AsGeoJSON(
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-14.466666666666667,55.25],[7.233333333333333,54],[0,58.86666666666667],[-11.7,52.06666666666667],[-12.6,56.55]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')
    );
    
  • Red / Orange: the LineString is split into individual straight lines made of two points, each of which is then buffered and the resulting Polygons are merged using ST_Union.
    SELECT ST_AsGeoJSON(
    ST_Union(
    ARRAY[
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-14.466666666666667,55.25],[7.233333333333333,54]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[7.233333333333333,54],[0,58.86666666666667]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[0,58.86666666666667],[-11.7,52.06666666666667]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-11.7,52.06666666666667],[-12.6,56.55]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry
    ]
    ));
  • Yellow: the LineString is split into individual straight lines made of two points, each of which is then wrapped into a MultiPolygon using ST_Collect.
    SELECT ST_AsGeoJSON(
    ST_Collect(
    ARRAY[
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-14.466666666666667,55.25],[7.233333333333333,54]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[7.233333333333333,54],[0,58.86666666666667]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[0,58.86666666666667],[-11.7,52.06666666666667]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry,
    ST_Buffer(ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-11.7,52.06666666666667],[-12.6,56.55]]}')::geography, 9260, 'quad_segs=30 endcap=round join=round')::geometry
    ]
    ));

Is anyone able to explain the differing results we're seeing, ostensibly with the same set of coordinates and particularly why an intersecting line changes the buffer so significantly?

This is an example of creating a (different) route, showing how the buffer geometry changes considerably as soon as a line is added which intersects another part of the route.

Click this link: https://vimeo.com/320203046

  • 3
    The doc has a warning for geography buffers: For geography this may not behave as expected if object is sufficiently large that it falls between two UTM zones or crosses the dateline Your sample lines do span 2 UTM zones. – JGH Feb 27 at 12:15
  • 1
    When you say "intersects itself," have you ensured that the final line string geometry is simple? From that link, "many PostGIS methods require, or more accurately, assume that geometries that are operated on are both simple and valid." They won't behave properly if not. – jpmc26 Feb 27 at 17:17
3

Interestingly the curved version is correct if you play with geography. See the image below. The green line is your original GeoJSON plotted with a GIS client that can only use a planer coordinate system. The purple line is created with PostGIS query

SELECT ST_Segmentize(
ST_GeomFromGeoJSON('{"type":"LineString","coordinates":[[-14.466666666666667,55.25],[7.233333333333333,54],[0,58.86666666666667],[-11.7,52.06666666666667],[-12.6,56.55]]}')::geography
,100000)::geometry;

So, the linestring as a geography is segmentized to have a vertex at every 100 km. Cast back to geometry is done just for the non-geography capable viewer.

You can see that straight geography linestrings look curved when showed in planar coordinate system because they follow the shortest path on spheroid, the "Great circle".

When you created a buffer for a self-intersecting linestring the algorithm finds the correct intersection point of the geography linestrings. This point is obviously added as a new vertex for the buffer algorithm, the buffer area gets a bend and the result "Grey" is rather close to correct Great circle result.

You had a right idea with your tests with splitted linestrings. However, you did the split in planar coordinate system and therefore the test did not reveal the curvature of lines.

It is good practice to segmentize/densify very long segments first because algorithms often deal only with vertices and relation between the vertices is supposed to be linear.

This is your GeoJSON geometry after it has been segmentized as geography

LINESTRING ( -14.466666666666667 55.25, -13.08889310538655 55.28704917903751, -11.709085410988154 55.30856429689069, -10.328321900711986 55.31451602902716, -8.947686100999091 55.30489625061711, -7.5682608415558965 55.2797180910541, -6.191122358792509 55.2390158458828, -4.8173344877503546 55.182844747514125, -3.4479430194254133 55.11128059830464, -2.08397029629682 55.02441927167354, -0.7264101130495206 54.92237608887058, 0.6237770178397885 54.8052850807411, 1.965668184524082 54.673298145319606, 3.2983819365313143 54.52658411328878, 4.621081656167955 54.36532773424728, 5.9329784191675055 54.18972859732803, 7.233333333333333 54, 6.426253723121359 54.63004535265149, 5.5938895637791735 55.25455611476192, 4.735073398743345 55.87322373000668, 3.8485894193265504 56.48571996916197, 2.933173741727981 57.09169567833502, 1.9875152761669712 57.69077947986619, 1.0102573167737552 58.282576433304996, 0 58.86666666666667, -0.8577481647393383 58.47718586936625, -1.696558719455395 58.082108080613004, -2.5168773980849872 57.681640525397924, -3.3191531135004273 57.27598307621911, -4.103836026168624 56.86532837869072, -4.871375832210857 56.449861996384726, -5.622220255301017 56.02976257142281, -6.35681372690526 55.605201997699375, -7.075596239659377 55.17634560396123, -7.77900235915053 54.743352344289534, -8.46746037997258 54.30637499382855, -9.141391612621591 53.86556034787863, -9.80120978855862 53.42104942272201, -10.44732057156455 52.97297765677625, -11.080121164325472 52.52147511087597, -11.7 52.06666666666667, -11.802260557330674 52.627431878544535, -11.907175699454038 53.1881067638425, -12.014870581450193 53.74868704835126, -12.125478108810857 54.30916819186383, -12.239139549252082 54.86954536705044, -12.356005203049094 55.429813436297565, -12.476235138501552 55.98996692627955, -12.6 56.55 )

enter image description here

  • That's brilliant, thank you so much for the detailed answer. I'm really struggling here as a programmer without much geospatial experience but this helps immeasurably. – Jerome Feb 27 at 14:59
  • Alternatively, if you need to use lat/long coordinates, just use geography for everything. ST_Buffer supports it directly. – jpmc26 Feb 27 at 17:24
1

I publish this information as additional information from GIS cartographer.

I decided to check how PostGIS will behave with your source data,

see the result in the form of a script and in three figures, only I created in my example 1 degree buffer, i.e. no deviations PostGIS - QGIS not showed me,

as a result, check your syntax for spelling

create table byffer1 as SELECT ST_Buffer(ST_GeomFromText('LineString(-14.466666666666667 55.25,7.233333333333333 54, 0 58.86666666666667, -11.7 52.06666666666667,-12.6 56.55)'), 1, 'endcap=round join=round'); figur 1 enter image description here

create table byffer2 as SELECT ST_Union( ARRAY[ ST_Buffer(ST_GeomFromText('LineString (-14.466666666666667 55.25,7.233333333333333 54)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (7.233333333333333 54, 0 58.86666666666667)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (0 58.86666666666667,-11.7 52.06666666666667)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (-11.7 52.06666666666667,-12.6 56.55)'), 1, 'endcap=round join=round')] ); figur 2 enter image description here

create table byffer3 as SELECT ST_Collect( ARRAY[ ST_Buffer(ST_GeomFromText('LineString (-14.466666666666667 55.25, 7.233333333333333 54)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (7.233333333333333 54,0 58.86666666666667)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (0 58.86666666666667,-11.7 52.06666666666667)'), 1, 'endcap=round join=round'), ST_Buffer(ST_GeomFromText('LineString (-11.7 52.06666666666667,-12.6 56.55)'), 1, 'endcap=round join=round')] ); figur 3 enter image description here

maybe I amused you : -)... with respect

  • 1
    Welcome, and be careful with the syntax : -)... – Cyril Feb 27 at 17:41

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.