QGIS API Documentation  2.9.0-Master
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
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 
124 bool QgsInvertedPolygonRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
125 {
126  if ( !context.painter() )
127  {
128  return false;
129  }
130 
131  // store this feature as a feature to render with decoration if needed
132  if ( selected || drawVertexMarker )
133  {
134  mFeatureDecorations.append( FeatureDecoration( feature, selected, drawVertexMarker, layer ) );
135  }
136 
137  // Features are grouped by category of symbols (returned by symbol(s)ForFeature)
138  // This way, users can have multiple inverted polygon fills for a layer,
139  // for instance, with rule based renderer and different symbols
140  // that have transparency.
141  //
142  // In order to assign a unique category to a set of symbols
143  // during each rendering session (between startRender() and stopRender()),
144  // we build an unique id as a QByteArray that is the concatenation
145  // of each symbol's memory address.
146  // The only assumption made here is that symbol(s)ForFeature will
147  // always return the same address for the same symbol(s) shared amongst
148  // different features.
149  // This QByteArray can then be used as a key for a QMap where the list of
150  // features for this category is stored
151  QByteArray catId;
153  {
154  QgsSymbolV2List syms( mSubRenderer->symbolsForFeature( feature ) );
155  foreach ( QgsSymbolV2* sym, syms )
156  {
157  // append the memory address
158  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
159  }
160  }
161  else
162  {
163  QgsSymbolV2* sym = mSubRenderer->symbolForFeature( feature );
164  if ( sym )
165  {
166  catId.append( reinterpret_cast<const char*>( &sym ), sizeof( sym ) );
167  }
168  }
169 
170  if ( catId.isEmpty() )
171  {
172  return false;
173  }
174 
175  if ( ! mSymbolCategories.contains( catId ) )
176  {
177  CombinedFeature cFeat;
178  // store the first feature
179  cFeat.feature = feature;
180  mSymbolCategories.insert( catId, mSymbolCategories.count() );
181  mFeaturesCategories.append( cFeat );
182  }
183 
184  // update the geometry
185  CombinedFeature& cFeat = mFeaturesCategories[ mSymbolCategories[catId] ];
186  if ( !feature.geometry() )
187  {
188  return false;
189  }
190  QScopedPointer<QgsGeometry> geom( new QgsGeometry( *feature.geometry() ) );
191 
192  const QgsCoordinateTransform* xform = context.coordinateTransform();
193  if ( xform )
194  {
195  geom->transform( *xform );
196  }
197 
198  if ( mPreprocessingEnabled )
199  {
200  // fix the polygon if it is not valid
201  if ( ! geom->isGeosValid() )
202  {
203  geom.reset( geom->buffer( 0, 0 ) );
204  }
205  }
206 
207  if ( !geom )
208  return false; // do not let invalid geometries sneak in!
209 
210  // add the geometry to the list of geometries for this feature
211  cFeat.geometries.append( geom.take() );
212 
213  return true;
214 }
215 
217 {
218  if ( !mSubRenderer )
219  {
220  return;
221  }
222  if ( !context.painter() )
223  {
224  return;
225  }
226 
227  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
228  {
229  QgsFeature feat = cit->feature; // just a copy, so that we do not accumulate geometries again
230  if ( mPreprocessingEnabled )
231  {
232  // compute the unary union on the polygons
233  QScopedPointer<QgsGeometry> unioned( QgsGeometry::unaryUnion( cit->geometries ) );
234  // compute the difference with the extent
235  QScopedPointer<QgsGeometry> rect( QgsGeometry::fromPolygon( mExtentPolygon ) );
236  QgsGeometry *final = rect->difference( const_cast<QgsGeometry*>( unioned.data() ) );
237  feat.setGeometry( final );
238  }
239  else
240  {
241  // No preprocessing involved.
242  // We build here a "reversed" geometry of all the polygons
243  //
244  // The final geometry is a multipolygon F, with :
245  // * the first polygon of F having the current extent as its exterior ring
246  // * each polygon's exterior ring is added as interior ring of the first polygon of F
247  // * each polygon's interior ring is added as new polygons in F
248  //
249  // No validity check is done, on purpose, it will be very slow and painting
250  // operations do not need geometries to be valid
251  QgsMultiPolygon finalMulti;
252  finalMulti.append( mExtentPolygon );
253  foreach ( QgsGeometry* geom, cit->geometries )
254  {
255  QgsMultiPolygon multi;
256  if (( geom->wkbType() == QGis::WKBPolygon ) ||
257  ( geom->wkbType() == QGis::WKBPolygon25D ) )
258  {
259  multi.append( geom->asPolygon() );
260  }
261  else if (( geom->wkbType() == QGis::WKBMultiPolygon ) ||
262  ( geom->wkbType() == QGis::WKBMultiPolygon25D ) )
263  {
264  multi = geom->asMultiPolygon();
265  }
266 
267  for ( int i = 0; i < multi.size(); i++ )
268  {
269  const QgsPolyline& exterior = multi[i][0];
270  // add the exterior ring as interior ring to the first polygon
271  // make sure it satisfies at least very basic requirements of GEOS
272  // (otherwise the creation of GEOS geometry will fail)
273  if ( exterior.count() < 4 || exterior[0] != exterior[exterior.count() - 1] )
274  continue;
275  finalMulti[0].append( exterior );
276 
277  // add interior rings as new polygons
278  for ( int j = 1; j < multi[i].size(); j++ )
279  {
280  QgsPolygon new_poly;
281  new_poly.append( multi[i][j] );
282  finalMulti.append( new_poly );
283  }
284  }
285  }
286  feat.setGeometry( QgsGeometry::fromMultiPolygon( finalMulti ) );
287  }
288  if ( feat.geometry() )
289  mSubRenderer->renderFeature( feat, mContext );
290  }
291  for ( FeatureCategoryVector::iterator cit = mFeaturesCategories.begin(); cit != mFeaturesCategories.end(); ++cit )
292  {
293  foreach ( QgsGeometry* g, cit->geometries )
294  {
295  delete g;
296  }
297  }
298 
299  // when no features are visible, we still have to draw the exterior rectangle
300  // warning: when sub renderers have more than one possible symbols,
301  // there is no way to choose a correct one, because there is no attribute here
302  // in that case, nothing will be rendered
303  if ( mFeaturesCategories.isEmpty() )
304  {
305  // empty feature with default attributes
306  QgsFeature feat( mFields );
307  feat.setGeometry( QgsGeometry::fromPolygon( mExtentPolygon ) );
308  mSubRenderer->renderFeature( feat, mContext );
309  }
310 
311  // draw feature decorations
312  foreach ( FeatureDecoration deco, mFeatureDecorations )
313  {
314  mSubRenderer->renderFeature( deco.feature, mContext, deco.layer, deco.selected, deco.drawMarkers );
315  }
316 
317  mSubRenderer->stopRender( mContext );
318 }
319 
321 {
322  if ( !mSubRenderer )
323  {
324  return "INVERTED: NULL";
325  }
326  return "INVERTED [" + mSubRenderer->dump() + "]";
327 }
328 
330 {
331  QgsInvertedPolygonRenderer* newRenderer;
332  if ( mSubRenderer.isNull() )
333  {
334  newRenderer = new QgsInvertedPolygonRenderer( 0 );
335  }
336  else
337  {
338  newRenderer = new QgsInvertedPolygonRenderer( mSubRenderer->clone() );
339  }
341  copyPaintEffect( newRenderer );
342  return newRenderer;
343 }
344 
346 {
348  //look for an embedded renderer <renderer-v2>
349  QDomElement embeddedRendererElem = element.firstChildElement( "renderer-v2" );
350  if ( !embeddedRendererElem.isNull() )
351  {
352  r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
353  }
354  r->setPreprocessingEnabled( element.attribute( "preprocessing", "0" ).toInt() == 1 );
355  return r;
356 }
357 
358 QDomElement QgsInvertedPolygonRenderer::save( QDomDocument& doc )
359 {
360  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
361  rendererElem.setAttribute( "type", "invertedPolygonRenderer" );
362  rendererElem.setAttribute( "preprocessing", preprocessingEnabled() ? "1" : "0" );
363 
364  if ( mSubRenderer )
365  {
366  QDomElement embeddedRendererElem = mSubRenderer->save( doc );
367  rendererElem.appendChild( embeddedRendererElem );
368  }
369 
370  if ( mPaintEffect )
371  mPaintEffect->saveProperties( doc, rendererElem );
372 
373  return rendererElem;
374 }
375 
377 {
378  if ( !mSubRenderer )
379  {
380  return 0;
381  }
382  return mSubRenderer->symbolForFeature( feature );
383 }
384 
386 {
387  if ( !mSubRenderer )
388  return 0;
389  return mSubRenderer->originalSymbolForFeature( feat );
390 }
391 
393 {
394  if ( !mSubRenderer )
395  {
396  return QgsSymbolV2List();
397  }
398  return mSubRenderer->symbolsForFeature( feature );
399 }
400 
402 {
403  if ( !mSubRenderer )
404  return QgsSymbolV2List();
405  return mSubRenderer->originalSymbolsForFeature( feat );
406 }
407 
409 {
410  if ( !mSubRenderer )
411  {
412  return QgsSymbolV2List();
413  }
414  return mSubRenderer->symbols();
415 }
416 
418 {
419  if ( !mSubRenderer )
420  {
421  return 0;
422  }
423  return mSubRenderer->capabilities();
424 }
425 
427 {
428  if ( !mSubRenderer )
429  {
430  return QList<QString>();
431  }
432  return mSubRenderer->usedAttributes();
433 }
434 
436 {
437  if ( !mSubRenderer )
438  {
439  return QgsLegendSymbologyList();
440  }
441  return mSubRenderer->legendSymbologyItems( iconSize );
442 }
443 
445 {
446  if ( !mSubRenderer )
447  {
448  return QgsLegendSymbolList();
449  }
450  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
451 }
452 
454 {
455  if ( !mSubRenderer )
456  {
457  return false;
458  }
459  return mSubRenderer->willRenderFeature( feat );
460 }
461 
463 {
464  if ( renderer->type() == "invertedPolygonRenderer" )
465  {
466  return dynamic_cast<QgsInvertedPolygonRenderer*>( renderer->clone() );
467  }
468 
469  if ( renderer->type() == "singleSymbol" ||
470  renderer->type() == "categorizedSymbol" ||
471  renderer->type() == "graduatedSymbol" ||
472  renderer->type() == "RuleRenderer" )
473  {
474  return new QgsInvertedPolygonRenderer( renderer->clone() );
475  }
476  return 0;
477 }
478 
#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.
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
virtual QgsLegendSymbolList legendSymbolItems(double scaleDenominator=-1, QString rule="") override
Proxy that will call this method on the embedded renderer.
QVector< QgsPoint > QgsPolyline
polyline is represented as a vector of points
Definition: qgsgeometry.h:33
static QgsInvertedPolygonRenderer * convertFromRenderer(const QgsFeatureRendererV2 *renderer)
creates a QgsInvertedPolygonRenderer by a conversion from an existing renderer.
QgsGeometry * geometry() const
Get the geometry object associated with this feature.
Definition: qgsfeature.cpp:112
QgsPolygon asPolygon() const
return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list ...
Container of fields for a vector layer.
Definition: qgsfield.h:172
QgsInvertedPolygonRenderer(const QgsFeatureRendererV2 *embeddedRenderer=0)
Constructor.
QgsPoint transform(const QgsPoint &p, TransformDirection direction=ForwardTransform) const
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:113
virtual QString dump() const override
const QgsCoordinateTransform * coordinateTransform() const
QgsGeometry * difference(QgsGeometry *geometry)
Returns a geometry representing the points making up this geometry that do not make up other...
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 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
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:34
virtual QgsFeatureRendererV2 * clone() const =0
void setGeometry(const QgsGeometry &geom)
Set this feature's geometry from another QgsGeometry object (deep copy)
Definition: qgsfeature.cpp:134
virtual QgsFeatureRendererV2 * clone() const override
Used to clone this feature renderer.
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
virtual QgsSymbolV2List symbols() override
Proxy that will call this method on the embedded renderer.
QVector< QgsPolygon > QgsMultiPolygon
a collection of QgsPolygons that share a common collection of attributes
Definition: qgsgeometry.h:48
QGis::WkbType wkbType() const
Returns type of wkb (point / linestring / polygon etc.)
QVector< QgsPolyline > QgsPolygon
polygon: first item of the list is outer ring, inner rings (if any) start from second item ...
Definition: qgsgeometry.h:39
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.
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.
QgsPoint toMapCoordinates(int x, int y) 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.
static QgsFeatureRendererV2 * load(QDomElement &symbologyElem)
create a renderer from XML element
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.
static QgsGeometry * fromMultiPolygon(const QgsMultiPolygon &multipoly)
construct geometry from a multipolygon
static QgsGeometry * fromPolygon(const QgsPolygon &polygon)
construct geometry from a polygon
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.
virtual QgsSymbolV2 * originalSymbolForFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.
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.
virtual bool willRenderFeature(QgsFeature &feat) override
Proxy that will call this method on the embedded renderer.