QGIS API Documentation 3.99.0-Master (26c88405ac0)
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 "qgsvectorlayer.h"
45
46#include <QDomDocument>
47#include <QDomElement>
48#include <QRegularExpression>
49#include <QSettings>
50#include <QUuid>
51
52QgsRendererCategory::QgsRendererCategory( const QVariant &value, QgsSymbol *symbol, const QString &label, bool render, const QString &uuid )
53 : mValue( value )
54 , mSymbol( symbol )
55 , mLabel( label )
56 , mRender( render )
57{
58 mUuid = !uuid.isEmpty() ? uuid : QUuid::createUuid().toString();
59}
60
62 : mValue( cat.mValue )
63 , mSymbol( cat.mSymbol ? cat.mSymbol->clone() : nullptr )
64 , mLabel( cat.mLabel )
65 , mRender( cat.mRender )
66 , mUuid( cat.mUuid )
67{
68}
69
71{
72 if ( &cat == this )
73 return *this;
74
75 mValue = cat.mValue;
76 mSymbol.reset( cat.mSymbol ? cat.mSymbol->clone() : nullptr );
77 mLabel = cat.mLabel;
78 mRender = cat.mRender;
79 mUuid = cat.mUuid;
80 return *this;
81}
82
84
86{
87 return mUuid;
88}
89
91{
92 return mValue;
93}
94
96{
97 return mSymbol.get();
98}
99
101{
102 return mLabel;
103}
104
106{
107 return mRender;
108}
109
111{
112 mValue = value;
113}
114
116{
117 if ( mSymbol.get() != s ) mSymbol.reset( s );
118}
119
121{
122 mLabel = label;
123}
124
126{
127 mRender = render;
128}
129
131{
132 return QStringLiteral( "%1::%2::%3:%4\n" ).arg( mValue.toString(), mLabel, mSymbol->dump() ).arg( mRender );
133}
134
135void QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, QVariantMap props ) const
136{
137 if ( !mSymbol.get() || props.value( QStringLiteral( "attribute" ), QString() ).toString().isEmpty() )
138 return;
139
140 QString attrName = props[ QStringLiteral( "attribute" )].toString();
141
142 QgsSldExportContext context;
143 context.setExtraProperties( props );
144 toSld( doc, element, attrName, context );
145}
146
147bool QgsRendererCategory::toSld( QDomDocument &doc, QDomElement &element, const QString &classAttribute, QgsSldExportContext &context ) const
148{
149 if ( !mSymbol.get() || classAttribute.isEmpty() )
150 return false;
151
152 QString attrName = classAttribute;
153
154 // try to determine if attribute name is actually a field reference or expression.
155 // If it's a field reference, we need to quote it.
156 // Because we don't have access to the layer or fields here, we treat a parser error
157 // as just an unquoted field name (eg a field name with spaces)
158 const QgsExpression attrExpression = QgsExpression( attrName );
159 if ( attrExpression.hasParserError() )
160 {
161 attrName = QgsExpression::quotedColumnRef( attrName );
162 }
163 else if ( attrExpression.isField() )
164 {
166 qgis::down_cast<const QgsExpressionNodeColumnRef *>( attrExpression.rootNode() )->name()
167 );
168 }
169
170 QDomElement ruleElem = doc.createElement( QStringLiteral( "se:Rule" ) );
171
172 QDomElement nameElem = doc.createElement( QStringLiteral( "se:Name" ) );
173 nameElem.appendChild( doc.createTextNode( mLabel ) );
174 ruleElem.appendChild( nameElem );
175
176 QDomElement descrElem = doc.createElement( QStringLiteral( "se:Description" ) );
177 QDomElement titleElem = doc.createElement( QStringLiteral( "se:Title" ) );
178 QString descrStr = QStringLiteral( "%1 is '%2'" ).arg( attrName, mValue.toString() );
179 titleElem.appendChild( doc.createTextNode( !mLabel.isEmpty() ? mLabel : descrStr ) );
180 descrElem.appendChild( titleElem );
181 ruleElem.appendChild( descrElem );
182
183 // create the ogc:Filter for the range
184 QString filterFunc;
185 if ( mValue.userType() == QMetaType::Type::QVariantList )
186 {
187 const QVariantList list = mValue.toList();
188 if ( list.size() == 1 )
189 {
190 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( list.at( 0 ) ) );
191 }
192 else
193 {
194 QStringList valuesList;
195 valuesList.reserve( list.size() );
196 for ( const QVariant &v : list )
197 {
198 valuesList << QgsExpression::quotedValue( v );
199 }
200 filterFunc = QStringLiteral( "%1 IN (%2)" ).arg( attrName,
201 valuesList.join( ',' ) );
202 }
203 }
204 else if ( QgsVariantUtils::isNull( mValue ) || mValue.toString().isEmpty() )
205 {
206 filterFunc = QStringLiteral( "ELSE" );
207 }
208 else
209 {
210 filterFunc = QStringLiteral( "%1 = %2" ).arg( attrName, QgsExpression::quotedValue( mValue ) );
211 }
212
213 QgsSymbolLayerUtils::createFunctionElement( doc, ruleElem, filterFunc, context );
214
215 // add the mix/max scale denoms if we got any from the callers
216 const QVariantMap oldProps = context.extraProperties();
217 QVariantMap props = oldProps;
218 QgsSymbolLayerUtils::applyScaleDependency( doc, ruleElem, props );
219 context.setExtraProperties( props );
220 mSymbol->toSld( doc, ruleElem, context );
221 context.setExtraProperties( oldProps );
222 if ( !QgsSymbolLayerUtils::hasSldSymbolizer( ruleElem ) )
223 {
224 // symbol could not be converted to SLD, or is an "empty" symbol. In this case we do not generate a rule, as
225 // SLD spec requires a Symbolizer element to be present
226 return false;
227 }
228
229 element.appendChild( ruleElem );
230 return true;
231}
232
234
236 : QgsFeatureRenderer( QStringLiteral( "categorizedSymbol" ) )
237 , mAttrName( attrName )
238{
239 //important - we need a deep copy of the categories list, not a shared copy. This is required because
240 //QgsRendererCategory::symbol() is marked const, and so retrieving the symbol via this method does not
241 //trigger a detachment and copy of mCategories BUT that same method CAN be used to modify a symbol in place
242 for ( const QgsRendererCategory &cat : categories )
243 {
244 if ( !cat.symbol() )
245 {
246 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
247 }
248 mCategories << cat;
249 }
250}
251
253{
255 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
256 for ( ; catIt != mCategories.constEnd(); ++catIt )
257 {
258 if ( QgsSymbol *catSymbol = catIt->symbol() )
259 {
260 if ( catSymbol->flags().testFlag( Qgis::SymbolFlag::AffectsLabeling ) )
262 }
263 }
264
265 return res;
266}
267
269
271{
272 mSymbolHash.clear();
273
274 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
275 {
276 const QVariant val = cat.value();
277 if ( val.userType() == QMetaType::Type::QVariantList )
278 {
279 const QVariantList list = val.toList();
280 for ( const QVariant &v : list )
281 {
282 mSymbolHash.insert( v.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
283 }
284 }
285 else
286 {
287 mSymbolHash.insert( val.toString(), ( cat.renderState() || mCounting ) ? cat.symbol() : nullptr );
288 }
289 }
290}
291
296
298{
299 bool found = false;
300 return symbolForValue( value, found );
301}
302
303QgsSymbol *QgsCategorizedSymbolRenderer::symbolForValue( const QVariant &value, bool &foundMatchingSymbol ) const
304{
305 foundMatchingSymbol = false;
306
307 // TODO: special case for int, double
308 QHash<QString, QgsSymbol *>::const_iterator it = mSymbolHash.constFind( QgsVariantUtils::isNull( value ) ? QString() : value.toString() );
309 if ( it == mSymbolHash.constEnd() )
310 {
311 if ( mSymbolHash.isEmpty() )
312 {
313 QgsDebugError( QStringLiteral( "there are no hashed symbols!!!" ) );
314 }
315 else
316 {
317 QgsDebugMsgLevel( "attribute value not found: " + value.toString(), 3 );
318 }
319 return nullptr;
320 }
321
322 foundMatchingSymbol = true;
323
324 return *it;
325}
326
328{
329 return originalSymbolForFeature( feature, context );
330}
331
332QVariant QgsCategorizedSymbolRenderer::valueForFeature( const QgsFeature &feature, QgsRenderContext &context ) const
333{
334 QgsAttributes attrs = feature.attributes();
335 QVariant value;
336 if ( mAttrNum == -1 )
337 {
338 Q_ASSERT( mExpression );
339
340 value = mExpression->evaluate( &context.expressionContext() );
341 }
342 else
343 {
344 value = attrs.value( mAttrNum );
345 }
346
347 return value;
348}
349
351{
352 QVariant value = valueForFeature( feature, context );
353
354 bool foundCategory = false;
355 // find the right symbol for the category
356 QgsSymbol *symbol = symbolForValue( value, foundCategory );
357
358 if ( !foundCategory )
359 {
360 // if no symbol found, use default symbol
361 return symbolForValue( QVariant( "" ), foundCategory );
362 }
363
364 return symbol;
365}
366
367
369{
370 for ( int i = 0; i < mCategories.count(); i++ )
371 {
372 if ( mCategories[i].value() == val )
373 return i;
374 }
375 return -1;
376}
377
379{
380 int idx = -1;
381 for ( int i = 0; i < mCategories.count(); i++ )
382 {
383 if ( mCategories[i].label() == val )
384 {
385 if ( idx != -1 )
386 return -1;
387 else
388 idx = i;
389 }
390 }
391 return idx;
392}
393
394bool QgsCategorizedSymbolRenderer::updateCategoryValue( int catIndex, const QVariant &value )
395{
396 if ( catIndex < 0 || catIndex >= mCategories.size() )
397 return false;
398 mCategories[catIndex].setValue( value );
399 return true;
400}
401
403{
404 if ( catIndex < 0 || catIndex >= mCategories.size() )
405 return false;
406 mCategories[catIndex].setSymbol( symbol );
407 return true;
408}
409
410bool QgsCategorizedSymbolRenderer::updateCategoryLabel( int catIndex, const QString &label )
411{
412 if ( catIndex < 0 || catIndex >= mCategories.size() )
413 return false;
414 mCategories[catIndex].setLabel( label );
415 return true;
416}
417
419{
420 if ( catIndex < 0 || catIndex >= mCategories.size() )
421 return false;
422 mCategories[catIndex].setRenderState( render );
423 return true;
424}
425
427{
428 if ( !cat.symbol() )
429 {
430 QgsDebugError( QStringLiteral( "invalid symbol in a category! ignoring..." ) );
431 return;
432 }
433
434 mCategories.append( cat );
435}
436
438{
439 if ( catIndex < 0 || catIndex >= mCategories.size() )
440 return false;
441
442 mCategories.removeAt( catIndex );
443 return true;
444}
445
450
452{
453 if ( from < 0 || from >= mCategories.size() || to < 0 || to >= mCategories.size() ) return;
454 mCategories.move( from, to );
455}
456
458{
459 return qgsVariantLessThan( c1.value(), c2.value() );
460}
462{
463 return qgsVariantGreaterThan( c1.value(), c2.value() );
464}
465
467{
468 if ( order == Qt::AscendingOrder )
469 {
470 std::sort( mCategories.begin(), mCategories.end(), valueLessThan );
471 }
472 else
473 {
474 std::sort( mCategories.begin(), mCategories.end(), valueGreaterThan );
475 }
476}
477
479{
480 return QString::localeAwareCompare( c1.label(), c2.label() ) < 0;
481}
482
484{
485 return QString::localeAwareCompare( c1.label(), c2.label() ) > 0;
486}
487
489{
490 if ( order == Qt::AscendingOrder )
491 {
492 std::sort( mCategories.begin(), mCategories.end(), labelLessThan );
493 }
494 else
495 {
496 std::sort( mCategories.begin(), mCategories.end(), labelGreaterThan );
497 }
498}
499
501{
502 QgsFeatureRenderer::startRender( context, fields );
503
504 mCounting = context.rendererScale() == 0.0;
505
506 // make sure that the hash table is up to date
507 rebuildHash();
508
509 // find out classification attribute index from name
510 mAttrNum = fields.lookupField( mAttrName );
511 if ( mAttrNum == -1 )
512 {
513 mExpression = std::make_unique<QgsExpression>( mAttrName );
514 mExpression->prepare( &context.expressionContext() );
515 }
516
517 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
518 {
519 cat.symbol()->startRender( context, fields );
520 }
521}
522
524{
526
527 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
528 {
529 cat.symbol()->stopRender( context );
530 }
531 mExpression.reset();
532}
533
535{
536 QSet<QString> attributes;
537
538 // mAttrName can contain either attribute name or an expression.
539 // Sometimes it is not possible to distinguish between those two,
540 // e.g. "a - b" can be both a valid attribute name or expression.
541 // Since we do not have access to fields here, try both options.
542 attributes << mAttrName;
543
544 QgsExpression testExpr( mAttrName );
545 if ( !testExpr.hasParserError() )
546 attributes.unite( testExpr.referencedColumns() );
547
548 QgsCategoryList::const_iterator catIt = mCategories.constBegin();
549 for ( ; catIt != mCategories.constEnd(); ++catIt )
550 {
551 QgsSymbol *catSymbol = catIt->symbol();
552 if ( catSymbol )
553 {
554 attributes.unite( catSymbol->usedAttributes( context ) );
555 }
556 }
557 return attributes;
558}
559
561{
562 QgsExpression testExpr( mAttrName );
563 if ( !testExpr.hasParserError() )
564 {
565 QgsExpressionContext context;
566 context.appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( nullptr ) ); // unfortunately no layer access available!
567 testExpr.prepare( &context );
568 return testExpr.needsGeometry();
569 }
570 return false;
571}
572
574{
575 QString s = QStringLiteral( "CATEGORIZED: idx %1\n" ).arg( mAttrName );
576 for ( int i = 0; i < mCategories.count(); i++ )
577 s += mCategories[i].dump();
578 return s;
579}
580
595
596void QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, const QVariantMap &props ) const
597{
598 QgsSldExportContext context;
599 context.setExtraProperties( props );
600 toSld( doc, element, context );
601}
602
603bool QgsCategorizedSymbolRenderer::toSld( QDomDocument &doc, QDomElement &element, QgsSldExportContext &context ) const
604{
605 const QVariantMap oldProps = context.extraProperties();
606 QVariantMap newProps = oldProps;
607 newProps[ QStringLiteral( "attribute" )] = mAttrName;
608 context.setExtraProperties( newProps );
609
610 // create a Rule for each range
611 bool result = true;
612 for ( QgsCategoryList::const_iterator it = mCategories.constBegin(); it != mCategories.constEnd(); ++it )
613 {
614 if ( !it->toSld( doc, element, mAttrName, context ) )
615 result = false;
616 }
617 context.setExtraProperties( oldProps );
618 return result;
619}
620
622{
623 int attrNum = fields.lookupField( mAttrName );
624 bool isExpression = ( attrNum == -1 );
625
626 bool hasDefault = false;
627 bool defaultActive = false;
628 bool allActive = true;
629 bool noneActive = true;
630
631 //we need to build lists of both inactive and active values, as either list may be required
632 //depending on whether the default category is active or not
633 QString activeValues;
634 QString inactiveValues;
635
636 for ( const QgsRendererCategory &cat : std::as_const( mCategories ) )
637 {
638 if ( cat.value() == "" || QgsVariantUtils::isNull( cat.value() ) )
639 {
640 hasDefault = true;
641 defaultActive = cat.renderState();
642 }
643
644 noneActive = noneActive && !cat.renderState();
645 allActive = allActive && cat.renderState();
646
647 const bool isList = cat.value().userType() == QMetaType::Type::QVariantList;
648 QString value = QgsExpression::quotedValue( cat.value(), static_cast<QMetaType::Type>( cat.value().userType() ) );
649
650 if ( !cat.renderState() )
651 {
652 if ( value != "" )
653 {
654 if ( isList )
655 {
656 const QVariantList list = cat.value().toList();
657 for ( const QVariant &v : list )
658 {
659 if ( !inactiveValues.isEmpty() )
660 inactiveValues.append( ',' );
661
662 inactiveValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
663 }
664 }
665 else
666 {
667 if ( !inactiveValues.isEmpty() )
668 inactiveValues.append( ',' );
669
670 inactiveValues.append( value );
671 }
672 }
673 }
674 else
675 {
676 if ( value != "" )
677 {
678 if ( isList )
679 {
680 const QVariantList list = cat.value().toList();
681 for ( const QVariant &v : list )
682 {
683 if ( !activeValues.isEmpty() )
684 activeValues.append( ',' );
685
686 activeValues.append( QgsExpression::quotedValue( v, isExpression ? static_cast<QMetaType::Type>( v.userType() ) : fields.at( attrNum ).type() ) );
687 }
688 }
689 else
690 {
691 if ( !activeValues.isEmpty() )
692 activeValues.append( ',' );
693
694 activeValues.append( value );
695 }
696 }
697 }
698 }
699
700 QString attr = isExpression ? mAttrName : QStringLiteral( "\"%1\"" ).arg( mAttrName );
701
702 if ( allActive && hasDefault )
703 {
704 return QString();
705 }
706 else if ( noneActive )
707 {
708 return QStringLiteral( "FALSE" );
709 }
710 else if ( defaultActive )
711 {
712 return QStringLiteral( "(%1) NOT IN (%2) OR (%1) IS NULL" ).arg( attr, inactiveValues );
713 }
714 else
715 {
716 return QStringLiteral( "(%1) IN (%2)" ).arg( attr, activeValues );
717 }
718}
719
721{
722 Q_UNUSED( context )
723 QgsSymbolList lst;
724 lst.reserve( mCategories.count() );
725 for ( const QgsRendererCategory &cat : mCategories )
726 {
727 lst.append( cat.symbol() );
728 }
729 return lst;
730}
731
733{
734 for ( const QgsRendererCategory &cat : mCategories )
735 {
736 QgsStyleSymbolEntity entity( cat.symbol() );
737 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity, cat.value().toString(), cat.label() ) ) )
738 return false;
739 }
740
741 if ( mSourceColorRamp )
742 {
744 if ( !visitor->visit( QgsStyleEntityVisitorInterface::StyleLeaf( &entity ) ) )
745 return false;
746 }
747
748 return true;
749}
750
752{
753 QDomElement symbolsElem = element.firstChildElement( QStringLiteral( "symbols" ) );
754 if ( symbolsElem.isNull() )
755 return nullptr;
756
757 QDomElement catsElem = element.firstChildElement( QStringLiteral( "categories" ) );
758 if ( catsElem.isNull() )
759 return nullptr;
760
761 QgsSymbolMap symbolMap = QgsSymbolLayerUtils::loadSymbols( symbolsElem, context );
762 QgsCategoryList cats;
763
764 // Value from string (long, ulong, double and string)
765 const auto valueFromString = []( const QString & value, const QString & valueType ) -> QVariant
766 {
767 if ( valueType == QLatin1String( "double" ) )
768 {
769 bool ok;
770 const auto val { value.toDouble( &ok ) };
771 if ( ok )
772 {
773 return val;
774 }
775 }
776 else if ( valueType == QLatin1String( "ulong" ) )
777 {
778 bool ok;
779 const auto val { value.toULongLong( &ok ) };
780 if ( ok )
781 {
782 return val;
783 }
784 }
785 else if ( valueType == QLatin1String( "long" ) )
786 {
787 bool ok;
788 const auto val { value.toLongLong( &ok ) };
789 if ( ok )
790 {
791 return val;
792 }
793 }
794 else if ( valueType == QLatin1String( "bool" ) )
795 {
796 if ( value.toLower() == QLatin1String( "false" ) )
797 return false;
798 if ( value.toLower() == QLatin1String( "true" ) )
799 return true;
800 }
801 else if ( valueType == QLatin1String( "NULL" ) )
802 {
803 // This is the default ("fallback") category
804 return QVariant();
805 }
806 return value;
807 };
808
809 QDomElement catElem = catsElem.firstChildElement();
810 int i = 0;
811 QSet<QString> usedUuids;
812 while ( !catElem.isNull() )
813 {
814 if ( catElem.tagName() == QLatin1String( "category" ) )
815 {
816 QVariant value;
817 if ( catElem.hasAttribute( QStringLiteral( "value" ) ) )
818 {
819 value = valueFromString( catElem.attribute( QStringLiteral( "value" ) ), catElem.attribute( QStringLiteral( "type" ), QString() ) ) ;
820 }
821 else
822 {
823 QVariantList values;
824 QDomElement valElem = catElem.firstChildElement();
825 while ( !valElem.isNull() )
826 {
827 if ( valElem.tagName() == QLatin1String( "val" ) )
828 {
829 values << valueFromString( valElem.attribute( QStringLiteral( "value" ) ), valElem.attribute( QStringLiteral( "type" ), QString() ) );
830 }
831 valElem = valElem.nextSiblingElement();
832 }
833 if ( !values.isEmpty() )
834 value = values;
835 }
836 QString symbolName = catElem.attribute( QStringLiteral( "symbol" ) );
837 QString label = catElem.attribute( QStringLiteral( "label" ) );
838 bool render = catElem.attribute( QStringLiteral( "render" ) ) != QLatin1String( "false" );
839 QString uuid = catElem.attribute( QStringLiteral( "uuid" ), QString::number( i++ ) );
840 while ( usedUuids.contains( uuid ) )
841 {
842 uuid = QUuid::createUuid().toString();
843 }
844 if ( symbolMap.contains( symbolName ) )
845 {
846 QgsSymbol *symbol = symbolMap.take( symbolName );
847 cats.append( QgsRendererCategory( value, symbol, label, render, uuid ) );
848 usedUuids << uuid;
849 }
850 }
851 catElem = catElem.nextSiblingElement();
852 }
853
854 QString attrName = element.attribute( QStringLiteral( "attr" ) );
855
857
858 // delete symbols if there are any more
860
861 // try to load source symbol (optional)
862 QDomElement sourceSymbolElem = element.firstChildElement( QStringLiteral( "source-symbol" ) );
863 if ( !sourceSymbolElem.isNull() )
864 {
865 QgsSymbolMap sourceSymbolMap = QgsSymbolLayerUtils::loadSymbols( sourceSymbolElem, context );
866 if ( sourceSymbolMap.contains( QStringLiteral( "0" ) ) )
867 {
868 r->setSourceSymbol( sourceSymbolMap.take( QStringLiteral( "0" ) ) );
869 }
870 QgsSymbolLayerUtils::clearSymbolMap( sourceSymbolMap );
871 }
872
873 // try to load color ramp (optional)
874 QDomElement sourceColorRampElem = element.firstChildElement( QStringLiteral( "colorramp" ) );
875 if ( !sourceColorRampElem.isNull() && sourceColorRampElem.attribute( QStringLiteral( "name" ) ) == QLatin1String( "[source]" ) )
876 {
877 r->setSourceColorRamp( QgsSymbolLayerUtils::loadColorRamp( sourceColorRampElem ).release() );
878 }
879
880 QDomElement rotationElem = element.firstChildElement( QStringLiteral( "rotation" ) );
881 if ( !rotationElem.isNull() && !rotationElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
882 {
883 for ( const QgsRendererCategory &cat : r->mCategories )
884 {
885 convertSymbolRotation( cat.symbol(), rotationElem.attribute( QStringLiteral( "field" ) ) );
886 }
887 if ( r->mSourceSymbol )
888 {
889 convertSymbolRotation( r->mSourceSymbol.get(), rotationElem.attribute( QStringLiteral( "field" ) ) );
890 }
891 }
892
893 QDomElement sizeScaleElem = element.firstChildElement( QStringLiteral( "sizescale" ) );
894 if ( !sizeScaleElem.isNull() && !sizeScaleElem.attribute( QStringLiteral( "field" ) ).isEmpty() )
895 {
896 for ( const QgsRendererCategory &cat : r->mCategories )
897 {
899 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
900 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
901 }
902 if ( r->mSourceSymbol && r->mSourceSymbol->type() == Qgis::SymbolType::Marker )
903 {
905 QgsSymbolLayerUtils::decodeScaleMethod( sizeScaleElem.attribute( QStringLiteral( "scalemethod" ) ) ),
906 sizeScaleElem.attribute( QStringLiteral( "field" ) ) );
907 }
908 }
909
910 QDomElement ddsLegendSizeElem = element.firstChildElement( QStringLiteral( "data-defined-size-legend" ) );
911 if ( !ddsLegendSizeElem.isNull() )
912 {
913 r->mDataDefinedSizeLegend.reset( QgsDataDefinedSizeLegend::readXml( ddsLegendSizeElem, context ) );
914 }
915
916 // TODO: symbol levels
917 return r;
918}
919
920QDomElement QgsCategorizedSymbolRenderer::save( QDomDocument &doc, const QgsReadWriteContext &context )
921{
922 // clazy:skip
923 QDomElement rendererElem = doc.createElement( RENDERER_TAG_NAME );
924 rendererElem.setAttribute( QStringLiteral( "type" ), QStringLiteral( "categorizedSymbol" ) );
925 rendererElem.setAttribute( QStringLiteral( "attr" ), mAttrName );
926
927 // String for type
928 // We just need string, bool, and three numeric types: double, ulong and long for unsigned, signed and float/double
929 const auto stringForType = []( const QMetaType::Type type ) -> QString
930 {
931 if ( type == QMetaType::Type::QChar || type == QMetaType::Type::Int || type == QMetaType::Type::LongLong )
932 {
933 return QStringLiteral( "long" );
934 }
935 else if ( type == QMetaType::Type::UInt || type == QMetaType::Type::ULongLong )
936 {
937 return QStringLiteral( "ulong" );
938 }
939 else if ( type == QMetaType::Type::Double )
940 {
941 return QStringLiteral( "double" ) ;
942 }
943 else if ( type == QMetaType::Type::Bool )
944 {
945 return QStringLiteral( "bool" );
946 }
947 else // Default: string
948 {
949 return QStringLiteral( "string" );
950 }
951 };
952
953 // categories
954 if ( !mCategories.isEmpty() )
955 {
956 int i = 0;
958 QDomElement catsElem = doc.createElement( QStringLiteral( "categories" ) );
959 QgsCategoryList::const_iterator it = mCategories.constBegin();
960 for ( ; it != mCategories.constEnd(); ++it )
961 {
962 const QgsRendererCategory &cat = *it;
963 QString symbolName = QString::number( i );
964 symbols.insert( symbolName, cat.symbol() );
965
966 QDomElement catElem = doc.createElement( QStringLiteral( "category" ) );
967 if ( cat.value().userType() == QMetaType::Type::QVariantList )
968 {
969 const QVariantList list = cat.value().toList();
970 for ( const QVariant &v : list )
971 {
972 QDomElement valueElem = doc.createElement( QStringLiteral( "val" ) );
973 valueElem.setAttribute( QStringLiteral( "value" ), v.toString() );
974 valueElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( v.userType() ) ) );
975 catElem.appendChild( valueElem );
976 }
977 }
978 else
979 {
980 if ( QgsVariantUtils::isNull( cat.value() ) )
981 {
982 // We need to save NULL value as specific kind, it is the default ("fallback") category
983 catElem.setAttribute( QStringLiteral( "value" ), "NULL" );
984 catElem.setAttribute( QStringLiteral( "type" ), "NULL" );
985 }
986 else
987 {
988 catElem.setAttribute( QStringLiteral( "value" ), cat.value().toString() );
989 catElem.setAttribute( QStringLiteral( "type" ), stringForType( static_cast<QMetaType::Type>( cat.value().userType() ) ) );
990 }
991 }
992 catElem.setAttribute( QStringLiteral( "symbol" ), symbolName );
993 catElem.setAttribute( QStringLiteral( "label" ), cat.label() );
994 catElem.setAttribute( QStringLiteral( "render" ), cat.renderState() ? "true" : "false" );
995 catElem.setAttribute( QStringLiteral( "uuid" ), cat.uuid() );
996 catsElem.appendChild( catElem );
997 i++;
998 }
999 rendererElem.appendChild( catsElem );
1000
1001 // save symbols
1002 QDomElement symbolsElem = QgsSymbolLayerUtils::saveSymbols( symbols, QStringLiteral( "symbols" ), doc, context );
1003 rendererElem.appendChild( symbolsElem );
1004 }
1005
1006 // save source symbol
1007 if ( mSourceSymbol )
1008 {
1009 QgsSymbolMap sourceSymbols;
1010 sourceSymbols.insert( QStringLiteral( "0" ), mSourceSymbol.get() );
1011 QDomElement sourceSymbolElem = QgsSymbolLayerUtils::saveSymbols( sourceSymbols, QStringLiteral( "source-symbol" ), doc, context );
1012 rendererElem.appendChild( sourceSymbolElem );
1013 }
1014
1015 // save source color ramp
1016 if ( mSourceColorRamp )
1017 {
1018 QDomElement colorRampElem = QgsSymbolLayerUtils::saveColorRamp( QStringLiteral( "[source]" ), mSourceColorRamp.get(), doc );
1019 rendererElem.appendChild( colorRampElem );
1020 }
1021
1022 QDomElement rotationElem = doc.createElement( QStringLiteral( "rotation" ) );
1023 rendererElem.appendChild( rotationElem );
1024
1025 QDomElement sizeScaleElem = doc.createElement( QStringLiteral( "sizescale" ) );
1026 rendererElem.appendChild( sizeScaleElem );
1027
1029 {
1030 QDomElement ddsLegendElem = doc.createElement( QStringLiteral( "data-defined-size-legend" ) );
1031 mDataDefinedSizeLegend->writeXml( ddsLegendElem, context );
1032 rendererElem.appendChild( ddsLegendElem );
1033 }
1034
1035 saveRendererData( doc, rendererElem, context );
1036
1037 return rendererElem;
1038}
1039
1040
1041QgsLegendSymbolList QgsCategorizedSymbolRenderer::baseLegendSymbolItems() const
1042{
1044 for ( const QgsRendererCategory &cat : mCategories )
1045 {
1046 lst << QgsLegendSymbolItem( cat.symbol(), cat.label(), cat.uuid(), true );
1047 }
1048 return lst;
1049}
1050
1051QString QgsCategorizedSymbolRenderer::displayString( const QVariant &v, int precision )
1052{
1053
1054 auto _displayString = [ ]( const QVariant & v, int precision ) -> QString
1055 {
1056
1057 if ( QgsVariantUtils::isNull( v ) )
1058 {
1060 }
1061
1062 const bool isNumeric {v.userType() == QMetaType::Type::Double || v.userType() == QMetaType::Type::Int || v.userType() == QMetaType::Type::UInt || v.userType() == QMetaType::Type::LongLong || v.userType() == QMetaType::Type::ULongLong};
1063
1064 // Special treatment for numeric types if group separator is set or decimalPoint is not a dot
1065 if ( v.userType() == QMetaType::Type::Double )
1066 {
1067 // if value doesn't contain a double (a default value expression for instance),
1068 // apply no transformation
1069 bool ok;
1070 v.toDouble( &ok );
1071 if ( !ok )
1072 return v.toString();
1073
1074 // Locales with decimal point != '.' or that require group separator: use QLocale
1075 if ( QLocale().decimalPoint() != '.' ||
1076 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1077 {
1078 if ( precision > 0 )
1079 {
1080 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1081 {
1082 return QLocale().toString( v.toDouble(), 'g', precision );
1083 }
1084 else
1085 {
1086 return QLocale().toString( v.toDouble(), 'f', precision );
1087 }
1088 }
1089 else
1090 {
1091 // Precision is not set, let's guess it from the
1092 // standard conversion to string
1093 const QString s( v.toString() );
1094 const int dotPosition( s.indexOf( '.' ) );
1095 int precision;
1096 if ( dotPosition < 0 && s.indexOf( 'e' ) < 0 )
1097 {
1098 precision = 0;
1099 return QLocale().toString( v.toDouble(), 'f', precision );
1100 }
1101 else
1102 {
1103 if ( dotPosition < 0 ) precision = 0;
1104 else precision = s.length() - dotPosition - 1;
1105
1106 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1107 {
1108 return QLocale().toString( v.toDouble(), 'g', precision );
1109 }
1110 else
1111 {
1112 return QLocale().toString( v.toDouble(), 'f', precision );
1113 }
1114 }
1115 }
1116 }
1117 // Default for doubles with precision
1118 else if ( precision > 0 )
1119 {
1120 if ( -1 < v.toDouble() && v.toDouble() < 1 )
1121 {
1122 return QString::number( v.toDouble(), 'g', precision );
1123 }
1124 else
1125 {
1126 return QString::number( v.toDouble(), 'f', precision );
1127 }
1128 }
1129 }
1130 // Other numeric types than doubles
1131 else if ( isNumeric &&
1132 !( QLocale().numberOptions() & QLocale::NumberOption::OmitGroupSeparator ) )
1133 {
1134 bool ok;
1135 const qlonglong converted( v.toLongLong( &ok ) );
1136 if ( ok )
1137 return QLocale().toString( converted );
1138 }
1139 else if ( v.userType() == QMetaType::Type::QByteArray )
1140 {
1141 return QObject::tr( "BLOB" );
1142 }
1143
1144 // Fallback if special rules do not apply
1145 return v.toString();
1146 };
1147
1148 if ( v.userType() == QMetaType::Type::QStringList || v.userType() == QMetaType::Type::QVariantList )
1149 {
1150 // Note that this code is never hit because the joining of lists (merged categories) happens
1151 // in data(); I'm leaving this here anyway because it is tested and it may be useful for
1152 // other purposes in the future.
1153 QString result;
1154 const QVariantList list = v.toList();
1155 for ( const QVariant &var : list )
1156 {
1157 if ( !result.isEmpty() )
1158 {
1159 result.append( ';' );
1160 }
1161 result.append( _displayString( var, precision ) );
1162 }
1163 return result;
1164 }
1165 else
1166 {
1167 return _displayString( v, precision );
1168 }
1169}
1170
1172{
1174 {
1175 // check that all symbols that have the same size expression
1176 QgsProperty ddSize;
1177 for ( const QgsRendererCategory &category : mCategories )
1178 {
1179 const QgsMarkerSymbol *symbol = static_cast<const QgsMarkerSymbol *>( category.symbol() );
1180 if ( ddSize )
1181 {
1182 QgsProperty sSize( symbol->dataDefinedSize() );
1183 if ( sSize != ddSize )
1184 {
1185 // no common size expression
1186 return baseLegendSymbolItems();
1187 }
1188 }
1189 else
1190 {
1191 ddSize = symbol->dataDefinedSize();
1192 }
1193 }
1194
1195 if ( ddSize && ddSize.isActive() )
1196 {
1198
1200 ddSizeLegend.updateFromSymbolAndProperty( static_cast<const QgsMarkerSymbol *>( mSourceSymbol.get() ), ddSize );
1201 lst += ddSizeLegend.legendSymbolList();
1202
1203 lst += baseLegendSymbolItems();
1204 return lst;
1205 }
1206 }
1207
1208 return baseLegendSymbolItems();
1209}
1210
1212{
1213 const QVariant value = valueForFeature( feature, context );
1214
1215 for ( const QgsRendererCategory &cat : mCategories )
1216 {
1217 bool match = false;
1218 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1219 {
1220 const QVariantList list = cat.value().toList();
1221 for ( const QVariant &v : list )
1222 {
1223 if ( value == v )
1224 {
1225 match = true;
1226 break;
1227 }
1228 }
1229 }
1230 else
1231 {
1232 // NULL cat value may be stored as an empty string or an invalid variant, depending on how
1233 // the renderer was constructed and which QGIS version was used
1234 if ( QgsVariantUtils::isNull( value ) )
1235 {
1236 match = cat.value().toString().isEmpty() || QgsVariantUtils::isNull( cat.value() );
1237 }
1238 else
1239 {
1240 match = value == cat.value();
1241 }
1242 }
1243
1244 if ( match )
1245 {
1246 if ( cat.renderState() || mCounting )
1247 return QSet< QString >() << cat.uuid();
1248 else
1249 return QSet< QString >();
1250 }
1251 }
1252
1253 return QSet< QString >();
1254}
1255
1256QString QgsCategorizedSymbolRenderer::legendKeyToExpression( const QString &key, QgsVectorLayer *layer, bool &ok ) const
1257{
1258 ok = false;
1259 int i = 0;
1260 for ( i = 0; i < mCategories.size(); i++ )
1261 {
1262 if ( mCategories[i].uuid() == key )
1263 {
1264 ok = true;
1265 break;
1266 }
1267 }
1268
1269 if ( !ok )
1270 {
1271 ok = false;
1272 return QString();
1273 }
1274
1275 const int fieldIndex = layer ? layer->fields().lookupField( mAttrName ) : -1;
1276 const bool isNumeric = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).isNumeric() : false;
1277 const QMetaType::Type fieldType = layer && fieldIndex >= 0 ? layer->fields().at( fieldIndex ).type() : QMetaType::Type::UnknownType;
1278 const QString attributeComponent = QgsExpression::quoteFieldExpression( mAttrName, layer );
1279
1280 ok = true;
1281 const QgsRendererCategory &cat = mCategories[i];
1282 if ( cat.value().userType() == QMetaType::Type::QVariantList )
1283 {
1284 const QVariantList list = cat.value().toList();
1285 QStringList parts;
1286 parts.reserve( list.size() );
1287 for ( const QVariant &v : list )
1288 {
1289 parts.append( QgsExpression::quotedValue( v ) );
1290 }
1291
1292 return QStringLiteral( "%1 IN (%2)" ).arg( attributeComponent, parts.join( QLatin1String( ", " ) ) );
1293 }
1294 else
1295 {
1296 // Numeric NULL cat value is stored as an empty string
1297 QVariant value = cat.value();
1298 if ( isNumeric && value.toString().isEmpty() )
1299 {
1300 value = QVariant();
1301 }
1302
1303 if ( QgsVariantUtils::isNull( value ) )
1304 return QStringLiteral( "%1 IS NULL" ).arg( attributeComponent );
1305 else if ( fieldType == QMetaType::Type::UnknownType )
1306 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value ) );
1307 else
1308 return QStringLiteral( "%1 = %2" ).arg( attributeComponent, QgsExpression::quotedValue( value, fieldType ) );
1309 }
1310}
1311
1316
1318{
1319 return mSourceSymbol.get();
1320}
1321
1326
1331
1336
1341
1343{
1344 setSourceColorRamp( ramp );
1345 double num = mCategories.count() - 1;
1346 double count = 0;
1347
1348 QgsRandomColorRamp *randomRamp = dynamic_cast<QgsRandomColorRamp *>( ramp );
1349 if ( randomRamp )
1350 {
1351 //ramp is a random colors ramp, so inform it of the total number of required colors
1352 //this allows the ramp to pregenerate a set of visually distinctive colors
1353 randomRamp->setTotalColorCount( mCategories.count() );
1354 }
1355
1356 for ( const QgsRendererCategory &cat : mCategories )
1357 {
1358 double value = count / num;
1359 cat.symbol()->setColor( mSourceColorRamp->color( value ) );
1360 count += 1;
1361 }
1362}
1363
1365{
1366 int i = 0;
1367 for ( const QgsRendererCategory &cat : mCategories )
1368 {
1369 QgsSymbol *symbol = sym->clone();
1370 symbol->setColor( cat.symbol()->color() );
1371 updateCategorySymbol( i, symbol );
1372 ++i;
1373 }
1374 setSourceSymbol( sym->clone() );
1375}
1376
1378{
1379 return true;
1380}
1381
1383{
1384 for ( const QgsRendererCategory &category : std::as_const( mCategories ) )
1385 {
1386 if ( category.uuid() == key )
1387 {
1388 return category.renderState();
1389 }
1390 }
1391
1392 return true;
1393}
1394
1396{
1397 bool ok = false;
1398 int i = 0;
1399 for ( i = 0; i < mCategories.size(); i++ )
1400 {
1401 if ( mCategories[i].uuid() == key )
1402 {
1403 ok = true;
1404 break;
1405 }
1406 }
1407
1408 if ( ok )
1409 updateCategorySymbol( i, symbol );
1410 else
1411 delete symbol;
1412}
1413
1414void QgsCategorizedSymbolRenderer::checkLegendSymbolItem( const QString &key, bool state )
1415{
1416 for ( int i = 0; i < mCategories.size(); i++ )
1417 {
1418 if ( mCategories[i].uuid() == key )
1419 {
1420 updateCategoryRenderState( i, state );
1421 break;
1422 }
1423 }
1424}
1425
1427{
1428 std::unique_ptr< QgsCategorizedSymbolRenderer > r;
1429 if ( renderer->type() == QLatin1String( "categorizedSymbol" ) )
1430 {
1431 r.reset( static_cast<QgsCategorizedSymbolRenderer *>( renderer->clone() ) );
1432 }
1433 else if ( renderer->type() == QLatin1String( "graduatedSymbol" ) )
1434 {
1435 const QgsGraduatedSymbolRenderer *graduatedSymbolRenderer = dynamic_cast<const QgsGraduatedSymbolRenderer *>( renderer );
1436 if ( graduatedSymbolRenderer )
1437 {
1438 r = std::make_unique<QgsCategorizedSymbolRenderer>( QString(), QgsCategoryList() );
1439 if ( graduatedSymbolRenderer->sourceSymbol() )
1440 r->setSourceSymbol( graduatedSymbolRenderer->sourceSymbol()->clone() );
1441 if ( graduatedSymbolRenderer->sourceColorRamp() )
1442 {
1443 r->setSourceColorRamp( graduatedSymbolRenderer->sourceColorRamp()->clone() );
1444 }
1445 r->setClassAttribute( graduatedSymbolRenderer->classAttribute() );
1446 }
1447 }
1448 else if ( renderer->type() == QLatin1String( "RuleRenderer" ) )
1449 {
1450 const QgsRuleBasedRenderer *ruleBasedSymbolRenderer = dynamic_cast<const QgsRuleBasedRenderer *>( renderer );
1451 if ( ruleBasedSymbolRenderer )
1452 {
1453 r = std::make_unique<QgsCategorizedSymbolRenderer>( QString(), QgsCategoryList() );
1454
1455 const QList< QgsRuleBasedRenderer::Rule * > rules = const_cast< QgsRuleBasedRenderer * >( ruleBasedSymbolRenderer )->rootRule()->children();
1456 bool canConvert = true;
1457
1458 bool isFirst = true;
1459 QString attribute;
1460 QVariant value;
1462
1463 for ( QgsRuleBasedRenderer::Rule *rule : rules )
1464 {
1465 if ( rule->isElse() || rule->minimumScale() != 0 || rule->maximumScale() != 0 || !rule->symbol() || !rule->children().isEmpty() )
1466 {
1467 canConvert = false;
1468 break;
1469 }
1470
1471 QgsExpression e( rule->filterExpression() );
1472
1473 if ( !e.rootNode() )
1474 {
1475 canConvert = false;
1476 break;
1477 }
1478
1479 if ( const QgsExpressionNodeBinaryOperator *binOp = dynamic_cast<const QgsExpressionNodeBinaryOperator *>( e.rootNode() ) )
1480 {
1481 if ( binOp->op() == QgsExpressionNodeBinaryOperator::boEQ )
1482 {
1483 const QString left = binOp->opLeft()->dump();
1484 if ( !isFirst && left != attribute )
1485 {
1486 canConvert = false;
1487 break;
1488 }
1489 else if ( isFirst )
1490 {
1491 attribute = left;
1492 }
1493
1494 const QgsExpressionNodeLiteral *literal = dynamic_cast<const QgsExpressionNodeLiteral *>( binOp->opRight() );
1495 if ( literal )
1496 {
1498 cat.setValue( literal->value() );
1499 cat.setSymbol( rule->symbol()->clone() );
1500 cat.setLabel( rule->label().isEmpty() ? literal->value().toString() : rule->label() );
1501 cat.setRenderState( rule->active() );
1502 categories.append( cat );
1503 }
1504 else
1505 {
1506 canConvert = false;
1507 break;
1508 }
1509 }
1510 else
1511 {
1512 canConvert = false;
1513 }
1514 }
1515 else
1516 {
1517 canConvert = false;
1518 break;
1519 }
1520
1521 isFirst = false;
1522 }
1523
1524 if ( canConvert )
1525 {
1526 r = std::make_unique< QgsCategorizedSymbolRenderer >( attribute, categories );
1527 }
1528 else
1529 {
1530 r.reset();
1531 }
1532 }
1533 }
1534 else if ( renderer->type() == QLatin1String( "pointDisplacement" ) || renderer->type() == QLatin1String( "pointCluster" ) )
1535 {
1536 const QgsPointDistanceRenderer *pointDistanceRenderer = dynamic_cast<const QgsPointDistanceRenderer *>( renderer );
1537 if ( pointDistanceRenderer )
1538 r.reset( convertFromRenderer( pointDistanceRenderer->embeddedRenderer() ) );
1539 }
1540 else if ( renderer->type() == QLatin1String( "invertedPolygonRenderer" ) )
1541 {
1542 const QgsInvertedPolygonRenderer *invertedPolygonRenderer = dynamic_cast<const QgsInvertedPolygonRenderer *>( renderer );
1543 if ( invertedPolygonRenderer )
1544 r.reset( convertFromRenderer( invertedPolygonRenderer->embeddedRenderer() ) );
1545 }
1546 else if ( renderer->type() == QLatin1String( "embeddedSymbol" ) && layer )
1547 {
1548 const QgsEmbeddedSymbolRenderer *embeddedRenderer = dynamic_cast<const QgsEmbeddedSymbolRenderer *>( renderer );
1552 req.setNoAttributes();
1553 QgsFeatureIterator it = layer->getFeatures( req );
1554 QgsFeature feature;
1555 while ( it.nextFeature( feature ) && categories.size() < 2000 )
1556 {
1557 if ( feature.embeddedSymbol() )
1558 categories.append( QgsRendererCategory( feature.id(), feature.embeddedSymbol()->clone(), QString::number( feature.id() ) ) );
1559 }
1560 categories.append( QgsRendererCategory( QVariant(), embeddedRenderer->defaultSymbol()->clone(), QString() ) );
1561 r = std::make_unique<QgsCategorizedSymbolRenderer>( QStringLiteral( "$id" ), categories );
1562 }
1563
1564 // If not one of the specifically handled renderers, then just grab the symbol from the renderer
1565 // Could have applied this to specific renderer types (singleSymbol, graduatedSymbol)
1566
1567 if ( !r )
1568 {
1569 r = std::make_unique< QgsCategorizedSymbolRenderer >( QString(), QgsCategoryList() );
1570 QgsRenderContext context;
1571 QgsSymbolList symbols = const_cast<QgsFeatureRenderer *>( renderer )->symbols( context );
1572 if ( !symbols.isEmpty() )
1573 {
1574 QgsSymbol *newSymbol = symbols.at( 0 )->clone();
1577 r->setSourceSymbol( newSymbol );
1578 }
1579 }
1580
1581 renderer->copyRendererData( r.get() );
1582
1583 return r.release();
1584}
1585
1590
1595
1596int QgsCategorizedSymbolRenderer::matchToSymbols( QgsStyle *style, Qgis::SymbolType type, QVariantList &unmatchedCategories, QStringList &unmatchedSymbols, const bool caseSensitive, const bool useTolerantMatch )
1597{
1598 if ( !style )
1599 return 0;
1600
1601 int matched = 0;
1602 unmatchedSymbols = style->symbolNames();
1603 const QSet< QString > allSymbolNames( unmatchedSymbols.begin(), unmatchedSymbols.end() );
1604
1605 const thread_local QRegularExpression tolerantMatchRe( QStringLiteral( "[^\\w\\d ]" ), QRegularExpression::UseUnicodePropertiesOption );
1606
1607 for ( int catIdx = 0; catIdx < mCategories.count(); ++catIdx )
1608 {
1609 const QVariant value = mCategories.at( catIdx ).value();
1610 const QString val = value.toString().trimmed();
1611 std::unique_ptr< QgsSymbol > symbol( style->symbol( val ) );
1612 // case-sensitive match
1613 if ( symbol && symbol->type() == type )
1614 {
1615 matched++;
1616 unmatchedSymbols.removeAll( val );
1617 updateCategorySymbol( catIdx, symbol.release() );
1618 continue;
1619 }
1620
1621 if ( !caseSensitive || useTolerantMatch )
1622 {
1623 QString testVal = val;
1624 if ( useTolerantMatch )
1625 testVal.replace( tolerantMatchRe, QString() );
1626
1627 bool foundMatch = false;
1628 for ( const QString &name : allSymbolNames )
1629 {
1630 QString testName = name.trimmed();
1631 if ( useTolerantMatch )
1632 testName.replace( tolerantMatchRe, QString() );
1633
1634 if ( testName == testVal || ( !caseSensitive && testName.trimmed().compare( testVal, Qt::CaseInsensitive ) == 0 ) )
1635 {
1636 // found a case-insensitive match
1637 std::unique_ptr< QgsSymbol > symbol( style->symbol( name ) );
1638 if ( symbol && symbol->type() == type )
1639 {
1640 matched++;
1641 unmatchedSymbols.removeAll( name );
1642 updateCategorySymbol( catIdx, symbol.release() );
1643 foundMatch = true;
1644 break;
1645 }
1646 }
1647 }
1648 if ( foundMatch )
1649 continue;
1650 }
1651
1652 unmatchedCategories << value;
1653 }
1654
1655 return matched;
1656}
1657
1658QgsCategoryList QgsCategorizedSymbolRenderer::createCategories( const QList<QVariant> &values, const QgsSymbol *symbol, QgsVectorLayer *layer, const QString &attributeName )
1659{
1660 QgsCategoryList cats;
1661 QVariantList vals = values;
1662 // sort the categories first
1663 QgsSymbolLayerUtils::sortVariantList( vals, Qt::AscendingOrder );
1664
1665 if ( layer && !attributeName.isNull() )
1666 {
1667 const QgsFields fields = layer->fields();
1668 for ( const QVariant &value : vals )
1669 {
1670 QgsSymbol *newSymbol = symbol->clone();
1672 if ( !QgsVariantUtils::isNull( value ) )
1673 {
1674 const int fieldIdx = fields.lookupField( attributeName );
1675 QString categoryName = displayString( value );
1676 if ( fieldIdx != -1 )
1677 {
1678 const QgsField field = fields.at( fieldIdx );
1679 const QgsEditorWidgetSetup setup = field.editorWidgetSetup();
1681 categoryName = formatter->representValue( layer, fieldIdx, setup.config(), QVariant(), value );
1682 }
1683 cats.append( QgsRendererCategory( value, newSymbol, categoryName, true ) );
1684 }
1685 }
1686 }
1687
1688 // add null (default) value
1689 QgsSymbol *newSymbol = symbol->clone();
1691 cats.append( QgsRendererCategory( QVariant(), newSymbol, QString(), true ) );
1692
1693 return cats;
1694}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
@ EmbeddedSymbols
Retrieve any embedded feature symbology.
Definition qgis.h:2200
QFlags< FeatureRendererFlag > FeatureRendererFlags
Flags controlling behavior of vector feature renderers.
Definition qgis.h:838
@ AffectsLabeling
If present, indicates that the renderer will participate in the map labeling problem.
Definition qgis.h:829
SymbolType
Symbol types.
Definition qgis.h:610
@ Marker
Marker symbol.
Definition qgis.h:611
@ AffectsLabeling
If present, indicates that the symbol will participate in the map labeling problem.
Definition qgis.h:849
static QString nullRepresentation()
Returns the string used to represent the value NULL throughout QGIS.
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 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 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:58
QgsAttributes attributes
Definition qgsfeature.h:67
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.
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:54
QMetaType::Type type
Definition qgsfield.h:61
bool isNumeric
Definition qgsfield.h:57
QgsEditorWidgetSetup editorWidgetSetup() const
Gets the editor widget setup for the field.
Definition qgsfield.cpp:747
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.
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.
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:1429
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:1397
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:88
QgsSymbol * symbol(const QString &name)
Returns a NEW copy of symbol.
Definition qgsstyle.cpp:320
QStringList symbolNames() const
Returns a list of names of symbols.
Definition qgsstyle.cpp:342
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:231
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 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:588
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:593
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:61
#define QgsDebugError(str)
Definition qgslogger.h:57
#define RENDERER_TAG_NAME
Definition qgsrenderer.h:55
QMap< QString, QgsSymbol * > QgsSymbolMap
Definition qgsrenderer.h:50
QList< QgsSymbol * > QgsSymbolList
Definition qgsrenderer.h:49
Contains information relating to the style entity currently being visited.