QGIS API Documentation 3.99.0-Master (d270888f95f)
Loading...
Searching...
No Matches
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
19#include "qgsexpression.h"
20#include "qgsfillsymbol.h"
22#include "qgslinesymbol.h"
23#include "qgslogger.h"
24#include "qgsmarkersymbol.h"
25#include "qgsogcutils.h"
27#include "qgsproperty.h"
28#include "qgsrendercontext.h"
29#include "qgsscaleutils.h"
31#include "qgssldexportcontext.h"
33#include "qgssymbollayer.h"
34#include "qgssymbollayerutils.h"
35#include "qgsvectorlayer.h"
36
37#include <QDomDocument>
38#include <QDomElement>
39#include <QSet>
40#include <QString>
41#include <QUuid>
42
43using namespace Qt::StringLiterals;
44
45QgsRuleBasedRenderer::Rule::Rule( QgsSymbol *symbol, int scaleMinDenom, int scaleMaxDenom, const QString &filterExp, const QString &label, const QString &description, bool elseRule )
46 : mSymbol( symbol )
47 , mMaximumScale( scaleMinDenom )
48 , mMinimumScale( scaleMaxDenom )
49 , mFilterExp( filterExp )
50 , mLabel( label )
51 , mDescription( description )
52 , mElseRule( elseRule )
53{
54 if ( mElseRule )
55 mFilterExp = u"ELSE"_s;
56
57 mRuleKey = QUuid::createUuid().toString();
58 initFilter();
59}
60
62{
63 qDeleteAll( mChildren );
64 // do NOT delete parent
65}
66
68{
69 if ( mFilterExp.trimmed().compare( "ELSE"_L1, Qt::CaseInsensitive ) == 0 )
70 {
71 mElseRule = true;
72 mFilter.reset();
73 }
74 else if ( mFilterExp.trimmed().isEmpty() )
75 {
76 mElseRule = false;
77 mFilter.reset();
78 }
79 else
80 {
81 mElseRule = false;
82 mFilter = std::make_unique< QgsExpression >( mFilterExp );
83 }
84}
85
87{
88 mChildren.append( rule );
89 rule->mParent = this;
90 updateElseRules();
91}
92
94{
95 mChildren.insert( i, rule );
96 rule->mParent = this;
97 updateElseRules();
98}
99
101{
102 mChildren.removeAll( rule );
103 delete rule;
104 updateElseRules();
105}
106
108{
109 delete mChildren.takeAt( i );
110 updateElseRules();
111}
112
114{
115 mChildren.removeAll( rule );
116 rule->mParent = nullptr;
117 updateElseRules();
118 return rule;
119}
120
122{
123 Rule *rule = mChildren.takeAt( i );
124 rule->mParent = nullptr;
125 updateElseRules();
126 return rule;
127}
128
130{
131 // we could use a hash / map for search if this will be slow...
132
133 if ( key == mRuleKey )
134 return this;
135
136 const auto constMChildren = mChildren;
137 for ( Rule *rule : constMChildren )
138 {
139 Rule *r = rule->findRuleByKey( key );
140 if ( r )
141 return r;
142 }
143 return nullptr;
144}
145
146void QgsRuleBasedRenderer::Rule::updateElseRules()
147{
148 mElseRules.clear();
149 const auto constMChildren = mChildren;
150 for ( Rule *rule : constMChildren )
151 {
152 if ( rule->isElse() )
153 mElseRules << rule;
154 }
155}
156
158{
159 mFilterExp = u"ELSE"_s;
160 mElseRule = iselse;
161 mFilter.reset();
162}
163
165{
166 // NOTE: if visitEnter returns false it means "don't visit the rule", not "abort all further visitations"
168 return true;
169
170 if ( mSymbol )
171 {
172 QgsStyleSymbolEntity entity( mSymbol.get() );
173 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
174 return false;
175 }
176
177 if ( !mChildren.empty() )
178 {
179 for ( const Rule *rule : mChildren )
180 {
181
182 if ( !rule->accept( visitor ) )
183 return false;
184 }
185 }
186
188 return false;
189
190 return true;
191}
192
193QString QgsRuleBasedRenderer::Rule::dump( int indent ) const
194{
195 QString off;
196 off.fill( QChar( ' ' ), indent );
197 QString symbolDump = ( mSymbol ? mSymbol->dump() : u"[]"_s );
198 QString msg = off + u"RULE %1 - scale [%2,%3] - filter %4 - symbol %5\n"_s
199 .arg( mLabel ).arg( mMaximumScale ).arg( mMinimumScale )
200 .arg( mFilterExp, symbolDump );
201
202 QStringList lst;
203 const auto constMChildren = mChildren;
204 for ( Rule *rule : constMChildren )
205 {
206 lst.append( rule->dump( indent + 2 ) );
207 }
208 msg += lst.join( QLatin1Char( '\n' ) );
209 return msg;
210}
211
213{
214 // attributes needed by this rule
215 QSet<QString> attrs;
216 if ( mFilter )
217 attrs.unite( mFilter->referencedColumns() );
218 if ( mSymbol )
219 attrs.unite( mSymbol->usedAttributes( context ) );
220
221 // attributes needed by child rules
222 const auto constMChildren = mChildren;
223 for ( Rule *rule : constMChildren )
224 {
225 attrs.unite( rule->usedAttributes( context ) );
226 }
227 return attrs;
228}
229
231{
232 if ( mFilter && mFilter->needsGeometry() )
233 return true;
234
235 const auto constMChildren = mChildren;
236 for ( Rule *rule : constMChildren )
237 {
238 if ( rule->needsGeometry() )
239 return true;
240 }
241
242 return false;
243}
244
246{
247 QgsSymbolList lst;
248 if ( mSymbol )
249 lst.append( mSymbol.get() );
250
251 const auto constMChildren = mChildren;
252 for ( Rule *rule : constMChildren )
253 {
254 lst += rule->symbols( context );
255 }
256 return lst;
257}
258
260{
261 mSymbol.reset( sym );
262}
263
265{
266 mFilterExp = filterExp;
267 initFilter();
268}
269
271{
273 if ( currentLevel != -1 ) // root rule should not be shown
274 {
275 lst << QgsLegendSymbolItem( mSymbol.get(), mLabel, mRuleKey, true, mMaximumScale, mMinimumScale, currentLevel, mParent ? mParent->mRuleKey : QString() );
276 }
277
278 for ( RuleList::const_iterator it = mChildren.constBegin(); it != mChildren.constEnd(); ++it )
279 {
280 Rule *rule = *it;
281 lst << rule->legendSymbolItems( currentLevel + 1 );
282 }
283 return lst;
284}
285
286
288{
289 if ( ! mFilter || mElseRule || ! context )
290 return true;
291
292 context->expressionContext().setFeature( f );
293 QVariant res = mFilter->evaluate( &context->expressionContext() );
294 return res.toBool();
295}
296
298{
299 if ( qgsDoubleNear( scale, 0.0 ) ) // so that we can count features in classes without scale context
300 return true;
301 if ( qgsDoubleNear( mMaximumScale, 0.0 ) && qgsDoubleNear( mMinimumScale, 0.0 ) )
302 return true;
303
304 // maxScale is inclusive ( < --> no render )
305 if ( !qgsDoubleNear( mMaximumScale, 0.0 ) && QgsScaleUtils::lessThanMaximumScale( scale, mMaximumScale ) )
306 return false;
307
308 // minScale is exclusive ( >= --> no render )
309 if ( !qgsDoubleNear( mMinimumScale, 0.0 ) && QgsScaleUtils::equalToOrGreaterThanMinimumScale( scale, mMinimumScale ) )
310 return false;
311
312 return true;
313}
314
316{
317 QgsSymbol *sym = mSymbol ? mSymbol->clone() : nullptr;
318 Rule *newrule = new Rule( sym, mMaximumScale, mMinimumScale, mFilterExp, mLabel, mDescription );
319 newrule->setActive( mIsActive );
320 // clone children
321 const auto constMChildren = mChildren;
322 for ( Rule *rule : constMChildren )
323 newrule->appendChild( rule->clone() );
324 return newrule;
325}
326
327QDomElement QgsRuleBasedRenderer::Rule::save( QDomDocument &doc, QgsSymbolMap &symbolMap ) const
328{
329 QDomElement ruleElem = doc.createElement( u"rule"_s );
330
331 if ( mSymbol )
332 {
333 int symbolIndex = symbolMap.size();
334 symbolMap[QString::number( symbolIndex )] = mSymbol.get();
335 ruleElem.setAttribute( u"symbol"_s, symbolIndex );
336 }
337 if ( !mFilterExp.isEmpty() )
338 ruleElem.setAttribute( u"filter"_s, mFilterExp );
339 if ( mMaximumScale != 0 )
340 ruleElem.setAttribute( u"scalemindenom"_s, mMaximumScale );
341 if ( mMinimumScale != 0 )
342 ruleElem.setAttribute( u"scalemaxdenom"_s, mMinimumScale );
343 if ( !mLabel.isEmpty() )
344 ruleElem.setAttribute( u"label"_s, mLabel );
345 if ( !mDescription.isEmpty() )
346 ruleElem.setAttribute( u"description"_s, mDescription );
347 if ( !mIsActive )
348 ruleElem.setAttribute( u"checkstate"_s, 0 );
349 ruleElem.setAttribute( u"key"_s, mRuleKey );
350
351 const auto constMChildren = mChildren;
352 for ( Rule *rule : constMChildren )
353 {
354 ruleElem.appendChild( rule->save( doc, symbolMap ) );
355 }
356 return ruleElem;
357}
358
359void QgsRuleBasedRenderer::Rule::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
360{
361 QgsSldExportContext context;
362 context.setExtraProperties( props );
363 toSld( doc, element, context );
364}
365
366bool QgsRuleBasedRenderer::Rule::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &exportContext ) const
367{
368 // do not convert this rule if there are no symbols
369 QgsRenderContext context;
370 if ( symbols( context ).isEmpty() )
371 return false;
372
373 const QVariantMap oldProps = exportContext.extraProperties();
374 QVariantMap props = oldProps;
375 if ( !mFilterExp.isEmpty() )
376 {
377 QString filter = props.value( u"filter"_s, QString() ).toString();
378 if ( !filter.isEmpty() )
379 filter += " AND "_L1;
380 filter += mFilterExp;
381 props[ u"filter"_s] = filter;
382 }
383
384 QgsSymbolLayerUtils::mergeScaleDependencies( mMaximumScale, mMinimumScale, props );
385 exportContext.setExtraProperties( props );
386
387 if ( mSymbol )
388 {
389 QDomElement ruleElem = doc.createElement( u"se:Rule"_s );
390
391 //XXX: <se:Name> is the rule identifier, but our the Rule objects
392 // have no properties could be used as identifier. Use the label.
393 QDomElement nameElem = doc.createElement( u"se:Name"_s );
394 nameElem.appendChild( doc.createTextNode( mLabel ) );
395 ruleElem.appendChild( nameElem );
396
397 if ( !mLabel.isEmpty() || !mDescription.isEmpty() )
398 {
399 QDomElement descrElem = doc.createElement( u"se:Description"_s );
400 if ( !mLabel.isEmpty() )
401 {
402 QDomElement titleElem = doc.createElement( u"se:Title"_s );
403 titleElem.appendChild( doc.createTextNode( mLabel ) );
404 descrElem.appendChild( titleElem );
405 }
406 if ( !mDescription.isEmpty() )
407 {
408 QDomElement abstractElem = doc.createElement( u"se:Abstract"_s );
409 abstractElem.appendChild( doc.createTextNode( mDescription ) );
410 descrElem.appendChild( abstractElem );
411 }
412 ruleElem.appendChild( descrElem );
413 }
414
415 if ( !props.value( u"filter"_s, QString() ).toString().isEmpty() )
416 {
417 QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, props.value( u"filter"_s, QString() ).toString(), exportContext );
418 }
419
420 QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
421 exportContext.setExtraProperties( props );
422
423 mSymbol->toSld( doc, ruleElem, exportContext );
424
425 // Only create rules if symbol could be converted to SLD, and is not an "empty" symbol. Otherwise we do not generate a rule, as
426 // SLD spec requires a Symbolizer element to be present
428 {
429 element.appendChild( ruleElem );
430 }
431 }
432
433 // loop into children rule list
434 bool result = true;
435 for ( Rule *rule : std::as_const( mChildren ) )
436 {
437 if ( !rule->toSld( doc, element, exportContext ) )
438 result = false;
439 }
440 exportContext.setExtraProperties( oldProps );
441 return result;
442}
443
445{
446 mActiveChildren.clear();
447
448 if ( ! mIsActive )
449 return false;
450
451 // filter out rules which are not compatible with this scale
452 if ( !isScaleOK( context.rendererScale() ) )
453 return false;
454
455 // init this rule
456 if ( mFilter )
457 mFilter->prepare( &context.expressionContext() );
458 if ( mSymbol )
459 mSymbol->startRender( context, fields );
460
461 // init children
462 // build temporary list of active rules (usable with this scale)
463 QStringList subfilters;
464 const auto constMChildren = mChildren;
465 for ( Rule *rule : constMChildren )
466 {
467 QString subfilter;
468 if ( rule->startRender( context, fields, subfilter ) )
469 {
470 // only add those which are active with current scale
471 mActiveChildren.append( rule );
472 subfilters.append( subfilter );
473 }
474 }
475
476 // subfilters (on the same level) are joined with OR
477 // Finally they are joined with their parent (this) with AND
478 QString sf;
479 // If there are subfilters present (and it's not a single empty one), group them and join them with OR
480 if ( subfilters.length() > 1 || !subfilters.value( 0 ).isEmpty() )
481 {
482 if ( subfilters.contains( u"TRUE"_s ) )
483 {
484 sf = u"TRUE"_s;
485 }
486 else
487 {
488 // test for a common case -- all subfilters can be combined into a single "field in (...)" expression
489 if ( QgsExpression::attemptReduceToInClause( subfilters, sf ) )
490 {
491 // success! we can use a simple "field IN (...)" list!
492 }
493 // If we have more than 50 rules (to stay on the safe side) make a binary tree or SQLITE will fail,
494 // see: https://github.com/qgis/QGIS/issues/27269
495 else if ( subfilters.count() > 50 )
496 {
497 std::function<QString( const QStringList & )>bt = [ &bt ]( const QStringList & subf )
498 {
499 if ( subf.count( ) == 1 )
500 {
501 return subf.at( 0 );
502 }
503 else if ( subf.count( ) == 2 )
504 {
505 return subf.join( ") OR ("_L1 ).prepend( '(' ).append( ')' );
506 }
507 else
508 {
509 int midpos = static_cast<int>( subf.length() / 2 );
510 return u"(%1) OR (%2)"_s.arg( bt( subf.mid( 0, midpos ) ), bt( subf.mid( midpos ) ) );
511 }
512 };
513 sf = bt( subfilters );
514 }
515 else
516 {
517 sf = subfilters.join( ") OR ("_L1 ).prepend( '(' ).append( ')' );
518 }
519 }
520 }
521
522 // Now join the subfilters with their parent (this) based on if
523 // * The parent is an else rule
524 // * The existence of parent filter and subfilters
525
526 // No filter expression: ELSE rule or catchall rule
527 if ( !mFilter )
528 {
529 if ( mSymbol || sf.isEmpty() )
530 filter = u"TRUE"_s;
531 else
532 filter = sf;
533 }
534 else if ( mSymbol )
535 filter = mFilterExp;
536 else if ( !mFilterExp.trimmed().isEmpty() && !sf.isEmpty() )
537 filter = u"(%1) AND (%2)"_s.arg( mFilterExp, sf );
538 else if ( !mFilterExp.trimmed().isEmpty() )
539 filter = mFilterExp;
540 else if ( sf.isEmpty() )
541 filter = u"TRUE"_s;
542 else
543 filter = sf;
544
545 filter = filter.trimmed();
546
547 return true;
548}
549
551{
552 return !mActiveChildren.empty();
553}
554
556{
557 QSet<int> symbolZLevelsSet;
558
559 // process this rule
560 if ( mSymbol )
561 {
562 // find out which Z-levels are used
563 for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
564 {
565 symbolZLevelsSet.insert( mSymbol->symbolLayer( i )->renderingPass() );
566 }
567 }
568
569 // process children
570 QList<Rule *>::iterator it;
571 for ( it = mActiveChildren.begin(); it != mActiveChildren.end(); ++it )
572 {
573 Rule *rule = *it;
574 symbolZLevelsSet.unite( rule->collectZLevels() );
575 }
576 return symbolZLevelsSet;
577}
578
579void QgsRuleBasedRenderer::Rule::setNormZLevels( const QMap<int, int> &zLevelsToNormLevels )
580{
581 if ( mSymbol )
582 {
583 for ( int i = 0; i < mSymbol->symbolLayerCount(); i++ )
584 {
585 int normLevel = zLevelsToNormLevels.value( mSymbol->symbolLayer( i )->renderingPass() );
586 mSymbolNormZLevels.insert( normLevel );
587 }
588 }
589
590 // prepare list of normalized levels for each rule
591 const auto constMActiveChildren = mActiveChildren;
592 for ( Rule *rule : constMActiveChildren )
593 {
594 rule->setNormZLevels( zLevelsToNormLevels );
595 }
596}
597
598
600{
601 if ( !isFilterOK( featToRender.feat, &context ) )
602 return Filtered;
603
604 bool rendered = false;
605
606 // create job for this feature and this symbol, add to list of jobs
607 if ( mSymbol && mIsActive )
608 {
609 // add job to the queue: each symbol's zLevel must be added
610 const auto constMSymbolNormZLevels = mSymbolNormZLevels;
611 for ( int normZLevel : constMSymbolNormZLevels )
612 {
613 //QgsDebugMsgLevel(QString("add job at level %1").arg(normZLevel),2);
614 renderQueue[normZLevel].jobs.append( new RenderJob( featToRender, mSymbol.get() ) );
615 rendered = true;
616 }
617 }
618
619 bool matchedAChild = false;
620
621 // process children
622 const auto constMChildren = mChildren;
623 for ( Rule *rule : constMChildren )
624 {
625 // Don't process else rules yet
626 if ( !rule->isElse() )
627 {
628 const RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
629 // consider inactive items as "matched" so the else rule will ignore them
630 matchedAChild |= ( res == Rendered || res == Inactive );
631 rendered |= ( res == Rendered );
632 }
633 }
634
635 // If none of the rules passed then we jump into the else rules and process them.
636 if ( !matchedAChild )
637 {
638 const auto constMElseRules = mElseRules;
639 for ( Rule *rule : constMElseRules )
640 {
641 const RenderResult res = rule->renderFeature( featToRender, context, renderQueue );
642 matchedAChild |= ( res == Rendered || res == Inactive );
643 rendered |= res == Rendered;
644 }
645 }
646 if ( !mIsActive || ( mSymbol && !rendered ) || ( matchedAChild && !rendered ) )
647 return Inactive;
648 else if ( rendered )
649 return Rendered;
650 else
651 return Filtered;
652}
653
655{
656 if ( !isFilterOK( feature, context ) )
657 return false;
658
659 if ( mSymbol )
660 return true;
661
662 const auto constMActiveChildren = mActiveChildren;
663 for ( Rule *rule : constMActiveChildren )
664 {
665 if ( rule->isElse() )
666 {
667 if ( rule->children().isEmpty() )
668 {
669 RuleList lst = rulesForFeature( feature, context, false );
670 lst.removeOne( rule );
671
672 if ( lst.empty() )
673 {
674 return true;
675 }
676 }
677 else
678 {
679 return rule->willRenderFeature( feature, context );
680 }
681 }
682 else if ( rule->willRenderFeature( feature, context ) )
683 {
684 return true;
685 }
686 }
687 return false;
688}
689
691{
692 QgsSymbolList lst;
693 if ( !isFilterOK( feature, context ) )
694 return lst;
695 if ( mSymbol )
696 lst.append( mSymbol.get() );
697
698 const auto constMActiveChildren = mActiveChildren;
699 for ( Rule *rule : constMActiveChildren )
700 {
701 lst += rule->symbolsForFeature( feature, context );
702 }
703 return lst;
704}
705
707{
708 QSet< QString> res;
709 if ( !isFilterOK( feature, context ) )
710 return res;
711
712 res.insert( mRuleKey );
713
714 // first determine if any non else rules match at this level
715 bool matchedNonElseRule = false;
716 for ( Rule *rule : std::as_const( mActiveChildren ) )
717 {
718 if ( rule->isElse() )
719 {
720 continue;
721 }
722 if ( rule->willRenderFeature( feature, context ) )
723 {
724 res.unite( rule->legendKeysForFeature( feature, context ) );
725 matchedNonElseRule = true;
726 }
727 }
728
729 // second chance -- allow else rules to take effect if valid
730 if ( !matchedNonElseRule )
731 {
732 for ( Rule *rule : std::as_const( mActiveChildren ) )
733 {
734 if ( rule->isElse() )
735 {
736 if ( rule->children().isEmpty() )
737 {
738 RuleList lst = rulesForFeature( feature, context, false );
739 lst.removeOne( rule );
740
741 if ( lst.empty() )
742 {
743 res.unite( rule->legendKeysForFeature( feature, context ) );
744 }
745 }
746 else
747 {
748 res.unite( rule->legendKeysForFeature( feature, context ) );
749 }
750 }
751 }
752 }
753 return res;
754}
755
757{
758 RuleList lst;
759 if ( ! isFilterOK( feature, context ) || ( context && ! isScaleOK( context->rendererScale() ) ) )
760 return lst;
761
762 if ( mSymbol )
763 lst.append( this );
764
765 RuleList listChildren = children();
766 if ( onlyActive )
767 listChildren = mActiveChildren;
768
769 for ( Rule *rule : std::as_const( listChildren ) )
770 {
771 lst += rule->rulesForFeature( feature, context, onlyActive );
772 }
773 return lst;
774}
775
777{
778 if ( mSymbol )
779 mSymbol->stopRender( context );
780
781 const auto constMActiveChildren = mActiveChildren;
782 for ( Rule *rule : constMActiveChildren )
783 {
784 rule->stopRender( context );
785 }
786
787 mActiveChildren.clear();
788 mSymbolNormZLevels.clear();
789}
790
791QgsRuleBasedRenderer::Rule *QgsRuleBasedRenderer::Rule::create( QDomElement &ruleElem, QgsSymbolMap &symbolMap, bool reuseId, const QgsReadWriteContext &context )
792{
793 QString symbolIdx = ruleElem.attribute( u"symbol"_s );
794 QgsSymbol *symbol = nullptr;
795 if ( !symbolIdx.isEmpty() )
796 {
797 if ( symbolMap.contains( symbolIdx ) )
798 {
799 symbol = symbolMap.take( symbolIdx );
800 }
801 else
802 {
803 QgsDebugError( "symbol for rule " + symbolIdx + " not found!" );
804 }
805 }
806
807 QString filterExp = ruleElem.attribute( u"filter"_s );
808 QString label = context.projectTranslator()->translate( u"project:layers:%1:legendsymbollabels"_s.arg( context.currentLayerId() ), ruleElem.attribute( u"label"_s ) );
809 QgsDebugMsgLevel( "context" + u"project:layers:%1:legendsymbollabels"_s.arg( context.currentLayerId() ) + " source " + ruleElem.attribute( u"label"_s ), 3 );
810 QString description = ruleElem.attribute( u"description"_s );
811 int scaleMinDenom = ruleElem.attribute( u"scalemindenom"_s, u"0"_s ).toInt();
812 int scaleMaxDenom = ruleElem.attribute( u"scalemaxdenom"_s, u"0"_s ).toInt();
813
814 QString ruleKey;
815 if ( reuseId )
816 ruleKey = ruleElem.attribute( u"key"_s );
817 else
818 ruleKey = QUuid::createUuid().toString();
819 Rule *rule = new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
820
821 if ( !ruleKey.isEmpty() )
822 rule->mRuleKey = ruleKey;
823
824 rule->setActive( ruleElem.attribute( u"checkstate"_s, u"1"_s ).toInt() );
825
826 QDomElement childRuleElem = ruleElem.firstChildElement( u"rule"_s );
827 while ( !childRuleElem.isNull() )
828 {
829 Rule *childRule = create( childRuleElem, symbolMap, true, context );
830 if ( childRule )
831 {
832 rule->appendChild( childRule );
833 }
834 else
835 {
836 QgsDebugError( u"failed to init a child rule!"_s );
837 }
838 childRuleElem = childRuleElem.nextSiblingElement( u"rule"_s );
839 }
840
841 return rule;
842}
843
845{
846 RuleList l;
847 for ( QgsRuleBasedRenderer::Rule *c : mChildren )
848 {
849 l += c;
850 l += c->descendants();
851 }
852 return l;
853}
854
856{
857 if ( ruleElem.localName() != "Rule"_L1 )
858 {
859 QgsDebugError( u"invalid element: Rule element expected, %1 found!"_s.arg( ruleElem.tagName() ) );
860 return nullptr;
861 }
862
863 QString label, description, filterExp;
864 int scaleMinDenom = 0, scaleMaxDenom = 0;
865 QgsSymbolLayerList layers;
866
867 // retrieve the Rule element child nodes
868 QDomElement childElem = ruleElem.firstChildElement();
869 while ( !childElem.isNull() )
870 {
871 if ( childElem.localName() == "Name"_L1 )
872 {
873 // <se:Name> tag contains the rule identifier,
874 // so prefer title tag for the label property value
875 if ( label.isEmpty() )
876 label = childElem.firstChild().nodeValue();
877 }
878 else if ( childElem.localName() == "Description"_L1 )
879 {
880 // <se:Description> can contains a title and an abstract
881 QDomElement titleElem = childElem.firstChildElement( u"Title"_s );
882 if ( !titleElem.isNull() )
883 {
884 label = titleElem.firstChild().nodeValue();
885 }
886
887 QDomElement abstractElem = childElem.firstChildElement( u"Abstract"_s );
888 if ( !abstractElem.isNull() )
889 {
890 description = abstractElem.firstChild().nodeValue();
891 }
892 }
893 else if ( childElem.localName() == "Abstract"_L1 )
894 {
895 // <sld:Abstract> (v1.0)
896 description = childElem.firstChild().nodeValue();
897 }
898 else if ( childElem.localName() == "Title"_L1 )
899 {
900 // <sld:Title> (v1.0)
901 label = childElem.firstChild().nodeValue();
902 }
903 else if ( childElem.localName() == "Filter"_L1 )
904 {
906 if ( filter )
907 {
908 if ( filter->hasParserError() )
909 {
910 QgsDebugError( "parser error: " + filter->parserErrorString() );
911 }
912 else
913 {
914 filterExp = filter->expression();
915 }
916 delete filter;
917 }
918 }
919 else if ( childElem.localName() == "ElseFilter"_L1 )
920 {
921 filterExp = "ELSE"_L1;
922
923 }
924 else if ( childElem.localName() == "MinScaleDenominator"_L1 )
925 {
926 bool ok;
927 int v = childElem.firstChild().nodeValue().toInt( &ok );
928 if ( ok )
929 scaleMinDenom = v;
930 }
931 else if ( childElem.localName() == "MaxScaleDenominator"_L1 )
932 {
933 bool ok;
934 int v = childElem.firstChild().nodeValue().toInt( &ok );
935 if ( ok )
936 scaleMaxDenom = v;
937 }
938 else if ( childElem.localName().endsWith( "Symbolizer"_L1 ) )
939 {
940 // create symbol layers for this symbolizer
941 QgsSymbolLayerUtils::createSymbolLayerListFromSld( childElem, geomType, layers );
942 }
943
944 childElem = childElem.nextSiblingElement();
945 }
946
947 // now create the symbol
948 QgsSymbol *symbol = nullptr;
949 if ( !layers.isEmpty() )
950 {
951 switch ( geomType )
952 {
954 symbol = new QgsLineSymbol( layers );
955 break;
956
958 symbol = new QgsFillSymbol( layers );
959 break;
960
962 symbol = new QgsMarkerSymbol( layers );
963 break;
964
965 default:
966 QgsDebugError( u"invalid geometry type: found %1"_s.arg( qgsEnumValueToKey( geomType ) ) );
967 return nullptr;
968 }
969 }
970
971 // and then create and return the new rule
972 return new Rule( symbol, scaleMinDenom, scaleMaxDenom, filterExp, label, description );
973}
974
975
977
983
985 : QgsFeatureRenderer( u"RuleRenderer"_s )
986{
987 mRootRule = new Rule( nullptr ); // root has no symbol, no filter etc - just a container
988 mRootRule->appendChild( new Rule( defaultSymbol ) );
989}
990
995
996
998{
999 // not used at all
1000 return nullptr;
1001}
1002
1004{
1006
1007 std::function< void( Rule *rule ) > exploreRule;
1008 exploreRule = [&res, &exploreRule]( Rule * rule )
1009 {
1010 if ( !rule )
1011 return;
1012
1013 if ( QgsSymbol *symbol = rule->symbol() )
1014 {
1015 if ( symbol->flags().testFlag( Qgis::SymbolFlag::AffectsLabeling ) )
1017 }
1018
1019 for ( Rule *child : rule->children() )
1020 {
1021 exploreRule( child );
1022 }
1023 };
1024 exploreRule( mRootRule );
1025
1026 return res;
1027}
1028
1030 QgsRenderContext &context,
1031 int layer,
1032 bool selected,
1033 bool drawVertexMarker )
1034{
1035 Q_UNUSED( layer )
1036
1037 int flags = ( selected ? FeatIsSelected : 0 ) | ( drawVertexMarker ? FeatDrawMarkers : 0 );
1038 mCurrentFeatures.append( FeatureToRender( feature, flags ) );
1039
1040 // check each active rule
1041 return mRootRule->renderFeature( mCurrentFeatures.last(), context, mRenderQueue ) == Rule::Rendered;
1042}
1043
1044
1046{
1047 QgsFeatureRenderer::startRender( context, fields );
1048
1049 // prepare active children
1050 mRootRule->startRender( context, fields, mFilter );
1051
1052 QSet<int> symbolZLevelsSet = mRootRule->collectZLevels();
1053 QList<int> symbolZLevels( symbolZLevelsSet.begin(), symbolZLevelsSet.end() );
1054 std::sort( symbolZLevels.begin(), symbolZLevels.end() );
1055
1056 // create mapping from unnormalized levels [unlimited range] to normalized levels [0..N-1]
1057 // and prepare rendering queue
1058 QMap<int, int> zLevelsToNormLevels;
1059 int maxNormLevel = -1;
1060 const auto constSymbolZLevels = symbolZLevels;
1061 for ( int zLevel : constSymbolZLevels )
1062 {
1063 zLevelsToNormLevels[zLevel] = ++maxNormLevel;
1064 mRenderQueue.append( RenderLevel( zLevel ) );
1065 QgsDebugMsgLevel( u"zLevel %1 -> %2"_s.arg( zLevel ).arg( maxNormLevel ), 4 );
1066 }
1067
1068 mRootRule->setNormZLevels( zLevelsToNormLevels );
1069}
1070
1072{
1073 return !mRootRule->hasActiveChildren();
1074}
1075
1077{
1079
1080 //
1081 // do the actual rendering
1082 //
1083
1084 // go through all levels
1085 if ( !context.renderingStopped() )
1086 {
1087 const auto constMRenderQueue = mRenderQueue;
1088 for ( const RenderLevel &level : constMRenderQueue )
1089 {
1090 //QgsDebugMsgLevel(QString("level %1").arg(level.zIndex), 2);
1091 // go through all jobs at the level
1092 for ( const RenderJob *job : std::as_const( level.jobs ) )
1093 {
1094 context.expressionContext().setFeature( job->ftr.feat );
1095 //QgsDebugMsgLevel(QString("job fid %1").arg(job->f->id()), 2);
1096 // render feature - but only with symbol layers with specified zIndex
1097 QgsSymbol *s = job->symbol;
1098 int count = s->symbolLayerCount();
1099 for ( int i = 0; i < count; i++ )
1100 {
1101 // TODO: better solution for this
1102 // renderFeatureWithSymbol asks which symbol layer to draw
1103 // but there are multiple transforms going on!
1104 if ( s->symbolLayer( i )->renderingPass() == level.zIndex )
1105 {
1106 int flags = job->ftr.flags;
1107 renderFeatureWithSymbol( job->ftr.feat, job->symbol, context, i, flags & FeatIsSelected, flags & FeatDrawMarkers );
1108 }
1109 }
1110 }
1111 }
1112 }
1113
1114 // clean current features
1115 mCurrentFeatures.clear();
1116
1117 // clean render queue
1118 mRenderQueue.clear();
1119
1120 // clean up rules from temporary stuff
1121 mRootRule->stopRender( context );
1122}
1123
1125{
1126 return mFilter;
1127}
1128
1129QSet<QString> QgsRuleBasedRenderer::usedAttributes( const QgsRenderContext &context ) const
1130{
1131 return mRootRule->usedAttributes( context );
1132}
1133
1135{
1136 return mRootRule->needsGeometry();
1137}
1138
1140{
1141 QgsRuleBasedRenderer::Rule *clonedRoot = mRootRule->clone();
1142
1143 // normally with clone() the individual rules get new keys (UUID), but here we want to keep
1144 // the tree of rules intact, so that other components that may use the rule keys work nicely (e.g. map themes)
1145 clonedRoot->setRuleKey( mRootRule->ruleKey() );
1146 RuleList origDescendants = mRootRule->descendants();
1147 RuleList clonedDescendants = clonedRoot->descendants();
1148 Q_ASSERT( origDescendants.count() == clonedDescendants.count() );
1149 for ( int i = 0; i < origDescendants.count(); ++i )
1150 clonedDescendants[i]->setRuleKey( origDescendants[i]->ruleKey() );
1151
1152 QgsRuleBasedRenderer *r = new QgsRuleBasedRenderer( clonedRoot );
1153
1154 copyRendererData( r );
1155 return r;
1156}
1157
1158void QgsRuleBasedRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
1159{
1160 QgsSldExportContext context;
1161 context.setExtraProperties( props );
1162 toSld( doc, element, context );
1163}
1164
1165bool QgsRuleBasedRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
1166{
1167 return mRootRule->toSld( doc, element, context );
1168}
1169
1170// TODO: ideally this function should be removed in favor of legendSymbol(ogy)Items
1172{
1173 return mRootRule->symbols( context );
1174}
1175
1176QDomElement QgsRuleBasedRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
1177{
1178 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
1179 rendererElem.setAttribute( u"type"_s, u"RuleRenderer"_s );
1180
1182
1183 QDomElement rulesElem = mRootRule->save( doc, symbols );
1184 rulesElem.setTagName( u"rules"_s ); // instead of just "rule"
1185 rendererElem.appendChild( rulesElem );
1186
1187 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, u"symbols"_s, doc, context );
1188 rendererElem.appendChild( symbolsElem );
1189
1190 saveRendererData( doc, rendererElem, context );
1191
1192 return rendererElem;
1193}
1194
1196{
1197 return true;
1198}
1199
1201{
1202 Rule *rule = mRootRule->findRuleByKey( key );
1203 return rule ? rule->active() : true;
1204}
1205
1206void QgsRuleBasedRenderer::checkLegendSymbolItem( const QString &key, bool state )
1207{
1208 Rule *rule = mRootRule->findRuleByKey( key );
1209 if ( rule )
1210 rule->setActive( state );
1211}
1212
1213QString QgsRuleBasedRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *, bool &ok ) const
1214{
1215 ok = false;
1216 Rule *rule = mRootRule->findRuleByKey( key );
1217 if ( !rule )
1218 return QString();
1219
1220 std::function<QString( Rule *rule )> ruleToExpression;
1221 ruleToExpression = [&ruleToExpression]( Rule * rule ) -> QString
1222 {
1223 if ( rule->isElse() && rule->parent() )
1224 {
1225 // gather the expressions for all other rules on this level and invert them
1226
1227 QStringList otherRules;
1228 const QList<QgsRuleBasedRenderer::Rule *> siblings = rule->parent()->children();
1229 for ( Rule *sibling : siblings )
1230 {
1231 if ( sibling == rule || sibling->isElse() )
1232 continue;
1233
1234 const QString siblingExpression = ruleToExpression( sibling );
1235 if ( siblingExpression.isEmpty() )
1236 return u"FALSE"_s; // nothing will match this rule
1237
1238 otherRules.append( siblingExpression );
1239 }
1240
1241 if ( otherRules.empty() )
1242 return u"TRUE"_s; // all features will match the else rule
1243 else
1244 return (
1245 otherRules.size() > 1
1246 ? u"NOT ((%1))"_s.arg( otherRules.join( ") OR ("_L1 ) )
1247 : u"NOT (%1)"_s.arg( otherRules.at( 0 ) )
1248 );
1249 }
1250 else
1251 {
1252 QStringList ruleParts;
1253 if ( !rule->filterExpression().isEmpty() )
1254 ruleParts.append( rule->filterExpression() );
1255
1256 if ( !qgsDoubleNear( rule->minimumScale(), 0.0 ) )
1257 ruleParts.append( u"@map_scale <= %1"_s.arg( rule->minimumScale() ) );
1258
1259 if ( !qgsDoubleNear( rule->maximumScale(), 0.0 ) )
1260 ruleParts.append( u"@map_scale >= %1"_s.arg( rule->maximumScale() ) );
1261
1262 if ( !ruleParts.empty() )
1263 {
1264 return (
1265 ruleParts.size() > 1
1266 ? u"(%1)"_s.arg( ruleParts.join( ") AND ("_L1 ) )
1267 : ruleParts.at( 0 )
1268 );
1269 }
1270 else
1271 {
1272 return QString();
1273 }
1274 }
1275 };
1276
1277 QStringList parts;
1278 while ( rule )
1279 {
1280 const QString ruleFilter = ruleToExpression( rule );
1281 if ( !ruleFilter.isEmpty() )
1282 parts.append( ruleFilter );
1283
1284 rule = rule->parent();
1285 }
1286
1287 ok = true;
1288 return parts.empty() ? u"TRUE"_s
1289 : ( parts.size() > 1
1290 ? u"(%1)"_s.arg( parts.join( ") AND ("_L1 ) )
1291 : parts.at( 0 ) );
1292}
1293
1294void QgsRuleBasedRenderer::setLegendSymbolItem( const QString &key, QgsSymbol *symbol )
1295{
1296 Rule *rule = mRootRule->findRuleByKey( key );
1297 if ( rule )
1298 rule->setSymbol( symbol );
1299 else
1300 delete symbol;
1301}
1302
1304{
1305 return mRootRule->legendSymbolItems();
1306}
1307
1308
1310{
1311 // load symbols
1312 QDomElement symbolsElem = element.firstChildElement( u"symbols"_s );
1313 if ( symbolsElem.isNull() )
1314 return nullptr;
1315
1316 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
1317
1318 QDomElement rulesElem = element.firstChildElement( u"rules"_s );
1319
1320 Rule *root = Rule::create( rulesElem, symbolMap, true, context );
1321 if ( !root )
1322 return nullptr;
1323
1325
1326 // delete symbols if there are any more
1328
1329 return r;
1330}
1331
1333{
1334 // retrieve child rules
1335 Rule *root = nullptr;
1336
1337 QDomElement ruleElem = element.firstChildElement( u"Rule"_s );
1338 while ( !ruleElem.isNull() )
1339 {
1340 Rule *child = Rule::createFromSld( ruleElem, geomType );
1341 if ( child )
1342 {
1343 // create the root rule if not done before
1344 if ( !root )
1345 root = new Rule( nullptr );
1346
1347 root->appendChild( child );
1348 }
1349
1350 ruleElem = ruleElem.nextSiblingElement( u"Rule"_s );
1351 }
1352
1353 if ( !root )
1354 {
1355 // no valid rules was found
1356 return nullptr;
1357 }
1358
1359 // create and return the new renderer
1360 return new QgsRuleBasedRenderer( root );
1361}
1362
1365
1367{
1368 QString attr = r->classAttribute();
1369 // categorizedAttr could be either an attribute name or an expression.
1370 // the only way to differentiate is to test it as an expression...
1371 QgsExpression testExpr( attr );
1372 if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1373 {
1374 //not an expression, so need to quote column name
1375 attr = QgsExpression::quotedColumnRef( attr );
1376 }
1377
1378 const auto constCategories = r->categories();
1379 for ( const QgsRendererCategory &cat : constCategories )
1380 {
1381 QString value;
1382 // not quoting numbers saves a type cast
1383 if ( QgsVariantUtils::isNull( cat.value() ) )
1384 value = "NULL";
1385 else if ( cat.value().userType() == QMetaType::Type::Int )
1386 value = cat.value().toString();
1387 else if ( cat.value().userType() == QMetaType::Type::Double )
1388 // we loose precision here - so we may miss some categories :-(
1389 // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1390 value = QString::number( cat.value().toDouble(), 'f', 4 );
1391 else
1392 value = QgsExpression::quotedString( cat.value().toString() );
1393 const QString filter = u"%1 %2 %3"_s.arg( attr, QgsVariantUtils::isNull( cat.value() ) ? u"IS"_s : u"="_s, value );
1394 const QString label = !cat.label().isEmpty() ? cat.label() :
1395 cat.value().isValid() ? value : QString();
1396 initialRule->appendChild( new Rule( cat.symbol()->clone(), 0, 0, filter, label ) );
1397 }
1398}
1399
1401{
1402 QString attr = r->classAttribute();
1403 // categorizedAttr could be either an attribute name or an expression.
1404 // the only way to differentiate is to test it as an expression...
1405 QgsExpression testExpr( attr );
1406 if ( testExpr.hasParserError() || ( testExpr.isField() && !attr.startsWith( '\"' ) ) )
1407 {
1408 //not an expression, so need to quote column name
1409 attr = QgsExpression::quotedColumnRef( attr );
1410 }
1411 else if ( !testExpr.isField() )
1412 {
1413 //otherwise wrap expression in brackets
1414 attr = u"(%1)"_s.arg( attr );
1415 }
1416
1417 bool firstRange = true;
1418 const auto constRanges = r->ranges();
1419 for ( const QgsRendererRange &rng : constRanges )
1420 {
1421 // due to the loss of precision in double->string conversion we may miss out values at the limit of the range
1422 // TODO: have a possibility to construct expressions directly as a parse tree to avoid loss of precision
1423 QString filter = u"%1 %2 %3 AND %1 <= %4"_s.arg( attr, firstRange ? u">="_s : u">"_s,
1424 QString::number( rng.lowerValue(), 'f', 4 ),
1425 QString::number( rng.upperValue(), 'f', 4 ) );
1426 firstRange = false;
1427 QString label = rng.label().isEmpty() ? filter : rng.label();
1428 initialRule->appendChild( new Rule( rng.symbol()->clone(), 0, 0, filter, label ) );
1429 }
1430}
1431
1433{
1434 std::sort( scales.begin(), scales.end() ); // make sure the scales are in ascending order
1435 double oldScale = initialRule->maximumScale();
1436 double maxDenom = initialRule->minimumScale();
1437 QgsSymbol *symbol = initialRule->symbol();
1438 const auto constScales = scales;
1439 for ( int scale : constScales )
1440 {
1441 if ( initialRule->maximumScale() >= scale )
1442 continue; // jump over the first scales out of the interval
1443 if ( maxDenom != 0 && maxDenom <= scale )
1444 break; // ignore the latter scales out of the interval
1445 initialRule->appendChild( new Rule( symbol->clone(), oldScale, scale, QString(), u"%1 - %2"_s.arg( oldScale ).arg( scale ) ) );
1446 oldScale = scale;
1447 }
1448 // last rule
1449 initialRule->appendChild( new Rule( symbol->clone(), oldScale, maxDenom, QString(), u"%1 - %2"_s.arg( oldScale ).arg( maxDenom ) ) );
1450}
1451
1453{
1454 QString msg( u"Rule-based renderer:\n"_s );
1455 msg += mRootRule->dump();
1456 return msg;
1457}
1458
1460{
1461 return mRootRule->willRenderFeature( feature, &context );
1462}
1463
1465{
1466 return mRootRule->symbolsForFeature( feature, &context );
1467}
1468
1470{
1471 return mRootRule->symbolsForFeature( feature, &context );
1472}
1473
1474QSet< QString > QgsRuleBasedRenderer::legendKeysForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
1475{
1476 return mRootRule->legendKeysForFeature( feature, &context );
1477}
1478
1480{
1481 return mRootRule->accept( visitor );
1482}
1483
1485{
1486 std::unique_ptr< QgsRuleBasedRenderer > r;
1487 if ( renderer->type() == "RuleRenderer"_L1 )
1488 {
1489 r.reset( dynamic_cast<QgsRuleBasedRenderer *>( renderer->clone() ) );
1490 }
1491 else if ( renderer->type() == "singleSymbol"_L1 )
1492 {
1493 const QgsSingleSymbolRenderer *singleSymbolRenderer = dynamic_cast<const QgsSingleSymbolRenderer *>( renderer );
1494 if ( !singleSymbolRenderer )
1495 return nullptr;
1496
1497 std::unique_ptr< QgsSymbol > origSymbol( singleSymbolRenderer->symbol()->clone() );
1498 r = std::make_unique< QgsRuleBasedRenderer >( origSymbol.release() );
1499 }
1500 else if ( renderer->type() == "categorizedSymbol"_L1 )
1501 {
1502 const QgsCategorizedSymbolRenderer *categorizedRenderer = dynamic_cast<const QgsCategorizedSymbolRenderer *>( renderer );
1503 if ( !categorizedRenderer )
1504 return nullptr;
1505
1506 QString attr = categorizedRenderer->classAttribute();
1507 // categorizedAttr could be either an attribute name or an expression.
1508 bool isField = false;
1509 if ( layer )
1510 {
1511 isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1512 }
1513 else
1514 {
1515 QgsExpression testExpr( attr );
1516 isField = testExpr.hasParserError() || testExpr.isField();
1517 }
1518 if ( isField && !attr.contains( '\"' ) )
1519 {
1520 //not an expression, so need to quote column name
1521 attr = QgsExpression::quotedColumnRef( attr );
1522 }
1523
1524 auto rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1525
1526 QString expression;
1527 QString value;
1528 QgsRendererCategory category;
1529 for ( const QgsRendererCategory &category : categorizedRenderer->categories() )
1530 {
1531 auto rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1532
1533 rule->setLabel( category.label() );
1534
1535 //We first define the rule corresponding to the category
1536 if ( category.value().userType() == QMetaType::Type::QVariantList )
1537 {
1538 QStringList values;
1539 const QVariantList list = category.value().toList();
1540 for ( const QVariant &v : list )
1541 {
1542 //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1543 if ( QVariant( v ).convert( QMetaType::Type::Double ) )
1544 {
1545 values << v.toString();
1546 }
1547 else
1548 {
1549 values << QgsExpression::quotedString( v.toString() );
1550 }
1551 }
1552
1553 if ( values.empty() )
1554 {
1555 expression = u"ELSE"_s;
1556 }
1557 else
1558 {
1559 expression = u"%1 IN (%2)"_s.arg( attr, values.join( ',' ) );
1560 }
1561 }
1562 else
1563 {
1564 //If the value is a number, we can use it directly, otherwise we need to quote it in the rule
1565 if ( category.value().convert( QMetaType::Type::Double ) )
1566 {
1567 value = category.value().toString();
1568 }
1569 else
1570 {
1571 value = QgsExpression::quotedString( category.value().toString() );
1572 }
1573
1574 //An empty category is equivalent to the ELSE keyword
1575 if ( value == "''"_L1 )
1576 {
1577 expression = u"ELSE"_s;
1578 }
1579 else
1580 {
1581 expression = u"%1 = %2"_s.arg( attr, value );
1582 }
1583 }
1584 rule->setFilterExpression( expression );
1585
1586 //Then we construct an equivalent symbol.
1587 //Ideally we could simply copy the symbol, but the categorized renderer allows a separate interface to specify
1588 //data dependent area and rotation, so we need to convert these to obtain the same rendering
1589
1590 std::unique_ptr< QgsSymbol > origSymbol( category.symbol()->clone() );
1591 rule->setSymbol( origSymbol.release() );
1592
1593 rootrule->appendChild( rule.release() );
1594 }
1595
1596 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1597 }
1598 else if ( renderer->type() == "graduatedSymbol"_L1 )
1599 {
1600 const QgsGraduatedSymbolRenderer *graduatedRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1601 if ( !graduatedRenderer )
1602 return nullptr;
1603
1604 QString attr = graduatedRenderer->classAttribute();
1605 // categorizedAttr could be either an attribute name or an expression.
1606 // the only way to differentiate is to test it as an expression...
1607 bool isField = false;
1608 if ( layer )
1609 {
1610 isField = QgsExpression::expressionToLayerFieldIndex( attr, layer ) != -1;
1611 }
1612 else
1613 {
1614 QgsExpression testExpr( attr );
1615 isField = testExpr.hasParserError() || testExpr.isField();
1616 }
1617 if ( isField && !attr.contains( '\"' ) )
1618 {
1619 //not an expression, so need to quote column name
1620 attr = QgsExpression::quotedColumnRef( attr );
1621 }
1622 else if ( !isField )
1623 {
1624 //otherwise wrap expression in brackets
1625 attr = u"(%1)"_s.arg( attr );
1626 }
1627
1628 auto rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1629
1630 QString expression;
1631 const QgsRangeList ranges = graduatedRenderer->ranges();
1632 const bool isInverted = ranges.size() > 1 ?
1633 ranges.at( 0 ).upperValue() > ranges.at( 1 ).upperValue() :
1634 false;
1635 QgsRendererRange range;
1636 for ( int i = 0; i < ranges.size(); ++i )
1637 {
1638 range = ranges.value( i );
1639 auto rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1640
1641 rule->setLabel( range.label() );
1642 const QString upperValue = QString::number( range.upperValue(), 'f', 16 );
1643 const QString lowerValue = QString::number( range.lowerValue(), 'f', 16 );
1644 // Lower and upper boundaries have to be open-ended
1645 if ( i == 0 )
1646 {
1647 expression = !isInverted ?
1648 u"%1 <= %2"_s.arg( attr, upperValue ) :
1649 u"%1 > %2"_s.arg( attr, lowerValue );
1650 }
1651 else if ( i == ranges.size() - 1 )
1652 {
1653 expression = !isInverted ?
1654 u"%1 > %2"_s.arg( attr, lowerValue ) :
1655 u"%1 <= %2"_s.arg( attr, upperValue );
1656 }
1657 else
1658 {
1659 expression = attr + " > " + lowerValue + " AND " + \
1660 attr + " <= " + upperValue;
1661 }
1662 rule->setFilterExpression( expression );
1663
1664 //Then we construct an equivalent symbol.
1665 //Ideally we could simply copy the symbol, but the graduated renderer allows a separate interface to specify
1666 //data dependent area and rotation, so we need to convert these to obtain the same rendering
1667
1668 std::unique_ptr< QgsSymbol > symbol( range.symbol()->clone() );
1669 rule->setSymbol( symbol.release() );
1670
1671 rootrule->appendChild( rule.release() );
1672 }
1673
1674 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1675 }
1676 else if ( renderer->type() == "pointDisplacement"_L1 || renderer->type() == "pointCluster"_L1 )
1677 {
1678 if ( const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer ) )
1679 return convertFromRenderer( pointDistanceRenderer->embeddedRenderer() );
1680 }
1681 else if ( renderer->type() == "invertedPolygonRenderer"_L1 )
1682 {
1683 if ( const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer ) )
1684 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1685 }
1686 else if ( renderer->type() == "mergedFeatureRenderer"_L1 )
1687 {
1688 if ( const QgsMergedFeatureRenderer *mergedRenderer = dynamic_cast<const QgsMergedFeatureRenderer *>( renderer ) )
1689 r.reset( convertFromRenderer( mergedRenderer->embeddedRenderer() ) );
1690 }
1691 else if ( renderer->type() == "embeddedSymbol"_L1 && layer )
1692 {
1693 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1694
1695 auto rootrule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1696
1699 req.setNoAttributes();
1700 QgsFeatureIterator it = layer->getFeatures( req );
1701 QgsFeature feature;
1702 while ( it.nextFeature( feature ) && rootrule->children().size() < 500 )
1703 {
1704 if ( feature.embeddedSymbol() )
1705 {
1706 auto rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1707 rule->setFilterExpression( u"$id=%1"_s.arg( feature.id() ) );
1708 rule->setLabel( QString::number( feature.id() ) );
1709 rule->setSymbol( feature.embeddedSymbol()->clone() );
1710 rootrule->appendChild( rule.release() );
1711 }
1712 }
1713
1714 auto rule = std::make_unique< QgsRuleBasedRenderer::Rule >( nullptr );
1715 rule->setFilterExpression( u"ELSE"_s );
1716 rule->setLabel( QObject::tr( "All other features" ) );
1717 rule->setSymbol( embeddedRenderer->defaultSymbol()->clone() );
1718 rootrule->appendChild( rule.release() );
1719
1720 r = std::make_unique< QgsRuleBasedRenderer >( rootrule.release() );
1721 }
1722
1723 if ( r )
1724 {
1725 renderer->copyRendererData( r.get() );
1726 }
1727
1728 return r.release();
1729}
1730
1731void QgsRuleBasedRenderer::convertToDataDefinedSymbology( QgsSymbol *symbol, const QString &sizeScaleField, const QString &rotationField )
1732{
1733 QString sizeExpression;
1734 switch ( symbol->type() )
1735 {
1737 for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1738 {
1739 QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( symbol->symbolLayer( j ) );
1740 if ( ! sizeScaleField.isEmpty() )
1741 {
1742 sizeExpression = u"%1*(%2)"_s.arg( msl->size() ).arg( sizeScaleField );
1744 }
1745 if ( ! rotationField.isEmpty() )
1746 {
1748 }
1749 }
1750 break;
1752 if ( ! sizeScaleField.isEmpty() )
1753 {
1754 for ( int j = 0; j < symbol->symbolLayerCount(); ++j )
1755 {
1756 if ( symbol->symbolLayer( j )->layerType() == "SimpleLine"_L1 )
1757 {
1758 QgsLineSymbolLayer *lsl = static_cast<QgsLineSymbolLayer *>( symbol->symbolLayer( j ) );
1759 sizeExpression = u"%1*(%2)"_s.arg( lsl->width() ).arg( sizeScaleField );
1761 }
1762 if ( symbol->symbolLayer( j )->layerType() == "MarkerLine"_L1 )
1763 {
1764 QgsSymbol *marker = symbol->symbolLayer( j )->subSymbol();
1765 for ( int k = 0; k < marker->symbolLayerCount(); ++k )
1766 {
1767 QgsMarkerSymbolLayer *msl = static_cast<QgsMarkerSymbolLayer *>( marker->symbolLayer( k ) );
1768 sizeExpression = u"%1*(%2)"_s.arg( msl->size() ).arg( sizeScaleField );
1770 }
1771 }
1772 }
1773 }
1774 break;
1775 default:
1776 break;
1777 }
1778}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2254
@ EmbeddedSymbols
Retrieve any embedded feature symbology.
Definition qgis.h:2258
QFlags< FeatureRendererFlag > FeatureRendererFlags
Flags controlling behavior of vector feature renderers.
Definition qgis.h:857
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:848
GeometryType
The geometry types are used to group Qgis::WkbType in a coarse way.
Definition qgis.h:365
@ Point
Points.
Definition qgis.h:366
@ Line
Lines.
Definition qgis.h:367
@ Polygon
Polygons.
Definition qgis.h:368
@ Marker
Marker symbol.
Definition qgis.h:630
@ Line
Line symbol.
Definition qgis.h:631
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
Definition qgis.h:868
A feature renderer which represents features using a list of renderer categories.
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.
Handles 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)
Fetch next feature and stores in f, returns true on success.
QgsFeatureRenderer(const QString &type)
virtual void stopRender(QgsRenderContext &context)
Must be called when a render cycle has finished, to allow the renderer to clean up.
QString type() const
void copyRendererData(QgsFeatureRenderer *destRenderer) const
Clones generic renderer data to another renderer.
void saveRendererData(QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context)
Saves generic renderer data into the specified element.
void renderFeatureWithSymbol(const QgsFeature &feature, QgsSymbol *symbol, QgsRenderContext &context, int layer, bool selected, bool drawVertexMarker)
Render the feature with the symbol using context.
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.
virtual QgsFeatureRenderer * clone() const =0
Create a deep copy of this renderer.
Wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
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:60
QgsFeatureId id
Definition qgsfeature.h:68
const QgsSymbol * embeddedSymbol() const
Returns the feature's embedded symbology, or nullptr if the feature has no embedded symbol.
Container of fields for a vector layer.
Definition qgsfields.h:46
A fill symbol type, for rendering Polygon and MultiPolygon geometries.
A vector feature renderer which uses numeric attributes to classify features into different ranges.
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.
A polygon-only feature renderer used to display features inverted.
Stores information about one class/rule of a vector layer renderer in a unified way that can be used ...
Abstract base class for line symbol layers.
virtual double width() const
Returns the estimated width for the line symbol layer.
A line symbol type, for rendering LineString and MultiLineString geometries.
Abstract base class for marker symbol layers.
double size() const
Returns the symbol size.
A marker symbol type, for rendering Point and MultiPoint geometries.
A polygon or line-only feature renderer used to render a set of features merged (or dissolved) into a...
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...
virtual QString translate(const QString &context, const QString &sourceText, const char *disambiguation=nullptr, int n=-1) const =0
Translates a string using the Qt QTranslator mechanism.
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.
A container for the context for various read/write operations on objects.
const QgsProjectTranslator * projectTranslator() const
Returns the project translator.
const QString currentLayerId() const
Returns the currently used layer id as string.
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...
Represents a value range for a QgsGraduatedSymbolRenderer.
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.
Represents an individual rule for a 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
static QgsRuleBasedRenderer::Rule * create(QDomElement &ruleElem, QgsSymbolMap &symbolMap, bool reuseId=true, const QgsReadWriteContext &context=QgsReadWriteContext())
Create a rule from an XML definition.
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.
@ Inactive
The rule is inactive.
@ Filtered
The rule does not apply.
@ 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
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
Returns a list of the symbols used by this rule and all children of this rule.
bool isScaleOK(double scale) const
Check if this rule applies for a given scale.
static QgsRuleBasedRenderer::Rule * createFromSld(QDomElement &element, Qgis::GeometryType geomType)
Create a rule from the SLD provided in element and for the specified geometry type.
QgsExpression * filter() const
A filter that will check if this rule applies.
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.
QString description() const
A human readable description for this rule.
void stopRender(QgsRenderContext &context)
Stop a rendering process.
bool hasActiveChildren() const
Returns true if the rule has any active children.
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.
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.
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, QVariantMap props) const
Saves the symbol layer as SLD.
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
Returns true if the legend symbology item with the specified key 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.
bool canSkipRender() override
Returns true if the renderer can be entirely skipped, i.e.
void checkLegendSymbolItem(const QString &key, bool state=true) override
Sets whether the legend symbology item with the specified ley should be 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
Returns true if symbology items in legend are 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.
Qgis::FeatureRendererFlags flags() const override
Returns flags associated with the renderer.
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.
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
static QgsFeatureRenderer * createFromSld(QDomElement &element, Qgis::GeometryType geomType)
Creates a new rule based renderer from an SLD XML element.
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...
bool renderFeature(const QgsFeature &feature, QgsRenderContext &context, int layer=-1, bool selected=false, bool drawVertexMarker=false) override
Render a feature using this renderer in the given context.
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.
Q_DECL_DEPRECATED 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.
static bool equalToOrGreaterThanMinimumScale(const double scale, const double minScale)
Returns whether the scale is equal to or greater than the minScale, taking non-round numbers into acc...
static bool lessThanMaximumScale(const double scale, const double maxScale)
Returns whether the scale is less than the maxScale, taking non-round numbers into account.
A feature renderer which renders all features with the same symbol.
QgsSymbol * symbol() const
Returns the symbol which will be rendered for every feature.
Holds SLD export options and other information related to SLD export of a QGIS layer style.
void setExtraProperties(const QVariantMap &properties)
Sets the open ended set of properties that can drive/inform the SLD encoding.
QVariantMap extraProperties() const
Returns the open ended set of properties that can drive/inform the SLD encoding.
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:1398
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 Q_DECL_DEPRECATED bool createFunctionElement(QDomDocument &doc, QDomElement &element, const QString &function)
Creates an OGC function element.
static bool hasSldSymbolizer(const QDomElement &element)
Returns true if a DOM element contains an SLD Symbolizer element.
static bool createSymbolLayerListFromSld(QDomElement &element, Qgis::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.
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:231
QgsSymbolLayer * symbolLayer(int layer)
Returns the symbol layer at the specified index.
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:353
Qgis::SymbolType type() const
Returns the symbol's type.
Definition qgssymbol.h:294
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Represents a vector layer which manages a vector based dataset.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const final
Queries the layer for features specified in request.
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
QString qgsEnumValueToKey(const T &value, bool *returnOk=nullptr)
Returns the value for the given key of an enum.
Definition qgis.h:7091
bool qgsDoubleNear(double a, double b, double epsilon=4 *std::numeric_limits< double >::epsilon())
Compare two doubles (but allow some difference).
Definition qgis.h:6900
QList< QgsLegendSymbolItem > QgsLegendSymbolList
#define QgsDebugMsgLevel(str, level)
Definition qgslogger.h:63
#define QgsDebugError(str)
Definition qgslogger.h:59
#define RENDERER_TAG_NAME
Definition qgsrenderer.h:57
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition qgsrenderer.h:52
QList< QgsSymbol * > QgsSymbolList
Definition qgsrenderer.h:51
QList< QgsRendererRange > QgsRangeList
QList< QgsSymbolLayer * > QgsSymbolLayerList
Definition qgssymbol.h:30
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.