QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsrulebasedlabeling.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrulebasedlabeling.cpp
3  ---------------------
4  begin : September 2015
5  copyright : (C) 2015 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
15 #include "qgsrulebasedlabeling.h"
16 #include "qgssymbollayerutils.h"
17 
19  : QgsVectorLayerLabelProvider( layer, QString(), withFeatureLoop, nullptr )
20 {
21  mRules.reset( rules.clone() );
22  mRules->rootRule()->createSubProviders( layer, mSubProviders, this );
23 }
24 
26 {
27  return new QgsVectorLayerLabelProvider( layer, providerId, withFeatureLoop, settings );
28 }
29 
30 bool QgsRuleBasedLabelProvider::prepare( const QgsRenderContext &context, QSet<QString> &attributeNames )
31 {
32  for ( QgsVectorLayerLabelProvider *provider : qgis::as_const( mSubProviders ) )
33  provider->setEngine( mEngine );
34 
35  // populate sub-providers
36  mRules->rootRule()->prepare( context, attributeNames, mSubProviders );
37  return true;
38 }
39 
40 void QgsRuleBasedLabelProvider::registerFeature( const QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry )
41 {
42  // will register the feature to relevant sub-providers
43  mRules->rootRule()->registerFeature( feature, context, mSubProviders, obstacleGeometry );
44 }
45 
46 QList<QgsAbstractLabelProvider *> QgsRuleBasedLabelProvider::subProviders()
47 {
48  QList<QgsAbstractLabelProvider *> lst;
49  for ( QgsVectorLayerLabelProvider *subprovider : qgis::as_const( mSubProviders ) )
50  lst << subprovider;
51  return lst;
52 }
53 
54 
56 
57 QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings *settings, double scaleMinDenom, double scaleMaxDenom, const QString &filterExp, const QString &description, bool elseRule )
58  : mSettings( settings )
59  , mMaximumScale( scaleMinDenom )
60  , mMinimumScale( scaleMaxDenom )
61  , mFilterExp( filterExp )
62  , mDescription( description )
63  , mElseRule( elseRule )
64 
65 {
66  initFilter();
67 }
68 
70 {
71  qDeleteAll( mChildren );
72  // do NOT delete parent
73 }
74 
76 {
77  if ( mSettings.get() == settings )
78  return;
79 
80  mSettings.reset( settings );
81 }
82 
84 {
85  RuleList l;
86  for ( Rule *c : mChildren )
87  {
88  l += c;
89  l += c->descendants();
90  }
91  return l;
92 }
93 
94 void QgsRuleBasedLabeling::Rule::initFilter()
95 {
96  if ( mElseRule || mFilterExp.compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
97  {
98  mElseRule = true;
99  mFilter.reset( nullptr );
100  }
101  else if ( !mFilterExp.isEmpty() )
102  {
103  mFilter.reset( new QgsExpression( mFilterExp ) );
104  }
105  else
106  {
107  mFilter.reset( nullptr );
108  }
109 }
110 
111 void QgsRuleBasedLabeling::Rule::updateElseRules()
112 {
113  mElseRules.clear();
114  for ( Rule *rule : qgis::as_const( mChildren ) )
115  {
116  if ( rule->isElse() )
117  mElseRules << rule;
118  }
119 }
120 
122 {
123  if ( mSettings && mSettings->format().containsAdvancedEffects() )
124  return true;
125 
126  for ( Rule *rule : qgis::as_const( mChildren ) )
127  {
128  if ( rule->requiresAdvancedEffects() )
129  return true;
130  }
131 
132  return false;
133 }
134 
135 void QgsRuleBasedLabeling::Rule::subProviderIds( QStringList &list ) const
136 {
137  for ( const Rule *rule : qgis::as_const( mChildren ) )
138  {
139  if ( rule->settings() )
140  list << rule->ruleKey();
141 
142  rule->subProviderIds( list );
143  }
144 }
145 
146 
148 {
149  mChildren.append( rule );
150  rule->mParent = this;
151  updateElseRules();
152 }
153 
155 {
156  mChildren.insert( i, rule );
157  rule->mParent = this;
158  updateElseRules();
159 }
160 
162 {
163  delete mChildren.at( i );
164  mChildren.removeAt( i );
165  updateElseRules();
166 }
167 
169 {
170  // we could use a hash / map for search if this will be slow...
171 
172  if ( key == mRuleKey )
173  return this;
174 
175  for ( Rule *rule : mChildren )
176  {
177  const Rule *r = rule->findRuleByKey( key );
178  if ( r )
179  return r;
180  }
181  return nullptr;
182 }
183 
185 {
186  if ( key == mRuleKey )
187  return this;
188 
189  for ( Rule *rule : qgis::as_const( mChildren ) )
190  {
191  Rule *r = rule->findRuleByKey( key );
192  if ( r )
193  return r;
194  }
195  return nullptr;
196 }
197 
199 {
200  QgsPalLayerSettings *s = mSettings.get() ? new QgsPalLayerSettings( *mSettings ) : nullptr;
201  Rule *newrule = new Rule( s, mMaximumScale, mMinimumScale, mFilterExp, mDescription );
202  newrule->setActive( mIsActive );
203  // clone children
204  for ( Rule *rule : mChildren )
205  newrule->appendChild( rule->clone() );
206  return newrule;
207 }
208 
210 {
211  QgsPalLayerSettings *settings = nullptr;
212  QDomElement settingsElem = ruleElem.firstChildElement( QStringLiteral( "settings" ) );
213  if ( !settingsElem.isNull() )
214  {
215  settings = new QgsPalLayerSettings;
216  settings->readXml( settingsElem, context );
217  }
218 
219  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
220  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
221  int scaleMinDenom = ruleElem.attribute( QStringLiteral( "scalemindenom" ), QStringLiteral( "0" ) ).toInt();
222  int scaleMaxDenom = ruleElem.attribute( QStringLiteral( "scalemaxdenom" ), QStringLiteral( "0" ) ).toInt();
223  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
224  Rule *rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, description );
225 
226  if ( !ruleKey.isEmpty() )
227  rule->mRuleKey = ruleKey;
228 
229  rule->setActive( ruleElem.attribute( QStringLiteral( "active" ), QStringLiteral( "1" ) ).toInt() );
230 
231  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
232  while ( !childRuleElem.isNull() )
233  {
234  Rule *childRule = create( childRuleElem, context );
235  if ( childRule )
236  {
237  rule->appendChild( childRule );
238  }
239  else
240  {
241  //QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
242  }
243  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
244  }
245 
246  return rule;
247 }
248 
249 QDomElement QgsRuleBasedLabeling::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
250 {
251  QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
252 
253  if ( mSettings )
254  {
255  ruleElem.appendChild( mSettings->writeXml( doc, context ) );
256  }
257  if ( !mFilterExp.isEmpty() )
258  ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
259  if ( !qgsDoubleNear( mMaximumScale, 0 ) )
260  ruleElem.setAttribute( QStringLiteral( "scalemindenom" ), mMaximumScale );
261  if ( !qgsDoubleNear( mMinimumScale, 0 ) )
262  ruleElem.setAttribute( QStringLiteral( "scalemaxdenom" ), mMinimumScale );
263  if ( !mDescription.isEmpty() )
264  ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
265  if ( !mIsActive )
266  ruleElem.setAttribute( QStringLiteral( "active" ), 0 );
267  ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
268 
269  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
270  {
271  Rule *rule = *it;
272  ruleElem.appendChild( rule->save( doc, context ) );
273  }
274  return ruleElem;
275 }
276 
278 {
279  if ( mSettings )
280  {
281  // add provider!
282  QgsVectorLayerLabelProvider *p = provider->createProvider( layer, mRuleKey, false, mSettings.get() );
283  delete subProviders.value( this, nullptr );
284  subProviders[this] = p;
285  }
286 
287  // call recursively
288  for ( Rule *rule : qgis::as_const( mChildren ) )
289  {
290  rule->createSubProviders( layer, subProviders, provider );
291  }
292 }
293 
295 {
296  if ( mSettings )
297  {
298  QgsVectorLayerLabelProvider *p = subProviders[this];
299  if ( !p->prepare( context, attributeNames ) )
300  {
301  subProviders.remove( this );
302  delete p;
303  }
304  }
305 
306  if ( mFilter )
307  {
308  attributeNames.unite( mFilter->referencedColumns() );
309  mFilter->prepare( &context.expressionContext() );
310  }
311 
312  // call recursively
313  for ( Rule *rule : qgis::as_const( mChildren ) )
314  {
315  rule->prepare( context, attributeNames, subProviders );
316  }
317 }
318 
320 {
321  if ( !isFilterOK( feature, context )
322  || !isScaleOK( context.rendererScale() ) )
323  return Filtered;
324 
325  bool registered = false;
326 
327  // do we have active subprovider for the rule?
328  if ( subProviders.contains( this ) && mIsActive )
329  {
330  subProviders[this]->registerFeature( feature, context, obstacleGeometry );
331  registered = true;
332  }
333 
334  bool willRegisterSomething = false;
335 
336  // call recursively
337  for ( Rule *rule : qgis::as_const( mChildren ) )
338  {
339  // Don't process else rules yet
340  if ( !rule->isElse() )
341  {
342  RegisterResult res = rule->registerFeature( feature, context, subProviders, obstacleGeometry );
343  // consider inactive items as "registered" so the else rule will ignore them
344  willRegisterSomething |= ( res == Registered || res == Inactive );
345  registered |= willRegisterSomething;
346  }
347  }
348 
349  // If none of the rules passed then we jump into the else rules and process them.
350  if ( !willRegisterSomething )
351  {
352  for ( Rule *rule : qgis::as_const( mElseRules ) )
353  {
354  registered |= rule->registerFeature( feature, context, subProviders, obstacleGeometry ) != Filtered;
355  }
356  }
357 
358  if ( !mIsActive )
359  return Inactive;
360  else if ( registered )
361  return Registered;
362  else
363  return Filtered;
364 }
365 
366 bool QgsRuleBasedLabeling::Rule::isFilterOK( const QgsFeature &f, QgsRenderContext &context ) const
367 {
368  if ( ! mFilter || mElseRule )
369  return true;
370 
371  context.expressionContext().setFeature( f );
372  QVariant res = mFilter->evaluate( &context.expressionContext() );
373  return res.toBool();
374 }
375 
376 bool QgsRuleBasedLabeling::Rule::isScaleOK( double scale ) const
377 {
378  if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
379  return true;
380  if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) )
381  return true;
382  if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale )
383  return false;
384  if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )
385  return false;
386  return true;
387 }
388 
390 
392  : mRootRule( root )
393 {
394 }
395 
397 {
398  Rule *rootRule = mRootRule->clone();
399 
400  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
401  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
402  rootRule->setRuleKey( mRootRule->ruleKey() );
403  RuleList origDescendants = mRootRule->descendants();
404  RuleList clonedDescendants = rootRule->descendants();
405  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
406  for ( int i = 0; i < origDescendants.count(); ++i )
407  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
408 
409  return new QgsRuleBasedLabeling( rootRule );
410 }
411 
413 {
414 }
415 
417 {
418  return mRootRule.get();
419 }
420 
422 {
423  return mRootRule.get();
424 }
425 
426 
427 QgsRuleBasedLabeling *QgsRuleBasedLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
428 {
429  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
430 
431  Rule *root = Rule::create( rulesElem, context );
432  if ( !root )
433  return nullptr;
434 
435  QgsRuleBasedLabeling *rl = new QgsRuleBasedLabeling( root );
436  return rl;
437 }
438 
440 {
441  return QStringLiteral( "rule-based" );
442 }
443 
444 QDomElement QgsRuleBasedLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
445 {
446  QDomElement elem = doc.createElement( QStringLiteral( "labeling" ) );
447  elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "rule-based" ) );
448 
449  QDomElement rulesElem = mRootRule->save( doc, context );
450  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
451  elem.appendChild( rulesElem );
452 
453  return elem;
454 }
455 
457 {
458  return new QgsRuleBasedLabelProvider( *this, layer, false );
459 }
460 
462 {
463  QStringList lst;
464  mRootRule->subProviderIds( lst );
465  return lst;
466 }
467 
468 QgsPalLayerSettings QgsRuleBasedLabeling::settings( const QString &providerId ) const
469 {
470  const Rule *rule = mRootRule->findRuleByKey( providerId );
471  if ( rule && rule->settings() )
472  return *rule->settings();
473 
474  return QgsPalLayerSettings();
475 }
476 
478 {
479  return mRootRule->requiresAdvancedEffects();
480 }
481 
483 {
484  if ( settings )
485  {
486  Rule *rule = mRootRule->findRuleByKey( providerId );
487  if ( rule && rule->settings() )
488  rule->setSettings( settings );
489  }
490 }
491 
492 void QgsRuleBasedLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
493 {
494  if ( !mRootRule )
495  {
496  return;
497  }
498 
499  const QgsRuleBasedLabeling::RuleList rules = mRootRule->children();
500  for ( Rule *rule : rules )
501  {
502  QgsPalLayerSettings *settings = rule->settings();
503 
504  if ( settings && settings->drawLabels )
505  {
506  QDomDocument doc = parent.ownerDocument();
507 
508  QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) );
509  parent.appendChild( ruleElement );
510 
511  if ( !rule->filterExpression().isEmpty() )
512  {
513  QgsSymbolLayerUtils::createFunctionElement( doc, ruleElement, rule->filterExpression() );
514  }
515 
516  // scale dependencies, the actual behavior is that the PAL settings min/max and
517  // the rule min/max get intersected
518  QgsStringMap localProps = QgsStringMap( props );
519  QgsSymbolLayerUtils::mergeScaleDependencies( rule->maximumScale(), rule->minimumScale(), localProps );
520  if ( settings->scaleVisibility )
521  {
522  QgsSymbolLayerUtils::mergeScaleDependencies( settings->maximumScale, settings->minimumScale, localProps );
523  }
524  QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, localProps );
525 
526  QgsAbstractVectorLayerLabeling::writeTextSymbolizer( ruleElement, *settings, props );
527  }
528 
529  }
530 
531 }
static void mergeScaleDependencies(double mScaleMinDenom, double mScaleMaxDenom, QgsStringMap &props)
Merges the local scale limits, if any, with the ones already in the map, if any.
Class for parsing and evaluation of expressions (formerly called "search strings").
The class is used as a container of context for various read/write operations on other objects...
QgsVectorLayerLabelProvider(QgsVectorLayer *layer, const QString &providerId, bool withFeatureLoop, const QgsPalLayerSettings *settings, const QString &layerName=QString())
Convenience constructor to initialize the provider from given vector layer.
bool requiresAdvancedEffects() const
Returns true if this rule or any of its children requires advanced composition effects to render...
QgsPalLayerSettings * settings() const
Returns the labeling settings.
double rendererScale() const
Returns the renderer map scale.
QgsVectorLayerLabelProvider * provider(QgsVectorLayer *layer) const override
double maximumScale
The maximum map scale (i.e.
void setSettings(QgsPalLayerSettings *settings, const QString &providerId=QString()) override
Set pal settings for a specific provider (takes ownership).
QgsRuleBasedLabeling::RuleToProviderMap mSubProviders
label providers are owned by labeling engine
static bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
bool requiresAdvancedEffects() const override
Returns true if drawing labels requires advanced effects like composition modes, which could prevent ...
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const override
Returns labeling configuration as XML element.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
const QgsLabelingEngine * mEngine
Associated labeling engine.
void readXml(QDomElement &elem, const QgsReadWriteContext &context)
Read settings from a DOM element.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QgsStringMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
void insertChild(int i, QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
RegisterResult
The result of registering a rule.
QgsRuleBasedLabeling::Rule * clone() const
clone this rule, return new instance
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
void registerFeature(const QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry=QgsGeometry()) override
Register a feature for labeling as one or more QgsLabelFeature objects stored into mLabels...
QString description() const
A human readable description for this rule.
The QgsVectorLayerLabelProvider class implements a label provider for vector layers.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:111
bool drawLabels
Whether to draw labels for this layer.
QList< QgsAbstractLabelProvider * > subProviders() override
Returns subproviders.
RegisterResult registerFeature(const QgsFeature &feature, QgsRenderContext &context, RuleToProviderMap &subProviders, const QgsGeometry &obstacleGeometry=QgsGeometry())
register individual features
QString ruleKey() const
Unique rule identifier (for identification of rule within labeling, used as provider ID) ...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
QMap< QgsRuleBasedLabeling::Rule *, QgsVectorLayerLabelProvider * > RuleToProviderMap
QMap< QString, QString > QgsStringMap
Definition: qgis.h:587
QgsRuleBasedLabeling::RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
QList< QgsRuleBasedLabeling::Rule * > RuleList
void prepare(const QgsRenderContext &context, QSet< QString > &attributeNames, RuleToProviderMap &subProviders)
call prepare() on sub-providers and populate attributeNames
static QgsRuleBasedLabeling::Rule * create(const QDomElement &ruleElem, const QgsReadWriteContext &context)
Create a rule from an XML definition.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
QgsMapLayer * layer() const
Returns the associated layer, or nullptr if no layer is associated with the provider.
QString type() const override
Unique type string of the labeling configuration implementation.
void appendChild(QgsRuleBasedLabeling::Rule *rule)
add child rule, take ownership, sets this as parent
QStringList subProviders() const override
Gets list of sub-providers within the layer&#39;s labeling.
bool prepare(const QgsRenderContext &context, QSet< QString > &attributeNames) override
Prepare for registration of features.
virtual bool prepare(const QgsRenderContext &context, QSet< QString > &attributeNames)
Prepare for registration of features.
std::unique_ptr< QgsRuleBasedLabeling > mRules
owned copy
std::unique_ptr< Rule > mRootRule
virtual QgsVectorLayerLabelProvider * createProvider(QgsVectorLayer *layer, const QString &providerId, bool withFeatureLoop, const QgsPalLayerSettings *settings)
create a label provider
void setSettings(QgsPalLayerSettings *settings)
Sets new settings (or nullptr). Deletes old settings if any.
QgsPalLayerSettings mSettings
Layer&#39;s labeling configuration.
QgsRuleBasedLabeling(QgsRuleBasedLabeling::Rule *root)
Constructs the labeling from given tree of rules (takes ownership)
const QgsRuleBasedLabeling::Rule * findRuleByKey(const QString &key) const
Try to find a rule given its unique key.
void setActive(bool state)
Sets if this rule is active.
QgsExpressionContext & expressionContext()
Gets the expression context.
static QgsRuleBasedLabeling * create(const QDomElement &element, const QgsReadWriteContext &context)
Create the instance from a DOM element with saved configuration.
QgsRuleBasedLabeling * clone() const override
Returns a new copy of the object.
void setRuleKey(const QString &key)
Override the assigned rule key (should be used just internally by rule-based labeling) ...
QgsPalLayerSettings settings(const QString &providerId=QString()) const override
Gets associated label settings.
void subProviderIds(QStringList &list) const
append rule keys of descendants that contain valid settings (i.e.
Contains information about the context of a rendering operation.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
Rule(QgsPalLayerSettings *settings, double maximumScale=0, double minimumScale=0, const QString &filterExp=QString(), const QString &description=QString(), bool elseRule=false)
takes ownership of settings, settings may be nullptr
virtual void writeTextSymbolizer(QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props) const
Writes a TextSymbolizer element contents based on the provided labeling settings. ...
QString providerId() const
Returns provider ID - useful in case there is more than one label provider within a layer (e...
void toSld(QDomNode &parent, const QgsStringMap &props) const override
Writes the SE 1.1 TextSymbolizer element based on the current layer labeling settings.
void removeChildAt(int i)
delete child rule
QgsRuleBasedLabeling::Rule * rootRule()
Represents a vector layer which manages a vector based data sets.
void createSubProviders(QgsVectorLayer *layer, RuleToProviderMap &subProviders, QgsRuleBasedLabelProvider *provider)
add providers
QgsRuleBasedLabelProvider(const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop=true)
double minimumScale
The minimum map scale (i.e.