Skip to content

Commit

Permalink
[FEATURE] Add option to QgsGeometry::smooth to not smooth
Browse files Browse the repository at this point in the history
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
  • Loading branch information
nyalldawson committed Sep 2, 2016
1 parent 01da222 commit 4d60d0c
Show file tree
Hide file tree
Showing 15 changed files with 429 additions and 86 deletions.
1 change: 1 addition & 0 deletions doc/api_break.dox
Expand Up @@ -581,6 +581,7 @@ method to determine if a geometry is valid.</li>
<li>static bool compare( const QgsPolyline& p1, const QgsPolyline& p2, double epsilon ) has been renamed to comparePolylines</li>
<li>static bool compare( const QgsPolygon& p1, const QgsPolygon& p2, double epsilon ) has been renamed to comparePolygons</li>
<li>static bool compare( const QgsMultiPolygon& p1, const QgsMultiPolygon& p2, double epsilon ) has been renamed to compareMultiPolygons</li>
<li>smoothLine and smoothPolygon are no longer public API (use smooth() instead)</li>
</ul>

\subsection qgis_api_break_3_0_QgsGeometryAnalyzer QgsGeometryAnalyzer
Expand Down
10 changes: 4 additions & 6 deletions python/core/geometry/qgsgeometry.sip
Expand Up @@ -849,14 +849,12 @@ class QgsGeometry
* @param offset fraction of line to create new vertices along, between 0 and 1.0
* eg the default value of 0.25 will create new vertices 25% and 75% along each line segment
* of the geometry for each iteration. Smaller values result in "tighter" smoothing.
* @param minimumDistance minimum segment length to apply smoothing to
* @param maxAngle maximum angle at node (0-180) at which smoothing will be applied
* @note added in 2.9
*/
QgsGeometry smooth( const unsigned int iterations, const double offset ) const;

/** Smooths a polygon using the Chaikin algorithm*/
QgsPolygon smoothPolygon( const QgsPolygon &polygon, const unsigned int iterations = 1, const double offset = 0.25 ) const;
/** Smooths a polyline using the Chaikin algorithm*/
QgsPolyline smoothLine( const QgsPolyline &polyline, const unsigned int iterations = 1, const double offset = 0.25 ) const;
QgsGeometry smooth( const unsigned int iterations = 1, const double offset = 0.25,
double minimumDistance = -1.0, double maxAngle = 180.0 ) const;

/** Creates and returns a new geometry engine
*/
Expand Down
9 changes: 9 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -444,6 +444,15 @@ qgis:singlesidedbuffer: >

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:smoothgeometry: >
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.

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.

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.

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.

qgis:snappointstogrid: >
This algorithm modifies the position of points in a vector layer, so they fall in the coordinates of a grid.

Expand Down
25 changes: 13 additions & 12 deletions python/plugins/processing/algs/qgis/Smooth.py
Expand Up @@ -38,6 +38,7 @@ class Smooth(GeoAlgorithm):
INPUT_LAYER = 'INPUT_LAYER'
OUTPUT_LAYER = 'OUTPUT_LAYER'
ITERATIONS = 'ITERATIONS'
MAX_ANGLE = 'MAX_ANGLE'
OFFSET = 'OFFSET'

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

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT_LAYER))
iterations = self.getParameterValue(self.ITERATIONS)
offset = self.getParameterValue(self.OFFSET)
max_angle = self.getParameterValue(self.MAX_ANGLE)

writer = self.getOutputFromName(
self.OUTPUT_LAYER).getVectorWriter(
layer.fields().toList(),
layer.wkbType(),
layer.crs())

outFeat = QgsFeature()

features = vector.features(layer)
total = 100.0 / len(features)

for current, inFeat in enumerate(features):
inGeom = inFeat.geometry()
attrs = inFeat.attributes()
for current, input_feature in enumerate(features):
output_feature = input_feature
if input_feature.geometry():
output_geometry = input_feature.geometry().smooth(iterations, offset, -1, max_angle)
if not output_geometry:
raise GeoAlgorithmExecutionException(
self.tr('Error smoothing geometry'))

outGeom = inGeom.smooth(iterations, offset)
if not outGeom:
raise GeoAlgorithmExecutionException(
self.tr('Error smoothing geometry'))
output_feature.setGeometry(output_geometry)

outFeat.setGeometry(outGeom)
outFeat.setAttributes(attrs)
writer.addFeature(outFeat)
writer.addFeature(output_feature)
progress.setPercentage(int(current * total))

del writer
@@ -0,0 +1,15 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>smoothed_lines</Name>
<ElementPath>smoothed_lines</ElementPath>
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>7</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>11.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>5.00000</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:smoothed_lines fid="lines.0">
<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>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.2">
<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>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 5,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.4">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines fid="lines.6">
</ogr:smoothed_lines>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,15 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>smoothed_lines_max_angle</Name>
<ElementPath>smoothed_lines_max_angle</ElementPath>
<GeometryType>2</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>7</FeatureCount>
<ExtentXMin>-1.00000</ExtentXMin>
<ExtentXMax>11.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>5.00000</ExtentYMax>
</DatasetSpecificInfo>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>-1</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>11</gml:X><gml:Y>5</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.0">
<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>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.1">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>-1,-1 1,-1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.2">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>2,0 2,2 3,2 3,3</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.3">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>3,1 5,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.4">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>7,-3 10,-3</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.5">
<ogr:geometryProperty><gml:LineString srsName="EPSG:4326"><gml:coordinates>6,-3 10,1</gml:coordinates></gml:LineString></ogr:geometryProperty>
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
<gml:featureMember>
<ogr:smoothed_lines_max_angle fid="lines.6">
</ogr:smoothed_lines_max_angle>
</gml:featureMember>
</ogr:FeatureCollection>
28 changes: 28 additions & 0 deletions python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml
Expand Up @@ -965,3 +965,31 @@ tests:
OUTPUT:
name: expected/simplify_grid_lines.gml
type: vector

- algorithm: qgis:smoothgeometry
name: Smooth (lines)
params:
INPUT_LAYER:
name: lines.gml
type: vector
ITERATIONS: 1
MAX_ANGLE: 180.0
OFFSET: 0.25
results:
OUTPUT_LAYER:
name: expected/smoothed_lines.gml
type: vector

- algorithm: qgis:smoothgeometry
name: Smooth (lines, with max angle)
params:
INPUT_LAYER:
name: lines.gml
type: vector
ITERATIONS: 1
MAX_ANGLE: 60.0
OFFSET: 0.25
results:
OUTPUT_LAYER:
name: expected/smoothed_lines_max_angle.gml
type: vector

0 comments on commit 4d60d0c

Please sign in to comment.