QGIS API Documentation  3.8.0-Zanzibar (11aff65)
qgsrulebasedrenderer.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsrulebasedrenderer.cpp - Rule-based renderer (symbology)
3  ---------------------
4  begin : May 2010
5  copyright : (C) 2010 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 
16 #include "qgsrulebasedrenderer.h"
17 #include "qgssymbollayer.h"
18 #include "qgsexpression.h"
19 #include "qgssymbollayerutils.h"
20 #include "qgsrendercontext.h"
21 #include "qgsvectorlayer.h"
22 #include "qgslogger.h"
23 #include "qgsogcutils.h"
27 #include "qgspainteffect.h"
28 #include "qgspainteffectregistry.h"
29 #include "qgsproperty.h"
30 
31 #include <QSet>
32 
33 #include <QDomDocument>
34 #include <QDomElement>
35 #include <QUuid>
36 
37 
38 QgsRuleBasedRenderer::Rule::Rule( QgsSymbol *symbol, int scaleMinDenom, int scaleMaxDenom, const QString &filterExp, const QString &label, const QString &description, bool elseRule )
39  : mParent( nullptr )
40  , mSymbol( symbol )
41  , mMaximumScale( scaleMinDenom )
42  , mMinimumScale( scaleMaxDenom )
43  , mFilterExp( filterExp )
44  , mLabel( label )
45  , mDescription( description )
46  , mElseRule( elseRule )
47 {
48  if ( mElseRule )
49  mFilterExp = QStringLiteral( "ELSE" );
50 
51  mRuleKey = QUuid::createUuid().toString();
52  initFilter();
53 }
54 
56 {
57  qDeleteAll( mChildren );
58  // do NOT delete parent
59 }
60 
62 {
63  if ( mFilterExp.trimmed().compare( QLatin1String( "ELSE" ), Qt::CaseInsensitive ) == 0 )
64  {
65  mElseRule = true;
66  mFilter.reset();
67  }
68  else if ( mFilterExp.trimmed().isEmpty() )
69  {
70  mElseRule = false;
71  mFilter.reset();
72  }
73  else
74  {
75  mElseRule = false;
76  mFilter = qgis::make_unique< QgsExpression >( mFilterExp );
77  }
78 }
79 
81 {
82  mChildren.append( rule );
83  rule->mParent = this;
84  updateElseRules();
85 }
86 
88 {
89  mChildren.insert( i, rule );
90  rule->mParent = this;
91  updateElseRules();
92 }
93 
95 {
96  mChildren.removeAll( rule );
97  delete rule;
98  updateElseRules();
99 }
100 
102 {
103  delete mChildren.takeAt( i );
104  updateElseRules();
105 }
106 
108 {
109  mChildren.removeAll( rule );
110  rule->mParent = nullptr;
111  updateElseRules();
112  return rule;
113 }
114 
116 {
117  Rule *rule = mChildren.takeAt( i );
118  rule->mParent = nullptr;
119  updateElseRules();
120  return rule;
121 }
122 
124 {
125  // we could use a hash / map for search if this will be slow...
126 
127  if ( key == mRuleKey )
128  return this;
129 
130  const auto constMChildren = mChildren;
131  for ( Rule *rule : constMChildren )
132  {
133  Rule *r = rule->findRuleByKey( key );
134  if ( r )
135  return r;
136  }
137  return nullptr;
138 }
139 
140 void QgsRuleBasedRenderer::Rule::updateElseRules()
141 {
142  mElseRules.clear();
143  const auto constMChildren = mChildren;
144  for ( Rule *rule : constMChildren )
145  {
146  if ( rule->isElse() )
147  mElseRules << rule;
148  }
149 }
150 
152 {
153  mFilterExp = QStringLiteral( "ELSE" );
154  mElseRule = iselse;
155  mFilter.reset();
156 }
157 
158 
159 QString QgsRuleBasedRenderer::Rule::dump( int indent ) const
160 {
161  QString off;
162  off.fill( QChar( ' ' ), indent );
163  QString symbolDump = ( mSymbol ? mSymbol->dump() : QStringLiteral( "[]" ) );
164  QString msg = off + QStringLiteral( "RULE %1 - scale [%2,%3] - filter %4 - symbol %5\n" )
165  .arg( mLabel ).arg( mMaximumScale ).arg( mMinimumScale )
166  .arg( mFilterExp, symbolDump );
167 
168  QStringList lst;
169  const auto constMChildren = mChildren;
170  for ( Rule *rule : constMChildren )
171  {
172  lst.append( rule->dump( indent + 2 ) );
173  }
174  msg += lst.join( QStringLiteral( "\n" ) );
175  return msg;
176 }
177 
179 {
180  // attributes needed by this rule
181  QSet<QString> attrs;
182  if ( mFilter )
183  attrs.unite( mFilter->referencedColumns() );
184  if ( mSymbol )
185  attrs.unite( mSymbol->usedAttributes( context ) );
186 
187  // attributes needed by child rules
188  const auto constMChildren = mChildren;
189  for ( Rule *rule : constMChildren )
190  {
191  attrs.unite( rule->usedAttributes( context ) );
192  }
193  return attrs;
194 }
195 
197 {
198  if ( mFilter && mFilter->needsGeometry() )
199  return true;
200 
201  const auto constMChildren = mChildren;
202  for ( Rule *rule : constMChildren )
203  {
204  if ( rule->needsGeometry() )
205  return true;
206  }
207 
208  return false;
209 }
210 
212 {
213  QgsSymbolList lst;
214  if ( mSymbol )
215  lst.append( mSymbol.get() );
216 
217  const auto constMChildren = mChildren;
218  for ( Rule *rule : constMChildren )
219  {
220  lst += rule->symbols( context );
221  }
222  return lst;
223 }
224 
226 {
227  mSymbol.reset( sym );
228 }
229 
230 void QgsRuleBasedRenderer::Rule::setFilterExpression( const QString &filterExp )
231 {
232  mFilterExp = filterExp;
233  initFilter();
234 }
235 
237 {
239  if ( currentLevel != -1 ) // root rule should not be shown
240  {
241  lst << QgsLegendSymbolItem( mSymbol.get(), mLabel, mRuleKey, true, mMaximumScale, mMinimumScale, currentLevel, mParent ? mParent->mRuleKey : QString() );
242  }
243 
244  for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
245  {
246  Rule *rule = *it;
247  lst << rule->legendSymbolItems( currentLevel + 1 );
248  }
249  return lst;
250 }
251 
252 
254 {
255  if ( ! mFilter || mElseRule || ! context )
256  return true;
257 
258  context->expressionContext().setFeature( f );
259  QVariant res = mFilter->evaluate( &context->expressionContext() );
260  return res.toBool();
261 }
262 
263 bool QgsRuleBasedRenderer::Rule::isScaleOK( double scale ) const
264 {
265  if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
266  return true;
267  if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) )
268  return true;
269  if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && mMaximumScale > scale )
270  return false;
271  if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && mMinimumScale < scale )
272  return false;
273  return true;
274 }
275 
277 {
278  QgsSymbol *sym = mSymbol ? mSymbol->clone() : nullptr;
279  Rule *newrule = new Rule( sym, mMaximumScale, mMinimumScale, mFilterExp, mLabel, mDescription );
280  newrule->setActive( mIsActive );
281  // clone children
282  const auto constMChildren = mChildren;
283  for ( Rule *rule : constMChildren )
284  newrule->appendChild( rule->clone() );
285  return newrule;
286 }
287 
288 QDomElement QgsRuleBasedRenderer::Rule::save( QDomDocument &doc, QgsSymbolMap &symbolMap ) const
289 {
290  QDomElement ruleElem = doc.createElement( QStringLiteral( "rule" ) );
291 
292  if ( mSymbol )
293  {
294  int symbolIndex = symbolMap.size();
295  symbolMap[QString::number( symbolIndex )] = mSymbol.get();
296  ruleElem.setAttribute( QStringLiteral( "symbol" ), symbolIndex );
297  }
298  if ( !mFilterExp.isEmpty() )
299  ruleElem.setAttribute( QStringLiteral( "filter" ), mFilterExp );
300  if ( mMaximumScale != 0 )
301  ruleElem.setAttribute( QStringLiteral( "scalemindenom" ), mMaximumScale );
302  if ( mMinimumScale != 0 )
303  ruleElem.setAttribute( QStringLiteral( "scalemaxdenom" ), mMinimumScale );
304  if ( !mLabel.isEmpty() )
305  ruleElem.setAttribute( QStringLiteral( "label" ), mLabel );
306  if ( !mDescription.isEmpty() )
307  ruleElem.setAttribute( QStringLiteral( "description" ), mDescription );
308  if ( !mIsActive )
309  ruleElem.setAttribute( QStringLiteral( "checkstate" ), 0 );
310  ruleElem.setAttribute( QStringLiteral( "key" ), mRuleKey );
311 
312  const auto constMChildren = mChildren;
313  for ( Rule *rule : constMChildren )
314  {
315  ruleElem.appendChild( rule->save( doc, symbolMap ) );
316  }
317  return ruleElem;
318 }
319 
320 void QgsRuleBasedRenderer::Rule::toSld( QDomDocument &doc, QDomElement &element, QgsStringMap props ) const
321 {
322  // do not convert this rule if there are no symbols
323  QgsRenderContext context;
324  if ( symbols( context ).isEmpty() )
325  return;
326 
327  if ( !mFilterExp.isEmpty() )
328  {
329  if ( !props.value( QStringLiteral( "filter" ), QString() ).isEmpty() )
330  props[ QStringLiteral( "filter" )] += QLatin1String( " AND " );
331  props[ QStringLiteral( "filter" )] += mFilterExp;
332  }
333 
334  QgsSymbolLayerUtils::mergeScaleDependencies( mMaximumScale, mMinimumScale, props );
335 
336  if ( mSymbol )
337  {
338  QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
339  element.appendChild( ruleElem );
340 
341  //XXX: <se:Name> is the rule identifier, but our the Rule objects
342  // have no properties could be used as identifier. Use the label.
343  QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
344  nameElem.appendChild( doc.createTextNode( mLabel ) );
345  ruleElem.appendChild( nameElem );
346 
347  if ( !mLabel.isEmpty() || !mDescription.isEmpty() )
348  {
349  QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
350  if ( !mLabel.isEmpty() )
351  {
352  QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
353  titleElem.appendChild( doc.createTextNode( mLabel ) );
354  descrElem.appendChild( titleElem );
355  }
356  if ( !mDescription.isEmpty() )
357  {
358  QDomElement abstractElem = doc.createElement( QStringLiteral( "se:Abstract" ) );
359  abstractElem.appendChild( doc.createTextNode( mDescription ) );
360  descrElem.appendChild( abstractElem );
361  }
362  ruleElem.appendChild( descrElem );
363  }
364 
365  if ( !props.value( QStringLiteral( "filter" ), QString() ).isEmpty() )
366  {
367  QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, props.value( QStringLiteral( "filter" ), QString() ) );
368  }
369 
370  QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
371 
372  mSymbol->toSld( doc, ruleElem, props );
373  }
374 
375  // loop into children rule list
376  const auto constMChildren = mChildren;
377  for ( Rule *rule : constMChildren )
378  {
379  rule->toSld( doc, element, props );
380  }
381 }
382 
384 {
385  mActiveChildren.clear();
386 
387  if ( ! mIsActive )
388  return false;
389 
390  // filter out rules which are not compatible with this scale
391  if ( !isScaleOK( context.rendererScale() ) )
392  return false;
393 
394  // init this rule
395  if ( mFilter )
396  mFilter->prepare( &context.expressionContext() );
397  if ( mSymbol )
398  mSymbol->startRender( context, fields );
399 
400  // init children
401  // build temporary list of active rules (usable with this scale)
402  QStringList subfilters;
403  const auto constMChildren = mChildren;
404  for ( Rule *rule : constMChildren )
405  {
406  QString subfilter;
407  if ( rule->startRender( context, fields, subfilter ) )
408  {
409  // only add those which are active with current scale
410  mActiveChildren.append( rule );
411  subfilters.append( subfilter );
412  }
413  }
414 
415  // subfilters (on the same level) are joined with OR
416  // Finally they are joined with their parent (this) with AND
417  QString sf;
418  // If there are subfilters present (and it's not a single empty one), group them and join them with OR
419  if ( subfilters.length() > 1 || !subfilters.value( 0 ).isEmpty() )
420  {
421  if ( subfilters.contains( QStringLiteral( "TRUE" ) ) )
422  {
423  sf = QStringLiteral( "TRUE" );
424  }
425  // If we have more than 50 rules (to stay on the safe side) make a binary tree or SQLITE will fail,
426  // see: https://github.com/qgis/QGIS/issues/27269
427  else if ( subfilters.count() > 50 )
428  {
429  std::function<QString( const QStringList & )>bt = [ &bt ]( const QStringList & subf )
430  {
431  if ( subf.count( ) == 1 )
432  {
433  return subf.at( 0 );
434  }
435  else if ( subf.count( ) == 2 )
436  {
437  return subf.join( QStringLiteral( ") OR (" ) ).prepend( '(' ).append( ')' );
438  }
439  else
440  {
441  int midpos = static_cast<int>( subf.length() / 2 );
442  return QStringLiteral( "(%1) OR (%2)" ).arg( bt( subf.mid( 0, midpos ) ) ).arg( bt( subf.mid( midpos ) ) );
443  }
444  };
445  sf = bt( subfilters );
446  }
447  else
448  {
449  sf = subfilters.join( QStringLiteral( ") OR (" ) ).prepend( '(' ).append( ')' );
450  }
451  }
452 
453  // Now join the subfilters with their parent (this) based on if
454  // * The parent is an else rule
455  // * The existence of parent filter and subfilters
456 
457  // No filter expression: ELSE rule or catchall rule
458  if ( !mFilter )
459  {
460  if ( mSymbol || sf.isEmpty() )
461  filter = QStringLiteral( "TRUE" );
462  else
463  filter = sf;
464  }
465  else if ( mSymbol )
466  filter = mFilterExp;
467  else if ( !mFilterExp.trimmed().isEmpty() && !sf.isEmpty() )
468  filter = QStringLiteral( "(%1) AND (%2)" ).arg( mFilterExp, sf );
469  else if ( !mFilterExp.trimmed().isEmpty() )
470  filter = mFilterExp;
471  else if ( sf.isEmpty() )
472  filter = QStringLiteral( "TRUE" );
473  else
474  filter = sf;
475 
476  filter = filter.trimmed();
477 
478  return true;
479 }
480 
482 {
483  QSet<int> symbolZLevelsSet;
484 
485  // process this rule
486  if ( mSymbol )
487  {
488  // find out which Z-levels are used
489  for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
490  {
491  symbolZLevelsSet.insert( mSymbol->symbolLayer( i )->renderingPass() );
492  }
493  }
494 
495  // process children
496  QList<Rule *>::iterator it;
497  for ( it = mActiveChildren.begin(); it != mActiveChildren.end(); ++it )
498  {
499  Rule *rule = *it;
500  symbolZLevelsSet.unite( rule->collectZLevels() );
501  }
502  return symbolZLevelsSet;
503 }
504 
505 void QgsRuleBasedRenderer::Rule::setNormZLevels( const QMap<int, int> &zLevelsToNormLevels )
506 {
507  if ( mSymbol )
508  {
509  for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
510  {
511  int normLevel = zLevelsToNormLevels.value( mSymbol->symbolLayer( i )->renderingPass() );
512  mSymbolNormZLevels.insert( normLevel );
513  }
514  }
515 
516  // prepare list of normalized levels for each rule
517  const auto constMActiveChildren = mActiveChildren;
518  for ( Rule *rule : constMActiveChildren )
519  {
520  rule->setNormZLevels( zLevelsToNormLevels );
521  }
522 }
523 
524 
526 {
527  if ( !isFilterOK( featToRender.feat, &context ) )
528  return Filtered;
529 
530  bool rendered = false;
531 
532  // create job for this feature and this symbol, add to list of jobs
533  if ( mSymbol && mIsActive )
534  {
535  // add job to the queue: each symbol's zLevel must be added
536  const auto constMSymbolNormZLevels = mSymbolNormZLevels;
537  for ( int normZLevel : constMSymbolNormZLevels )
538  {
539  //QgsDebugMsg(QString("add job at level %1").arg(normZLevel));
540  renderQueue[normZLevel].jobs.append( new RenderJob( featToRender, mSymbol.get() ) );
541  rendered = true;
542  }
543  }
544 
545  bool willrendersomething = false;
546 
547  // process children
548  const auto constMChildren = mChildren;
549  for ( Rule *rule : constMChildren )
550  {
551  // Don't process else rules yet
552  if ( !rule->isElse() )
553  {
554  RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
555  // consider inactive items as "rendered" so the else rule will ignore them
556  willrendersomething |= ( res == Rendered || res == Inactive );
557  rendered |= ( res == Rendered );
558  }
559  }
560 
561  // If none of the rules passed then we jump into the else rules and process them.
562  if ( !willrendersomething )
563  {
564  const auto constMElseRules = mElseRules;
565  for ( Rule *rule : constMElseRules )
566  {
567  rendered |= rule->renderFeature( featToRender, context, renderQueue ) == Rendered;
568  }
569  }
570  if ( !mIsActive || ( mSymbol && !rendered ) )
571  return Inactive;
572  else if ( rendered )
573  return Rendered;
574  else
575  return Filtered;
576 }
577 
579 {
580  if ( !isFilterOK( feature, context ) )
581  return false;
582 
583  if ( mSymbol )
584  return true;
585 
586  const auto constMActiveChildren = mActiveChildren;
587  for ( Rule *rule : constMActiveChildren )
588  {
589  if ( rule->isElse() )
590  {
591  if ( rule->children().isEmpty() )
592  {
593  RuleList lst = rulesForFeature( feature, context, false );
594  lst.removeOne( rule );
595 
596  if ( lst.empty() )
597  {
598  return true;
599  }
600  }
601  else
602  {
603  return rule->willRenderFeature( feature, context );
604  }
605  }
606  else if ( rule->willRenderFeature( feature, context ) )
607  {
608  return true;
609  }
610  }
611  return false;
612 }
613 
615 {
616  QgsSymbolList lst;
617  if ( !isFilterOK( feature, context ) )
618  return lst;
619  if ( mSymbol )
620  lst.append( mSymbol.get() );
621 
622  const auto constMActiveChildren = mActiveChildren;
623  for ( Rule *rule : constMActiveChildren )
624  {
625  lst += rule->symbolsForFeature( feature, context );
626  }
627  return lst;
628 }
629 
631 {
632  QSet< QString> lst;
633  if ( !isFilterOK( feature, context ) )
634  return lst;
635  lst.insert( mRuleKey );
636 
637  const auto constMActiveChildren = mActiveChildren;
638  for ( Rule *rule : constMActiveChildren )
639  {
640  bool validKey = false;
641  if ( rule->isElse() )
642  {
643  RuleList lst = rulesForFeature( feature, context, false );
644  lst.removeOne( rule );
645 
646  if ( lst.empty() )
647  {
648  validKey = true;
649  }
650  }
651  else if ( !rule->isElse( ) && rule->willRenderFeature( feature, context ) )
652  {
653  validKey = true;
654  }
655 
656  if ( validKey )
657  {
658  lst.unite( rule->legendKeysForFeature( feature, context ) );
659  }
660  }
661  return lst;
662 }
663 
665 {
666  RuleList lst;
667  if ( ! isFilterOK( feature, context ) || ( context && ! isScaleOK( context->rendererScale() ) ) )
668  return lst;
669 
670  if ( mSymbol )
671  lst.append( this );
672 
673  RuleList listChildren = children();
674  if ( onlyActive )
675  listChildren = mActiveChildren;
676 
677  const auto constListChildren = listChildren;
678  for ( Rule *rule : constListChildren )
679  {
680  lst += rule->rulesForFeature( feature, context, onlyActive );
681  }
682  return lst;
683 }
684 
686 {
687  if ( mSymbol )
688  mSymbol->stopRender( context );
689 
690  const auto constMActiveChildren = mActiveChildren;
691  for ( Rule *rule : constMActiveChildren )
692  {
693  rule->stopRender( context );
694  }
695 
696  mActiveChildren.clear();
697  mSymbolNormZLevels.clear();
698 }
699 
701 {
702  QString symbolIdx = ruleElem.attribute( QStringLiteral( "symbol" ) );
703  QgsSymbol *symbol = nullptr;
704  if ( !symbolIdx.isEmpty() )
705  {
706  if ( symbolMap.contains( symbolIdx ) )
707  {
708  symbol = symbolMap.take( symbolIdx );
709  }
710  else
711  {
712  QgsDebugMsg( "symbol for rule " + symbolIdx + " not found!" );
713  }
714  }
715 
716  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
717  QString label = ruleElem.attribute( QStringLiteral( "label" ) );
718  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
719  int scaleMinDenom = ruleElem.attribute( QStringLiteral( "scalemindenom" ), QStringLiteral( "0" ) ).toInt();
720  int scaleMaxDenom = ruleElem.attribute( QStringLiteral( "scalemaxdenom" ), QStringLiteral( "0" ) ).toInt();
721  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
722  Rule *rule = new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
723 
724  if ( !ruleKey.isEmpty() )
725  rule->mRuleKey = ruleKey;
726 
727  rule->setActive( ruleElem.attribute( QStringLiteral( "checkstate" ), QStringLiteral( "1" ) ).toInt() );
728 
729  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
730  while ( !childRuleElem.isNull() )
731  {
732  Rule *childRule = create( childRuleElem, symbolMap );
733  if ( childRule )
734  {
735  rule->appendChild( childRule );
736  }
737  else
738  {
739  QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
740  }
741  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
742  }
743 
744  return rule;
745 }
746 
748 {
749  RuleList l;
750  for ( QgsRuleBasedRenderer::Rule *c : mChildren )
751  {
752  l += c;
753  l += c->descendants();
754  }
755  return l;
756 }
757 
759 {
760  if ( ruleElem.localName() != QLatin1String( "Rule" ) )
761  {
762  QgsDebugMsg( QStringLiteral( "invalid element: Rule element expected, %1 found!" ).arg( ruleElem.tagName() ) );
763  return nullptr;
764  }
765 
766  QString label, description, filterExp;
767  int scaleMinDenom = 0, scaleMaxDenom = 0;
768  QgsSymbolLayerList layers;
769 
770  // retrieve the Rule element child nodes
771  QDomElement childElem = ruleElem.firstChildElement();
772  while ( !childElem.isNull() )
773  {
774  if ( childElem.localName() == QLatin1String( "Name" ) )
775  {
776  // <se:Name> tag contains the rule identifier,
777  // so prefer title tag for the label property value
778  if ( label.isEmpty() )
779  label = childElem.firstChild().nodeValue();
780  }
781  else if ( childElem.localName() == QLatin1String( "Description" ) )
782  {
783  // <se:Description> can contains a title and an abstract
784  QDomElement titleElem = childElem.firstChildElement( QStringLiteral( "Title" ) );
785  if ( !titleElem.isNull() )
786  {
787  label = titleElem.firstChild().nodeValue();
788  }
789 
790  QDomElement abstractElem = childElem.firstChildElement( QStringLiteral( "Abstract" ) );
791  if ( !abstractElem.isNull() )
792  {
793  description = abstractElem.firstChild().nodeValue();
794  }
795  }
796  else if ( childElem.localName() == QLatin1String( "Abstract" ) )
797  {
798  // <sld:Abstract> (v1.0)
799  description = childElem.firstChild().nodeValue();
800  }
801  else if ( childElem.localName() == QLatin1String( "Title" ) )
802  {
803  // <sld:Title> (v1.0)
804  label = childElem.firstChild().nodeValue();
805  }
806  else if ( childElem.localName() == QLatin1String( "Filter" ) )
807  {
809  if ( filter )
810  {
811  if ( filter->hasParserError() )
812  {
813  QgsDebugMsg( "parser error: " + filter->parserErrorString() );
814  }
815  else
816  {
817  filterExp = filter->expression();
818  }
819  delete filter;
820  }
821  }
822  else if ( childElem.localName() == QLatin1String( "MinScaleDenominator" ) )
823  {
824  bool ok;
825  int v = childElem.firstChild().nodeValue().toInt( &ok );
826  if ( ok )
827  scaleMinDenom = v;
828  }
829  else if ( childElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
830  {
831  bool ok;
832  int v = childElem.firstChild().nodeValue().toInt( &ok );
833  if ( ok )
834  scaleMaxDenom = v;
835  }
836  else if ( childElem.localName().endsWith( QLatin1String( "Symbolizer" ) ) )
837  {
838  // create symbol layers for this symbolizer
839  QgsSymbolLayerUtils::createSymbolLayerListFromSld( childElem, geomType, layers );
840  }
841 
842  childElem = childElem.nextSiblingElement();
843  }
844 
845  // now create the symbol
846  QgsSymbol *symbol = nullptr;
847  if ( !layers.isEmpty() )
848  {
849  switch ( geomType )
850  {
852  symbol = new QgsLineSymbol( layers );
853  break;
854 
856  symbol = new QgsFillSymbol( layers );
857  break;
858 
860  symbol = new QgsMarkerSymbol( layers );
861  break;
862 
863  default:
864  QgsDebugMsg( QStringLiteral( "invalid geometry type: found %1" ).arg( geomType ) );
865  return nullptr;
866  }
867  }
868 
869  // and then create and return the new rule
870  return new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
871 }
872 
873 
875 
877  : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
878  , mRootRule( root )
879 {
880 }
881 
883  : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
884 {
885  mRootRule = new Rule( nullptr ); // root has no symbol, no filter etc - just a container
886  mRootRule->appendChild( new Rule( defaultSymbol ) );
887 }
888 
890 {
891  delete mRootRule;
892 }
893 
894 
896 {
897  // not used at all
898  return nullptr;
899 }
900 
902  QgsRenderContext &context,
903  int layer,
904  bool selected,
905  bool drawVertexMarker )
906 {
907  Q_UNUSED( layer )
908 
909  int flags = ( selected ? FeatIsSelected : 0 ) | ( drawVertexMarker ? FeatDrawMarkers : 0 );
910  mCurrentFeatures.append( FeatureToRender( feature, flags ) );
911 
912  // check each active rule
913  return mRootRule->renderFeature( mCurrentFeatures.last(), context, mRenderQueue ) == Rule::Rendered;
914 }
915 
916 
918 {
919  QgsFeatureRenderer::startRender( context, fields );
920 
921  // prepare active children
922  mRootRule->startRender( context, fields, mFilter );
923 
924  QSet<int> symbolZLevelsSet = mRootRule->collectZLevels();
925  QList<int> symbolZLevels = symbolZLevelsSet.toList();
926  std::sort( symbolZLevels.begin(), symbolZLevels.end() );
927 
928  // create mapping from unnormalized levels [unlimited range] to normalized levels [0..N-1]
929  // and prepare rendering queue
930  QMap<int, int> zLevelsToNormLevels;
931  int maxNormLevel = -1;
932  const auto constSymbolZLevels = symbolZLevels;
933  for ( int zLevel : constSymbolZLevels )
934  {
935  zLevelsToNormLevels[zLevel] = ++maxNormLevel;
936  mRenderQueue.append( RenderLevel( zLevel ) );
937  QgsDebugMsgLevel( QStringLiteral( "zLevel %1 -> %2" ).arg( zLevel ).arg( maxNormLevel ), 4 );
938  }
939 
940  mRootRule->setNormZLevels( zLevelsToNormLevels );
941 }
942 
944 {
946 
947  //
948  // do the actual rendering
949  //
950 
951  // go through all levels
952  if ( !context.renderingStopped() )
953  {
954  const auto constMRenderQueue = mRenderQueue;
955  for ( const RenderLevel &level : constMRenderQueue )
956  {
957  //QgsDebugMsg(QString("level %1").arg(level.zIndex));
958  // go through all jobs at the level
959  for ( const RenderJob *job : qgis::as_const( level.jobs ) )
960  {
961  context.expressionContext().setFeature( job->ftr.feat );
962  //QgsDebugMsg(QString("job fid %1").arg(job->f->id()));
963  // render feature - but only with symbol layers with specified zIndex
964  QgsSymbol *s = job->symbol;
965  int count = s->symbolLayerCount();
966  for ( int i = 0; i < count; i++ )
967  {
968  // TODO: better solution for this
969  // renderFeatureWithSymbol asks which symbol layer to draw
970  // but there are multiple transforms going on!
971  if ( s->symbolLayer( i )->renderingPass() == level.zIndex )
972  {
973  int flags = job->ftr.flags;
974  renderFeatureWithSymbol( job->ftr.feat, job->symbol, context, i, flags & FeatIsSelected, flags & FeatDrawMarkers );
975  }
976  }
977  }
978  }
979  }
980 
981  // clean current features
982  mCurrentFeatures.clear();
983 
984  // clean render queue
985  mRenderQueue.clear();
986 
987  // clean up rules from temporary stuff
988  mRootRule->stopRender( context );
989 }
990 
992 {
993  return mFilter;
994 }
995 
996 QSet<QString> QgsRuleBasedRenderer::usedAttributes( const QgsRenderContext &context ) const
997 {
998  return mRootRule->usedAttributes( context );
999 }
1000 
1002 {
1003  return mRootRule->needsGeometry();
1004 }
1005 
1007 {
1008  QgsRuleBasedRenderer::Rule *clonedRoot = mRootRule->clone();
1009 
1010  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
1011  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
1012  clonedRoot->setRuleKey( mRootRule->ruleKey() );
1013  RuleList origDescendants = mRootRule->descendants();
1014  RuleList clonedDescendants = clonedRoot->descendants();
1015  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
1016  for ( int i = 0; i < origDescendants.count(); ++i )
1017  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
1018 
1019  QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( clonedRoot );
1020 
1022  copyRendererData( r );
1023  return r;
1024 }
1025 
1026 void QgsRuleBasedRenderer::toSld( QDomDocument &doc, QDomElement &element, const QgsStringMap &props ) const
1027 {
1028  mRootRule->toSld( doc, element, props );
1029 }
1030 
1031 // TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items
1033 {
1034  return mRootRule->symbols( context );
1035 }
1036 
1037 QDomElement QgsRuleBasedRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
1038 {
1039  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
1040  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "RuleRenderer" ) );
1041  rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1042  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1043 
1045 
1046  QDomElement rulesElem = mRootRule->save( doc, symbols );
1047  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
1048  rendererElem.appendChild( rulesElem );
1049 
1050  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
1051  rendererElem.appendChild( symbolsElem );
1052 
1054  mPaintEffect->saveProperties( doc, rendererElem );
1055 
1056  if ( !mOrderBy.isEmpty() )
1057  {
1058  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
1059  mOrderBy.save( orderBy );
1060  rendererElem.appendChild( orderBy );
1061  }
1062  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1063 
1064  return rendererElem;
1065 }
1066 
1068 {
1069  return true;
1070 }
1071 
1073 {
1074  Rule *rule = mRootRule->findRuleByKey( key );
1075  return rule ? rule->active() : true;
1076 }
1077 
1078 void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1079 {
1080  Rule *rule = mRootRule->findRuleByKey( key );
1081  if ( rule )
1082  rule->setActive( state );
1083 }
1084 
1085 void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1086 {
1087  Rule *rule = mRootRule->findRuleByKey( key );
1088  if ( rule )
1089  rule->setSymbol( symbol );
1090  else
1091  delete symbol;
1092 }
1093 
1095 {
1096  return mRootRule->legendSymbolItems();
1097 }
1098 
1099 
1101 {
1102  // load symbols
1103  QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
1104  if ( symbolsElem.isNull() )
1105  return nullptr;
1106 
1107  QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1108 
1109  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
1110 
1111  Rule *root = Rule::create( rulesElem, symbolMap );
1112  if ( !root )
1113  return nullptr;
1114 
1115  QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( root );
1116 
1117  // delete symbols if there are any more
1119 
1120  return r;
1121 }
1122 
1124 {
1125  // retrieve child rules
1126  Rule *root = nullptr;
1127 
1128  QDomElement ruleElem = element.firstChildElement( QStringLiteral( "Rule" ) );
1129  while ( !ruleElem.isNull() )
1130  {
1131  Rule *child = Rule::createFromSld( ruleElem, geomType );
1132  if ( child )
1133  {
1134  // create the root rule if not done before
1135  if ( !root )
1136  root = new Rule( nullptr );
1137 
1138  root->appendChild( child );
1139  }
1140 
1141  ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
1142  }
1143 
1144  if ( !root )
1145  {
1146  // no valid rules was found
1147  return nullptr;
1148  }
1149 
1150  // create and return the new renderer
1151  return new QgsRuleBasedRenderer( root );
1152 }
1153 
1156 
1158 {
1159  QString attr = r->classAttribute();
1160  // categorizedAttr could be either an attribute name or an expression.
1161  // the only way to differentiate is to test it as an expression...
1162  QgsExpression testExpr( attr );
1163  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1164  {
1165  //not an expression, so need to quote column name
1166  attr = QgsExpression::quotedColumnRef( attr );
1167  }
1168 
1169  const auto constCategories = r->categories();
1170  for ( const QgsRendererCategory &cat : constCategories )
1171  {
1172  QString value;
1173  // not quoting numbers saves a type cast
1174  if ( cat.value().type() == QVariant::Int )
1175  value = cat.value().toString();
1176  else if ( cat.value().type() == QVariant::Double )
1177  // we loose precision here - so we may miss some categories :-(
1178  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1179  value = QString::number( cat.value().toDouble(), 'f', 4 );
1180  else
1181  value = QgsExpression::quotedString( cat.value().toString() );
1182  QString filter = QStringLiteral( "%1 = %2" ).arg( attr, value );
1183  QString label = filter;
1184  initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1185  }
1186 }
1187 
1189 {
1190  QString attr = r->classAttribute();
1191  // categorizedAttr could be either an attribute name or an expression.
1192  // the only way to differentiate is to test it as an expression...
1193  QgsExpression testExpr( attr );
1194  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1195  {
1196  //not an expression, so need to quote column name
1197  attr = QgsExpression::quotedColumnRef( attr );
1198  }
1199  else if ( !testExpr.isField() )
1200  {
1201  //otherwise wrap expression in brackets
1202  attr = QStringLiteral( "(%1)" ).arg( attr );
1203  }
1204 
1205  bool firstRange = true;
1206  const auto constRanges = r->ranges();
1207  for ( const QgsRendererRange &rng : constRanges )
1208  {
1209  // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1210  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1211  QString filter = QStringLiteral( "%1 %2 %3 AND %1 <= %4" ).arg( attr, firstRange ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
1212  QString::number( rng.lowerValue(), 'f', 4 ),
1213  QString::number( rng.upperValue(), 'f', 4 ) );
1214  firstRange = false;
1215  QString label = filter;
1216  initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1217  }
1218 }
1219 
1221 {
1222  std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1223  double oldScale = initialRule->maximumScale();
1224  double maxDenom = initialRule->minimumScale();
1225  QgsSymbol *symbol = initialRule->symbol();
1226  const auto constScales = scales;
1227  for ( int scale : constScales )
1228  {
1229  if ( initialRule->maximumScale() >= scale )
1230  continue; // jump over the first scales out of the interval
1231  if ( maxDenom != 0 && maxDenom <= scale )
1232  break; // ignore the latter scales out of the interval
1233  initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( scale ) ) );
1234  oldScale = scale;
1235  }
1236  // last rule
1237  initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( maxDenom ) ) );
1238 }
1239 
1241 {
1242  QString msg( QStringLiteral( "Rule-based renderer:\n" ) );
1243  msg += mRootRule->dump();
1244  return msg;
1245 }
1246 
1248 {
1249  return mRootRule->willRenderFeature( feature, &context );
1250 }
1251 
1253 {
1254  return mRootRule->symbolsForFeature( feature, &context );
1255 }
1256 
1258 {
1259  return mRootRule->symbolsForFeature( feature, &context );
1260 }
1261 
1262 QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1263 {
1264  return mRootRule->legendKeysForFeature( feature, &context );
1265 }
1266 
1268 {
1269  std::unique_ptr< QgsRuleBasedRenderer > r;
1270  if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1271  {
1272  r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1273  }
1274  else if ( renderer->type() == QLatin1String( "singleSymbol" ) )
1275  {
1276  const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1277  if ( !singleSymbolRenderer )
1278  return nullptr;
1279 
1280  std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1281  r = qgis::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1282  }
1283  else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1284  {
1285  const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1286  if ( !categorizedRenderer )
1287  return nullptr;
1288 
1289  QString attr = categorizedRenderer->classAttribute();
1290  // categorizedAttr could be either an attribute name or an expression.
1291  // the only way to differentiate is to test it as an expression...
1292  QgsExpression testExpr( attr );
1293  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1294  {
1295  //not an expression, so need to quote column name
1296  attr = QgsExpression::quotedColumnRef( attr );
1297  }
1298 
1299  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = qgis::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1300 
1301  QString expression;
1302  QString value;
1303  QgsRendererCategory category;
1304  for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1305  {
1306  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = qgis::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1307 
1308  rule->setLabel( category.label() );
1309 
1310  //We first define the rule corresponding to the category
1311  if ( category.value().type() == QVariant::List )
1312  {
1313  QStringList values;
1314  const QVariantList list = category.value().toList();
1315  for ( const QVariant &v : list )
1316  {
1317  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1318  if ( QVariant( v ).convert( QVariant::Double ) )
1319  {
1320  values << v.toString();
1321  }
1322  else
1323  {
1324  values << QgsExpression::quotedString( v.toString() );
1325  }
1326  }
1327 
1328  if ( values.empty() )
1329  {
1330  expression = QStringLiteral( "ELSE" );
1331  }
1332  else
1333  {
1334  expression = QStringLiteral( "%1 IN (%2)" ).arg( attr, values.join( ',' ) );
1335  }
1336  }
1337  else
1338  {
1339  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1340  if ( category.value().convert( QVariant::Double ) )
1341  {
1342  value = category.value().toString();
1343  }
1344  else
1345  {
1346  value = QgsExpression::quotedString( category.value().toString() );
1347  }
1348 
1349  //An empty category is equivalent to the ELSE keyword
1350  if ( value == QLatin1String( "''" ) )
1351  {
1352  expression = QStringLiteral( "ELSE" );
1353  }
1354  else
1355  {
1356  expression = QStringLiteral( "%1 = %2" ).arg( attr, value );
1357  }
1358  }
1359  rule->setFilterExpression( expression );
1360 
1361  //Then we construct an equivalent symbol.
1362  //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1363  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1364 
1365  std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1366  rule->setSymbol( origSymbol.release() );
1367 
1368  rootrule->appendChild( rule.release() );
1369  }
1370 
1371  r = qgis::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1372  }
1373  else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1374  {
1375  const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1376  if ( !graduatedRenderer )
1377  return nullptr;
1378 
1379  QString attr = graduatedRenderer->classAttribute();
1380  // categorizedAttr could be either an attribute name or an expression.
1381  // the only way to differentiate is to test it as an expression...
1382  QgsExpression testExpr( attr );
1383  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1384  {
1385  //not an expression, so need to quote column name
1386  attr = QgsExpression::quotedColumnRef( attr );
1387  }
1388  else if ( !testExpr.isField() )
1389  {
1390  //otherwise wrap expression in brackets
1391  attr = QStringLiteral( "(%1)" ).arg( attr );
1392  }
1393 
1394  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = qgis::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1395 
1396  QString expression;
1397  QgsRendererRange range;
1398  for ( int i = 0; i < graduatedRenderer->ranges().size(); ++i )
1399  {
1400  range = graduatedRenderer->ranges().value( i );
1401  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = qgis::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1402  rule->setLabel( range.label() );
1403  if ( i == 0 )//The lower boundary of the first range is included, while it is excluded for the others
1404  {
1405  expression = attr + " >= " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1406  attr + " <= " + QString::number( range.upperValue(), 'f' );
1407  }
1408  else
1409  {
1410  expression = attr + " > " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1411  attr + " <= " + QString::number( range.upperValue(), 'f' );
1412  }
1413  rule->setFilterExpression( expression );
1414 
1415  //Then we construct an equivalent symbol.
1416  //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1417  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1418 
1419  std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1420  rule->setSymbol( symbol.release() );
1421 
1422  rootrule->appendChild( rule.release() );
1423  }
1424 
1425  r = qgis::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1426  }
1427  else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1428  {
1429  const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1430  if ( pointDistanceRenderer )
1431  return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1432  }
1433  else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1434  {
1435  const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1436  if ( invertedPolygonRenderer )
1437  r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1438  }
1439 
1440  if ( r )
1441  {
1442  r->setOrderBy( renderer->orderBy() );
1443  r->setOrderByEnabled( renderer->orderByEnabled() );
1444  }
1445 
1446  return r.release();
1447 }
1448 
1449 void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1450 {
1451  QString sizeExpression;
1452  switch ( symbol->type() )
1453  {
1454  case QgsSymbol::Marker:
1455  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1456  {
1457  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1458  if ( ! sizeScaleField.isEmpty() )
1459  {
1460  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1462  }
1463  if ( ! rotationField.isEmpty() )
1464  {
1466  }
1467  }
1468  break;
1469  case QgsSymbol::Line:
1470  if ( ! sizeScaleField.isEmpty() )
1471  {
1472  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1473  {
1474  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "SimpleLine" ) )
1475  {
1476  QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1477  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( lsl->width() ).arg( sizeScaleField );
1479  }
1480  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "MarkerLine" ) )
1481  {
1482  QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1483  for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1484  {
1485  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1486  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1488  }
1489  }
1490  }
1491  }
1492  break;
1493  default:
1494  break;
1495  }
1496 }
QgsRuleBasedRenderer(QgsRuleBasedRenderer::Rule *root)
Constructs the renderer from given tree of rules (takes ownership)
An abstract base class for distance based point renderers (e.g., clusterer and displacement renderers...
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.
QString label() const
Returns the label for this category, which is used to represent the category within legends and the l...
Class for parsing and evaluation of expressions (formerly called "search strings").
bool hasParserError() const
Returns true if an error occurred when parsing the input expression.
The class is used as a container of context for various read/write operations on other objects...
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns the attributes used to evaluate the expression of this rule.
double rendererScale() const
Returns the renderer map scale.
QDomElement save(QDomDocument &doc, QgsSymbolMap &symbolMap) const
QgsSymbolList originalSymbolsForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Equivalent of originalSymbolsForFeature() call extended to support renderers that may use more symbol...
QList< QgsLegendSymbolItem > QgsLegendSymbolList
bool filterNeedsGeometry() const override
Returns true if this renderer requires the geometry to apply the filter.
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:521
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes)
QgsRuleBasedRenderer::RuleList descendants() const
Returns all children, grand-children, grand-grand-children, grand-gra...
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
static bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Abstract base class for all rendered symbols.
Definition: qgssymbol.h:61
QgsRuleBasedRenderer::Rule * takeChild(QgsRuleBasedRenderer::Rule *rule)
take child rule out, set parent as nullptr
virtual QgsSymbol * subSymbol()
Returns the symbol&#39;s sub symbol, if present.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns a list of attributes required by this renderer.
QList< QgsRuleBasedRenderer::RenderLevel > RenderQueue
Rendering queue: a list of rendering levels.
static void applyScaleDependency(QDomDocument &doc, QDomElement &ruleElem, QgsStringMap &props)
Checks if the properties contain scaleMinDenom and scaleMaxDenom, if available, they are added into t...
const QgsRuleBasedRenderer::RuleList & children()
Returns all children rules of this rule.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
#define QgsDebugMsg(str)
Definition: qgslogger.h:38
static void convertToDataDefinedSymbology(QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField=QString())
helper function to convert the size scale and rotation fields present in some other renderers to data...
This class keeps data about a rules for rule-based renderer.
QString ruleKey() const
Unique rule identifier (for identification of rule within renderer)
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
store renderer info to XML element
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference)
Definition: qgis.h:265
void setRuleKey(const QString &key)
Override the assigned rule key (should be used just internally by rule-based renderer) ...
QgsFeatureRequest::OrderBy orderBy() const
Gets the order in which features shall be processed by this renderer.
void toSld(QDomDocument &doc, QDomElement &element, QgsStringMap props) const
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
static QgsProperty fromField(const QString &fieldName, bool isActive=true)
Returns a new FieldBasedProperty created from the specified field name.
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
QString classAttribute() const
Returns the class attribute for the renderer, which is the field name or expression string from the l...
static QgsProperty fromExpression(const QString &expression, bool isActive=true)
Returns a new ExpressionBasedProperty created from the specified expression.
bool renderingStopped() const
Returns true if the rendering operation has been stopped and any ongoing rendering should be canceled...
QSet< QString > legendKeysForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns legend keys matching a specified feature.
Container of fields for a vector layer.
Definition: qgsfields.h:42
#define RENDERER_TAG_NAME
Definition: qgsrenderer.h:49
void setUsingSymbolLevels(bool usingSymbolLevels)
Definition: qgsrenderer.h:272
void checkLegendSymbolItem(const QString &key, bool state=true) override
item in symbology was checked
QSet< int > collectZLevels()
Gets all used z-levels from this rule and children.
Rule * mRootRule
the root node with hierarchical list of rules
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:150
QList< FeatureToRender > mCurrentFeatures
static void clearSymbolMap(QgsSymbolMap &symbols)
double maximumScale() const
Returns the maximum map scale (i.e.
bool willRenderFeature(const QgsFeature &feature, QgsRenderContext *context=nullptr)
only tell whether a feature will be rendered without actually rendering it
QgsPaintEffect * mPaintEffect
Definition: qgsrenderer.h:505
Line symbol.
Definition: qgssymbol.h:86
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
void setNormZLevels(const QMap< int, int > &zLevelsToNormLevels)
assign normalized z-levels [0..N-1] for this rule&#39;s symbol for quick access during rendering ...
QString description() const
A human readable description for this rule.
QString parserErrorString() const
Returns parser error.
QMap< QString, QString > QgsStringMap
Definition: qgis.h:587
virtual double width() const
Returns the estimated width for the line symbol layer.
A marker symbol type, for rendering Point and MultiPoint geometries.
Definition: qgssymbol.h:766
A line symbol type, for rendering LineString and MultiLineString geometries.
Definition: qgssymbol.h:966
bool startRender(QgsRenderContext &context, const QgsFields &fields, QString &filter)
prepare the rule for rendering and its children (build active children array)
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
void renderFeatureWithSymbol(const QgsFeature &feature, QgsSymbol *symbol, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker) SIP_THROW(QgsCsException)
Render the feature with the symbol using context.
bool active() const
Returns if this rule is active.
QList< QgsRuleBasedRenderer::Rule * > RuleList
QString dump(int indent=0) const
Dump for debug purpose.
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
int renderingPass() const
Specifies the rendering pass in which this symbol layer should be rendered.
RenderResult
The result of rendering a rule.
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:43
QString type() const
Definition: qgsrenderer.h:129
bool legendSymbolItemsCheckable() const override
items of symbology items in legend should be checkable
static QgsFeatureRenderer * createFromSld(QDomElement &element, QgsWkbTypes::GeometryType geomType)
QgsInvertedPolygonRenderer is a polygon-only feature renderer used to display features inverted...
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
static QgsFeatureRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a new rule-based renderer instance from XML.
static QDomElement saveSymbols(QgsSymbolMap &symbols, const QString &tagName, QDomDocument &doc, const QgsReadWriteContext &context)
Writes a collection of symbols to XML with specified tagName for the top-level element.
QgsRuleBasedRenderer::RuleList rulesForFeature(const QgsFeature &feature, QgsRenderContext *context=nullptr, bool onlyActive=true)
Returns the list of rules used to render the feature in a specific context.
double size() const
Returns the symbol size.
QgsSymbolList symbols(const QgsRenderContext &context=QgsRenderContext()) const
A QgsRuleBasedRenderer rendering job, consisting of a feature to be rendered with a particular symbol...
void removeChild(QgsRuleBasedRenderer::Rule *rule)
delete child rule
QgsExpression * filter() const
A filter that will check if this rule applies.
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition: qgssymbol.h:51
When drawing a vector layer with rule-based renderer, it goes through the rules and draws features wi...
bool isFilterOK(const QgsFeature &f, QgsRenderContext *context=nullptr) const
Check if a given feature shall be rendered by this rule.
QgsSymbol * symbol() const
double minimumScale() const
Returns the minimum map scale (i.e.
void insertChild(int i, QgsRuleBasedRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
Render level: a list of jobs to be drawn at particular level for a QgsRuleBasedRenderer.
bool isScaleOK(double scale) const
Check if this rule applies for a given scale.
static QgsSymbolMap loadSymbols(QDomElement &element, const QgsReadWriteContext &context)
Reads a collection of symbols from XML and returns them in a map. Caller is responsible for deleting ...
QgsSymbolLayer * symbolLayer(int layer)
Returns a specific symbol layer contained in the symbol.
Definition: qgssymbol.cpp:358
bool orderByEnabled() const
Returns whether custom ordering will be applied before features are processed by this renderer...
void stopRender(QgsRenderContext &context)
Stop a rendering process.
void setOrderBy(const QgsFeatureRequest::OrderBy &orderBy)
Define the order in which features shall be processed by this renderer.
QgsRuleBasedRenderer::Rule * takeChildAt(int i)
take child rule out, set parent as nullptr
void setSymbol(QgsSymbol *sym)
Sets a new symbol (or nullptr). Deletes old symbol.
QVariant value() const
Returns the value corresponding to this category.
QgsSymbol * symbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns symbol for current feature. Should not be used individually: there could be more symbols for ...
QString expression() const
Returns the original, unmodified expression string.
QgsExpressionContext & expressionContext()
Gets the expression context.
GeometryType
The geometry types are used to group QgsWkbTypes::Type in a coarse way.
Definition: qgswkbtypes.h:139
static void refineRuleRanges(QgsRuleBasedRenderer::Rule *initialRule, QgsGraduatedSymbolRenderer *r)
take a rule and create a list of new rules based on the ranges from graduated symbol renderer ...
void setActive(bool state)
Sets if this rule is active.
QgsRuleBasedRenderer::Rule * clone() const
clone this rule, return new instance
Marker symbol.
Definition: qgssymbol.h:85
QgsSymbolList symbolsForFeature(const QgsFeature &feature, QgsRenderContext *context=nullptr)
tell which symbols will be used to render the feature
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
The class stores information about one class/rule of a vector layer renderer in a unified way that ca...
Contains information about the context of a rendering operation.
Abstract base class for marker symbol layers.
void setIsElse(bool iselse)
Sets if this rule is an ELSE rule.
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
bool usingSymbolLevels() const
Definition: qgsrenderer.h:271
SymbolType type() const
Returns the symbol&#39;s type.
Definition: qgssymbol.h:120
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
Definition: qgsrenderer.cpp:93
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
static QgsRuleBasedRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer)
creates a QgsRuleBasedRenderer from an existing renderer.
void CORE_EXPORT save(QDomElement &elem) const
Serialize to XML.
bool isField() const
Checks whether an expression consists only of a single field reference.
bool renderFeature(const QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override SIP_THROW(QgsCsException)
Render a feature using this renderer in the given context.
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QMap< QString, QgsSymbol *> QgsSymbolMap
Definition: qgsrenderer.h:44
bool needsGeometry() const
Returns true if this rule or one of its chilren needs the geometry to be applied. ...
static QString quotedString(QString text)
Returns a quoted version of a string (in single quotes)
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:49
QgsSymbolList symbolsForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns list of symbols used for rendering the feature.
bool legendSymbolItemChecked(const QString &key) override
items of symbology items in legend is checked
Rule(QgsSymbol *symbol, int maximumScale=0, int minimumScale=0, const QString &filterExp=QString(), const QString &label=QString(), const QString &description=QString(), bool elseRule=false)
Constructor takes ownership of the symbol.
QgsRuleBasedRenderer * clone() const override
Create a deep copy of this renderer.
QgsLegendSymbolList legendSymbolItems(int currentLevel=-1) const
QgsRuleBasedRenderer::Rule::RenderResult renderFeature(QgsRuleBasedRenderer::FeatureToRender &featToRender, QgsRenderContext &context, QgsRuleBasedRenderer::RenderQueue &renderQueue)
Render a given feature, will recursively call subclasses and only render if the constraints apply...
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
Definition: qgssymbol.h:1061
static void refineRuleCategories(QgsRuleBasedRenderer::Rule *initialRule, QgsCategorizedSymbolRenderer *r)
take a rule and create a list of new rules based on the categories from categorized symbol renderer ...
bool willRenderFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns whether the renderer will render a feature or not.
QString filter(const QgsFields &fields=QgsFields()) override
If a renderer does not require all the features this method may be overridden and return an expressio...
void setFilterExpression(const QString &filterExp)
Set the expression used to check if a given feature shall be rendered with this rule.
Feature for rendering by a QgsRuleBasedRenderer.
QgsSymbolList symbols(QgsRenderContext &context) const override
Returns list of symbols used by the renderer.
static void refineRuleScales(QgsRuleBasedRenderer::Rule *initialRule, QList< int > scales)
take a rule and create a list of new rules with intervals of scales given by the passed scale denomin...
void removeChildAt(int i)
delete child rule
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
static QgsRuleBasedRenderer::Rule * create(QDomElement &ruleElem, QgsSymbolMap &symbolMap)
Create a rule from an XML definition.
QgsRuleBasedRenderer::Rule * findRuleByKey(const QString &key)
Try to find a rule given its unique key.
static QgsExpression * expressionFromOgcFilter(const QDomElement &element, QgsVectorLayer *layer=nullptr)
Parse XML with OGC filter into QGIS expression.
const QgsRangeList & ranges() const
void setLabel(const QString &label)
Sets the label for this category, which is used to represent the category within legends and the laye...
static bool createSymbolLayerListFromSld(QDomElement &element, QgsWkbTypes::GeometryType geomType, QgsSymbolLayerList &layers)
QgsSymbol * symbol() const
Returns the symbol which will be rendered for every feature.
void setLegendSymbolItem(const QString &key, QgsSymbol *symbol) override
Sets the symbol to be used for a legend symbol item.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
void appendChild(QgsRuleBasedRenderer::Rule *rule)
add child rule, take ownership, sets this as parent
static QgsRuleBasedRenderer::Rule * createFromSld(QDomElement &element, QgsWkbTypes::GeometryType geomType)
Create a rule from the SLD provided in element and for the specified geometry type.
virtual void setDataDefinedProperty(Property key, const QgsProperty &property)
Sets a data defined property for the layer.
void toSld(QDomDocument &doc, QDomElement &element, const QgsStringMap &props=QgsStringMap()) const override
used from subclasses to create SLD Rule elements following SLD v1.1 specs
QString dump() const override
Returns debug information about this renderer.
virtual QString layerType() const =0
Returns a string that represents this layer type.
QSet< QString > legendKeysForFeature(const QgsFeature &feature, QgsRenderContext *context=nullptr)
Returns which legend keys match the feature.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.