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