QGIS API Documentation  3.0.2-Girona (307d082)
qgslabelingengine.cpp
Go to the documentation of this file.
1 
2 /***************************************************************************
3  qgslabelingengine.cpp
4  --------------------------------------
5  Date : September 2015
6  Copyright : (C) 2015 by Martin Dobias
7  Email : wonder dot sk at gmail dot com
8  ***************************************************************************
9  * *
10  * This program is free software; you can redistribute it and/or modify *
11  * it under the terms of the GNU General Public License as published by *
12  * the Free Software Foundation; either version 2 of the License, or *
13  * (at your option) any later version. *
14  * *
15  ***************************************************************************/
16 
17 #include "qgslabelingengine.h"
18 
19 #include "qgslogger.h"
20 
21 #include "feature.h"
22 #include "labelposition.h"
23 #include "layer.h"
24 #include "pal.h"
25 #include "problem.h"
26 #include "qgsrendercontext.h"
27 #include "qgsmaplayer.h"
28 
29 
30 // helper function for checking for job cancelation within PAL
31 static bool _palIsCanceled( void *ctx )
32 {
33  return ( reinterpret_cast< QgsRenderContext * >( ctx ) )->renderingStopped();
34 }
35 
42 {
43  public:
44 
45  explicit QgsLabelSorter( const QgsMapSettings &mapSettings )
46  : mMapSettings( mapSettings )
47  {}
48 
50  {
51  QgsLabelFeature *lf1 = lp1->getFeaturePart()->feature();
52  QgsLabelFeature *lf2 = lp2->getFeaturePart()->feature();
53 
54  if ( !qgsDoubleNear( lf1->zIndex(), lf2->zIndex() ) )
55  return lf1->zIndex() < lf2->zIndex();
56 
57  //equal z-index, so fallback to respecting layer render order
58  QStringList layerIds = mMapSettings.layerIds();
59  int layer1Pos = layerIds.indexOf( lf1->provider()->layerId() );
60  int layer2Pos = layerIds.indexOf( lf2->provider()->layerId() );
61  if ( layer1Pos != layer2Pos && layer1Pos >= 0 && layer2Pos >= 0 )
62  return layer1Pos > layer2Pos; //higher positions are rendered first
63 
64  //same layer, so render larger labels first
65  return lf1->size().width() * lf1->size().height() > lf2->size().width() * lf2->size().height();
66  }
67 
68  private:
69 
70  const QgsMapSettings &mMapSettings;
71 };
72 
73 
75  : mResults( new QgsLabelingResults )
76 {}
77 
79 {
80  qDeleteAll( mProviders );
81  qDeleteAll( mSubProviders );
82 }
83 
84 QList< QgsMapLayer * > QgsLabelingEngine::participatingLayers() const
85 {
86  QSet< QgsMapLayer * > layers;
87  Q_FOREACH ( QgsAbstractLabelProvider *provider, mProviders )
88  {
89  if ( provider->layer() )
90  layers << provider->layer();
91  }
92  Q_FOREACH ( QgsAbstractLabelProvider *provider, mSubProviders )
93  {
94  if ( provider->layer() )
95  layers << provider->layer();
96  }
97  return layers.toList();
98 }
99 
101 {
102  provider->setEngine( this );
103  mProviders << provider;
104 }
105 
107 {
108  int idx = mProviders.indexOf( provider );
109  if ( idx >= 0 )
110  {
111  delete mProviders.takeAt( idx );
112  }
113 }
114 
116 {
117  QgsAbstractLabelProvider::Flags flags = provider->flags();
118 
119  // create the pal layer
120  pal::Layer *l = p.addLayer( provider,
121  provider->name(),
122  provider->placement(),
123  provider->priority(),
124  true,
125  flags.testFlag( QgsAbstractLabelProvider::DrawLabels ),
126  flags.testFlag( QgsAbstractLabelProvider::DrawAllLabels ) );
127 
128  // extra flags for placement of labels for linestrings
129  l->setArrangementFlags( static_cast< pal::LineArrangementFlags >( provider->linePlacementFlags() ) );
130 
131  // set label mode (label per feature is the default)
133 
134  // set whether adjacent lines should be merged
136 
137  // set obstacle type
138  l->setObstacleType( provider->obstacleType() );
139 
140  // set whether location of centroid must be inside of polygons
142 
143  // set how to show upside-down labels
144  pal::Layer::UpsideDownLabels upsdnlabels;
145  switch ( provider->upsidedownLabels() )
146  {
148  upsdnlabels = pal::Layer::Upright;
149  break;
151  upsdnlabels = pal::Layer::ShowDefined;
152  break;
154  upsdnlabels = pal::Layer::ShowAll;
155  break;
156  default:
157  Q_ASSERT( "unsupported upside-down label setting" && false );
158  return;
159  }
160  l->setUpsidedownLabels( upsdnlabels );
161 
162 
163  QList<QgsLabelFeature *> features = provider->labelFeatures( context );
164 
165  Q_FOREACH ( QgsLabelFeature *feature, features )
166  {
167  try
168  {
169  l->registerFeature( feature );
170  }
171  catch ( std::exception &e )
172  {
173  Q_UNUSED( e );
174  QgsDebugMsgLevel( QString( "Ignoring feature %1 due PAL exception:" ).arg( feature->id() ) + QString::fromLatin1( e.what() ), 4 );
175  continue;
176  }
177  }
178 
179  // any sub-providers?
180  Q_FOREACH ( QgsAbstractLabelProvider *subProvider, provider->subProviders() )
181  {
182  mSubProviders << subProvider;
183  processProvider( subProvider, context, p );
184  }
185 }
186 
187 
189 {
191 
192  pal::Pal p;
194  switch ( settings.searchMethod() )
195  {
196  default:
198  s = pal::CHAIN;
199  break;
201  s = pal::POPMUSIC_TABU;
202  break;
205  break;
208  break;
210  s = pal::FALP;
211  break;
212  }
213  p.setSearch( s );
214 
215  // set number of candidates generated per feature
216  int candPoint, candLine, candPolygon;
217  settings.numCandidatePositions( candPoint, candLine, candPolygon );
218  p.setPointP( candPoint );
219  p.setLineP( candLine );
220  p.setPolyP( candPolygon );
221 
223 
224 
225  // for each provider: get labels and register them in PAL
226  Q_FOREACH ( QgsAbstractLabelProvider *provider, mProviders )
227  {
228  bool appendedLayerScope = false;
229  if ( QgsMapLayer *ml = provider->layer() )
230  {
231  appendedLayerScope = true;
233  }
234  processProvider( provider, context, p );
235  if ( appendedLayerScope )
236  delete context.expressionContext().popScope();
237  }
238 
239 
240  // NOW DO THE LAYOUT (from QgsPalLabeling::drawLabeling)
241 
242  QPainter *painter = context.painter();
243 
245  QPolygonF visiblePoly = mMapSettings.visiblePolygon();
246  visiblePoly.append( visiblePoly.at( 0 ) ); //close polygon
247  QgsGeometry mapBoundaryGeom = QgsGeometry::fromQPolygonF( visiblePoly );
248 
249  if ( !qgsDoubleNear( mMapSettings.rotation(), 0.0 ) )
250  {
251  //PAL features are prerotated, so extent also needs to be unrotated
253  // yes - this is rotated in the opposite direction... phew, this is confusing!
254  mapBoundaryGeom.rotate( mMapSettings.rotation(), mMapSettings.visibleExtent().center() );
255  }
256 
257  QgsRectangle extent = extentGeom.boundingBox();
258 
259 
260  p.registerCancelationCallback( &_palIsCanceled, reinterpret_cast< void * >( &context ) );
261 
262  QTime t;
263  t.start();
264 
265  // do the labeling itself
266  std::unique_ptr< pal::Problem > problem;
267  try
268  {
269  problem = p.extractProblem( extent, mapBoundaryGeom );
270  }
271  catch ( std::exception &e )
272  {
273  Q_UNUSED( e );
274  QgsDebugMsgLevel( "PAL EXCEPTION :-( " + QString::fromLatin1( e.what() ), 4 );
275  return;
276  }
277 
278  if ( context.renderingStopped() )
279  {
280  return; // it has been canceled
281  }
282 
283 #if 1 // XXX strk
284  // features are pre-rotated but not scaled/translated,
285  // so we only disable rotation here. Ideally, they'd be
286  // also pre-scaled/translated, as suggested here:
287  // https://issues.qgis.org/issues/11856
289  xform.setMapRotation( 0, 0, 0 );
290 #else
291  const QgsMapToPixel &xform = mMapSettings->mapToPixel();
292 #endif
293 
294  // draw rectangles with all candidates
295  // this is done before actual solution of the problem
296  // before number of candidates gets reduced
297  // TODO mCandidates.clear();
298  if ( settings.testFlag( QgsLabelingEngineSettings::DrawCandidates ) && problem )
299  {
300  painter->setBrush( Qt::NoBrush );
301  for ( int i = 0; i < problem->getNumFeatures(); i++ )
302  {
303  for ( int j = 0; j < problem->getFeatureCandidateCount( i ); j++ )
304  {
305  pal::LabelPosition *lp = problem->getFeatureCandidate( i, j );
306 
307  QgsPalLabeling::drawLabelCandidateRect( lp, painter, &xform );
308  }
309  }
310  }
311 
312  // find the solution
313  QList<pal::LabelPosition *> labels = p.solveProblem( problem.get(), settings.testFlag( QgsLabelingEngineSettings::UseAllLabels ) );
314 
315  QgsDebugMsgLevel( QString( "LABELING work: %1 ms ... labels# %2" ).arg( t.elapsed() ).arg( labels.size() ), 4 );
316  t.restart();
317 
318  if ( context.renderingStopped() )
319  {
320  return;
321  }
322  painter->setRenderHint( QPainter::Antialiasing );
323 
324  // sort labels
325  std::sort( labels.begin(), labels.end(), QgsLabelSorter( mMapSettings ) );
326 
327  // draw the labels
328  for ( pal::LabelPosition *label : qgis::as_const( labels ) )
329  {
330  if ( context.renderingStopped() )
331  break;
332 
333  QgsLabelFeature *lf = label->getFeaturePart()->feature();
334  if ( !lf )
335  {
336  continue;
337  }
338 
339  lf->provider()->drawLabel( context, label );
340  }
341 
342  // Reset composition mode for further drawing operations
343  painter->setCompositionMode( QPainter::CompositionMode_SourceOver );
344 
345  QgsDebugMsgLevel( QString( "LABELING draw: %1 ms" ).arg( t.elapsed() ), 4 );
346 }
347 
349 {
350  return mResults.release();
351 }
352 
353 
355 
357 {
358  return mLayer ? mLayer->provider() : nullptr;
359 
360 }
361 
363  : mLayerId( layer ? layer->id() : QString() )
364  , mLayer( layer )
365  , mProviderId( providerId )
366  , mFlags( DrawLabels )
367  , mPlacement( QgsPalLayerSettings::AroundPoint )
368  , mLinePlacementFlags( 0 )
369  , mPriority( 0.5 )
370  , mObstacleType( QgsPalLayerSettings::PolygonInterior )
371  , mUpsidedownLabels( QgsPalLayerSettings::Upright )
372 {
373 }
374 
375 
376 //
377 // QgsLabelingUtils
378 //
379 
380 QString QgsLabelingUtils::encodePredefinedPositionOrder( const QVector<QgsPalLayerSettings::PredefinedPointPosition> &positions )
381 {
382  QStringList predefinedOrderString;
383  Q_FOREACH ( QgsPalLayerSettings::PredefinedPointPosition position, positions )
384  {
385  switch ( position )
386  {
388  predefinedOrderString << QStringLiteral( "TL" );
389  break;
391  predefinedOrderString << QStringLiteral( "TSL" );
392  break;
394  predefinedOrderString << QStringLiteral( "T" );
395  break;
397  predefinedOrderString << QStringLiteral( "TSR" );
398  break;
400  predefinedOrderString << QStringLiteral( "TR" );
401  break;
403  predefinedOrderString << QStringLiteral( "L" );
404  break;
406  predefinedOrderString << QStringLiteral( "R" );
407  break;
409  predefinedOrderString << QStringLiteral( "BL" );
410  break;
412  predefinedOrderString << QStringLiteral( "BSL" );
413  break;
415  predefinedOrderString << QStringLiteral( "B" );
416  break;
418  predefinedOrderString << QStringLiteral( "BSR" );
419  break;
421  predefinedOrderString << QStringLiteral( "BR" );
422  break;
423  }
424  }
425  return predefinedOrderString.join( QStringLiteral( "," ) );
426 }
427 
428 QVector<QgsPalLayerSettings::PredefinedPointPosition> QgsLabelingUtils::decodePredefinedPositionOrder( const QString &positionString )
429 {
430  QVector<QgsPalLayerSettings::PredefinedPointPosition> result;
431  QStringList predefinedOrderList = positionString.split( ',' );
432  Q_FOREACH ( const QString &position, predefinedOrderList )
433  {
434  QString cleaned = position.trimmed().toUpper();
435  if ( cleaned == QLatin1String( "TL" ) )
437  else if ( cleaned == QLatin1String( "TSL" ) )
439  else if ( cleaned == QLatin1String( "T" ) )
441  else if ( cleaned == QLatin1String( "TSR" ) )
443  else if ( cleaned == QLatin1String( "TR" ) )
445  else if ( cleaned == QLatin1String( "L" ) )
447  else if ( cleaned == QLatin1String( "R" ) )
449  else if ( cleaned == QLatin1String( "BL" ) )
451  else if ( cleaned == QLatin1String( "BSL" ) )
453  else if ( cleaned == QLatin1String( "B" ) )
455  else if ( cleaned == QLatin1String( "BSR" ) )
457  else if ( cleaned == QLatin1String( "BR" ) )
459  }
460  return result;
461 }
Label below point, slightly right of center.
void processProvider(QgsAbstractLabelProvider *provider, QgsRenderContext &context, pal::Pal &p)
Layer * addLayer(QgsAbstractLabelProvider *provider, const QString &layerName, QgsPalLayerSettings::Placement arrangement, double defaultPriority, bool active, bool toLabel, bool displayAll=false)
add a new layer
Definition: pal.cpp:108
A rectangle specified with double values.
Definition: qgsrectangle.h:39
Base class for all map layer types.
Definition: qgsmaplayer.h:56
Label on bottom-left of point.
void setMapRotation(double degrees, double cx, double cy)
Set map rotation in degrees (clockwise)
void addProvider(QgsAbstractLabelProvider *provider)
Add provider of label features. Takes ownership of the provider.
QList< QgsAbstractLabelProvider * > mProviders
List of providers (the are owned by the labeling engine)
Definition: pal.h:67
QgsFeatureId id() const
Identifier of the label (unique within the parent label provider)
void removeProvider(QgsAbstractLabelProvider *provider)
Remove provider if the provider&#39;s initialization failed. Provider instance is deleted.
QgsLabelFeature * feature()
Returns the parent feature.
Definition: feature.h:118
double rotation() const
Returns the rotation of the resulting map image, in degrees clockwise.
QStringList layerIds() const
Get list of layer IDs for map rendering The layers are stored in the reverse order of how they are re...
QgsAbstractLabelProvider * provider() const
Return provider of this instance.
Label on top-left of point.
QgsAbstractLabelProvider(QgsMapLayer *layer, const QString &providerId=QString())
Construct the provider with default values.
static void drawLabelCandidateRect(pal::LabelPosition *lp, QPainter *painter, const QgsMapToPixel *xform, QList< QgsLabelCandidate > *candidates=nullptr)
A set of features which influence the labeling process.
Definition: layer.h:63
PredefinedPointPosition
Positions for labels when using the QgsPalLabeling::OrderedPositionsAroundPoint placement mode...
OperationResult rotate(double rotation, const QgsPointXY &center)
Rotate this geometry around the Z axis.
QgsPalLayerSettings::ObstacleType obstacleType() const
How the feature geometries will work as obstacles.
Whether to use also label candidates that are partially outside of the map view.
QgsLabelingEngine()
Construct the labeling engine with default settings.
QList< QgsAbstractLabelProvider * > mSubProviders
bool renderingStopped() const
Main Pal labeling class.
Definition: pal.h:89
Is slower and best than TABU, worse and faster than TABU_CHAIN.
Definition: pal.h:66
UpsideDownLabels
Definition: layer.h:74
Label on top of point, slightly right of center.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
void setUpsidedownLabels(UpsideDownLabels ud)
Sets how upside down labels will be handled within the layer.
Definition: layer.h:223
void setShowPartial(bool show)
Set flag show partial label.
Definition: pal.cpp:537
Whether to label each part of multi-part features separately.
FeaturePart * getFeaturePart()
return the feature corresponding to this labelposition
virtual QList< QgsLabelFeature * > labelFeatures(QgsRenderContext &context)=0
Return list of label features (they are owned by the provider and thus deleted on its destruction) ...
void setObstacleType(QgsPalLayerSettings::ObstacleType obstacleType)
Sets the obstacle type, which controls how features within the layer act as obstacles for labels...
Definition: layer.h:175
QgsRectangle visibleExtent() const
Return the actual extent derived from requested extent that takes takes output image size into accoun...
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
void setCentroidInside(bool forceInside)
Sets whether labels placed at the centroid of features within the layer are forced to be placed insid...
Definition: layer.h:238
Is a little bit better than CHAIN but slower.
Definition: pal.h:65
Whether adjacent lines (with the same label text) should be merged.
std::unique_ptr< Problem > extractProblem(const QgsRectangle &extent, const QgsGeometry &mapBoundary)
Extracts the labeling problem for the specified map extent - only features within this extent will be...
Definition: pal.cpp:455
The QgsMapSettings class contains configuration for rendering of the map.
QgsMapLayer * layer() const
Returns the associated layer, or nullptr if no layer is associated with the provider.
Perform transforms between map coordinates and device coordinates.
Definition: qgsmaptopixel.h:36
Whether to draw all labels even if there would be collisions.
unsigned int linePlacementFlags() const
For layers with linestring geometries - extra placement flags (or-ed combination of QgsPalLayerSettin...
static QgsGeometry fromRect(const QgsRectangle &rect)
Creates a new geometry from a QgsRectangle.
double zIndex() const
Returns the label&#39;s z-index.
Label on left of point.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
Show upside down for all labels, including dynamic ones.
Is the best but slowest.
Definition: pal.h:64
bool operator()(pal::LabelPosition *lp1, pal::LabelPosition *lp2) const
void setPointP(int point_p)
set # candidates to generate for points features Higher the value is, longer Pal::labeller will spend...
Definition: pal.cpp:485
Whether location of centroid must be inside of polygons.
void setLineP(int line_p)
set maximum # candidates to generate for lines features Higher the value is, longer Pal::labeller wil...
Definition: pal.cpp:491
Upside-down labels (90 <= angle < 270) are shown upright.
QList< QgsMapLayer *> participatingLayers() const
Returns a list of layers with providers in the engine.
Whether all features will be labelled even though overlaps occur.
QgsPalLayerSettings::Placement placement() const
What placement strategy to use for the labels.
const QgsMapToPixel & mapToPixel() const
The QgsAbstractLabelProvider class is an interface class.
Whether to draw rectangles of generated candidates (good for debugging)
QString layerId() const
Returns ID of associated layer, or empty string if no layer is associated with the provider...
QSizeF size() const
Size of the label (in map units)
QgsPalLayerSettings::UpsideDownLabels upsidedownLabels() const
How to handle labels that would be upside down.
void numCandidatePositions(int &candPoint, int &candLine, int &candPolygon) const
Get number of candidate positions that will be generated for each label feature (default to 8) ...
QgsLabelSorter(const QgsMapSettings &mapSettings)
QgsExpressionContext & expressionContext()
Gets the expression context.
~QgsLabelingEngine()
Clean up everything (especially the registered providers)
bool registerFeature(QgsLabelFeature *label)
Register a feature in the layer.
Definition: layer.cpp:96
Flags flags() const
Flags associated with the provider.
static QString encodePredefinedPositionOrder(const QVector< QgsPalLayerSettings::PredefinedPointPosition > &positions)
Encodes an ordered list of predefined point label positions to a string.
void run(QgsRenderContext &context)
compute the labeling with given map settings and providers
virtual QList< QgsAbstractLabelProvider * > subProviders()
Return list of child providers - useful if the provider needs to put labels into more layers with dif...
Contains information about the context of a rendering operation.
QPainter * painter()
Returns the destination QPainter for the render operation.
The QgsLabelFeature class describes a feature that should be used within the labeling engine...
QString name() const
Name of the layer (for statistics, debugging etc.) - does not need to be unique.
std::unique_ptr< QgsLabelingResults > mResults
Resulting labeling layout.
void setPolyP(int poly_p)
set maximum # candidates to generate for polygon features Higher the value is, longer Pal::labeller w...
Definition: pal.cpp:497
QgsMapSettings mMapSettings
Associated map settings instance.
void registerCancelationCallback(FnIsCanceled fnCanceled, void *context)
Register a function that returns whether this job has been canceled - PAL calls it during the computa...
Definition: pal.cpp:449
QgsLabelingResults * takeResults()
Return pointer to recently computed results and pass the ownership of results to the caller...
void appendScope(QgsExpressionContextScope *scope)
Appends a scope to the end of the context.
Stores global configuration for labeling engine.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
QList< LabelPosition * > solveProblem(Problem *prob, bool displayAll)
Definition: pal.cpp:460
void setLabelMode(LabelMode mode)
Sets the layer&#39;s labeling mode.
Definition: layer.h:197
Label below point, slightly left of center.
Helper class for sorting labels into correct draw order.
double priority() const
Default priority of labels (may be overridden by individual labels)
bool testFlag(Flag f) const
Test whether a particular flag is enabled.
static QgsGeometry fromQPolygonF(const QPolygonF &polygon)
Construct geometry from a QPolygonF.
LabelPosition is a candidate feature label position.
Definition: labelposition.h:55
Label on top of point, slightly left of center.
Label on right of point.
void setArrangementFlags(LineArrangementFlags flags)
Sets the layer&#39;s arrangement flags.
Definition: layer.h:127
Whether the labels should be rendered.
Class that stores computed placement from labeling engine.
QPolygonF visiblePolygon() const
Return the visible area as a polygon (may be rotated)
static QVector< QgsPalLayerSettings::PredefinedPointPosition > decodePredefinedPositionOrder(const QString &positionString)
Decodes a string to an ordered list of predefined point label positions.
QgsPointXY center() const
Returns the center point of the rectangle.
Definition: qgsrectangle.h:170
SearchMethod
Search method to use.
Definition: pal.h:61
QgsExpressionContextScope * popScope()
Removes the last scope from the expression context and return it.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Show upside down when rotation is layer- or data-defined.
virtual void drawLabel(QgsRenderContext &context, pal::LabelPosition *label) const =0
draw this label at the position determined by the labeling engine
Is the worst but fastest method.
Definition: pal.h:63
void setSearch(SearchMethod method)
Select the search method to use.
Definition: pal.cpp:577
void setEngine(const QgsLabelingEngine *engine)
Associate provider with a labeling engine (should be only called internally from QgsLabelingEngine) ...
Search searchMethod() const
Which search method to use for removal collisions between labels.
const QgsLabelingEngineSettings & labelingEngineSettings() const
Returns global configuration of the labeling engine.
void setMergeConnectedLines(bool merge)
Sets whether connected lines should be merged before labeling.
Definition: layer.h:210