QGIS API Documentation  2.99.0-Master (716ff6c)
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, context.convertToPainterUnits( M_SQRT2 * symbol->size(),
76  symbol->sizeUnit(), symbol->sizeMapUnitScale() ) );
77  }
78  }
79 
80  QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderMillimeters, 1.0, false );
81 
82  QList<QPointF> symbolPositions;
83  QList<QPointF> labelPositions;
84  double circleRadius = -1.0;
85  calculateSymbolAndLabelPositions( symbolContext, centerPoint, group.size(), diagonal, symbolPositions, labelPositions, circleRadius );
86 
87  //draw circle
88  if ( circleRadius > 0 )
89  drawCircle( circleRadius, symbolContext, centerPoint, group.size() );
90 
91  if ( group.size() > 1 )
92  {
93  //draw mid point
94  QgsFeature firstFeature = group.at( 0 ).feature;
95  if ( mCenterSymbol )
96  {
97  mCenterSymbol->renderPoint( centerPoint, &firstFeature, context, -1, false );
98  }
99  else
100  {
101  context.painter()->drawRect( QRectF( centerPoint.x() - symbolContext.outputLineWidth( 1 ), centerPoint.y() - symbolContext.outputLineWidth( 1 ), symbolContext.outputLineWidth( 2 ), symbolContext.outputLineWidth( 2 ) ) );
102  }
103  }
104 
105  //draw symbols on the circle
106  drawSymbols( group, context, symbolPositions );
107  //and also the labels
108  if ( mLabelIndex >= 0 )
109  {
110  drawLabels( centerPoint, symbolContext, labelPositions, group );
111  }
112 }
113 
114 
116 {
117  if ( mCenterSymbol )
118  {
119  mCenterSymbol->startRender( context, fields );
120  }
121 
122  QgsPointDistanceRenderer::startRender( context, fields );
123 }
124 
126 {
128  if ( mCenterSymbol )
129  {
130  mCenterSymbol->stopRender( context );
131  }
132 }
133 
135 {
137  r->setLabelAttributeName( symbologyElem.attribute( QStringLiteral( "labelAttributeName" ) ) );
138  QFont labelFont;
139  if ( !QgsFontUtils::setFromXmlChildNode( labelFont, symbologyElem, QStringLiteral( "labelFontProperties" ) ) )
140  {
141  labelFont.fromString( symbologyElem.attribute( QStringLiteral( "labelFont" ), QLatin1String( "" ) ) );
142  }
143  r->setLabelFont( labelFont );
144  r->setPlacement( static_cast< Placement >( symbologyElem.attribute( QStringLiteral( "placement" ), QStringLiteral( "0" ) ).toInt() ) );
145  r->setCircleWidth( symbologyElem.attribute( QStringLiteral( "circleWidth" ), QStringLiteral( "0.4" ) ).toDouble() );
146  r->setCircleColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "circleColor" ), QLatin1String( "" ) ) ) );
147  r->setLabelColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "labelColor" ), QLatin1String( "" ) ) ) );
148  r->setCircleRadiusAddition( symbologyElem.attribute( QStringLiteral( "circleRadiusAddition" ), QStringLiteral( "0.0" ) ).toDouble() );
149  r->setMaxLabelScaleDenominator( symbologyElem.attribute( QStringLiteral( "maxLabelScaleDenominator" ), QStringLiteral( "-1" ) ).toDouble() );
150  r->setTolerance( symbologyElem.attribute( QStringLiteral( "tolerance" ), QStringLiteral( "0.00001" ) ).toDouble() );
151  r->setToleranceUnit( QgsUnitTypes::decodeRenderUnit( symbologyElem.attribute( QStringLiteral( "toleranceUnit" ), QStringLiteral( "MapUnit" ) ) ) );
152  r->setToleranceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( symbologyElem.attribute( QStringLiteral( "toleranceUnitScale" ) ) ) );
153 
154  //look for an embedded renderer <renderer-v2>
155  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( QStringLiteral( "renderer-v2" ) );
156  if ( !embeddedRendererElem.isNull() )
157  {
158  r->setEmbeddedRenderer( QgsFeatureRenderer::load( embeddedRendererElem ) );
159  }
160 
161  //center symbol
162  QDomElement centerSymbolElem = symbologyElem.firstChildElement( QStringLiteral( "symbol" ) );
163  if ( !centerSymbolElem.isNull() )
164  {
165  r->setCenterSymbol( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( centerSymbolElem ) );
166  }
167  return r;
168 }
169 
171 {
172  return mCenterSymbol.get();
173 }
174 
175 QDomElement QgsPointDisplacementRenderer::save( QDomDocument &doc )
176 {
177  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
178  rendererElement.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
179  rendererElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "pointDisplacement" ) );
180  rendererElement.setAttribute( QStringLiteral( "labelAttributeName" ), mLabelAttributeName );
181  rendererElement.appendChild( QgsFontUtils::toXmlElement( mLabelFont, doc, QStringLiteral( "labelFontProperties" ) ) );
182  rendererElement.setAttribute( QStringLiteral( "circleWidth" ), QString::number( mCircleWidth ) );
183  rendererElement.setAttribute( QStringLiteral( "circleColor" ), QgsSymbolLayerUtils::encodeColor( mCircleColor ) );
184  rendererElement.setAttribute( QStringLiteral( "labelColor" ), QgsSymbolLayerUtils::encodeColor( mLabelColor ) );
185  rendererElement.setAttribute( QStringLiteral( "circleRadiusAddition" ), QString::number( mCircleRadiusAddition ) );
186  rendererElement.setAttribute( QStringLiteral( "placement" ), static_cast< int >( mPlacement ) );
187  rendererElement.setAttribute( QStringLiteral( "maxLabelScaleDenominator" ), QString::number( mMaxLabelScaleDenominator ) );
188  rendererElement.setAttribute( QStringLiteral( "tolerance" ), QString::number( mTolerance ) );
189  rendererElement.setAttribute( QStringLiteral( "toleranceUnit" ), QgsUnitTypes::encodeUnit( mToleranceUnit ) );
190  rendererElement.setAttribute( QStringLiteral( "toleranceUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mToleranceMapUnitScale ) );
191 
192  if ( mRenderer )
193  {
194  QDomElement embeddedRendererElem = mRenderer->save( doc );
195  rendererElement.appendChild( embeddedRendererElem );
196  }
197  if ( mCenterSymbol )
198  {
199  QDomElement centerSymbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "centerSymbol" ), mCenterSymbol.get(), doc );
200  rendererElement.appendChild( centerSymbolElem );
201  }
202 
204  mPaintEffect->saveProperties( doc, rendererElement );
205 
206  if ( !mOrderBy.isEmpty() )
207  {
208  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
209  mOrderBy.save( orderBy );
210  rendererElement.appendChild( orderBy );
211  }
212  rendererElement.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
213 
214  return rendererElement;
215 }
216 
218 {
219  QSet<QString> attr = QgsPointDistanceRenderer::usedAttributes( context );
220  if ( mCenterSymbol )
221  attr.unite( mCenterSymbol->usedAttributes( context ) );
222  return attr;
223 }
224 
226 {
227  mCenterSymbol.reset( symbol );
228 }
229 
230 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolRenderContext &symbolContext, QPointF centerPoint, int nPosition,
231  double symbolDiagonal, QList<QPointF> &symbolPositions, QList<QPointF> &labelShifts, double &circleRadius ) const
232 {
233  symbolPositions.clear();
234  labelShifts.clear();
235 
236  if ( nPosition < 1 )
237  {
238  return;
239  }
240  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
241  {
242  symbolPositions.append( centerPoint );
243  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
244  return;
245  }
246 
247  double circleAdditionPainterUnits = symbolContext.outputLineWidth( mCircleRadiusAddition );
248 
249  switch ( mPlacement )
250  {
251  case Ring:
252  {
253  double minDiameterToFitSymbols = nPosition * symbolDiagonal / ( 2.0 * M_PI );
254  double radius = qMax( symbolDiagonal / 2, minDiameterToFitSymbols ) + circleAdditionPainterUnits;
255 
256  double fullPerimeter = 2 * M_PI;
257  double angleStep = fullPerimeter / nPosition;
258  for ( double currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
259  {
260  double sinusCurrentAngle = sin( currentAngle );
261  double cosinusCurrentAngle = cos( currentAngle );
262  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
263  QPointF labelShift( ( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
264  symbolPositions.append( centerPoint + positionShift );
265  labelShifts.append( labelShift );
266  }
267 
268  circleRadius = radius;
269  break;
270  }
271  case ConcentricRings:
272  {
273  double centerDiagonal = symbolContext.renderContext().convertToPainterUnits( M_SQRT2 * mCenterSymbol->size(),
274  mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
275 
276  int pointsRemaining = nPosition;
277  int ringNumber = 1;
278  double firstRingRadius = centerDiagonal / 2.0 + symbolDiagonal / 2.0;
279  while ( pointsRemaining > 0 )
280  {
281  double radiusCurrentRing = qMax( firstRingRadius + ( ringNumber - 1 ) * symbolDiagonal + ringNumber * circleAdditionPainterUnits, 0.0 );
282  int maxPointsCurrentRing = qMax( floor( 2 * M_PI * radiusCurrentRing / symbolDiagonal ), 1.0 );
283  int actualPointsCurrentRing = qMin( maxPointsCurrentRing, pointsRemaining );
284 
285  double angleStep = 2 * M_PI / actualPointsCurrentRing;
286  double currentAngle = 0.0;
287  for ( int i = 0; i < actualPointsCurrentRing; ++i )
288  {
289  double sinusCurrentAngle = sin( currentAngle );
290  double cosinusCurrentAngle = cos( currentAngle );
291  QPointF positionShift( radiusCurrentRing * sinusCurrentAngle, radiusCurrentRing * cosinusCurrentAngle );
292  QPointF labelShift( ( radiusCurrentRing + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radiusCurrentRing + symbolDiagonal / 2 ) * cosinusCurrentAngle );
293  symbolPositions.append( centerPoint + positionShift );
294  labelShifts.append( labelShift );
295  currentAngle += angleStep;
296  }
297 
298  pointsRemaining -= actualPointsCurrentRing;
299  ringNumber++;
300  circleRadius = radiusCurrentRing;
301  }
302  break;
303  }
304  }
305 }
306 
307 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolRenderContext &context, QPointF centerPoint, int nSymbols )
308 {
309  QPainter *p = context.renderContext().painter();
310  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
311  {
312  return;
313  }
314 
315  //draw Circle
316  QPen circlePen( mCircleColor );
317  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
318  p->setPen( circlePen );
319  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
320 }
321 
322 void QgsPointDisplacementRenderer::drawSymbols( const ClusteredGroup &group, QgsRenderContext &context, const QList<QPointF> &symbolPositions )
323 {
324  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
325  ClusteredGroup::const_iterator groupIt = group.constBegin();
326  for ( ; symbolPosIt != symbolPositions.constEnd() && groupIt != group.constEnd();
327  ++symbolPosIt, ++groupIt )
328  {
329  context.expressionContext().setFeature( groupIt->feature );
330  groupIt->symbol->startRender( context );
331  groupIt->symbol->renderPoint( *symbolPosIt, &( groupIt->feature ), context, -1, groupIt->isSelected );
332  groupIt->symbol->stopRender( context );
333  }
334 }
335 
337 {
338  if ( renderer->type() == QLatin1String( "pointDisplacement" ) )
339  {
340  return dynamic_cast<QgsPointDisplacementRenderer *>( renderer->clone() );
341  }
342  else if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
343  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
344  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
345  renderer->type() == QLatin1String( "RuleRenderer" ) )
346  {
348  pointRenderer->setEmbeddedRenderer( renderer->clone() );
349  return pointRenderer;
350  }
351  else if ( renderer->type() == QLatin1String( "pointCluster" ) )
352  {
354  const QgsPointClusterRenderer *clusterRenderer = static_cast< const QgsPointClusterRenderer * >( renderer );
355  if ( clusterRenderer->embeddedRenderer() )
356  pointRenderer->setEmbeddedRenderer( clusterRenderer->embeddedRenderer()->clone() );
357  pointRenderer->setTolerance( clusterRenderer->tolerance() );
358  pointRenderer->setToleranceUnit( clusterRenderer->toleranceUnit() );
359  pointRenderer->setToleranceMapUnitScale( clusterRenderer->toleranceMapUnitScale() );
360  if ( const_cast< QgsPointClusterRenderer * >( clusterRenderer )->clusterSymbol() )
361  pointRenderer->setCenterSymbol( const_cast< QgsPointClusterRenderer * >( clusterRenderer )->clusterSymbol()->clone() );
362  return pointRenderer;
363  }
364  else
365  {
366  return nullptr;
367  }
368 }
An abstract base class for distance based point renderers (e.g., 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:449
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc)
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:998
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
Container of fields for a vector layer.
Definition: qgsfields.h:39
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:50
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:435
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:61
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:93
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:396
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.
double convertToPainterUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to painter units (pixels).
QPainter * painter()
Returns the destination QPainter for the render operation.
std::unique_ptr< QgsFeatureRenderer > mRenderer
Embedded base renderer. This can be used for rendering individual, isolated points.
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()...
virtual QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Return a list of attributes required by this renderer.
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 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 QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Return a list of attributes required by this renderer.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.