QGIS API Documentation  2.99.0-Master (f1c3692)
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  if ( !mSubRenderer )
97  {
98  return;
99  }
100 
101  // first call start render on the sub renderer
102  mSubRenderer->startRender( context, fields );
103 
104  mFeaturesCategories.clear();
105  mSymbolCategories.clear();
106  mFeatureDecorations.clear();
107  mFields = fields;
108 
109  // We compute coordinates of the extent which will serve as exterior ring
110  // for the final polygon
111  // It must be computed in the destination CRS if reprojection is enabled.
112  const QgsMapToPixel &mtp( context.mapToPixel() );
113 
114  if ( !context.painter() )
115  {
116  return;
117  }
118 
119  // convert viewport to dest CRS
120  QRect e( context.painter()->viewport() );
121  // add some space to hide borders and tend to infinity
122  e.adjust( -e.width() * 5, -e.height() * 5, e.width() * 5, e.height() * 5 );
123  QgsPolylineXY exteriorRing;
124  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
125  exteriorRing << mtp.toMapCoordinates( e.topRight() );
126  exteriorRing << mtp.toMapCoordinates( e.bottomRight() );
127  exteriorRing << mtp.toMapCoordinates( e.bottomLeft() );
128  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
129 
130  // copy the rendering context
131  mContext = context;
132 
133  // If reprojection is enabled, we must reproject during renderFeature
134  // and act as if there is no reprojection
135  // If we don't do that, there is no need to have a simple rectangular extent
136  // that covers the whole screen
137  // (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
138  if ( context.coordinateTransform().isValid() )
139  {
140  // disable projection
142  // recompute extent so that polygon clipping is correct
143  QRect v( context.painter()->viewport() );
144  mContext.setExtent( QgsRectangle( mtp.toMapCoordinates( v.topLeft() ), mtp.toMapCoordinates( v.bottomRight() ) ) );
145  // do we have to recompute the MapToPixel ?
146  }
147 
148  mExtentPolygon.clear();
149  mExtentPolygon.append( exteriorRing );
150 }
151 
152 bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker )
153 {
154  if ( !context.painter() )
155  {
156  return false;
157  }
158 
159  // store this feature as a feature to render with decoration if needed
160  if ( selected || drawVertexMarker )
161  {
162  mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
163  }
164 
165  // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
166  // This way, users can have multiple inverted polygon fills for a layer,
167  // for instance, with rule based renderer and different symbols
168  // that have transparency.
169  //
170  // In order to assign a unique category to a set of symbols
171  // during each rendering session (between startRender() and stopRender()),
172  // we build an unique id as a QByteArray that is the concatenation
173  // of each symbol's memory address.
174  // The only assumption made here is that symbol(s)ForFeature will
175  // always return the same address for the same symbol(s) shared amongst
176  // different features.
177  // This QByteArray can then be used as a key for a QMap where the list of
178  // features for this category is stored
179  QByteArray catId;
181  {
182  QgsSymbolList syms( mSubRenderer->symbolsForFeature( feature, context ) );
183  Q_FOREACH ( QgsSymbol *sym, syms )
184  {
185  // append the memory address
186  catId.append( reinterpret_cast<const char *>( &sym ), sizeof( sym ) );
187  }
188  }
189  else
190  {
191  QgsSymbol *sym = mSubRenderer->symbolForFeature( feature, context );
192  if ( sym )
193  {
194  catId.append( reinterpret_cast<const char *>( &sym ), sizeof( sym ) );
195  }
196  }
197 
198  if ( catId.isEmpty() )
199  {
200  return false;
201  }
202 
203  if ( ! mSymbolCategories.contains( catId ) )
204  {
205  CombinedFeature cFeat;
206  // store the first feature
207  cFeat.feature = feature;
208  mSymbolCategories.insert( catId, mSymbolCategories.count() );
209  mFeaturesCategories.append( cFeat );
210  }
211 
212  // update the geometry
213  CombinedFeature &cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
214  if ( !feature.hasGeometry() )
215  {
216  return false;
217  }
218  QgsGeometry geom = feature.geometry();
219 
221  if ( xform.isValid() )
222  {
223  geom.transform( xform );
224  }
225 
226  if ( mPreprocessingEnabled )
227  {
228  // fix the polygon if it is not valid
229  if ( ! geom.isGeosValid() )
230  {
231  geom = geom.buffer( 0, 0 );
232  }
233  }
234 
235  if ( geom.isNull() )
236  return false; // do not let invalid geometries sneak in!
237 
238  // add the geometry to the list of geometries for this feature
239  cFeat.geometries.append( geom );
240 
241  return true;
242 }
243 
245 {
246  if ( !mSubRenderer )
247  {
248  return;
249  }
250  if ( !context.painter() )
251  {
252  return;
253  }
254 
255  QgsMultiPolygonXY finalMulti; //avoid expensive allocation for list for every feature
256  QgsPolygonXY newPoly;
257 
258  Q_FOREACH ( const CombinedFeature &cit, mFeaturesCategories )
259  {
260  finalMulti.resize( 0 ); //preserve capacity - don't use clear!
261  QgsFeature feat = cit.feature; // just a copy, so that we do not accumulate geometries again
262  if ( mPreprocessingEnabled )
263  {
264  // compute the unary union on the polygons
265  QgsGeometry unioned( QgsGeometry::unaryUnion( cit.geometries ) );
266  // compute the difference with the extent
267  QgsGeometry rect = QgsGeometry::fromPolygonXY( mExtentPolygon );
268  QgsGeometry final = rect.difference( unioned );
269  feat.setGeometry( final );
270  }
271  else
272  {
273  // No preprocessing involved.
274  // We build here a "reversed" geometry of all the polygons
275  //
276  // The final geometry is a multipolygon F, with :
277  // * the first polygon of F having the current extent as its exterior ring
278  // * each polygon's exterior ring is added as interior ring of the first polygon of F
279  // * each polygon's interior ring is added as new polygons in F
280  //
281  // No validity check is done, on purpose, it will be very slow and painting
282  // operations do not need geometries to be valid
283 
284  finalMulti.append( mExtentPolygon );
285  Q_FOREACH ( const QgsGeometry &geom, cit.geometries )
286  {
287  QgsMultiPolygonXY multi;
289 
290  if ( ( type == QgsWkbTypes::Polygon ) || ( type == QgsWkbTypes::CurvePolygon ) )
291  {
292  multi.append( geom.asPolygon() );
293  }
294  else if ( ( type == QgsWkbTypes::MultiPolygon ) || ( type == QgsWkbTypes::MultiSurface ) )
295  {
296  multi = geom.asMultiPolygon();
297  }
298 
299  for ( int i = 0; i < multi.size(); i++ )
300  {
301  const QgsPolylineXY &exterior = multi[i][0];
302  // add the exterior ring as interior ring to the first polygon
303  // make sure it satisfies at least very basic requirements of GEOS
304  // (otherwise the creation of GEOS geometry will fail)
305  if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
306  continue;
307  finalMulti[0].append( exterior );
308 
309  // add interior rings as new polygons
310  for ( int j = 1; j < multi[i].size(); j++ )
311  {
312  newPoly.resize( 0 ); //preserve capacity - don't use clear!
313  newPoly.append( multi[i][j] );
314  finalMulti.append( newPoly );
315  }
316  }
317  }
318  feat.setGeometry( QgsGeometry::fromMultiPolygonXY( finalMulti ) );
319  }
320  if ( feat.hasGeometry() )
321  {
322  mContext.expressionContext().setFeature( feat );
323  mSubRenderer->renderFeature( feat, mContext );
324  }
325  }
326 
327  // when no features are visible, we still have to draw the exterior rectangle
328  // warning: when sub renderers have more than one possible symbols,
329  // there is no way to choose a correct one, because there is no attribute here
330  // in that case, nothing will be rendered
331  if ( mFeaturesCategories.isEmpty() )
332  {
333  // empty feature with default attributes
334  QgsFeature feat( mFields );
335  feat.setGeometry( QgsGeometry::fromPolygonXY( mExtentPolygon ) );
336  mSubRenderer->renderFeature( feat, mContext );
337  }
338 
339  // draw feature decorations
340  Q_FOREACH ( FeatureDecoration deco, mFeatureDecorations )
341  {
342  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
343  }
344 
345  mSubRenderer->stopRender( mContext );
346 }
347 
349 {
350  if ( !mSubRenderer )
351  {
352  return QStringLiteral( "INVERTED: NULL" );
353  }
354  return "INVERTED [" + mSubRenderer->dump() + ']';
355 }
356 
358 {
359  QgsInvertedPolygonRenderer *newRenderer = nullptr;
360  if ( !mSubRenderer )
361  {
362  newRenderer = new QgsInvertedPolygonRenderer( nullptr );
363  }
364  else
365  {
366  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
367  }
369  copyRendererData( newRenderer );
370  return newRenderer;
371 }
372 
374 {
376  //look for an embedded renderer <renderer-v2>
377  QDomElement embeddedRendererElem = element.firstChildElement( QStringLiteral( "renderer-v2" ) );
378  if ( !embeddedRendererElem.isNull() )
379  {
380  QgsFeatureRenderer *renderer = QgsFeatureRenderer::load( embeddedRendererElem, context );
381  r->setEmbeddedRenderer( renderer );
382  }
383  r->setPreprocessingEnabled( element.attribute( QStringLiteral( "preprocessing" ), QStringLiteral( "0" ) ).toInt() == 1 );
384  return r;
385 }
386 
387 QDomElement QgsInvertedPolygonRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
388 {
389  // clazy:skip
390 
391  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
392  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "invertedPolygonRenderer" ) );
393  rendererElem.setAttribute( QStringLiteral( "preprocessing" ), preprocessingEnabled() ? QStringLiteral( "1" ) : QStringLiteral( "0" ) );
394  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
395 
396  if ( mSubRenderer )
397  {
398  QDomElement embeddedRendererElem = mSubRenderer->save( doc, context );
399  rendererElem.appendChild( embeddedRendererElem );
400  }
401 
403  mPaintEffect->saveProperties( doc, rendererElem );
404 
405  if ( !mOrderBy.isEmpty() )
406  {
407  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
408  mOrderBy.save( orderBy );
409  rendererElem.appendChild( orderBy );
410  }
411  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
412 
413  return rendererElem;
414 }
415 
417 {
418  if ( !mSubRenderer )
419  {
420  return nullptr;
421  }
422  return mSubRenderer->symbolForFeature( feature, context );
423 }
424 
426 {
427  if ( !mSubRenderer )
428  return nullptr;
429  return mSubRenderer->originalSymbolForFeature( feat, context );
430 }
431 
433 {
434  if ( !mSubRenderer )
435  {
436  return QgsSymbolList();
437  }
438  return mSubRenderer->symbolsForFeature( feature, context );
439 }
440 
442 {
443  if ( !mSubRenderer )
444  return QgsSymbolList();
445  return mSubRenderer->originalSymbolsForFeature( feat, context );
446 }
447 
449 {
450  if ( !mSubRenderer )
451  {
452  return QgsSymbolList();
453  }
454  return mSubRenderer->symbols( context );
455 }
456 
457 QgsFeatureRenderer::Capabilities QgsInvertedPolygonRenderer::capabilities()
458 {
459  if ( !mSubRenderer )
460  {
461  return 0;
462  }
463  return mSubRenderer->capabilities();
464 }
465 
467 {
468  if ( !mSubRenderer )
469  {
470  return QSet<QString>();
471  }
472  return mSubRenderer->usedAttributes( context );
473 }
474 
476 {
477  if ( !mSubRenderer )
478  {
479  return QgsLegendSymbolList();
480  }
481  return mSubRenderer->legendSymbolItems();
482 }
483 
485 {
486  if ( !mSubRenderer )
487  {
488  return false;
489  }
490  return mSubRenderer->willRenderFeature( feat, context );
491 }
492 
494 {
495  if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
496  {
497  return dynamic_cast<QgsInvertedPolygonRenderer *>( renderer->clone() );
498  }
499 
500  if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
501  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
502  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
503  renderer->type() == QLatin1String( "RuleRenderer" ) )
504  {
505  return new QgsInvertedPolygonRenderer( renderer->clone() );
506  }
507  return nullptr;
508 }
509 
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:225
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:502
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:486
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
Needs to 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 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
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...