QGIS API Documentation 4.1.0-Master (64dc32379c2)
Loading...
Searching...
No Matches
qgscategorizedsymbolrenderer.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgscategorizedsymbolrenderer.cpp
3 ---------------------
4 begin : November 2009
5 copyright : (C) 2009 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 ***************************************************************************/
16
17#include <algorithm>
18#include <memory>
19
20#include "qgsapplication.h"
21#include "qgscolorramp.h"
22#include "qgscolorrampimpl.h"
27#include "qgsfeature.h"
28#include "qgsfieldformatter.h"
32#include "qgslogger.h"
33#include "qgsmarkersymbol.h"
34#include "qgspainteffect.h"
36#include "qgsproperty.h"
38#include "qgssldexportcontext.h"
39#include "qgsstyle.h"
41#include "qgssymbol.h"
42#include "qgssymbollayer.h"
43#include "qgssymbollayerutils.h"
44#include "qgsvariantutils.h"
45#include "qgsvectorlayer.h"
46
47#include <QDomDocument>
48#include <QDomElement>
49#include <QRegularExpression>
50#include <QSettings>
51#include <QString>
52#include <QUuid>
53
54using namespace Qt::StringLiterals;
55
56QgsRendererCategory::QgsRendererCategory( const QVariant &value, QgsSymbol *symbol, const QString &label, bool render, const QString &uuid )
57 : mValue( value )
58 , mSymbol( symbol )
59 , mLabel( label )
60 , mRender( render )
61{
62 mUuid = !uuid.isEmpty() ? uuid : QUuid::createUuid().toString();
63}
64
66 : mValue( cat.mValue )
67 , mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
68 , mLabel( cat.mLabel )
69 , mRender( cat.mRender )
70 , mUuid( cat.mUuid )
71{}
72
74{
75 if ( &cat == this )
76 return *this;
77
78 mValue = cat.mValue;
79 mSymbol.reset( cat.mSymbol ? cat.mSymbol->clone() : nullptr );
80 mLabel = cat.mLabel;
81 mRender = cat.mRender;
82 mUuid = cat.mUuid;
83 return *this;
84}
85
87
89{
90 return mUuid;
91}
92
94{
95 return mValue;
96}
97
99{
100 return mSymbol.get();
101}
102
104{
105 return mLabel;
106}
107
109{
110 return mRender;
111}
112
114{
115 mValue = value;
116}
117
119{
120 if ( mSymbol.get() != s )
121 mSymbol.reset( s );
122}
123
125{
126 mLabel = label;
127}
128
130{
131 mRender = render;
132}
133
135{
136 return u"%1::%2::%3:%4\n"_s.arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
137}
138
139void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
140{
141 if ( !mSymbol.get() || props.value( u"attribute"_s, QString() ).toString().isEmpty() )
142 return;
143
144 QString attrName = props[u"attribute"_s].toString();
145
146 QgsSldExportContext context;
147 context.setExtraProperties( props );
148 toSld( doc, element, attrName, context );
149}
150
151bool QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, const QString &classAttribute, QgsSldExportContext &context ) const
152{
153 if ( !mSymbol.get() || classAttribute.isEmpty() )
154 return false;
155
156 QString attrName = classAttribute;
157
158 // try to determine if attribute name is actually a field reference or expression.
159 // If it's a field reference, we need to quote it.
160 // Because we don't have access to the layer or fields here, we treat a parser error
161 // as just an unquoted field name (eg a field name with spaces)
162 const QgsExpression attrExpression = QgsExpression( attrName );
163 if ( attrExpression.hasParserError() )
164 {
165 attrName = QgsExpression::quotedColumnRef( attrName );
166 }
167 else if ( attrExpression.isField() )
168 {
169 attrName = QgsExpression::quotedColumnRef( qgis::down_cast<const QgsExpressionNodeColumnRef *>( attrExpression.rootNode() )->name() );
170 }
171
172 QDomElement ruleElem = doc.createElement( u"se:Rule"_s );
173
174 QDomElement nameElem = doc.createElement( u"se:Name"_s );
175 nameElem.appendChild( doc.createTextNode( mLabel ) );
176 ruleElem.appendChild( nameElem );
177
178 QDomElement descrElem = doc.createElement( u"se:Description"_s );
179 QDomElement titleElem = doc.createElement( u"se:Title"_s );
180 QString descrStr = u"%1 is '%2'"_s.arg( attrName, mValue.toString() );
181 titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
182 descrElem.appendChild( titleElem );
183 ruleElem.appendChild( descrElem );
184
185 // create the ogc:Filter for the range
186 QString filterFunc;
187 if ( mValue.userType() == QMetaType::Type::QVariantList )
188 {
189 const QVariantList list = mValue.toList();
190 if ( list.size() == 1 )
191 {
192 filterFunc = u"%1 = %2"_s.arg( attrName, QgsExpression::quotedValue( list.at( 0 ) ) );
193 }
194 else
195 {
196 QStringList valuesList;
197 valuesList.reserve( list.size() );
198 for ( const QVariant &v : list )
199 {
200 valuesList << QgsExpression::quotedValue( v );
201 }
202 filterFunc = u"%1 IN (%2)"_s.arg( attrName, valuesList.join( ',' ) );
203 }
204 }
205 else if ( QgsVariantUtils::isNull( mValue ) || mValue.toString().isEmpty() )
206 {
207 filterFunc = u"ELSE"_s;
208 }
209 else
210 {
211 filterFunc = u"%1 = %2"_s.arg( attrName, QgsExpression::quotedValue( mValue ) );
212 }
213
214 QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc, context );
215
216 // add the mix/max scale denoms if we got any from the callers
217 const QVariantMap oldProps = context.extraProperties();
218 QVariantMap props = oldProps;
219 QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
220 context.setExtraProperties( props );
221 mSymbol->toSld( doc, ruleElem, context );
222 context.setExtraProperties( oldProps );
223 if ( !QgsSymbolLayerUtils::hasSldSymbolizer( ruleElem ) )
224 {
225 // symbol could not be converted to SLD, or is an "empty" symbol. In this case we do not generate a rule, as
226 // SLD spec requires a Symbolizer element to be present
227 return false;
228 }
229
230 element.appendChild( ruleElem );
231 return true;
232}
233
235
237 : QgsFeatureRenderer( u"categorizedSymbol"_s )
238 , mAttrName( attrName )
239{
240 //important - we need a deep copy of the categories list, not a shared copy. This is required because
241 //QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
242 //trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
243 for ( const QgsRendererCategory &cat : categories )
244 {
245 if ( !cat.symbol() )
246 {
247 QgsDebugError( u"invalid symbol in a category! ignoring..."_s );
248 }
249 mCategories << cat;
250 }
251}
252
254{
256 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
257 for ( ; catIt != mCategories.constEnd(); ++catIt )
258 {
259 if ( QgsSymbol *catSymbol = catIt->symbol() )
260 {
261 if ( catSymbol->flags().testFlag( Qgis::SymbolFlag::AffectsLabeling ) )
263 }
264 }
265
266 return res;
267}
268
270
272{
273 mSymbolHash.clear();
274
275 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
276 {
277 const QVariant val = cat.value();
278 if ( val.userType() == QMetaType::Type::QVariantList )
279 {
280 const QVariantList list = val.toList();
281 for ( const QVariant &v : list )
282 {
283 mSymbolHash.insert( v.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
284 }
285 }
286 else
287 {
288 mSymbolHash.insert( val.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
289 }
290 }
291}
292
297
299{
300 bool found = false;
301 return symbolForValue( value, found );
302}
303
304QgsSymbol *QgsCategorizedSymbolRenderer::symbolForValue( const QVariant &value, bool &foundMatchingSymbol ) const
305{
306 foundMatchingSymbol = false;
307
308 // TODO: special case for int, double
309 QHash<QString, QgsSymbol *>::const_iterator it = mSymbolHash.constFind( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
310 if ( it == mSymbolHash.constEnd() )
311 {
312 if ( mSymbolHash.isEmpty() )
313 {
314 QgsDebugError( u"there are no hashed symbols!!!"_s );
315 }
316 else
317 {
318 QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
319 }
320 return nullptr;
321 }
322
323 foundMatchingSymbol = true;
324
325 return *it;
326}
327
329{
330 return originalSymbolForFeature( feature, context );
331}
332
333QVariant QgsCategorizedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
334{
335 QgsAttributes attrs = feature.attributes();
336 QVariant value;
337 if ( mAttrNum == -1 )
338 {
339 Q_ASSERT( mExpression );
340
341 value = mExpression->evaluate( &context.expressionContext() );
342 }
343 else
344 {
345 value = attrs.value( mAttrNum );
346 }
347
348 return value;
349}
350
352{
353 QVariant value = valueForFeature( feature, context );
354
355 bool foundCategory = false;
356 // find the right symbol for the category
357 QgsSymbol *symbol = symbolForValue( value, foundCategory );
358
359 if ( !foundCategory )
360 {
361 // if no symbol found, use default symbol
362 return symbolForValue( QVariant( "" ), foundCategory );
363 }
364
365 return symbol;
366}
367
368
370{
371 for ( int i = 0; i < mCategories.count(); i++ )
372 {
373 if ( mCategories[i].value() == val )
374 return i;
375 }
376 return -1;
377}
378
380{
381 int idx = -1;
382 for ( int i = 0; i < mCategories.count(); i++ )
383 {
384 if ( mCategories[i].label() == val )
385 {
386 if ( idx != -1 )
387 return -1;
388 else
389 idx = i;
390 }
391 }
392 return idx;
393}
394
395bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
396{
397 if ( catIndex < 0 || catIndex >= mCategories.size() )
398 return false;
399 mCategories[catIndex].setValue( value );
400 return true;
401}
402
404{
405 if ( catIndex < 0 || catIndex >= mCategories.size() )
406 return false;
407 mCategories[catIndex].setSymbol( symbol );
408 return true;
409}
410
411bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString &label )
412{
413 if ( catIndex < 0 || catIndex >= mCategories.size() )
414 return false;
415 mCategories[catIndex].setLabel( label );
416 return true;
417}
418
420{
421 if ( catIndex < 0 || catIndex >= mCategories.size() )
422 return false;
423 mCategories[catIndex].setRenderState( render );
424 return true;
425}
426
428{
429 if ( !cat.symbol() )
430 {
431 QgsDebugError( u"invalid symbol in a category! ignoring..."_s );
432 return;
433 }
434
435 mCategories.append( cat );
436}
437
439{
440 if ( catIndex < 0 || catIndex >= mCategories.size() )
441 return false;
442
443 mCategories.removeAt( catIndex );
444 return true;
445}
446
451
453{
454 if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() )
455 return;
456 mCategories.move( from, to );
457}
458
460{
461 return qgsVariantLessThan( c1.value(), c2.value() );
462}
464{
465 return qgsVariantGreaterThan( c1.value(), c2.value() );
466}
467
469{
470 if ( order == Qt::AscendingOrder )
471 {
472 std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
473 }
474 else
475 {
476 std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
477 }
478}
479
481{
482 return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
483}
484
486{
487 return QString::localeAwareCompare( c1.label(), c2.label() ) > 0;
488}
489
491{
492 if ( order == Qt::AscendingOrder )
493 {
494 std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
495 }
496 else
497 {
498 std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
499 }
500}
501
503{
504 QgsFeatureRenderer::startRender( context, fields );
505
506 mCounting = context.rendererScale() == 0.0;
507
508 // make sure that the hash table is up to date
509 rebuildHash();
510
511 // find out classification attribute index from name
512 mAttrNum = fields.lookupField( mAttrName );
513 if ( mAttrNum == -1 )
514 {
515 mExpression = std::make_unique<QgsExpression>( mAttrName );
516 mExpression->prepare( &context.expressionContext() );
517 }
518
519 mAttrIsNumeric = mAttrNum != -1 && fields.at( mAttrNum ).isNumeric();
520
521 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
522 {
523 cat.symbol()->startRender( context, fields );
524 }
525}
526
528{
530
531 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
532 {
533 cat.symbol()->stopRender( context );
534 }
535 mExpression.reset();
536}
537
539{
540 QSet<QString> attributes;
541
542 // mAttrName can contain either attribute name or an expression.
543 // Sometimes it is not possible to distinguish between those two,
544 // e.g. "a - b" can be both a valid attribute name or expression.
545 // Since we do not have access to fields here, try both options.
546 attributes << mAttrName;
547
548 QgsExpression testExpr( mAttrName );
549 if ( !testExpr.hasParserError() )
550 attributes.unite( testExpr.referencedColumns() );
551
552 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
553 for ( ; catIt != mCategories.constEnd(); ++catIt )
554 {
555 QgsSymbol *catSymbol = catIt->symbol();
556 if ( catSymbol )
557 {
558 attributes.unite( catSymbol->usedAttributes( context ) );
559 }
560 }
561 return attributes;
562}
563
565{
566 QgsExpression testExpr( mAttrName );
567 if ( !testExpr.hasParserError() )
568 {
569 QgsExpressionContext context;
570 context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
571 testExpr.prepare( &context );
572 return testExpr.needsGeometry();
573 }
574 return false;
575}
576
578{
579 QString s = u"CATEGORIZED: idx %1\n"_s.arg( mAttrName );
580 for ( int i = 0; i < mCategories.count(); i++ )
581 s += mCategories[i].dump();
582 return s;
583}
584
599
600void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
601{
602 QgsSldExportContext context;
603 context.setExtraProperties( props );
604 toSld( doc, element, context );
605}
606
607bool QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
608{
609 const QVariantMap oldProps = context.extraProperties();
610 QVariantMap newProps = oldProps;
611 newProps[u"attribute"_s] = mAttrName;
612 context.setExtraProperties( newProps );
613
614 // create a Rule for each range
615 bool result = true;
616 for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
617 {
618 if ( !it->toSld( doc, element, mAttrName, context ) )
619 result = false;
620 }
621 context.setExtraProperties( oldProps );
622 return result;
623}
624
626{
627 int attrNum = fields.lookupField( mAttrName );
628 bool isExpression = ( attrNum == -1 );
629
630 bool hasDefault = false;
631 bool defaultActive = false;
632 bool allActive = true;
633 bool noneActive = true;
634
635 //we need to build lists of both inactive and active values, as either list may be required
636 //depending on whether the default category is active or not
637 QString activeValues;
638 QString inactiveValues;
639
640 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
641 {
642 if ( cat.value() == "" || QgsVariantUtils::isNull( cat.value() ) )
643 {
644 hasDefault = true;
645 defaultActive = cat.renderState();
646 }
647
648 noneActive = noneActive && !cat.renderState();
649 allActive = allActive && cat.renderState();
650
651 const bool isList = cat.value().userType() == QMetaType::Type::QVariantList;
652 QString value = QgsExpression::quotedValue( cat.value(), static_cast<QMetaType::Type>( cat.value().userType() ) );
653
654 if ( !cat.renderState() )
655 {
656 if ( value != "" )
657 {
658 if ( isList )
659 {
660 const QVariantList list = cat.value().toList();
661 for ( const QVariant &v : list )
662 {
663 if ( !inactiveValues.isEmpty() )
664 inactiveValues.append( ',' );
665
666 inactiveValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
667 }
668 }
669 else
670 {
671 if ( !inactiveValues.isEmpty() )
672 inactiveValues.append( ',' );
673
674 inactiveValues.append( value );
675 }
676 }
677 }
678 else
679 {
680 if ( value != "" )
681 {
682 if ( isList )
683 {
684 const QVariantList list = cat.value().toList();
685 for ( const QVariant &v : list )
686 {
687 if ( !activeValues.isEmpty() )
688 activeValues.append( ',' );
689
690 activeValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
691 }
692 }
693 else
694 {
695 if ( !activeValues.isEmpty() )
696 activeValues.append( ',' );
697
698 activeValues.append( value );
699 }
700 }
701 }
702 }
703
704 QString attr = isExpression ? mAttrName : u"\"%1\""_s.arg( mAttrName );
705
706 if ( allActive && hasDefault )
707 {
708 return QString();
709 }
710 else if ( noneActive )
711 {
712 return u"FALSE"_s;
713 }
714 else if ( defaultActive )
715 {
716 return u"(%1) NOT IN (%2) OR (%1) IS NULL"_s.arg( attr, inactiveValues );
717 }
718 else
719 {
720 return u"(%1) IN (%2)"_s.arg( attr, activeValues );
721 }
722}
723
725{
726 Q_UNUSED( context )
727 QgsSymbolList lst;
728 lst.reserve( mCategories.count() );
729 for ( const QgsRendererCategory &cat : mCategories )
730 {
731 lst.append( cat.symbol() );
732 }
733 return lst;
734}
735
737{
738 for ( const QgsRendererCategory &cat : mCategories )
739 {
740 QgsStyleSymbolEntity entity( cat.symbol() );
741 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, cat.value().toString(), cat.label() ) ) )
742 return false;
743 }
744
745 if ( mSourceColorRamp )
746 {
748 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
749 return false;
750 }
751
752 return true;
753}
754
756{
757 QDomElement symbolsElem = element.firstChildElement( u"symbols"_s );
758 if ( symbolsElem.isNull() )
759 return nullptr;
760
761 QDomElement catsElem = element.firstChildElement( u"categories"_s );
762 if ( catsElem.isNull() )
763 return nullptr;
764
765 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
766 QgsCategoryList cats;
767
768 // Value from string (long, ulong, double and string)
769 const auto valueFromString = []( const QString &value, const QString &valueType ) -> QVariant {
770 if ( valueType == "double"_L1 )
771 {
772 bool ok;
773 const auto val { value.toDouble( &ok ) };
774 if ( ok )
775 {
776 return val;
777 }
778 }
779 else if ( valueType == "ulong"_L1 )
780 {
781 bool ok;
782 const auto val { value.toULongLong( &ok ) };
783 if ( ok )
784 {
785 return val;
786 }
787 }
788 else if ( valueType == "long"_L1 )
789 {
790 bool ok;
791 const auto val { value.toLongLong( &ok ) };
792 if ( ok )
793 {
794 return val;
795 }
796 }
797 else if ( valueType == "bool"_L1 )
798 {
799 if ( value.toLower() == "false"_L1 )
800 return false;
801 if ( value.toLower() == "true"_L1 )
802 return true;
803 }
804 else if ( valueType == "NULL"_L1 )
805 {
806 // This is the default ("fallback") category
807 return QVariant();
808 }
809 return value;
810 };
811
812 QDomElement catElem = catsElem.firstChildElement();
813 int i = 0;
814 QSet<QString> usedUuids;
815 while ( !catElem.isNull() )
816 {
817 if ( catElem.tagName() == "category"_L1 )
818 {
819 QVariant value;
820 if ( catElem.hasAttribute( u"value"_s ) )
821 {
822 value = valueFromString( catElem.attribute( u"value"_s ), catElem.attribute( u"type"_s, QString() ) );
823 }
824 else
825 {
826 QVariantList values;
827 QDomElement valElem = catElem.firstChildElement();
828 while ( !valElem.isNull() )
829 {
830 if ( valElem.tagName() == "val"_L1 )
831 {
832 values << valueFromString( valElem.attribute( u"value"_s ), valElem.attribute( u"type"_s, QString() ) );
833 }
834 valElem = valElem.nextSiblingElement();
835 }
836 if ( !values.isEmpty() )
837 value = values;
838 }
839
840 QString symbolName = catElem.attribute( u"symbol"_s );
841 QString label = context.projectTranslator()->translate( u"project:layers:%1:legendsymbollabels"_s.arg( context.currentLayerId() ), catElem.attribute( u"label"_s ) );
842 QgsDebugMsgLevel( "context" + u"project:layers:%1:legendsymbollabels"_s.arg( context.currentLayerId() ) + " source " + catElem.attribute( u"label"_s ), 3 );
843
844 bool render = catElem.attribute( u"render"_s ) != "false"_L1;
845 QString uuid = catElem.attribute( u"uuid"_s, QString::number( i++ ) );
846
847 while ( usedUuids.contains( uuid ) )
848 {
849 uuid = QUuid::createUuid().toString();
850 }
851 if ( symbolMap.contains( symbolName ) )
852 {
853 QgsSymbol *symbol = symbolMap.take( symbolName );
854 cats.append( QgsRendererCategory( value, symbol, label, render, uuid ) );
855 usedUuids << uuid;
856 }
857 }
858 catElem = catElem.nextSiblingElement();
859 }
860
861 QString attrName = element.attribute( u"attr"_s );
862
864
865 // delete symbols if there are any more
867
868 // try to load source symbol (optional)
869 QDomElement sourceSymbolElem = element.firstChildElement( u"source-symbol"_s );
870 if ( !sourceSymbolElem.isNull() )
871 {
872 QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
873 if ( sourceSymbolMap.contains( u"0"_s ) )
874 {
875 r->setSourceSymbol( sourceSymbolMap.take( u"0"_s ) );
876 }
877 QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
878 }
879
880 // try to load color ramp (optional)
881 QDomElement sourceColorRampElem = element.firstChildElement( u"colorramp"_s );
882 if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( u"name"_s ) == "[source]"_L1 )
883 {
884 r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ).release() );
885 }
886
887 QDomElement rotationElem = element.firstChildElement( u"rotation"_s );
888 if ( !rotationElem.isNull() && !rotationElem.attribute( u"field"_s ).isEmpty() )
889 {
890 for ( const QgsRendererCategory &cat : r->mCategories )
891 {
892 convertSymbolRotation( cat.symbol(), rotationElem.attribute( u"field"_s ) );
893 }
894 if ( r->mSourceSymbol )
895 {
896 convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( u"field"_s ) );
897 }
898 }
899
900 QDomElement sizeScaleElem = element.firstChildElement( u"sizescale"_s );
901 if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( u"field"_s ).isEmpty() )
902 {
903 for ( const QgsRendererCategory &cat : r->mCategories )
904 {
905 convertSymbolSizeScale( cat.symbol(), QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( u"scalemethod"_s ) ), sizeScaleElem.attribute( u"field"_s ) );
906 }
907 if ( r->mSourceSymbol && r->mSourceSymbol->type() == Qgis::SymbolType::Marker )
908 {
909 convertSymbolSizeScale( r->mSourceSymbol.get(), QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( u"scalemethod"_s ) ), sizeScaleElem.attribute( u"field"_s ) );
910 }
911 }
912
913 QDomElement ddsLegendSizeElem = element.firstChildElement( u"data-defined-size-legend"_s );
914 if ( !ddsLegendSizeElem.isNull() )
915 {
916 r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
917 }
918
919 // TODO: symbol levels
920 return r;
921}
922
923QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
924{
925 // clazy:skip
926 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
927 rendererElem.setAttribute( u"type"_s, u"categorizedSymbol"_s );
928 rendererElem.setAttribute( u"attr"_s, mAttrName );
929
930 // String for type
931 // We just need string, bool, and three numeric types: double, ulong and long for unsigned, signed and float/double
932 const auto stringForType = []( const QMetaType::Type type ) -> QString {
933 if ( type == QMetaType::Type::QChar || type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
934 {
935 return u"long"_s;
936 }
937 else if ( type == QMetaType::Type::UInt || type == QMetaType::Type::ULongLong )
938 {
939 return u"ulong"_s;
940 }
941 else if ( type == QMetaType::Type::Double )
942 {
943 return u"double"_s;
944 }
945 else if ( type == QMetaType::Type::Bool )
946 {
947 return u"bool"_s;
948 }
949 else // Default: string
950 {
951 return u"string"_s;
952 }
953 };
954
955 // categories
956 if ( !mCategories.isEmpty() )
957 {
958 int i = 0;
960 QDomElement catsElem = doc.createElement( u"categories"_s );
961 QgsCategoryList::const_iterator it = mCategories.constBegin();
962 for ( ; it != mCategories.constEnd(); ++it )
963 {
964 const QgsRendererCategory &cat = *it;
965 QString symbolName = QString::number( i );
966 symbols.insert( symbolName, cat.symbol() );
967
968 QDomElement catElem = doc.createElement( u"category"_s );
969 if ( cat.value().userType() == QMetaType::Type::QVariantList )
970 {
971 const QVariantList list = cat.value().toList();
972 for ( const QVariant &v : list )
973 {
974 QDomElement valueElem = doc.createElement( u"val"_s );
975 valueElem.setAttribute( u"value"_s, v.toString() );
976 valueElem.setAttribute( u"type"_s, stringForType( static_cast<QMetaType::Type>( v.userType() ) ) );
977 catElem.appendChild( valueElem );
978 }
979 }
980 else
981 {
982 if ( QgsVariantUtils::isNull( cat.value() ) )
983 {
984 // We need to save NULL value as specific kind, it is the default ("fallback") category
985 catElem.setAttribute( u"value"_s, "NULL" );
986 catElem.setAttribute( u"type"_s, "NULL" );
987 }
988 else
989 {
990 catElem.setAttribute( u"value"_s, cat.value().toString() );
991 catElem.setAttribute( u"type"_s, stringForType( static_cast<QMetaType::Type>( cat.value().userType() ) ) );
992 }
993 }
994 catElem.setAttribute( u"symbol"_s, symbolName );
995 catElem.setAttribute( u"label"_s, cat.label() );
996 catElem.setAttribute( u"render"_s, cat.renderState() ? "true" : "false" );
997 catElem.setAttribute( u"uuid"_s, cat.uuid() );
998 catsElem.appendChild( catElem );
999 i++;
1000 }
1001 rendererElem.appendChild( catsElem );
1002
1003 // save symbols
1004 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, u"symbols"_s, doc, context );
1005 rendererElem.appendChild( symbolsElem );
1006 }
1007
1008 // save source symbol
1009 if ( mSourceSymbol )
1010 {
1011 QgsSymbolMap sourceSymbols;
1012 sourceSymbols.insert( u"0"_s, mSourceSymbol.get() );
1013 QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, u"source-symbol"_s, doc, context );
1014 rendererElem.appendChild( sourceSymbolElem );
1015 }
1016
1017 // save source color ramp
1018 if ( mSourceColorRamp )
1019 {
1020 QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( u"[source]"_s, mSourceColorRamp.get(), doc );
1021 rendererElem.appendChild( colorRampElem );
1022 }
1023
1024 QDomElement rotationElem = doc.createElement( u"rotation"_s );
1025 rendererElem.appendChild( rotationElem );
1026
1027 QDomElement sizeScaleElem = doc.createElement( u"sizescale"_s );
1028 rendererElem.appendChild( sizeScaleElem );
1029
1031 {
1032 QDomElement ddsLegendElem = doc.createElement( u"data-defined-size-legend"_s );
1033 mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
1034 rendererElem.appendChild( ddsLegendElem );
1035 }
1036
1037 saveRendererData( doc, rendererElem, context );
1038
1039 return rendererElem;
1040}
1041
1042
1043QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
1044{
1046 for ( const QgsRendererCategory &cat : mCategories )
1047 {
1048 lst << QgsLegendSymbolItem( cat.symbol(), cat.label(), cat.uuid(), true );
1049 }
1050 return lst;
1051}
1052
1053QString QgsCategorizedSymbolRenderer::displayString( const QVariant &v, int precision )
1054{
1055 return QgsVariantUtils::displayString( v, precision );
1056}
1057
1059{
1061 {
1062 // check that all symbols that have the same size expression
1063 QgsProperty ddSize;
1064 for ( const QgsRendererCategory &category : mCategories )
1065 {
1066 const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
1067 if ( ddSize )
1068 {
1069 QgsProperty sSize( symbol->dataDefinedSize() );
1070 if ( sSize != ddSize )
1071 {
1072 // no common size expression
1073 return baseLegendSymbolItems();
1074 }
1075 }
1076 else
1077 {
1078 ddSize = symbol->dataDefinedSize();
1079 }
1080 }
1081
1082 if ( ddSize && ddSize.isActive() )
1083 {
1085
1087 ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
1088 lst += ddSizeLegend.legendSymbolList();
1089
1090 lst += baseLegendSymbolItems();
1091 return lst;
1092 }
1093 }
1094
1095 return baseLegendSymbolItems();
1096}
1097
1099{
1100 const QVariant value = valueForFeature( feature, context );
1101
1102 // "all other values" category value (AKA "else" rule) is represented with an invalid QVariant
1103 QString elseRuleUUID;
1104
1105 for ( const QgsRendererCategory &cat : mCategories )
1106 {
1107 bool match = false;
1108
1109 if ( QgsVariantUtils::isNull( cat.value() ) || ( mAttrIsNumeric && cat.value().toString().isEmpty() ) )
1110 {
1111 elseRuleUUID = cat.uuid();
1112 }
1113
1114 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1115 {
1116 const QVariantList list = cat.value().toList();
1117 for ( const QVariant &v : list )
1118 {
1119 if ( value == v )
1120 {
1121 match = true;
1122 break;
1123 }
1124 }
1125 }
1126 else
1127 {
1128 // NULL cat value may be stored as an empty string or an invalid variant, depending on how
1129 // the renderer was constructed and which QGIS version was used
1130 if ( QgsVariantUtils::isNull( value ) )
1131 {
1132 match = cat.value().toString().isEmpty() || QgsVariantUtils::isNull( cat.value() );
1133 }
1134 else
1135 {
1136 match = value == cat.value();
1137 }
1138 }
1139
1140 if ( match )
1141 {
1142 if ( cat.renderState() || mCounting )
1143 return QSet< QString >() << cat.uuid();
1144 else
1145 return QSet< QString >();
1146 }
1147 }
1148
1149 // if there is an "else" rule category, then the feature will be rendered with that category symbol
1150 if ( !elseRuleUUID.isEmpty() )
1151 {
1152 return QSet< QString >() << elseRuleUUID;
1153 }
1154
1155 return QSet< QString >();
1156}
1157
1158QString QgsCategorizedSymbolRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *layer, bool &ok ) const
1159{
1160 ok = false;
1161 int i = 0;
1162 for ( i = 0; i < mCategories.size(); i++ )
1163 {
1164 if ( mCategories[i].uuid() == key )
1165 {
1166 ok = true;
1167 break;
1168 }
1169 }
1170
1171 if ( !ok )
1172 {
1173 ok = false;
1174 return QString();
1175 }
1176
1177 const int fieldIndex = layer ? layer->fields().lookupField( mAttrName ) : -1;
1178 const bool isNumeric = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).isNumeric() : false;
1179 const QMetaType::Type fieldType = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).type() : QMetaType::Type::UnknownType;
1180 const QString attributeComponent = QgsExpression::quoteFieldExpression( mAttrName, layer );
1181
1182 ok = true;
1183 const QgsRendererCategory &cat = mCategories[i];
1184 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1185 {
1186 const QVariantList list = cat.value().toList();
1187 QStringList parts;
1188 parts.reserve( list.size() );
1189 for ( const QVariant &v : list )
1190 {
1191 parts.append( QgsExpression::quotedValue( v ) );
1192 }
1193
1194 return u"%1 IN (%2)"_s.arg( attributeComponent, parts.join( ", "_L1 ) );
1195 }
1196 else
1197 {
1198 // Numeric NULL cat value is stored as an empty string
1199 QVariant value = cat.value();
1200 if ( isNumeric && value.toString().isEmpty() )
1201 {
1202 value = QVariant();
1203 }
1204
1205 if ( QgsVariantUtils::isNull( value ) )
1206 return u"%1 IS NULL"_s.arg( attributeComponent );
1207 else if ( fieldType == QMetaType::Type::UnknownType )
1208 return u"%1 = %2"_s.arg( attributeComponent, QgsExpression::quotedValue( value ) );
1209 else
1210 return u"%1 = %2"_s.arg( attributeComponent, QgsExpression::quotedValue( value, fieldType ) );
1211 }
1212}
1213
1218
1220{
1221 return mSourceSymbol.get();
1222}
1223
1228
1233
1238
1243
1245{
1246 setSourceColorRamp( ramp );
1247 double num = mCategories.count() - 1;
1248 double count = 0;
1249
1250 QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp );
1251 if ( randomRamp )
1252 {
1253 //ramp is a random colors ramp, so inform it of the total number of required colors
1254 //this allows the ramp to pregenerate a set of visually distinctive colors
1255 randomRamp->setTotalColorCount( mCategories.count() );
1256 }
1257
1258 for ( const QgsRendererCategory &cat : mCategories )
1259 {
1260 double value = count / num;
1261 cat.symbol()->setColor( mSourceColorRamp->color( value ) );
1262 count += 1;
1263 }
1264}
1265
1267{
1268 int i = 0;
1269 for ( const QgsRendererCategory &cat : mCategories )
1270 {
1271 QgsSymbol *symbol = sym->clone();
1272 symbol->setColor( cat.symbol()->color() );
1273 updateCategorySymbol( i, symbol );
1274 ++i;
1275 }
1276 setSourceSymbol( sym->clone() );
1277}
1278
1280{
1281 return true;
1282}
1283
1285{
1286 for ( const QgsRendererCategory &category : std::as_const( mCategories ) )
1287 {
1288 if ( category.uuid() == key )
1289 {
1290 return category.renderState();
1291 }
1292 }
1293
1294 return true;
1295}
1296
1298{
1299 bool ok = false;
1300 int i = 0;
1301 for ( i = 0; i < mCategories.size(); i++ )
1302 {
1303 if ( mCategories[i].uuid() == key )
1304 {
1305 ok = true;
1306 break;
1307 }
1308 }
1309
1310 if ( ok )
1311 updateCategorySymbol( i, symbol );
1312 else
1313 delete symbol;
1314}
1315
1316void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
1317{
1318 for ( int i = 0; i < mCategories.size(); i++ )
1319 {
1320 if ( mCategories[i].uuid() == key )
1321 {
1322 updateCategoryRenderState( i, state );
1323 break;
1324 }
1325 }
1326}
1327
1329{
1330 std::unique_ptr< QgsCategorizedSymbolRenderer > r;
1331 if ( renderer->type() == "categorizedSymbol"_L1 )
1332 {
1333 r.reset( static_cast<QgsCategorizedSymbolRenderer *>( renderer->clone() ) );
1334 }
1335 else if ( renderer->type() == "graduatedSymbol"_L1 )
1336 {
1337 const QgsGraduatedSymbolRenderer *graduatedSymbolRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1338 if ( graduatedSymbolRenderer )
1339 {
1340 r = std::make_unique<QgsCategorizedSymbolRenderer>( QString(), QgsCategoryList() );
1341 if ( graduatedSymbolRenderer->sourceSymbol() )
1342 r->setSourceSymbol( graduatedSymbolRenderer->sourceSymbol()->clone() );
1343 if ( graduatedSymbolRenderer->sourceColorRamp() )
1344 {
1345 r->setSourceColorRamp( graduatedSymbolRenderer->sourceColorRamp()->clone() );
1346 }
1347 r->setClassAttribute( graduatedSymbolRenderer->classAttribute() );
1348 }
1349 }
1350 else if ( renderer->type() == "RuleRenderer"_L1 )
1351 {
1352 const QgsRuleBasedRenderer *ruleBasedSymbolRenderer = dynamic_cast<const QgsRuleBasedRenderer *>( renderer );
1353 if ( ruleBasedSymbolRenderer )
1354 {
1355 r = std::make_unique<QgsCategorizedSymbolRenderer>( QString(), QgsCategoryList() );
1356
1357 const QList< QgsRuleBasedRenderer::Rule * > rules = const_cast< QgsRuleBasedRenderer * >( ruleBasedSymbolRenderer )->rootRule()->children();
1358 bool canConvert = true;
1359
1360 bool isFirst = true;
1361 QString attribute;
1362 QVariant value;
1364
1365 for ( QgsRuleBasedRenderer::Rule *rule : rules )
1366 {
1367 if ( rule->isElse() || rule->minimumScale() != 0 || rule->maximumScale() != 0 || !rule->symbol() || !rule->children().isEmpty() )
1368 {
1369 canConvert = false;
1370 break;
1371 }
1372
1373 QgsExpression e( rule->filterExpression() );
1374
1375 if ( !e.rootNode() )
1376 {
1377 canConvert = false;
1378 break;
1379 }
1380
1381 if ( const QgsExpressionNodeBinaryOperator *binOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
1382 {
1383 if ( binOp->op() == QgsExpressionNodeBinaryOperator::boEQ )
1384 {
1385 const QString left = binOp->opLeft()->dump();
1386 if ( !isFirst && left != attribute )
1387 {
1388 canConvert = false;
1389 break;
1390 }
1391 else if ( isFirst )
1392 {
1393 attribute = left;
1394 }
1395
1396 const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( binOp->opRight() );
1397 if ( literal )
1398 {
1400 cat.setValue( literal->value() );
1401 cat.setSymbol( rule->symbol()->clone() );
1402 cat.setLabel( rule->label().isEmpty() ? literal->value().toString() : rule->label() );
1403 cat.setRenderState( rule->active() );
1404 categories.append( cat );
1405 }
1406 else
1407 {
1408 canConvert = false;
1409 break;
1410 }
1411 }
1412 else
1413 {
1414 canConvert = false;
1415 }
1416 }
1417 else
1418 {
1419 canConvert = false;
1420 break;
1421 }
1422
1423 isFirst = false;
1424 }
1425
1426 if ( canConvert )
1427 {
1428 r = std::make_unique< QgsCategorizedSymbolRenderer >( attribute, categories );
1429 }
1430 else
1431 {
1432 r.reset();
1433 }
1434 }
1435 }
1436 else if ( renderer->type() == "pointDisplacement"_L1 || renderer->type() == "pointCluster"_L1 )
1437 {
1438 const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1439 if ( pointDistanceRenderer )
1440 r.reset( convertFromRenderer( pointDistanceRenderer->embeddedRenderer() ) );
1441 }
1442 else if ( renderer->type() == "invertedPolygonRenderer"_L1 )
1443 {
1444 const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1445 if ( invertedPolygonRenderer )
1446 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1447 }
1448 else if ( renderer->type() == "embeddedSymbol"_L1 && layer )
1449 {
1450 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1454 req.setNoAttributes();
1455 QgsFeatureIterator it = layer->getFeatures( req );
1456 QgsFeature feature;
1457 while ( it.nextFeature( feature ) && categories.size() < 2000 )
1458 {
1459 if ( feature.embeddedSymbol() )
1460 categories.append( QgsRendererCategory( feature.id(), feature.embeddedSymbol()->clone(), QString::number( feature.id() ) ) );
1461 }
1462 categories.append( QgsRendererCategory( QVariant(), embeddedRenderer->defaultSymbol()->clone(), QString() ) );
1463 r = std::make_unique<QgsCategorizedSymbolRenderer>( u"$id"_s, categories );
1464 }
1465
1466 // If not one of the specifically handled renderers, then just grab the symbol from the renderer
1467 // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
1468
1469 if ( !r )
1470 {
1471 r = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
1472 QgsRenderContext context;
1473 QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
1474 if ( !symbols.isEmpty() )
1475 {
1476 QgsSymbol *newSymbol = symbols.at( 0 )->clone();
1479 r->setSourceSymbol( newSymbol );
1480 }
1481 }
1482
1483 renderer->copyRendererData( r.get() );
1484
1485 return r.release();
1486}
1487
1492
1497
1499 QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch
1500)
1501{
1502 if ( !style )
1503 return 0;
1504
1505 int matched = 0;
1506 unmatchedSymbols = style->symbolNames();
1507 const QSet< QString > allSymbolNames( unmatchedSymbols.begin(), unmatchedSymbols.end() );
1508
1509 const thread_local QRegularExpression tolerantMatchRe( u"[^\\w\\d ]"_s, QRegularExpression::UseUnicodePropertiesOption );
1510
1511 for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
1512 {
1513 const QVariant value = mCategories.at( catIdx ).value();
1514 const QString val = value.toString().trimmed();
1515 std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
1516 // case-sensitive match
1517 if ( symbol && symbol->type() == type )
1518 {
1519 matched++;
1520 unmatchedSymbols.removeAll( val );
1521 updateCategorySymbol( catIdx, symbol.release() );
1522 continue;
1523 }
1524
1525 if ( !caseSensitive || useTolerantMatch )
1526 {
1527 QString testVal = val;
1528 if ( useTolerantMatch )
1529 testVal.replace( tolerantMatchRe, QString() );
1530
1531 bool foundMatch = false;
1532 for ( const QString &name : allSymbolNames )
1533 {
1534 QString testName = name.trimmed();
1535 if ( useTolerantMatch )
1536 testName.replace( tolerantMatchRe, QString() );
1537
1538 if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
1539 {
1540 // found a case-insensitive match
1541 std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
1542 if ( symbol && symbol->type() == type )
1543 {
1544 matched++;
1545 unmatchedSymbols.removeAll( name );
1546 updateCategorySymbol( catIdx, symbol.release() );
1547 foundMatch = true;
1548 break;
1549 }
1550 }
1551 }
1552 if ( foundMatch )
1553 continue;
1554 }
1555
1556 unmatchedCategories << value;
1557 }
1558
1559 return matched;
1560}
1561
1562QgsCategoryList QgsCategorizedSymbolRenderer::createCategories( const QList<QVariant> &values, const QgsSymbol *symbol, QgsVectorLayer *layer, const QString &attributeName )
1563{
1564 QgsCategoryList cats;
1565 QVariantList vals = values;
1566 // sort the categories first
1567 QgsSymbolLayerUtils::sortVariantList( vals, Qt::AscendingOrder );
1568
1569 if ( layer && !attributeName.isNull() )
1570 {
1571 const QgsFields fields = layer->fields();
1572 for ( const QVariant &value : vals )
1573 {
1574 QgsSymbol *newSymbol = symbol->clone();
1576 if ( !QgsVariantUtils::isNull( value ) )
1577 {
1578 const int fieldIdx = fields.lookupField( attributeName );
1579 QString categoryName = QgsVariantUtils::displayString( value );
1580 if ( fieldIdx != -1 )
1581 {
1582 const QgsField field = fields.at( fieldIdx );
1583 const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1585 categoryName = formatter->representValue( layer, fieldIdx, setup.config(), QVariant(), value );
1586 }
1587 cats.append( QgsRendererCategory( value, newSymbol, categoryName, true ) );
1588 }
1589 }
1590 }
1591
1592 // add null (default) value
1593 QgsSymbol *newSymbol = symbol->clone();
1595 cats.append( QgsRendererCategory( QVariant(), newSymbol, QString(), true ) );
1596
1597 return cats;
1598}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2329
@ EmbeddedSymbols
Retrieve any embedded feature symbology.
Definition qgis.h:2334
QFlags< FeatureRendererFlag > FeatureRendererFlags
Flags controlling behavior of vector feature renderers.
Definition qgis.h:886
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:877
SymbolType
Symbol types.
Definition qgis.h:637
@ Marker
Marker symbol.
Definition qgis.h:638
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
Definition qgis.h:897
static QgsFieldFormatterRegistry * fieldFormatterRegistry()
Gets the registry of available field formatters.
A vector of attributes.
void sortByValue(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their value.
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...
QgsSymbol * symbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
To be overridden.
void updateSymbols(QgsSymbol *sym)
Update all the symbols but leave categories and colors.
bool updateCategoryRenderState(int catIndex, bool render)
Changes the render state for the category with the specified index.
void setSourceColorRamp(QgsColorRamp *ramp)
Sets the source color ramp.
void stopRender(QgsRenderContext &context) override
Must be called when a render cycle has finished, to allow the renderer to clean up.
QgsSymbol * sourceSymbol()
Returns the renderer's source symbol, which is the base symbol used for the each categories' symbol b...
const QgsCategoryList & categories() const
Returns a list of all categories recognized by the renderer.
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...
int matchToSymbols(QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, bool caseSensitive=true, bool useTolerantMatch=false)
Replaces category symbols with the symbols from a style that have a matching name and symbol type.
std::unique_ptr< QgsColorRamp > mSourceColorRamp
Q_DECL_DEPRECATED QgsSymbol * symbolForValue(const QVariant &value) const
Returns the matching symbol corresponding to an attribute value.
std::unique_ptr< QgsSymbol > mSourceSymbol
static QgsCategorizedSymbolRenderer * convertFromRenderer(const QgsFeatureRenderer *renderer, QgsVectorLayer *layer=nullptr)
Creates a new QgsCategorizedSymbolRenderer from an existing renderer.
void updateColorRamp(QgsColorRamp *ramp)
Update the color ramp used and all symbols colors.
QgsDataDefinedSizeLegend * dataDefinedSizeLegend() const
Returns configuration of appearance of legend when using data-defined size for marker symbols.
static QgsCategoryList createCategories(const QVariantList &values, const QgsSymbol *symbol, QgsVectorLayer *layer=nullptr, const QString &fieldName=QString())
Create categories for a list of values.
QHash< QString, QgsSymbol * > mSymbolHash
hashtable for faster access to symbols
bool filterNeedsGeometry() const override
Returns true if this renderer requires the geometry to apply the filter.
QSet< QString > usedAttributes(const QgsRenderContext &context) const override
Returns a list of attributes required by this renderer.
void setSourceSymbol(QgsSymbol *sym)
Sets the source symbol for the renderer, which is the base symbol used for the each categories' symbo...
void startRender(QgsRenderContext &context, const QgsFields &fields) override
Must be called when a new render cycle is started.
QgsSymbolList symbols(QgsRenderContext &context) const override
Returns list of symbols used by the renderer.
int categoryIndexForValue(const QVariant &val)
Returns the index for the category with the specified value (or -1 if not found).
static QgsFeatureRenderer * create(QDomElement &element, const QgsReadWriteContext &context)
Creates a categorized renderer from an XML element.
bool updateCategorySymbol(int catIndex, QgsSymbol *symbol)
Changes the symbol for the category with the specified index.
bool accept(QgsStyleEntityVisitorInterface *visitor) const override
Accepts the specified symbology visitor, causing it to visit all symbols associated with the renderer...
void setLegendSymbolItem(const QString &key, QgsSymbol *symbol) override
Sets the symbol to be used for a legend symbol item.
std::unique_ptr< QgsExpression > mExpression
std::unique_ptr< QgsDataDefinedSizeLegend > mDataDefinedSizeLegend
bool legendSymbolItemChecked(const QString &key) override
Returns true if the legend symbology item with the specified key is checked.
bool legendSymbolItemsCheckable() const override
Returns true if symbology items in legend are checkable.
void addCategory(const QgsRendererCategory &category)
Adds a new category to the renderer.
QgsCategorizedSymbolRenderer(const QString &attrName=QString(), const QgsCategoryList &categories=QgsCategoryList())
Constructor for QgsCategorizedSymbolRenderer.
void sortByLabel(Qt::SortOrder order=Qt::AscendingOrder)
Sorts the existing categories by their label.
QgsSymbol * originalSymbolForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns symbol for feature.
int mAttrNum
attribute index (derived from attribute name in startRender)
QgsLegendSymbolList legendSymbolItems() const override
Returns a list of symbology items for the legend.
void moveCategory(int from, int to)
Moves an existing category at index position from to index position to.
QDomElement save(QDomDocument &doc, const QgsReadWriteContext &context) override
Stores renderer properties to an XML element.
bool deleteCategory(int catIndex)
Deletes the category with the specified index from the renderer.
Qgis::FeatureRendererFlags flags() const override
Returns flags associated with the renderer.
Q_DECL_DEPRECATED QgsSymbol * skipRender()
void checkLegendSymbolItem(const QString &key, bool state=true) override
Sets whether the legend symbology item with the specified ley should be checked.
QString dump() const override
Returns debug information about this renderer.
QSet< QString > legendKeysForFeature(const QgsFeature &feature, QgsRenderContext &context) const override
Returns legend keys matching a specified feature.
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each categories' color is derived.
bool mAttrIsNumeric
whether the attribute is numeric (derived from attribute name in startRender)
bool updateCategoryValue(int catIndex, const QVariant &value)
Changes the value for the category with the specified index.
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.
QgsCategorizedSymbolRenderer * clone() const override
Create a deep copy of this renderer.
void deleteAllCategories()
Deletes all existing categories from the renderer.
void setDataDefinedSizeLegend(QgsDataDefinedSizeLegend *settings)
Configures appearance of legend when renderer is configured to use data-defined size for marker symbo...
bool updateCategoryLabel(int catIndex, const QString &label)
Changes the label for the category with the specified index.
static Q_DECL_DEPRECATED QString displayString(const QVariant &value, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
~QgsCategorizedSymbolRenderer() override
int categoryIndexForLabel(const QString &val)
Returns the index of the category with the specified label (or -1 if the label was not found,...
Abstract base class for color ramps.
virtual QgsColorRamp * clone() const =0
Creates a clone of the color ramp.
Object that keeps configuration of appearance of marker symbol's data-defined size in legend.
static QgsDataDefinedSizeLegend * readXml(const QDomElement &elem, const QgsReadWriteContext &context) SIP_FACTORY
Creates instance from given element and returns it (caller takes ownership). Returns nullptr on error...
void updateFromSymbolAndProperty(const QgsMarkerSymbol *symbol, const QgsProperty &ddSize)
Updates the list of classes, source symbol and title label from given symbol and property.
QgsLegendSymbolList legendSymbolList() const
Generates legend symbol items according to the configuration.
Holder for the widget type and its configuration for a field.
QString type() const
Returns the widget type to use.
QVariantMap config() const
Returns the widget configuration.
A vector feature renderer which uses embedded feature symbology to render per-feature symbols.
static QList< QgsExpressionContextScope * > globalProjectLayerScopes(const QgsMapLayer *layer)
Creates a list of three scopes: global, layer's project and layer.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void appendScopes(const QList< QgsExpressionContextScope * > &scopes)
Appends a list of scopes to the end of the context.
A binary expression operator, which operates on two values.
An expression node for literal values.
QVariant value() const
The value of the literal.
Handles parsing and evaluation of expressions (formerly called "search strings").
bool prepare(const QgsExpressionContext *context)
Gets the expression ready for evaluation - find out column indexes.
static QString quotedValue(const QVariant &value)
Returns a string representation of a literal value, including appropriate quotations where required.
static QString quoteFieldExpression(const QString &expression, const QgsVectorLayer *layer)
Validate if the expression is a field in the layer and ensure it is quoted.
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.
QSet< QString > referencedColumns() const
Gets list of columns referenced by the expression.
static QString quotedColumnRef(QString name)
Returns a quoted column reference (in double quotes).
const QgsExpressionNode * rootNode() const
Returns the root node of the expression.
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
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.
static void convertSymbolRotation(QgsSymbol *symbol, const QString &field)
Converts old rotation expressions to symbol level data defined angles.
void saveRendererData(QDomDocument &doc, QDomElement &element, const QgsReadWriteContext &context)
Saves generic renderer data into the specified element.
virtual const QgsFeatureRenderer * embeddedRenderer() const
Returns the current embedded renderer (subrenderer) for this feature renderer.
virtual void startRender(QgsRenderContext &context, const QgsFields &fields)
Must be called when a new render cycle is started.
static void convertSymbolSizeScale(QgsSymbol *symbol, Qgis::ScaleMethod method, const QString &field)
Converts old sizeScale expressions to symbol level data defined sizes.
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
QgsAttributes attributes
Definition qgsfeature.h:69
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.
QgsFieldFormatter * fieldFormatter(const QString &id) const
Gets a field formatter by its id.
A field formatter helps to handle and display values for a field.
virtual QString representValue(QgsVectorLayer *layer, int fieldIndex, const QVariantMap &config, const QVariant &cache, const QVariant &value) const
Create a pretty String representation of the value.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
QMetaType::Type type
Definition qgsfield.h:63
bool isNumeric
Definition qgsfield.h:59
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition qgsfield.cpp:749
Container of fields for a vector layer.
Definition qgsfields.h:46
QgsField at(int i) const
Returns the field at particular index (must be in range 0..N-1).
Q_INVOKABLE int lookupField(const QString &fieldName) const
Looks up field's index from the field name.
A vector feature renderer which uses numeric attributes to classify features into different ranges.
QgsSymbol * sourceSymbol()
Returns the renderer's source symbol, which is the base symbol used for the each classes' symbol befo...
QgsColorRamp * sourceColorRamp()
Returns the source color ramp, from which each classes' color is derived.
QString classAttribute() const
Returns the attribute name (or expression) used for 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 ...
A marker symbol type, for rendering Point and MultiPoint geometries.
QgsProperty dataDefinedSize() const
Returns data defined size for whole symbol (including all symbol layers).
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
An abstract base class for distance based point renderers (e.g., clusterer and displacement renderers...
const QgsFeatureRenderer * embeddedRenderer() const override
Returns the current embedded renderer (subrenderer) for this feature renderer.
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.
A store for object properties.
bool isActive() const
Returns whether the property is currently active.
A color ramp consisting of random colors, constrained within component ranges.
virtual void setTotalColorCount(int colorCount)
Sets the desired total number of unique colors for the resultant ramp.
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.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
void setRenderState(bool render)
Sets whether the category is currently enabled and should be rendered.
std::unique_ptr< QgsSymbol > mSymbol
QgsSymbol * symbol() const
Returns the symbol which will be used to render this category.
void setSymbol(QgsSymbol *s)
Sets the symbol which will be used to render this category.
QString uuid() const
Returns the unique identifier for this category.
QgsRendererCategory()=default
bool renderState() const
Returns true if the category is currently enabled and should be rendered.
QString dump() const
Returns a string representing the categories settings, used for debugging purposes only.
void setLabel(const QString &label)
Sets the label for this category, which is used to represent the category within legends and the laye...
Q_DECL_DEPRECATED void toSld(QDomDocument &doc, QDomElement &element, QVariantMap props) const
Converts the category to a matching SLD rule, within the specified DOM document and element.
void setValue(const QVariant &value)
Sets the value corresponding to 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...
QgsRendererCategory & operator=(QgsRendererCategory cat)
Represents an individual rule for a rule-based renderer.
Rule based renderer.
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.
A color ramp entity for QgsStyle databases.
Definition qgsstyle.h:1491
An interface for classes which can visit style entity (e.g.
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:1462
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:91
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition qgsstyle.cpp:336
QStringList symbolNames() const
Returns a list of names of symbols.
Definition qgsstyle.cpp:358
static void sortVariantList(QList< QVariant > &list, Qt::SortOrder order)
Sorts the passed list in requested order.
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 std::unique_ptr< QgsColorRamp > loadColorRamp(QDomElement &element)
Creates a color ramp from the settings encoded in an XML element.
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 void clearSymbolLayerMasks(QgsSymbol *symbol)
Remove recursively masks from all symbol symbol layers.
static Qgis::ScaleMethod decodeScaleMethod(const QString &str)
Decodes a symbol scale method from a string.
static QDomElement saveColorRamp(const QString &name, const QgsColorRamp *ramp, QDomDocument &doc)
Encodes a color ramp's settings to an XML element.
static void clearSymbolMap(QgsSymbolMap &symbols)
static void resetSymbolLayerIds(QgsSymbol *symbol)
Regenerate recursively unique id from all symbol symbol layers.
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.
Abstract base class for all rendered symbols.
Definition qgssymbol.h:227
void setColor(const QColor &color) const
Sets the color for the symbol.
QSet< QString > usedAttributes(const QgsRenderContext &context) const
Returns a list of attributes required to render this feature.
virtual QgsSymbol * clone() const =0
Returns a deep copy of this symbol.
static QString displayString(const QVariant &variant, int precision=-1)
Returns a localized representation of value with the given precision, if precision is -1 then precisi...
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.
bool qgsVariantLessThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is less than the second.
Definition qgis.cpp:596
bool qgsVariantGreaterThan(const QVariant &lhs, const QVariant &rhs)
Compares two QVariant values and returns whether the first is greater than the second.
Definition qgis.cpp:601
bool labelGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool valueLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool valueGreaterThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
bool labelLessThan(const QgsRendererCategory &c1, const QgsRendererCategory &c2)
QList< QgsRendererCategory > QgsCategoryList
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
Contains information relating to the style entity currently being visited.