QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
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 
17 
19  : QgsVectorLayerLabelProvider( layer, QString(), withFeatureLoop )
20  , mRules( rules )
21 {
23 }
24 
26 {
27  // sub-providers owned by labeling engine
28 }
29 
31 {
32  return new QgsVectorLayerLabelProvider( layer, providerId, withFeatureLoop, settings );
33 }
34 
35 bool QgsRuleBasedLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
36 {
37  Q_FOREACH ( QgsVectorLayerLabelProvider* provider, mSubProviders )
38  provider->setEngine( mEngine );
39 
40  // populate sub-providers
41  mRules.rootRule()->prepare( context, attributeNames, mSubProviders );
42  return true;
43 }
44 
46 {
47  // will register the feature to relevant sub-providers
48  mRules.rootRule()->registerFeature( feature, context, mSubProviders, obstacleGeometry );
49 }
50 
52 {
54  Q_FOREACH ( QgsVectorLayerLabelProvider* subprovider, mSubProviders )
55  lst << subprovider;
56  return lst;
57 }
58 
59 
61 
62 QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings* settings, int scaleMinDenom, int scaleMaxDenom, const QString& filterExp, const QString& description, bool elseRule )
63  : mParent( nullptr )
64  , mSettings( settings )
65  , mScaleMinDenom( scaleMinDenom )
66  , mScaleMaxDenom( scaleMaxDenom )
67  , mFilterExp( filterExp )
68  , mDescription( description )
69  , mElseRule( elseRule )
70  , mIsActive( true )
71  , mFilter( nullptr )
72 {
74  initFilter();
75 }
76 
78 {
79  delete mSettings;
80  delete mFilter;
81  qDeleteAll( mChildren );
82  // do NOT delete parent
83 }
84 
86 {
87  if ( mSettings == settings )
88  return;
89 
90  delete mSettings;
92 }
93 
95 {
96  if ( mElseRule || mFilterExp.compare( "ELSE", Qt::CaseInsensitive ) == 0 )
97  {
98  mElseRule = true;
99  mFilter = nullptr;
100  }
101  else if ( !mFilterExp.isEmpty() )
102  {
103  delete mFilter;
105  }
106  else
107  {
108  mFilter = nullptr;
109  }
110 }
111 
113 {
114  mElseRules.clear();
115  Q_FOREACH ( Rule* rule, mChildren )
116  {
117  if ( rule->isElse() )
118  mElseRules << rule;
119  }
120 }
121 
123 {
124  Q_FOREACH ( const Rule* rule, mChildren )
125  {
126  if ( rule->settings() )
127  list << rule->ruleKey();
128 
129  rule->subProviderIds( list );
130  }
131 }
132 
133 
135 {
136  mChildren.append( rule );
137  rule->mParent = this;
138  updateElseRules();
139 }
140 
142 {
143  mChildren.insert( i, rule );
144  rule->mParent = this;
145  updateElseRules();
146 }
147 
149 {
150  delete mChildren.at( i );
151  mChildren.removeAt( i );
152  updateElseRules();
153 }
154 
156 {
157  // we could use a hash / map for search if this will be slow...
158 
159  if ( key == mRuleKey )
160  return this;
161 
162  Q_FOREACH ( Rule* rule, mChildren )
163  {
164  const Rule* r = rule->findRuleByKey( key );
165  if ( r )
166  return r;
167  }
168  return nullptr;
169 }
170 
172 {
175  newrule->setActive( mIsActive );
176  // clone children
177  Q_FOREACH ( Rule* rule, mChildren )
178  newrule->appendChild( rule->clone() );
179  return newrule;
180 }
181 
183 {
184  QgsPalLayerSettings* settings = nullptr;
185  QDomElement settingsElem = ruleElem.firstChildElement( "settings" );
186  if ( !settingsElem.isNull() )
187  {
188  settings = new QgsPalLayerSettings;
189  settings->readXml( settingsElem );
190  }
191 
192  QString filterExp = ruleElem.attribute( "filter" );
193  QString description = ruleElem.attribute( "description" );
194  int scaleMinDenom = ruleElem.attribute( "scalemindenom", "0" ).toInt();
195  int scaleMaxDenom = ruleElem.attribute( "scalemaxdenom", "0" ).toInt();
196  QString ruleKey = ruleElem.attribute( "key" );
197  Rule* rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, description );
198 
199  if ( !ruleKey.isEmpty() )
200  rule->mRuleKey = ruleKey;
201 
202  rule->setActive( ruleElem.attribute( "active", "1" ).toInt() );
203 
204  QDomElement childRuleElem = ruleElem.firstChildElement( "rule" );
205  while ( !childRuleElem.isNull() )
206  {
207  Rule* childRule = create( childRuleElem );
208  if ( childRule )
209  {
210  rule->appendChild( childRule );
211  }
212  else
213  {
214  //QgsDebugMsg( "failed to init a child rule!" );
215  }
216  childRuleElem = childRuleElem.nextSiblingElement( "rule" );
217  }
218 
219  return rule;
220 }
221 
223 {
224  QDomElement ruleElem = doc.createElement( "rule" );
225 
226  if ( mSettings )
227  {
228  ruleElem.appendChild( mSettings->writeXml( doc ) );
229  }
230  if ( !mFilterExp.isEmpty() )
231  ruleElem.setAttribute( "filter", mFilterExp );
232  if ( mScaleMinDenom != 0 )
233  ruleElem.setAttribute( "scalemindenom", mScaleMinDenom );
234  if ( mScaleMaxDenom != 0 )
235  ruleElem.setAttribute( "scalemaxdenom", mScaleMaxDenom );
236  if ( !mDescription.isEmpty() )
237  ruleElem.setAttribute( "description", mDescription );
238  if ( !mIsActive )
239  ruleElem.setAttribute( "active", 0 );
240  ruleElem.setAttribute( "key", mRuleKey );
241 
242  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
243  {
244  Rule* rule = *it;
245  ruleElem.appendChild( rule->save( doc ) );
246  }
247  return ruleElem;
248 }
249 
251 {
252  if ( mSettings )
253  {
254  // add provider!
255  QgsVectorLayerLabelProvider *p = provider->createProvider( layer, mRuleKey, false, mSettings );
256  delete subProviders.value( this, nullptr );
257  subProviders[this] = p;
258  }
259 
260  // call recursively
261  Q_FOREACH ( Rule* rule, mChildren )
262  {
263  rule->createSubProviders( layer, subProviders, provider );
264  }
265 }
266 
268 {
269  if ( mSettings )
270  {
271  QgsVectorLayerLabelProvider* p = subProviders[this];
272  if ( !p->prepare( context, attributeNames ) )
273  {
274  subProviders.remove( this );
275  delete p;
276  }
277  }
278 
279  if ( mFilter )
280  {
281  attributeNames << mFilter->referencedColumns();
282  mFilter->prepare( &context.expressionContext() );
283  }
284 
285  // call recursively
286  Q_FOREACH ( Rule* rule, mChildren )
287  {
288  rule->prepare( context, attributeNames, subProviders );
289  }
290 }
291 
293 {
294  if ( !isFilterOK( feature, context )
295  || !isScaleOK( context.rendererScale() ) )
296  return Filtered;
297 
298  bool registered = false;
299 
300  // do we have active subprovider for the rule?
301  if ( subProviders.contains( this ) && mIsActive )
302  {
303  subProviders[this]->registerFeature( feature, context, obstacleGeometry );
304  registered = true;
305  }
306 
307  bool willRegisterSomething = false;
308 
309  // call recursively
310  Q_FOREACH ( Rule* rule, mChildren )
311  {
312  // Don't process else rules yet
313  if ( !rule->isElse() )
314  {
315  RegisterResult res = rule->registerFeature( feature, context, subProviders, obstacleGeometry );
316  // consider inactive items as "registered" so the else rule will ignore them
317  willRegisterSomething |= ( res == Registered || res == Inactive );
318  registered |= willRegisterSomething;
319  }
320  }
321 
322  // If none of the rules passed then we jump into the else rules and process them.
323  if ( !willRegisterSomething )
324  {
325  Q_FOREACH ( Rule* rule, mElseRules )
326  {
327  registered |= rule->registerFeature( feature, context, subProviders, obstacleGeometry ) != Filtered;
328  }
329  }
330 
331  if ( !mIsActive )
332  return Inactive;
333  else if ( registered )
334  return Registered;
335  else
336  return Filtered;
337 }
338 
340 {
341  if ( ! mFilter || mElseRule )
342  return true;
343 
344  context.expressionContext().setFeature( f );
345  QVariant res = mFilter->evaluate( &context.expressionContext() );
346  return res.toInt() != 0;
347 }
348 
349 bool QgsRuleBasedLabeling::Rule::isScaleOK( double scale ) const
350 {
351  if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
352  return true;
353  if ( mScaleMinDenom == 0 && mScaleMaxDenom == 0 )
354  return true;
355  if ( mScaleMinDenom != 0 && mScaleMinDenom > scale )
356  return false;
357  if ( mScaleMaxDenom != 0 && mScaleMaxDenom < scale )
358  return false;
359  return true;
360 }
361 
363 
365  : mRootRule( root )
366 {
367 
368 }
369 
371 {
372  mRootRule = other.mRootRule->clone();
373 
374  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
375  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. visibility presets)
376  mRootRule->setRuleKey( other.mRootRule->ruleKey() );
377  RuleList origDescendants = other.mRootRule->descendants();
378  RuleList clonedDescendants = mRootRule->descendants();
379  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
380  for ( int i = 0; i < origDescendants.count(); ++i )
381  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
382 }
383 
385 {
386  delete mRootRule;
387 }
388 
389 
391 {
392  QDomElement rulesElem = element.firstChildElement( "rules" );
393 
394  Rule* root = Rule::create( rulesElem );
395  if ( !root )
396  return nullptr;
397 
398  QgsRuleBasedLabeling* rl = new QgsRuleBasedLabeling( root );
399  return rl;
400 }
401 
403 {
404  return "rule-based";
405 }
406 
408 {
409  QDomElement elem = doc.createElement( "labeling" );
410  elem.setAttribute( "type", "rule-based" );
411 
412  QDomElement rulesElem = mRootRule->save( doc );
413  rulesElem.setTagName( "rules" ); // instead of just "rule"
414  elem.appendChild( rulesElem );
415 
416  return elem;
417 }
418 
420 {
421  return new QgsRuleBasedLabelProvider( *this, layer, false );
422 }
423 
425 {
426  QStringList lst;
427  mRootRule->subProviderIds( lst );
428  return lst;
429 }
430 
432 {
433  Q_UNUSED( layer );
434  const Rule* rule = mRootRule->findRuleByKey( providerId );
435  if ( rule && rule->settings() )
436  return *rule->settings();
437 
438  return QgsPalLayerSettings();
439 }
Class for parsing and evaluation of expressions (formerly called "search strings").
void clear()
QgsPalLayerSettings * settings() const
Get the labeling settings.
double rendererScale() const
virtual QgsVectorLayerLabelProvider * provider(QgsVectorLayer *layer) const override
Factory for label provider implementation.
bool isScaleOK(double scale) const
Check if this rule applies for a given scale.
bool contains(const Key &key) const
Q_DECL_DEPRECATED QVariant evaluate(const QgsFeature *f)
Evaluate the feature and return the result.
void updateElseRules()
Check which child rules are else rules and update the internal list of else rules.
QgsRuleBasedLabeling::RuleToProviderMap mSubProviders
label providers are owned by labeling engine
virtual bool prepare(const QgsRenderContext &context, QStringList &attributeNames) override
Prepare for registration of features.
QDomNode appendChild(const QDomNode &newChild)
RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
int scaleMinDenom() const
The minimum scale at which this label rule should be applied.
QString attribute(const QString &name, const QString &defValue) const
void setTagName(const QString &name)
Q_DECL_DEPRECATED bool prepare(const QgsFields &fields)
Get the expression ready for evaluation - find out column indexes.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
bool isElse() const
Check if this rule is an ELSE rule.
RegisterResult
The result of registering a rule.
QStringList referencedColumns() const
Get list of columns referenced by the expression.
Rule * clone() const
clone this rule, return new instance
const T & at(int i) const
void removeAt(int i)
void prepare(const QgsRenderContext &context, QStringList &attributeNames, RuleToProviderMap &subProviders)
call prepare() on sub-providers and populate attributeNames
QDomElement nextSiblingElement(const QString &tagName) const
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:76
virtual QList< QgsAbstractLabelProvider * > subProviders() override
return subproviders
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:187
bool isFilterOK(QgsFeature &f, QgsRenderContext &context) const
Check if a given feature shall be labelled by this rule.
virtual bool prepare(const QgsRenderContext &context, QStringList &attributeNames)
Prepare for registration of features.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:353
virtual QgsPalLayerSettings settings(QgsVectorLayer *layer, const QString &providerId=QString()) const override
Get associated label settings.
virtual QDomElement save(QDomDocument &doc) const override
Return labeling configuration as XML element.
static Rule * create(const QDomElement &ruleElem)
Create a rule from an XML definition.
virtual QString type() const override
Unique type string of the labeling configuration implementation.
int count(const T &value) const
virtual QStringList subProviders() const override
Get list of sub-providers within the layer&#39;s labeling.
void append(const T &value)
void initFilter()
Initialize filters.
int toInt(bool *ok) const
void setAttribute(const QString &name, const QString &value)
int toInt(bool *ok, int base) const
virtual QgsVectorLayerLabelProvider * createProvider(QgsVectorLayer *layer, const QString &providerId, bool withFeatureLoop, const QgsPalLayerSettings *settings)
create a label provider
void setSettings(QgsPalLayerSettings *settings)
set new settings (or NULL). Deletes old settings if any.
QgsPalLayerSettings mSettings
Layer&#39;s labeling configuration.
bool isEmpty() const
QgsVectorLayerLabelProvider(QgsVectorLayer *layer, const QString &providerId, bool withFeatureLoop=true, const QgsPalLayerSettings *settings=nullptr, const QString &layerName=QString())
Convenience constructor to initialize the provider from given vector layer.
void readXml(QDomElement &elem)
Read settings from a DOM element.
const QgsLabelingEngineV2 * mEngine
Associated labeling engine.
void setEngine(const QgsLabelingEngineV2 *engine)
Associate provider with a labeling engine (should be only called internally from QgsLabelingEngineV2)...
QgsRuleBasedLabeling(QgsRuleBasedLabeling::Rule *root)
Constructs the labeling from given tree of rules (takes ownership)
const Rule * findRuleByKey(const QString &key) const
Try to find a rule given its unique key.
QDomElement save(QDomDocument &doc) const
store labeling info to XML element
static QgsRuleBasedLabeling * create(const QDomElement &element)
Create the instance from a DOM element with saved configuration.
void setActive(bool state)
Sets if this rule is active.
QgsExpressionContext & expressionContext()
Gets the expression context.
bool isNull() const
void setRuleKey(const QString &key)
Override the assigned rule key (should be used just internally by rule-based labeling) ...
void subProviderIds(QStringList &list) const
append rule keys of descendants that contain valid settings (i.e. they will be sub-providers) ...
QgsPalLayerSettings * mSettings
Contains information about the context of a rendering operation.
Rule(QgsPalLayerSettings *settings, int scaleMinDenom=0, int scaleMaxDenom=0, const QString &filterExp=QString(), const QString &description=QString(), bool elseRule=false)
takes ownership of settings
void insert(int i, const T &value)
QgsRuleBasedLabeling mRules
owned copy
QDomElement firstChildElement(const QString &tagName) const
virtual void registerFeature(QgsFeature &feature, QgsRenderContext &context, QgsGeometry *obstacleGeometry=nullptr) override
Register a feature for labeling as one or more QgsLabelFeature objects stored into mLabels...
void insertChild(int i, Rule *rule)
add child rule, take ownership, sets this as parent
QDomElement writeXml(QDomDocument &doc)
Write settings into a DOM element.
QString providerId() const
Returns provider ID - useful in case there is more than one label provider within a layer (e...
void appendChild(Rule *rule)
add child rule, take ownership, sets this as parent
RegisterResult registerFeature(QgsFeature &feature, QgsRenderContext &context, RuleToProviderMap &subProviders, QgsGeometry *obstacleGeometry=nullptr)
register individual features
const_iterator constEnd() const
int scaleMaxDenom() const
The maximum scale denominator at which this label rule should be applied.
QDomElement createElement(const QString &tagName)
const_iterator constBegin() const
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
int compare(const QString &other) const
QString toString() const
QgsRuleBasedLabelProvider(const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop=true)
QUuid createUuid()
const T value(const Key &key) const
int remove(const Key &key)