QGIS API Documentation  2.11.0-Master
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 "qgssymbolv2.h"
19 #include "qgssymbollayerv2utils.h"
20 
21 #include "qgslogger.h"
22 #include "qgsfeature.h"
23 #include "qgsvectorlayer.h"
24 #include "qgssymbollayerv2.h"
25 #include "qgsogcutils.h"
26 #include "qgspainteffect.h"
27 
28 #include <QDomDocument>
29 #include <QDomElement>
30 
32  : QgsFeatureRendererV2( "invertedPolygonRenderer" )
33  , mPreprocessingEnabled( false )
34 {
35  if ( subRenderer )
36  {
37  setEmbeddedRenderer( subRenderer );
38  }
39  else
40  {
42  }
43 }
44 
46 {
47 }
48 
50 {
51  if ( subRenderer )
52  {
53  mSubRenderer.reset( const_cast<QgsFeatureRendererV2*>( subRenderer )->clone() );
54  }
55  else
56  {
57  mSubRenderer.reset( 0 );
58  }
59 }
60 
62 {
63  return mSubRenderer.data();
64 }
65 
67 {
68  if ( !mSubRenderer )
69  {
70  return;
71  }
72 
73  // first call start render on the sub renderer
74  mSubRenderer->startRender( context, fields );
75 
76  mFeaturesCategories.clear();
77  mSymbolCategories.clear();
78  mFeatureDecorations.clear();
79  mFields = fields;
80 
81  // We compute coordinates of the extent which will serve as exterior ring
82  // for the final polygon
83  // It must be computed in the destination CRS if reprojection is enabled.
84  const QgsMapToPixel& mtp( context.mapToPixel() );
85 
86  if ( !context.painter() )
87  {
88  return;
89  }
90 
91  // convert viewport to dest CRS
92  QRect e( context.painter()->viewport() );
93  // add some space to hide borders and tend to infinity
94  e.adjust( -e.width()*5, -e.height()*5, e.width()*5, e.height()*5 );
95  QgsPolyline exteriorRing;
96  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
97  exteriorRing << mtp.toMapCoordinates( e.topRight() );
98  exteriorRing << mtp.toMapCoordinates( e.bottomRight() );
99  exteriorRing << mtp.toMapCoordinates( e.bottomLeft() );
100  exteriorRing << mtp.toMapCoordinates( e.topLeft() );
101 
102  // copy the rendering context
103  mContext = context;
104 
105  // If reprojection is enabled, we must reproject during renderFeature
106  // and act as if there is no reprojection
107  // If we don't do that, there is no need to have a simple rectangular extent
108  // that covers the whole screen
109  // (a rectangle in the destCRS cannot be expressed as valid coordinates in the sourceCRS in general)
110  if ( context.coordinateTransform() )
111  {
112  // disable projection
113  mContext.setCoordinateTransform( 0 );
114  // recompute extent so that polygon clipping is correct
115  QRect v( context.painter()->viewport() );
116  mContext.setExtent( QgsRectangle( mtp.toMapCoordinates( v.topLeft() ), mtp.toMapCoordinates( v.bottomRight() ) ) );
117  // do we have to recompute the MapToPixel ?
118  }
119 
120  mExtentPolygon.clear();
121  mExtentPolygon.append( exteriorRing );
122 
123  return;
124 }
125 
126 bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
127 {
128  if ( !context.painter() )
129  {
130  return false;
131  }
132 
133  // store this feature as a feature to render with decoration if needed
134  if ( selected || drawVertexMarker )
135  {
136  mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
137  }
138 
139  // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
140  // This way, users can have multiple inverted polygon fills for a layer,
141  // for instance, with rule based renderer and different symbols
142  // that have transparency.
143  //
144  // In order to assign a unique category to a set of symbols
145  // during each rendering session (between startRender() and stopRender()),
146  // we build an unique id as a QByteArray that is the concatenation
147  // of each symbol's memory address.
148  // The only assumption made here is that symbol(s)ForFeature will
149  // always return the same address for the same symbol(s) shared amongst
150  // different features.
151  // This QByteArray can then be used as a key for a QMap where the list of
152  // features for this category is stored
153  QByteArray catId;
155  {
156  QgsSymbolV2List syms( mSubRenderer->symbolsForFeature( feature ) );
157  foreach ( QgsSymbolV2* sym, syms )
158  {
159  // append the memory address
160  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
161  }
162  }
163  else
164  {
165  QgsSymbolV2* sym = mSubRenderer->symbolForFeature( feature );
166  if ( sym )
167  {
168  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
169  }
170  }
171 
172  if ( catId.isEmpty() )
173  {
174  return false;
175  }
176 
177  if ( ! mSymbolCategories.contains( catId ) )
178  {
179  CombinedFeature cFeat;
180  // store the first feature
181  cFeat.feature = feature;
182  mSymbolCategories.insert( catId, mSymbolCategories.count() );
183  mFeaturesCategories.append( cFeat );
184  }
185 
186  // update the geometry
187  CombinedFeature& cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
188  if ( !feature.constGeometry() )
189  {
190  return false;
191  }
192  QScopedPointer<QgsGeometry> geom( new QgsGeometry( *feature.constGeometry() ) );
193 
194  const QgsCoordinateTransform* xform = context.coordinateTransform();
195  if ( xform )
196  {
197  geom->transform( *xform );
198  }
199 
200  if ( mPreprocessingEnabled )
201  {
202  // fix the polygon if it is not valid
203  if ( ! geom->isGeosValid() )
204  {
205  geom.reset( geom->buffer( 0, 0 ) );
206  }
207  }
208 
209  if ( !geom )
210  return false; // do not let invalid geometries sneak in!
211 
212  // add the geometry to the list of geometries for this feature
213  cFeat.geometries.append( geom.take() );
214 
215  return true;
216 }
217 
219 {
220  if ( !mSubRenderer )
221  {
222  return;
223  }
224  if ( !context.painter() )
225  {
226  return;
227  }
228 
229  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
230  {
231  QgsFeature feat = cit->feature; // just a copy, so that we do not accumulate geometries again
232  if ( mPreprocessingEnabled )
233  {
234  // compute the unary union on the polygons
235  QScopedPointer<QgsGeometry> unioned( QgsGeometry::unaryUnion( cit->geometries ) );
236  // compute the difference with the extent
237  QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
238  QgsGeometry *final = rect->difference( const_cast<QgsGeometry*>( unioned.data() ) );
239  feat.setGeometry( final );
240  }
241  else
242  {
243  // No preprocessing involved.
244  // We build here a "reversed" geometry of all the polygons
245  //
246  // The final geometry is a multipolygon F, with :
247  // * the first polygon of F having the current extent as its exterior ring
248  // * each polygon's exterior ring is added as interior ring of the first polygon of F
249  // * each polygon's interior ring is added as new polygons in F
250  //
251  // No validity check is done, on purpose, it will be very slow and painting
252  // operations do not need geometries to be valid
253  QgsMultiPolygon finalMulti;
254  finalMulti.append( mExtentPolygon );
255  foreach ( QgsGeometry* geom, cit->geometries )
256  {
257  QgsMultiPolygon multi;
258  if (( geom->wkbType() == QGis::WKBPolygon ) ||
259  ( geom->wkbType() == QGis::WKBPolygon25D ) )
260  {
261  multi.append( geom->asPolygon() );
262  }
263  else if (( geom->wkbType() == QGis::WKBMultiPolygon ) ||
264  ( geom->wkbType() == QGis::WKBMultiPolygon25D ) )
265  {
266  multi = geom->asMultiPolygon();
267  }
268 
269  for ( int i = 0; i < multi.size(); i++ )
270  {
271  const QgsPolyline& exterior = multi[i][0];
272  // add the exterior ring as interior ring to the first polygon
273  // make sure it satisfies at least very basic requirements of GEOS
274  // (otherwise the creation of GEOS geometry will fail)
275  if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
276  continue;
277  finalMulti[0].append( exterior );
278 
279  // add interior rings as new polygons
280  for ( int j = 1; j < multi[i].size(); j++ )
281  {
282  QgsPolygon new_poly;
283  new_poly.append( multi[i][j] );
284  finalMulti.append( new_poly );
285  }
286  }
287  }
288  feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
289  }
290  if ( feat.constGeometry() )
291  mSubRenderer->renderFeature( feat, mContext );
292  }
293  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
294  {
295  foreach ( QgsGeometry* g, cit->geometries )
296  {
297  delete g;
298  }
299  }
300 
301  // when no features are visible, we still have to draw the exterior rectangle
302  // warning: when sub renderers have more than one possible symbols,
303  // there is no way to choose a correct one, because there is no attribute here
304  // in that case, nothing will be rendered
305  if ( mFeaturesCategories.isEmpty() )
306  {
307  // empty feature with default attributes
308  QgsFeature feat( mFields );
309  feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
310  mSubRenderer->renderFeature( feat, mContext );
311  }
312 
313  // draw feature decorations
314  foreach ( FeatureDecoration deco, mFeatureDecorations )
315  {
316  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
317  }
318 
319  mSubRenderer->stopRender( mContext );
320 }
321 
323 {
324  if ( !mSubRenderer )
325  {
326  return "INVERTED: NULL";
327  }
328  return "INVERTED [" + mSubRenderer->dump() + "]";
329 }
330 
332 {
333  QgsInvertedPolygonRenderer* newRenderer;
334  if ( mSubRenderer.isNull() )
335  {
336  newRenderer = new QgsInvertedPolygonRenderer( 0 );
337  }
338  else
339  {
340  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
341  }
343  copyPaintEffect( newRenderer );
344  return newRenderer;
345 }
346 
348 {
350  //look for an embedded renderer <renderer-v2>
351  QDomElement embeddedRendererElem = element.firstChildElement( "renderer-v2" );
352  if ( !embeddedRendererElem.isNull() )
353  {
354  r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
355  }
356  r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
357  return r;
358 }
359 
361 {
362  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
363  rendererElem.setAttribute( "type", "invertedPolygonRenderer" );
364  rendererElem.setAttribute( "preprocessing", preprocessingEnabled() ? "1" : "0" );
365  rendererElem.setAttribute( "forceraster", ( mForceRaster ? "1" : "0" ) );
366 
367  if ( mSubRenderer )
368  {
369  QDomElement embeddedRendererElem = mSubRenderer->save( doc );
370  rendererElem.appendChild( embeddedRendererElem );
371  }
372 
373  if ( mPaintEffect )
374  mPaintEffect->saveProperties( doc, rendererElem );
375 
376  return rendererElem;
377 }
378 
380 {
381  if ( !mSubRenderer )
382  {
383  return 0;
384  }
385  return mSubRenderer->symbolForFeature( feature );
386 }
387 
389 {
390  if ( !mSubRenderer )
391  return 0;
392  return mSubRenderer->originalSymbolForFeature( feat );
393 }
394 
396 {
397  if ( !mSubRenderer )
398  {
399  return QgsSymbolV2List();
400  }
401  return mSubRenderer->symbolsForFeature( feature );
402 }
403 
405 {
406  if ( !mSubRenderer )
407  return QgsSymbolV2List();
408  return mSubRenderer->originalSymbolsForFeature( feat );
409 }
410 
412 {
413  if ( !mSubRenderer )
414  {
415  return QgsSymbolV2List();
416  }
417  return mSubRenderer->symbols();
418 }
419 
421 {
422  if ( !mSubRenderer )
423  {
424  return 0;
425  }
426  return mSubRenderer->capabilities();
427 }
428 
430 {
431  if ( !mSubRenderer )
432  {
433  return QList<QString>();
434  }
435  return mSubRenderer->usedAttributes();
436 }
437 
439 {
440  if ( !mSubRenderer )
441  {
442  return QgsLegendSymbologyList();
443  }
444  return mSubRenderer->legendSymbologyItems( iconSize );
445 }
446 
448 {
449  if ( !mSubRenderer )
450  {
451  return QgsLegendSymbolList();
452  }
453  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
454 }
455 
457 {
458  if ( !mSubRenderer )
459  {
460  return false;
461  }
462  return mSubRenderer->willRenderFeature( feat );
463 }
464 
466 {
467  if ( renderer->type() == "invertedPolygonRenderer" )
468  {
469  return dynamic_cast<QgsInvertedPolygonRenderer*>( renderer->clone() );
470  }
471 
472  if ( renderer->type() == "singleSymbol" ||
473  renderer->type() == "categorizedSymbol" ||
474  renderer->type() == "graduatedSymbol" ||
475  renderer->type() == "RuleRenderer" )
476  {
477  return new QgsInvertedPolygonRenderer( renderer->clone() );
478  }
479  return 0;
480 }
481 
void clear()
#define RENDERER_TAG_NAME
Definition: qgsrendererv2.h:48
A rectangle specified with double values.
Definition: qgsrectangle.h:35
virtual QgsSymbolV2 * symbolForFeature(QgsFeature &feature) override
Proxy that will call this method on the embedded renderer.
bool contains(const Key &key) const
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
Renders a given feature.
QList< QgsSymbolV2 * > QgsSymbolV2List
Definition: qgsrendererv2.h:39
QDomNode appendChild(const QDomNode &newChild)
void append(const T &value)
iterator begin()
QString attribute(const QString &name, const QString &defValue) const
virtual QgsLegendSymbolList legendSymbolItems(double scaleDenominator=-1, QString rule="") override
Proxy that will call this method on the embedded renderer.
static QgsInvertedPolygonRenderer * convertFromRenderer(const QgsFeatureRendererV2 *renderer)
Creates a QgsInvertedPolygonRenderer by a conversion from an existing renderer.
QgsPolygon asPolygon() const
Return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list...
bool isEmpty() const
Container of fields for a vector layer.
Definition: qgsfield.h:173
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:75
QgsInvertedPolygonRenderer(const QgsFeatureRendererV2 *embeddedRenderer=0)
Constructor.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:162
virtual QString dump() const override
const QgsCoordinateTransform * coordinateTransform() const
void clear()
void setExtent(const QgsRectangle &extent)
QgsPaintEffect * mPaintEffect
QgsMultiPolygon asMultiPolygon() const
Return contents of the geometry as a multi polygon if wkbType is WKBMultiPolygon, otherwise an empty ...
void reset(T *other)
void setCoordinateTransform(const QgsCoordinateTransform *t)
Sets coordinate transformation.
QString type() const
Definition: qgsrendererv2.h:82
void setEmbeddedRenderer(const QgsFeatureRendererV2 *subRenderer)
Sets 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
void clear()
virtual QgsFeatureRendererV2 * clone() const =0
void setGeometry(const QgsGeometry &geom)
Set this feature's geometry from another QgsGeometry object.
Definition: qgsfeature.cpp:104
void append(const T &value)
virtual QgsFeatureRendererV2 * clone() const override
Used to clone this feature renderer.
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
void setAttribute(const QString &name, const QString &value)
int toInt(bool *ok, int base) const
virtual QgsSymbolV2List symbols() override
Proxy that will call this method on the embedded renderer.
QGis::WkbType wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
static QgsFeatureRendererV2 * defaultRenderer(QGis::GeometryType geomType)
return a new renderer - used by default in vector layers
virtual QgsSymbolV2List originalSymbolsForFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.
QList< QPair< QString, QPixmap > > QgsLegendSymbologyList
virtual QList< QString > usedAttributes() override
Proxy that will call this method on the embedded renderer.
QByteArray & append(char ch)
T * data() const
virtual QDomElement save(QDomDocument &doc) override
Creates an XML representation of the renderer.
virtual QgsLegendSymbologyList legendSymbologyItems(QSize iconSize) override
Proxy that will call this method on the embedded renderer.
bool isNull() const
QRect viewport() const
bool isNull() const
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
Contains information about the context of a rendering operation.
const QgsFeatureRendererV2 * embeddedRenderer() const
QPainter * painter()
void copyPaintEffect(QgsFeatureRendererV2 *destRenderer) const
Copies paint effect of this renderer to another renderer.
bool isEmpty() const
static QgsFeatureRendererV2 * load(QDomElement &symbologyElem)
create a renderer from XML element
QDomElement firstChildElement(const QString &tagName) const
int count(const T &value) const
void adjust(int dx1, int dy1, int dx2, int dy2)
const QgsGeometry * constGeometry() const
Gets a const pointer to the geometry object associated with this feature.
Definition: qgsfeature.cpp:68
Class for doing transforms between two map coordinate systems.
virtual int capabilities() override
Proxy that will call this method on the embedded renderer.
const QgsMapToPixel & mapToPixel() const
static QgsGeometry * unaryUnion(const QList< QgsGeometry * > &geometryList)
Compute the unary union on a list of geometries.
iterator insert(const Key &key, const T &value)
static QgsGeometry * fromMultiPolygon(const QgsMultiPolygon &multipoly)
Creates a new geometry from a QgsMultiPolygon.
static QgsGeometry * fromPolygon(const QgsPolygon &polygon)
Creates a new geometry from a QgsPolygon.
virtual void stopRender(QgsRenderContext &context) override
The actual rendering will take place here.
static QgsFeatureRendererV2 * create(QDomElement &element)
Creates a renderer out of an XML, for loading.
QDomElement createElement(const QString &tagName)
virtual QgsSymbolV2 * originalSymbolForFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.
int size() const
QList< QPair< QString, QgsSymbolV2 * > > QgsLegendSymbolList
Definition: qgsrendererv2.h:43
virtual QgsSymbolV2List symbolsForFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.
iterator end()
int count(const Key &key) const
virtual bool willRenderFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.