Skip to content

Commit b2782ee

Browse files
author
Hugo Mercier
committed
Inverted polygons: use unary union
1 parent 5ec4fef commit b2782ee

File tree

5 files changed

+94
-65
lines changed

5 files changed

+94
-65
lines changed

python/core/qgsgeometry.sip

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -442,3 +442,12 @@ class QgsGeometry
442442
void validateGeometry( QList<QgsGeometry::Error> &errors /Out/ );
443443
}; // class QgsGeometry
444444

445+
/** namespace where QgsGeometry-based algorithms lie */
446+
namespace QgsGeometryAlgorithms
447+
{
448+
/** compute the unary union on a list of geometries. May be faster than an iterative union on a set of geometries.
449+
@param geometryList a list of QgsGeometry* as input
450+
@returns the new computed QgsGeometry, or null
451+
*/
452+
QgsGeometry* unaryUnion( const QList<QgsGeometry*>& geometryList );
453+
};

src/core/qgsgeometry.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6396,3 +6396,23 @@ QgsGeometry* QgsGeometry::convertToPolygon( bool destMultipart )
63966396
return 0;
63976397
}
63986398
}
6399+
6400+
namespace QgsGeometryAlgorithms
6401+
{
6402+
6403+
QgsGeometry* unaryUnion( const QList<QgsGeometry*>& geometryList )
6404+
{
6405+
QList<GEOSGeometry*> geoms;
6406+
foreach( QgsGeometry* g, geometryList )
6407+
{
6408+
// const cast: it is ok here, since the pointers will only be used to be stored
6409+
// in a list for a call to union
6410+
geoms.append( const_cast<GEOSGeometry*>(g->asGeos()) );
6411+
}
6412+
GEOSGeometry* unioned = _makeUnion( geoms );
6413+
QgsGeometry *ret = new QgsGeometry();
6414+
ret->fromGeos( unioned );
6415+
return ret;
6416+
}
6417+
6418+
}// QgsGeometryAlgorithms

src/core/qgsgeometry.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -679,4 +679,14 @@ class CORE_EXPORT QgsConstWkbPtr
679679
inline operator const unsigned char *() const { return mP; }
680680
};
681681

682+
/** namespace where QgsGeometry-based algorithms lie */
683+
namespace QgsGeometryAlgorithms
684+
{
685+
/** compute the unary union on a list of geometries. May be faster than an iterative union on a set of geometries.
686+
@param geometryList a list of QgsGeometry* as input
687+
@returns the new computed QgsGeometry, or null
688+
*/
689+
QgsGeometry* unaryUnion( const QList<QgsGeometry*>& geometryList );
690+
}
691+
682692
#endif

src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp

Lines changed: 53 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -172,9 +172,7 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
172172

173173
if ( ! mSymbolCategories.contains( catId ) )
174174
{
175-
// the exterior ring must be a square in the destination CRS
176175
CombinedFeature cFeat;
177-
cFeat.multiPolygon.append( mExtentPolygon );
178176
// store the first feature
179177
cFeat.feature = feature;
180178
mSymbolCategories.insert( catId, mSymbolCategories.count() );
@@ -197,61 +195,15 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
197195

198196
if ( mPreprocessingEnabled )
199197
{
200-
// preprocessing
201-
if ( ! cFeat.feature.geometry() )
198+
// fix the polygon if it is not valid
199+
if ( ! geom->isGeosValid() )
202200
{
203-
// first feature: add the current geometry
204-
cFeat.feature.setGeometry( new QgsGeometry( *geom ) );
205-
}
206-
else
207-
{
208-
// other features: combine them (union)
209-
QgsGeometry* combined = cFeat.feature.geometry()->combine( geom.data() );
210-
if ( combined && combined->isGeosValid() )
211-
{
212-
cFeat.feature.setGeometry( combined );
213-
}
201+
geom.reset( geom->buffer( 0, 0 ) );
214202
}
215203
}
216-
else
217-
{
218-
// No preprocessing involved.
219-
// We build here a "reversed" geometry of all the polygons
220-
//
221-
// The final geometry is a multipolygon F, with :
222-
// * the first polygon of F having the current extent as its exterior ring
223-
// * each polygon's exterior ring is added as interior ring of the first polygon of F
224-
// * each polygon's interior ring is added as new polygons in F
225-
//
226-
// No validity check is done, on purpose, it will be very slow and painting
227-
// operations do not need geometries to be valid
228-
229-
QgsMultiPolygon multi;
230-
if (( geom->wkbType() == QGis::WKBPolygon ) ||
231-
( geom->wkbType() == QGis::WKBPolygon25D ) )
232-
{
233-
multi.append( geom->asPolygon() );
234-
}
235-
else if (( geom->wkbType() == QGis::WKBMultiPolygon ) ||
236-
( geom->wkbType() == QGis::WKBMultiPolygon25D ) )
237-
{
238-
multi = geom->asMultiPolygon();
239-
}
240-
241-
for ( int i = 0; i < multi.size(); i++ )
242-
{
243-
// add the exterior ring as interior ring to the first polygon
244-
cFeat.multiPolygon[0].append( multi[i][0] );
204+
// add the geometry to the list of geometries for this feature
205+
cFeat.geometries.append( geom.take() );
245206

246-
// add interior rings as new polygons
247-
for ( int j = 1; j < multi[i].size(); j++ )
248-
{
249-
QgsPolygon new_poly;
250-
new_poly.append( multi[i][j] );
251-
cFeat.multiPolygon.append( new_poly );
252-
}
253-
}
254-
}
255207
return true;
256208
}
257209

@@ -268,23 +220,61 @@ void QgsInvertedPolygonRenderer::stopRender( QgsRenderContext& context )
268220

269221
for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
270222
{
271-
QgsFeature feat( cit->feature );
272-
if ( !mPreprocessingEnabled )
223+
QgsFeature& feat = cit->feature;
224+
if ( mPreprocessingEnabled )
273225
{
274-
// no preprocessing - the final polygon has already been prepared
275-
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit->multiPolygon ) );
226+
// compute the unary union on the polygons
227+
QScopedPointer<QgsGeometry> unioned( QgsGeometryAlgorithms::unaryUnion( cit->geometries ) );
228+
// compute the difference with the extent
229+
QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
230+
QgsGeometry *final = rect->difference( const_cast<QgsGeometry*>(unioned.data()) );
231+
if ( final )
232+
{
233+
feat.setGeometry( final );
234+
}
276235
}
277236
else
278237
{
279-
// preprocessing mode - we still have to invert (using difference)
280-
if ( feat.geometry() )
238+
// No preprocessing involved.
239+
// We build here a "reversed" geometry of all the polygons
240+
//
241+
// The final geometry is a multipolygon F, with :
242+
// * the first polygon of F having the current extent as its exterior ring
243+
// * each polygon's exterior ring is added as interior ring of the first polygon of F
244+
// * each polygon's interior ring is added as new polygons in F
245+
//
246+
// No validity check is done, on purpose, it will be very slow and painting
247+
// operations do not need geometries to be valid
248+
QgsMultiPolygon finalMulti;
249+
finalMulti.append( mExtentPolygon );
250+
foreach( QgsGeometry* geom, cit->geometries )
281251
{
282-
QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
283-
QgsGeometry *final = rect->difference( feat.geometry() );
284-
if ( final )
252+
QgsMultiPolygon multi;
253+
if (( geom->wkbType() == QGis::WKBPolygon ) ||
254+
( geom->wkbType() == QGis::WKBPolygon25D ) )
255+
{
256+
multi.append( geom->asPolygon() );
257+
}
258+
else if (( geom->wkbType() == QGis::WKBMultiPolygon ) ||
259+
( geom->wkbType() == QGis::WKBMultiPolygon25D ) )
260+
{
261+
multi = geom->asMultiPolygon();
262+
}
263+
264+
for ( int i = 0; i < multi.size(); i++ )
285265
{
286-
feat.setGeometry( final );
266+
// add the exterior ring as interior ring to the first polygon
267+
finalMulti[0].append( multi[i][0] );
268+
269+
// add interior rings as new polygons
270+
for ( int j = 1; j < multi[i].size(); j++ )
271+
{
272+
QgsPolygon new_poly;
273+
new_poly.append( multi[i][j] );
274+
finalMulti.append( new_poly );
275+
}
287276
}
277+
feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
288278
}
289279
}
290280
mSubRenderer->renderFeature( feat, mContext );

src/core/symbology-ng/qgsinvertedpolygonrenderer.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
130130
/** Structure where the reversed geometry is built during renderFeature */
131131
struct CombinedFeature
132132
{
133-
QgsMultiPolygon multiPolygon; //< the final combined geometry
134-
QgsFeature feature; //< one feature (for attriute-based rendering)
133+
QList<QgsGeometry*> geometries; //< list of geometries
134+
QgsFeature feature; //< one feature (for attriute-based rendering)
135135
};
136136
typedef QVector<CombinedFeature> FeatureCategoryVector;
137137
/** where features are stored, based on the index of their symbol category @see mSymbolCategories */

0 commit comments

Comments
 (0)