Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Inverted polygon renderer: add an option to preprocess polygons using…
… an union
  • Loading branch information
Hugo Mercier committed May 26, 2014
1 parent 19041a8 commit 80ae9ef
Show file tree
Hide file tree
Showing 15 changed files with 329 additions and 40 deletions.
111 changes: 79 additions & 32 deletions src/core/symbology-ng/qgsinvertedpolygonrenderer.cpp
Expand Up @@ -28,7 +28,7 @@
#include <QDomElement>

QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* subRenderer )
: QgsFeatureRendererV2( "invertedPolygonRenderer" )
: QgsFeatureRendererV2( "invertedPolygonRenderer" ), mPreprocessingEnabled( false )
{
if ( subRenderer ) {
setEmbeddedRenderer( subRenderer );
Expand Down Expand Up @@ -154,15 +154,6 @@ 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) )
{
// the exterior ring must be a square in the destination CRS
Expand All @@ -173,7 +164,7 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
mFeaturesCategoryMap.insert( catId, cFeat );
}

// update the gometry
// update the geometry
CombinedFeature& cFeat = mFeaturesCategoryMap[catId];
QgsMultiPolygon multi;
QgsGeometry* geom = feature.geometry();
Expand All @@ -190,37 +181,69 @@ bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderCo
multi = geom->asMultiPolygon();
}

for ( int i = 0; i < multi.size(); i++ ) {
// add the exterior ring as interior ring to the first polygon
if ( mTransform ) {
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 );
if ( mPreprocessingEnabled )
{
// preprocessing
if ( ! cFeat.feature.geometry() )
{
// first feature: add the current geometry
cFeat.feature.setGeometry( new QgsGeometry(*geom) );
}
else
{
cFeat.multiPolygon[0].append( multi[i][0] );
// other features: combine them (union)
QgsGeometry* combined = cFeat.feature.geometry()->combine( geom );
if ( combined && combined->isGeosValid() )
{
cFeat.feature.setGeometry( combined );
}
}
// add interior rings as new polygons
for ( int j = 1; j < multi[i].size(); j++ ) {
QgsPolygon new_poly;
}
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
if ( mTransform ) {
QgsPolyline new_ls;
QgsPolyline& old_ls = multi[i][j];
QgsPolyline& old_ls = multi[i][0];
for ( int k = 0; k < old_ls.size(); k++ ) {
new_ls.append( mTransform->transform( old_ls[k] ) );
}
new_poly.append( new_ls );
cFeat.multiPolygon[0].append( new_ls );
}
else
{
new_poly.append( multi[i][j] );
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 ) {
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 );
}
else
{
new_poly.append( multi[i][j] );
}

cFeat.multiPolygon.append( new_poly );
cFeat.multiPolygon.append( new_poly );
}
}
}
return true;
Expand All @@ -239,7 +262,24 @@ void QgsInvertedPolygonRenderer::stopRender( QgsRenderContext& context )
for ( FeatureCategoryMap::iterator cit = mFeaturesCategoryMap.begin(); cit != mFeaturesCategoryMap.end(); ++cit)
{
QgsFeature feat( cit.value().feature );
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit.value().multiPolygon ) );
if ( !mPreprocessingEnabled )
{
// no preprocessing - the final polygon has already been prepared
feat.setGeometry( QgsGeometry::fromMultiPolygon( cit.value().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, context );
}

Expand Down Expand Up @@ -280,12 +320,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 @@ -297,13 +342,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
13 changes: 13 additions & 0 deletions src/core/symbology-ng/qgsinvertedpolygonrenderer.h
Expand Up @@ -104,6 +104,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 Down Expand Up @@ -145,6 +155,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
Expand Up @@ -67,6 +67,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 @@ -122,11 +125,15 @@ 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 );
}
1 change: 1 addition & 0 deletions src/gui/symbology-ng/qgsinvertedpolygonrendererwidget.h
Expand Up @@ -54,6 +54,7 @@ class GUI_EXPORT QgsInvertedPolygonRendererWidget : public QgsRendererV2Widget,

private slots:
void on_mRendererComboBox_currentIndexChanged( int index );
void on_mMergePolygonsCheckBox_stateChanged( int state );
};


Expand Down
14 changes: 12 additions & 2 deletions src/ui/qgsinvertedpolygonrendererwidgetbase.ui
Expand Up @@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>390</width>
<height>79</height>
<width>316</width>
<height>75</height>
</rect>
</property>
<property name="windowTitle">
Expand All @@ -28,6 +28,16 @@
</item>
</layout>
</item>
<item>
<widget class="QCheckBox" name="mMergePolygonsCheckBox">
<property name="toolTip">
<string/>
</property>
<property name="text">
<string>Merge polygons before rendering (slow)</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
Expand Down
11 changes: 10 additions & 1 deletion tests/src/core/testqgsinvertedpolygonrenderer.cpp
Expand Up @@ -47,6 +47,7 @@ class TestQgsInvertedPolygon: public QObject

void singleSubRenderer();
void graduatedSubRenderer();
void preprocess();

private:
bool mTestHasError;
Expand All @@ -73,7 +74,7 @@ void TestQgsInvertedPolygon::initTestCase()
//
//create a poly layer that will be used in all tests...
//
QString myPolysFileName = mTestDataDir + "polys.shp";
QString myPolysFileName = mTestDataDir + "polys_overlapping.shp";
QFileInfo myPolyFileInfo( myPolysFileName );
mpPolysLayer = new QgsVectorLayer( myPolyFileInfo.filePath(),
myPolyFileInfo.completeBaseName(), "ogr" );
Expand Down Expand Up @@ -115,6 +116,14 @@ void TestQgsInvertedPolygon::graduatedSubRenderer()
QVERIFY( imageCheck( "inverted_polys_graduated" ) );
}

void TestQgsInvertedPolygon::preprocess()
{
// FIXME will have to find some overlapping polygons
mReport += "<h2>Inverted polygon renderer, preprocessing test</h2>\n";
QVERIFY( setQml( "inverted_polys_preprocess.qml" ) );
QVERIFY( imageCheck( "inverted_polys_preprocess" ) );
}

//
// Private helper functions not called directly by CTest
//
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 3 additions & 3 deletions tests/testdata/inverted_polys_graduated.qml
@@ -1,7 +1,7 @@
<!DOCTYPE qgis PUBLIC 'http://mrcc.com/qgis.dtd' 'SYSTEM'>
<qgis version="2.3.0-Master" minimumScale="1" maximumScale="1e+08" simplifyDrawingHints="1" minLabelScale="1" maxLabelScale="1e+08" simplifyDrawingTol="1" simplifyMaxScale="1" hasScaleBasedVisibilityFlag="0" simplifyLocal="1" scaleBasedLabelVisibilityFlag="0">
<renderer-v2 type="invertedPolygonRenderer">
<renderer-v2 attr="Name" symbollevels="0" type="categorizedSymbol">
<renderer-v2 preprocessing="0" type="invertedPolygonRenderer">
<renderer-v2 attr="Name" symbollevels="1" type="categorizedSymbol">
<categories>
<category symbol="0" value="Dam" label="Dam"/>
<category symbol="1" value="Lake" label="Lake"/>
Expand All @@ -27,7 +27,7 @@
</layer>
</symbol>
<symbol alpha="1" type="fill" name="1">
<layer pass="0" class="ShapeburstFill" locked="0">
<layer pass="1" class="ShapeburstFill" locked="0">
<prop k="blur_radius" v="0"/>
<prop k="color1" v="0,0,255,255"/>
<prop k="color2" v="0,255,0,255"/>
Expand Down

0 comments on commit 80ae9ef

Please sign in to comment.