QGIS API Documentation  2.0.1-Dufour
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgspointdisplacementrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspointdisplacementrenderer.cpp
3  --------------------------------
4  begin : January 26, 2010
5  copyright : (C) 2010 by Marco Hugentobler
6  email : marco at hugis dot net
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
19 #include "qgsgeometry.h"
20 #include "qgslogger.h"
21 #include "qgsspatialindex.h"
22 #include "qgssymbolv2.h"
23 #include "qgssymbollayerv2utils.h"
24 #include "qgsvectorlayer.h"
25 
26 #include <QDomElement>
27 #include <QPainter>
28 
29 #include <cmath>
30 
32  : QgsFeatureRendererV2( "pointDisplacement" )
33  , mLabelAttributeName( labelAttributeName )
34  , mLabelIndex( -1 )
35  , mTolerance( 0.00001 )
36  , mCircleWidth( 0.4 )
37  , mCircleColor( QColor( 125, 125, 125 ) )
38  , mCircleRadiusAddition( 0 )
39  , mMaxLabelScaleDenominator( -1 )
40 {
42  mCenterSymbol = new QgsMarkerSymbolV2(); //the symbol for the center of a displacement group
43  mDrawLabels = true;
44 }
45 
47 {
48  delete mCenterSymbol;
49  delete mRenderer;
50 }
51 
53 {
64  if ( mCenterSymbol )
65  {
66  r->setCenterSymbol( dynamic_cast<QgsMarkerSymbolV2*>( mCenterSymbol->clone() ) );
67  }
68  return r;
69 }
70 
71 void QgsPointDisplacementRenderer::toSld( QDomDocument& doc, QDomElement &element ) const
72 {
73  mRenderer->toSld( doc, element );
74 }
75 
76 
77 bool QgsPointDisplacementRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
78 {
79  Q_UNUSED( drawVertexMarker );
80  //point position in screen coords
81  QgsGeometry* geom = feature.geometry();
82  QGis::WkbType geomType = geom->wkbType();
83  if ( geomType != QGis::WKBPoint && geomType != QGis::WKBPoint25D )
84  {
85  //can only render point type
86  return false;
87  }
88  QPointF pt;
89  _getPoint( pt, context, geom->asWkb() );
90 
91 
92  //get list of labels and symbols
93  QStringList labelAttributeList;
94  QList<QgsMarkerSymbolV2*> symbolList;
95 
96  if ( mDisplacementIds.contains( feature.id() ) )
97  {
98  //create the symbol for the whole display group if the id is the first entry in a display group
99  QList<QMap<QgsFeatureId, QgsFeature> >::iterator it = mDisplacementGroups.begin();
100  for ( ; it != mDisplacementGroups.end(); ++it )
101  {
102  //create the symbol for the whole display group if the id is the first entry in a display group
103  if ( feature.id() == it->begin().key() )
104  {
105  QMap<QgsFeatureId, QgsFeature>::iterator attIt = it->begin();
106  for ( ; attIt != it->end(); ++attIt )
107  {
108  if ( mDrawLabels )
109  {
110  labelAttributeList << getLabel( attIt.value() );
111  }
112  else
113  {
114  labelAttributeList << QString();
115  }
116  symbolList << dynamic_cast<QgsMarkerSymbolV2*>( firstSymbolForFeature( mRenderer, attIt.value() ) );
117  }
118  }
119  }
120  }
121  else //only one feature
122  {
123  symbolList << dynamic_cast<QgsMarkerSymbolV2*>( firstSymbolForFeature( mRenderer, feature ) );
124  if ( mDrawLabels )
125  {
126  labelAttributeList << getLabel( feature );
127  }
128  else
129  {
130  labelAttributeList << QString();
131  }
132  }
133 
134  if ( symbolList.isEmpty() && labelAttributeList.isEmpty() )
135  {
136  return true; //display all point symbols for one posi
137  }
138 
139 
140  //draw symbol
141  double diagonal = 0;
142  double currentWidthFactor; //scale symbol size to map unit and output resolution
143 
144  QList<QgsMarkerSymbolV2*>::const_iterator it = symbolList.constBegin();
145  for ( ; it != symbolList.constEnd(); ++it )
146  {
147  if ( *it )
148  {
149  currentWidthFactor = QgsSymbolLayerV2Utils::lineWidthScaleFactor( context, ( *it )->outputUnit() );
150  double currentDiagonal = sqrt( 2 * (( *it )->size() * ( *it )->size() ) ) * currentWidthFactor;
151  if ( currentDiagonal > diagonal )
152  {
153  diagonal = currentDiagonal;
154  }
155  }
156  }
157 
158 
159  QgsSymbolV2RenderContext symbolContext( context, QgsSymbolV2::MM, 1.0, selected );
160  double circleAdditionPainterUnits = symbolContext.outputLineWidth( mCircleRadiusAddition );
161  double radius = qMax(( diagonal / 2 ), labelAttributeList.size() * diagonal / 2 / M_PI ) + circleAdditionPainterUnits;
162 
163  //draw Circle
164  drawCircle( radius, symbolContext, pt, symbolList.size() );
165 
166  QList<QPointF> symbolPositions;
167  QList<QPointF> labelPositions;
168  calculateSymbolAndLabelPositions( pt, labelAttributeList.size(), radius, diagonal, symbolPositions, labelPositions );
169 
170  //draw mid point
171  if ( labelAttributeList.size() > 1 )
172  {
173  if ( mCenterSymbol )
174  {
175  mCenterSymbol->renderPoint( pt, &feature, context, layer, selected );
176  }
177  else
178  {
179  context.painter()->drawRect( QRectF( pt.x() - symbolContext.outputLineWidth( 1 ), pt.y() - symbolContext.outputLineWidth( 1 ), symbolContext.outputLineWidth( 2 ), symbolContext.outputLineWidth( 2 ) ) );
180  }
181  }
182 
183  //draw symbols on the circle
184  drawSymbols( feature, context, symbolList, symbolPositions, selected );
185  //and also the labels
186  drawLabels( pt, symbolContext, labelPositions, labelAttributeList );
187  return true;
188 }
189 
191 {
192  delete mRenderer;
193  mRenderer = r;
194 }
195 
197 {
198  Q_UNUSED( feature );
199  return 0; //not used any more
200 }
201 
203 {
204  mRenderer->startRender( context, vlayer );
205 
206  //create groups with features that have the same position
207  createDisplacementGroups( const_cast<QgsVectorLayer*>( vlayer ), context.extent() );
208  printInfoDisplacementGroups(); //just for debugging
209 
210  if ( mLabelAttributeName.isEmpty() )
211  {
212  mLabelIndex = -1;
213  }
214  else
215  {
217  }
218 
220  {
221  mDrawLabels = false;
222  }
223  else
224  {
225  mDrawLabels = true;
226  }
227 
228  if ( mCenterSymbol )
229  {
230  mCenterSymbol->startRender( context, vlayer );
231  }
232 }
233 
235 {
236  QgsDebugMsg( "QgsPointDisplacementRenderer::stopRender" );
237  mRenderer->stopRender( context );
238  if ( mCenterSymbol )
239  {
240  mCenterSymbol->stopRender( context );
241  }
242 }
243 
245 {
246  QList<QString> attributeList;
247  if ( !mLabelAttributeName.isEmpty() )
248  {
249  attributeList.push_back( mLabelAttributeName );
250  }
251  if ( mRenderer )
252  {
253  attributeList += mRenderer->usedAttributes();
254  }
255  return attributeList;
256 }
257 
259 {
260  if ( mRenderer )
261  {
262  return mRenderer->symbols();
263  }
264  else
265  {
266  return QgsSymbolV2List();
267  }
268 }
269 
271 {
273  r->setLabelAttributeName( symbologyElem.attribute( "labelAttributeName" ) );
274  QFont labelFont;
275  labelFont.fromString( symbologyElem.attribute( "labelFont", "" ) );
276  r->setLabelFont( labelFont );
277  r->setCircleWidth( symbologyElem.attribute( "circleWidth", "0.4" ).toDouble() );
278  r->setCircleColor( QgsSymbolLayerV2Utils::decodeColor( symbologyElem.attribute( "circleColor", "" ) ) );
279  r->setLabelColor( QgsSymbolLayerV2Utils::decodeColor( symbologyElem.attribute( "labelColor", "" ) ) );
280  r->setCircleRadiusAddition( symbologyElem.attribute( "circleRadiusAddition", "0.0" ).toDouble() );
281  r->setMaxLabelScaleDenominator( symbologyElem.attribute( "maxLabelScaleDenominator", "-1" ).toDouble() );
282 
283  //look for an embedded renderer <renderer-v2>
284  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( "renderer-v2" );
285  if ( !embeddedRendererElem.isNull() )
286  {
287  r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
288  }
289 
290  //center symbol
291  QDomElement centerSymbolElem = symbologyElem.firstChildElement( "symbol" );
292  if ( !centerSymbolElem.isNull() )
293  {
294  r->setCenterSymbol( dynamic_cast<QgsMarkerSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( centerSymbolElem ) ) );
295  }
296  return r;
297 }
298 
299 QDomElement QgsPointDisplacementRenderer::save( QDomDocument& doc )
300 {
301  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
302  rendererElement.setAttribute( "type", "pointDisplacement" );
303  rendererElement.setAttribute( "labelAttributeName", mLabelAttributeName );
304  rendererElement.setAttribute( "labelFont", mLabelFont.toString() );
305  rendererElement.setAttribute( "circleWidth", QString::number( mCircleWidth ) );
306  rendererElement.setAttribute( "circleColor", QgsSymbolLayerV2Utils::encodeColor( mCircleColor ) );
307  rendererElement.setAttribute( "labelColor", QgsSymbolLayerV2Utils::encodeColor( mLabelColor ) );
308  rendererElement.setAttribute( "circleRadiusAddition", QString::number( mCircleRadiusAddition ) );
309  rendererElement.setAttribute( "maxLabelScaleDenominator", QString::number( mMaxLabelScaleDenominator ) );
310 
311  if ( mRenderer )
312  {
313  QDomElement embeddedRendererElem = mRenderer->save( doc );
314  rendererElement.appendChild( embeddedRendererElem );
315  }
316  if ( mCenterSymbol )
317  {
318  QDomElement centerSymbolElem = QgsSymbolLayerV2Utils::saveSymbol( "centerSymbol", mCenterSymbol, doc );
319  rendererElement.appendChild( centerSymbolElem );
320  }
321  return rendererElement;
322 }
323 
325 {
326  if ( mRenderer )
327  {
328  return mRenderer->legendSymbologyItems( iconSize );
329  }
330  return QgsLegendSymbologyList();
331 }
332 
334 {
335  if ( mRenderer )
336  {
337  return mRenderer->legendSymbolItems();
338  }
339  return QgsLegendSymbolList();
340 }
341 
343 {
344  if ( !vlayer || ( vlayer->wkbType() != QGis::WKBPoint && vlayer->wkbType() != QGis::WKBPoint25D ) )
345  {
346  return;
347  }
348 
349  mDisplacementGroups.clear();
350  mDisplacementIds.clear();
351 
352  //use a spatial index to check if there is already a point at a position
353  QgsSpatialIndex spatialIndex;
354 
355  //attributes
356  QgsAttributeList attList;
357  QList<QString> attributeStrings = usedAttributes();
358  QList<QString>::const_iterator attStringIt = attributeStrings.constBegin();
359  for ( ; attStringIt != attributeStrings.constEnd(); ++attStringIt )
360  {
361  attList.push_back( vlayer->fieldNameIndex( *attStringIt ) );
362  }
363 
364  QgsFeature f;
365  QList<QgsFeatureId> intersectList;
366 
367  //Because the new vector api does not allow querying features by id within a nextFeature loop, default constructed QgsFeature() is
368  //inserted first and the real features are created in a second loop
369 
370  QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( viewExtent ).setSubsetOfAttributes( attList ) );
371  while ( fit.nextFeature( f ) )
372  {
373  intersectList.clear();
374 
375  //check, if there is already a point at that position
376  if ( f.geometry() )
377  {
378  intersectList = spatialIndex.intersects( searchRect( f.geometry()->asPoint() ) );
379  if ( intersectList.empty() )
380  {
381  spatialIndex.insertFeature( f );
382  }
383  else
384  {
385  //go through all the displacement group maps and search an entry where the id equals the result of the spatial search
386  QgsFeatureId existingEntry = intersectList.at( 0 );
387  bool found = false;
388  QList< QMap<QgsFeatureId, QgsFeature> >::iterator it = mDisplacementGroups.begin();
389  for ( ; it != mDisplacementGroups.end(); ++it )
390  {
391  if ( it->size() > 0 && it->contains( existingEntry ) )
392  {
393  found = true;
394  QgsFeature feature;
395  it->insert( f.id(), QgsFeature() );
396  mDisplacementIds.insert( f.id() );
397  break;
398  }
399  }
400 
401  if ( !found )//insert the already existing feature and the new one into a map
402  {
403  QMap<QgsFeatureId, QgsFeature> newMap;
404  newMap.insert( existingEntry, QgsFeature() );
405  mDisplacementIds.insert( existingEntry );
406  newMap.insert( f.id(), QgsFeature() );
407  mDisplacementIds.insert( f.id() );
408  mDisplacementGroups.push_back( newMap );
409  }
410  }
411  }
412  }
413 
414  //insert the real features into mDisplacementGroups
415  QList< QMap<QgsFeatureId, QgsFeature> >::iterator it = mDisplacementGroups.begin();
416  for ( ; it != mDisplacementGroups.end(); ++it )
417  {
418  QMap<QgsFeatureId, QgsFeature>::iterator mapIt = it->begin();
419  for ( ; mapIt != it->end(); ++mapIt )
420  {
421  QgsFeature fet;
422  vlayer->getFeatures( QgsFeatureRequest().setFilterFid( mapIt.key() ) ).nextFeature( fet );
423  mapIt.value() = fet;
424  }
425  }
426 
427 }
428 
430 {
431  return QgsRectangle( p.x() - mTolerance, p.y() - mTolerance, p.x() + mTolerance, p.y() + mTolerance );
432 }
433 
435 {
436  int nGroups = mDisplacementGroups.size();
437  QgsDebugMsg( "number of displacement groups:" + QString::number( nGroups ) );
438  for ( int i = 0; i < nGroups; ++i )
439  {
440  QgsDebugMsg( "***************displacement group " + QString::number( i ) );
441  QMap<QgsFeatureId, QgsFeature>::const_iterator it = mDisplacementGroups.at( i ).constBegin();
442  for ( ; it != mDisplacementGroups.at( i ).constEnd(); ++it )
443  {
444  QgsDebugMsg( FID_TO_STRING( it.key() ) );
445  }
446  }
447  QgsDebugMsg( "********all displacement ids*********" );
448  QSet<QgsFeatureId>::const_iterator iIt = mDisplacementIds.constBegin();
449  for ( ; iIt != mDisplacementIds.constEnd(); ++iIt )
450  {
451  QgsDebugMsg( FID_TO_STRING( *iIt ) );
452  }
453 }
454 
455 void QgsPointDisplacementRenderer::setDisplacementGroups( const QList< QMap<QgsFeatureId, QgsFeature> >& list )
456 {
457  mDisplacementGroups = list;
458  mDisplacementIds.clear();
459 
460  QList<QMap<QgsFeatureId, QgsFeature> >::const_iterator list_it = mDisplacementGroups.constBegin();
461  for ( ; list_it != mDisplacementGroups.constEnd(); ++list_it )
462  {
463  QMap<QgsFeatureId, QgsFeature>::const_iterator map_it = list_it->constBegin();
464  for ( ; map_it != list_it->constEnd(); ++map_it )
465  {
466  mDisplacementIds.insert( map_it.key() );
467  }
468  }
469 }
470 
472 {
473  QString attribute;
474  const QgsAttributes& attrs = f.attributes();
475  if ( mLabelIndex >= 0 && mLabelIndex < attrs.count() )
476  {
477  attribute = attrs[mLabelIndex].toString();
478  }
479  return attribute;
480 }
481 
483 {
484  delete mCenterSymbol;
485  mCenterSymbol = symbol;
486 }
487 
488 
489 
490 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( const QPointF& centerPoint, int nPosition, double radius,
491  double symbolDiagonal, QList<QPointF>& symbolPositions, QList<QPointF>& labelShifts ) const
492 {
493  symbolPositions.clear();
494  labelShifts.clear();
495 
496  if ( nPosition < 1 )
497  {
498  return;
499  }
500  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
501  {
502  symbolPositions.append( centerPoint );
503  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
504  return;
505  }
506 
507  double fullPerimeter = 2 * M_PI;
508  double angleStep = fullPerimeter / nPosition;
509  double currentAngle;
510 
511  for ( currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
512  {
513  double sinusCurrentAngle = sin( currentAngle );
514  double cosinusCurrentAngle = cos( currentAngle );
515  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
516  QPointF labelShift(( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
517  symbolPositions.append( centerPoint + positionShift );
518  labelShifts.append( labelShift );
519  }
520 }
521 
522 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolV2RenderContext& context, const QPointF& centerPoint, int nSymbols )
523 {
524  QPainter* p = context.renderContext().painter();
525  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
526  {
527  return;
528  }
529 
530  //draw Circle
531  QPen circlePen( mCircleColor );
532  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
533  p->setPen( circlePen );
534  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
535 }
536 
537 void QgsPointDisplacementRenderer::drawSymbols( QgsFeature& f, QgsRenderContext& context, const QList<QgsMarkerSymbolV2*>& symbolList, const QList<QPointF>& symbolPositions, bool selected )
538 {
539  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
540  QList<QgsMarkerSymbolV2*>::const_iterator symbolIt = symbolList.constBegin();
541  for ( ; symbolPosIt != symbolPositions.constEnd() && symbolIt != symbolList.constEnd(); ++symbolPosIt, ++symbolIt )
542  {
543  if ( *symbolIt )
544  {
545  ( *symbolIt )->renderPoint( *symbolPosIt, &f, context, -1, selected );
546  }
547  }
548 }
549 
550 void QgsPointDisplacementRenderer::drawLabels( const QPointF& centerPoint, QgsSymbolV2RenderContext& context, const QList<QPointF>& labelShifts, const QStringList& labelList )
551 {
552  QPainter* p = context.renderContext().painter();
553  if ( !p )
554  {
555  return;
556  }
557 
558  QPen labelPen( mLabelColor );
559  p->setPen( labelPen );
560 
561  //scale font (for printing)
562  QFont pixelSizeFont = mLabelFont;
563  pixelSizeFont.setPixelSize( context.outputLineWidth( mLabelFont.pointSizeF() * 0.3527 ) );
564  QFont scaledFont = pixelSizeFont;
565  scaledFont.setPixelSize( pixelSizeFont.pixelSize() * context.renderContext().rasterScaleFactor() );
566  p->setFont( scaledFont );
567 
568  QFontMetricsF fontMetrics( pixelSizeFont );
569  QPointF currentLabelShift; //considers the signs to determine the label position
570 
571  QList<QPointF>::const_iterator labelPosIt = labelShifts.constBegin();
572  QStringList::const_iterator text_it = labelList.constBegin();
573 
574  for ( ; labelPosIt != labelShifts.constEnd() && text_it != labelList.constEnd(); ++labelPosIt, ++text_it )
575  {
576  currentLabelShift = *labelPosIt;
577  if ( currentLabelShift.x() < 0 )
578  {
579  currentLabelShift.setX( currentLabelShift.x() - fontMetrics.width( *text_it ) );
580  }
581  if ( currentLabelShift.y() > 0 )
582  {
583  currentLabelShift.setY( currentLabelShift.y() + fontMetrics.ascent() );
584  }
585 
586  QPointF drawingPoint( centerPoint + currentLabelShift );
587  p->save();
588  p->translate( drawingPoint.x(), drawingPoint.y() );
589  p->scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
590  p->drawText( QPointF( 0, 0 ), *text_it );
591  p->restore();
592  }
593 }
594 
596 {
597  if ( !r )
598  {
599  return 0;
600  }
601 
602  QgsSymbolV2List symbolList = r->symbolsForFeature( f );
603  if ( symbolList.size() < 1 )
604  {
605  return 0;
606  }
607 
608  return symbolList.at( 0 );
609 }