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