QGIS API Documentation  3.20.0-Odense (decaadbb31)
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  rendererElem.setAttribute( QStringLiteral( "symbollevels" ), ( mUsingSymbolLevels ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1084  rendererElem.setAttribute( QStringLiteral( "forceraster" ), ( mForceRaster ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1085 
1087 
1088  QDomElement rulesElem = mRootRule->save( doc, symbols );
1089  rulesElem.setTagName( QStringLiteral( "rules" ) ); // instead of just "rule"
1090  rendererElem.appendChild( rulesElem );
1091 
1092  QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
1093  rendererElem.appendChild( symbolsElem );
1094 
1096  mPaintEffect->saveProperties( doc, rendererElem );
1097 
1098  if ( !mOrderBy.isEmpty() )
1099  {
1100  QDomElement orderBy = doc.createElement( QStringLiteral( "orderby" ) );
1101  mOrderBy.save( orderBy );
1102  rendererElem.appendChild( orderBy );
1103  }
1104  rendererElem.setAttribute( QStringLiteral( "enableorderby" ), ( mOrderByEnabled ? QStringLiteral( "1" ) : QStringLiteral( "0" ) ) );
1105 
1106  return rendererElem;
1107 }
1108 
1110 {
1111  return true;
1112 }
1113 
1115 {
1116  Rule *rule = mRootRule->findRuleByKey( key );
1117  return rule ? rule->active() : true;
1118 }
1119 
1120 void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1121 {
1122  Rule *rule = mRootRule->findRuleByKey( key );
1123  if ( rule )
1124  rule->setActive( state );
1125 }
1126 
1127 void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1128 {
1129  Rule *rule = mRootRule->findRuleByKey( key );
1130  if ( rule )
1131  rule->setSymbol( symbol );
1132  else
1133  delete symbol;
1134 }
1135 
1137 {
1138  return mRootRule->legendSymbolItems();
1139 }
1140 
1141 
1143 {
1144  // load symbols
1145  QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
1146  if ( symbolsElem.isNull() )
1147  return nullptr;
1148 
1149  QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1150 
1151  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
1152 
1153  Rule *root = Rule::create( rulesElem, symbolMap );
1154  if ( !root )
1155  return nullptr;
1156 
1157  QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( root );
1158 
1159  // delete symbols if there are any more
1161 
1162  return r;
1163 }
1164 
1166 {
1167  // retrieve child rules
1168  Rule *root = nullptr;
1169 
1170  QDomElement ruleElem = element.firstChildElement( QStringLiteral( "Rule" ) );
1171  while ( !ruleElem.isNull() )
1172  {
1173  Rule *child = Rule::createFromSld( ruleElem, geomType );
1174  if ( child )
1175  {
1176  // create the root rule if not done before
1177  if ( !root )
1178  root = new Rule( nullptr );
1179 
1180  root->appendChild( child );
1181  }
1182 
1183  ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
1184  }
1185 
1186  if ( !root )
1187  {
1188  // no valid rules was found
1189  return nullptr;
1190  }
1191 
1192  // create and return the new renderer
1193  return new QgsRuleBasedRenderer( root );
1194 }
1195 
1198 
1200 {
1201  QString attr = r->classAttribute();
1202  // categorizedAttr could be either an attribute name or an expression.
1203  // the only way to differentiate is to test it as an expression...
1204  QgsExpression testExpr( attr );
1205  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1206  {
1207  //not an expression, so need to quote column name
1208  attr = QgsExpression::quotedColumnRef( attr );
1209  }
1210 
1211  const auto constCategories = r->categories();
1212  for ( const QgsRendererCategory &cat : constCategories )
1213  {
1214  QString value;
1215  // not quoting numbers saves a type cast
1216  if ( cat.value().type() == QVariant::Int )
1217  value = cat.value().toString();
1218  else if ( cat.value().type() == QVariant::Double )
1219  // we loose precision here - so we may miss some categories :-(
1220  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1221  value = QString::number( cat.value().toDouble(), 'f', 4 );
1222  else
1223  value = QgsExpression::quotedString( cat.value().toString() );
1224  QString filter = QStringLiteral( "%1 = %2" ).arg( attr, value );
1225  QString label = filter;
1226  initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1227  }
1228 }
1229 
1231 {
1232  QString attr = r->classAttribute();
1233  // categorizedAttr could be either an attribute name or an expression.
1234  // the only way to differentiate is to test it as an expression...
1235  QgsExpression testExpr( attr );
1236  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1237  {
1238  //not an expression, so need to quote column name
1239  attr = QgsExpression::quotedColumnRef( attr );
1240  }
1241  else if ( !testExpr.isField() )
1242  {
1243  //otherwise wrap expression in brackets
1244  attr = QStringLiteral( "(%1)" ).arg( attr );
1245  }
1246 
1247  bool firstRange = true;
1248  const auto constRanges = r->ranges();
1249  for ( const QgsRendererRange &rng : constRanges )
1250  {
1251  // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1252  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1253  QString filter = QStringLiteral( "%1 %2 %3 AND %1 <= %4" ).arg( attr, firstRange ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
1254  QString::number( rng.lowerValue(), 'f', 4 ),
1255  QString::number( rng.upperValue(), 'f', 4 ) );
1256  firstRange = false;
1257  QString label = rng.label().isEmpty() ? filter : rng.label();
1258  initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1259  }
1260 }
1261 
1263 {
1264  std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1265  double oldScale = initialRule->maximumScale();
1266  double maxDenom = initialRule->minimumScale();
1267  QgsSymbol *symbol = initialRule->symbol();
1268  const auto constScales = scales;
1269  for ( int scale : constScales )
1270  {
1271  if ( initialRule->maximumScale() >= scale )
1272  continue; // jump over the first scales out of the interval
1273  if ( maxDenom != 0 && maxDenom <= scale )
1274  break; // ignore the latter scales out of the interval
1275  initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( scale ) ) );
1276  oldScale = scale;
1277  }
1278  // last rule
1279  initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( maxDenom ) ) );
1280 }
1281 
1283 {
1284  QString msg( QStringLiteral( "Rule-based renderer:\n" ) );
1285  msg += mRootRule->dump();
1286  return msg;
1287 }
1288 
1290 {
1291  return mRootRule->willRenderFeature( feature, &context );
1292 }
1293 
1295 {
1296  return mRootRule->symbolsForFeature( feature, &context );
1297 }
1298 
1300 {
1301  return mRootRule->symbolsForFeature( feature, &context );
1302 }
1303 
1304 QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1305 {
1306  return mRootRule->legendKeysForFeature( feature, &context );
1307 }
1308 
1310 {
1311  return mRootRule->accept( visitor );
1312 }
1313 
1315 {
1316  std::unique_ptr< QgsRuleBasedRenderer > r;
1317  if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1318  {
1319  r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1320  }
1321  else if ( renderer->type() == QLatin1String( "singleSymbol" ) )
1322  {
1323  const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1324  if ( !singleSymbolRenderer )
1325  return nullptr;
1326 
1327  std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1328  r = std::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1329  }
1330  else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1331  {
1332  const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1333  if ( !categorizedRenderer )
1334  return nullptr;
1335 
1336  QString attr = categorizedRenderer->classAttribute();
1337  // categorizedAttr could be either an attribute name or an expression.
1338  // the only way to differentiate is to test it as an expression...
1339  QgsExpression testExpr( attr );
1340  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1341  {
1342  //not an expression, so need to quote column name
1343  attr = QgsExpression::quotedColumnRef( attr );
1344  }
1345 
1346  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1347 
1348  QString expression;
1349  QString value;
1350  QgsRendererCategory category;
1351  for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1352  {
1353  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1354 
1355  rule->setLabel( category.label() );
1356 
1357  //We first define the rule corresponding to the category
1358  if ( category.value().type() == QVariant::List )
1359  {
1360  QStringList values;
1361  const QVariantList list = category.value().toList();
1362  for ( const QVariant &v : list )
1363  {
1364  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1365  if ( QVariant( v ).convert( QVariant::Double ) )
1366  {
1367  values << v.toString();
1368  }
1369  else
1370  {
1371  values << QgsExpression::quotedString( v.toString() );
1372  }
1373  }
1374 
1375  if ( values.empty() )
1376  {
1377  expression = QStringLiteral( "ELSE" );
1378  }
1379  else
1380  {
1381  expression = QStringLiteral( "%1 IN (%2)" ).arg( attr, values.join( ',' ) );
1382  }
1383  }
1384  else
1385  {
1386  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1387  if ( category.value().convert( QVariant::Double ) )
1388  {
1389  value = category.value().toString();
1390  }
1391  else
1392  {
1393  value = QgsExpression::quotedString( category.value().toString() );
1394  }
1395 
1396  //An empty category is equivalent to the ELSE keyword
1397  if ( value == QLatin1String( "''" ) )
1398  {
1399  expression = QStringLiteral( "ELSE" );
1400  }
1401  else
1402  {
1403  expression = QStringLiteral( "%1 = %2" ).arg( attr, value );
1404  }
1405  }
1406  rule->setFilterExpression( expression );
1407 
1408  //Then we construct an equivalent symbol.
1409  //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1410  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1411 
1412  std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1413  rule->setSymbol( origSymbol.release() );
1414 
1415  rootrule->appendChild( rule.release() );
1416  }
1417 
1418  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1419  }
1420  else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1421  {
1422  const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1423  if ( !graduatedRenderer )
1424  return nullptr;
1425 
1426  QString attr = graduatedRenderer->classAttribute();
1427  // categorizedAttr could be either an attribute name or an expression.
1428  // the only way to differentiate is to test it as an expression...
1429  QgsExpression testExpr( attr );
1430  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1431  {
1432  //not an expression, so need to quote column name
1433  attr = QgsExpression::quotedColumnRef( attr );
1434  }
1435  else if ( !testExpr.isField() )
1436  {
1437  //otherwise wrap expression in brackets
1438  attr = QStringLiteral( "(%1)" ).arg( attr );
1439  }
1440 
1441  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1442 
1443  QString expression;
1444  QgsRendererRange range;
1445  for ( int i = 0; i < graduatedRenderer->ranges().size(); ++i )
1446  {
1447  range = graduatedRenderer->ranges().value( i );
1448  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1449  rule->setLabel( range.label() );
1450  if ( i == 0 )//The lower boundary of the first range is included, while it is excluded for the others
1451  {
1452  expression = attr + " >= " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1453  attr + " <= " + QString::number( range.upperValue(), 'f' );
1454  }
1455  else
1456  {
1457  expression = attr + " > " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1458  attr + " <= " + QString::number( range.upperValue(), 'f' );
1459  }
1460  rule->setFilterExpression( expression );
1461 
1462  //Then we construct an equivalent symbol.
1463  //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1464  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1465 
1466  std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1467  rule->setSymbol( symbol.release() );
1468 
1469  rootrule->appendChild( rule.release() );
1470  }
1471 
1472  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1473  }
1474  else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1475  {
1476  if ( const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer ) )
1477  return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1478  }
1479  else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1480  {
1481  if ( const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer ) )
1482  r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1483  }
1484  else if ( renderer->type() == QLatin1String( "mergedFeatureRenderer" ) )
1485  {
1486  if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
1487  r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
1488  }
1489  else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1490  {
1491  const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1492 
1493  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1494 
1495  QgsFeatureRequest req;
1497  req.setNoAttributes();
1498  QgsFeatureIterator it = layer->getFeatures( req );
1499  QgsFeature feature;
1500  while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
1501  {
1502  if ( feature.embeddedSymbol() )
1503  {
1504  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1505  rule->setFilterExpression( QStringLiteral( "$id=%1" ).arg( feature.id() ) );
1506  rule->setLabel( QString::number( feature.id() ) );
1507  rule->setSymbol( feature.embeddedSymbol()->clone() );
1508  rootrule->appendChild( rule.release() );
1509  }
1510  }
1511 
1512  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1513  rule->setFilterExpression( QStringLiteral( "ELSE" ) );
1514  rule->setLabel( QObject::tr( "All other features" ) );
1515  rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
1516  rootrule->appendChild( rule.release() );
1517 
1518  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1519  }
1520 
1521  if ( r )
1522  {
1523  r->setOrderBy( renderer->orderBy() );
1524  r->setOrderByEnabled( renderer->orderByEnabled() );
1525  }
1526 
1527  return r.release();
1528 }
1529 
1530 void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1531 {
1532  QString sizeExpression;
1533  switch ( symbol->type() )
1534  {
1536  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1537  {
1538  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1539  if ( ! sizeScaleField.isEmpty() )
1540  {
1541  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1543  }
1544  if ( ! rotationField.isEmpty() )
1545  {
1547  }
1548  }
1549  break;
1551  if ( ! sizeScaleField.isEmpty() )
1552  {
1553  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1554  {
1555  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "SimpleLine" ) )
1556  {
1557  QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1558  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( lsl->width() ).arg( sizeScaleField );
1560  }
1561  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "MarkerLine" ) )
1562  {
1563  QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1564  for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1565  {
1566  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1567  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1569  }
1570  }
1571  }
1572  }
1573  break;
1574  default:
1575  break;
1576  }
1577 }
@ 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.
QgsPaintEffect * mPaintEffect
Definition: qgsrenderer.h:537
QString type() const
Definition: qgsrenderer.h:141
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
Definition: qgsrenderer.cpp:52
bool orderByEnabled() const
Returns whether custom ordering will be applied before features are processed by this renderer.
QgsFeatureRequest::OrderBy mOrderBy
Definition: qgsrenderer.h:553
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:94
QgsFeatureRequest::OrderBy orderBy() const
Gets the order in which features shall be processed by this renderer.
void CORE_EXPORT save(QDomElement &elem) const
Serialize to XML.
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:288
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.
static bool isDefaultStack(QgsPaintEffect *effect)
Tests whether a paint effect matches the default effects stack.
virtual bool saveProperties(QDomDocument &doc, QDomElement &element) const
Saves the current state of the effect to a DOM element.
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
store renderer info to 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:598
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:49
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition: qgsrenderer.h:44
QList< QgsSymbol * > QgsSymbolList
Definition: qgsrenderer.h:43
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.