26 QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() =
default;
28 QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() =
default;
30 void QgsCategorizeUsingStyleAlgorithm::initAlgorithm(
const QVariantMap & )
34 addParameter(
new QgsProcessingParameterExpression( QStringLiteral(
"FIELD" ), QObject::tr(
"Categorize using expression" ), QVariant(), QStringLiteral(
"INPUT" ) ) );
37 addParameter(
new QgsProcessingParameterBoolean( QStringLiteral(
"CASE_SENSITIVE" ), QObject::tr(
"Use case-sensitive match to symbol names" ),
false ) );
38 addParameter(
new QgsProcessingParameterBoolean( QStringLiteral(
"TOLERANT" ), QObject::tr(
"Ignore non-alphanumeric characters while matching" ),
false ) );
42 std::unique_ptr< QgsProcessingParameterFeatureSink > failCategories = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"NON_MATCHING_CATEGORIES" ), QObject::tr(
"Non-matching categories" ),
46 addParameter( failCategories.release() );
48 std::unique_ptr< QgsProcessingParameterFeatureSink > failSymbols = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral(
"NON_MATCHING_SYMBOLS" ), QObject::tr(
"Non-matching symbol names" ),
51 addParameter( failSymbols.release() );
54 QgsProcessingAlgorithm::Flags QgsCategorizeUsingStyleAlgorithm::flags()
const
57 f |= FlagNotAvailableInStandaloneTool;
61 QString QgsCategorizeUsingStyleAlgorithm::name()
const
63 return QStringLiteral(
"categorizeusingstyle" );
66 QString QgsCategorizeUsingStyleAlgorithm::displayName()
const
68 return QObject::tr(
"Create categorized renderer from styles" );
71 QStringList QgsCategorizeUsingStyleAlgorithm::tags()
const
73 return QObject::tr(
"file,database,symbols,names,category,categories" ).split(
',' );
76 QString QgsCategorizeUsingStyleAlgorithm::group()
const
78 return QObject::tr(
"Cartography" );
81 QString QgsCategorizeUsingStyleAlgorithm::groupId()
const
83 return QStringLiteral(
"cartography" );
86 QString QgsCategorizeUsingStyleAlgorithm::shortHelpString()
const
88 return QObject::tr(
"Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. If no "
89 "style file is specified, symbols from the user's current style library are used instead.\n\n"
90 "The specified expression (or field name) is used to create categories for the renderer. A category will be "
91 "created for each unique value within the layer.\n\n"
92 "Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
93 "a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
94 "The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
95 "Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
96 "while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
97 "If desired, tables can also be output containing lists of the categories which could not be matched "
98 "to symbols, and symbols which were not matched to categories."
102 QString QgsCategorizeUsingStyleAlgorithm::shortDescription()
const
104 return QObject::tr(
"Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
107 QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance()
const
109 return new QgsCategorizeUsingStyleAlgorithm();
116 SetCategorizedRendererPostProcessor( std::unique_ptr< QgsCategorizedSymbolRenderer > renderer )
117 : mRenderer( std::move( renderer ) )
122 if (
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
125 vl->setRenderer( mRenderer.release() );
126 vl->triggerRepaint();
132 std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
139 QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral(
"INPUT" ), context );
143 mField = parameterAsString( parameters, QStringLiteral(
"FIELD" ), context );
145 mLayerId = layer->
id();
146 mLayerName = layer->
name();
148 mLayerFields = layer->
fields();
155 mExpression.prepare( &mExpressionContext );
159 if ( !mExpression.needsGeometry() )
169 const QString styleFile = parameterAsFile( parameters, QStringLiteral(
"STYLE" ), context );
170 const bool caseSensitive = parameterAsBoolean( parameters, QStringLiteral(
"CASE_SENSITIVE" ), context );
171 const bool tolerant = parameterAsBoolean( parameters, QStringLiteral(
"TOLERANT" ), context );
174 std::unique_ptr< QgsStyle >importedStyle;
175 if ( !styleFile.isEmpty() )
177 importedStyle = qgis::make_unique< QgsStyle >();
178 if ( !importedStyle->importXml( styleFile ) )
180 throw QgsProcessingException( QObject::tr(
"An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
182 style = importedStyle.get();
190 nonMatchingCategoryFields.
append(
QgsField( QStringLiteral(
"category" ), QVariant::String ) );
191 QString nonMatchingCategoriesDest;
192 std::unique_ptr< QgsFeatureSink > nonMatchingCategoriesSink( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING_CATEGORIES" ), context, nonMatchingCategoriesDest, nonMatchingCategoryFields,
QgsWkbTypes::NoGeometry ) );
193 if ( !nonMatchingCategoriesSink && parameters.contains( QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ) && parameters.value( QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ).isValid() )
194 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ) );
197 nonMatchingSymbolFields.
append(
QgsField( QStringLiteral(
"name" ), QVariant::String ) );
198 QString nonMatchingSymbolsDest;
199 std::unique_ptr< QgsFeatureSink > nonMatchingSymbolsSink( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING_SYMBOLS" ), context, nonMatchingSymbolsDest, nonMatchingSymbolFields,
QgsWkbTypes::NoGeometry ) );
200 if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral(
"NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral(
"NON_MATCHING_SYMBOLS" ) ).isValid() )
203 QSet<QVariant> uniqueVals;
205 while ( mIterator.nextFeature( feature ) )
207 mExpressionContext.setFeature( feature );
208 QVariant value = mExpression.evaluate( &mExpressionContext );
209 if ( uniqueVals.contains( value ) )
214 QVariantList sortedUniqueVals = qgis::setToList( uniqueVals );
215 std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
218 cats.reserve( uniqueVals.count() );
220 for (
const QVariant &val : qgis::as_const( sortedUniqueVals ) )
225 mRenderer = qgis::make_unique< QgsCategorizedSymbolRenderer >( mField, cats );
231 QVariantList unmatchedCategories;
232 QStringList unmatchedSymbols;
233 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
237 feedback->
pushInfo( QObject::tr(
"Matched %1 categories to symbols from file." ).arg( matched ) );
241 feedback->
reportError( QObject::tr(
"No categories could be matched to symbols in file." ) );
244 if ( !unmatchedCategories.empty() )
246 feedback->
pushInfo( QObject::tr(
"\n%1 categories could not be matched:" ).arg( unmatchedCategories.count() ) );
247 std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
248 for (
const QVariant &cat : qgis::as_const( unmatchedCategories ) )
250 feedback->
pushInfo( QStringLiteral(
"∙ “%1”" ).arg( cat.toString() ) );
251 if ( nonMatchingCategoriesSink )
260 if ( !unmatchedSymbols.empty() )
262 feedback->
pushInfo( QObject::tr(
"\n%1 symbols in style were not matched:" ).arg( unmatchedSymbols.count() ) );
263 std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
264 for (
const QString &name : qgis::as_const( unmatchedSymbols ) )
266 feedback->
pushInfo( QStringLiteral(
"∙ “%1”" ).arg( name ) );
267 if ( nonMatchingSymbolsSink )
280 results.insert( QStringLiteral(
"OUTPUT" ), mLayerId );
281 if ( nonMatchingCategoriesSink )
282 results.insert( QStringLiteral(
"NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
283 if ( nonMatchingSymbolsSink )
284 results.insert( QStringLiteral(
"NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );