QGIS API Documentation  2.99.0-Master (53aba61)
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 
29  : QgsPointDistanceRenderer( QStringLiteral( "pointDisplacement" ), labelAttributeName )
30  , mPlacement( Ring )
31  , mCircleWidth( 0.4 )
32  , mCircleColor( QColor( 125, 125, 125 ) )
33  , mCircleRadiusAddition( 0 )
34 {
35  mCenterSymbol.reset( new QgsMarkerSymbol() );
36 }
37 
39 {
41  if ( mRenderer )
42  r->setEmbeddedRenderer( mRenderer->clone() );
43  r->setCircleWidth( mCircleWidth );
44  r->setCircleColor( mCircleColor );
47  r->setPlacement( mPlacement );
48  r->setCircleRadiusAddition( mCircleRadiusAddition );
53  if ( mCenterSymbol )
54  {
55  r->setCenterSymbol( mCenterSymbol->clone() );
56  }
57  copyRendererData( r );
58  return r;
59 }
60 
61 void QgsPointDisplacementRenderer::drawGroup( QPointF centerPoint, QgsRenderContext &context, const ClusteredGroup &group )
62 {
63 
64  //calculate max diagonal size from all symbols in group
65  double diagonal = 0;
66 
67  Q_FOREACH ( const GroupedFeature &feature, group )
68  {
69  if ( QgsMarkerSymbol *symbol = feature.symbol() )
70  {
71  diagonal = std::max( diagonal, context.convertToPainterUnits( M_SQRT2 * symbol->size(),
72  symbol->sizeUnit(), symbol->sizeMapUnitScale() ) );
73  }
74  }
75 
76  QgsSymbolRenderContext symbolContext( context, QgsUnitTypes::RenderMillimeters, 1.0, false );
77 
78  QList<QPointF> symbolPositions;
79  QList<QPointF> labelPositions;
80  double circleRadius = -1.0;
81  calculateSymbolAndLabelPositions( symbolContext, centerPoint, group.size(), diagonal, symbolPositions, labelPositions, circleRadius );
82 
83  //draw circle
84  if ( circleRadius > 0 )
85  drawCircle( circleRadius, symbolContext, centerPoint, group.size() );
86 
87  if ( group.size() > 1 )
88  {
89  //draw mid point
90  QgsFeature firstFeature = group.at( 0 ).feature;
91  if ( mCenterSymbol )
92  {
93  mCenterSymbol->renderPoint( centerPoint, &firstFeature, context, -1, false );
94  }
95  else
96  {
97  context.painter()->drawRect( QRectF( centerPoint.x() - symbolContext.outputLineWidth( 1 ), centerPoint.y() - symbolContext.outputLineWidth( 1 ), symbolContext.outputLineWidth( 2 ), symbolContext.outputLineWidth( 2 ) ) );
98  }
99  }
100 
101  //draw symbols on the circle
102  drawSymbols( group, context, symbolPositions );
103  //and also the labels
104  if ( mLabelIndex >= 0 )
105  {
106  drawLabels( centerPoint, symbolContext, labelPositions, group );
107  }
108 }
109 
110 
112 {
113  if ( mCenterSymbol )
114  {
115  mCenterSymbol->startRender( context, fields );
116  }
117 
118  QgsPointDistanceRenderer::startRender( context, fields );
119 }
120 
122 {
124  if ( mCenterSymbol )
125  {
126  mCenterSymbol->stopRender( context );
127  }
128 }
129 
131 {
133  r->setLabelAttributeName( symbologyElem.attribute( QStringLiteral( "labelAttributeName" ) ) );
134  QFont labelFont;
135  if ( !QgsFontUtils::setFromXmlChildNode( labelFont, symbologyElem, QStringLiteral( "labelFontProperties" ) ) )
136  {
137  labelFont.fromString( symbologyElem.attribute( QStringLiteral( "labelFont" ), QLatin1String( "" ) ) );
138  }
139  r->setLabelFont( labelFont );
140  r->setPlacement( static_cast< Placement >( symbologyElem.attribute( QStringLiteral( "placement" ), QStringLiteral( "0" ) ).toInt() ) );
141  r->setCircleWidth( symbologyElem.attribute( QStringLiteral( "circleWidth" ), QStringLiteral( "0.4" ) ).toDouble() );
142  r->setCircleColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "circleColor" ), QLatin1String( "" ) ) ) );
143  r->setLabelColor( QgsSymbolLayerUtils::decodeColor( symbologyElem.attribute( QStringLiteral( "labelColor" ), QLatin1String( "" ) ) ) );
144  r->setCircleRadiusAddition( symbologyElem.attribute( QStringLiteral( "circleRadiusAddition" ), QStringLiteral( "0.0" ) ).toDouble() );
145  r->setMinimumLabelScale( symbologyElem.attribute( QStringLiteral( "maxLabelScaleDenominator" ), QStringLiteral( "-1" ) ).toDouble() );
146  r->setTolerance( symbologyElem.attribute( QStringLiteral( "tolerance" ), QStringLiteral( "0.00001" ) ).toDouble() );
147  r->setToleranceUnit( QgsUnitTypes::decodeRenderUnit( symbologyElem.attribute( QStringLiteral( "toleranceUnit" ), QStringLiteral( "MapUnit" ) ) ) );
148  r->setToleranceMapUnitScale( QgsSymbolLayerUtils::decodeMapUnitScale( symbologyElem.attribute( QStringLiteral( "toleranceUnitScale" ) ) ) );
149 
150  //look for an embedded renderer <renderer-v2>
151  QDomElement embeddedRendererElem = symbologyElem.firstChildElement( QStringLiteral( "renderer-v2" ) );
152  if ( !embeddedRendererElem.isNull() )
153  {
154  r->setEmbeddedRenderer( QgsFeatureRenderer::load( embeddedRendererElem, context ) );
155  }
156 
157  //center symbol
158  QDomElement centerSymbolElem = symbologyElem.firstChildElement( QStringLiteral( "symbol" ) );
159  if ( !centerSymbolElem.isNull() )
160  {
161  r->setCenterSymbol( QgsSymbolLayerUtils::loadSymbol<QgsMarkerSymbol>( centerSymbolElem, context ) );
162  }
163  return r;
164 }
165 
167 {
168  return mCenterSymbol.get();
169 }
170 
171 QDomElement QgsPointDisplacementRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
172 {
173  QDomElement rendererElement = doc.createElement( RENDERER_TAG_NAME );
174  rendererElement.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? "1" : "0" ) );
175  rendererElement.setAttribute( QStringLiteral( "type" ), QStringLiteral( "pointDisplacement" ) );
176  rendererElement.setAttribute( QStringLiteral( "labelAttributeName" ), mLabelAttributeName );
177  rendererElement.appendChild( QgsFontUtils::toXmlElement( mLabelFont, doc, QStringLiteral( "labelFontProperties" ) ) );
178  rendererElement.setAttribute( QStringLiteral( "circleWidth" ), QString::number( mCircleWidth ) );
179  rendererElement.setAttribute( QStringLiteral( "circleColor" ), QgsSymbolLayerUtils::encodeColor( mCircleColor ) );
180  rendererElement.setAttribute( QStringLiteral( "labelColor" ), QgsSymbolLayerUtils::encodeColor( mLabelColor ) );
181  rendererElement.setAttribute( QStringLiteral( "circleRadiusAddition" ), QString::number( mCircleRadiusAddition ) );
182  rendererElement.setAttribute( QStringLiteral( "placement" ), static_cast< int >( mPlacement ) );
183  rendererElement.setAttribute( QStringLiteral( "maxLabelScaleDenominator" ), QString::number( mMinLabelScale ) );
184  rendererElement.setAttribute( QStringLiteral( "tolerance" ), QString::number( mTolerance ) );
185  rendererElement.setAttribute( QStringLiteral( "toleranceUnit" ), QgsUnitTypes::encodeUnit( mToleranceUnit ) );
186  rendererElement.setAttribute( QStringLiteral( "toleranceUnitScale" ), QgsSymbolLayerUtils::encodeMapUnitScale( mToleranceMapUnitScale ) );
187 
188  if ( mRenderer )
189  {
190  QDomElement embeddedRendererElem = mRenderer->save( doc, context );
191  rendererElement.appendChild( embeddedRendererElem );
192  }
193  if ( mCenterSymbol )
194  {
195  QDomElement centerSymbolElem = QgsSymbolLayerUtils::saveSymbol( QStringLiteral( "centerSymbol" ), mCenterSymbol.get(), doc, context );
196  rendererElement.appendChild( centerSymbolElem );
197  }
198 
200  mPaintEffect->saveProperties( doc, rendererElement );
201 
202  if ( !mOrderBy.isEmpty() )
203  {
204  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
205  mOrderBy.save( orderBy );
206  rendererElement.appendChild( orderBy );
207  }
208  rendererElement.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? "1" : "0" ) );
209 
210  return rendererElement;
211 }
212 
214 {
215  QSet<QString> attr = QgsPointDistanceRenderer::usedAttributes( context );
216  if ( mCenterSymbol )
217  attr.unite( mCenterSymbol->usedAttributes( context ) );
218  return attr;
219 }
220 
222 {
223  mCenterSymbol.reset( symbol );
224 }
225 
226 void QgsPointDisplacementRenderer::calculateSymbolAndLabelPositions( QgsSymbolRenderContext &symbolContext, QPointF centerPoint, int nPosition,
227  double symbolDiagonal, QList<QPointF> &symbolPositions, QList<QPointF> &labelShifts, double &circleRadius ) const
228 {
229  symbolPositions.clear();
230  labelShifts.clear();
231 
232  if ( nPosition < 1 )
233  {
234  return;
235  }
236  else if ( nPosition == 1 ) //If there is only one feature, draw it exactly at the center position
237  {
238  symbolPositions.append( centerPoint );
239  labelShifts.append( QPointF( symbolDiagonal / 2.0, -symbolDiagonal / 2.0 ) );
240  return;
241  }
242 
243  double circleAdditionPainterUnits = symbolContext.outputLineWidth( mCircleRadiusAddition );
244 
245  switch ( mPlacement )
246  {
247  case Ring:
248  {
249  double minDiameterToFitSymbols = nPosition * symbolDiagonal / ( 2.0 * M_PI );
250  double radius = std::max( symbolDiagonal / 2, minDiameterToFitSymbols ) + circleAdditionPainterUnits;
251 
252  double fullPerimeter = 2 * M_PI;
253  double angleStep = fullPerimeter / nPosition;
254  for ( double currentAngle = 0.0; currentAngle < fullPerimeter; currentAngle += angleStep )
255  {
256  double sinusCurrentAngle = std::sin( currentAngle );
257  double cosinusCurrentAngle = std::cos( currentAngle );
258  QPointF positionShift( radius * sinusCurrentAngle, radius * cosinusCurrentAngle );
259  QPointF labelShift( ( radius + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radius + symbolDiagonal / 2 ) * cosinusCurrentAngle );
260  symbolPositions.append( centerPoint + positionShift );
261  labelShifts.append( labelShift );
262  }
263 
264  circleRadius = radius;
265  break;
266  }
267  case ConcentricRings:
268  {
269  double centerDiagonal = symbolContext.renderContext().convertToPainterUnits( M_SQRT2 * mCenterSymbol->size(),
270  mCenterSymbol->sizeUnit(), mCenterSymbol->sizeMapUnitScale() );
271 
272  int pointsRemaining = nPosition;
273  int ringNumber = 1;
274  double firstRingRadius = centerDiagonal / 2.0 + symbolDiagonal / 2.0;
275  while ( pointsRemaining > 0 )
276  {
277  double radiusCurrentRing = std::max( firstRingRadius + ( ringNumber - 1 ) * symbolDiagonal + ringNumber * circleAdditionPainterUnits, 0.0 );
278  int maxPointsCurrentRing = std::max( std::floor( 2 * M_PI * radiusCurrentRing / symbolDiagonal ), 1.0 );
279  int actualPointsCurrentRing = std::min( maxPointsCurrentRing, pointsRemaining );
280 
281  double angleStep = 2 * M_PI / actualPointsCurrentRing;
282  double currentAngle = 0.0;
283  for ( int i = 0; i < actualPointsCurrentRing; ++i )
284  {
285  double sinusCurrentAngle = std::sin( currentAngle );
286  double cosinusCurrentAngle = std::cos( currentAngle );
287  QPointF positionShift( radiusCurrentRing * sinusCurrentAngle, radiusCurrentRing * cosinusCurrentAngle );
288  QPointF labelShift( ( radiusCurrentRing + symbolDiagonal / 2 ) * sinusCurrentAngle, ( radiusCurrentRing + symbolDiagonal / 2 ) * cosinusCurrentAngle );
289  symbolPositions.append( centerPoint + positionShift );
290  labelShifts.append( labelShift );
291  currentAngle += angleStep;
292  }
293 
294  pointsRemaining -= actualPointsCurrentRing;
295  ringNumber++;
296  circleRadius = radiusCurrentRing;
297  }
298  break;
299  }
300  }
301 }
302 
303 void QgsPointDisplacementRenderer::drawCircle( double radiusPainterUnits, QgsSymbolRenderContext &context, QPointF centerPoint, int nSymbols )
304 {
305  QPainter *p = context.renderContext().painter();
306  if ( nSymbols < 2 || !p ) //draw circle only if multiple features
307  {
308  return;
309  }
310 
311  //draw Circle
312  QPen circlePen( mCircleColor );
313  circlePen.setWidthF( context.outputLineWidth( mCircleWidth ) );
314  p->setPen( circlePen );
315  p->drawArc( QRectF( centerPoint.x() - radiusPainterUnits, centerPoint.y() - radiusPainterUnits, 2 * radiusPainterUnits, 2 * radiusPainterUnits ), 0, 5760 );
316 }
317 
318 void QgsPointDisplacementRenderer::drawSymbols( const ClusteredGroup &group, QgsRenderContext &context, const QList<QPointF> &symbolPositions )
319 {
320  QList<QPointF>::const_iterator symbolPosIt = symbolPositions.constBegin();
321  ClusteredGroup::const_iterator groupIt = group.constBegin();
322  for ( ; symbolPosIt != symbolPositions.constEnd() && groupIt != group.constEnd();
323  ++symbolPosIt, ++groupIt )
324  {
325  context.expressionContext().setFeature( groupIt->feature );
326  groupIt->symbol()->startRender( context );
327  groupIt->symbol()->renderPoint( *symbolPosIt, &( groupIt->feature ), context, -1, groupIt->isSelected );
328  groupIt->symbol()->stopRender( context );
329  }
330 }
331 
333 {
334  if ( renderer->type() == QLatin1String( "pointDisplacement" ) )
335  {
336  return dynamic_cast<QgsPointDisplacementRenderer *>( renderer->clone() );
337  }
338  else if ( renderer->type() == QLatin1String( "singleSymbol" ) ||
339  renderer->type() == QLatin1String( "categorizedSymbol" ) ||
340  renderer->type() == QLatin1String( "graduatedSymbol" ) ||
341  renderer->type() == QLatin1String( "RuleRenderer" ) )
342  {
344  pointRenderer->setEmbeddedRenderer( renderer->clone() );
345  return pointRenderer;
346  }
347  else if ( renderer->type() == QLatin1String( "pointCluster" ) )
348  {
350  const QgsPointClusterRenderer *clusterRenderer = static_cast< const QgsPointClusterRenderer * >( renderer );
351  if ( clusterRenderer->embeddedRenderer() )
352  pointRenderer->setEmbeddedRenderer( clusterRenderer->embeddedRenderer()->clone() );
353  pointRenderer->setTolerance( clusterRenderer->tolerance() );
354  pointRenderer->setToleranceUnit( clusterRenderer->toleranceUnit() );
355  pointRenderer->setToleranceMapUnitScale( clusterRenderer->toleranceMapUnitScale() );
356  if ( const_cast< QgsPointClusterRenderer * >( clusterRenderer )->clusterSymbol() )
357  pointRenderer->setCenterSymbol( const_cast< QgsPointClusterRenderer * >( clusterRenderer )->clusterSymbol()->clone() );
358  return pointRenderer;
359  }
360  else
361  {
362  return nullptr;
363  }
364 }
An abstract base class for distance based point renderers (e.g., clusterer and displacement renderers...
The class is used as a container of context for various read/write operations on other objects...
QString mLabelAttributeName
Attribute name for labeling. An empty string indicates that no labels should be rendered.
static QgsFeatureRenderer * create(QDomElement &symbologyElem, const QgsReadWriteContext &context)
Create a renderer from XML element.
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:472
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.
double outputLineWidth(double width) const
Definition: qgssymbol.cpp:1027
static QString encodeMapUnitScale(const QgsMapUnitScale &mapUnitScale)
Container of fields for a vector layer.
Definition: qgsfields.h:41
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
QgsPaintEffect * mPaintEffect
Definition: qgsrenderer.h:458
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.
double mMinLabelScale
Maximum scale denominator for label display. A zero value indicates no scale limitation.
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)
QgsPointDisplacementRenderer(const QString &labelAttributeName=QString())
Constructor for QgsPointDisplacementRenderer.
QFont labelFont() const
Returns the font used for labeling points.
QString type() const
Definition: qgsrenderer.h:124
double mTolerance
Distance tolerance. Points that are closer together than this distance are considered clustered...
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
store renderer info to XML element
static QgsFeatureRenderer * load(QDomElement &symbologyElem, const QgsReadWriteContext &context)
create a renderer from XML element
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.
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.
QgsRenderContext & renderContext()
Definition: qgssymbol.h:425
QgsMapUnitScale mToleranceMapUnitScale
Map unit scale for distance tolerance.
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. ...
QgsMarkerSymbol * symbol() const
Base symbol for rendering feature.
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.
void setMinimumLabelScale(double scale)
Sets the minimum map scale (i.e.
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.
static QDomElement saveSymbol(const QString &symbolName, QgsSymbol *symbol, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a symbol definition to XML.
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:48
QList< QgsPointDistanceRenderer::GroupedFeature > ClusteredGroup
A group of clustered points (ie features within the distance tolerance).
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.