diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml
index f325ebe7279..8919deffa1e 100644
--- a/python/plugins/processing/algs/help/qgis.yaml
+++ b/python/plugins/processing/algs/help/qgis.yaml
@@ -425,6 +425,15 @@ qgis:simplifygeometries: >
qgis:singlepartstomultipart:
+qgis:singlesidedbuffer: >
+ This algorithm buffers lines by a specified distance on one side of the line only.
+
+ The segments parameter controls the number of line segments to use to approximate a quarter circle when creating rounded buffers.
+
+ The join style parameter specifies whether round, mitre or beveled joins should be used when buffering corners in a line.
+
+ The mitre limit parameter is only applicable for mitre join styles, and controls the maximum distance from the buffer to use when creating a mitred join.
+
qgis:snappointstogrid: >
This algorithm modifies the position of points in a vector layer, so they fall in the coordinates of a grid.
diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
index a4e88ad1f1e..bd77e361815 100644
--- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
+++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py
@@ -153,6 +153,7 @@ from .PointOnSurface import PointOnSurface
from .OffsetLine import OffsetLine
from .PolygonCentroids import PolygonCentroids
from .Translate import Translate
+from .SingleSidedBuffer import SingleSidedBuffer
pluginPath = os.path.normpath(os.path.join(
os.path.split(os.path.dirname(__file__))[0], os.pardir))
@@ -207,7 +208,7 @@ class QGISAlgorithmProvider(AlgorithmProvider):
RectanglesOvalsDiamondsFixed(), MergeLines(),
BoundingBox(), Boundary(), PointOnSurface(),
OffsetLine(), PolygonCentroids(),
- Translate()
+ Translate(), SingleSidedBuffer()
]
if hasMatplotlib:
diff --git a/python/plugins/processing/algs/qgis/SingleSidedBuffer.py b/python/plugins/processing/algs/qgis/SingleSidedBuffer.py
new file mode 100644
index 00000000000..da539a3f20c
--- /dev/null
+++ b/python/plugins/processing/algs/qgis/SingleSidedBuffer.py
@@ -0,0 +1,118 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ SingleSidedBuffer.py
+ --------------------
+ Date : August 2016
+ Copyright : (C) 2016 by Nyall Dawson
+ Email : nyall dot dawson at gmail dot com
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+__author__ = 'Nyall Dawson'
+__date__ = 'August 2016'
+__copyright__ = '(C) 2016, Nyall Dawson'
+
+# This will get replaced with a git SHA1 when you do a git archive323
+
+__revision__ = '$Format:%H$'
+
+import os
+
+from qgis.core import QgsGeometry, QgsWkbTypes
+
+from processing.core.GeoAlgorithm import GeoAlgorithm
+from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
+from processing.core.parameters import ParameterVector, ParameterSelection, ParameterNumber
+from processing.core.outputs import OutputVector
+from processing.tools import dataobjects, vector
+
+pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
+
+
+class SingleSidedBuffer(GeoAlgorithm):
+
+ INPUT_LAYER = 'INPUT_LAYER'
+ OUTPUT_LAYER = 'OUTPUT_LAYER'
+ DISTANCE = 'DISTANCE'
+ SIDE = 'SIDE'
+ SEGMENTS = 'SEGMENTS'
+ JOIN_STYLE = 'JOIN_STYLE'
+ MITRE_LIMIT = 'MITRE_LIMIT'
+
+ def defineCharacteristics(self):
+ self.name, self.i18n_name = self.trAlgorithm('Single sided buffer')
+ self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
+
+ self.addParameter(ParameterVector(self.INPUT_LAYER,
+ self.tr('Input layer'), [ParameterVector.VECTOR_TYPE_LINE]))
+ self.addParameter(ParameterNumber(self.DISTANCE,
+ self.tr('Distance'), default=10.0))
+ self.sides = [self.tr('Left'),
+ 'Right']
+ self.addParameter(ParameterSelection(
+ self.SIDE,
+ self.tr('Side'),
+ self.sides))
+
+ self.addParameter(ParameterNumber(self.SEGMENTS,
+ self.tr('Segments'), 1, default=8))
+
+ self.join_styles = [self.tr('Round'),
+ 'Mitre',
+ 'Bevel']
+ self.addParameter(ParameterSelection(
+ self.JOIN_STYLE,
+ self.tr('Join style'),
+ self.join_styles))
+ self.addParameter(ParameterNumber(self.MITRE_LIMIT,
+ self.tr('Mitre limit'), 1, default=2))
+
+ self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Single sided buffers')))
+
+ def processAlgorithm(self, progress):
+ layer = dataobjects.getObjectFromUri(
+ self.getParameterValue(self.INPUT_LAYER))
+
+ writer = self.getOutputFromName(
+ self.OUTPUT_LAYER).getVectorWriter(
+ layer.fields().toList(),
+ QgsWkbTypes.Polygon,
+ layer.crs())
+
+ distance = self.getParameterValue(self.DISTANCE)
+ segments = int(self.getParameterValue(self.SEGMENTS))
+ join_style = self.getParameterValue(self.JOIN_STYLE) + 1
+ if self.getParameterValue(self.SIDE) == 0:
+ side = QgsGeometry.SideLeft
+ else:
+ side = QgsGeometry.SideRight
+ miter_limit = self.getParameterValue(self.MITRE_LIMIT)
+
+ features = vector.features(layer)
+ total = 100.0 / len(features)
+
+ for current, input_feature in enumerate(features):
+ output_feature = input_feature
+ input_geometry = input_feature.geometry()
+ if input_geometry:
+ output_geometry = input_geometry.singleSidedBuffer(distance, segments,
+ side, join_style, miter_limit)
+ if not output_geometry:
+ raise GeoAlgorithmExecutionException(
+ self.tr('Error calculating single sided buffer'))
+
+ output_feature.setGeometry(output_geometry)
+
+ writer.addFeature(output_feature)
+ progress.setPercentage(int(current * total))
+
+ del writer
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gfs b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gfs
new file mode 100644
index 00000000000..a8c2eef2aaf
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gfs
@@ -0,0 +1,15 @@
+
+
+ single_sided_buffer_line
+ single_sided_buffer_line
+ 3
+ EPSG:4326
+
+ 7
+ -1.00000
+ 11.00000
+ -3.00000
+ 5.70711
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gml b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gml
new file mode 100644
index 00000000000..d1158aeeba2
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line.gml
@@ -0,0 +1,48 @@
+
+
+
+
+ -1-3
+ 115.707106781186548
+
+
+
+
+
+ 11,5 9,3 9,2 6,2 6,3 8,3 8.01921471959677,3.195090322016128 8.076120467488714,3.38268343236509 8.168530387697455,3.555570233019602 8.292893218813452,3.707106781186547 10.292893218813452,5.707106781186548 11,5
+
+
+
+
+ 1,-1 -1,-1 -1,0 1,0 1,-1
+
+
+
+
+ 3,3 3,2 2,2 2,0 1,0 1,2 1.01921471959677,2.195090322016128 1.076120467488713,2.38268343236509 1.168530387697455,2.555570233019602 1.292893218813453,2.707106781186547 1.444429766980398,2.831469612302545 1.61731656763491,2.923879532511287 1.804909677983872,2.98078528040323 2,3 3,3
+
+
+
+
+ 5,1 3,1 3,2 5,2 5,1
+
+
+
+
+ 10,-3 7,-3 7,-2 10,-2 10,-3
+
+
+
+
+ 10,1 6,-3 5.292893218813452,-2.292893218813453 9.292893218813452,1.707106781186547 10,1
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gfs b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gfs
new file mode 100644
index 00000000000..a54045e71b1
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gfs
@@ -0,0 +1,15 @@
+
+
+ single_sided_buffer_line_mitre
+ single_sided_buffer_line_mitre
+ 3
+ EPSG:4326
+
+ 7
+ -1.00000
+ 11.70711
+ -4.00000
+ 5.00000
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gml b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gml
new file mode 100644
index 00000000000..17462a4f976
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_line_mitre.gml
@@ -0,0 +1,48 @@
+
+
+
+
+ -1-4
+ 11.707106781186555
+
+
+
+
+
+ 6,2 9,2 9,3 11,5 11.707106781186548,4.292893218813452 10.0,2.585786437626905 10,1 6,1 6,2
+
+
+
+
+ -1,-1 1,-1 1,-2 -1,-2 -1,-1
+
+
+
+
+ 2,0 2,2 3,2 3,3 4,3 4,1 3,1 3,0 2,0
+
+
+
+
+ 3,1 5,1 5,0 3,0 3,1
+
+
+
+
+ 7,-3 10,-3 10,-4 7,-4 7,-3
+
+
+
+
+ 6,-3 10,1 10.707106781186548,0.292893218813453 6.707106781186548,-3.707106781186547 6,-3
+
+
+
+
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gfs b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gfs
new file mode 100644
index 00000000000..ad6196b036e
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gfs
@@ -0,0 +1,14 @@
+
+
+ single_sided_buffer_multiline_bevel
+ single_sided_buffer_multiline_bevel
+ EPSG:4326
+
+ 4
+ -1.00000
+ 6.02404
+ -1.00000
+ 5.11935
+
+
+
diff --git a/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gml b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gml
new file mode 100644
index 00000000000..e3fd94fb93f
--- /dev/null
+++ b/python/plugins/processing/tests/testdata/expected/single_sided_buffer_multiline_bevel.gml
@@ -0,0 +1,33 @@
+
+
+
+
+ -1-1
+ 6.0240381903374715.119353882875417
+
+
+
+
+
+ 1,-1 -1,-1 -1,0 1,0 1,-1
+
+
+
+
+ 5,1 3,1 3,2 5,2 5,15,1 5.024184261036468,2.414779270633399 6.024038190337471,2.397687750474408 5.999853929301003,0.982908479841009 5,1
+
+
+
+
+
+
+
+
+ 3,3 3,2 2,2 2,0 1,0 1,2 2,3 3,35.459500959692898,4.119769673704415 2.944337811900192,4.04721689059501 2.915503652020259,5.046801099766013 5.430666799812965,5.119353882875417 5.459500959692898,4.1197696737044155.58042226487524,2.946833013435702 3,3 3.020599614854323,3.999787805420657 5.601021879729563,3.946620818856359 5.58042226487524,2.946833013435702
+
+
+
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
index 95c67b00a21..12896a84040 100644
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
@@ -821,3 +821,52 @@ tests:
OUTPUT_LAYER:
name: expected/lines_translated.gml
type: vector
+
+
+ - algorithm: qgis:singlesidedbuffer
+ name: Single sided buffer lines (left, round)
+ params:
+ DISTANCE: 1.0
+ INPUT_LAYER:
+ name: lines.gml
+ type: vector
+ JOIN_STYLE: '0'
+ MITRE_LIMIT: 2
+ SEGMENTS: 8
+ SIDE: '0'
+ results:
+ OUTPUT_LAYER:
+ name: expected/single_sided_buffer_line.gml
+ type: vector
+
+ - algorithm: qgis:singlesidedbuffer
+ name: Single sided buffer lines (Right, mitre)
+ params:
+ DISTANCE: 1.0
+ INPUT_LAYER:
+ name: lines.gml
+ type: vector
+ JOIN_STYLE: '1'
+ MITRE_LIMIT: 2
+ SEGMENTS: 8
+ SIDE: '1'
+ results:
+ OUTPUT_LAYER:
+ name: expected/single_sided_buffer_line_mitre.gml
+ type: vector
+
+ - algorithm: qgis:singlesidedbuffer
+ name: Single sided buffer multiline (bevel)
+ params:
+ DISTANCE: 1.0
+ INPUT_LAYER:
+ name: multilines.gml
+ type: vector
+ JOIN_STYLE: '2'
+ MITRE_LIMIT: 2
+ SEGMENTS: 8
+ SIDE: '0'
+ results:
+ OUTPUT_LAYER:
+ name: expected/single_sided_buffer_multiline_bevel.gml
+ type: vector
\ No newline at end of file