QGIS API Documentation  3.24.2-Tisler (13c1a02865)
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 matchedAChild = 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  const RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
598  // consider inactive items as "matched" so the else rule will ignore them
599  matchedAChild |= ( 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 ( !matchedAChild )
606  {
607  const auto constMElseRules = mElseRules;
608  for ( Rule *rule : constMElseRules )
609  {
610  const RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
611  matchedAChild |= ( res == Rendered || res == Inactive );
612  rendered |= res == Rendered;
613  }
614  }
615  if ( !mIsActive || ( mSymbol && !rendered ) || ( matchedAChild && !rendered ) )
616  return Inactive;
617  else if ( rendered )
618  return Rendered;
619  else
620  return Filtered;
621 }
622 
624 {
625  if ( !isFilterOK( feature, context ) )
626  return false;
627 
628  if ( mSymbol )
629  return true;
630 
631  const auto constMActiveChildren = mActiveChildren;
632  for ( Rule *rule : constMActiveChildren )
633  {
634  if ( rule->isElse() )
635  {
636  if ( rule->children().isEmpty() )
637  {
638  RuleList lst = rulesForFeature( feature, context, false );
639  lst.removeOne( rule );
640 
641  if ( lst.empty() )
642  {
643  return true;
644  }
645  }
646  else
647  {
648  return rule->willRenderFeature( feature, context );
649  }
650  }
651  else if ( rule->willRenderFeature( feature, context ) )
652  {
653  return true;
654  }
655  }
656  return false;
657 }
658 
660 {
661  QgsSymbolList lst;
662  if ( !isFilterOK( feature, context ) )
663  return lst;
664  if ( mSymbol )
665  lst.append( mSymbol.get() );
666 
667  const auto constMActiveChildren = mActiveChildren;
668  for ( Rule *rule : constMActiveChildren )
669  {
670  lst += rule->symbolsForFeature( feature, context );
671  }
672  return lst;
673 }
674 
676 {
677  QSet< QString> lst;
678  if ( !isFilterOK( feature, context ) )
679  return lst;
680  lst.insert( mRuleKey );
681 
682  const auto constMActiveChildren = mActiveChildren;
683  for ( Rule *rule : constMActiveChildren )
684  {
685  bool validKey = false;
686  if ( rule->isElse() )
687  {
688  RuleList lst = rulesForFeature( feature, context, false );
689  lst.removeOne( rule );
690 
691  if ( lst.empty() )
692  {
693  validKey = true;
694  }
695  }
696  else if ( !rule->isElse( ) && rule->willRenderFeature( feature, context ) )
697  {
698  validKey = true;
699  }
700 
701  if ( validKey )
702  {
703  lst.unite( rule->legendKeysForFeature( feature, context ) );
704  }
705  }
706  return lst;
707 }
708 
710 {
711  RuleList lst;
712  if ( ! isFilterOK( feature, context ) || ( context && ! isScaleOK( context->rendererScale() ) ) )
713  return lst;
714 
715  if ( mSymbol )
716  lst.append( this );
717 
718  RuleList listChildren = children();
719  if ( onlyActive )
720  listChildren = mActiveChildren;
721 
722  const auto constListChildren = listChildren;
723  for ( Rule *rule : constListChildren )
724  {
725  lst += rule->rulesForFeature( feature, context, onlyActive );
726  }
727  return lst;
728 }
729 
731 {
732  if ( mSymbol )
733  mSymbol->stopRender( context );
734 
735  const auto constMActiveChildren = mActiveChildren;
736  for ( Rule *rule : constMActiveChildren )
737  {
738  rule->stopRender( context );
739  }
740 
741  mActiveChildren.clear();
742  mSymbolNormZLevels.clear();
743 }
744 
746 {
747  QString symbolIdx = ruleElem.attribute( QStringLiteral( "symbol" ) );
748  QgsSymbol *symbol = nullptr;
749  if ( !symbolIdx.isEmpty() )
750  {
751  if ( symbolMap.contains( symbolIdx ) )
752  {
753  symbol = symbolMap.take( symbolIdx );
754  }
755  else
756  {
757  QgsDebugMsg( "symbol for rule " + symbolIdx + " not found!" );
758  }
759  }
760 
761  QString filterExp = ruleElem.attribute( QStringLiteral( "filter" ) );
762  QString label = ruleElem.attribute( QStringLiteral( "label" ) );
763  QString description = ruleElem.attribute( QStringLiteral( "description" ) );
764  int scaleMinDenom = ruleElem.attribute( QStringLiteral( "scalemindenom" ), QStringLiteral( "0" ) ).toInt();
765  int scaleMaxDenom = ruleElem.attribute( QStringLiteral( "scalemaxdenom" ), QStringLiteral( "0" ) ).toInt();
766  QString ruleKey = ruleElem.attribute( QStringLiteral( "key" ) );
767  Rule *rule = new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
768 
769  if ( !ruleKey.isEmpty() )
770  rule->mRuleKey = ruleKey;
771 
772  rule->setActive( ruleElem.attribute( QStringLiteral( "checkstate" ), QStringLiteral( "1" ) ).toInt() );
773 
774  QDomElement childRuleElem = ruleElem.firstChildElement( QStringLiteral( "rule" ) );
775  while ( !childRuleElem.isNull() )
776  {
777  Rule *childRule = create( childRuleElem, symbolMap );
778  if ( childRule )
779  {
780  rule->appendChild( childRule );
781  }
782  else
783  {
784  QgsDebugMsg( QStringLiteral( "failed to init a child rule!" ) );
785  }
786  childRuleElem = childRuleElem.nextSiblingElement( QStringLiteral( "rule" ) );
787  }
788 
789  return rule;
790 }
791 
793 {
794  RuleList l;
795  for ( QgsRuleBasedRenderer::Rule *c : mChildren )
796  {
797  l += c;
798  l += c->descendants();
799  }
800  return l;
801 }
802 
804 {
805  if ( ruleElem.localName() != QLatin1String( "Rule" ) )
806  {
807  QgsDebugMsg( QStringLiteral( "invalid element: Rule element expected, %1 found!" ).arg( ruleElem.tagName() ) );
808  return nullptr;
809  }
810 
811  QString label, description, filterExp;
812  int scaleMinDenom = 0, scaleMaxDenom = 0;
813  QgsSymbolLayerList layers;
814 
815  // retrieve the Rule element child nodes
816  QDomElement childElem = ruleElem.firstChildElement();
817  while ( !childElem.isNull() )
818  {
819  if ( childElem.localName() == QLatin1String( "Name" ) )
820  {
821  // <se:Name> tag contains the rule identifier,
822  // so prefer title tag for the label property value
823  if ( label.isEmpty() )
824  label = childElem.firstChild().nodeValue();
825  }
826  else if ( childElem.localName() == QLatin1String( "Description" ) )
827  {
828  // <se:Description> can contains a title and an abstract
829  QDomElement titleElem = childElem.firstChildElement( QStringLiteral( "Title" ) );
830  if ( !titleElem.isNull() )
831  {
832  label = titleElem.firstChild().nodeValue();
833  }
834 
835  QDomElement abstractElem = childElem.firstChildElement( QStringLiteral( "Abstract" ) );
836  if ( !abstractElem.isNull() )
837  {
838  description = abstractElem.firstChild().nodeValue();
839  }
840  }
841  else if ( childElem.localName() == QLatin1String( "Abstract" ) )
842  {
843  // <sld:Abstract> (v1.0)
844  description = childElem.firstChild().nodeValue();
845  }
846  else if ( childElem.localName() == QLatin1String( "Title" ) )
847  {
848  // <sld:Title> (v1.0)
849  label = childElem.firstChild().nodeValue();
850  }
851  else if ( childElem.localName() == QLatin1String( "Filter" ) )
852  {
854  if ( filter )
855  {
856  if ( filter->hasParserError() )
857  {
858  QgsDebugMsg( "parser error: " + filter->parserErrorString() );
859  }
860  else
861  {
862  filterExp = filter->expression();
863  }
864  delete filter;
865  }
866  }
867  else if ( childElem.localName() == QLatin1String( "MinScaleDenominator" ) )
868  {
869  bool ok;
870  int v = childElem.firstChild().nodeValue().toInt( &ok );
871  if ( ok )
872  scaleMinDenom = v;
873  }
874  else if ( childElem.localName() == QLatin1String( "MaxScaleDenominator" ) )
875  {
876  bool ok;
877  int v = childElem.firstChild().nodeValue().toInt( &ok );
878  if ( ok )
879  scaleMaxDenom = v;
880  }
881  else if ( childElem.localName().endsWith( QLatin1String( "Symbolizer" ) ) )
882  {
883  // create symbol layers for this symbolizer
884  QgsSymbolLayerUtils::createSymbolLayerListFromSld( childElem, geomType, layers );
885  }
886 
887  childElem = childElem.nextSiblingElement();
888  }
889 
890  // now create the symbol
891  QgsSymbol *symbol = nullptr;
892  if ( !layers.isEmpty() )
893  {
894  switch ( geomType )
895  {
897  symbol = new QgsLineSymbol( layers );
898  break;
899 
901  symbol = new QgsFillSymbol( layers );
902  break;
903 
905  symbol = new QgsMarkerSymbol( layers );
906  break;
907 
908  default:
909  QgsDebugMsg( QStringLiteral( "invalid geometry type: found %1" ).arg( geomType ) );
910  return nullptr;
911  }
912  }
913 
914  // and then create and return the new rule
915  return new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
916 }
917 
918 
920 
922  : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
923  , mRootRule( root )
924 {
925 }
926 
928  : QgsFeatureRenderer( QStringLiteral( "RuleRenderer" ) )
929 {
930  mRootRule = new Rule( nullptr ); // root has no symbol, no filter etc - just a container
931  mRootRule->appendChild( new Rule( defaultSymbol ) );
932 }
933 
935 {
936  delete mRootRule;
937 }
938 
939 
941 {
942  // not used at all
943  return nullptr;
944 }
945 
947  QgsRenderContext &context,
948  int layer,
949  bool selected,
950  bool drawVertexMarker )
951 {
952  Q_UNUSED( layer )
953 
954  int flags = ( selected ? FeatIsSelected : 0 ) | ( drawVertexMarker ? FeatDrawMarkers : 0 );
955  mCurrentFeatures.append( FeatureToRender( feature, flags ) );
956 
957  // check each active rule
958  return mRootRule->renderFeature( mCurrentFeatures.last(), context, mRenderQueue ) == Rule::Rendered;
959 }
960 
961 
963 {
964  QgsFeatureRenderer::startRender( context, fields );
965 
966  // prepare active children
967  mRootRule->startRender( context, fields, mFilter );
968 
969  QSet<int> symbolZLevelsSet = mRootRule->collectZLevels();
970  QList<int> symbolZLevels = qgis::setToList( symbolZLevelsSet );
971  std::sort( symbolZLevels.begin(), symbolZLevels.end() );
972 
973  // create mapping from unnormalized levels [unlimited range] to normalized levels [0..N-1]
974  // and prepare rendering queue
975  QMap<int, int> zLevelsToNormLevels;
976  int maxNormLevel = -1;
977  const auto constSymbolZLevels = symbolZLevels;
978  for ( int zLevel : constSymbolZLevels )
979  {
980  zLevelsToNormLevels[zLevel] = ++maxNormLevel;
981  mRenderQueue.append( RenderLevel( zLevel ) );
982  QgsDebugMsgLevel( QStringLiteral( "zLevel %1 -> %2" ).arg( zLevel ).arg( maxNormLevel ), 4 );
983  }
984 
985  mRootRule->setNormZLevels( zLevelsToNormLevels );
986 }
987 
989 {
991 
992  //
993  // do the actual rendering
994  //
995 
996  // go through all levels
997  if ( !context.renderingStopped() )
998  {
999  const auto constMRenderQueue = mRenderQueue;
1000  for ( const RenderLevel &level : constMRenderQueue )
1001  {
1002  //QgsDebugMsg(QString("level %1").arg(level.zIndex));
1003  // go through all jobs at the level
1004  for ( const RenderJob *job : std::as_const( level.jobs ) )
1005  {
1006  context.expressionContext().setFeature( job->ftr.feat );
1007  //QgsDebugMsg(QString("job fid %1").arg(job->f->id()));
1008  // render feature - but only with symbol layers with specified zIndex
1009  QgsSymbol *s = job->symbol;
1010  int count = s->symbolLayerCount();
1011  for ( int i = 0; i < count; i++ )
1012  {
1013  // TODO: better solution for this
1014  // renderFeatureWithSymbol asks which symbol layer to draw
1015  // but there are multiple transforms going on!
1016  if ( s->symbolLayer( i )->renderingPass() == level.zIndex )
1017  {
1018  int flags = job->ftr.flags;
1019  renderFeatureWithSymbol( job->ftr.feat, job->symbol, context, i, flags & FeatIsSelected, flags & FeatDrawMarkers );
1020  }
1021  }
1022  }
1023  }
1024  }
1025 
1026  // clean current features
1027  mCurrentFeatures.clear();
1028 
1029  // clean render queue
1030  mRenderQueue.clear();
1031 
1032  // clean up rules from temporary stuff
1033  mRootRule->stopRender( context );
1034 }
1035 
1037 {
1038  return mFilter;
1039 }
1040 
1041 QSet<QString> QgsRuleBasedRenderer::usedAttributes( const QgsRenderContext &context ) const
1042 {
1043  return mRootRule->usedAttributes( context );
1044 }
1045 
1047 {
1048  return mRootRule->needsGeometry();
1049 }
1050 
1052 {
1053  QgsRuleBasedRenderer::Rule *clonedRoot = mRootRule->clone();
1054 
1055  // normally with clone() the individual rules get new keys (UUID), but here we want to keep
1056  // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
1057  clonedRoot->setRuleKey( mRootRule->ruleKey() );
1058  RuleList origDescendants = mRootRule->descendants();
1059  RuleList clonedDescendants = clonedRoot->descendants();
1060  Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
1061  for ( int i = 0; i < origDescendants.count(); ++i )
1062  clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
1063 
1064  QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( clonedRoot );
1065 
1066  copyRendererData( r );
1067  return r;
1068 }
1069 
1070 void QgsRuleBasedRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
1071 {
1072  mRootRule->toSld( doc, element, props );
1073 }
1074 
1075 // TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items
1077 {
1078  return mRootRule->symbols( context );
1079 }
1080 
1081 QDomElement QgsRuleBasedRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
1082 {
1083  QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
1084  rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "RuleRenderer" ) );
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 
1095  saveRendererData( doc, rendererElem, context );
1096 
1097  return rendererElem;
1098 }
1099 
1101 {
1102  return true;
1103 }
1104 
1106 {
1107  Rule *rule = mRootRule->findRuleByKey( key );
1108  return rule ? rule->active() : true;
1109 }
1110 
1111 void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1112 {
1113  Rule *rule = mRootRule->findRuleByKey( key );
1114  if ( rule )
1115  rule->setActive( state );
1116 }
1117 
1118 void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1119 {
1120  Rule *rule = mRootRule->findRuleByKey( key );
1121  if ( rule )
1122  rule->setSymbol( symbol );
1123  else
1124  delete symbol;
1125 }
1126 
1128 {
1129  return mRootRule->legendSymbolItems();
1130 }
1131 
1132 
1134 {
1135  // load symbols
1136  QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
1137  if ( symbolsElem.isNull() )
1138  return nullptr;
1139 
1140  QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1141 
1142  QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
1143 
1144  Rule *root = Rule::create( rulesElem, symbolMap );
1145  if ( !root )
1146  return nullptr;
1147 
1148  QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( root );
1149 
1150  // delete symbols if there are any more
1152 
1153  return r;
1154 }
1155 
1157 {
1158  // retrieve child rules
1159  Rule *root = nullptr;
1160 
1161  QDomElement ruleElem = element.firstChildElement( QStringLiteral( "Rule" ) );
1162  while ( !ruleElem.isNull() )
1163  {
1164  Rule *child = Rule::createFromSld( ruleElem, geomType );
1165  if ( child )
1166  {
1167  // create the root rule if not done before
1168  if ( !root )
1169  root = new Rule( nullptr );
1170 
1171  root->appendChild( child );
1172  }
1173 
1174  ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
1175  }
1176 
1177  if ( !root )
1178  {
1179  // no valid rules was found
1180  return nullptr;
1181  }
1182 
1183  // create and return the new renderer
1184  return new QgsRuleBasedRenderer( root );
1185 }
1186 
1189 
1191 {
1192  QString attr = r->classAttribute();
1193  // categorizedAttr could be either an attribute name or an expression.
1194  // the only way to differentiate is to test it as an expression...
1195  QgsExpression testExpr( attr );
1196  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1197  {
1198  //not an expression, so need to quote column name
1199  attr = QgsExpression::quotedColumnRef( attr );
1200  }
1201 
1202  const auto constCategories = r->categories();
1203  for ( const QgsRendererCategory &cat : constCategories )
1204  {
1205  QString value;
1206  // not quoting numbers saves a type cast
1207  if ( cat.value().isNull() )
1208  value = "NULL";
1209  else if ( cat.value().type() == QVariant::Int )
1210  value = cat.value().toString();
1211  else if ( cat.value().type() == QVariant::Double )
1212  // we loose precision here - so we may miss some categories :-(
1213  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1214  value = QString::number( cat.value().toDouble(), 'f', 4 );
1215  else
1216  value = QgsExpression::quotedString( cat.value().toString() );
1217  const QString filter = QStringLiteral( "%1 %2 %3" ).arg( attr, cat.value().isNull() ? QStringLiteral( "IS" ) : QStringLiteral( "=" ), value );
1218  const QString label = !cat.label().isEmpty() ? cat.label() :
1219  cat.value().isValid() ? value : QString();
1220  initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1221  }
1222 }
1223 
1225 {
1226  QString attr = r->classAttribute();
1227  // categorizedAttr could be either an attribute name or an expression.
1228  // the only way to differentiate is to test it as an expression...
1229  QgsExpression testExpr( attr );
1230  if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1231  {
1232  //not an expression, so need to quote column name
1233  attr = QgsExpression::quotedColumnRef( attr );
1234  }
1235  else if ( !testExpr.isField() )
1236  {
1237  //otherwise wrap expression in brackets
1238  attr = QStringLiteral( "(%1)" ).arg( attr );
1239  }
1240 
1241  bool firstRange = true;
1242  const auto constRanges = r->ranges();
1243  for ( const QgsRendererRange &rng : constRanges )
1244  {
1245  // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1246  // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1247  QString filter = QStringLiteral( "%1 %2 %3 AND %1 <= %4" ).arg( attr, firstRange ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
1248  QString::number( rng.lowerValue(), 'f', 4 ),
1249  QString::number( rng.upperValue(), 'f', 4 ) );
1250  firstRange = false;
1251  QString label = rng.label().isEmpty() ? filter : rng.label();
1252  initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1253  }
1254 }
1255 
1257 {
1258  std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1259  double oldScale = initialRule->maximumScale();
1260  double maxDenom = initialRule->minimumScale();
1261  QgsSymbol *symbol = initialRule->symbol();
1262  const auto constScales = scales;
1263  for ( int scale : constScales )
1264  {
1265  if ( initialRule->maximumScale() >= scale )
1266  continue; // jump over the first scales out of the interval
1267  if ( maxDenom != 0 && maxDenom <= scale )
1268  break; // ignore the latter scales out of the interval
1269  initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( scale ) ) );
1270  oldScale = scale;
1271  }
1272  // last rule
1273  initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( maxDenom ) ) );
1274 }
1275 
1277 {
1278  QString msg( QStringLiteral( "Rule-based renderer:\n" ) );
1279  msg += mRootRule->dump();
1280  return msg;
1281 }
1282 
1284 {
1285  return mRootRule->willRenderFeature( feature, &context );
1286 }
1287 
1289 {
1290  return mRootRule->symbolsForFeature( feature, &context );
1291 }
1292 
1294 {
1295  return mRootRule->symbolsForFeature( feature, &context );
1296 }
1297 
1298 QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1299 {
1300  return mRootRule->legendKeysForFeature( feature, &context );
1301 }
1302 
1304 {
1305  return mRootRule->accept( visitor );
1306 }
1307 
1309 {
1310  std::unique_ptr< QgsRuleBasedRenderer > r;
1311  if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1312  {
1313  r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1314  }
1315  else if ( renderer->type() == QLatin1String( "singleSymbol" ) )
1316  {
1317  const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1318  if ( !singleSymbolRenderer )
1319  return nullptr;
1320 
1321  std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1322  r = std::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1323  }
1324  else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1325  {
1326  const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1327  if ( !categorizedRenderer )
1328  return nullptr;
1329 
1330  QString attr = categorizedRenderer->classAttribute();
1331  // categorizedAttr could be either an attribute name or an expression.
1332  bool isField = false;
1333  if ( layer )
1334  {
1335  isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1336  }
1337  else
1338  {
1339  QgsExpression testExpr( attr );
1340  isField = testExpr.hasParserError() || testExpr.isField();
1341  }
1342  if ( isField && !attr.contains( '\"' ) )
1343  {
1344  //not an expression, so need to quote column name
1345  attr = QgsExpression::quotedColumnRef( attr );
1346  }
1347 
1348  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1349 
1350  QString expression;
1351  QString value;
1352  QgsRendererCategory category;
1353  for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1354  {
1355  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1356 
1357  rule->setLabel( category.label() );
1358 
1359  //We first define the rule corresponding to the category
1360  if ( category.value().type() == QVariant::List )
1361  {
1362  QStringList values;
1363  const QVariantList list = category.value().toList();
1364  for ( const QVariant &v : list )
1365  {
1366  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1367  if ( QVariant( v ).convert( QVariant::Double ) )
1368  {
1369  values << v.toString();
1370  }
1371  else
1372  {
1373  values << QgsExpression::quotedString( v.toString() );
1374  }
1375  }
1376 
1377  if ( values.empty() )
1378  {
1379  expression = QStringLiteral( "ELSE" );
1380  }
1381  else
1382  {
1383  expression = QStringLiteral( "%1 IN (%2)" ).arg( attr, values.join( ',' ) );
1384  }
1385  }
1386  else
1387  {
1388  //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1389  if ( category.value().convert( QVariant::Double ) )
1390  {
1391  value = category.value().toString();
1392  }
1393  else
1394  {
1395  value = QgsExpression::quotedString( category.value().toString() );
1396  }
1397 
1398  //An empty category is equivalent to the ELSE keyword
1399  if ( value == QLatin1String( "''" ) )
1400  {
1401  expression = QStringLiteral( "ELSE" );
1402  }
1403  else
1404  {
1405  expression = QStringLiteral( "%1 = %2" ).arg( attr, value );
1406  }
1407  }
1408  rule->setFilterExpression( expression );
1409 
1410  //Then we construct an equivalent symbol.
1411  //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1412  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1413 
1414  std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1415  rule->setSymbol( origSymbol.release() );
1416 
1417  rootrule->appendChild( rule.release() );
1418  }
1419 
1420  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1421  }
1422  else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1423  {
1424  const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1425  if ( !graduatedRenderer )
1426  return nullptr;
1427 
1428  QString attr = graduatedRenderer->classAttribute();
1429  // categorizedAttr could be either an attribute name or an expression.
1430  // the only way to differentiate is to test it as an expression...
1431  bool isField = false;
1432  if ( layer )
1433  {
1434  isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1435  }
1436  else
1437  {
1438  QgsExpression testExpr( attr );
1439  isField = testExpr.hasParserError() || testExpr.isField();
1440  }
1441  if ( isField && !attr.contains( '\"' ) )
1442  {
1443  //not an expression, so need to quote column name
1444  attr = QgsExpression::quotedColumnRef( attr );
1445  }
1446  else if ( !isField )
1447  {
1448  //otherwise wrap expression in brackets
1449  attr = QStringLiteral( "(%1)" ).arg( attr );
1450  }
1451 
1452  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1453 
1454  QString expression;
1455  QgsRendererRange range;
1456  for ( int i = 0; i < graduatedRenderer->ranges().size(); ++i )
1457  {
1458  range = graduatedRenderer->ranges().value( i );
1459  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1460  rule->setLabel( range.label() );
1461  if ( i == 0 )//The lower boundary of the first range is included, while it is excluded for the others
1462  {
1463  expression = attr + " >= " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1464  attr + " <= " + QString::number( range.upperValue(), 'f' );
1465  }
1466  else
1467  {
1468  expression = attr + " > " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1469  attr + " <= " + QString::number( range.upperValue(), 'f' );
1470  }
1471  rule->setFilterExpression( expression );
1472 
1473  //Then we construct an equivalent symbol.
1474  //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1475  //data dependent area and rotation, so we need to convert these to obtain the same rendering
1476 
1477  std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1478  rule->setSymbol( symbol.release() );
1479 
1480  rootrule->appendChild( rule.release() );
1481  }
1482 
1483  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1484  }
1485  else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1486  {
1487  if ( const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer ) )
1488  return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1489  }
1490  else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1491  {
1492  if ( const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer ) )
1493  r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1494  }
1495  else if ( renderer->type() == QLatin1String( "mergedFeatureRenderer" ) )
1496  {
1497  if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
1498  r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
1499  }
1500  else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1501  {
1502  const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1503 
1504  std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1505 
1506  QgsFeatureRequest req;
1508  req.setNoAttributes();
1509  QgsFeatureIterator it = layer->getFeatures( req );
1510  QgsFeature feature;
1511  while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
1512  {
1513  if ( feature.embeddedSymbol() )
1514  {
1515  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1516  rule->setFilterExpression( QStringLiteral( "$id=%1" ).arg( feature.id() ) );
1517  rule->setLabel( QString::number( feature.id() ) );
1518  rule->setSymbol( feature.embeddedSymbol()->clone() );
1519  rootrule->appendChild( rule.release() );
1520  }
1521  }
1522 
1523  std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1524  rule->setFilterExpression( QStringLiteral( "ELSE" ) );
1525  rule->setLabel( QObject::tr( "All other features" ) );
1526  rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
1527  rootrule->appendChild( rule.release() );
1528 
1529  r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1530  }
1531 
1532  if ( r )
1533  {
1534  renderer->copyRendererData( r.get() );
1535  }
1536 
1537  return r.release();
1538 }
1539 
1540 void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1541 {
1542  QString sizeExpression;
1543  switch ( symbol->type() )
1544  {
1546  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1547  {
1548  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1549  if ( ! sizeScaleField.isEmpty() )
1550  {
1551  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1553  }
1554  if ( ! rotationField.isEmpty() )
1555  {
1557  }
1558  }
1559  break;
1561  if ( ! sizeScaleField.isEmpty() )
1562  {
1563  for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1564  {
1565  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "SimpleLine" ) )
1566  {
1567  QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1568  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( lsl->width() ).arg( sizeScaleField );
1570  }
1571  if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "MarkerLine" ) )
1572  {
1573  QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1574  for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1575  {
1576  QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1577  sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1579  }
1580  }
1581  }
1582  }
1583  break;
1584  default:
1585  break;
1586  }
1587 }
@ 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 int expressionToLayerFieldIndex(const QString &expression, const QgsVectorLayer *layer)
Attempts to resolve an expression to a field index from the given layer.
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:1222
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:1578
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.