QGIS API Documentation  3.0.2-Girona (307d082)
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, int scaleMinDenom, int scaleMaxDenom, const QString &filterExp, const QString &description, bool elseRule )
58  : mParent( nullptr )
59  , mSettings( settings )
60  , mMaximumScale( scaleMinDenom )
61  , mMinimumScale( scaleMaxDenom )
62  , mFilterExp( filterExp )
63  , mDescription( description )
64  , mElseRule( elseRule )
65  , mIsActive( true )
66 
67 {
68  mRuleKey = QUuid::createUuid().toString();
69  initFilter();
70 }
71 
73 {
74  delete mSettings;
75  delete mFilter;
76  qDeleteAll( mChildren );
77  // do NOT delete parent
78 }
79 
81 {
82  if ( mSettings == settings )
83  return;
84 
85  delete mSettings;
86  mSettings = settings;
87 }
88 
89 void QgsRuleBasedLabeling::Rule::initFilter()
90 {
91  if ( mElseRule || mFilterExp.compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
92  {
93  mElseRule = true;
94  mFilter = nullptr;
95  }
96  else if ( !mFilterExp.isEmpty() )
97  {
98  delete mFilter;
99  mFilter = new QgsExpression( mFilterExp );
100  }
101  else
102  {
103  mFilter = nullptr;
104  }
105 }
106 
107 void QgsRuleBasedLabeling::Rule::updateElseRules()
108 {
109  mElseRules.clear();
110  Q_FOREACH ( Rule *rule, mChildren )
111  {
112  if ( rule->isElse() )
113  mElseRules << rule;
114  }
115 }
116 
118 {
119  if ( mSettings && mSettings->format().containsAdvancedEffects() )
120  return true;
121 
122  Q_FOREACH ( Rule *rule, mChildren )
123  {
124  if ( rule->requiresAdvancedEffects() )
125  return true;
126  }
127 
128  return false;
129 }
130 
131 void QgsRuleBasedLabeling::Rule::subProviderIds( QStringList &list ) const
132 {
133  Q_FOREACH ( const Rule *rule, mChildren )
134  {
135  if ( rule->settings() )
136  list << rule->ruleKey();
137 
138  rule->subProviderIds( list );
139  }
140 }
141 
142 
144 {
145  mChildren.append( rule );
146  rule->mParent = this;
147  updateElseRules();
148 }
149 
151 {
152  mChildren.insert( i, rule );
153  rule->mParent = this;
154  updateElseRules();
155 }
156 
158 {
159  delete mChildren.at( i );
160  mChildren.removeAt( i );
161  updateElseRules();
162 }
163 
165 {
166  // we could use a hash / map for search if this will be slow...
167 
168  if ( key == mRuleKey )
169  return this;
170 
171  Q_FOREACH ( Rule *rule, mChildren )
172  {
173  const Rule *r = rule->findRuleByKey( key );
174  if ( r )
175  return r;
176  }
177  return nullptr;
178 }
179 
181 {
182  if ( key == mRuleKey )
183  return this;
184 
185  for ( Rule *rule : qgis::as_const( mChildren ) )
186  {
187  Rule *r = rule->findRuleByKey( key );
188  if ( r )
189  return r;
190  }
191  return nullptr;
192 }
193 
195 {
196  QgsPalLayerSettings *s = mSettings ? new QgsPalLayerSettings( *mSettings ) : nullptr;
197  Rule *newrule = new Rule( s, mMaximumScale, mMinimumScale, mFilterExp, mDescription );
198  newrule->setActive( mIsActive );
199  // clone children
200  Q_FOREACH ( Rule *rule, mChildren )
201  newrule->appendChild( rule->clone() );
202  return newrule;
203 }
204 
206 {
207  QgsPalLayerSettings *settings = nullptr;
208  QDomElement settingsElem = ruleElem.firstChildElement( QStringLiteral( "settings" ) );
209  if ( !settingsElem.isNull() )
210  {
211  settings = new QgsPalLayerSettings;
212  settings->readXml( settingsElem, context );
213  }
214 
215  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
216  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
217  int scaleMinDenom = ruleElem.attribute( QStringLiteral( "scalemindenom" ), QStringLiteral( "0" ) ).toInt();
218  int scaleMaxDenom = ruleElem.attribute( QStringLiteral( "scalemaxdenom" ), QStringLiteral( "0" ) ).toInt();
219  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
220  Rule *rule = new Rule( settings, scaleMinDenom, scaleMaxDenom, filterExp, description );
221 
222  if ( !ruleKey.isEmpty() )
223  rule->mRuleKey = ruleKey;
224 
225  rule->setActive( ruleElem.attribute( QStringLiteral( "active" ), QStringLiteral( "1" ) ).toInt() );
226 
227  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
228  while ( !childRuleElem.isNull() )
229  {
230  Rule *childRule = create( childRuleElem, context );
231  if ( childRule )
232  {
233  rule->appendChild( childRule );
234  }
235  else
236  {
237  //QgsDebugMsg( "failed to init a child rule!" );
238  }
239  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
240  }
241 
242  return rule;
243 }
244 
245 QDomElement QgsRuleBasedLabeling::Rule::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
246 {
247  QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
248 
249  if ( mSettings )
250  {
251  ruleElem.appendChild( mSettings->writeXml( doc, context ) );
252  }
253  if ( !mFilterExp.isEmpty() )
254  ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
255  if ( mMaximumScale != 0 )
256  ruleElem.setAttribute( QStringLiteral( "scalemindenom" ), mMaximumScale );
257  if ( mMinimumScale != 0 )
258  ruleElem.setAttribute( QStringLiteral( "scalemaxdenom" ), mMinimumScale );
259  if ( !mDescription.isEmpty() )
260  ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
261  if ( !mIsActive )
262  ruleElem.setAttribute( QStringLiteral( "active" ), 0 );
263  ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
264 
265  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
266  {
267  Rule *rule = *it;
268  ruleElem.appendChild( rule->save( doc, context ) );
269  }
270  return ruleElem;
271 }
272 
274 {
275  if ( mSettings )
276  {
277  // add provider!
278  QgsVectorLayerLabelProvider *p = provider->createProvider( layer, mRuleKey, false, mSettings );
279  delete subProviders.value( this, nullptr );
280  subProviders[this] = p;
281  }
282 
283  // call recursively
284  Q_FOREACH ( Rule *rule, mChildren )
285  {
286  rule->createSubProviders( layer, subProviders, provider );
287  }
288 }
289 
291 {
292  if ( mSettings )
293  {
294  QgsVectorLayerLabelProvider *p = subProviders[this];
295  if ( !p->prepare( context, attributeNames ) )
296  {
297  subProviders.remove( this );
298  delete p;
299  }
300  }
301 
302  if ( mFilter )
303  {
304  attributeNames.unite( mFilter->referencedColumns() );
305  mFilter->prepare( &context.expressionContext() );
306  }
307 
308  // call recursively
309  Q_FOREACH ( Rule *rule, mChildren )
310  {
311  rule->prepare( context, attributeNames, subProviders );
312  }
313 }
314 
316 {
317  if ( !isFilterOK( feature, context )
318  || !isScaleOK( context.rendererScale() ) )
319  return Filtered;
320 
321  bool registered = false;
322 
323  // do we have active subprovider for the rule?
324  if ( subProviders.contains( this ) && mIsActive )
325  {
326  subProviders[this]->registerFeature( feature, context, obstacleGeometry );
327  registered = true;
328  }
329 
330  bool willRegisterSomething = false;
331 
332  // call recursively
333  Q_FOREACH ( Rule *rule, mChildren )
334  {
335  // Don't process else rules yet
336  if ( !rule->isElse() )
337  {
338  RegisterResult res = rule->registerFeature( feature, context, subProviders, obstacleGeometry );
339  // consider inactive items as "registered" so the else rule will ignore them
340  willRegisterSomething |= ( res == Registered || res == Inactive );
341  registered |= willRegisterSomething;
342  }
343  }
344 
345  // If none of the rules passed then we jump into the else rules and process them.
346  if ( !willRegisterSomething )
347  {
348  Q_FOREACH ( Rule *rule, mElseRules )
349  {
350  registered |= rule->registerFeature( feature, context, subProviders, obstacleGeometry ) != Filtered;
351  }
352  }
353 
354  if ( !mIsActive )
355  return Inactive;
356  else if ( registered )
357  return Registered;
358  else
359  return Filtered;
360 }
361 
362 bool QgsRuleBasedLabeling::Rule::isFilterOK( QgsFeature &f, QgsRenderContext &context ) const
363 {
364  if ( ! mFilter || mElseRule )
365  return true;
366 
367  context.expressionContext().setFeature( f );
368  QVariant res = mFilter->evaluate( &context.expressionContext() );
369  return res.toInt() != 0;
370 }
371 
372 bool QgsRuleBasedLabeling::Rule::isScaleOK( double scale ) const
373 {
374  if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
375  return true;
376  if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) )
377  return true;
378  if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale )
379  return false;
380  if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )
381  return false;
382  return true;
383 }
384 
386 
388  : mRootRule( root )
389 {
390 }
391 
393 {
395 
396  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
397  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
398  rootRule->setRuleKey( mRootRule->ruleKey() );
399  RuleList origDescendants = mRootRule->descendants();
400  RuleList clonedDescendants = rootRule->descendants();
401  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
402  for ( int i = 0; i < origDescendants.count(); ++i )
403  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
404 
405  return new QgsRuleBasedLabeling( rootRule );
406 }
407 
409 {
410  delete mRootRule;
411 }
412 
413 
414 QgsRuleBasedLabeling *QgsRuleBasedLabeling::create( const QDomElement &element, const QgsReadWriteContext &context )
415 {
416  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
417 
418  Rule *root = Rule::create( rulesElem, context );
419  if ( !root )
420  return nullptr;
421 
422  QgsRuleBasedLabeling *rl = new QgsRuleBasedLabeling( root );
423  return rl;
424 }
425 
427 {
428  return QStringLiteral( "rule-based" );
429 }
430 
431 QDomElement QgsRuleBasedLabeling::save( QDomDocument &doc, const QgsReadWriteContext &context ) const
432 {
433  QDomElement elem = doc.createElement( QStringLiteral( "labeling" ) );
434  elem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "rule-based" ) );
435 
436  QDomElement rulesElem = mRootRule->save( doc, context );
437  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
438  elem.appendChild( rulesElem );
439 
440  return elem;
441 }
442 
444 {
445  return new QgsRuleBasedLabelProvider( *this, layer, false );
446 }
447 
449 {
450  QStringList lst;
451  mRootRule->subProviderIds( lst );
452  return lst;
453 }
454 
455 QgsPalLayerSettings QgsRuleBasedLabeling::settings( const QString &providerId ) const
456 {
457  const Rule *rule = mRootRule->findRuleByKey( providerId );
458  if ( rule && rule->settings() )
459  return *rule->settings();
460 
461  return QgsPalLayerSettings();
462 }
463 
465 {
467 }
468 
470 {
471  if ( settings )
472  {
473  Rule *rule = mRootRule->findRuleByKey( providerId );
474  if ( rule && rule->settings() )
475  rule->setSettings( settings );
476  }
477 }
478 
479 void QgsRuleBasedLabeling::toSld( QDomNode &parent, const QgsStringMap &props ) const
480 {
481  if ( !mRootRule )
482  {
483  return;
484  }
485 
487  for ( Rule *rule : rules )
488  {
489  QgsPalLayerSettings *settings = rule->settings();
490 
491  if ( settings->drawLabels )
492  {
493  QDomDocument doc = parent.ownerDocument();
494 
495  QDomElement ruleElement = doc.createElement( QStringLiteral( "se:Rule" ) );
496  parent.appendChild( ruleElement );
497 
498  if ( !rule->filterExpression().isEmpty() )
499  {
500  QgsSymbolLayerUtils::createFunctionElement( doc, ruleElement, rule->filterExpression() );
501  }
502 
503  // scale dependencies, the actual behavior is that the PAL settings min/max and
504  // the rule min/max get intersected
505  QgsStringMap localProps = QgsStringMap( props );
506  QgsSymbolLayerUtils::mergeScaleDependencies( rule->maximumScale(), rule->minimumScale(), localProps );
507  if ( settings->scaleVisibility )
508  {
509  QgsSymbolLayerUtils::mergeScaleDependencies( settings->maximumScale, settings->minimumScale, localProps );
510  }
511  QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElement, localProps );
512 
513  QgsAbstractVectorLayerLabeling::writeTextSymbolizer( ruleElement, *settings, props );
514  }
515 
516  }
517 
518 }
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
Get 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)
Rule(QgsPalLayerSettings *settings, int maximumScale=0, int minimumScale=0, const QString &filterExp=QString(), const QString &description=QString(), bool elseRule=false)
takes ownership of settings
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
Return 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
bool isElse() const
Check if this rule is an ELSE rule.
RegisterResult
The result of registering a rule.
QgsRuleBasedLabeling::Rule * clone() const
clone this rule, return new instance
static void mergeScaleDependencies(int mScaleMinDenom, int mScaleMaxDenom, QgsStringMap &props)
Merges the local scale limits, if any, with the ones already in the map, if any.
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
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:62
QMap< QgsRuleBasedLabeling::Rule *, QgsVectorLayerLabelProvider * > RuleToProviderMap
const QgsRuleBasedLabeling::RuleList & children() const
Return all children rules of this rule.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:479
bool qgsDoubleNear(double a, double b, double epsilon=4 *DBL_EPSILON)
Compare two doubles (but allow some difference)
Definition: qgis.h:251
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.
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
Get 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
QDomElement writeXml(QDomDocument &doc, const QgsReadWriteContext &context)
Write settings into a DOM element.
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.
QgsRuleBasedLabeling::Rule * rootRule()
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.
const QgsTextFormat & format() const
Returns the label text formatting settings, e.g., font settings, buffer settings, etc...
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
Return 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
Get 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.
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...
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
Represents a vector layer which manages a vector based data sets.
void createSubProviders(QgsVectorLayer *layer, RuleToProviderMap &subProviders, QgsRuleBasedLabelProvider *provider)
add providers
bool containsAdvancedEffects() const
Returns true if any component of the font format requires advanced effects such as blend modes...
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.