Skip to content
Permalink
Browse files

Add method to QgsGeometry to remove interior rings from a polygon

With an optional area threshold
  • Loading branch information
nyalldawson committed Nov 28, 2016
1 parent 5ef16bf commit 57f482e2d908b1941c2641240010fa8fb726a9f2
@@ -57,6 +57,14 @@ class QgsCurvePolygon: public QgsSurface
/** Removes ring. Exterior ring is 0, first interior ring 1, ...*/
bool removeInteriorRing( int nr );

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

virtual void draw( QPainter& p ) const;
void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
bool transformZ = false );
@@ -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 minimumRingArea
* parameter is specified then only rings smaller than this minimum
* area will be removed.
* @note added in QGIS 3.0
*/
QgsGeometry removeInteriorRings( double minimumRingArea = -1 ) const;

/** Translate this geometry by dx, dy
@return 0 in case of success*/
int translate( double dx, double dy );
@@ -546,6 +546,24 @@ bool QgsCurvePolygon::removeInteriorRing( int nr )
return true;
}

void QgsCurvePolygon::removeInteriorRings( double minimumRingArea )
{
for ( int ringIndex = mInteriorRings.size() - 1; ringIndex >= 0; --ringIndex )
{
if ( minimumRingArea < 0 )
delete mInteriorRings.takeAt( ringIndex );
else
{
double area;
mInteriorRings.at( ringIndex )->sumUpArea( area );
if ( area < minimumRingArea )
delete mInteriorRings.takeAt( ringIndex );
}
}

clearCache();
}

void QgsCurvePolygon::draw( QPainter& p ) const
{
if ( mInteriorRings.size() < 1 )
@@ -83,6 +83,14 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
//! Removes ring. Exterior ring is 0, first interior ring 1, ...
bool removeInteriorRing( int nr );

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

virtual void draw( QPainter& p ) const override;
void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
bool transformZ = false ) override;
@@ -683,6 +683,41 @@ int QgsGeometry::addPart( const QgsGeometry& newPart )
return addPart( newPart.d->geometry->clone() );
}

QgsGeometry QgsGeometry::removeInteriorRings( double minimumRingArea ) const
{
if ( !d->geometry || type() != QgsWkbTypes::PolygonGeometry )
{
return QgsGeometry();
}

if ( QgsWkbTypes::isMultiType( d->geometry->wkbType() ) )
{
QList<QgsGeometry> parts = asGeometryCollection();
QList<QgsGeometry> results;
Q_FOREACH ( const QgsGeometry& part, parts )
{
QgsGeometry result = part.removeInteriorRings( minimumRingArea );
if ( result )
results << result;
}
if ( results.isEmpty() )
return QgsGeometry();

QgsGeometry first = results.takeAt( 0 );
Q_FOREACH ( const QgsGeometry& result, results )
{
first.addPart( result );
}
return first;
}
else
{
QgsCurvePolygon* newPoly = static_cast< QgsCurvePolygon* >( d->geometry->clone() );
newPoly->removeInteriorRings( minimumRingArea );
return QgsGeometry( newPoly );
}
}

int QgsGeometry::addPart( GEOSGeometry *newPart )
{
if ( !d->geometry || !newPart )
@@ -398,6 +398,14 @@ class CORE_EXPORT QgsGeometry
// TODO QGIS 3.0 returns an enum instead of a magic constant
int addPart( const QgsGeometry& newPart );

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

/** Translate this geometry by dx, dy
@return 0 in case of success*/
int translate( double dx, double dy );
@@ -3050,6 +3050,31 @@ void TestQgsGeometry::polygon()
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.12, 0.5 ), -0.02, 0.0000000001 );
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );

// remove interior rings
QgsLineString removeRingsExt;
removeRingsExt.setPoints( QList<QgsPointV2>() << QgsPointV2( 0, 0 ) << QgsPointV2( 1, 0 ) << QgsPointV2( 1, 1 ) << QgsPointV2( 0, 0 ) );
QgsPolygonV2 removeRings1;
removeRings1.removeInteriorRings();

removeRings1.setExteriorRing( boundary1.clone() );
removeRings1.removeInteriorRings();
QCOMPARE( removeRings1.numInteriorRings(), 0 );

// add interior rings
QgsLineString removeRingsRing1;
removeRingsRing1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0.1, 0.1 ) << QgsPointV2( 0.2, 0.1 ) << QgsPointV2( 0.2, 0.2 ) << QgsPointV2( 0.1, 0.1 ) );
QgsLineString removeRingsRing2;
removeRingsRing1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0.6, 0.8 ) << QgsPointV2( 0.9, 0.8 ) << QgsPointV2( 0.9, 0.9 ) << QgsPointV2( 0.6, 0.8 ) );
removeRings1.setInteriorRings( QList< QgsCurve* >() << removeRingsRing1.clone() << removeRingsRing2.clone() );

// remove ring with size filter
removeRings1.removeInteriorRings( 0.0075 );
QCOMPARE( removeRings1.numInteriorRings(), 1 );

// remove ring with no size filter
removeRings1.removeInteriorRings();
QCOMPARE( removeRings1.numInteriorRings(), 0 );

}

void TestQgsGeometry::multiPoint()
@@ -3620,5 +3620,31 @@ def testExtendLine(self):
self.assertTrue(compareWkt(result, exp, 0.00001),
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

def testRemoveRings(self):
empty = QgsGeometry()
self.assertFalse(empty.removeInteriorRings())

# not a polygon
point = QgsGeometry.fromWkt('Point(1 2)')
self.assertFalse(point.removeInteriorRings())

# polygon
polygon = QgsGeometry.fromWkt('Polygon((0 0, 1 0, 1 1, 0 0),(0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1))')
removed = polygon.removeInteriorRings()
exp = 'Polygon((0 0, 1 0, 1 1, 0 0))'
result = removed.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))

# multipolygon
multipolygon = QgsGeometry.fromWkt('MultiPolygon(((0 0, 1 0, 1 1, 0 0),(0.1 0.1, 0.2 0.1, 0.2 0.2, 0.1 0.1)),'
'((10 0, 11 0, 11 1, 10 0),(10.1 10.1, 10.2 0.1, 10.2 0.2, 10.1 0.1)))')
removed = multipolygon.removeInteriorRings()
exp = 'MultiPolygon(((0 0, 1 0, 1 1, 0 0)),((10 0, 11 0, 11 1, 10 0)))'
result = removed.exportToWkt()
self.assertTrue(compareWkt(result, exp, 0.00001),
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))


if __name__ == '__main__':
unittest.main()

0 comments on commit 57f482e

Please sign in to comment.
You can’t perform that action at this time.