QGIS API Documentation  2.8.2-Wien
 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"
26 
27 #include <QDomElement>
28 #include <QPainter>
29 
30 #include <cmath>
31 
33  : QgsFeatureRendererV2( "pointDisplacement" )
34  , mLabelAttributeName( labelAttributeName )
35  , mLabelIndex( -1 )
36  , mTolerance( 0.00001 )
37  , mCircleWidth( 0.4 )
38  , mCircleColor( QColor( 125, 125, 125 ) )
39  , mCircleRadiusAddition( 0 )
40  , mMaxLabelScaleDenominator( -1 )
41  , mSpatialIndex( NULL )
42 {
44  mCenterSymbol = new QgsMarkerSymbolV2(); //the symbol for the center of a displacement group
45  mDrawLabels = true;
46 }
47 
49 {
50  delete mCenterSymbol;
51  delete mRenderer;
52 }
53 
55 {
56  QgsPointDisplacementRenderer* r = new QgsPointDisplacementRenderer( mLabelAttributeName );
57  r->setEmbeddedRenderer( mRenderer->clone() );
58  r->setCircleWidth( mCircleWidth );
59  r->setCircleColor( mCircleColor );
60  r->setLabelFont( mLabelFont );
61  r->setLabelColor( mLabelColor );
62  r->setCircleRadiusAddition( mCircleRadiusAddition );
63  r->setMaxLabelScaleDenominator( mMaxLabelScaleDenominator );
64  r->setTolerance( mTolerance );
65  if ( mCenterSymbol )
66  {
67  r->setCenterSymbol( dynamic_cast<QgsMarkerSymbolV2*>( mCenterSymbol->clone() ) );
68  }
69  return r;
70 }
71 
72 void QgsPointDisplacementRenderer::toSld( QDomDocument& doc, QDomElement &element ) const
73 {
74  mRenderer->toSld( doc, element );
75 }
76 
77 
78 bool QgsPointDisplacementRenderer::renderFeature( QgsFeature& feature, QgsRenderContext& context, int layer, bool selected, bool drawVertexMarker )
79 {
80  Q_UNUSED( drawVertexMarker );
81  Q_UNUSED( context );
82  Q_UNUSED( layer );
83 
84  //check, if there is already a point at that position
85  if ( !feature.geometry() )
86  return false;
87 
88  //point position in screen coords
89  QgsGeometry* geom = feature.geometry();
90  QGis::WkbType geomType = geom->wkbType();
91  if ( geomType != QGis::WKBPoint && geomType != QGis::WKBPoint25D )
92  {
93  //can only render point type
94  return false;
95  }
96 
97  if ( selected )
98  mSelectedFeatures.insert( feature.id() );
99 
100  QList<QgsFeatureId> intersectList = mSpatialIndex->intersects( searchRect( feature.geometry()->asPoint() ) );
101  if ( intersectList.empty() )
102  {
103  mSpatialIndex->insertFeature( feature );
104  // create new group
105  DisplacementGroup newGroup;
106  newGroup.insert( feature.id(), feature );
107  mDisplacementGroups.push_back( newGroup );
108  // add to group index
109  mGroupIndex.insert( feature.id(), mDisplacementGroups.count() - 1 );
110  return true;
111  }
112 
113  //go through all the displacement group maps and search an entry where the id equals the result of the spatial search
114  QgsFeatureId existingEntry = intersectList.at( 0 );
115 
116  int groupIdx = mGroupIndex[ existingEntry ];
117  DisplacementGroup& group = mDisplacementGroups[groupIdx];
118 
119  // add to a group
120  group.insert( feature.id(), feature );
121  // add to group index
122  mGroupIndex.insert( feature.id(), groupIdx );
123  return true;
124 }
125 
126 void QgsPointDisplacementRenderer::drawGroup( const DisplacementGroup& group, QgsRenderContext& context )
127 {
128  const QgsFeature& feature = group.begin().value();
129  bool selected = mSelectedFeatures.contains( feature.id() ); // maybe we should highlight individual features instead of the whole group?
130 
131  QPointF pt;
132  _getPoint( pt, context, feature.geometry()->asWkb() );
133 
134  //get list of labels and symbols
135  QStringList labelAttributeList;
136  QList<QgsMarkerSymbolV2*> symbolList;
137 
138  for ( DisplacementGroup::const_iterator attIt = group.constBegin(); attIt != group.constEnd(); ++attIt )
139  {
140  labelAttributeList << ( mDrawLabels ? getLabel( attIt.value() ) : QString() );
141  QgsFeature& f = const_cast<QgsFeature&>( attIt.value() ); // other parts of API use non-const ref to QgsFeature :-/
142  symbolList << dynamic_cast<QgsMarkerSymbolV2*>( firstSymbolForFeature( mRenderer, f ) );
143  }
144 
145  //draw symbol
146  double diagonal = 0;
147  double currentWidthFactor; //scale symbol size to map unit and output resolution
148 
149  QList<QgsMarkerSymbolV2*>::const_iterator it = symbolList.constBegin();
150  for ( ; it != symbolList.constEnd(); ++it )
151  {
152  if ( *it )
153  {
154  currentWidthFactor = QgsSymbolLayerV2Utils::lineWidthScaleFactor( context, ( *it )->outputUnit(), ( *it )->mapUnitScale() );
155  double currentDiagonal = sqrt( 2 * (( *it )->size() * ( *it )->size() ) ) * currentWidthFactor;
156  if ( currentDiagonal > diagonal )
157  {
158  diagonal = currentDiagonal;
159  }
160  }
161  }
162 
163 
164  QgsSymbolV2RenderContext symbolContext( context, QgsSymbolV2::MM, 1.0, selected );
165  double circleAdditionPainterUnits = symbolContext.outputLineWidth( mCircleRadiusAddition );
166  double radius = qMax(( diagonal / 2 ), labelAttributeList.size() * diagonal / 2 / M_PI ) + circleAdditionPainterUnits;
167 
168  //draw Circle
169  drawCircle( radius, symbolContext, pt, symbolList.size() );
170 
171  QList<QPointF> symbolPositions;
172  QList<QPointF> labelPositions;
173  calculateSymbolAndLabelPositions( pt, labelAttributeList.size(), radius, diagonal, symbolPositions, labelPositions );
174 
175  //draw mid point
176  if ( labelAttributeList.size() > 1 )
177  {
178  if ( mCenterSymbol )
179  {
180  mCenterSymbol->renderPoint( pt, &feature, context, -1, selected );
181  }
182  else
183  {
184  context.painter()->drawRect( QRectF( pt.x() - symbolContext.outputLineWidth( 1 ), pt.y() - symbolContext.outputLineWidth( 1 ), symbolContext.outputLineWidth( 2 ), symbolContext.outputLineWidth( 2 ) ) );
185  }
186  }
187 
188  //draw symbols on the circle
189  drawSymbols( feature, context, symbolList, symbolPositions, selected );
190  //and also the labels
191  drawLabels( pt, symbolContext, labelPositions, labelAttributeList );
192 }
193 
195 {
196  delete mRenderer;
197  mRenderer = r;
198 }
199 
201 {
202  QList<QString> attributeList;
203  if ( !mLabelAttributeName.isEmpty() )
204  {
205  attributeList.push_back( mLabelAttributeName );
206  }
207  if ( mRenderer )
208  {
209  attributeList += mRenderer->usedAttributes();
210  }
211  return attributeList;
212 }
213 
215 {
216  if ( !mRenderer )
217  {
218  return 0;
219  }
220  return mRenderer->capabilities();
221 }
222 
224 {
225  if ( !mRenderer )
226  {
227  return QgsSymbolV2List();
228  }
229  return mRenderer->symbols();
230 }
231 
233 {
234  if ( !mRenderer )
235  {
236  return 0;
237  }
238  return mRenderer->symbolForFeature( feature );
239 }
240 
242 {
243  if ( !mRenderer )
244  return 0;
245  return mRenderer->originalSymbolForFeature( feat );
246 }
247 
249 {
250  if ( !mRenderer )
251  {
252  return QgsSymbolV2List();
253  }
254  return mRenderer->symbolsForFeature( feature );
255 }
256 
258 {
259  if ( !mRenderer )
260  return QgsSymbolV2List();
261  return mRenderer->originalSymbolsForFeature( feat );
262 }
263 
265 {
266  if ( !mRenderer )
267  {
268  return false;
269  }
270  return mRenderer->willRenderFeature( feat );
271 }
272 
273 
275 {
276  mRenderer->startRender( context, fields );
277 
278  mDisplacementGroups.clear();
279  mGroupIndex.clear();
280  mSpatialIndex = new QgsSpatialIndex;
281  mSelectedFeatures.clear();
282 
283  if ( mLabelAttributeName.isEmpty() )
284  {
285  mLabelIndex = -1;
286  }
287  else
288  {
289  mLabelIndex = fields.fieldNameIndex( mLabelAttributeName );
290  }
291 
292  if ( mMaxLabelScaleDenominator > 0 && context.rendererScale() > mMaxLabelScaleDenominator )
293  {
294  mDrawLabels = false;
295  }
296  else
297  {
298  mDrawLabels = true;
299  }
300 
301  if ( mCenterSymbol )
302  {
303  mCenterSymbol->startRender( context, &fields );
304  }
305 }
306 
308 {
309  QgsDebugMsg( "QgsPointDisplacementRenderer::stopRender" );
310 
311  //printInfoDisplacementGroups(); //just for debugging
312 
313  for ( QList<DisplacementGroup>::const_iterator it = mDisplacementGroups.begin(); it != mDisplacementGroups.end(); ++it )
314  drawGroup( *it, context );
315 
316  mDisplacementGroups.clear();
317  mGroupIndex.clear();
318  delete mSpatialIndex;
319  mSpatialIndex = 0;
320  mSelectedFeatures.clear();
321 
322  mRenderer->stopRender( context );
323  if ( mCenterSymbol )
324  {
325  mCenterSymbol->stopRender( context );
326  }
327 }
328 
330 {
332  r->setLabelAttributeName( symbologyElem.attribute( "labelAttributeName" ) );
333  QFont labelFont;
334  labelFont.fromString( symbologyElem.attribute( "labelFont", "" ) );
335  r->setLabelFont( labelFont );
336  r->setCircleWidth( symbologyElem.attribute( "circleWidth", "0.4" ).toDouble() );
337  r->setCircleColor( QgsSymbolLayerV2Utils::decodeColor( symbologyElem.attribute( "circleColor", "" ) ) );
338  r->setLabelColor( QgsSymbolLayerV2Utils::decodeColor( symbologyElem.attribute( "labelColor", "" ) ) );
339  r->setCircleRadiusAddition( symbologyElem.attribute( "circleRadiusAddition", "0.0" ).toDouble() );
340  r->setMaxLabelScaleDenominator( symbologyElem.attribute( "maxLabelScaleDenominator", "-1" ).toDouble() );
341  r->setTolerance( symbologyElem.attribute( "tolerance", "0.00001" ).toDouble() );
342 
343  //look for an embedded renderer <renderer-v2>
344  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( "renderer-v2" );
345  if ( !embeddedRendererElem.isNull() )
346  {
347  r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
348  }
349 
350  //center symbol
351  QDomElement centerSymbolElem = symbologyElem.firstChildElement( "symbol" );
352  if ( !centerSymbolElem.isNull() )
353  {
354  r->setCenterSymbol( QgsSymbolLayerV2Utils::loadSymbol<QgsMarkerSymbolV2>( centerSymbolElem ) );
355  }
356  return r;
357 }
358 
359 QDomElement QgsPointDisplacementRenderer::save( QDomDocument& doc )
360 {
361  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
362  rendererElement.setAttribute( "type", "pointDisplacement" );
363  rendererElement.setAttribute( "labelAttributeName", mLabelAttributeName );
364  rendererElement.setAttribute( "labelFont", mLabelFont.toString() );
365  rendererElement.setAttribute( "circleWidth", QString::number( mCircleWidth ) );
366  rendererElement.setAttribute( "circleColor", QgsSymbolLayerV2Utils::encodeColor( mCircleColor ) );
367  rendererElement.setAttribute( "labelColor", QgsSymbolLayerV2Utils::encodeColor( mLabelColor ) );
368  rendererElement.setAttribute( "circleRadiusAddition", QString::number( mCircleRadiusAddition ) );
369  rendererElement.setAttribute( "maxLabelScaleDenominator", QString::number( mMaxLabelScaleDenominator ) );
370  rendererElement.setAttribute( "tolerance", QString::number( mTolerance ) );
371 
372  if ( mRenderer )
373  {
374  QDomElement embeddedRendererElem = mRenderer->save( doc );
375  rendererElement.appendChild( embeddedRendererElem );
376  }
377  if ( mCenterSymbol )
378  {
379  QDomElement centerSymbolElem = QgsSymbolLayerV2Utils::saveSymbol( "centerSymbol", mCenterSymbol, doc );
380  rendererElement.appendChild( centerSymbolElem );
381  }
382  return rendererElement;
383 }
384 
386 {
387  if ( mRenderer )
388  {
389  return mRenderer->legendSymbologyItems( iconSize );
390  }
391  return QgsLegendSymbologyList();
392 }
393 
395 {
396  if ( mRenderer )
397  {
398  return mRenderer->legendSymbolItems( scaleDenominator, rule );
399  }
400  return QgsLegendSymbolList();
401 }
402 
403 
404 QgsRectangle QgsPointDisplacementRenderer::searchRect( const QgsPoint& p ) const
405 {
406  return QgsRectangle( p.x() - mTolerance, p.y() - mTolerance, p.x() + mTolerance, p.y() + mTolerance );
407 }
408 
409 void QgsPointDisplacementRenderer::printInfoDisplacementGroups()
410 {
411  int nGroups = mDisplacementGroups.size();
412  QgsDebugMsg( "number of displacement groups:" + QString::number( nGroups ) );
413  for ( int i = 0; i < nGroups; ++i )
414  {
415  QgsDebugMsg( "***************displacement group " + QString::number( i ) );
416  QMap<QgsFeatureId, QgsFeature>::const_iterator it = mDisplacementGroups.at( i ).constBegin();
417  for ( ; it != mDisplacementGroups.at( i ).constEnd(); ++it )
418  {
419  QgsDebugMsg( FID_TO_STRING( it.key() ) );
420  }
421  }
422 }
423 
424 QString QgsPointDisplacementRenderer::getLabel( const QgsFeature& f )
425 {
426  QString attribute;
427  const QgsAttributes& attrs = f.attributes();
428  if ( mLabelIndex >= 0 && mLabelIndex < attrs.count() )
429  {
430  attribute = attrs[mLabelIndex].toString();
431  }
432  return attribute;
433 }
434 
436 {
437  delete mCenterSymbol;
438  mCenterSymbol = symbol;
439 }
440 
441 
442 
443 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( const QPointF& centerPoint, int nPosition, double radius,
444  double symbolDiagonal, QList<QPointF>& symbolPositions, QList<QPointF>& labelShifts ) const
445 {
446  symbolPositions.clear();
447  labelShifts.clear();
448 
449  if ( nPosition < 1 )
450  {
451  return;
452  }
453  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
454  {
455  symbolPositions.append( centerPoint );
456  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
457  return;
458  }
459 
460  double fullPerimeter = 2 * M_PI;
461  double angleStep = fullPerimeter / nPosition;
462  double currentAngle;
463 
464  for ( currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
465  {
466  double sinusCurrentAngle = sin( currentAngle );
467  double cosinusCurrentAngle = cos( currentAngle );
468  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
469  QPointF labelShift(( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
470  symbolPositions.append( centerPoint + positionShift );
471  labelShifts.append( labelShift );
472  }
473 }
474 
475 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolV2RenderContext& context, const QPointF& centerPoint, int nSymbols )
476 {
477  QPainter* p = context.renderContext().painter();
478  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
479  {
480  return;
481  }
482 
483  //draw Circle
484  QPen circlePen( mCircleColor );
485  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
486  p->setPen( circlePen );
487  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
488 }
489 
490 void QgsPointDisplacementRenderer::drawSymbols( const QgsFeature& f, QgsRenderContext& context, const QList<QgsMarkerSymbolV2*>& symbolList, const QList<QPointF>& symbolPositions, bool selected )
491 {
492  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
493  QList<QgsMarkerSymbolV2*>::const_iterator symbolIt = symbolList.constBegin();
494  for ( ; symbolPosIt != symbolPositions.constEnd() && symbolIt != symbolList.constEnd(); ++symbolPosIt, ++symbolIt )
495  {
496  if ( *symbolIt )
497  {
498  ( *symbolIt )->renderPoint( *symbolPosIt, &f, context, -1, selected );
499  }
500  }
501 }
502 
503 void QgsPointDisplacementRenderer::drawLabels( const QPointF& centerPoint, QgsSymbolV2RenderContext& context, const QList<QPointF>& labelShifts, const QStringList& labelList )
504 {
505  QPainter* p = context.renderContext().painter();
506  if ( !p )
507  {
508  return;
509  }
510 
511  QPen labelPen( mLabelColor );
512  p->setPen( labelPen );
513 
514  //scale font (for printing)
515  QFont pixelSizeFont = mLabelFont;
516  pixelSizeFont.setPixelSize( context.outputLineWidth( mLabelFont.pointSizeF() * 0.3527 ) );
517  QFont scaledFont = pixelSizeFont;
518  scaledFont.setPixelSize( pixelSizeFont.pixelSize() * context.renderContext().rasterScaleFactor() );
519  p->setFont( scaledFont );
520 
521  QFontMetricsF fontMetrics( pixelSizeFont );
522  QPointF currentLabelShift; //considers the signs to determine the label position
523 
524  QList<QPointF>::const_iterator labelPosIt = labelShifts.constBegin();
525  QStringList::const_iterator text_it = labelList.constBegin();
526 
527  for ( ; labelPosIt != labelShifts.constEnd() && text_it != labelList.constEnd(); ++labelPosIt, ++text_it )
528  {
529  currentLabelShift = *labelPosIt;
530  if ( currentLabelShift.x() < 0 )
531  {
532  currentLabelShift.setX( currentLabelShift.x() - fontMetrics.width( *text_it ) );
533  }
534  if ( currentLabelShift.y() > 0 )
535  {
536  currentLabelShift.setY( currentLabelShift.y() + fontMetrics.ascent() );
537  }
538 
539  QPointF drawingPoint( centerPoint + currentLabelShift );
540  p->save();
541  p->translate( drawingPoint.x(), drawingPoint.y() );
542  p->scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
543  p->drawText( QPointF( 0, 0 ), *text_it );
544  p->restore();
545  }
546 }
547 
548 QgsSymbolV2* QgsPointDisplacementRenderer::firstSymbolForFeature( QgsFeatureRendererV2* r, QgsFeature& f )
549 {
550  if ( !r )
551  {
552  return 0;
553  }
554 
555  QgsSymbolV2List symbolList = r->symbolsForFeature( f );
556  if ( symbolList.size() < 1 )
557  {
558  return 0;
559  }
560 
561  return symbolList.at( 0 );
562 }
563 
565 {
566  if ( renderer->type() == "pointDisplacement" )
567  {
568  return dynamic_cast<QgsPointDisplacementRenderer*>( renderer->clone() );
569  }
570 
571  if ( renderer->type() == "singleSymbol" ||
572  renderer->type() == "categorizedSymbol" ||
573  renderer->type() == "graduatedSymbol" ||
574  renderer->type() == "RuleRenderer" )
575  {
577  pointRenderer->setEmbeddedRenderer( renderer->clone() );
578  return pointRenderer;
579  }
580  return 0;
581 }