QGIS API Documentation  2.99.0-Master (37c43df)
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 }
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 
91 void QgsInvertedPolygonRenderer::checkLegendSymbolItem( const QString& key, bool state )
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().isValid() )
144  {
145  // disable projection
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  QgsSymbolList syms( mSubRenderer->symbolsForFeature( feature, context ) );
190  Q_FOREACH ( QgsSymbol* sym, syms )
191  {
192  // append the memory address
193  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
194  }
195  }
196  else
197  {
198  QgsSymbol* 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.hasGeometry() )
222  {
223  return false;
224  }
225  QgsGeometry geom = feature.geometry();
226 
228  if ( xform.isValid() )
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 = geom.buffer( 0, 0 );
239  }
240  }
241 
242  if ( geom.isEmpty() )
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 );
247 
248  return true;
249 }
250 
252 {
253  if ( !mSubRenderer )
254  {
255  return;
256  }
257  if ( !context.painter() )
258  {
259  return;
260  }
261 
262  QgsMultiPolygon finalMulti; //avoid expensive allocation for list for every feature
263  QgsPolygon newPoly;
264 
265  Q_FOREACH ( const CombinedFeature& cit, mFeaturesCategories )
266  {
267  finalMulti.resize( 0 ); //preserve capacity - don't use clear!
268  QgsFeature feat = cit.feature; // just a copy, so that we do not accumulate geometries again
269  if ( mPreprocessingEnabled )
270  {
271  // compute the unary union on the polygons
272  QgsGeometry unioned( QgsGeometry::unaryUnion( cit.geometries ) );
273  // compute the difference with the extent
274  QgsGeometry rect = QgsGeometry::fromPolygon( mExtentPolygon );
275  QgsGeometry final = rect.difference( unioned );
276  feat.setGeometry( final );
277  }
278  else
279  {
280  // No preprocessing involved.
281  // We build here a "reversed" geometry of all the polygons
282  //
283  // The final geometry is a multipolygon F, with :
284  // * the first polygon of F having the current extent as its exterior ring
285  // * each polygon's exterior ring is added as interior ring of the first polygon of F
286  // * each polygon's interior ring is added as new polygons in F
287  //
288  // No validity check is done, on purpose, it will be very slow and painting
289  // operations do not need geometries to be valid
290 
291  finalMulti.append( mExtentPolygon );
292  Q_FOREACH ( const QgsGeometry& geom, cit.geometries )
293  {
294  QgsMultiPolygon multi;
296 
297  if (( type == QgsWkbTypes::Polygon ) || ( type == QgsWkbTypes::CurvePolygon ) )
298  {
299  multi.append( geom.asPolygon() );
300  }
301  else if (( type == QgsWkbTypes::MultiPolygon ) || ( type == QgsWkbTypes::MultiSurface ) )
302  {
303  multi = geom.asMultiPolygon();
304  }
305 
306  for ( int i = 0; i < multi.size(); i++ )
307  {
308  const QgsPolyline& exterior = multi[i][0];
309  // add the exterior ring as interior ring to the first polygon
310  // make sure it satisfies at least very basic requirements of GEOS
311  // (otherwise the creation of GEOS geometry will fail)
312  if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
313  continue;
314  finalMulti[0].append( exterior );
315 
316  // add interior rings as new polygons
317  for ( int j = 1; j < multi[i].size(); j++ )
318  {
319  newPoly.resize( 0 ); //preserve capacity - don't use clear!
320  newPoly.append( multi[i][j] );
321  finalMulti.append( newPoly );
322  }
323  }
324  }
325  feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
326  }
327  if ( feat.hasGeometry() )
328  {
329  mContext.expressionContext().setFeature( feat );
330  mSubRenderer->renderFeature( feat, mContext );
331  }
332  }
333 
334  // when no features are visible, we still have to draw the exterior rectangle
335  // warning: when sub renderers have more than one possible symbols,
336  // there is no way to choose a correct one, because there is no attribute here
337  // in that case, nothing will be rendered
338  if ( mFeaturesCategories.isEmpty() )
339  {
340  // empty feature with default attributes
341  QgsFeature feat( mFields );
342  feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
343  mSubRenderer->renderFeature( feat, mContext );
344  }
345 
346  // draw feature decorations
347  Q_FOREACH ( FeatureDecoration deco, mFeatureDecorations )
348  {
349  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
350  }
351 
352  mSubRenderer->stopRender( mContext );
353 }
354 
356 {
357  if ( !mSubRenderer )
358  {
359  return QStringLiteral( "INVERTED: NULL" );
360  }
361  return "INVERTED [" + mSubRenderer->dump() + ']';
362 }
363 
365 {
366  QgsInvertedPolygonRenderer* newRenderer;
367  if ( mSubRenderer.isNull() )
368  {
369  newRenderer = new QgsInvertedPolygonRenderer( nullptr );
370  }
371  else
372  {
373  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer.data()->clone() );
374  }
376  copyRendererData( newRenderer );
377  return newRenderer;
378 }
379 
381 {
383  //look for an embedded renderer <renderer-v2>
384  QDomElement embeddedRendererElem = element.firstChildElement( QStringLiteral( "renderer-v2" ) );
385  if ( !embeddedRendererElem.isNull() )
386  {
387  QgsFeatureRenderer* renderer = QgsFeatureRenderer::load( embeddedRendererElem );
388  r->setEmbeddedRenderer( renderer );
389  }
390  r->setPreprocessingEnabled( element.attribute( QStringLiteral( "preprocessing" ), QStringLiteral( "0" ) ).toInt() == 1 );
391  return r;
392 }
393 
394 QDomElement QgsInvertedPolygonRenderer::save( QDomDocument& doc )
395 {
396  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
397  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "invertedPolygonRenderer" ) );
398  rendererElem.setAttribute( QStringLiteral( "preprocessing" ), preprocessingEnabled() ? "1" : "0" );
399  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
400 
401  if ( mSubRenderer )
402  {
403  QDomElement embeddedRendererElem = mSubRenderer->save( doc );
404  rendererElem.appendChild( embeddedRendererElem );
405  }
406 
408  mPaintEffect->saveProperties( doc, rendererElem );
409 
410  if ( !mOrderBy.isEmpty() )
411  {
412  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
413  mOrderBy.save( orderBy );
414  rendererElem.appendChild( orderBy );
415  }
416  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
417 
418  return rendererElem;
419 }
420 
422 {
423  if ( !mSubRenderer )
424  {
425  return nullptr;
426  }
427  return mSubRenderer->symbolForFeature( feature, context );
428 }
429 
431 {
432  if ( !mSubRenderer )
433  return nullptr;
434  return mSubRenderer->originalSymbolForFeature( feat, context );
435 }
436 
438 {
439  if ( !mSubRenderer )
440  {
441  return QgsSymbolList();
442  }
443  return mSubRenderer->symbolsForFeature( feature, context );
444 }
445 
447 {
448  if ( !mSubRenderer )
449  return QgsSymbolList();
450  return mSubRenderer->originalSymbolsForFeature( feat, context );
451 }
452 
454 {
455  if ( !mSubRenderer )
456  {
457  return QgsSymbolList();
458  }
459  return mSubRenderer->symbols( context );
460 }
461 
462 QgsFeatureRenderer::Capabilities QgsInvertedPolygonRenderer::capabilities()
463 {
464  if ( !mSubRenderer )
465  {
466  return 0;
467  }
468  return mSubRenderer->capabilities();
469 }
470 
472 {
473  if ( !mSubRenderer )
474  {
475  return QSet<QString>();
476  }
477  return mSubRenderer->usedAttributes();
478 }
479 
481 {
482  if ( !mSubRenderer )
483  {
484  return QgsLegendSymbologyList();
485  }
486  return mSubRenderer->legendSymbologyItems( iconSize );
487 }
488 
489 QgsLegendSymbolList QgsInvertedPolygonRenderer::legendSymbolItems( double scaleDenominator, const QString& rule )
490 {
491  if ( !mSubRenderer )
492  {
493  return QgsLegendSymbolList();
494  }
495  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
496 }
497 
499 {
500  if ( !mSubRenderer )
501  {
502  return false;
503  }
504  return mSubRenderer->willRenderFeature( feat, context );
505 }
506 
508 {
509  if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
510  {
511  return dynamic_cast<QgsInvertedPolygonRenderer*>( renderer->clone() );
512  }
513 
514  if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
515  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
516  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
517  renderer->type() == QLatin1String( "RuleRenderer" ) )
518  {
519  return new QgsInvertedPolygonRenderer( renderer->clone() );
520  }
521  return nullptr;
522 }
523 
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:190
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
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:448
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:46
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:36
QgsPoint toMapCoordinates(int x, int y) const
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:78
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:49
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:434
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:135
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:214
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:33
QString type() const
Definition: qgsrenderer.h:92
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:61
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:52
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()
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:162
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:40
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:44
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:366
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...