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