QGIS API Documentation  2.99.0-Master (37c43df)
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 "qgssymbollayerutils.h"
20 #include "qgsfontutils.h"
21 #include "qgspainteffectregistry.h"
22 #include "qgspainteffect.h"
24 
25 #include <QPainter>
26 #include <cmath>
27 
28 #ifndef M_SQRT2
29 #define M_SQRT2 1.41421356237309504880
30 #endif
31 
33  : QgsPointDistanceRenderer( QStringLiteral( "pointDisplacement" ), labelAttributeName )
34  , mPlacement( Ring )
35  , mCircleWidth( 0.4 )
36  , mCircleColor( QColor( 125, 125, 125 ) )
37  , mCircleRadiusAddition( 0 )
38 {
39  mCenterSymbol.reset( new QgsMarkerSymbol() );
40 }
41 
43 {
45  if ( mRenderer )
46  r->setEmbeddedRenderer( mRenderer->clone() );
47  r->setCircleWidth( mCircleWidth );
48  r->setCircleColor( mCircleColor );
51  r->setPlacement( mPlacement );
52  r->setCircleRadiusAddition( mCircleRadiusAddition );
57  if ( mCenterSymbol )
58  {
59  r->setCenterSymbol( mCenterSymbol->clone() );
60  }
61  copyRendererData( r );
62  return r;
63 }
64 
65 void QgsPointDisplacementRenderer::drawGroup( QPointF centerPoint, QgsRenderContext& context, const ClusteredGroup& group )
66 {
67 
68  //calculate max diagonal size from all symbols in group
69  double diagonal = 0;
70 
71  Q_FOREACH ( const GroupedFeature& feature, group )
72  {
73  if ( QgsMarkerSymbol* symbol = feature.symbol )
74  {
75  diagonal = qMax( diagonal, QgsSymbolLayerUtils::convertToPainterUnits( context,
76  M_SQRT2 * symbol->size(),
77  symbol->sizeUnit(), symbol->sizeMapUnitScale() ) );
78  }
79  }
80 
81  QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderMillimeters, 1.0, false );
82 
83  QList<QPointF> symbolPositions;
84  QList<QPointF> labelPositions;
85  double circleRadius = -1.0;
86  calculateSymbolAndLabelPositions( symbolContext, centerPoint, group.size(), diagonal, symbolPositions, labelPositions, circleRadius );
87 
88  //draw circle
89  if ( circleRadius > 0 )
90  drawCircle( circleRadius, symbolContext, centerPoint, group.size() );
91 
92  if ( group.size() > 1 )
93  {
94  //draw mid point
95  QgsFeature firstFeature = group.at( 0 ).feature;
96  if ( mCenterSymbol )
97  {
98  mCenterSymbol->renderPoint( centerPoint, &firstFeature, context, -1, false );
99  }
100  else
101  {
102  context.painter()->drawRect( QRectF( centerPoint.x() - symbolContext.outputLineWidth( 1 ), centerPoint.y() - symbolContext.outputLineWidth( 1 ), symbolContext.outputLineWidth( 2 ), symbolContext.outputLineWidth( 2 ) ) );
103  }
104  }
105 
106  //draw symbols on the circle
107  drawSymbols( group, context, symbolPositions );
108  //and also the labels
109  if ( mLabelIndex >= 0 )
110  {
111  drawLabels( centerPoint, symbolContext, labelPositions, group );
112  }
113 }
114 
115 
117 {
118  if ( mCenterSymbol )
119  {
120  mCenterSymbol->startRender( context, fields );
121  }
122 
123  QgsPointDistanceRenderer::startRender( context, fields );
124 }
125 
127 {
129  if ( mCenterSymbol )
130  {
131  mCenterSymbol->stopRender( context );
132  }
133 }
134 
136 {
138  r->setLabelAttributeName( symbologyElem.attribute( QStringLiteral( "labelAttributeName" ) ) );
139  QFont labelFont;
140  if ( !QgsFontUtils::setFromXmlChildNode( labelFont, symbologyElem, QStringLiteral( "labelFontProperties" ) ) )
141  {
142  labelFont.fromString( symbologyElem.attribute( QStringLiteral( "labelFont" ), QLatin1String( "" ) ) );
143  }
144  r->setLabelFont( labelFont );
145  r->setPlacement( static_cast< Placement >( symbologyElem.attribute( QStringLiteral( "placement" ), QStringLiteral( "0" ) ).toInt() ) );
146  r->setCircleWidth( symbologyElem.attribute( QStringLiteral( "circleWidth" ), QStringLiteral( "0.4" ) ).toDouble() );
147  r->setCircleColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "circleColor" ), QLatin1String( "" ) ) ) );
148  r->setLabelColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "labelColor" ), QLatin1String( "" ) ) ) );
149  r->setCircleRadiusAddition( symbologyElem.attribute( QStringLiteral( "circleRadiusAddition" ), QStringLiteral( "0.0" ) ).toDouble() );
150  r->setMaxLabelScaleDenominator( symbologyElem.attribute( QStringLiteral( "maxLabelScaleDenominator" ), QStringLiteral( "-1" ) ).toDouble() );
151  r->setTolerance( symbologyElem.attribute( QStringLiteral( "tolerance" ), QStringLiteral( "0.00001" ) ).toDouble() );
152  r->setToleranceUnit( QgsUnitTypes::decodeRenderUnit( symbologyElem.attribute( QStringLiteral( "toleranceUnit" ), QStringLiteral( "MapUnit" ) ) ) );
153  r->setToleranceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( symbologyElem.attribute( QStringLiteral( "toleranceUnitScale" ) ) ) );
154 
155  //look for an embedded renderer <renderer-v2>
156  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( QStringLiteral( "renderer-v2" ) );
157  if ( !embeddedRendererElem.isNull() )
158  {
159  r->setEmbeddedRenderer( QgsFeatureRenderer::load( embeddedRendererElem ) );
160  }
161 
162  //center symbol
163  QDomElement centerSymbolElem = symbologyElem.firstChildElement( QStringLiteral( "symbol" ) );
164  if ( !centerSymbolElem.isNull() )
165  {
166  r->setCenterSymbol( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( centerSymbolElem ) );
167  }
168  return r;
169 }
170 
172 {
173  return mCenterSymbol.data();
174 }
175 
176 QDomElement QgsPointDisplacementRenderer::save( QDomDocument& doc )
177 {
178  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
179  rendererElement.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
180  rendererElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "pointDisplacement" ) );
181  rendererElement.setAttribute( QStringLiteral( "labelAttributeName" ), mLabelAttributeName );
182  rendererElement.appendChild( QgsFontUtils::toXmlElement( mLabelFont, doc, QStringLiteral( "labelFontProperties" ) ) );
183  rendererElement.setAttribute( QStringLiteral( "circleWidth" ), QString::number( mCircleWidth ) );
184  rendererElement.setAttribute( QStringLiteral( "circleColor" ), QgsSymbolLayerUtils::encodeColor( mCircleColor ) );
185  rendererElement.setAttribute( QStringLiteral( "labelColor" ), QgsSymbolLayerUtils::encodeColor( mLabelColor ) );
186  rendererElement.setAttribute( QStringLiteral( "circleRadiusAddition" ), QString::number( mCircleRadiusAddition ) );
187  rendererElement.setAttribute( QStringLiteral( "placement" ), static_cast< int >( mPlacement ) );
188  rendererElement.setAttribute( QStringLiteral( "maxLabelScaleDenominator" ), QString::number( mMaxLabelScaleDenominator ) );
189  rendererElement.setAttribute( QStringLiteral( "tolerance" ), QString::number( mTolerance ) );
190  rendererElement.setAttribute( QStringLiteral( "toleranceUnit" ), QgsUnitTypes::encodeUnit( mToleranceUnit ) );
191  rendererElement.setAttribute( QStringLiteral( "toleranceUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mToleranceMapUnitScale ) );
192 
193  if ( mRenderer )
194  {
195  QDomElement embeddedRendererElem = mRenderer->save( doc );
196  rendererElement.appendChild( embeddedRendererElem );
197  }
198  if ( mCenterSymbol )
199  {
200  QDomElement centerSymbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "centerSymbol" ), mCenterSymbol.data(), doc );
201  rendererElement.appendChild( centerSymbolElem );
202  }
203 
205  mPaintEffect->saveProperties( doc, rendererElement );
206 
207  if ( !mOrderBy.isEmpty() )
208  {
209  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
210  mOrderBy.save( orderBy );
211  rendererElement.appendChild( orderBy );
212  }
213  rendererElement.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
214 
215  return rendererElement;
216 }
217 
219 {
220  QSet<QString> attr = QgsPointDistanceRenderer::usedAttributes();
221  if ( mCenterSymbol )
222  attr.unite( mCenterSymbol->usedAttributes() );
223  return attr;
224 }
225 
227 {
228  mCenterSymbol.reset( symbol );
229 }
230 
231 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolRenderContext& symbolContext, QPointF centerPoint, int nPosition,
232  double symbolDiagonal, QList<QPointF>& symbolPositions, QList<QPointF>& labelShifts, double& circleRadius ) const
233 {
234  symbolPositions.clear();
235  labelShifts.clear();
236 
237  if ( nPosition < 1 )
238  {
239  return;
240  }
241  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
242  {
243  symbolPositions.append( centerPoint );
244  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
245  return;
246  }
247 
248  double circleAdditionPainterUnits = symbolContext.outputLineWidth( mCircleRadiusAddition );
249 
250  switch ( mPlacement )
251  {
252  case Ring:
253  {
254  double minDiameterToFitSymbols = nPosition * symbolDiagonal / ( 2.0 * M_PI );
255  double radius = qMax( symbolDiagonal / 2, minDiameterToFitSymbols ) + circleAdditionPainterUnits;
256 
257  double fullPerimeter = 2 * M_PI;
258  double angleStep = fullPerimeter / nPosition;
259  for ( double currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
260  {
261  double sinusCurrentAngle = sin( currentAngle );
262  double cosinusCurrentAngle = cos( currentAngle );
263  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
264  QPointF labelShift(( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
265  symbolPositions.append( centerPoint + positionShift );
266  labelShifts.append( labelShift );
267  }
268 
269  circleRadius = radius;
270  break;
271  }
272  case ConcentricRings:
273  {
274  double centerDiagonal = QgsSymbolLayerUtils::convertToPainterUnits( symbolContext.renderContext(),
275  M_SQRT2 * mCenterSymbol->size(),
276  mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
277 
278  int pointsRemaining = nPosition;
279  int ringNumber = 1;
280  double firstRingRadius = centerDiagonal / 2.0 + symbolDiagonal / 2.0;
281  while ( pointsRemaining > 0 )
282  {
283  double radiusCurrentRing = qMax( firstRingRadius + ( ringNumber - 1 ) * symbolDiagonal + ringNumber * circleAdditionPainterUnits, 0.0 );
284  int maxPointsCurrentRing = qMax( floor( 2 * M_PI * radiusCurrentRing / symbolDiagonal ), 1.0 );
285  int actualPointsCurrentRing = qMin( maxPointsCurrentRing, pointsRemaining );
286 
287  double angleStep = 2 * M_PI / actualPointsCurrentRing;
288  double currentAngle = 0.0;
289  for ( int i = 0; i < actualPointsCurrentRing; ++i )
290  {
291  double sinusCurrentAngle = sin( currentAngle );
292  double cosinusCurrentAngle = cos( currentAngle );
293  QPointF positionShift( radiusCurrentRing * sinusCurrentAngle, radiusCurrentRing * cosinusCurrentAngle );
294  QPointF labelShift(( radiusCurrentRing + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radiusCurrentRing + symbolDiagonal / 2 ) * cosinusCurrentAngle );
295  symbolPositions.append( centerPoint + positionShift );
296  labelShifts.append( labelShift );
297  currentAngle += angleStep;
298  }
299 
300  pointsRemaining -= actualPointsCurrentRing;
301  ringNumber++;
302  circleRadius = radiusCurrentRing;
303  }
304  break;
305  }
306  }
307 }
308 
309 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolRenderContext& context, QPointF centerPoint, int nSymbols )
310 {
311  QPainter* p = context.renderContext().painter();
312  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
313  {
314  return;
315  }
316 
317  //draw Circle
318  QPen circlePen( mCircleColor );
319  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
320  p->setPen( circlePen );
321  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
322 }
323 
324 void QgsPointDisplacementRenderer::drawSymbols( const ClusteredGroup& group, QgsRenderContext& context, const QList<QPointF>& symbolPositions )
325 {
326  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
327  ClusteredGroup::const_iterator groupIt = group.constBegin();
328  for ( ; symbolPosIt != symbolPositions.constEnd() && groupIt != group.constEnd();
329  ++symbolPosIt, ++groupIt )
330  {
331  context.expressionContext().setFeature( groupIt->feature );
332  groupIt->symbol->startRender( context );
333  groupIt->symbol->renderPoint( *symbolPosIt, &( groupIt->feature ), context, -1, groupIt->isSelected );
334  groupIt->symbol->stopRender( context );
335  }
336 }
337 
339 {
340  if ( renderer->type() == QLatin1String( "pointDisplacement" ) )
341  {
342  return dynamic_cast<QgsPointDisplacementRenderer*>( renderer->clone() );
343  }
344  else if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
345  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
346  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
347  renderer->type() == QLatin1String( "RuleRenderer" ) )
348  {
350  pointRenderer->setEmbeddedRenderer( renderer->clone() );
351  return pointRenderer;
352  }
353  else if ( renderer->type() == QLatin1String( "pointCluster" ) )
354  {
356  const QgsPointClusterRenderer* clusterRenderer = static_cast< const QgsPointClusterRenderer* >( renderer );
357  if ( clusterRenderer->embeddedRenderer() )
358  pointRenderer->setEmbeddedRenderer( clusterRenderer->embeddedRenderer()->clone() );
359  pointRenderer->setTolerance( clusterRenderer->tolerance() );
360  pointRenderer->setToleranceUnit( clusterRenderer->toleranceUnit() );
361  pointRenderer->setToleranceMapUnitScale( clusterRenderer->toleranceMapUnitScale() );
362  if ( const_cast< QgsPointClusterRenderer* >( clusterRenderer )->clusterSymbol() )
363  pointRenderer->setCenterSymbol( const_cast< QgsPointClusterRenderer* >( clusterRenderer )->clusterSymbol()->clone() );
364  return pointRenderer;
365  }
366  else
367  {
368  return nullptr;
369  }
370 }
An abstract base class for distance based point renderers (eg clusterer and displacement renderers)...
QString mLabelAttributeName
Attribute name for labeling. An empty string indicates that no labels should be rendered.
QgsMarkerSymbol * centerSymbol()
Returns the symbol for the center of a displacement group (but not ownership of the symbol)...
QgsPointDisplacementRenderer * clone() const override
Create a deep copy of this renderer.
QgsUnitTypes::RenderUnit mToleranceUnit
Unit for distance tolerance.
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:448
QScopedPointer< QgsFeatureRenderer > mRenderer
Embedded base renderer. This can be used for rendering individual, isolated points.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc)
virtual QSet< QString > usedAttributes() const override
Return a list of attributes required by this renderer.
QList< GroupedFeature > ClusteredGroup
A group of clustered points (ie features within the distance tolerance).
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void setLabelFont(const QFont &font)
Sets the font used for labeling points.
QgsFeatureRequest::OrderBy orderBy() const
Get the order in which features shall be processed by this renderer.
void setTolerance(double distance)
Sets the tolerance distance for grouping points.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
void setLabelColor(const QColor &color)
Sets the color to use for for labeling points.
void setToleranceMapUnitScale(const QgsMapUnitScale &scale)
Sets the map unit scale object for the distance tolerance.
void setToleranceUnit(QgsUnitTypes::RenderUnit unit)
Sets the units for the tolerance distance.
static QgsFeatureRenderer * load(QDomElement &symbologyElem)
create a renderer from XML element
double outputLineWidth(double width) const
Definition: qgssymbol.cpp:1012
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
Container of fields for a vector layer.
Definition: qgsfields.h:36
void stopRender(QgsRenderContext &context) override
Needs to be called when a render cycle has finished to clean up.
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:49
QDomElement save(QDomDocument &doc) override
store renderer info to XML element
double mMaxLabelScaleDenominator
Maximum scale denominator for label display. A negative number indicatese no scale limitation...
QgsPaintEffect * mPaintEffect
Definition: qgsrenderer.h:434
virtual QSet< QString > usedAttributes() const override
Return a list of attributes required by this renderer.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:135
virtual void startRender(QgsRenderContext &context, const QgsFields &fields) override
Needs to be called when a new render cycle is started.
void setCenterSymbol(QgsMarkerSymbol *symbol)
Sets the center symbol for a displacement group.
QgsUnitTypes::RenderUnit toleranceUnit() const
Returns the units for the tolerance distance.
void drawLabels(QPointF centerPoint, QgsSymbolRenderContext &context, const QList< QPointF > &labelShifts, const ClusteredGroup &group)
Renders the labels for a group.
static QString encodeColor(const QColor &color)
static QgsFeatureRenderer * create(QDomElement &symbologyElem)
Create a renderer from XML element.
QgsPointDisplacementRenderer(const QString &labelAttributeName=QString())
Constructor for QgsPointDisplacementRenderer.
QFont labelFont() const
Returns the font used for labeling points.
QString type() const
Definition: qgsrenderer.h:92
double mTolerance
Distance tolerance. Points that are closer together than this distance are considered clustered...
void setCircleColor(const QColor &color)
Sets the color used for drawing the displacement group circle.
void setLabelAttributeName(const QString &name)
Sets the attribute name for labeling points.
A renderer that automatically clusters points with the same geographic position.
#define M_PI
Place points in concentric rings around group.
static bool setFromXmlChildNode(QFont &font, const QDomElement &element, const QString &childNode)
Sets the properties of a font to match the properties stored in an XML child node.
void setPlacement(Placement placement)
Sets the placement method used for dispersing the points.
QColor mLabelColor
Label text color.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields) override
Needs to be called when a new render cycle is started.
void setMaxLabelScaleDenominator(double denominator)
Sets the maximum scale at which points should be labeled by the renderer.
QgsRenderContext & renderContext()
Definition: qgssymbol.h:393
QgsMapUnitScale mToleranceMapUnitScale
Map unit scale for distance tolerance.
QgsMarkerSymbol * symbol
Base symbol for rendering feature.
double tolerance() const
Returns the tolerance distance for grouping points.
void stopRender(QgsRenderContext &context) override
Needs to be called when a render cycle has finished to clean up.
const QgsMapUnitScale & toleranceMapUnitScale() const
Returns the map unit scale object for the distance tolerance.
static Q_INVOKABLE QString encodeUnit(QgsUnitTypes::DistanceUnit unit)
Encodes a distance unit to a string.
Place points in a single ring around group.
QgsExpressionContext & expressionContext()
Gets the expression context.
Contains properties for a feature within a clustered group.
A renderer that automatically displaces points with the same geographic location. ...
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
Contains information about the context of a rendering operation.
QPainter * painter()
void CORE_EXPORT save(QDomElement &elem) const
Serialize to XML.
static Q_INVOKABLE RenderUnit decodeRenderUnit(const QString &string, bool *ok=0)
Decodes a render unit from a string.
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:48
static QgsMapUnitScale decodeMapUnitScale(const QString &str)
static QDomElement toXmlElement(const QFont &font, QDomDocument &document, const QString &elementName)
Returns a DOM element containing the properties of the font.
int mLabelIndex
Label attribute index (or -1 if none). This index is not stored, it is requested in the startRender()...
void setCircleRadiusAddition(double distance)
Sets a factor for increasing the ring size of displacement groups.
void setEmbeddedRenderer(QgsFeatureRenderer *r) override
Sets an embedded renderer (subrenderer) for this feature renderer.
static double convertToPainterUnits(const QgsRenderContext &c, double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale())
Converts a size from the specied units to painter units.
static QgsPointDisplacementRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
Creates a QgsPointDisplacementRenderer from an existing renderer.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
void setCircleWidth(double width)
Sets the line width for the displacement group circle.
static QColor decodeColor(const QString &str)
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.