QGIS API Documentation  2.12.0-Lyon
qgsinvertedpolygonrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsinvertedpolygonrenderer.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 
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 #include "qgspainteffect.h"
27 #include "qgspainteffectregistry.h"
28 
29 #include <QDomDocument>
30 #include <QDomElement>
31 
33  : QgsFeatureRendererV2( "invertedPolygonRenderer" )
34  , mPreprocessingEnabled( false )
35 {
36  if ( subRenderer )
37  {
38  setEmbeddedRenderer( subRenderer );
39  }
40  else
41  {
43  }
44 }
45 
47 {
48 }
49 
51 {
52  if ( subRenderer )
53  {
54  mSubRenderer.reset( const_cast<QgsFeatureRendererV2*>( subRenderer )->clone() );
55  }
56  else
57  {
58  mSubRenderer.reset( 0 );
59  }
60 }
61 
63 {
64  return mSubRenderer.data();
65 }
66 
68 {
69  if ( !mSubRenderer )
70  {
71  return;
72  }
73 
74  // first call start render on the sub renderer
75  mSubRenderer->startRender( context, fields );
76 
77  mFeaturesCategories.clear();
78  mSymbolCategories.clear();
79  mFeatureDecorations.clear();
80  mFields = fields;
81 
82  // We compute coordinates of the extent which will serve as exterior ring
83  // for the final polygon
84  // It must be computed in the destination CRS if reprojection is enabled.
85  const QgsMapToPixel& mtp( context.mapToPixel() );
86 
87  if ( !context.painter() )
88  {
89  return;
90  }
91 
92  // convert viewport to dest CRS
93  QRect e( context.painter()->viewport() );
94  // add some space to hide borders and tend to infinity
95  e.adjust( -e.width()*5, -e.height()*5, e.width()*5, e.height()*5 );
96  QgsPolyline exteriorRing;
97  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
98  exteriorRing << mtp.toMapCoordinates( e.topRight() );
99  exteriorRing << mtp.toMapCoordinates( e.bottomRight() );
100  exteriorRing << mtp.toMapCoordinates( e.bottomLeft() );
101  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
102 
103  // copy the rendering context
104  mContext = context;
105 
106  // If reprojection is enabled, we must reproject during renderFeature
107  // and act as if there is no reprojection
108  // If we don't do that, there is no need to have a simple rectangular extent
109  // that covers the whole screen
110  // (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
111  if ( context.coordinateTransform() )
112  {
113  // disable projection
114  mContext.setCoordinateTransform( 0 );
115  // recompute extent so that polygon clipping is correct
116  QRect v( context.painter()->viewport() );
117  mContext.setExtent( QgsRectangle( mtp.toMapCoordinates( v.topLeft() ), mtp.toMapCoordinates( v.bottomRight() ) ) );
118  // do we have to recompute the MapToPixel ?
119  }
120 
121  mExtentPolygon.clear();
122  mExtentPolygon.append( exteriorRing );
123 
124  return;
125 }
126 
127 bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
128 {
129  if ( !context.painter() )
130  {
131  return false;
132  }
133 
134  // store this feature as a feature to render with decoration if needed
135  if ( selected || drawVertexMarker )
136  {
137  mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
138  }
139 
140  // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
141  // This way, users can have multiple inverted polygon fills for a layer,
142  // for instance, with rule based renderer and different symbols
143  // that have transparency.
144  //
145  // In order to assign a unique category to a set of symbols
146  // during each rendering session (between startRender() and stopRender()),
147  // we build an unique id as a QByteArray that is the concatenation
148  // of each symbol's memory address.
149  // The only assumption made here is that symbol(s)ForFeature will
150  // always return the same address for the same symbol(s) shared amongst
151  // different features.
152  // This QByteArray can then be used as a key for a QMap where the list of
153  // features for this category is stored
154  QByteArray catId;
156  {
157  QgsSymbolV2List syms( mSubRenderer->symbolsForFeature( feature, context ) );
158  Q_FOREACH ( QgsSymbolV2* sym, syms )
159  {
160  // append the memory address
161  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
162  }
163  }
164  else
165  {
166  QgsSymbolV2* sym = mSubRenderer->symbolForFeature( feature, context );
167  if ( sym )
168  {
169  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
170  }
171  }
172 
173  if ( catId.isEmpty() )
174  {
175  return false;
176  }
177 
178  if ( ! mSymbolCategories.contains( catId ) )
179  {
180  CombinedFeature cFeat;
181  // store the first feature
182  cFeat.feature = feature;
183  mSymbolCategories.insert( catId, mSymbolCategories.count() );
184  mFeaturesCategories.append( cFeat );
185  }
186 
187  // update the geometry
188  CombinedFeature& cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
189  if ( !feature.constGeometry() )
190  {
191  return false;
192  }
193  QScopedPointer<QgsGeometry> geom( new QgsGeometry( *feature.constGeometry() ) );
194 
195  const QgsCoordinateTransform* xform = context.coordinateTransform();
196  if ( xform )
197  {
198  geom->transform( *xform );
199  }
200 
201  if ( mPreprocessingEnabled )
202  {
203  // fix the polygon if it is not valid
204  if ( ! geom->isGeosValid() )
205  {
206  geom.reset( geom->buffer( 0, 0 ) );
207  }
208  }
209 
210  if ( !geom )
211  return false; // do not let invalid geometries sneak in!
212 
213  // add the geometry to the list of geometries for this feature
214  cFeat.geometries.append( geom.take() );
215 
216  return true;
217 }
218 
220 {
221  if ( !mSubRenderer )
222  {
223  return;
224  }
225  if ( !context.painter() )
226  {
227  return;
228  }
229 
230  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
231  {
232  QgsFeature feat = cit->feature; // just a copy, so that we do not accumulate geometries again
233  if ( mPreprocessingEnabled )
234  {
235  // compute the unary union on the polygons
236  QScopedPointer<QgsGeometry> unioned( QgsGeometry::unaryUnion( cit->geometries ) );
237  // compute the difference with the extent
238  QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
239  QgsGeometry *final = rect->difference( const_cast<QgsGeometry*>( unioned.data() ) );
240  feat.setGeometry( final );
241  }
242  else
243  {
244  // No preprocessing involved.
245  // We build here a "reversed" geometry of all the polygons
246  //
247  // The final geometry is a multipolygon F, with :
248  // * the first polygon of F having the current extent as its exterior ring
249  // * each polygon's exterior ring is added as interior ring of the first polygon of F
250  // * each polygon's interior ring is added as new polygons in F
251  //
252  // No validity check is done, on purpose, it will be very slow and painting
253  // operations do not need geometries to be valid
254  QgsMultiPolygon finalMulti;
255  finalMulti.append( mExtentPolygon );
256  Q_FOREACH ( QgsGeometry* geom, cit->geometries )
257  {
258  QgsMultiPolygon multi;
259  if (( geom->wkbType() == QGis::WKBPolygon ) ||
260  ( geom->wkbType() == QGis::WKBPolygon25D ) )
261  {
262  multi.append( geom->asPolygon() );
263  }
264  else if (( geom->wkbType() == QGis::WKBMultiPolygon ) ||
265  ( geom->wkbType() == QGis::WKBMultiPolygon25D ) )
266  {
267  multi = geom->asMultiPolygon();
268  }
269 
270  for ( int i = 0; i < multi.size(); i++ )
271  {
272  const QgsPolyline& exterior = multi[i][0];
273  // add the exterior ring as interior ring to the first polygon
274  // make sure it satisfies at least very basic requirements of GEOS
275  // (otherwise the creation of GEOS geometry will fail)
276  if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
277  continue;
278  finalMulti[0].append( exterior );
279 
280  // add interior rings as new polygons
281  for ( int j = 1; j < multi[i].size(); j++ )
282  {
283  QgsPolygon new_poly;
284  new_poly.append( multi[i][j] );
285  finalMulti.append( new_poly );
286  }
287  }
288  }
289  feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
290  }
291  if ( feat.constGeometry() )
292  {
293  mContext.expressionContext().setFeature( feat );
294  mSubRenderer->renderFeature( feat, mContext );
295  }
296  }
297  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
298  {
299  Q_FOREACH ( QgsGeometry* g, cit->geometries )
300  {
301  delete g;
302  }
303  }
304 
305  // when no features are visible, we still have to draw the exterior rectangle
306  // warning: when sub renderers have more than one possible symbols,
307  // there is no way to choose a correct one, because there is no attribute here
308  // in that case, nothing will be rendered
309  if ( mFeaturesCategories.isEmpty() )
310  {
311  // empty feature with default attributes
312  QgsFeature feat( mFields );
313  feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
314  mSubRenderer->renderFeature( feat, mContext );
315  }
316 
317  // draw feature decorations
318  Q_FOREACH ( FeatureDecoration deco, mFeatureDecorations )
319  {
320  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
321  }
322 
323  mSubRenderer->stopRender( mContext );
324 }
325 
327 {
328  if ( !mSubRenderer )
329  {
330  return "INVERTED: NULL";
331  }
332  return "INVERTED [" + mSubRenderer->dump() + "]";
333 }
334 
336 {
337  QgsInvertedPolygonRenderer* newRenderer;
338  if ( mSubRenderer.isNull() )
339  {
340  newRenderer = new QgsInvertedPolygonRenderer( 0 );
341  }
342  else
343  {
344  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data() );
345  }
347  copyPaintEffect( newRenderer );
348  return newRenderer;
349 }
350 
352 {
354  //look for an embedded renderer <renderer-v2>
355  QDomElement embeddedRendererElem = element.firstChildElement( "renderer-v2" );
356  if ( !embeddedRendererElem.isNull() )
357  {
358  QgsFeatureRendererV2* renderer = QgsFeatureRendererV2::load( embeddedRendererElem );
359  r->setEmbeddedRenderer( renderer );
360  delete renderer;
361  }
362  r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
363  return r;
364 }
365 
367 {
368  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
369  rendererElem.setAttribute( "type", "invertedPolygonRenderer" );
370  rendererElem.setAttribute( "preprocessing", preprocessingEnabled() ? "1" : "0" );
371  rendererElem.setAttribute( "forceraster", ( mForceRaster ? "1" : "0" ) );
372 
373  if ( mSubRenderer )
374  {
375  QDomElement embeddedRendererElem = mSubRenderer->save( doc );
376  rendererElem.appendChild( embeddedRendererElem );
377  }
378 
380  mPaintEffect->saveProperties( doc, rendererElem );
381 
382  return rendererElem;
383 }
384 
386 {
387  if ( !mSubRenderer )
388  {
389  return 0;
390  }
391  return mSubRenderer->symbolForFeature( feature, context );
392 }
393 
395 {
396  if ( !mSubRenderer )
397  return 0;
398  return mSubRenderer->originalSymbolForFeature( feat, context );
399 }
400 
402 {
403  if ( !mSubRenderer )
404  {
405  return QgsSymbolV2List();
406  }
407  return mSubRenderer->symbolsForFeature( feature, context );
408 }
409 
411 {
412  if ( !mSubRenderer )
413  return QgsSymbolV2List();
414  return mSubRenderer->originalSymbolsForFeature( feat, context );
415 }
416 
418 {
419  if ( !mSubRenderer )
420  {
421  return QgsSymbolV2List();
422  }
423  return mSubRenderer->symbols( context );
424 }
425 
427 {
428  if ( !mSubRenderer )
429  {
430  return 0;
431  }
432  return mSubRenderer->capabilities();
433 }
434 
436 {
437  if ( !mSubRenderer )
438  {
439  return QList<QString>();
440  }
441  return mSubRenderer->usedAttributes();
442 }
443 
445 {
446  if ( !mSubRenderer )
447  {
448  return QgsLegendSymbologyList();
449  }
450  return mSubRenderer->legendSymbologyItems( iconSize );
451 }
452 
454 {
455  if ( !mSubRenderer )
456  {
457  return QgsLegendSymbolList();
458  }
459  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
460 }
461 
463 {
464  if ( !mSubRenderer )
465  {
466  return false;
467  }
468  return mSubRenderer->willRenderFeature( feat, context );
469 }
470 
472 {
473  if ( renderer->type() == "invertedPolygonRenderer" )
474  {
475  return dynamic_cast<QgsInvertedPolygonRenderer*>( renderer->clone() );
476  }
477 
478  if ( renderer->type() == "singleSymbol" ||
479  renderer->type() == "categorizedSymbol" ||
480  renderer->type() == "graduatedSymbol" ||
481  renderer->type() == "RuleRenderer" )
482  {
483  return new QgsInvertedPolygonRenderer( renderer->clone() );
484  }
485  return 0;
486 }
487 
void clear()
#define RENDERER_TAG_NAME
Definition: qgsrendererv2.h:48
A rectangle specified with double values.
Definition: qgsrectangle.h:35
bool contains(const Key &key) const
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
Renders a given feature.
QList< QgsSymbolV2 * > QgsSymbolV2List
Definition: qgsrendererv2.h:39
QDomNode appendChild(const QDomNode &newChild)
void append(const T &value)
iterator begin()
QString attribute(const QString &name, const QString &defValue) const
virtual bool willRenderFeature(QgsFeature &feat, QgsRenderContext &context) override
Proxy that will call this method on the embedded renderer.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
static QgsInvertedPolygonRenderer * convertFromRenderer(const QgsFeatureRendererV2 *renderer)
Creates a QgsInvertedPolygonRenderer by a conversion from an existing renderer.
QgsPolygon asPolygon() const
Return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
bool isEmpty() const
Container of fields for a vector layer.
Definition: qgsfield.h:177
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
QgsInvertedPolygonRenderer(const QgsFeatureRendererV2 *embeddedRenderer=0)
Constructor.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:176
virtual QString dump() const override
const QgsCoordinateTransform * coordinateTransform() const
void clear()
void setExtent(const QgsRectangle &extent)
QgsPaintEffect * mPaintEffect
QgsMultiPolygon asMultiPolygon() const
Return contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty ...
void reset(T *other)
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
QString type() const
Definition: qgsrendererv2.h:82
void setEmbeddedRenderer(const QgsFeatureRendererV2 *subRenderer)
Sets the embedded renderer.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields) override
Needs to be called when a new render cycle is started.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:34
void clear()
virtual QgsFeatureRendererV2 * clone() const =0
void setGeometry(const QgsGeometry &geom)
Set this feature's geometry from another QgsGeometry object.
Definition: qgsfeature.cpp:106
void append(const T &value)
virtual QgsFeatureRendererV2 * clone() const override
Used to clone this feature renderer.
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
virtual QgsSymbolV2List originalSymbolsForFeature(QgsFeature &feat, QgsRenderContext &context) override
Proxy that will call this method on the embedded renderer.
virtual QgsLegendSymbolList legendSymbolItems(double scaleDenominator=-1, const QString &rule="") override
Proxy that will call this method on the embedded renderer.
void setAttribute(const QString &name, const QString &value)
int toInt(bool *ok, int base) const
virtual QgsSymbolV2 * originalSymbolForFeature(QgsFeature &feat, QgsRenderContext &context) override
Proxy that will call this method on the embedded renderer.
QGis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static QgsFeatureRendererV2 * defaultRenderer(QGis::GeometryType geomType)
return a new renderer - used by default in vector layers
virtual Q_DECL_DEPRECATED QgsSymbolV2List symbols()
for symbol levels
QList< QPair< QString, QPixmap > > QgsLegendSymbologyList
virtual QgsSymbolV2 * symbolForFeature(QgsFeature &feature, QgsRenderContext &context) override
Proxy that will call this method on the embedded renderer.
virtual QList< QString > usedAttributes() override
Proxy that will call this method on the embedded renderer.
QByteArray & append(char ch)
T * data() const
virtual QDomElement save(QDomDocument &doc) override
Creates an XML representation of the renderer.
QgsExpressionContext & expressionContext()
Gets the expression context.
virtual QgsLegendSymbologyList legendSymbologyItems(QSize iconSize) override
Proxy that will call this method on the embedded renderer.
bool isNull() const
QRect viewport() const
bool isNull() const
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
Contains information about the context of a rendering operation.
const QgsFeatureRendererV2 * embeddedRenderer() const
QPainter * painter()
void copyPaintEffect(QgsFeatureRendererV2 *destRenderer) const
Copies paint effect of this renderer to another renderer.
bool isEmpty() const
static QgsFeatureRendererV2 * load(QDomElement &symbologyElem)
create a renderer from XML element
QDomElement firstChildElement(const QString &tagName) const
int count(const T &value) const
void adjust(int dx1, int dy1, int dx2, int dy2)
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:70
Class for doing transforms between two map coordinate systems.
virtual int capabilities() override
Proxy that will call this method on the embedded renderer.
const QgsMapToPixel & mapToPixel() const
static QgsGeometry * unaryUnion(const QList< QgsGeometry * > &geometryList)
Compute the unary union on a list of geometries.
iterator insert(const Key &key, const T &value)
static QgsGeometry * fromMultiPolygon(const QgsMultiPolygon &multipoly)
Creates a new geometry from a QgsMultiPolygon.
static QgsGeometry * fromPolygon(const QgsPolygon &polygon)
Creates a new geometry from a QgsPolygon.
virtual void stopRender(QgsRenderContext &context) override
The actual rendering will take place here.
static QgsFeatureRendererV2 * create(QDomElement &element)
Creates a renderer out of an XML, for loading.
QDomElement createElement(const QString &tagName)
virtual QgsSymbolV2List symbolsForFeature(QgsFeature &feat, QgsRenderContext &context) override
Proxy that will call this method on the embedded renderer.
int size() const
QList< QPair< QString, QgsSymbolV2 * > > QgsLegendSymbolList
Definition: qgsrendererv2.h:43
iterator end()
int count(const Key &key) const