QGIS API Documentation  2.2.0-Valmiera
 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  r->setTolerance( symbologyElem.attribute( "tolerance", "0.00001" ).toDouble() );
283 
284  //look for an embedded renderer <renderer-v2>
285  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( "renderer-v2" );
286  if ( !embeddedRendererElem.isNull() )
287  {
288  r->setEmbeddedRenderer( QgsFeatureRendererV2::load( embeddedRendererElem ) );
289  }
290 
291  //center symbol
292  QDomElement centerSymbolElem = symbologyElem.firstChildElement( "symbol" );
293  if ( !centerSymbolElem.isNull() )
294  {
295  r->setCenterSymbol( dynamic_cast<QgsMarkerSymbolV2*>( QgsSymbolLayerV2Utils::loadSymbol( centerSymbolElem ) ) );
296  }
297  return r;
298 }
299 
300 QDomElement QgsPointDisplacementRenderer::save( QDomDocument& doc )
301 {
302  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
303  rendererElement.setAttribute( "type", "pointDisplacement" );
304  rendererElement.setAttribute( "labelAttributeName", mLabelAttributeName );
305  rendererElement.setAttribute( "labelFont", mLabelFont.toString() );
306  rendererElement.setAttribute( "circleWidth", QString::number( mCircleWidth ) );
307  rendererElement.setAttribute( "circleColor", QgsSymbolLayerV2Utils::encodeColor( mCircleColor ) );
308  rendererElement.setAttribute( "labelColor", QgsSymbolLayerV2Utils::encodeColor( mLabelColor ) );
309  rendererElement.setAttribute( "circleRadiusAddition", QString::number( mCircleRadiusAddition ) );
310  rendererElement.setAttribute( "maxLabelScaleDenominator", QString::number( mMaxLabelScaleDenominator ) );
311  rendererElement.setAttribute( "tolerance", QString::number( mTolerance ) );
312 
313  if ( mRenderer )
314  {
315  QDomElement embeddedRendererElem = mRenderer->save( doc );
316  rendererElement.appendChild( embeddedRendererElem );
317  }
318  if ( mCenterSymbol )
319  {
320  QDomElement centerSymbolElem = QgsSymbolLayerV2Utils::saveSymbol( "centerSymbol", mCenterSymbol, doc );
321  rendererElement.appendChild( centerSymbolElem );
322  }
323  return rendererElement;
324 }
325 
327 {
328  if ( mRenderer )
329  {
330  return mRenderer->legendSymbologyItems( iconSize );
331  }
332  return QgsLegendSymbologyList();
333 }
334 
336 {
337  if ( mRenderer )
338  {
339  return mRenderer->legendSymbolItems( scaleDenominator, rule );
340  }
341  return QgsLegendSymbolList();
342 }
343 
345 {
346  if ( !vlayer || ( vlayer->wkbType() != QGis::WKBPoint && vlayer->wkbType() != QGis::WKBPoint25D ) )
347  {
348  return;
349  }
350 
351  mDisplacementGroups.clear();
352  mDisplacementIds.clear();
353 
354  //use a spatial index to check if there is already a point at a position
355  QgsSpatialIndex spatialIndex;
356 
357  //attributes
358  QgsAttributeList attList;
359  QList<QString> attributeStrings = usedAttributes();
360  QList<QString>::const_iterator attStringIt = attributeStrings.constBegin();
361  for ( ; attStringIt != attributeStrings.constEnd(); ++attStringIt )
362  {
363  attList.push_back( vlayer->fieldNameIndex( *attStringIt ) );
364  }
365 
366  QgsFeature f;
367  QList<QgsFeatureId> intersectList;
368 
369  //Because the new vector api does not allow querying features by id within a nextFeature loop, default constructed QgsFeature() is
370  //inserted first and the real features are created in a second loop
371 
372  QgsFeatureIterator fit = vlayer->getFeatures( QgsFeatureRequest().setFilterRect( viewExtent ).setSubsetOfAttributes( attList ) );
373  while ( fit.nextFeature( f ) )
374  {
375  intersectList.clear();
376 
377  //check, if there is already a point at that position
378  if ( f.geometry() )
379  {
380  intersectList = spatialIndex.intersects( searchRect( f.geometry()->asPoint() ) );
381  if ( intersectList.empty() )
382  {
383  spatialIndex.insertFeature( f );
384  }
385  else
386  {
387  //go through all the displacement group maps and search an entry where the id equals the result of the spatial search
388  QgsFeatureId existingEntry = intersectList.at( 0 );
389  bool found = false;
390  QList< QMap<QgsFeatureId, QgsFeature> >::iterator it = mDisplacementGroups.begin();
391  for ( ; it != mDisplacementGroups.end(); ++it )
392  {
393  if ( it->size() > 0 && it->contains( existingEntry ) )
394  {
395  found = true;
396  QgsFeature feature;
397  it->insert( f.id(), QgsFeature() );
398  mDisplacementIds.insert( f.id() );
399  break;
400  }
401  }
402 
403  if ( !found )//insert the already existing feature and the new one into a map
404  {
405  QMap<QgsFeatureId, QgsFeature> newMap;
406  newMap.insert( existingEntry, QgsFeature() );
407  mDisplacementIds.insert( existingEntry );
408  newMap.insert( f.id(), QgsFeature() );
409  mDisplacementIds.insert( f.id() );
410  mDisplacementGroups.push_back( newMap );
411  }
412  }
413  }
414  }
415 
416  //insert the real features into mDisplacementGroups
417  QList< QMap<QgsFeatureId, QgsFeature> >::iterator it = mDisplacementGroups.begin();
418  for ( ; it != mDisplacementGroups.end(); ++it )
419  {
420  QMap<QgsFeatureId, QgsFeature>::iterator mapIt = it->begin();
421  for ( ; mapIt != it->end(); ++mapIt )
422  {
423  QgsFeature fet;
424  vlayer->getFeatures( QgsFeatureRequest().setFilterFid( mapIt.key() ) ).nextFeature( fet );
425  mapIt.value() = fet;
426  }
427  }
428 
429 }
430 
432 {
433  return QgsRectangle( p.x() - mTolerance, p.y() - mTolerance, p.x() + mTolerance, p.y() + mTolerance );
434 }
435 
437 {
438  int nGroups = mDisplacementGroups.size();
439  QgsDebugMsg( "number of displacement groups:" + QString::number( nGroups ) );
440  for ( int i = 0; i < nGroups; ++i )
441  {
442  QgsDebugMsg( "***************displacement group " + QString::number( i ) );
443  QMap<QgsFeatureId, QgsFeature>::const_iterator it = mDisplacementGroups.at( i ).constBegin();
444  for ( ; it != mDisplacementGroups.at( i ).constEnd(); ++it )
445  {
446  QgsDebugMsg( FID_TO_STRING( it.key() ) );
447  }
448  }
449  QgsDebugMsg( "********all displacement ids*********" );
450  QSet<QgsFeatureId>::const_iterator iIt = mDisplacementIds.constBegin();
451  for ( ; iIt != mDisplacementIds.constEnd(); ++iIt )
452  {
453  QgsDebugMsg( FID_TO_STRING( *iIt ) );
454  }
455 }
456 
457 void QgsPointDisplacementRenderer::setDisplacementGroups( const QList< QMap<QgsFeatureId, QgsFeature> >& list )
458 {
459  mDisplacementGroups = list;
460  mDisplacementIds.clear();
461 
462  QList<QMap<QgsFeatureId, QgsFeature> >::const_iterator list_it = mDisplacementGroups.constBegin();
463  for ( ; list_it != mDisplacementGroups.constEnd(); ++list_it )
464  {
465  QMap<QgsFeatureId, QgsFeature>::const_iterator map_it = list_it->constBegin();
466  for ( ; map_it != list_it->constEnd(); ++map_it )
467  {
468  mDisplacementIds.insert( map_it.key() );
469  }
470  }
471 }
472 
474 {
475  QString attribute;
476  const QgsAttributes& attrs = f.attributes();
477  if ( mLabelIndex >= 0 && mLabelIndex < attrs.count() )
478  {
479  attribute = attrs[mLabelIndex].toString();
480  }
481  return attribute;
482 }
483 
485 {
486  delete mCenterSymbol;
487  mCenterSymbol = symbol;
488 }
489 
490 
491 
492 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( const QPointF& centerPoint, int nPosition, double radius,
493  double symbolDiagonal, QList<QPointF>& symbolPositions, QList<QPointF>& labelShifts ) const
494 {
495  symbolPositions.clear();
496  labelShifts.clear();
497 
498  if ( nPosition < 1 )
499  {
500  return;
501  }
502  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
503  {
504  symbolPositions.append( centerPoint );
505  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
506  return;
507  }
508 
509  double fullPerimeter = 2 * M_PI;
510  double angleStep = fullPerimeter / nPosition;
511  double currentAngle;
512 
513  for ( currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
514  {
515  double sinusCurrentAngle = sin( currentAngle );
516  double cosinusCurrentAngle = cos( currentAngle );
517  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
518  QPointF labelShift(( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
519  symbolPositions.append( centerPoint + positionShift );
520  labelShifts.append( labelShift );
521  }
522 }
523 
524 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolV2RenderContext& context, const QPointF& centerPoint, int nSymbols )
525 {
526  QPainter* p = context.renderContext().painter();
527  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
528  {
529  return;
530  }
531 
532  //draw Circle
533  QPen circlePen( mCircleColor );
534  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
535  p->setPen( circlePen );
536  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
537 }
538 
539 void QgsPointDisplacementRenderer::drawSymbols( QgsFeature& f, QgsRenderContext& context, const QList<QgsMarkerSymbolV2*>& symbolList, const QList<QPointF>& symbolPositions, bool selected )
540 {
541  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
542  QList<QgsMarkerSymbolV2*>::const_iterator symbolIt = symbolList.constBegin();
543  for ( ; symbolPosIt != symbolPositions.constEnd() && symbolIt != symbolList.constEnd(); ++symbolPosIt, ++symbolIt )
544  {
545  if ( *symbolIt )
546  {
547  ( *symbolIt )->renderPoint( *symbolPosIt, &f, context, -1, selected );
548  }
549  }
550 }
551 
552 void QgsPointDisplacementRenderer::drawLabels( const QPointF& centerPoint, QgsSymbolV2RenderContext& context, const QList<QPointF>& labelShifts, const QStringList& labelList )
553 {
554  QPainter* p = context.renderContext().painter();
555  if ( !p )
556  {
557  return;
558  }
559 
560  QPen labelPen( mLabelColor );
561  p->setPen( labelPen );
562 
563  //scale font (for printing)
564  QFont pixelSizeFont = mLabelFont;
565  pixelSizeFont.setPixelSize( context.outputLineWidth( mLabelFont.pointSizeF() * 0.3527 ) );
566  QFont scaledFont = pixelSizeFont;
567  scaledFont.setPixelSize( pixelSizeFont.pixelSize() * context.renderContext().rasterScaleFactor() );
568  p->setFont( scaledFont );
569 
570  QFontMetricsF fontMetrics( pixelSizeFont );
571  QPointF currentLabelShift; //considers the signs to determine the label position
572 
573  QList<QPointF>::const_iterator labelPosIt = labelShifts.constBegin();
574  QStringList::const_iterator text_it = labelList.constBegin();
575 
576  for ( ; labelPosIt != labelShifts.constEnd() && text_it != labelList.constEnd(); ++labelPosIt, ++text_it )
577  {
578  currentLabelShift = *labelPosIt;
579  if ( currentLabelShift.x() < 0 )
580  {
581  currentLabelShift.setX( currentLabelShift.x() - fontMetrics.width( *text_it ) );
582  }
583  if ( currentLabelShift.y() > 0 )
584  {
585  currentLabelShift.setY( currentLabelShift.y() + fontMetrics.ascent() );
586  }
587 
588  QPointF drawingPoint( centerPoint + currentLabelShift );
589  p->save();
590  p->translate( drawingPoint.x(), drawingPoint.y() );
591  p->scale( 1.0 / context.renderContext().rasterScaleFactor(), 1.0 / context.renderContext().rasterScaleFactor() );
592  p->drawText( QPointF( 0, 0 ), *text_it );
593  p->restore();
594  }
595 }
596 
598 {
599  if ( !r )
600  {
601  return 0;
602  }
603 
604  QgsSymbolV2List symbolList = r->symbolsForFeature( f );
605  if ( symbolList.size() < 1 )
606  {
607  return 0;
608  }
609 
610  return symbolList.at( 0 );
611 }