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