Skip to content
Permalink
Browse files
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 80ae9ef28b1c5734a108a27a39ccaa024eb8eaf3
@@ -28,7 +28,7 @@
#include <QDomElement>

QgsInvertedPolygonRenderer::QgsInvertedPolygonRenderer( const QgsFeatureRendererV2* subRenderer )
: QgsFeatureRendererV2( "invertedPolygonRenderer" )
: QgsFeatureRendererV2( "invertedPolygonRenderer" ), mPreprocessingEnabled( false )
{
if ( subRenderer ) {
setEmbeddedRenderer( subRenderer );
@@ -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
@@ -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();
@@ -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;
@@ -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 );
}

@@ -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 )
@@ -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 )
{
@@ -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& );
@@ -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;
};


@@ -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;
@@ -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 );
}
@@ -54,6 +54,7 @@ class GUI_EXPORT QgsInvertedPolygonRendererWidget : public QgsRendererV2Widget,

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


@@ -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">
@@ -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/>
@@ -47,6 +47,7 @@ class TestQgsInvertedPolygon: public QObject

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

private:
bool mTestHasError;
@@ -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" );
@@ -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
//
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -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"/>
@@ -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"/>

0 comments on commit 80ae9ef

Please sign in to comment.