Skip to content

Commit 4d60d0c

Browse files
committed
[FEATURE] Add option to QgsGeometry::smooth to not smooth
segments shorter than a certain threshold or sharp corners with an angle exceeding a threshold Expose the angle threshold to processing smooth algorithm Also: - optimise QgsGeometry::smooth for new geometry classes - Fix smooth does not work with geometries containing Z/M
1 parent 01da222 commit 4d60d0c

15 files changed

+429
-86
lines changed

doc/api_break.dox

+1
Original file line numberDiff line numberDiff line change
@@ -581,6 +581,7 @@ method to determine if a geometry is valid.</li>
581581
<li>static bool compare( const QgsPolyline& p1, const QgsPolyline& p2, double epsilon ) has been renamed to comparePolylines</li>
582582
<li>static bool compare( const QgsPolygon& p1, const QgsPolygon& p2, double epsilon ) has been renamed to comparePolygons</li>
583583
<li>static bool compare( const QgsMultiPolygon& p1, const QgsMultiPolygon& p2, double epsilon ) has been renamed to compareMultiPolygons</li>
584+
<li>smoothLine and smoothPolygon are no longer public API (use smooth() instead)</li>
584585
</ul>
585586

586587
\subsection qgis_api_break_3_0_QgsGeometryAnalyzer QgsGeometryAnalyzer

python/core/geometry/qgsgeometry.sip

+4-6
Original file line numberDiff line numberDiff line change
@@ -849,14 +849,12 @@ class QgsGeometry
849849
* @param offset fraction of line to create new vertices along, between 0 and 1.0
850850
* eg the default value of 0.25 will create new vertices 25% and 75% along each line segment
851851
* of the geometry for each iteration. Smaller values result in "tighter" smoothing.
852+
* @param minimumDistance minimum segment length to apply smoothing to
853+
* @param maxAngle maximum angle at node (0-180) at which smoothing will be applied
852854
* @note added in 2.9
853855
*/
854-
QgsGeometry smooth( const unsigned int iterations, const double offset ) const;
855-
856-
/** Smooths a polygon using the Chaikin algorithm*/
857-
QgsPolygon smoothPolygon( const QgsPolygon &polygon, const unsigned int iterations = 1, const double offset = 0.25 ) const;
858-
/** Smooths a polyline using the Chaikin algorithm*/
859-
QgsPolyline smoothLine( const QgsPolyline &polyline, const unsigned int iterations = 1, const double offset = 0.25 ) const;
856+
QgsGeometry smooth( const unsigned int iterations = 1, const double offset = 0.25,
857+
double minimumDistance = -1.0, double maxAngle = 180.0 ) const;
860858

861859
/** Creates and returns a new geometry engine
862860
*/

python/plugins/processing/algs/help/qgis.yaml

+9
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,15 @@ qgis:singlesidedbuffer: >
444444

445445
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.
446446

447+
qgis:smoothgeometry: >
448+
This algorithm smooths the geometries in a line or polygon layer. It creates a new layer with the same features as the ones in the input layer, but with geometries containing a higher number of vertices and corners in the geometries smoothed out.
449+
450+
The iterations parameter dictates how many smoothing iterations will be applied to each geometry. A higher number of iterations results in smoother geometries with the cost of greater number of nodes in the geometries.
451+
452+
The offset parameter controls how "tightly" the smoothed geometries follow the original geometries. Smaller values results in a tighter fit, and larger values will create a looser fit.
453+
454+
The maximum angle parameter can be used to prevent smoothing of nodes with large angles. Any node where the angle of the segments to either side is larger then this will not be smoothed. Eg setting the maximum angle to 90 degrees or lower would preserve right angles in the geometry.
455+
447456
qgis:snappointstogrid: >
448457
This algorithm modifies the position of points in a vector layer, so they fall in the coordinates of a grid.
449458

python/plugins/processing/algs/qgis/Smooth.py

+13-12
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ class Smooth(GeoAlgorithm):
3838
INPUT_LAYER = 'INPUT_LAYER'
3939
OUTPUT_LAYER = 'OUTPUT_LAYER'
4040
ITERATIONS = 'ITERATIONS'
41+
MAX_ANGLE = 'MAX_ANGLE'
4142
OFFSET = 'OFFSET'
4243

4344
def defineCharacteristics(self):
@@ -50,37 +51,37 @@ def defineCharacteristics(self):
5051
self.tr('Iterations'), default=1, minValue=1, maxValue=10))
5152
self.addParameter(ParameterNumber(self.OFFSET,
5253
self.tr('Offset'), default=0.25, minValue=0.0, maxValue=0.5))
54+
self.addParameter(ParameterNumber(self.MAX_ANGLE,
55+
self.tr('Maximum node angle to smooth'), default=180.0, minValue=0.0, maxValue=180.0))
5356
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Smoothed')))
5457

5558
def processAlgorithm(self, progress):
5659
layer = dataobjects.getObjectFromUri(
5760
self.getParameterValue(self.INPUT_LAYER))
5861
iterations = self.getParameterValue(self.ITERATIONS)
5962
offset = self.getParameterValue(self.OFFSET)
63+
max_angle = self.getParameterValue(self.MAX_ANGLE)
6064

6165
writer = self.getOutputFromName(
6266
self.OUTPUT_LAYER).getVectorWriter(
6367
layer.fields().toList(),
6468
layer.wkbType(),
6569
layer.crs())
6670

67-
outFeat = QgsFeature()
68-
6971
features = vector.features(layer)
7072
total = 100.0 / len(features)
7173

72-
for current, inFeat in enumerate(features):
73-
inGeom = inFeat.geometry()
74-
attrs = inFeat.attributes()
74+
for current, input_feature in enumerate(features):
75+
output_feature = input_feature
76+
if input_feature.geometry():
77+
output_geometry = input_feature.geometry().smooth(iterations, offset, -1, max_angle)
78+
if not output_geometry:
79+
raise GeoAlgorithmExecutionException(
80+
self.tr('Error smoothing geometry'))
7581

76-
outGeom = inGeom.smooth(iterations, offset)
77-
if not outGeom:
78-
raise GeoAlgorithmExecutionException(
79-
self.tr('Error smoothing geometry'))
82+
output_feature.setGeometry(output_geometry)
8083

81-
outFeat.setGeometry(outGeom)
82-
outFeat.setAttributes(attrs)
83-
writer.addFeature(outFeat)
84+
writer.addFeature(output_feature)
8485
progress.setPercentage(int(current * total))
8586

8687
del writer
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>smoothed_lines</Name>
4+
<ElementPath>smoothed_lines</ElementPath>
5+
<GeometryType>2</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>7</FeatureCount>
9+
<ExtentXMin>-1.00000</ExtentXMin>
10+
<ExtentXMax>11.00000</ExtentXMax>
11+
<ExtentYMin>-3.00000</ExtentYMin>
12+
<ExtentYMax>5.00000</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
</GMLFeatureClass>
15+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:smoothed_lines fid="lines.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,2 8.25,2.0 9.0,2.25 9.0,2.75 9.5,3.5 11,5</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
</ogr:smoothed_lines>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:smoothed_lines fid="lines.1">
21+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
22+
</ogr:smoothed_lines>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:smoothed_lines fid="lines.2">
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2.0,1.5 2.25,2.0 2.75,2.0 3.0,2.25 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
27+
</ogr:smoothed_lines>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:smoothed_lines fid="lines.3">
31+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 5,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
32+
</ogr:smoothed_lines>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:smoothed_lines fid="lines.4">
36+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
37+
</ogr:smoothed_lines>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:smoothed_lines fid="lines.5">
41+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
42+
</ogr:smoothed_lines>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:smoothed_lines fid="lines.6">
46+
</ogr:smoothed_lines>
47+
</gml:featureMember>
48+
</ogr:FeatureCollection>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<GMLFeatureClassList>
2+
<GMLFeatureClass>
3+
<Name>smoothed_lines_max_angle</Name>
4+
<ElementPath>smoothed_lines_max_angle</ElementPath>
5+
<GeometryType>2</GeometryType>
6+
<SRSName>EPSG:4326</SRSName>
7+
<DatasetSpecificInfo>
8+
<FeatureCount>7</FeatureCount>
9+
<ExtentXMin>-1.00000</ExtentXMin>
10+
<ExtentXMax>11.00000</ExtentXMax>
11+
<ExtentYMin>-3.00000</ExtentYMin>
12+
<ExtentYMax>5.00000</ExtentYMax>
13+
</DatasetSpecificInfo>
14+
</GMLFeatureClass>
15+
</GMLFeatureClassList>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<ogr:FeatureCollection
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation=""
5+
xmlns:ogr="http://ogr.maptools.org/"
6+
xmlns:gml="http://www.opengis.net/gml">
7+
<gml:boundedBy>
8+
<gml:Box>
9+
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
10+
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
11+
</gml:Box>
12+
</gml:boundedBy>
13+
14+
<gml:featureMember>
15+
<ogr:smoothed_lines_max_angle fid="lines.0">
16+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,2 9,2 9.0,2.75 9.5,3.5 11,5</gml:coordinates></gml:LineString></ogr:geometryProperty>
17+
</ogr:smoothed_lines_max_angle>
18+
</gml:featureMember>
19+
<gml:featureMember>
20+
<ogr:smoothed_lines_max_angle fid="lines.1">
21+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
22+
</ogr:smoothed_lines_max_angle>
23+
</gml:featureMember>
24+
<gml:featureMember>
25+
<ogr:smoothed_lines_max_angle fid="lines.2">
26+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
27+
</ogr:smoothed_lines_max_angle>
28+
</gml:featureMember>
29+
<gml:featureMember>
30+
<ogr:smoothed_lines_max_angle fid="lines.3">
31+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 5,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
32+
</ogr:smoothed_lines_max_angle>
33+
</gml:featureMember>
34+
<gml:featureMember>
35+
<ogr:smoothed_lines_max_angle fid="lines.4">
36+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
37+
</ogr:smoothed_lines_max_angle>
38+
</gml:featureMember>
39+
<gml:featureMember>
40+
<ogr:smoothed_lines_max_angle fid="lines.5">
41+
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
42+
</ogr:smoothed_lines_max_angle>
43+
</gml:featureMember>
44+
<gml:featureMember>
45+
<ogr:smoothed_lines_max_angle fid="lines.6">
46+
</ogr:smoothed_lines_max_angle>
47+
</gml:featureMember>
48+
</ogr:FeatureCollection>

python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

+28
Original file line numberDiff line numberDiff line change
@@ -965,3 +965,31 @@ tests:
965965
OUTPUT:
966966
name: expected/simplify_grid_lines.gml
967967
type: vector
968+
969+
- algorithm: qgis:smoothgeometry
970+
name: Smooth (lines)
971+
params:
972+
INPUT_LAYER:
973+
name: lines.gml
974+
type: vector
975+
ITERATIONS: 1
976+
MAX_ANGLE: 180.0
977+
OFFSET: 0.25
978+
results:
979+
OUTPUT_LAYER:
980+
name: expected/smoothed_lines.gml
981+
type: vector
982+
983+
- algorithm: qgis:smoothgeometry
984+
name: Smooth (lines, with max angle)
985+
params:
986+
INPUT_LAYER:
987+
name: lines.gml
988+
type: vector
989+
ITERATIONS: 1
990+
MAX_ANGLE: 60.0
991+
OFFSET: 0.25
992+
results:
993+
OUTPUT_LAYER:
994+
name: expected/smoothed_lines_max_angle.gml
995+
type: vector

0 commit comments

Comments
 (0)