QGIS API Documentation  3.0.2-Girona (307d082)
qgspointdistancerenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgspointdistancerenderer.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 "qgssymbollayerutils.h"
21 #include "qgsspatialindex.h"
22 #include "qgsmultipoint.h"
23 #include "qgslogger.h"
24 
25 #include <QDomElement>
26 #include <QPainter>
27 
28 #include <cmath>
29 
30 QgsPointDistanceRenderer::QgsPointDistanceRenderer( const QString &rendererName, const QString &labelAttributeName )
31  : QgsFeatureRenderer( rendererName )
32  , mLabelAttributeName( labelAttributeName )
33  , mLabelIndex( -1 )
34  , mTolerance( 3 )
35  , mToleranceUnit( QgsUnitTypes::RenderMillimeters )
36  , mDrawLabels( true )
37 
38 {
40 }
41 
42 void QgsPointDistanceRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
43 {
44  mRenderer->toSld( doc, element, props );
45 }
46 
47 
48 bool QgsPointDistanceRenderer::renderFeature( QgsFeature &feature, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker )
49 {
50  Q_UNUSED( drawVertexMarker );
51  Q_UNUSED( context );
52  Q_UNUSED( layer );
53 
54  /*
55  * IMPORTANT: This algorithm is ported to Python in the processing "Points Displacement" algorithm.
56  * Please port any changes/improvements to that algorithm too!
57  */
58 
59  //check if there is already a point at that position
60  if ( !feature.hasGeometry() )
61  return false;
62 
63  QgsMarkerSymbol *symbol = firstSymbolForFeature( feature, context );
64 
65  //if the feature has no symbol (e.g., no matching rule in a rule-based renderer), skip it
66  if ( !symbol )
67  return false;
68 
69  //point position in screen coords
70  QgsGeometry geom = feature.geometry();
71  QgsWkbTypes::Type geomType = geom.wkbType();
72  if ( QgsWkbTypes::flatType( geomType ) != QgsWkbTypes::Point )
73  {
74  //can only render point type
75  return false;
76  }
77 
78  QString label;
79  if ( mDrawLabels )
80  {
81  label = getLabel( feature );
82  }
83 
85  QgsFeature transformedFeature = feature;
86  if ( xform.isValid() )
87  {
88  geom.transform( xform );
89  transformedFeature.setGeometry( geom );
90  }
91 
92  double searchDistance = context.convertToMapUnits( mTolerance, mToleranceUnit, mToleranceMapUnitScale );
93  QgsPointXY point = transformedFeature.geometry().asPoint();
94  QList<QgsFeatureId> intersectList = mSpatialIndex->intersects( searchRect( point, searchDistance ) );
95  if ( intersectList.empty() )
96  {
97  mSpatialIndex->insertFeature( transformedFeature );
98  // create new group
99  ClusteredGroup newGroup;
100  newGroup << GroupedFeature( transformedFeature, symbol->clone(), selected, label );
101  mClusteredGroups.push_back( newGroup );
102  // add to group index
103  mGroupIndex.insert( transformedFeature.id(), mClusteredGroups.count() - 1 );
104  mGroupLocations.insert( transformedFeature.id(), point );
105  }
106  else
107  {
108  // find group with closest location to this point (may be more than one within search tolerance)
109  QgsFeatureId minDistFeatureId = intersectList.at( 0 );
110  double minDist = mGroupLocations.value( minDistFeatureId ).distance( point );
111  for ( int i = 1; i < intersectList.count(); ++i )
112  {
113  QgsFeatureId candidateId = intersectList.at( i );
114  double newDist = mGroupLocations.value( candidateId ).distance( point );
115  if ( newDist < minDist )
116  {
117  minDist = newDist;
118  minDistFeatureId = candidateId;
119  }
120  }
121 
122  int groupIdx = mGroupIndex[ minDistFeatureId ];
123  ClusteredGroup &group = mClusteredGroups[groupIdx];
124 
125  // calculate new centroid of group
126  QgsPointXY oldCenter = mGroupLocations.value( minDistFeatureId );
127  mGroupLocations[ minDistFeatureId ] = QgsPointXY( ( oldCenter.x() * group.size() + point.x() ) / ( group.size() + 1.0 ),
128  ( oldCenter.y() * group.size() + point.y() ) / ( group.size() + 1.0 ) );
129 
130  // add to a group
131  group << GroupedFeature( transformedFeature, symbol->clone(), selected, label );
132  // add to group index
133  mGroupIndex.insert( transformedFeature.id(), groupIdx );
134  }
135 
136  return true;
137 }
138 
139 void QgsPointDistanceRenderer::drawGroup( const ClusteredGroup &group, QgsRenderContext &context )
140 {
141  //calculate centroid of all points, this will be center of group
142  QgsMultiPoint *groupMultiPoint = new QgsMultiPoint();
143  Q_FOREACH ( const GroupedFeature &f, group )
144  {
145  groupMultiPoint->addGeometry( f.feature.geometry().constGet()->clone() );
146  }
147  QgsGeometry groupGeom( groupMultiPoint );
148  QgsGeometry centroid = groupGeom.centroid();
149  QPointF pt = centroid.asQPointF();
150  context.mapToPixel().transformInPlace( pt.rx(), pt.ry() );
151 
152  context.expressionContext().appendScope( createGroupScope( group ) );
153  drawGroup( pt, context, group );
154  delete context.expressionContext().popScope();
155 }
156 
158 {
159  mRenderer.reset( r );
160 }
161 
163 {
164  return mRenderer.get();
165 }
166 
167 void QgsPointDistanceRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
168 {
169  if ( !mRenderer )
170  return;
171 
172  mRenderer->setLegendSymbolItem( key, symbol );
173 }
174 
176 {
177  if ( !mRenderer )
178  return false;
179 
180  return mRenderer->legendSymbolItemsCheckable();
181 }
182 
184 {
185  if ( !mRenderer )
186  return false;
187 
188  return mRenderer->legendSymbolItemChecked( key );
189 }
190 
191 void QgsPointDistanceRenderer::checkLegendSymbolItem( const QString &key, bool state )
192 {
193  if ( !mRenderer )
194  return;
195 
196  mRenderer->checkLegendSymbolItem( key, state );
197 }
198 
200 {
201  if ( !mRenderer )
202  return QgsFeatureRenderer::filter( fields );
203  else
204  return mRenderer->filter( fields );
205 }
206 
207 QSet<QString> QgsPointDistanceRenderer::usedAttributes( const QgsRenderContext &context ) const
208 {
209  QSet<QString> attributeList;
210  if ( !mLabelAttributeName.isEmpty() )
211  {
212  attributeList.insert( mLabelAttributeName );
213  }
214  if ( mRenderer )
215  {
216  attributeList += mRenderer->usedAttributes( context );
217  }
218  return attributeList;
219 }
220 
221 QgsFeatureRenderer::Capabilities QgsPointDistanceRenderer::capabilities()
222 {
223  if ( !mRenderer )
224  {
225  return nullptr;
226  }
227  return mRenderer->capabilities();
228 }
229 
231 {
232  if ( !mRenderer )
233  {
234  return QgsSymbolList();
235  }
236  return mRenderer->symbols( context );
237 }
238 
240 {
241  if ( !mRenderer )
242  {
243  return nullptr;
244  }
245  return mRenderer->symbolForFeature( feature, context );
246 }
247 
249 {
250  if ( !mRenderer )
251  return nullptr;
252  return mRenderer->originalSymbolForFeature( feat, context );
253 }
254 
256 {
257  if ( !mRenderer )
258  {
259  return QgsSymbolList();
260  }
261  return mRenderer->symbolsForFeature( feature, context );
262 }
263 
265 {
266  if ( !mRenderer )
267  return QgsSymbolList();
268  return mRenderer->originalSymbolsForFeature( feat, context );
269 }
270 
272 {
273  if ( !mRenderer )
274  return QSet< QString >() << QString();
275  return mRenderer->legendKeysForFeature( feat, context );
276 }
277 
279 {
280  if ( !mRenderer )
281  {
282  return false;
283  }
284  return mRenderer->willRenderFeature( feat, context );
285 }
286 
287 
289 {
290  QgsFeatureRenderer::startRender( context, fields );
291 
292  mRenderer->startRender( context, fields );
293 
294  mClusteredGroups.clear();
295  mGroupIndex.clear();
296  mGroupLocations.clear();
298 
299  if ( mLabelAttributeName.isEmpty() )
300  {
301  mLabelIndex = -1;
302  }
303  else
304  {
306  }
307 
308  if ( mMinLabelScale <= 0 || context.rendererScale() < mMinLabelScale )
309  {
310  mDrawLabels = true;
311  }
312  else
313  {
314  mDrawLabels = false;
315  }
316 }
317 
319 {
321 
322  //printInfoDisplacementGroups(); //just for debugging
323 
324  Q_FOREACH ( const ClusteredGroup &group, mClusteredGroups )
325  {
326  drawGroup( group, context );
327  }
328 
329  mClusteredGroups.clear();
330  mGroupIndex.clear();
331  mGroupLocations.clear();
332  delete mSpatialIndex;
333  mSpatialIndex = nullptr;
334 
335  mRenderer->stopRender( context );
336 }
337 
339 {
340  if ( mRenderer )
341  {
342  return mRenderer->legendSymbolItems();
343  }
344  return QgsLegendSymbolList();
345 }
346 
347 QgsRectangle QgsPointDistanceRenderer::searchRect( const QgsPointXY &p, double distance ) const
348 {
349  return QgsRectangle( p.x() - distance, p.y() - distance, p.x() + distance, p.y() + distance );
350 }
351 
352 void QgsPointDistanceRenderer::printGroupInfo() const
353 {
354 #ifdef QGISDEBUG
355  int nGroups = mClusteredGroups.size();
356  QgsDebugMsg( "number of displacement groups:" + QString::number( nGroups ) );
357  for ( int i = 0; i < nGroups; ++i )
358  {
359  QgsDebugMsg( "***************displacement group " + QString::number( i ) );
360  Q_FOREACH ( const GroupedFeature &feature, mClusteredGroups.at( i ) )
361  {
362  QgsDebugMsg( FID_TO_STRING( feature.feature.id() ) );
363  }
364  }
365 #endif
366 }
367 
368 QString QgsPointDistanceRenderer::getLabel( const QgsFeature &feature ) const
369 {
370  QString attribute;
371  QgsAttributes attrs = feature.attributes();
372  if ( mLabelIndex >= 0 && mLabelIndex < attrs.count() )
373  {
374  attribute = attrs.at( mLabelIndex ).toString();
375  }
376  return attribute;
377 }
378 
379 void QgsPointDistanceRenderer::drawLabels( QPointF centerPoint, QgsSymbolRenderContext &context, const QList<QPointF> &labelShifts, const ClusteredGroup &group )
380 {
381  QPainter *p = context.renderContext().painter();
382  if ( !p )
383  {
384  return;
385  }
386 
387  QPen labelPen( mLabelColor );
388  p->setPen( labelPen );
389 
390  //scale font (for printing)
391  QFont pixelSizeFont = mLabelFont;
392  pixelSizeFont.setPixelSize( context.outputLineWidth( mLabelFont.pointSizeF() * 0.3527 ) );
393  QFont scaledFont = pixelSizeFont;
394  scaledFont.setPixelSize( pixelSizeFont.pixelSize() );
395  p->setFont( scaledFont );
396 
397  QFontMetricsF fontMetrics( pixelSizeFont );
398  QPointF currentLabelShift; //considers the signs to determine the label position
399 
400  QList<QPointF>::const_iterator labelPosIt = labelShifts.constBegin();
401  ClusteredGroup::const_iterator groupIt = group.constBegin();
402 
403  for ( ; labelPosIt != labelShifts.constEnd() && groupIt != group.constEnd(); ++labelPosIt, ++groupIt )
404  {
405  currentLabelShift = *labelPosIt;
406  if ( currentLabelShift.x() < 0 )
407  {
408  currentLabelShift.setX( currentLabelShift.x() - fontMetrics.width( groupIt->label ) );
409  }
410  if ( currentLabelShift.y() > 0 )
411  {
412  currentLabelShift.setY( currentLabelShift.y() + fontMetrics.ascent() );
413  }
414 
415  QPointF drawingPoint( centerPoint + currentLabelShift );
416  p->save();
417  p->translate( drawingPoint.x(), drawingPoint.y() );
418  p->drawText( QPointF( 0, 0 ), groupIt->label );
419  p->restore();
420  }
421 }
422 
423 QgsExpressionContextScope *QgsPointDistanceRenderer::createGroupScope( const ClusteredGroup &group ) const
424 {
426  if ( group.size() > 1 )
427  {
428  //scan through symbols to check color, e.g., if all clustered symbols are same color
429  QColor groupColor;
430  ClusteredGroup::const_iterator groupIt = group.constBegin();
431  for ( ; groupIt != group.constEnd(); ++groupIt )
432  {
433  if ( !groupIt->symbol() )
434  continue;
435 
436  if ( !groupColor.isValid() )
437  {
438  groupColor = groupIt->symbol()->color();
439  }
440  else
441  {
442  if ( groupColor != groupIt->symbol()->color() )
443  {
444  groupColor = QColor();
445  break;
446  }
447  }
448  }
449 
450  if ( groupColor.isValid() )
451  {
453  }
454  else
455  {
456  //mixed colors
458  }
459 
461  }
462  if ( !group.empty() )
463  {
464  // data defined properties may require a feature in the expression context, so just use first feature in group
465  clusterScope->setFeature( group.at( 0 ).feature );
466  }
467  return clusterScope;
468 }
469 
470 QgsMarkerSymbol *QgsPointDistanceRenderer::firstSymbolForFeature( QgsFeature &feature, QgsRenderContext &context )
471 {
472  if ( !mRenderer )
473  {
474  return nullptr;
475  }
476 
477  QgsSymbolList symbolList = mRenderer->symbolsForFeature( feature, context );
478  if ( symbolList.isEmpty() )
479  {
480  return nullptr;
481  }
482 
483  return dynamic_cast< QgsMarkerSymbol * >( symbolList.at( 0 ) );
484 }
int lookupField(const QString &fieldName) const
Look up field&#39;s index from the field name.
Definition: qgsfields.cpp:299
QgsSpatialIndex * mSpatialIndex
Spatial index for fast lookup of nearby points.
QgsFeatureId id
Definition: qgsfeature.h:71
static const QString EXPR_CLUSTER_COLOR
Inbuilt variable name for cluster color variable.
Single variable definition for use within a QgsExpressionContextScope.
double convertToMapUnits(double size, QgsUnitTypes::RenderUnit unit, const QgsMapUnitScale &scale=QgsMapUnitScale()) const
Converts a size from the specified units to map units.
A rectangle specified with double values.
Definition: qgsrectangle.h:39
QString mLabelAttributeName
Attribute name for labeling. An empty string indicates that no labels should be rendered.
double rendererScale() const
Returns the renderer map scale.
QgsSymbol * symbolForFeature(QgsFeature &feature, QgsRenderContext &context) override
To be overridden.
QList< QgsLegendSymbolItem > QgsLegendSymbolList
QgsUnitTypes::RenderUnit mToleranceUnit
Unit for distance tolerance.
QgsSymbol * originalSymbolForFeature(QgsFeature &feat, QgsRenderContext &context) override
Return symbol for feature.
bool legendSymbolItemChecked(const QString &key) override
items of symbology items in legend is checked
QgsPointDistanceRenderer(const QString &rendererName, const QString &labelAttributeName=QString())
Constructor for QgsPointDistanceRenderer.
Multi point geometry collection.
Definition: qgsmultipoint.h:29
QgsFeatureRenderer::Capabilities capabilities() override
Returns details about internals of this renderer.
QgsSymbolList symbols(QgsRenderContext &context) override
Returns list of symbols used by the renderer.
void checkLegendSymbolItem(const QString &key, bool state) override
item in symbology was checked
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
QgsWkbTypes::Type wkbType() const
Returns type of the geometry as a WKB type (point / linestring / polygon etc.)
double y
Definition: qgspointxy.h:48
A class to represent a 2D point.
Definition: qgspointxy.h:43
Helper functions for various unit types.
Definition: qgsunittypes.h:37
virtual QString filter(const QgsFields &fields=QgsFields())
If a renderer does not require all the features this method may be overridden and return an expressio...
Definition: qgsrenderer.h:190
#define FID_TO_STRING(fid)
Definition: qgsfeature.h:52
double outputLineWidth(double width) const
Definition: qgssymbol.cpp:1060
void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const override
used from subclasses to create SLD Rule elements following SLD v1.1 specs
Container of fields for a vector layer.
Definition: qgsfields.h:42
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
QMap< QgsFeatureId, int > mGroupIndex
Mapping of feature ID to the feature&#39;s group index.
QgsGeometry centroid() const
Returns the center of mass of a geometry.
bool insertFeature(const QgsFeature &feature)
Adds a feature to the index.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:62
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:190
virtual QgsAbstractGeometry * clone() const =0
Clones the geometry by performing a deep copy.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:479
bool renderFeature(QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
Render a feature using this renderer in the given context.
void addVariable(const QgsExpressionContextScope::StaticVariable &variable)
Adds a variable into the context scope.
bool isValid() const
Returns true if the coordinate transform is valid, ie both the source and destination CRS have been s...
QgsSymbolList symbolsForFeature(QgsFeature &feat, QgsRenderContext &context) override
Returns list of symbols used for rendering the feature.
bool mDrawLabels
Whether labels should be drawn for points. This is set internally from startRender() depending on sca...
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)
void transformInPlace(double &x, double &y) const
Transform device coordinates to map coordinates.
QgsSymbolList originalSymbolsForFeature(QgsFeature &feat, QgsRenderContext &context) override
Equivalent of originalSymbolsForFeature() call extended to support renderers that may use more symbol...
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:67
void setLegendSymbolItem(const QString &key, QgsSymbol *symbol) override
Sets the symbol to be used for a legend symbol item.
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:43
double mTolerance
Distance tolerance. Points that are closer together than this distance are considered clustered...
QSet< QString > legendKeysForFeature(QgsFeature &feature, QgsRenderContext &context) override
Return legend keys matching a specified feature.
QMap< QgsFeatureId, QgsPointXY > mGroupLocations
Mapping of feature ID to approximate group location.
QgsCoordinateTransform coordinateTransform() const
Returns the current coordinate transform for the context, or an invalid transform is no coordinate tr...
QColor mLabelColor
Label text color.
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
Single scope for storing variables and functions for use within a QgsExpressionContext.
QgsGeometry geometry() const
Returns the geometry associated with this feature.
Definition: qgsfeature.cpp:101
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the scope.
QgsRenderContext & renderContext()
Returns a reference to the context&#39;s render context.
Definition: qgssymbol.h:453
QgsMapUnitScale mToleranceMapUnitScale
Map unit scale for distance tolerance.
double x
Definition: qgspointxy.h:47
OperationResult transform(const QgsCoordinateTransform &ct, QgsCoordinateTransform::TransformDirection direction=QgsCoordinateTransform::ForwardTransform, bool transformZ=false)
Transforms this geometry as described by the coordinate transform ct.
bool legendSymbolItemsCheckable() const override
items of symbology items in legend should be checkable
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
QgsExpressionContext & expressionContext()
Gets the expression context.
Contains properties for a feature within a clustered group.
bool addGeometry(QgsAbstractGeometry *g) override
Adds a geometry and takes ownership. Returns true in case of success.
QString filter(const QgsFields &fields=QgsFields()) override
If a renderer does not require all the features this method may be overridden and return an expressio...
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
Contains information about the context of a rendering operation.
A spatial index for QgsFeature objects.
QPainter * painter()
Returns the destination QPainter for the render operation.
const QgsMapToPixel & mapToPixel() const
QgsPointXY asPoint() const
Returns contents of the geometry as a point if wkbType is WKBPoint, otherwise returns [0...
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
Definition: qgsrenderer.cpp:92
std::unique_ptr< QgsFeatureRenderer > mRenderer
Embedded base renderer. This can be used for rendering individual, isolated points.
QPointF asQPointF() const
Returns contents of the geometry as a QPointF if wkbType is WKBPoint, otherwise returns a null QPoint...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
void setGeometry(const QgsGeometry &geometry)
Set the feature&#39;s geometry.
Definition: qgsfeature.cpp:137
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
Class for doing transforms between two map coordinate systems.
static const QString EXPR_CLUSTER_SIZE
Inbuilt variable name for cluster size variable.
QList< QgsPointDistanceRenderer::GroupedFeature > ClusteredGroup
A group of clustered points (ie features within the distance tolerance).
qint64 QgsFeatureId
Definition: qgsfeature.h:37
bool willRenderFeature(QgsFeature &feat, QgsRenderContext &context) override
Returns whether the renderer will render a feature or not.
QList< ClusteredGroup > mClusteredGroups
Groups of features that are considered clustered together.
int mLabelIndex
Label attribute index (or -1 if none). This index is not stored, it is requested in the startRender()...
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
void setEmbeddedRenderer(QgsFeatureRenderer *r) override
Sets an embedded renderer (subrenderer) for this feature renderer.
static QgsFeatureRenderer * defaultRenderer(QgsWkbTypes::GeometryType geomType)
return a new renderer - used by default in vector layers
Definition: qgsrenderer.cpp:75
A vector of attributes.
Definition: qgsattributes.h:58
static Type flatType(Type type)
Returns the flat type for a WKB type.
Definition: qgswkbtypes.h:427
QList< QgsFeatureId > intersects(const QgsRectangle &rectangle) const
Returns a list of features with a bounding box which intersects the specified rectangle.
QgsAttributes attributes
Definition: qgsfeature.h:72
QgsMarkerSymbol * clone() const override
Get a deep copy of this symbol.
Definition: qgssymbol.cpp:1536
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Return a list of attributes required by this renderer.