Skip to content

Commit

Permalink
Fix #10355 (crash) and #10338 (overlapping polygons) in inverted poly…
Browse files Browse the repository at this point in the history
…gon 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
  • Loading branch information
wonder-sk committed Jun 10, 2014
2 parents ae9048a + 25346fe commit 3fe12df
Show file tree
Hide file tree
Showing 18 changed files with 396 additions and 76 deletions.
10 changes: 10 additions & 0 deletions python/core/symbology-ng/qgsinvertedpolygonrenderer.sip
Original file line number Diff line number Diff line change
Expand Up @@ -71,4 +71,14 @@ class QgsInvertedPolygonRenderer : QgsFeatureRendererV2
/** @returns the current embedded renderer
*/
const QgsFeatureRendererV2* embeddedRenderer() const;

/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
bool preprocessingEnabled() const;
/**
@param enabled enables or disables the preprocessing.
When enabled, geometries will be merged with an union before being rendered.
It allows to fix some rendering artefacts (when rendering overlapping polygons for instance).
This will involve some CPU-demanding computations and is thus disabled by default.
*/
void setPreprocessingEnabled( bool enabled );
};
166 changes: 105 additions & 61 deletions src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* subRenderer )
: QgsFeatureRendererV2( "invertedPolygonRenderer" )
, mPreprocessingEnabled( false )
{
if ( subRenderer )
{
Expand Down Expand Up @@ -68,7 +69,6 @@ void QgsInvertedPolygonRenderer::startRender( QgsRenderContext& context, const Q
return;
}

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

if ( !context.painter() )
{
return;
}

// convert viewport to dest CRS
QRect e( context.painter()->viewport() );
// add some space to hide borders and tend to infinity
e.adjust( -e.width()*10, -e.height()*10, e.width()*10, e.height()*10 );
e.adjust( -e.width()*5, -e.height()*5, e.width()*5, e.height()*5 );
QgsPolyline exteriorRing;
exteriorRing << mtp.toMapCoordinates( e.topLeft() );
exteriorRing << mtp.toMapCoordinates( e.topRight() );
exteriorRing << mtp.toMapCoordinates( e.bottomRight() );
exteriorRing << mtp.toMapCoordinates( e.bottomLeft() );
exteriorRing << mtp.toMapCoordinates( e.topLeft() );

mTransform = context.coordinateTransform();
// copy the rendering context
mContext = context;

// If reprojection is enabled, we must reproject during renderFeature
// and act as if there is no reprojection
// If we don't do that, there is no need to have a simple rectangular extent
// that covers the whole screen
// (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
if ( mTransform )
if ( context.coordinateTransform() )
{
// disable projection
context.setCoordinateTransform( 0 );
mContext.setCoordinateTransform( 0 );
// recompute extent so that polygon clipping is correct
QRect v( context.painter()->viewport() );
mContext.setExtent( QgsRectangle( mtp.toMapCoordinates( v.topLeft() ), mtp.toMapCoordinates( v.bottomRight() ) ) );
// do we have to recompute the MapToPixel ?
}

mExtentPolygon.clear();
mExtentPolygon.append( exteriorRing );

mSubRenderer->startRender( mContext, fields );
}

bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
{
Q_UNUSED( context );
if ( !context.painter() )
{
return false;
}

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

// We build here a "reversed" geometry of all the polygons
//
// The final geometry is a multipolygon F, with :
// * the first polygon of F having the current extent as its exterior ring
// * each polygon's exterior ring is added as interior ring of the first polygon of F
// * each polygon's interior ring is added as new polygons in F
//
// No validity check is done, on purpose, it will be very slow and painting
// operations do not need geometries to be valid
if ( ! mFeaturesCategoryMap.contains( catId ) )
if ( ! mSymbolCategories.contains( catId ) )
{
// the exterior ring must be a square in the destination CRS
CombinedFeature cFeat;
cFeat.multiPolygon.append( mExtentPolygon );
// store the first feature
cFeat.feature = feature;
mFeaturesCategoryMap.insert( catId, cFeat );
mSymbolCategories.insert( catId, mSymbolCategories.count() );
mFeaturesCategoryMap.append( cFeat );
}

// update the gometry
CombinedFeature& cFeat = mFeaturesCategoryMap[catId];
// update the geometry
CombinedFeature& cFeat = mFeaturesCategoryMap[ mSymbolCategories[catId] ];
QgsMultiPolygon multi;
QgsGeometry* geom = feature.geometry();
if ( !geom )
{
return false;
}

const QgsCoordinateTransform* xform = context.coordinateTransform();
if ( xform )
{
geom->transform( *xform );
}

if (( geom->wkbType() == QGis::WKBPolygon ) ||
( geom->wkbType() == QGis::WKBPolygon25D ) )
{
Expand All @@ -191,43 +206,49 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
multi = geom->asMultiPolygon();
}

for ( int i = 0; i < multi.size(); i++ )
if ( mPreprocessingEnabled )
{
// add the exterior ring as interior ring to the first polygon
if ( mTransform )
// preprocessing
if ( ! cFeat.feature.geometry() )
{
QgsPolyline new_ls;
QgsPolyline& old_ls = multi[i][0];
for ( int k = 0; k < old_ls.size(); k++ )
{
new_ls.append( mTransform->transform( old_ls[k] ) );
}
cFeat.multiPolygon[0].append( new_ls );
// first feature: add the current geometry
cFeat.feature.setGeometry( new QgsGeometry( *geom ) );
}
else
{
cFeat.multiPolygon[0].append( multi[i][0] );
}
// add interior rings as new polygons
for ( int j = 1; j < multi[i].size(); j++ )
{
QgsPolygon new_poly;
if ( mTransform )
// other features: combine them (union)
QgsGeometry* combined = cFeat.feature.geometry()->combine( geom );
if ( combined && combined->isGeosValid() )
{
QgsPolyline new_ls;
QgsPolyline& old_ls = multi[i][j];
for ( int k = 0; k < old_ls.size(); k++ )
{
new_ls.append( mTransform->transform( old_ls[k] ) );
}
new_poly.append( new_ls );
cFeat.feature.setGeometry( combined );
}
else
}
}
else
{
// No preprocessing involved.
// We build here a "reversed" geometry of all the polygons
//
// The final geometry is a multipolygon F, with :
// * the first polygon of F having the current extent as its exterior ring
// * each polygon's exterior ring is added as interior ring of the first polygon of F
// * each polygon's interior ring is added as new polygons in F
//
// No validity check is done, on purpose, it will be very slow and painting
// operations do not need geometries to be valid

for ( int i = 0; i < multi.size(); i++ )
{
// add the exterior ring as interior ring to the first polygon
cFeat.multiPolygon[0].append( multi[i][0] );

// add interior rings as new polygons
for ( int j = 1; j < multi[i].size(); j++ )
{
QgsPolygon new_poly;
new_poly.append( multi[i][j] );
cFeat.multiPolygon.append( new_poly );
}

cFeat.multiPolygon.append( new_poly );
}
}
return true;
Expand All @@ -239,12 +260,33 @@ void QgsInvertedPolygonRenderer::stopRender( QgsRenderContext& context )
{
return;
}
if ( !context.painter() )
{
return;
}

for ( FeatureCategoryMap::iterator cit = mFeaturesCategoryMap.begin(); cit != mFeaturesCategoryMap.end(); ++cit )
{
QgsFeature feat( cit.value().feature );
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit.value().multiPolygon ) );
mSubRenderer->renderFeature( feat, context );
QgsFeature feat( cit->feature );
if ( !mPreprocessingEnabled )
{
// no preprocessing - the final polygon has already been prepared
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit->multiPolygon ) );
}
else
{
// preprocessing mode - we still have to invert (using difference)
if ( feat.geometry() )
{
QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
QgsGeometry *final = rect->difference( feat.geometry() );
if ( final )
{
feat.setGeometry( final );
}
}
}
mSubRenderer->renderFeature( feat, mContext );
}

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

// draw feature decorations
foreach ( FeatureDecoration deco, mFeatureDecorations )
{
mSubRenderer->renderFeature( deco.feature, context, deco.layer, deco.selected, deco.drawMarkers );
mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
}

mSubRenderer->stopRender( context );

if ( mTransform )
{
// restore the coordinate transform if needed
context.setCoordinateTransform( mTransform );
}
mSubRenderer->stopRender( mContext );
}

QString QgsInvertedPolygonRenderer::dump() const
Expand All @@ -285,12 +321,17 @@ QString QgsInvertedPolygonRenderer::dump() const

QgsFeatureRendererV2* QgsInvertedPolygonRenderer::clone()
{
QgsInvertedPolygonRenderer* newRenderer;
if ( mSubRenderer.isNull() )
{
return new QgsInvertedPolygonRenderer( 0 );
newRenderer = new QgsInvertedPolygonRenderer( 0 );
}
else
{
newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
}
// else
return new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
newRenderer->setPreprocessingEnabled( preprocessingEnabled() );
return newRenderer;
}

QgsFeatureRendererV2* QgsInvertedPolygonRenderer::create( QDomElement& element )
Expand All @@ -302,13 +343,15 @@ QgsFeatureRendererV2* QgsInvertedPolygonRenderer::create( QDomElement& element )
{
r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
}
r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
return r;
}

QDomElement QgsInvertedPolygonRenderer::save( QDomDocument& doc )
{
QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
rendererElem.setAttribute( "type", "invertedPolygonRenderer" );
rendererElem.setAttribute( "preprocessing", preprocessingEnabled() ? "1" : "0" );

if ( mSubRenderer )
{
Expand Down Expand Up @@ -390,3 +433,4 @@ bool QgsInvertedPolygonRenderer::willRenderFeature( QgsFeature& feat )
}
return mSubRenderer->willRenderFeature( feat );
}

24 changes: 20 additions & 4 deletions src/core/symbology-ng/qgsinvertedpolygonrenderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,16 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
*/
const QgsFeatureRendererV2* embeddedRenderer() const;

/** @returns true if the geometries are to be preprocessed (merged with an union) before rendering.*/
bool preprocessingEnabled() const { return mPreprocessingEnabled; }
/**
@param enabled enables or disables the preprocessing.
When enabled, geometries will be merged with an union before being rendered.
It allows to fix some rendering artefacts (when rendering overlapping polygons for instance).
This will involve some CPU-demanding computations and is thus disabled by default.
*/
void setPreprocessingEnabled( bool enabled ) { mPreprocessingEnabled = enabled; }

private:
/** Private copy constructor. @see clone() */
QgsInvertedPolygonRenderer( const QgsInvertedPolygonRenderer& );
Expand All @@ -123,15 +133,18 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
QgsMultiPolygon multiPolygon; //< the final combined geometry
QgsFeature feature; //< one feature (for attriute-based rendering)
};
typedef QMap< QByteArray, CombinedFeature > FeatureCategoryMap;
/** where features are stored, based on their symbol category */
typedef QVector<CombinedFeature> FeatureCategoryMap;
/** where features are stored, based on the index of their symbol category @see mSymbolCategories */
FeatureCategoryMap mFeaturesCategoryMap;

/** maps a category to an index */
QMap<QByteArray, int> mSymbolCategories;

/** the polygon used as exterior ring that covers the current extent */
QgsPolygon mExtentPolygon;

/** the current coordinate transform (or null) */
const QgsCoordinateTransform* mTransform;
/** the context used for rendering */
QgsRenderContext mContext;

/** fields of each feature*/
QgsFields mFields;
Expand All @@ -149,6 +162,9 @@ class CORE_EXPORT QgsInvertedPolygonRenderer : public QgsFeatureRendererV2
feature( a_feature ), selected( a_selected ), drawMarkers( a_drawMarkers ), layer( a_layer ) {}
};
QList<FeatureDecoration> mFeatureDecorations;

/** whether to preprocess (merge) geometries before rendering*/
bool mPreprocessingEnabled;
};


Expand Down
11 changes: 9 additions & 2 deletions src/gui/symbology-ng/qgsinvertedpolygonrendererwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ QgsInvertedPolygonRendererWidget::QgsInvertedPolygonRendererWidget( QgsVectorLay
{
// an existing inverted renderer
mRenderer.reset( static_cast<QgsInvertedPolygonRenderer*>( renderer ) );
mMergePolygonsCheckBox->blockSignals( true );
mMergePolygonsCheckBox->setCheckState( mRenderer->preprocessingEnabled() ? Qt::Checked : Qt::Unchecked );
mMergePolygonsCheckBox->blockSignals( false );
}

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

if ( mLayout->count() > 1 )
if ( mLayout->count() > 2 )
{
// remove the current renderer widget
mLayout->takeAt( 1 );
mLayout->takeAt( 2 );
}
mLayout->addWidget( mEmbeddedRendererWidget.data() );
}
}

void QgsInvertedPolygonRendererWidget::on_mMergePolygonsCheckBox_stateChanged( int state )
{
mRenderer->setPreprocessingEnabled( state == Qt::Checked );
}
Loading

0 comments on commit 3fe12df

Please sign in to comment.