QGIS API Documentation  2.8.2-Wien
 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
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