5

I have have a line feature which is split in equal pieces (v.split) of 20m. Now I try to Buffer each line segment only on the sides with 5m to get equal sized polygons in QGIS 3.1, like shown in the picture below. enter image description here

Running the buffer function in QGIS with:

{ 'DISSOLVE' : False, 
  'DISTANCE' : 5, 
  'END_CAP_STYLE' : 2, 
  'INPUT' : '[...mypath...]', 
  'JOIN_STYLE' : 1, 
  'MITER_LIMIT' : 3, 
  'OUTPUT' : 'TEMPORARY_OUTPUT', 
  'SEGMENTS' : 5 }

creates similar looking result but buffers also the line end which mix up the length defined by the line segment and leads to overlapping polygons (see I)

Using this input:

{ 'DISSOLVE' : False, 
  'DISTANCE' : 5, 
  'END_CAP_STYLE' : 1, 
  'INPUT' : '[...mypath...]', 
  'JOIN_STYLE' : 1, 
  'MITER_LIMIT' : 2, 
  'OUTPUT' : 'TEMPORARY_OUTPUT', 
  'SEGMENTS' : 5 } 

also leads to overlap and this edgy transition zones in the corner. (see II)

enter image description here

How can I create a result looks like described in the first picture?

Update:

I tried to implement xunilk's answer, but I'm stucking at some point. I have a line feature separated in 20m pieces (except the last is 18m), with an individual id.

enter image description here

but I'm getting this error message:

File "<input>", line 1, in <module>
  File "<string>", line 113, in <module>
TypeError: LineString geometry cannot be converted to a point.
Only Point types are permitted.

enter image description here

4
  • Perhaps you can create lines perpendicular to the ends of the line, use those lines to split the first result, then select and the end-of-line polygons by selecting polygons smaller than a certain area.
    – csk
    Commented Mar 18, 2020 at 17:48
  • This is typically one of the problems that at first glance seems easy, but then when one starts thinking about how to implement it, it is quite difficult... I'll try to have a hack at it just because it is facinating - but no promise. Commented Mar 18, 2020 at 18:23
  • Distance in external line "elbow" is not 20 m (first picture).
    – xunilk
    Commented Mar 18, 2020 at 19:50
  • Thx! I edited pic 1.
    – DrSnuggles
    Commented Mar 18, 2020 at 21:28

2 Answers 2

5

Based in azimuths and rectangular rotated buffers for each feature, I developed a code that produces a buffer for entire layer with desired characteristics.

from math import fabs, cos, sin, pi

registry = QgsProject.instance()

line = registry.mapLayersByName('line4')

feats_line = [ feat for feat in line[0].getFeatures() ]

n = len(feats_line)

points_line = [ feat.geometry().asMultiPolyline()[0][0] for feat in feats_line ]

points_line.append(feats_line[n-1].geometry().asMultiPolyline()[0][1])

points_line2 = [ QgsPointXY((points_line[i].x()+points_line[i+1].x())/2, 
                            (points_line[i].y()+points_line[i+1].y())/2) for i in range(n) ]

epsg = line[0].crs().postgisSrid()

uri = "Polygon?crs=epsg:" + str(epsg) + "&field=id:integer""&index=yes"

mem_layer = QgsVectorLayer(uri,
                           'buffer',
                           'memory')

prov = mem_layer.dataProvider()

az = [ points_line[i].azimuth(points_line[i+1]) for i in range(n) ]

for i in range(len(az)-1):
    if fabs(az[i] - az[i+1]) > 1e-6:
        ver = az[i+1]
        break

idx = az.index(ver)

geoms = []

for i, point in enumerate(points_line2):
    new_feat = QgsFeature()
    new_feat.setAttributes([i])

    bbox = QgsGeometry.fromPointXY(point).buffer(10, -1).boundingBox()

    tmp_feat = bbox.asWktPolygon()

    xmin1,ymin1,xmax1,ymax1 = bbox.toRectF().getCoords()
    xmin2,ymin2,xmax2,ymax2 = QgsGeometry.fromPointXY(point).buffer(5, -1).boundingBox().toRectF().getCoords()

    p1 = QgsPointXY(xmin1, ymax2)
    p2 = QgsPointXY(xmax1, ymin2)
    new_ext = QgsRectangle(p1,p2)
    new_tmp_feat = new_ext.asWktPolygon()
    geom = QgsGeometry.fromWkt(new_tmp_feat)
    geoms.append(geom)
    geom.rotate(az[i]-90, point)
    new_feat.setGeometry(geom)

    if i != idx:
        prov.addFeatures([new_feat])

pol1 = geoms[idx-1]
pol2 = geoms[idx+1]

#lines 3 and 4
d = 20
delta_x = d*cos((90 - az[idx-1])*pi/180)
delta_y = d*sin((90 - az[idx-1])*pi/180)

new_x3 = pol1.asPolygon()[0][1].x() + delta_x
new_y3 = pol1.asPolygon()[0][1].y() + delta_y

new_x4 = pol1.asPolygon()[0][2].x() + delta_x
new_y4 = pol1.asPolygon()[0][2].y() + delta_y

new_pt3 = QgsPointXY(new_x3, new_y3)
new_pt4 = QgsPointXY(new_x4, new_y4)

line3 = [ pol1.asPolygon()[0][1], new_pt3 ] 

new_line3 = QgsGeometry.fromPolylineXY(line3)

line4 = [ pol1.asPolygon()[0][2], new_pt4 ] 

new_line4 = QgsGeometry.fromPolylineXY(line4)

#lines 5 and 6
d = -20

delta_x = d*cos((90 - az[idx+1])*pi/180)
delta_y = d*sin((90 - az[idx+1])*pi/180)

new_x5 = pol2.asPolygon()[0][0].x() + delta_x
new_y5 = pol2.asPolygon()[0][0].y() + delta_y

new_pt5 = QgsPointXY(new_x5, new_y5)

new_x6 = pol2.asPolygon()[0][3].x() + delta_x
new_y6 = pol2.asPolygon()[0][3].y() + delta_y

new_pt6 = QgsPointXY(new_x6, new_y6)

line5 = [ pol2.asPolygon()[0][0], new_pt5 ] 

new_line5 = QgsGeometry.fromPolylineXY(line5)

line6 = [ pol2.asPolygon()[0][3], new_pt6 ] 

new_line6 = QgsGeometry.fromPolylineXY(line6)

#intersection lines(3, 5), lines(4, 6)

new_pt1 = new_line3.intersection(new_line5).asPoint()
new_pt2 = new_line4.intersection(new_line6).asPoint()

new_pol = [[ pol1.asPolygon()[0][1], new_pt1, pol2.asPolygon()[0][0], 
             pol2.asPolygon()[0][3], new_pt2, pol1.asPolygon()[0][2]] ]

new_geom = QgsGeometry.fromPolygonXY(new_pol)

new_feat.setAttributes([n])
new_feat.setGeometry(new_geom)
prov.addFeatures([new_feat])

QgsProject.instance().addMapLayer(mem_layer)

I tried it out with line layer of following image:

enter image description here

After running above code in Python Console of QGIS, it was produced a layer as expected:

enter image description here

2
  • I'd like to reproduce your results, but I get a error: File "<input>", line 1, in <module> File "<string>", line 113, in <module> TypeError: LineString geometry cannot be converted to a point. Only Point types are permitted.
    – DrSnuggles
    Commented Mar 20, 2020 at 16:03
  • Please, edit your question and put in it that error message.
    – xunilk
    Commented Mar 20, 2020 at 16:07
0

I have QGIS 3.12 installed and using Vector / Geoprocessing Tools / Buffer... I believe I am achieving exactly what you ask for. I do not see any problems with the result as you describe, and no need to use python code for it:

Line buffer example

QGIS version: 3.12.0-București
QGIS code revision: cd141490ec
Qt version: 5.11.2
GDAL version: 3.0.4
GEOS version: 3.8.1-CAPI-1.13.3
PROJ version: Rel. 6.3.1, February 10th, 2020
Processing algorithm…
Algorithm 'Buffer' starting…
Input parameters:
{ 'DISSOLVE' : False, 'DISTANCE' : 5, 'END_CAP_STYLE' : 1, 'INPUT' : 'LineString?crs=EPSG:3857&field=id:long(0,0)&field=left:double(0,0)&field=top:double(0,0)&field=right:double(0,0)&field=bottom:double(0,0)&uid={a90981ab-c715-488f-b7d4-222d2d20858d}', 'JOIN_STYLE' : 1, 'MITER_LIMIT' : 10, 'OUTPUT' : 'TEMPORARY_OUTPUT', 'SEGMENTS' : 5 }

Execution completed in 0.02 seconds
Results:
{'OUTPUT': 'Buffered_5f3f1a42_4386_4720_b0d5_ce77963b85e9'}

Loading resulting layers
Algorithm 'Buffer' finished

So maybe just problem with old version and updating QGIS would do?

Also you can use advanced buffering functionality in PostGIS (PostgreSQL). Your problem of buffer with flat caps and mitre joins are then matter of one SQL using ST_Buffer function with properly set buffer style parameters.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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