QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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  Q_FOREACH ( QgsVectorLayerLabelProvider *provider, 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( 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  Q_FOREACH ( QgsVectorLayerLabelProvider *subprovider, 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  Q_FOREACH ( Rule *rule, mChildren )
115  {
116  if ( rule->isElse() )
117  mElseRules << rule;
118  }
119 }
120 
122 {
123  if ( mSettings && mSettings->format().containsAdvancedEffects() )
124  return true;
125 
126  Q_FOREACH ( Rule *rule, 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  Q_FOREACH ( const Rule *rule, 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  Q_FOREACH ( 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  Q_FOREACH ( 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  Q_FOREACH ( Rule *rule, 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  Q_FOREACH ( Rule *rule, 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  Q_FOREACH ( Rule *rule, 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  Q_FOREACH ( Rule *rule, 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( 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.toInt() != 0;
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 {
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  delete mRootRule;
415 }
416 
417 
418 QgsRuleBasedLabeling *QgsRuleBasedLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
419 {
420  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
421 
422  Rule *root = Rule::create( rulesElem, context );
423  if ( !root )
424  return nullptr;
425 
426  QgsRuleBasedLabeling *rl = new QgsRuleBasedLabeling( root );
427  return rl;
428 }
429 
431 {
432  return QStringLiteral( "rule-based" );
433 }
434 
435 QDomElement QgsRuleBasedLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
436 {
437  QDomElement elem = doc.createElement( QStringLiteral( "labeling" ) );
438  elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "rule-based" ) );
439 
440  QDomElement rulesElem = mRootRule->save( doc, context );
441  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
442  elem.appendChild( rulesElem );
443 
444  return elem;
445 }
446 
448 {
449  return new QgsRuleBasedLabelProvider( *this, layer, false );
450 }
451 
453 {
454  QStringList lst;
455  mRootRule->subProviderIds( lst );
456  return lst;
457 }
458 
459 QgsPalLayerSettings QgsRuleBasedLabeling::settings( const QString &providerId ) const
460 {
461  const Rule *rule = mRootRule->findRuleByKey( providerId );
462  if ( rule && rule->settings() )
463  return *rule->settings();
464 
465  return QgsPalLayerSettings();
466 }
467 
469 {
471 }
472 
474 {
475  if ( settings )
476  {
477  Rule *rule = mRootRule->findRuleByKey( providerId );
478  if ( rule && rule->settings() )
479  rule->setSettings( settings );
480  }
481 }
482 
483 void QgsRuleBasedLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
484 {
485  if ( !mRootRule )
486  {
487  return;
488  }
489 
491  for ( Rule *rule : rules )
492  {
493  QgsPalLayerSettings *settings = rule->settings();
494 
495  if ( settings && settings->drawLabels )
496  {
497  QDomDocument doc = parent.ownerDocument();
498 
499  QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) );
500  parent.appendChild( ruleElement );
501 
502  if ( !rule->filterExpression().isEmpty() )
503  {
504  QgsSymbolLayerUtils::createFunctionElement( doc, ruleElement, rule->filterExpression() );
505  }
506 
507  // scale dependencies, the actual behavior is that the PAL settings min/max and
508  // the rule min/max get intersected
509  QgsStringMap localProps = QgsStringMap( props );
510  QgsSymbolLayerUtils::mergeScaleDependencies( rule->maximumScale(), rule->minimumScale(), localProps );
511  if ( settings->scaleVisibility )
512  {
513  QgsSymbolLayerUtils::mergeScaleDependencies( settings->maximumScale, settings->minimumScale, localProps );
514  }
515  QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, localProps );
516 
517  QgsAbstractVectorLayerLabeling::writeTextSymbolizer( ruleElement, *settings, props );
518  }
519 
520  }
521 
522 }
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...
QgsPalLayerSettings * settings() const
Gets the labeling settings.
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.
void subProviderIds(QStringList &list) const
append rule keys of descendants that contain valid settings (i.e.
QgsVectorLayerLabelProvider * provider(QgsVectorLayer *layer) const override
double maximumScale
The maximum map scale (i.e.
bool isElse() const
Check if this rule is an ELSE rule.
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
Returns true if this rule or any of its children requires advanced composition effects to render...
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.
QString description() const
A human readable description for this rule.
QString ruleKey() const
Unique rule identifier (for identification of rule within labeling, used as provider ID) ...
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.
double rendererScale() const
Returns the renderer map scale.
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:278
virtual void writeTextSymbolizer(QDomNode &parent, QgsPalLayerSettings &settings, const QgsStringMap &props) const
Writes a TextSymbolizer element contents based on the provided labeling settings. ...
The QgsVectorLayerLabelProvider class implements a label provider for vector layers.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:106
bool drawLabels
Whether to draw labels for this layer.
QList< QgsAbstractLabelProvider * > subProviders() override
Returns subproviders.
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:577
QList< QgsRuleBasedLabeling::Rule * > RuleList
void prepare(const QgsRenderContext &context, QSet< QString > &attributeNames, RuleToProviderMap &subProviders)
call prepare() on sub-providers and populate attributeNames
QgsRuleBasedLabeling::RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
QString providerId() const
Returns provider ID - useful in case there is more than one label provider within a layer (e...
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
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
const QgsRuleBasedLabeling::Rule * findRuleByKey(const QString &key) const
Try to find a rule given its unique key.
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.
QgsMapLayer * layer() const
Returns the associated layer, or nullptr if no layer is associated with the provider.
virtual bool prepare(const QgsRenderContext &context, QSet< QString > &attributeNames)
Prepare for registration of features.
std::unique_ptr< QgsRuleBasedLabeling > mRules
owned copy
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 NULL). Deletes old settings if any.
QgsPalLayerSettings mSettings
Layer&#39;s labeling configuration.
QgsRuleBasedLabeling::Rule * rootRule()
QgsRuleBasedLabeling(QgsRuleBasedLabeling::Rule *root)
Constructs the labeling from given tree of rules (takes ownership)
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.
RegisterResult registerFeature(QgsFeature &feature, QgsRenderContext &context, RuleToProviderMap &subProviders, const QgsGeometry &obstacleGeometry=QgsGeometry())
register individual features
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.
Contains information about the context of a rendering operation.
bool scaleVisibility
Set to true to limit label visibility to a range of scales.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) const
store labeling info to XML element
QgsRuleBasedLabeling::Rule * clone() const
clone this rule, return new instance
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
void registerFeature(QgsFeature &feature, QgsRenderContext &context, const QgsGeometry &obstacleGeometry=QgsGeometry()) override
Register a feature for labeling as one or more QgsLabelFeature objects stored into mLabels...
const QgsRuleBasedLabeling::RuleList & children() const
Returns all children rules of this rule.
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
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)
void setEngine(const QgsLabelingEngine *engine)
Associate provider with a labeling engine (should be only called internally from QgsLabelingEngine) ...
double minimumScale
The minimum map scale (i.e.