Skip to content

Commit 57f482e

Browse files
committed
Add method to QgsGeometry to remove interior rings from a polygon
With an optional area threshold
1 parent 5ef16bf commit 57f482e

File tree

8 files changed

+136
-0
lines changed

8 files changed

+136
-0
lines changed

python/core/geometry/qgscurvepolygon.sip

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class QgsCurvePolygon: public QgsSurface
5757
/** Removes ring. Exterior ring is 0, first interior ring 1, ...*/
5858
bool removeInteriorRing( int nr );
5959

60+
/**
61+
* Removes the interior rings from the polygon. If the minimumRingArea
62+
* parameter is specified then only rings smaller than this minimum
63+
* area will be removed.
64+
* @note added in QGIS 3.0
65+
*/
66+
void removeInteriorRings( double minimumRingArea = -1 );
67+
6068
virtual void draw( QPainter& p ) const;
6169
void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
6270
bool transformZ = false );

python/core/geometry/qgsgeometry.sip

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -345,6 +345,14 @@ class QgsGeometry
345345
// TODO QGIS 3.0 returns an enum instead of a magic constant
346346
int addPart( const QgsGeometry& newPart ) /PyName=addPartGeometry/;
347347

348+
/**
349+
* Removes the interior rings from a (multi)polygon geometry. If the minimumRingArea
350+
* parameter is specified then only rings smaller than this minimum
351+
* area will be removed.
352+
* @note added in QGIS 3.0
353+
*/
354+
QgsGeometry removeInteriorRings( double minimumRingArea = -1 ) const;
355+
348356
/** Translate this geometry by dx, dy
349357
@return 0 in case of success*/
350358
int translate( double dx, double dy );

src/core/geometry/qgscurvepolygon.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -546,6 +546,24 @@ bool QgsCurvePolygon::removeInteriorRing( int nr )
546546
return true;
547547
}
548548

549+
void QgsCurvePolygon::removeInteriorRings( double minimumRingArea )
550+
{
551+
for ( int ringIndex = mInteriorRings.size() - 1; ringIndex >= 0; --ringIndex )
552+
{
553+
if ( minimumRingArea < 0 )
554+
delete mInteriorRings.takeAt( ringIndex );
555+
else
556+
{
557+
double area;
558+
mInteriorRings.at( ringIndex )->sumUpArea( area );
559+
if ( area < minimumRingArea )
560+
delete mInteriorRings.takeAt( ringIndex );
561+
}
562+
}
563+
564+
clearCache();
565+
}
566+
549567
void QgsCurvePolygon::draw( QPainter& p ) const
550568
{
551569
if ( mInteriorRings.size() < 1 )

src/core/geometry/qgscurvepolygon.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,14 @@ class CORE_EXPORT QgsCurvePolygon: public QgsSurface
8383
//! Removes ring. Exterior ring is 0, first interior ring 1, ...
8484
bool removeInteriorRing( int nr );
8585

86+
/**
87+
* Removes the interior rings from the polygon. If the minimumRingArea
88+
* parameter is specified then only rings smaller than this minimum
89+
* area will be removed.
90+
* @note added in QGIS 3.0
91+
*/
92+
void removeInteriorRings( double minimumRingArea = -1 );
93+
8694
virtual void draw( QPainter& p ) const override;
8795
void transform( const QgsCoordinateTransform& ct, QgsCoordinateTransform::TransformDirection d = QgsCoordinateTransform::ForwardTransform,
8896
bool transformZ = false ) override;

src/core/geometry/qgsgeometry.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,41 @@ int QgsGeometry::addPart( const QgsGeometry& newPart )
683683
return addPart( newPart.d->geometry->clone() );
684684
}
685685

686+
QgsGeometry QgsGeometry::removeInteriorRings( double minimumRingArea ) const
687+
{
688+
if ( !d->geometry || type() != QgsWkbTypes::PolygonGeometry )
689+
{
690+
return QgsGeometry();
691+
}
692+
693+
if ( QgsWkbTypes::isMultiType( d->geometry->wkbType() ) )
694+
{
695+
QList<QgsGeometry> parts = asGeometryCollection();
696+
QList<QgsGeometry> results;
697+
Q_FOREACH ( const QgsGeometry& part, parts )
698+
{
699+
QgsGeometry result = part.removeInteriorRings( minimumRingArea );
700+
if ( result )
701+
results << result;
702+
}
703+
if ( results.isEmpty() )
704+
return QgsGeometry();
705+
706+
QgsGeometry first = results.takeAt( 0 );
707+
Q_FOREACH ( const QgsGeometry& result, results )
708+
{
709+
first.addPart( result );
710+
}
711+
return first;
712+
}
713+
else
714+
{
715+
QgsCurvePolygon* newPoly = static_cast< QgsCurvePolygon* >( d->geometry->clone() );
716+
newPoly->removeInteriorRings( minimumRingArea );
717+
return QgsGeometry( newPoly );
718+
}
719+
}
720+
686721
int QgsGeometry::addPart( GEOSGeometry *newPart )
687722
{
688723
if ( !d->geometry || !newPart )

src/core/geometry/qgsgeometry.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,14 @@ class CORE_EXPORT QgsGeometry
398398
// TODO QGIS 3.0 returns an enum instead of a magic constant
399399
int addPart( const QgsGeometry& newPart );
400400

401+
/**
402+
* Removes the interior rings from a (multi)polygon geometry. If the minimumRingArea
403+
* parameter is specified then only rings smaller than this minimum
404+
* area will be removed.
405+
* @note added in QGIS 3.0
406+
*/
407+
QgsGeometry removeInteriorRings( double minimumRingArea = -1 ) const;
408+
401409
/** Translate this geometry by dx, dy
402410
@return 0 in case of success*/
403411
int translate( double dx, double dy );

tests/src/core/testqgsgeometry.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3050,6 +3050,31 @@ void TestQgsGeometry::polygon()
30503050
QGSCOMPARENEAR( pd.pointDistanceToBoundary( 0.12, 0.5 ), -0.02, 0.0000000001 );
30513051
QGSCOMPARENEAR( pd.pointDistanceToBoundary( -0.1, 0.5 ), -0.1, 0.0000000001 );
30523052

3053+
// remove interior rings
3054+
QgsLineString removeRingsExt;
3055+
removeRingsExt.setPoints( QList<QgsPointV2>() << QgsPointV2( 0, 0 ) << QgsPointV2( 1, 0 ) << QgsPointV2( 1, 1 ) << QgsPointV2( 0, 0 ) );
3056+
QgsPolygonV2 removeRings1;
3057+
removeRings1.removeInteriorRings();
3058+
3059+
removeRings1.setExteriorRing( boundary1.clone() );
3060+
removeRings1.removeInteriorRings();
3061+
QCOMPARE( removeRings1.numInteriorRings(), 0 );
3062+
3063+
// add interior rings
3064+
QgsLineString removeRingsRing1;
3065+
removeRingsRing1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0.1, 0.1 ) << QgsPointV2( 0.2, 0.1 ) << QgsPointV2( 0.2, 0.2 ) << QgsPointV2( 0.1, 0.1 ) );
3066+
QgsLineString removeRingsRing2;
3067+
removeRingsRing1.setPoints( QList<QgsPointV2>() << QgsPointV2( 0.6, 0.8 ) << QgsPointV2( 0.9, 0.8 ) << QgsPointV2( 0.9, 0.9 ) << QgsPointV2( 0.6, 0.8 ) );
3068+
removeRings1.setInteriorRings( QList< QgsCurve* >() << removeRingsRing1.clone() << removeRingsRing2.clone() );
3069+
3070+
// remove ring with size filter
3071+
removeRings1.removeInteriorRings( 0.0075 );
3072+
QCOMPARE( removeRings1.numInteriorRings(), 1 );
3073+
3074+
// remove ring with no size filter
3075+
removeRings1.removeInteriorRings();
3076+
QCOMPARE( removeRings1.numInteriorRings(), 0 );
3077+
30533078
}
30543079

30553080
void TestQgsGeometry::multiPoint()

tests/src/python/test_qgsgeometry.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3620,5 +3620,31 @@ def testExtendLine(self):
36203620
self.assertTrue(compareWkt(result, exp, 0.00001),
36213621
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
36223622

3623+
def testRemoveRings(self):
3624+
empty = QgsGeometry()
3625+
self.assertFalse(empty.removeInteriorRings())
3626+
3627+
# not a polygon
3628+
point = QgsGeometry.fromWkt('Point(1 2)')
3629+
self.assertFalse(point.removeInteriorRings())
3630+
3631+
# polygon
3632+
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))')
3633+
removed = polygon.removeInteriorRings()
3634+
exp = 'Polygon((0 0, 1 0, 1 1, 0 0))'
3635+
result = removed.exportToWkt()
3636+
self.assertTrue(compareWkt(result, exp, 0.00001),
3637+
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
3638+
3639+
# multipolygon
3640+
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)),'
3641+
'((10 0, 11 0, 11 1, 10 0),(10.1 10.1, 10.2 0.1, 10.2 0.2, 10.1 0.1)))')
3642+
removed = multipolygon.removeInteriorRings()
3643+
exp = 'MultiPolygon(((0 0, 1 0, 1 1, 0 0)),((10 0, 11 0, 11 1, 10 0)))'
3644+
result = removed.exportToWkt()
3645+
self.assertTrue(compareWkt(result, exp, 0.00001),
3646+
"Extend line: mismatch Expected:\n{}\nGot:\n{}\n".format(exp, result))
3647+
3648+
36233649
if __name__ == '__main__':
36243650
unittest.main()

0 commit comments

Comments
 (0)