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