QGIS API Documentation  2.99.0-Master (5753576)
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.data();
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  return;
153 }
154 
155 bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
156 {
157  if ( !context.painter() )
158  {
159  return false;
160  }
161 
162  // store this feature as a feature to render with decoration if needed
163  if ( selected || drawVertexMarker )
164  {
165  mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
166  }
167 
168  // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
169  // This way, users can have multiple inverted polygon fills for a layer,
170  // for instance, with rule based renderer and different symbols
171  // that have transparency.
172  //
173  // In order to assign a unique category to a set of symbols
174  // during each rendering session (between startRender() and stopRender()),
175  // we build an unique id as a QByteArray that is the concatenation
176  // of each symbol's memory address.
177  // The only assumption made here is that symbol(s)ForFeature will
178  // always return the same address for the same symbol(s) shared amongst
179  // different features.
180  // This QByteArray can then be used as a key for a QMap where the list of
181  // features for this category is stored
182  QByteArray catId;
184  {
185  QgsSymbolList syms( mSubRenderer->symbolsForFeature( feature, context ) );
186  Q_FOREACH ( QgsSymbol* sym, syms )
187  {
188  // append the memory address
189  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
190  }
191  }
192  else
193  {
194  QgsSymbol* sym = mSubRenderer->symbolForFeature( feature, context );
195  if ( sym )
196  {
197  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
198  }
199  }
200 
201  if ( catId.isEmpty() )
202  {
203  return false;
204  }
205 
206  if ( ! mSymbolCategories.contains( catId ) )
207  {
208  CombinedFeature cFeat;
209  // store the first feature
210  cFeat.feature = feature;
211  mSymbolCategories.insert( catId, mSymbolCategories.count() );
212  mFeaturesCategories.append( cFeat );
213  }
214 
215  // update the geometry
216  CombinedFeature& cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
217  if ( !feature.hasGeometry() )
218  {
219  return false;
220  }
221  QgsGeometry geom = feature.geometry();
222 
224  if ( xform.isValid() )
225  {
226  geom.transform( xform );
227  }
228 
229  if ( mPreprocessingEnabled )
230  {
231  // fix the polygon if it is not valid
232  if ( ! geom.isGeosValid() )
233  {
234  geom = geom.buffer( 0, 0 );
235  }
236  }
237 
238  if ( geom.isEmpty() )
239  return false; // do not let invalid geometries sneak in!
240 
241  // add the geometry to the list of geometries for this feature
242  cFeat.geometries.append( geom );
243 
244  return true;
245 }
246 
248 {
249  if ( !mSubRenderer )
250  {
251  return;
252  }
253  if ( !context.painter() )
254  {
255  return;
256  }
257 
258  QgsMultiPolygon finalMulti; //avoid expensive allocation for list for every feature
259  QgsPolygon newPoly;
260 
261  Q_FOREACH ( const CombinedFeature& cit, mFeaturesCategories )
262  {
263  finalMulti.resize( 0 ); //preserve capacity - don't use clear!
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  QgsGeometry unioned( QgsGeometry::unaryUnion( cit.geometries ) );
269  // compute the difference with the extent
270  QgsGeometry rect = QgsGeometry::fromPolygon( mExtentPolygon );
271  QgsGeometry final = rect.difference( unioned );
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 
287  finalMulti.append( mExtentPolygon );
288  Q_FOREACH ( const 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  newPoly.resize( 0 ); //preserve capacity - don't use clear!
316  newPoly.append( multi[i][j] );
317  finalMulti.append( newPoly );
318  }
319  }
320  }
321  feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
322  }
323  if ( feat.hasGeometry() )
324  {
325  mContext.expressionContext().setFeature( feat );
326  mSubRenderer->renderFeature( feat, mContext );
327  }
328  }
329 
330  // when no features are visible, we still have to draw the exterior rectangle
331  // warning: when sub renderers have more than one possible symbols,
332  // there is no way to choose a correct one, because there is no attribute here
333  // in that case, nothing will be rendered
334  if ( mFeaturesCategories.isEmpty() )
335  {
336  // empty feature with default attributes
337  QgsFeature feat( mFields );
338  feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
339  mSubRenderer->renderFeature( feat, mContext );
340  }
341 
342  // draw feature decorations
343  Q_FOREACH ( FeatureDecoration deco, mFeatureDecorations )
344  {
345  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
346  }
347 
348  mSubRenderer->stopRender( mContext );
349 }
350 
352 {
353  if ( !mSubRenderer )
354  {
355  return QStringLiteral( "INVERTED: NULL" );
356  }
357  return "INVERTED [" + mSubRenderer->dump() + ']';
358 }
359 
361 {
362  QgsInvertedPolygonRenderer* newRenderer;
363  if ( mSubRenderer.isNull() )
364  {
365  newRenderer = new QgsInvertedPolygonRenderer( nullptr );
366  }
367  else
368  {
369  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data()->clone() );
370  }
372  copyRendererData( newRenderer );
373  return newRenderer;
374 }
375 
377 {
379  //look for an embedded renderer <renderer-v2>
380  QDomElement embeddedRendererElem = element.firstChildElement( QStringLiteral( "renderer-v2" ) );
381  if ( !embeddedRendererElem.isNull() )
382  {
383  QgsFeatureRenderer* renderer = QgsFeatureRenderer::load( embeddedRendererElem );
384  r->setEmbeddedRenderer( renderer );
385  }
386  r->setPreprocessingEnabled( element.attribute( QStringLiteral( "preprocessing" ), QStringLiteral( "0" ) ).toInt() == 1 );
387  return r;
388 }
389 
390 QDomElement QgsInvertedPolygonRenderer::save( QDomDocument& doc )
391 {
392  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
393  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "invertedPolygonRenderer" ) );
394  rendererElem.setAttribute( QStringLiteral( "preprocessing" ), preprocessingEnabled() ? "1" : "0" );
395  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
396 
397  if ( mSubRenderer )
398  {
399  QDomElement embeddedRendererElem = mSubRenderer->save( doc );
400  rendererElem.appendChild( embeddedRendererElem );
401  }
402 
404  mPaintEffect->saveProperties( doc, rendererElem );
405 
406  if ( !mOrderBy.isEmpty() )
407  {
408  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
409  mOrderBy.save( orderBy );
410  rendererElem.appendChild( orderBy );
411  }
412  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
413 
414  return rendererElem;
415 }
416 
418 {
419  if ( !mSubRenderer )
420  {
421  return nullptr;
422  }
423  return mSubRenderer->symbolForFeature( feature, context );
424 }
425 
427 {
428  if ( !mSubRenderer )
429  return nullptr;
430  return mSubRenderer->originalSymbolForFeature( feat, context );
431 }
432 
434 {
435  if ( !mSubRenderer )
436  {
437  return QgsSymbolList();
438  }
439  return mSubRenderer->symbolsForFeature( feature, context );
440 }
441 
443 {
444  if ( !mSubRenderer )
445  return QgsSymbolList();
446  return mSubRenderer->originalSymbolsForFeature( feat, context );
447 }
448 
450 {
451  if ( !mSubRenderer )
452  {
453  return QgsSymbolList();
454  }
455  return mSubRenderer->symbols( context );
456 }
457 
458 QgsFeatureRenderer::Capabilities QgsInvertedPolygonRenderer::capabilities()
459 {
460  if ( !mSubRenderer )
461  {
462  return 0;
463  }
464  return mSubRenderer->capabilities();
465 }
466 
468 {
469  if ( !mSubRenderer )
470  {
471  return QSet<QString>();
472  }
473  return mSubRenderer->usedAttributes();
474 }
475 
477 {
478  if ( !mSubRenderer )
479  {
480  return QgsLegendSymbologyList();
481  }
482  return mSubRenderer->legendSymbologyItems( iconSize );
483 }
484 
485 QgsLegendSymbolList QgsInvertedPolygonRenderer::legendSymbolItems( double scaleDenominator, const QString& rule )
486 {
487  if ( !mSubRenderer )
488  {
489  return QgsLegendSymbolList();
490  }
491  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
492 }
493 
495 {
496  if ( !mSubRenderer )
497  {
498  return false;
499  }
500  return mSubRenderer->willRenderFeature( feat, context );
501 }
502 
504 {
505  if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
506  {
507  return dynamic_cast<QgsInvertedPolygonRenderer*>( renderer->clone() );
508  }
509 
510  if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
511  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
512  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
513  renderer->type() == QLatin1String( "RuleRenderer" ) )
514  {
515  return new QgsInvertedPolygonRenderer( renderer->clone() );
516  }
517  return nullptr;
518 }
519 
QgsPolygon asPolygon() const
Return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
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:191
A rectangle specified with double values.
Definition: qgsrectangle.h:36
virtual bool legendSymbolItemChecked(const QString &key) override
items of symbology items in legend is checked
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:449
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
Renders a given feature.
static QgsFeatureRenderer * create(QDomElement &element)
Creates a renderer out of an XML, for loading.
void setCoordinateTransform(const QgsCoordinateTransform &t)
Sets coordinate transformation.
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:47
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.
static QgsFeatureRenderer * load(QDomElement &symbologyElem)
create a renderer from XML element
Container of fields for a vector layer.
Definition: qgsfields.h:39
QgsPoint toMapCoordinates(int x, int y) const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:79
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:50
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:435
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:136
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:199
void setExtent(const QgsRectangle &extent)
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:34
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:65
QString type() const
Definition: qgsrenderer.h:93
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 QgsLegendSymbolList legendSymbolItems(double scaleDenominator=-1, const QString &rule="") override
Proxy that will call this method on the embedded renderer.
static QgsGeometry unaryUnion(const QList< QgsGeometry > &geometryList)
Compute the unary union on a list of geometries.
QVector< QgsPolygon > QgsMultiPolygon
A collection of QgsPolygons that share a common collection of attributes.
Definition: qgsgeometry.h:62
bool isEmpty() const
Returns true if the geometry is empty (ie, contains no underlying geometry accessible via geometry)...
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:53
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:113
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 QDomElement save(QDomDocument &doc) override
store renderer info to XML element
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.
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.
QgsInvertedPolygonRenderer(QgsFeatureRenderer *embeddedRenderer=nullptr)
Constructor.
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:147
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:41
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
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.
QList< QPair< QString, QgsSymbol * > > QgsLegendSymbolList
Definition: qgsrenderer.h:45
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:397
virtual QSet< QString > usedAttributes() const override
Proxy that will call this method on the embedded renderer.
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.
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...