Skip to content

Commit e6758d6

Browse files
author
Hugo Mercier
committed
Add a new 'mask' feature renderer that can be used to invert polygon fills
1 parent bbca1d0 commit e6758d6

File tree

8 files changed

+636
-0
lines changed

8 files changed

+636
-0
lines changed

src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ SET(QGIS_CORE_SRCS
3636
symbology-ng/qgscategorizedsymbolrendererv2.cpp
3737
symbology-ng/qgsgraduatedsymbolrendererv2.cpp
3838
symbology-ng/qgsrulebasedrendererv2.cpp
39+
symbology-ng/qgsmaskrendererv2.cpp
3940
symbology-ng/qgsvectorcolorrampv2.cpp
4041
symbology-ng/qgscptcityarchive.cpp
4142
symbology-ng/qgsstylev2.cpp
@@ -583,6 +584,7 @@ SET(QGIS_CORE_HDRS
583584
symbology-ng/qgsrendererv2registry.h
584585
symbology-ng/qgsrulebasedrendererv2.h
585586
symbology-ng/qgssinglesymbolrendererv2.h
587+
symbology-ng/qgsmaskrendererv2.h
586588
symbology-ng/qgsstylev2.h
587589
symbology-ng/qgssvgcache.h
588590
symbology-ng/qgssymbollayerv2.h
Lines changed: 303 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,303 @@
1+
/***************************************************************************
2+
qgsmaskrendererv2.cpp
3+
---------------------
4+
begin : April 2014
5+
copyright : (C) 2014 Hugo Mercier / Oslandia
6+
email : hugo dot mercier at oslandia dot com
7+
***************************************************************************
8+
* *
9+
* This program is free software; you can redistribute it and/or modify *
10+
* it under the terms of the GNU General Public License as published by *
11+
* the Free Software Foundation; either version 2 of the License, or *
12+
* (at your option) any later version. *
13+
* *
14+
***************************************************************************/
15+
16+
#include "qgsmaskrendererv2.h"
17+
18+
#include "qgssymbolv2.h"
19+
#include "qgssymbollayerv2utils.h"
20+
21+
#include "qgslogger.h"
22+
#include "qgsfeature.h"
23+
#include "qgsvectorlayer.h"
24+
#include "qgssymbollayerv2.h"
25+
#include "qgsogcutils.h"
26+
27+
#include <QDomDocument>
28+
#include <QDomElement>
29+
30+
QgsMaskRendererV2::QgsMaskRendererV2( const QgsFeatureRendererV2* subRenderer )
31+
: QgsFeatureRendererV2( "maskRenderer" )
32+
{
33+
if ( subRenderer ) {
34+
setEmbeddedRenderer( subRenderer );
35+
}
36+
else {
37+
mSubRenderer.reset( QgsFeatureRendererV2::defaultRenderer( QGis::Polygon ) );
38+
}
39+
}
40+
41+
QgsMaskRendererV2::~QgsMaskRendererV2()
42+
{
43+
}
44+
45+
void QgsMaskRendererV2::setEmbeddedRenderer( const QgsFeatureRendererV2* subRenderer )
46+
{
47+
if ( subRenderer ) {
48+
mSubRenderer.reset( const_cast<QgsFeatureRendererV2*>(subRenderer)->clone() );
49+
}
50+
else {
51+
mSubRenderer.reset( 0 );
52+
}
53+
}
54+
55+
const QgsFeatureRendererV2* QgsMaskRendererV2::embeddedRenderer() const
56+
{
57+
return mSubRenderer.data();
58+
}
59+
60+
void QgsMaskRendererV2::startRender( QgsRenderContext& context, const QgsFields& fields )
61+
{
62+
if ( !mSubRenderer ) {
63+
return;
64+
}
65+
66+
mSubRenderer->startRender( context, fields );
67+
mFeaturesCategoryMap.clear();
68+
mFeatureDecorations.clear();
69+
mFields = fields;
70+
}
71+
72+
bool QgsMaskRendererV2::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
73+
{
74+
Q_UNUSED( context );
75+
76+
// Features are grouped by category of symbols (returned by symbol(s)ForFeature)
77+
// This way, users can have multiple inverted polygon fills for a layer,
78+
// for instance, with rule based renderer and different symbols
79+
// that have transparency.
80+
//
81+
// In order to assign a unique category to a set of symbols
82+
// during each rendering session (between startRender() and stopRender()),
83+
// we build an unique id as a QByteArray that is the concatenation
84+
// of each symbol's memory address.
85+
// The only assumption made here is that symbol(s)ForFeature will
86+
// always return the same address for the same symbol(s) shared amongst
87+
// different features.
88+
// This QByteArray can then be used as a key for a QMap where the list of
89+
// features for this category is stored
90+
QByteArray catId;
91+
if ( capabilities() & MoreSymbolsPerFeature ) {
92+
QgsSymbolV2List syms( mSubRenderer->symbolsForFeature( feature ) );
93+
foreach ( QgsSymbolV2* sym, syms )
94+
{
95+
// append the memory address
96+
catId.append( reinterpret_cast<const char*>(&sym), sizeof(sym) );
97+
}
98+
}
99+
else
100+
{
101+
QgsSymbolV2* sym = mSubRenderer->symbolForFeature( feature );
102+
if (sym) {
103+
catId.append( reinterpret_cast<const char*>(&sym), sizeof(sym) );
104+
}
105+
}
106+
if ( !catId.isEmpty() )
107+
{
108+
mFeaturesCategoryMap[catId].append( feature );
109+
}
110+
111+
// store this feature as a feature to render with decoration if needed
112+
if ( selected || drawVertexMarker )
113+
{
114+
mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer) );
115+
}
116+
117+
return true;
118+
}
119+
120+
void QgsMaskRendererV2::stopRender( QgsRenderContext& context )
121+
{
122+
if ( !mSubRenderer ) {
123+
return;
124+
}
125+
126+
// We build here a "reversed" geometry of all the polygons
127+
//
128+
// The final geometry is a multipolygon F, with :
129+
// * the first polygon of F having the current extent as its exterior ring
130+
// * each polygon's exterior ring is added as interior ring of the first polygon of F
131+
// * each polygon's interior ring is added as new polygons in F
132+
//
133+
// No validity check is done, on purpose, it will be very slow and painting
134+
// operations do not need geometries to be valid
135+
for ( FeatureCategoryMap::iterator cit = mFeaturesCategoryMap.begin(); cit != mFeaturesCategoryMap.end(); ++cit)
136+
{
137+
QgsMultiPolygon geom;
138+
geom.push_back( QgsPolygon() );
139+
140+
// build a rectangle out of the current extent
141+
QgsRectangle e = context.extent();
142+
// add some space to hide borders
143+
e.scale(2.0);
144+
QgsPolyline extent_ring;
145+
extent_ring.push_back( QgsPoint(e.xMinimum(), e.yMinimum()) );
146+
extent_ring.push_back( QgsPoint(e.xMaximum(), e.yMinimum()) );
147+
extent_ring.push_back( QgsPoint(e.xMaximum(), e.yMaximum()) );
148+
extent_ring.push_back( QgsPoint(e.xMinimum(), e.yMaximum()) );
149+
extent_ring.push_back( QgsPoint(e.xMinimum(), e.yMinimum()) );
150+
geom[0].push_back( extent_ring );
151+
152+
for ( QList<QgsFeature>::iterator fit = cit.value().begin(); fit != cit.value().end(); ++fit )
153+
{
154+
if ( ! fit->geometry() ) {
155+
continue;
156+
}
157+
QgsMultiPolygon multi;
158+
if ( (fit->geometry()->wkbType() == QGis::WKBPolygon) || (fit->geometry()->wkbType() == QGis::WKBPolygon25D) ) {
159+
multi.push_back( fit->geometry()->asPolygon() );
160+
}
161+
else if ( (fit->geometry()->wkbType() == QGis::WKBMultiPolygon) || (fit->geometry()->wkbType() == QGis::WKBMultiPolygon25D) ) {
162+
multi = fit->geometry()->asMultiPolygon();
163+
}
164+
for ( int i = 0; i < multi.size(); i++ ) {
165+
// add the exterior ring as interior ring
166+
geom[0].push_back( multi[i][0] );
167+
// add interior rings as new exterior rings
168+
for ( int j = 1; j < multi[i].size(); j++ ) {
169+
QgsPolygon new_poly;
170+
new_poly.push_back( multi[i][j] );
171+
geom.push_back( new_poly );
172+
}
173+
}
174+
}
175+
176+
QgsFeature feat( cit.value()[0] );
177+
feat.setGeometry( QgsGeometry::fromMultiPolygon( geom ) );
178+
mSubRenderer->renderFeature( feat, context );
179+
}
180+
181+
// when no features are visible, we still have to draw the exterior rectangle
182+
if ( mFeaturesCategoryMap.isEmpty() )
183+
{
184+
QgsRectangle e = context.extent();
185+
// add some space to hide borders
186+
e.scale(2.0);
187+
QgsFeature feat( mFields );
188+
feat.setGeometry( QgsGeometry::fromRect(e) );
189+
mSubRenderer->renderFeature( feat, context );
190+
}
191+
192+
// draw feature decorations
193+
foreach (FeatureDecoration deco, mFeatureDecorations )
194+
{
195+
mSubRenderer->renderFeature( deco.feature, context, deco.layer, deco.selected, deco.drawMarkers );
196+
}
197+
198+
mSubRenderer->stopRender( context );
199+
}
200+
201+
QString QgsMaskRendererV2::dump() const
202+
{
203+
if ( !mSubRenderer ) {
204+
return "MASK: NULL";
205+
}
206+
return "MASK [" + mSubRenderer->dump() + "]";
207+
}
208+
209+
QgsFeatureRendererV2* QgsMaskRendererV2::clone()
210+
{
211+
QgsMaskRendererV2* r = new QgsMaskRendererV2( mSubRenderer.data() );
212+
return r;
213+
}
214+
215+
QgsFeatureRendererV2* QgsMaskRendererV2::create( QDomElement& element )
216+
{
217+
QgsMaskRendererV2* r = new QgsMaskRendererV2();
218+
//look for an embedded renderer <renderer-v2>
219+
QDomElement embeddedRendererElem = element.firstChildElement( "renderer-v2" );
220+
if ( !embeddedRendererElem.isNull() )
221+
{
222+
r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
223+
}
224+
return r;
225+
}
226+
227+
QDomElement QgsMaskRendererV2::save( QDomDocument& doc )
228+
{
229+
QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
230+
rendererElem.setAttribute( "type", "maskRenderer" );
231+
232+
if ( mSubRenderer )
233+
{
234+
QDomElement embeddedRendererElem = mSubRenderer->save( doc );
235+
rendererElem.appendChild( embeddedRendererElem );
236+
}
237+
238+
return rendererElem;
239+
}
240+
241+
QgsSymbolV2* QgsMaskRendererV2::symbolForFeature( QgsFeature& feature )
242+
{
243+
if ( !mSubRenderer ) {
244+
return 0;
245+
}
246+
return mSubRenderer->symbolForFeature( feature );
247+
}
248+
249+
QgsSymbolV2List QgsMaskRendererV2::symbolsForFeature( QgsFeature& feature )
250+
{
251+
if ( !mSubRenderer ) {
252+
return QgsSymbolV2List();
253+
}
254+
return mSubRenderer->symbolsForFeature( feature );
255+
}
256+
257+
QgsSymbolV2List QgsMaskRendererV2::symbols()
258+
{
259+
if ( !mSubRenderer ) {
260+
return QgsSymbolV2List();
261+
}
262+
return mSubRenderer->symbols();
263+
}
264+
265+
int QgsMaskRendererV2::capabilities()
266+
{
267+
if ( !mSubRenderer ) {
268+
return 0;
269+
}
270+
return mSubRenderer->capabilities();
271+
}
272+
273+
QList<QString> QgsMaskRendererV2::usedAttributes()
274+
{
275+
if ( !mSubRenderer ) {
276+
return QList<QString>();
277+
}
278+
return mSubRenderer->usedAttributes();
279+
}
280+
281+
QgsLegendSymbologyList QgsMaskRendererV2::legendSymbologyItems( QSize iconSize )
282+
{
283+
if ( !mSubRenderer ) {
284+
return QgsLegendSymbologyList();
285+
}
286+
return mSubRenderer->legendSymbologyItems( iconSize );
287+
}
288+
289+
QgsLegendSymbolList QgsMaskRendererV2::legendSymbolItems( double scaleDenominator, QString rule )
290+
{
291+
if ( !mSubRenderer ) {
292+
return QgsLegendSymbolList();
293+
}
294+
return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
295+
}
296+
297+
bool QgsMaskRendererV2::willRenderFeature( QgsFeature& feat )
298+
{
299+
if ( !mSubRenderer ) {
300+
return false;
301+
}
302+
return mSubRenderer->willRenderFeature( feat );
303+
}

0 commit comments

Comments
 (0)