Skip to content

Commit 3fe12df

Browse files
committedJun 10, 2014
Fix #10355 (crash) and #10338 (overlapping polygons) in inverted polygon renderer
Conflicts: python/core/symbology-ng/qgsinvertedpolygonrenderer.sip src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp src/core/symbology-ng/qgsinvertedpolygonrenderer.h src/gui/symbology-ng/qgsinvertedpolygonrendererwidget.cpp src/gui/symbology-ng/qgsinvertedpolygonrendererwidget.h
2 parents ae9048a + 25346fe commit 3fe12df

18 files changed

+396
-76
lines changed
 

‎python/core/symbology-ng/qgsinvertedpolygonrenderer.sip

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,14 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
7171
/** @returns the current embedded renderer
7272
*/
7373
const QgsFeatureRendererV2* embeddedRenderer() const;
74+
75+
/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
76+
bool preprocessingEnabled() const;
77+
/**
78+
@param enabled enables or disables the preprocessing.
79+
When enabled, geometries will be merged with an union before being rendered.
80+
It allows to fix some rendering artefacts (when rendering overlapping polygons for instance).
81+
This will involve some CPU-demanding computations and is thus disabled by default.
82+
*/
83+
void setPreprocessingEnabled( bool enabled );
7484
};

‎src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp

Lines changed: 105 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* subRenderer )
3131
: QgsFeatureRendererV2( "invertedPolygonRenderer" )
32+
, mPreprocessingEnabled( false )
3233
{
3334
if ( subRenderer )
3435
{
@@ -68,7 +69,6 @@ void QgsInvertedPolygonRenderer::startRender( QgsRenderContext& context, const Q
6869
return;
6970
}
7071

71-
mSubRenderer->startRender( context, fields );
7272
mFeaturesCategoryMap.clear();
7373
mFeatureDecorations.clear();
7474
mFields = fields;
@@ -78,36 +78,52 @@ void QgsInvertedPolygonRenderer::startRender( QgsRenderContext& context, const Q
7878
// It must be computed in the destination CRS if reprojection is enabled.
7979
const QgsMapToPixel& mtp( context.mapToPixel() );
8080

81+
if ( !context.painter() )
82+
{
83+
return;
84+
}
85+
8186
// convert viewport to dest CRS
8287
QRect e( context.painter()->viewport() );
8388
// add some space to hide borders and tend to infinity
84-
e.adjust( -e.width()*10, -e.height()*10, e.width()*10, e.height()*10 );
89+
e.adjust( -e.width()*5, -e.height()*5, e.width()*5, e.height()*5 );
8590
QgsPolyline exteriorRing;
8691
exteriorRing << mtp.toMapCoordinates( e.topLeft() );
8792
exteriorRing << mtp.toMapCoordinates( e.topRight() );
8893
exteriorRing << mtp.toMapCoordinates( e.bottomRight() );
8994
exteriorRing << mtp.toMapCoordinates( e.bottomLeft() );
9095
exteriorRing << mtp.toMapCoordinates( e.topLeft() );
9196

92-
mTransform = context.coordinateTransform();
97+
// copy the rendering context
98+
mContext = context;
99+
93100
// If reprojection is enabled, we must reproject during renderFeature
94101
// and act as if there is no reprojection
95102
// If we don't do that, there is no need to have a simple rectangular extent
96103
// that covers the whole screen
97104
// (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
98-
if ( mTransform )
105+
if ( context.coordinateTransform() )
99106
{
100107
// disable projection
101-
context.setCoordinateTransform( 0 );
108+
mContext.setCoordinateTransform( 0 );
109+
// recompute extent so that polygon clipping is correct
110+
QRect v( context.painter()->viewport() );
111+
mContext.setExtent( QgsRectangle( mtp.toMapCoordinates( v.topLeft() ), mtp.toMapCoordinates( v.bottomRight() ) ) );
112+
// do we have to recompute the MapToPixel ?
102113
}
103114

104115
mExtentPolygon.clear();
105116
mExtentPolygon.append( exteriorRing );
117+
118+
mSubRenderer->startRender( mContext, fields );
106119
}
107120

108121
bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
109122
{
110-
Q_UNUSED( context );
123+
if ( !context.painter() )
124+
{
125+
return false;
126+
}
111127

112128
// store this feature as a feature to render with decoration if needed
113129
if ( selected || drawVertexMarker )
@@ -153,33 +169,32 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
153169
return false;
154170
}
155171

156-
// We build here a "reversed" geometry of all the polygons
157-
//
158-
// The final geometry is a multipolygon F, with :
159-
// * the first polygon of F having the current extent as its exterior ring
160-
// * each polygon's exterior ring is added as interior ring of the first polygon of F
161-
// * each polygon's interior ring is added as new polygons in F
162-
//
163-
// No validity check is done, on purpose, it will be very slow and painting
164-
// operations do not need geometries to be valid
165-
if ( ! mFeaturesCategoryMap.contains( catId ) )
172+
if ( ! mSymbolCategories.contains( catId ) )
166173
{
167174
// the exterior ring must be a square in the destination CRS
168175
CombinedFeature cFeat;
169176
cFeat.multiPolygon.append( mExtentPolygon );
170177
// store the first feature
171178
cFeat.feature = feature;
172-
mFeaturesCategoryMap.insert( catId, cFeat );
179+
mSymbolCategories.insert( catId, mSymbolCategories.count() );
180+
mFeaturesCategoryMap.append( cFeat );
173181
}
174182

175-
// update the gometry
176-
CombinedFeature& cFeat = mFeaturesCategoryMap[catId];
183+
// update the geometry
184+
CombinedFeature& cFeat = mFeaturesCategoryMap[ mSymbolCategories[catId] ];
177185
QgsMultiPolygon multi;
178186
QgsGeometry* geom = feature.geometry();
179187
if ( !geom )
180188
{
181189
return false;
182190
}
191+
192+
const QgsCoordinateTransform* xform = context.coordinateTransform();
193+
if ( xform )
194+
{
195+
geom->transform( *xform );
196+
}
197+
183198
if (( geom->wkbType() == QGis::WKBPolygon ) ||
184199
( geom->wkbType() == QGis::WKBPolygon25D ) )
185200
{
@@ -191,43 +206,49 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
191206
multi = geom->asMultiPolygon();
192207
}
193208

194-
for ( int i = 0; i < multi.size(); i++ )
209+
if ( mPreprocessingEnabled )
195210
{
196-
// add the exterior ring as interior ring to the first polygon
197-
if ( mTransform )
211+
// preprocessing
212+
if ( ! cFeat.feature.geometry() )
198213
{
199-
QgsPolyline new_ls;
200-
QgsPolyline& old_ls = multi[i][0];
201-
for ( int k = 0; k < old_ls.size(); k++ )
202-
{
203-
new_ls.append( mTransform->transform( old_ls[k] ) );
204-
}
205-
cFeat.multiPolygon[0].append( new_ls );
214+
// first feature: add the current geometry
215+
cFeat.feature.setGeometry( new QgsGeometry( *geom ) );
206216
}
207217
else
208218
{
209-
cFeat.multiPolygon[0].append( multi[i][0] );
210-
}
211-
// add interior rings as new polygons
212-
for ( int j = 1; j < multi[i].size(); j++ )
213-
{
214-
QgsPolygon new_poly;
215-
if ( mTransform )
219+
// other features: combine them (union)
220+
QgsGeometry* combined = cFeat.feature.geometry()->combine( geom );
221+
if ( combined && combined->isGeosValid() )
216222
{
217-
QgsPolyline new_ls;
218-
QgsPolyline& old_ls = multi[i][j];
219-
for ( int k = 0; k < old_ls.size(); k++ )
220-
{
221-
new_ls.append( mTransform->transform( old_ls[k] ) );
222-
}
223-
new_poly.append( new_ls );
223+
cFeat.feature.setGeometry( combined );
224224
}
225-
else
225+
}
226+
}
227+
else
228+
{
229+
// No preprocessing involved.
230+
// We build here a "reversed" geometry of all the polygons
231+
//
232+
// The final geometry is a multipolygon F, with :
233+
// * the first polygon of F having the current extent as its exterior ring
234+
// * each polygon's exterior ring is added as interior ring of the first polygon of F
235+
// * each polygon's interior ring is added as new polygons in F
236+
//
237+
// No validity check is done, on purpose, it will be very slow and painting
238+
// operations do not need geometries to be valid
239+
240+
for ( int i = 0; i < multi.size(); i++ )
241+
{
242+
// add the exterior ring as interior ring to the first polygon
243+
cFeat.multiPolygon[0].append( multi[i][0] );
244+
245+
// add interior rings as new polygons
246+
for ( int j = 1; j < multi[i].size(); j++ )
226247
{
248+
QgsPolygon new_poly;
227249
new_poly.append( multi[i][j] );
250+
cFeat.multiPolygon.append( new_poly );
228251
}
229-
230-
cFeat.multiPolygon.append( new_poly );
231252
}
232253
}
233254
return true;
@@ -239,12 +260,33 @@ void QgsInvertedPolygonRenderer::stopRender( QgsRenderContext& context )
239260
{
240261
return;
241262
}
263+
if ( !context.painter() )
264+
{
265+
return;
266+
}
242267

243268
for ( FeatureCategoryMap::iterator cit = mFeaturesCategoryMap.begin(); cit != mFeaturesCategoryMap.end(); ++cit )
244269
{
245-
QgsFeature feat( cit.value().feature );
246-
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit.value().multiPolygon ) );
247-
mSubRenderer->renderFeature( feat, context );
270+
QgsFeature feat( cit->feature );
271+
if ( !mPreprocessingEnabled )
272+
{
273+
// no preprocessing - the final polygon has already been prepared
274+
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit->multiPolygon ) );
275+
}
276+
else
277+
{
278+
// preprocessing mode - we still have to invert (using difference)
279+
if ( feat.geometry() )
280+
{
281+
QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
282+
QgsGeometry *final = rect->difference( feat.geometry() );
283+
if ( final )
284+
{
285+
feat.setGeometry( final );
286+
}
287+
}
288+
}
289+
mSubRenderer->renderFeature( feat, mContext );
248290
}
249291

250292
// when no features are visible, we still have to draw the exterior rectangle
@@ -256,22 +298,16 @@ void QgsInvertedPolygonRenderer::stopRender( QgsRenderContext& context )
256298
// empty feature with default attributes
257299
QgsFeature feat( mFields );
258300
feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
259-
mSubRenderer->renderFeature( feat, context );
301+
mSubRenderer->renderFeature( feat, mContext );
260302
}
261303

262304
// draw feature decorations
263305
foreach ( FeatureDecoration deco, mFeatureDecorations )
264306
{
265-
mSubRenderer->renderFeature( deco.feature, context, deco.layer, deco.selected, deco.drawMarkers );
307+
mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
266308
}
267309

268-
mSubRenderer->stopRender( context );
269-
270-
if ( mTransform )
271-
{
272-
// restore the coordinate transform if needed
273-
context.setCoordinateTransform( mTransform );
274-
}
310+
mSubRenderer->stopRender( mContext );
275311
}
276312

277313
QString QgsInvertedPolygonRenderer::dump() const
@@ -285,12 +321,17 @@ QString QgsInvertedPolygonRenderer::dump() const
285321

286322
QgsFeatureRendererV2* QgsInvertedPolygonRenderer::clone()
287323
{
324+
QgsInvertedPolygonRenderer* newRenderer;
288325
if ( mSubRenderer.isNull() )
289326
{
290-
return new QgsInvertedPolygonRenderer( 0 );
327+
newRenderer = new QgsInvertedPolygonRenderer( 0 );
328+
}
329+
else
330+
{
331+
newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
291332
}
292-
// else
293-
return new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
333+
newRenderer->setPreprocessingEnabled( preprocessingEnabled() );
334+
return newRenderer;
294335
}
295336

296337
QgsFeatureRendererV2* QgsInvertedPolygonRenderer::create( QDomElement& element )
@@ -302,13 +343,15 @@ QgsFeatureRendererV2* QgsInvertedPolygonRenderer::create( QDomElement& element )
302343
{
303344
r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
304345
}
346+
r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
305347
return r;
306348
}
307349

308350
QDomElement QgsInvertedPolygonRenderer::save( QDomDocument& doc )
309351
{
310352
QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
311353
rendererElem.setAttribute( "type", "invertedPolygonRenderer" );
354+
rendererElem.setAttribute( "preprocessing", preprocessingEnabled() ? "1" : "0" );
312355

313356
if ( mSubRenderer )
314357
{
@@ -390,3 +433,4 @@ bool QgsInvertedPolygonRenderer::willRenderFeature( QgsFeature& feat )
390433
}
391434
return mSubRenderer->willRenderFeature( feat );
392435
}
436+

‎src/core/symbology-ng/qgsinvertedpolygonrenderer.h

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
108108
*/
109109
const QgsFeatureRendererV2* embeddedRenderer() const;
110110

111+
/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
112+
bool preprocessingEnabled() const { return mPreprocessingEnabled; }
113+
/**
114+
@param enabled enables or disables the preprocessing.
115+
When enabled, geometries will be merged with an union before being rendered.
116+
It allows to fix some rendering artefacts (when rendering overlapping polygons for instance).
117+
This will involve some CPU-demanding computations and is thus disabled by default.
118+
*/
119+
void setPreprocessingEnabled( bool enabled ) { mPreprocessingEnabled = enabled; }
120+
111121
private:
112122
/** Private copy constructor. @see clone() */
113123
QgsInvertedPolygonRenderer( const QgsInvertedPolygonRenderer& );
@@ -123,15 +133,18 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
123133
QgsMultiPolygon multiPolygon; //< the final combined geometry
124134
QgsFeature feature; //< one feature (for attriute-based rendering)
125135
};
126-
typedef QMap< QByteArray, CombinedFeature > FeatureCategoryMap;
127-
/** where features are stored, based on their symbol category */
136+
typedef QVector<CombinedFeature> FeatureCategoryMap;
137+
/** where features are stored, based on the index of their symbol category @see mSymbolCategories */
128138
FeatureCategoryMap mFeaturesCategoryMap;
129139

140+
/** maps a category to an index */
141+
QMap<QByteArray, int> mSymbolCategories;
142+
130143
/** the polygon used as exterior ring that covers the current extent */
131144
QgsPolygon mExtentPolygon;
132145

133-
/** the current coordinate transform (or null) */
134-
const QgsCoordinateTransform* mTransform;
146+
/** the context used for rendering */
147+
QgsRenderContext mContext;
135148

136149
/** fields of each feature*/
137150
QgsFields mFields;
@@ -149,6 +162,9 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
149162
feature( a_feature ), selected( a_selected ), drawMarkers( a_drawMarkers ), layer( a_layer ) {}
150163
};
151164
QList<FeatureDecoration> mFeatureDecorations;
165+
166+
/** whether to preprocess (merge) geometries before rendering*/
167+
bool mPreprocessingEnabled;
152168
};
153169

154170

‎src/gui/symbology-ng/qgsinvertedpolygonrendererwidget.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ QgsInvertedPolygonRendererWidget::QgsInvertedPolygonRendererWidget( QgsVectorLay
6868
{
6969
// an existing inverted renderer
7070
mRenderer.reset( static_cast<QgsInvertedPolygonRenderer*>( renderer ) );
71+
mMergePolygonsCheckBox->blockSignals( true );
72+
mMergePolygonsCheckBox->setCheckState( mRenderer->preprocessingEnabled() ? Qt::Checked : Qt::Unchecked );
73+
mMergePolygonsCheckBox->blockSignals( false );
7174
}
7275

7376
int currentEmbeddedIdx = 0;
@@ -123,12 +126,16 @@ void QgsInvertedPolygonRendererWidget::on_mRendererComboBox_currentIndexChanged(
123126
{
124127
mEmbeddedRendererWidget.reset( m->createRendererWidget( mLayer, mStyle, const_cast<QgsFeatureRendererV2*>( mRenderer->embeddedRenderer() )->clone() ) );
125128

126-
if ( mLayout->count() > 1 )
129+
if ( mLayout->count() > 2 )
127130
{
128131
// remove the current renderer widget
129-
mLayout->takeAt( 1 );
132+
mLayout->takeAt( 2 );
130133
}
131134
mLayout->addWidget( mEmbeddedRendererWidget.data() );
132135
}
133136
}
134137

138+
void QgsInvertedPolygonRendererWidget::on_mMergePolygonsCheckBox_stateChanged( int state )
139+
{
140+
mRenderer->setPreprocessingEnabled( state == Qt::Checked );
141+
}

0 commit comments

Comments
 (0)
Please sign in to comment.