QGIS API Documentation  2.4.0-Chugiak
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups 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
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
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 );
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  {
384  return QgsSymbolV2List();
385  }
386  return mSubRenderer->symbolsForFeature( feature );
387 }
388 
390 {
391  if ( !mSubRenderer )
392  {
393  return QgsSymbolV2List();
394  }
395  return mSubRenderer->symbols();
396 }
397 
399 {
400  if ( !mSubRenderer )
401  {
402  return 0;
403  }
404  return mSubRenderer->capabilities();
405 }
406 
408 {
409  if ( !mSubRenderer )
410  {
411  return QList<QString>();
412  }
413  return mSubRenderer->usedAttributes();
414 }
415 
417 {
418  if ( !mSubRenderer )
419  {
420  return QgsLegendSymbologyList();
421  }
422  return mSubRenderer->legendSymbologyItems( iconSize );
423 }
424 
426 {
427  if ( !mSubRenderer )
428  {
429  return QgsLegendSymbolList();
430  }
431  return mSubRenderer->legendSymbolItems( scaleDenominator, rule );
432 }
433 
435 {
436  if ( !mSubRenderer )
437  {
438  return false;
439  }
440  return mSubRenderer->willRenderFeature( feat );
441 }
442 
#define RENDERER_TAG_NAME
Definition: qgsrendererv2.h:43
A rectangle specified with double values.
Definition: qgsrectangle.h:35
virtual QgsFeatureRendererV2 * clone()
Used to clone this feature renderer.
QList< QgsSymbolV2 * > QgsSymbolV2List
Definition: qgsrendererv2.h:37
QVector< QgsPoint > QgsPolyline
polyline is represented as a vector of points
Definition: qgsgeometry.h:38
QgsPolygon mExtentPolygon
the polygon used as exterior ring that covers the current extent
QgsGeometry * geometry() const
Get the geometry object associated with this feature.
Definition: qgsfeature.cpp:112
virtual QgsLegendSymbolList legendSymbolItems(double scaleDenominator=-1, QString rule="")
Proxy that will call this method on the embedded renderer.
QgsPolygon asPolygon() const
return contents of the geometry as a polygon if wkbType is WKBPolygon, otherwise an empty list ...
bool mPreprocessingEnabled
whether to preprocess (merge) geometries before rendering
Container of fields for a vector layer.
Definition: qgsfield.h:161
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:113
virtual int capabilities()
Proxy that will call this method on the embedded renderer.
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)
virtual QgsSymbolV2List symbols()
Proxy that will call this method on the embedded renderer.
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.
void setEmbeddedRenderer(const QgsFeatureRendererV2 *subRenderer)
sets the embedded renderer
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:33
void setGeometry(const QgsGeometry &geom)
Set this feature's geometry from another QgsGeometry object (deep copy)
Definition: qgsfeature.cpp:134
virtual void stopRender(QgsRenderContext &context)
The actual rendering will take place here.
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
QgsFields mFields
fields of each feature
QVector< QgsPolygon > QgsMultiPolygon
a collection of QgsPolygons that share a common collection of attributes
Definition: qgsgeometry.h:53
QGis::WkbType wkbType() const
Returns type of wkb (point / linestring / polygon etc.)
QgsPoint transform(const QgsPoint p, TransformDirection direction=ForwardTransform) const
QVector< QgsPolyline > QgsPolygon
polygon: first item of the list is outer ring, inner rings (if any) start from second item ...
Definition: qgsgeometry.h:44
static QgsFeatureRendererV2 * defaultRenderer(QGis::GeometryType geomType)
return a new renderer - used by default in vector layers
QList< QPair< QString, QPixmap > > QgsLegendSymbologyList
virtual QgsSymbolV2List symbolsForFeature(QgsFeature &feat)
Proxy that will call this method on the embedded renderer.
Class used to represent features that must be rendered with decorations (selection, vertex markers)
virtual bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false)
Renders a given feature.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
virtual QList< QString > usedAttributes()
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()
QMap< QByteArray, int > mSymbolCategories
maps a category to an index
FeatureCategoryVector mFeaturesCategories
where features are stored, based on the index of their symbol category
static QgsFeatureRendererV2 * load(QDomElement &symbologyElem)
create a renderer from XML element
Class for doing transforms between two map coordinate systems.
virtual QgsSymbolV2 * symbolForFeature(QgsFeature &feature)
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
QgsRenderContext mContext
the context used for rendering
static QgsGeometry * fromPolygon(const QgsPolygon &polygon)
construct geometry from a polygon
virtual QDomElement save(QDomDocument &doc)
Creates an XML representation of the renderer.
static QgsFeatureRendererV2 * create(QDomElement &element)
Creates a renderer out of an XML, for loading.
QScopedPointer< QgsFeatureRendererV2 > mSubRenderer
Embedded renderer.
virtual bool willRenderFeature(QgsFeature &feat)
Proxy that will call this method on the embedded renderer.
QList< QPair< QString, QgsSymbolV2 * > > QgsLegendSymbolList
Definition: qgsrendererv2.h:41
QList< FeatureDecoration > mFeatureDecorations
Structure where the reversed geometry is built during renderFeature.
virtual QgsLegendSymbologyList legendSymbologyItems(QSize iconSize)
Proxy that will call this method on the embedded renderer.