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