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