QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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
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"
29#include "qgsproperty.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
43QgsRuleBasedRenderer::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
145void 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
192QString 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
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
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
321QDomElement 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
353void 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
548void 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
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( symbolZLevelsSet.begin(), symbolZLevelsSet.end() );
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
1041QSet<QString> QgsRuleBasedRenderer::usedAttributes( const QgsRenderContext &context ) const
1042{
1043 return mRootRule->usedAttributes( context );
1044}
1045
1047{
1048 return mRootRule->needsGeometry();
1049}
1050
1052{
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
1070void 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
1081QDomElement 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
1111void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1112{
1113 Rule *rule = mRootRule->findRuleByKey( key );
1114 if ( rule )
1115 rule->setActive( state );
1116}
1117
1118QString QgsRuleBasedRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *, bool &ok ) const
1119{
1120 ok = false;
1121 Rule *rule = mRootRule->findRuleByKey( key );
1122 if ( !rule )
1123 return QString();
1124
1125 std::function<QString( Rule *rule )> ruleToExpression;
1126 ruleToExpression = [&ruleToExpression]( Rule * rule ) -> QString
1127 {
1128 if ( rule->isElse() && rule->parent() )
1129 {
1130 // gather the expressions for all other rules on this level and invert them
1131
1132 QStringList otherRules;
1133 const QList<QgsRuleBasedRenderer::Rule *> siblings = rule->parent()->children();
1134 for ( Rule *sibling : siblings )
1135 {
1136 if ( sibling == rule )
1137 continue;
1138
1139 const QString siblingExpression = ruleToExpression( sibling );
1140 if ( siblingExpression.isEmpty() )
1141 return QStringLiteral( "FALSE" ); // nothing will match this rule
1142
1143 otherRules.append( siblingExpression );
1144 }
1145
1146 if ( otherRules.empty() )
1147 return QStringLiteral( "TRUE" ); // all features will match the else rule
1148 else
1149 return (
1150 otherRules.size() > 1
1151 ? QStringLiteral( "NOT ((%1))" ).arg( otherRules.join( QLatin1String( ") OR (" ) ) )
1152 : QStringLiteral( "NOT (%1)" ).arg( otherRules.at( 0 ) )
1153 );
1154 }
1155 else
1156 {
1157 QStringList ruleParts;
1158 if ( !rule->filterExpression().isEmpty() )
1159 ruleParts.append( rule->filterExpression() );
1160
1161 if ( !qgsDoubleNear( rule->minimumScale(), 0.0 ) )
1162 ruleParts.append( QStringLiteral( "@map_scale <= %1" ).arg( rule->minimumScale() ) );
1163
1164 if ( !qgsDoubleNear( rule->maximumScale(), 0.0 ) )
1165 ruleParts.append( QStringLiteral( "@map_scale >= %1" ).arg( rule->maximumScale() ) );
1166
1167 if ( !ruleParts.empty() )
1168 {
1169 return (
1170 ruleParts.size() > 1
1171 ? QStringLiteral( "(%1)" ).arg( ruleParts.join( QLatin1String( ") AND (" ) ) )
1172 : ruleParts.at( 0 )
1173 );
1174 }
1175 else
1176 {
1177 return QString();
1178 }
1179 }
1180 };
1181
1182 QStringList parts;
1183 while ( rule )
1184 {
1185 const QString ruleFilter = ruleToExpression( rule );
1186 if ( !ruleFilter.isEmpty() )
1187 parts.append( ruleFilter );
1188
1189 rule = rule->parent();
1190 }
1191
1192 ok = true;
1193 return parts.empty() ? QStringLiteral( "TRUE" )
1194 : ( parts.size() > 1
1195 ? QStringLiteral( "(%1)" ).arg( parts.join( QLatin1String( ") AND (" ) ) )
1196 : parts.at( 0 ) );
1197}
1198
1199void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1200{
1201 Rule *rule = mRootRule->findRuleByKey( key );
1202 if ( rule )
1203 rule->setSymbol( symbol );
1204 else
1205 delete symbol;
1206}
1207
1209{
1210 return mRootRule->legendSymbolItems();
1211}
1212
1213
1215{
1216 // load symbols
1217 QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
1218 if ( symbolsElem.isNull() )
1219 return nullptr;
1220
1221 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1222
1223 QDomElement rulesElem = element.firstChildElement( QStringLiteral( "rules" ) );
1224
1225 Rule *root = Rule::create( rulesElem, symbolMap );
1226 if ( !root )
1227 return nullptr;
1228
1230
1231 // delete symbols if there are any more
1233
1234 return r;
1235}
1236
1238{
1239 // retrieve child rules
1240 Rule *root = nullptr;
1241
1242 QDomElement ruleElem = element.firstChildElement( QStringLiteral( "Rule" ) );
1243 while ( !ruleElem.isNull() )
1244 {
1245 Rule *child = Rule::createFromSld( ruleElem, geomType );
1246 if ( child )
1247 {
1248 // create the root rule if not done before
1249 if ( !root )
1250 root = new Rule( nullptr );
1251
1252 root->appendChild( child );
1253 }
1254
1255 ruleElem = ruleElem.nextSiblingElement( QStringLiteral( "Rule" ) );
1256 }
1257
1258 if ( !root )
1259 {
1260 // no valid rules was found
1261 return nullptr;
1262 }
1263
1264 // create and return the new renderer
1265 return new QgsRuleBasedRenderer( root );
1266}
1267
1270
1272{
1273 QString attr = r->classAttribute();
1274 // categorizedAttr could be either an attribute name or an expression.
1275 // the only way to differentiate is to test it as an expression...
1276 QgsExpression testExpr( attr );
1277 if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1278 {
1279 //not an expression, so need to quote column name
1280 attr = QgsExpression::quotedColumnRef( attr );
1281 }
1282
1283 const auto constCategories = r->categories();
1284 for ( const QgsRendererCategory &cat : constCategories )
1285 {
1286 QString value;
1287 // not quoting numbers saves a type cast
1288 if ( QgsVariantUtils::isNull( cat.value() ) )
1289 value = "NULL";
1290 else if ( cat.value().type() == QVariant::Int )
1291 value = cat.value().toString();
1292 else if ( cat.value().type() == QVariant::Double )
1293 // we loose precision here - so we may miss some categories :-(
1294 // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1295 value = QString::number( cat.value().toDouble(), 'f', 4 );
1296 else
1297 value = QgsExpression::quotedString( cat.value().toString() );
1298 const QString filter = QStringLiteral( "%1 %2 %3" ).arg( attr, QgsVariantUtils::isNull( cat.value() ) ? QStringLiteral( "IS" ) : QStringLiteral( "=" ), value );
1299 const QString label = !cat.label().isEmpty() ? cat.label() :
1300 cat.value().isValid() ? value : QString();
1301 initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1302 }
1303}
1304
1306{
1307 QString attr = r->classAttribute();
1308 // categorizedAttr could be either an attribute name or an expression.
1309 // the only way to differentiate is to test it as an expression...
1310 QgsExpression testExpr( attr );
1311 if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1312 {
1313 //not an expression, so need to quote column name
1314 attr = QgsExpression::quotedColumnRef( attr );
1315 }
1316 else if ( !testExpr.isField() )
1317 {
1318 //otherwise wrap expression in brackets
1319 attr = QStringLiteral( "(%1)" ).arg( attr );
1320 }
1321
1322 bool firstRange = true;
1323 const auto constRanges = r->ranges();
1324 for ( const QgsRendererRange &rng : constRanges )
1325 {
1326 // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1327 // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1328 QString filter = QStringLiteral( "%1 %2 %3 AND %1 <= %4" ).arg( attr, firstRange ? QStringLiteral( ">=" ) : QStringLiteral( ">" ),
1329 QString::number( rng.lowerValue(), 'f', 4 ),
1330 QString::number( rng.upperValue(), 'f', 4 ) );
1331 firstRange = false;
1332 QString label = rng.label().isEmpty() ? filter : rng.label();
1333 initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1334 }
1335}
1336
1338{
1339 std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1340 double oldScale = initialRule->maximumScale();
1341 double maxDenom = initialRule->minimumScale();
1342 QgsSymbol *symbol = initialRule->symbol();
1343 const auto constScales = scales;
1344 for ( int scale : constScales )
1345 {
1346 if ( initialRule->maximumScale() >= scale )
1347 continue; // jump over the first scales out of the interval
1348 if ( maxDenom != 0 && maxDenom <= scale )
1349 break; // ignore the latter scales out of the interval
1350 initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( scale ) ) );
1351 oldScale = scale;
1352 }
1353 // last rule
1354 initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), QStringLiteral( "%1 - %2" ).arg( oldScale ).arg( maxDenom ) ) );
1355}
1356
1358{
1359 QString msg( QStringLiteral( "Rule-based renderer:\n" ) );
1360 msg += mRootRule->dump();
1361 return msg;
1362}
1363
1365{
1366 return mRootRule->willRenderFeature( feature, &context );
1367}
1368
1370{
1371 return mRootRule->symbolsForFeature( feature, &context );
1372}
1373
1375{
1376 return mRootRule->symbolsForFeature( feature, &context );
1377}
1378
1379QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1380{
1381 return mRootRule->legendKeysForFeature( feature, &context );
1382}
1383
1385{
1386 return mRootRule->accept( visitor );
1387}
1388
1390{
1391 std::unique_ptr< QgsRuleBasedRenderer > r;
1392 if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1393 {
1394 r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1395 }
1396 else if ( renderer->type() == QLatin1String( "singleSymbol" ) )
1397 {
1398 const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1399 if ( !singleSymbolRenderer )
1400 return nullptr;
1401
1402 std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1403 r = std::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1404 }
1405 else if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1406 {
1407 const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1408 if ( !categorizedRenderer )
1409 return nullptr;
1410
1411 QString attr = categorizedRenderer->classAttribute();
1412 // categorizedAttr could be either an attribute name or an expression.
1413 bool isField = false;
1414 if ( layer )
1415 {
1416 isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1417 }
1418 else
1419 {
1420 QgsExpression testExpr( attr );
1421 isField = testExpr.hasParserError() || testExpr.isField();
1422 }
1423 if ( isField && !attr.contains( '\"' ) )
1424 {
1425 //not an expression, so need to quote column name
1426 attr = QgsExpression::quotedColumnRef( attr );
1427 }
1428
1429 std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1430
1431 QString expression;
1432 QString value;
1433 QgsRendererCategory category;
1434 for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1435 {
1436 std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1437
1438 rule->setLabel( category.label() );
1439
1440 //We first define the rule corresponding to the category
1441 if ( category.value().type() == QVariant::List )
1442 {
1443 QStringList values;
1444 const QVariantList list = category.value().toList();
1445 for ( const QVariant &v : list )
1446 {
1447 //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1448 if ( QVariant( v ).convert( QVariant::Double ) )
1449 {
1450 values << v.toString();
1451 }
1452 else
1453 {
1454 values << QgsExpression::quotedString( v.toString() );
1455 }
1456 }
1457
1458 if ( values.empty() )
1459 {
1460 expression = QStringLiteral( "ELSE" );
1461 }
1462 else
1463 {
1464 expression = QStringLiteral( "%1 IN (%2)" ).arg( attr, values.join( ',' ) );
1465 }
1466 }
1467 else
1468 {
1469 //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1470 if ( category.value().convert( QVariant::Double ) )
1471 {
1472 value = category.value().toString();
1473 }
1474 else
1475 {
1476 value = QgsExpression::quotedString( category.value().toString() );
1477 }
1478
1479 //An empty category is equivalent to the ELSE keyword
1480 if ( value == QLatin1String( "''" ) )
1481 {
1482 expression = QStringLiteral( "ELSE" );
1483 }
1484 else
1485 {
1486 expression = QStringLiteral( "%1 = %2" ).arg( attr, value );
1487 }
1488 }
1489 rule->setFilterExpression( expression );
1490
1491 //Then we construct an equivalent symbol.
1492 //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1493 //data dependent area and rotation, so we need to convert these to obtain the same rendering
1494
1495 std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1496 rule->setSymbol( origSymbol.release() );
1497
1498 rootrule->appendChild( rule.release() );
1499 }
1500
1501 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1502 }
1503 else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1504 {
1505 const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1506 if ( !graduatedRenderer )
1507 return nullptr;
1508
1509 QString attr = graduatedRenderer->classAttribute();
1510 // categorizedAttr could be either an attribute name or an expression.
1511 // the only way to differentiate is to test it as an expression...
1512 bool isField = false;
1513 if ( layer )
1514 {
1515 isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1516 }
1517 else
1518 {
1519 QgsExpression testExpr( attr );
1520 isField = testExpr.hasParserError() || testExpr.isField();
1521 }
1522 if ( isField && !attr.contains( '\"' ) )
1523 {
1524 //not an expression, so need to quote column name
1525 attr = QgsExpression::quotedColumnRef( attr );
1526 }
1527 else if ( !isField )
1528 {
1529 //otherwise wrap expression in brackets
1530 attr = QStringLiteral( "(%1)" ).arg( attr );
1531 }
1532
1533 std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1534
1535 QString expression;
1536 QgsRendererRange range;
1537 for ( int i = 0; i < graduatedRenderer->ranges().size(); ++i )
1538 {
1539 range = graduatedRenderer->ranges().value( i );
1540 std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1541 rule->setLabel( range.label() );
1542 if ( i == 0 )//The lower boundary of the first range is included, while it is excluded for the others
1543 {
1544 expression = attr + " >= " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1545 attr + " <= " + QString::number( range.upperValue(), 'f' );
1546 }
1547 else
1548 {
1549 expression = attr + " > " + QString::number( range.lowerValue(), 'f' ) + " AND " + \
1550 attr + " <= " + QString::number( range.upperValue(), 'f' );
1551 }
1552 rule->setFilterExpression( expression );
1553
1554 //Then we construct an equivalent symbol.
1555 //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1556 //data dependent area and rotation, so we need to convert these to obtain the same rendering
1557
1558 std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1559 rule->setSymbol( symbol.release() );
1560
1561 rootrule->appendChild( rule.release() );
1562 }
1563
1564 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1565 }
1566 else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1567 {
1568 if ( const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer ) )
1569 return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1570 }
1571 else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1572 {
1573 if ( const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer ) )
1574 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1575 }
1576 else if ( renderer->type() == QLatin1String( "mergedFeatureRenderer" ) )
1577 {
1578 if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
1579 r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
1580 }
1581 else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1582 {
1583 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1584
1585 std::unique_ptr< QgsRuleBasedRenderer::Rule > rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1586
1589 req.setNoAttributes();
1590 QgsFeatureIterator it = layer->getFeatures( req );
1591 QgsFeature feature;
1592 while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
1593 {
1594 if ( feature.embeddedSymbol() )
1595 {
1596 std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1597 rule->setFilterExpression( QStringLiteral( "$id=%1" ).arg( feature.id() ) );
1598 rule->setLabel( QString::number( feature.id() ) );
1599 rule->setSymbol( feature.embeddedSymbol()->clone() );
1600 rootrule->appendChild( rule.release() );
1601 }
1602 }
1603
1604 std::unique_ptr< QgsRuleBasedRenderer::Rule > rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1605 rule->setFilterExpression( QStringLiteral( "ELSE" ) );
1606 rule->setLabel( QObject::tr( "All other features" ) );
1607 rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
1608 rootrule->appendChild( rule.release() );
1609
1610 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1611 }
1612
1613 if ( r )
1614 {
1615 renderer->copyRendererData( r.get() );
1616 }
1617
1618 return r.release();
1619}
1620
1621void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1622{
1623 QString sizeExpression;
1624 switch ( symbol->type() )
1625 {
1627 for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1628 {
1629 QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1630 if ( ! sizeScaleField.isEmpty() )
1631 {
1632 sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1634 }
1635 if ( ! rotationField.isEmpty() )
1636 {
1638 }
1639 }
1640 break;
1642 if ( ! sizeScaleField.isEmpty() )
1643 {
1644 for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1645 {
1646 if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "SimpleLine" ) )
1647 {
1648 QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1649 sizeExpression = QStringLiteral( "%1*(%2)" ).arg( lsl->width() ).arg( sizeScaleField );
1651 }
1652 if ( symbol->symbolLayer( j )->layerType() == QLatin1String( "MarkerLine" ) )
1653 {
1654 QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1655 for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1656 {
1657 QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1658 sizeExpression = QStringLiteral( "%1*(%2)" ).arg( msl->size() ).arg( sizeScaleField );
1660 }
1661 }
1662 }
1663 }
1664 break;
1665 default:
1666 break;
1667 }
1668}
@ 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)
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
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
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:324
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
QString classAttribute() const
Returns the attribute name (or expression) used for the classification.
const QgsRangeList & ranges() const
Returns a list of all ranges used in the classification.
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
Returns the label used for the range.
QgsSymbol * symbol() const
Returns the symbol used for the range.
double upperValue() const
Returns the upper bound of the range.
double lowerValue() const
Returns the lower bound of the range.
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... you get it.
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
const QgsRuleBasedRenderer::RuleList & children() const
Returns all children rules of this rule.
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.
QgsRuleBasedRenderer::Rule * parent()
The parent rule.
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
bool isElse() const
Check if this rule is an ELSE rule.
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)
QString filterExpression() const
A filter that will check if this rule applies.
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
QString legendKeyToExpression(const QString &key, QgsVectorLayer *layer, bool &ok) const override
Attempts to convert the specified legend rule key to a QGIS expression matching the features displaye...
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:1342
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:93
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
Definition: qgssymbol.cpp:756
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
int symbolLayerCount() const
Returns the total number of symbol layers contained in the symbol.
Definition: qgssymbol.h:215
Qgis::SymbolType type() const
Returns the symbol's type.
Definition: qgssymbol.h:152
static bool isNull(const QVariant &variant)
Returns true if the specified variant should be considered a NULL value.
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:2527
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:29
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.