QGIS API Documentation  2.14.0-Essen
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 
18 
20  : QgsVectorLayerLabelProvider( layer, withFeatureLoop )
21  , mRules( rules )
22 {
24 }
25 
27 {
28  // sub-providers owned by labeling engine
29 }
30 
31 
32 bool QgsRuleBasedLabelProvider::prepare( const QgsRenderContext& context, QStringList& attributeNames )
33 {
34  Q_FOREACH ( QgsVectorLayerLabelProvider* provider, mSubProviders )
35  provider->setEngine( mEngine );
36 
37  // populate sub-providers
38  mRules.rootRule()->prepare( context, attributeNames, mSubProviders );
39  return true;
40 }
41 
43 {
44  // will register the feature to relevant sub-providers
45  mRules.rootRule()->registerFeature( feature, context, mSubProviders, obstacleGeometry );
46 }
47 
49 {
51  Q_FOREACH ( QgsVectorLayerLabelProvider* subprovider, mSubProviders )
52  lst << subprovider;
53  return lst;
54 }
55 
56 
58 
59 QgsRuleBasedLabeling::Rule::Rule( QgsPalLayerSettings* settings, int scaleMinDenom, int scaleMaxDenom, const QString& filterExp, const QString& description, bool elseRule )
60  : mParent( nullptr ), mSettings( settings )
61  , mScaleMinDenom( scaleMinDenom ), mScaleMaxDenom( scaleMaxDenom )
62  , mFilterExp( filterExp ), mDescription( description )
63  , mElseRule( elseRule )
64  , mIsActive( true )
65  , mFilter( nullptr )
66 {
67  initFilter();
68 }
69 
71 {
72  delete mSettings;
73  delete mFilter;
74  qDeleteAll( mChildren );
75  // do NOT delete parent
76 }
77 
79 {
80  if ( mSettings == settings )
81  return;
82 
83  delete mSettings;
85 }
86 
88 {
89  if ( mElseRule || mFilterExp.compare( "ELSE", Qt::CaseInsensitive ) == 0 )
90  {
91  mElseRule = true;
92  mFilter = nullptr;
93  }
94  else if ( !mFilterExp.isEmpty() )
95  {
96  delete mFilter;
98  }
99  else
100  {
101  mFilter = nullptr;
102  }
103 }
104 
106 {
107  mElseRules.clear();
108  Q_FOREACH ( Rule* rule, mChildren )
109  {
110  if ( rule->isElse() )
111  mElseRules << rule;
112  }
113 }
114 
115 
117 {
118  mChildren.append( rule );
119  rule->mParent = this;
120  updateElseRules();
121 }
122 
124 {
125  mChildren.insert( i, rule );
126  rule->mParent = this;
127  updateElseRules();
128 }
129 
131 {
132  delete mChildren.at( i );
133  mChildren.removeAt( i );
134  updateElseRules();
135 }
136 
138 {
141  newrule->setActive( mIsActive );
142  // clone children
143  Q_FOREACH ( Rule* rule, mChildren )
144  newrule->appendChild( rule->clone() );
145  return newrule;
146 }
147 
149 {
150  QgsPalLayerSettings* settings = nullptr;
151  QDomElement settingsElem = ruleElem.firstChildElement( "settings" );
152  if ( !settingsElem.isNull() )
153  {
154  settings = new QgsPalLayerSettings;
155  settings->readXml( settingsElem );
156  }
157 
158  QString filterExp = ruleElem.attribute( "filter" );
159  QString description = ruleElem.attribute( "description" );
160  int scaleMinDenom = ruleElem.attribute( "scalemindenom", "0" ).toInt();
161  int scaleMaxDenom = ruleElem.attribute( "scalemaxdenom", "0" ).toInt();
162  //QString ruleKey = ruleElem.attribute( "key" );
163  Rule* rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, description );
164 
165  //if ( !ruleKey.isEmpty() )
166  // rule->mRuleKey = ruleKey;
167 
168  rule->setActive( ruleElem.attribute( "active", "1" ).toInt() );
169 
170  QDomElement childRuleElem = ruleElem.firstChildElement( "rule" );
171  while ( !childRuleElem.isNull() )
172  {
173  Rule* childRule = create( childRuleElem );
174  if ( childRule )
175  {
176  rule->appendChild( childRule );
177  }
178  else
179  {
180  //QgsDebugMsg( "failed to init a child rule!" );
181  }
182  childRuleElem = childRuleElem.nextSiblingElement( "rule" );
183  }
184 
185  return rule;
186 }
187 
189 {
190  QDomElement ruleElem = doc.createElement( "rule" );
191 
192  if ( mSettings )
193  {
194  ruleElem.appendChild( mSettings->writeXml( doc ) );
195  }
196  if ( !mFilterExp.isEmpty() )
197  ruleElem.setAttribute( "filter", mFilterExp );
198  if ( mScaleMinDenom != 0 )
199  ruleElem.setAttribute( "scalemindenom", mScaleMinDenom );
200  if ( mScaleMaxDenom != 0 )
201  ruleElem.setAttribute( "scalemaxdenom", mScaleMaxDenom );
202  if ( !mDescription.isEmpty() )
203  ruleElem.setAttribute( "description", mDescription );
204  if ( !mIsActive )
205  ruleElem.setAttribute( "active", 0 );
206  //ruleElem.setAttribute( "key", mRuleKey );
207 
208  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
209  {
210  Rule* rule = *it;
211  ruleElem.appendChild( rule->save( doc ) );
212  }
213  return ruleElem;
214 }
215 
217 {
218  if ( mSettings )
219  {
220  // add provider!
222  subProviders[this] = p;
223  }
224 
225  // call recursively
226  Q_FOREACH ( Rule* rule, mChildren )
227  {
228  rule->createSubProviders( layer, subProviders );
229  }
230 }
231 
233 {
234  if ( mSettings )
235  {
236  QgsVectorLayerLabelProvider* p = subProviders[this];
237  if ( !p->prepare( context, attributeNames ) )
238  {
239  subProviders.remove( this );
240  delete p;
241  }
242  }
243 
244  if ( mFilter )
245  {
246  attributeNames << mFilter->referencedColumns();
247  mFilter->prepare( &context.expressionContext() );
248  }
249 
250  // call recursively
251  Q_FOREACH ( Rule* rule, mChildren )
252  {
253  rule->prepare( context, attributeNames, subProviders );
254  }
255 }
256 
258 {
259  if ( !isFilterOK( feature, context )
260  || !isScaleOK( context.rendererScale() ) )
261  return Filtered;
262 
263  bool registered = false;
264 
265  // do we have active subprovider for the rule?
266  if ( subProviders.contains( this ) && mIsActive )
267  {
268  subProviders[this]->registerFeature( feature, context, obstacleGeometry );
269  registered = true;
270  }
271 
272  bool willRegisterSomething = false;
273 
274  // call recursively
275  Q_FOREACH ( Rule* rule, mChildren )
276  {
277  // Don't process else rules yet
278  if ( !rule->isElse() )
279  {
280  RegisterResult res = rule->registerFeature( feature, context, subProviders, obstacleGeometry );
281  // consider inactive items as "registered" so the else rule will ignore them
282  willRegisterSomething |= ( res == Registered || res == Inactive );
283  registered |= willRegisterSomething;
284  }
285  }
286 
287  // If none of the rules passed then we jump into the else rules and process them.
288  if ( !willRegisterSomething )
289  {
290  Q_FOREACH ( Rule* rule, mElseRules )
291  {
292  registered |= rule->registerFeature( feature, context, subProviders, obstacleGeometry ) != Filtered;
293  }
294  }
295 
296  if ( !mIsActive )
297  return Inactive;
298  else if ( registered )
299  return Registered;
300  else
301  return Filtered;
302 }
303 
305 {
306  if ( ! mFilter || mElseRule )
307  return true;
308 
309  context.expressionContext().setFeature( f );
310  QVariant res = mFilter->evaluate( &context.expressionContext() );
311  return res.toInt() != 0;
312 }
313 
314 bool QgsRuleBasedLabeling::Rule::isScaleOK( double scale ) const
315 {
316  if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
317  return true;
318  if ( mScaleMinDenom == 0 && mScaleMaxDenom == 0 )
319  return true;
320  if ( mScaleMinDenom != 0 && mScaleMinDenom > scale )
321  return false;
322  if ( mScaleMaxDenom != 0 && mScaleMaxDenom < scale )
323  return false;
324  return true;
325 }
326 
328 
330  : mRootRule( root )
331 {
332 
333 }
334 
336 {
337  mRootRule = other.mRootRule->clone();
338 }
339 
341 {
342  delete mRootRule;
343 }
344 
346 {
347  QDomElement rulesElem = element.firstChildElement( "rules" );
348 
349  Rule* root = Rule::create( rulesElem );
350  if ( !root )
351  return nullptr;
352 
353  QgsRuleBasedLabeling* rl = new QgsRuleBasedLabeling( root );
354  return rl;
355 }
356 
358 {
359  return "rule-based";
360 }
361 
363 {
364  QDomElement elem = doc.createElement( "labeling" );
365  elem.setAttribute( "type", "rule-based" );
366 
367  QDomElement rulesElem = mRootRule->save( doc );
368  rulesElem.setTagName( "rules" ); // instead of just "rule"
369  elem.appendChild( rulesElem );
370 
371  return elem;
372 }
373 
375 {
376  return new QgsRuleBasedLabelProvider( *this, layer, false );
377 }
Class for parsing and evaluation of expressions (formerly called "search strings").
void clear()
QgsPalLayerSettings * settings() const
Get the labeling settings.
virtual QgsVectorLayerLabelProvider * provider(QgsVectorLayer *layer) const override
Factory for label provider implementation.
bool isElse() const
Check if this rule is an ELSE rule.
QStringList referencedColumns() const
Get list of columns referenced by the expression.
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.
QString description() const
A human readable description for this rule.
QDomNode appendChild(const QDomNode &newChild)
bool isFilterOK(QgsFeature &f, QgsRenderContext &context) const
Check if a given feature shall be labelled by this rule.
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.
QDomElement save(QDomDocument &doc) const
store labeling info to XML element
RegisterResult
The result of registering a rule.
double rendererScale() const
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
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 list of child providers - useful if the provider needs to put labels into more layers with dif...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
virtual bool prepare(const QgsRenderContext &context, QStringList &attributeNames)
Prepare for registration of features.
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Definition: qgis.h:285
int scaleMinDenom() const
The minimum scale at which this label rule should be applied.
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.
void createSubProviders(QgsVectorLayer *layer, RuleToProviderMap &subProviders)
add providers
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
void setSettings(QgsPalLayerSettings *settings)
set new settings (or NULL). Deletes old settings if any.
QgsPalLayerSettings mSettings
Layer&#39;s labeling configuration.
bool isEmpty() const
bool isScaleOK(double scale) const
Check if this rule applies for a given scale.
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)
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
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
Rule * clone() const
clone this rule, return new instance
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.
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
QDomElement createElement(const QString &tagName)
const_iterator constBegin() const
void removeChildAt(int i)
delete child rule
int scaleMaxDenom() const
The maximum scale denominator at which this label rule should be applied.
Represents a vector layer which manages a vector based data sets.
int compare(const QString &other) const
QgsRuleBasedLabelProvider(const QgsRuleBasedLabeling &rules, QgsVectorLayer *layer, bool withFeatureLoop=true)
int remove(const Key &key)