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