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