Skip to content

Commit

Permalink
Merge pull request #3808 from nyalldawson/processing_dupe_remove_rings
Browse files Browse the repository at this point in the history
[processing] Remove duplicate fill holes algorithm
  • Loading branch information
nyalldawson committed Nov 29, 2016
2 parents 5b97b4a + 6bb4934 commit 57f17e3
Show file tree
Hide file tree
Showing 20 changed files with 412 additions and 115 deletions.
19 changes: 17 additions & 2 deletions python/core/geometry/qgscurvepolygon.sip
Expand Up @@ -54,8 +54,23 @@ class QgsCurvePolygon: public QgsSurface
void setInteriorRings( const QList<QgsCurve*>& rings );
/** Adds an interior ring to the geometry (takes ownership)*/
virtual void addInteriorRing( QgsCurve* ring /Transfer/ );
/** Removes ring. Exterior ring is 0, first interior ring 1, ...*/
bool removeInteriorRing( int nr );

/**
* Removes an interior ring from the polygon. The first interior ring has index 0.
* The corresponding ring is removed from the polygon and deleted. If a ring was successfully removed
* the function will return true. It is not possible to remove the exterior ring using this method.
* @see removeInteriorRings()
*/
bool removeInteriorRing( int ringIndex );

/**
* Removes the interior rings from the polygon. If the minimumAllowedArea
* parameter is specified then only rings smaller than this minimum
* area will be removed.
* @note added in QGIS 3.0
* @see removeInteriorRing()
*/
void removeInteriorRings( double minimumAllowedArea = -1 );

virtual void draw( QPainter& p ) const;
void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
Expand Down
8 changes: 8 additions & 0 deletions python/core/geometry/qgsgeometry.sip
Expand Up @@ -345,6 +345,14 @@ class QgsGeometry
// TODO QGIS 3.0 returns an enum instead of a magic constant
int addPart( const QgsGeometry& newPart ) /PyName=addPartGeometry/;

/**
* Removes the interior rings from a (multi)polygon geometry. If the minimumAllowedArea
* parameter is specified then only rings smaller than this minimum
* area will be removed.
* @note added in QGIS 3.0
*/
QgsGeometry removeInteriorRings( double minimumAllowedArea = -1 ) const;

/** Translate this geometry by dx, dy
@return 0 in case of success*/
int translate( double dx, double dy );
Expand Down
2 changes: 2 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -130,6 +130,8 @@ qgis:deleteduplicategeometries: >
qgis:deleteholes: >
This algorithm takes a polygon layer and removes holes in polygons. It creates a new vector layer in which polygons with holes have been replaced by polygons with only their external ring. Attributes are not modified.

An optional minimum area parameter allows removing only holes which are smaller than a specified area threshold. Leaving this parameter as 0.0 results in all holes being removed.

qgis:densifygeometries:
This algorithm takes a polygon or line layer and generaates a new one in which the geometries have a larger number of vertices than the original one.

Expand Down
41 changes: 20 additions & 21 deletions python/plugins/processing/algs/qgis/DeleteHoles.py
Expand Up @@ -26,27 +26,41 @@

from qgis.core import QgsFeature, QgsGeometry
from processing.core.GeoAlgorithm import GeoAlgorithm
from processing.core.parameters import ParameterVector
from processing.core.parameters import (ParameterVector,
ParameterNumber)
from processing.core.outputs import OutputVector
from processing.tools import dataobjects, vector


class DeleteHoles(GeoAlgorithm):

INPUT = 'INPUT'
MIN_AREA = 'MIN_AREA'
OUTPUT = 'OUTPUT'

def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Delete holes')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
self.tags = self.tr('remove,delete,drop,holes,rings,fill')

self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON]))
self.addParameter(ParameterNumber(self.MIN_AREA,
self.tr('Remove holes with area less than'), 0, 10000000.0, default=0.0, optional=True))

self.addOutput(OutputVector(self.OUTPUT, self.tr('Cleaned'), datatype=[dataobjects.TYPE_VECTOR_POLYGON]))

def processAlgorithm(self, progress):
layer = dataobjects.getObjectFromUri(
self.getParameterValue(self.INPUT))
min_area = self.getParameterValue(self.MIN_AREA)
if min_area is not None:
try:
min_area = float(min_area)
except:
pass
if min_area == 0.0:
min_area = -1.0

writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(
layer.fields(),
Expand All @@ -56,28 +70,13 @@ def processAlgorithm(self, progress):
features = vector.features(layer)
total = 100.0 / len(features)

feat = QgsFeature()
for current, f in enumerate(features):
geometry = f.geometry()
if not geometry.isEmpty():
if geometry.isMultipart():
multi_polygon = geometry.asMultiPolygon()
for polygon in multi_polygon:
for ring in polygon[1:]:
polygon.remove(ring)
geometry = QgsGeometry.fromMultiPolygon(multi_polygon)

if f.hasGeometry():
if min_area is not None:
f.setGeometry(f.geometry().removeInteriorRings(min_area))
else:
polygon = geometry.asPolygon()
for ring in polygon[1:]:
polygon.remove(ring)
geometry = QgsGeometry.fromPolygon(polygon)
else:
geometry = QgsGeometry(None)

feat.setGeometry(geometry)
feat.setAttributes(f.attributes())
writer.addFeature(feat)
f.setGeometry(f.geometry().removeInteriorRings())
writer.addFeature(f)
progress.setPercentage(int(current * total))

del writer
1 change: 1 addition & 0 deletions python/plugins/processing/algs/qgis/PointsAlongGeometry.py
Expand Up @@ -55,6 +55,7 @@ def getIcon(self):
def defineCharacteristics(self):
self.name, self.i18n_name = self.trAlgorithm('Points along lines')
self.group, self.i18n_group = self.trAlgorithm('Vector geometry tools')
self.tags = self.tr('create,interpolate,points,lines')

self.addParameter(ParameterVector(self.INPUT,
self.tr('Input layer'),
Expand Down

This file was deleted.

44 changes: 0 additions & 44 deletions python/plugins/processing/algs/qgis/scripts/Fill_holes.py

This file was deleted.

30 changes: 30 additions & 0 deletions python/plugins/processing/tests/testdata/custom/remove_holes.gfs
@@ -0,0 +1,30 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>remove_holes</Name>
<ElementPath>remove_holes</ElementPath>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>2.00000</ExtentXMin>
<ExtentXMax>10.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>6.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>4</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
40 changes: 40 additions & 0 deletions python/plugins/processing/tests/testdata/custom/remove_holes.gml
@@ -0,0 +1,40 @@
<?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>2</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:remove_holes fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:remove_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:remove_holes fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 6,1 6,-3 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>elim</ogr:name>
<ogr:intval>2</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
</ogr:remove_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:remove_holes fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7,0 7,-2 9,-2 9,0 7,0</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:name>ASDF</ogr:name>
<ogr:intval>0</ogr:intval>
</ogr:remove_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:remove_holes fid="remove_holes.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,6 2,3 10,3 10,6 2,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>2.5,5.6 2.5,3.5 5.6,3.5 5.6,5.6 2.5,5.6</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>7.0,5.5 7,5 8,5 8.0,5.5 7.0,5.5</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs><gml:innerBoundaryIs><gml:LinearRing><gml:coordinates>6.0,4.5 6.0,3.5 9.0,3.5 9.0,4.5 6.0,4.5</gml:coordinates></gml:LinearRing></gml:innerBoundaryIs></gml:Polygon></ogr:geometryProperty>
</ogr:remove_holes>
</gml:featureMember>
</ogr:FeatureCollection>
@@ -0,0 +1,30 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>removed_holes</Name>
<ElementPath>removed_holes</ElementPath>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>4</FeatureCount>
<ExtentXMin>2.00000</ExtentXMin>
<ExtentXMax>10.00000</ExtentXMax>
<ExtentYMin>-3.00000</ExtentYMin>
<ExtentYMax>6.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>intval</Name>
<ElementPath>intval</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>floatval</Name>
<ElementPath>floatval</ElementPath>
<Type>Real</Type>
</PropertyDefn>
<PropertyDefn>
<Name>name</Name>
<ElementPath>name</ElementPath>
<Type>String</Type>
<Width>4</Width>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>
@@ -0,0 +1,40 @@
<?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>2</gml:X><gml:Y>-3</gml:Y></gml:coord>
<gml:coord><gml:X>10</gml:X><gml:Y>6</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>

<gml:featureMember>
<ogr:removed_holes fid="polys.4">
<ogr:intval>120</ogr:intval>
<ogr:floatval>-100291.43213</ogr:floatval>
</ogr:removed_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:removed_holes fid="polys.5">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>3,2 6,1 6,-3 2,-1 2,2 3,2</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>2</ogr:intval>
<ogr:floatval>3.33</ogr:floatval>
<ogr:name>elim</ogr:name>
</ogr:removed_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:removed_holes fid="polys.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>6,1 10,1 10,-3 6,-3 6,1</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
<ogr:intval>0</ogr:intval>
<ogr:name>ASDF</ogr:name>
</ogr:removed_holes>
</gml:featureMember>
<gml:featureMember>
<ogr:removed_holes fid="remove_holes.3">
<ogr:geometryProperty><gml:Polygon srsName="EPSG:4326"><gml:outerBoundaryIs><gml:LinearRing><gml:coordinates>2,6 2,3 10,3 10,6 2,6</gml:coordinates></gml:LinearRing></gml:outerBoundaryIs></gml:Polygon></ogr:geometryProperty>
</ogr:removed_holes>
</gml:featureMember>
</ogr:FeatureCollection>

0 comments on commit 57f17e3

Please sign in to comment.