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 QString QgsCategorizeUsingStyleAlgorithm::name()
const
56 return QStringLiteral(
"categorizeusingstyle" );
59 QString QgsCategorizeUsingStyleAlgorithm::displayName()
const
61 return QObject::tr(
"Create categorized renderer from styles" );
64 QStringList QgsCategorizeUsingStyleAlgorithm::tags()
const
66 return QObject::tr(
"file,database,symbols,names,category,categories" ).split(
',' );
69 QString QgsCategorizeUsingStyleAlgorithm::group()
const
71 return QObject::tr(
"Cartography" );
74 QString QgsCategorizeUsingStyleAlgorithm::groupId()
const
76 return QStringLiteral(
"cartography" );
79 QString QgsCategorizeUsingStyleAlgorithm::shortHelpString()
const
81 return QObject::tr(
"Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. If no "
82 "style file is specified, symbols from the user's current style library are used instead.\n\n"
83 "The specified expression (or field name) is used to create categories for the renderer. A category will be "
84 "created for each unique value within the layer.\n\n"
85 "Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
86 "a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
87 "The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
88 "Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
89 "while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
90 "If desired, tables can also be output containing lists of the categories which could not be matched "
91 "to symbols, and symbols which were not matched to categories."
95 QString QgsCategorizeUsingStyleAlgorithm::shortDescription()
const
97 return QObject::tr(
"Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
100 QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance()
const
102 return new QgsCategorizeUsingStyleAlgorithm();
109 SetCategorizedRendererPostProcessor( std::unique_ptr< QgsCategorizedSymbolRenderer > renderer )
110 : mRenderer( std::move( renderer ) )
115 if (
QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
118 vl->setRenderer( mRenderer.release() );
119 vl->triggerRepaint();
125 std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
132 QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral(
"INPUT" ), context );
136 mField = parameterAsString( parameters, QStringLiteral(
"FIELD" ), context );
138 mLayerId = layer->
id();
139 mLayerName = layer->
name();
141 mLayerFields = layer->
fields();
148 mExpression.prepare( &mExpressionContext );
152 if ( !mExpression.needsGeometry() )
162 const QString styleFile = parameterAsFile( parameters, QStringLiteral(
"STYLE" ), context );
163 const bool caseSensitive = parameterAsBoolean( parameters, QStringLiteral(
"CASE_SENSITIVE" ), context );
164 const bool tolerant = parameterAsBoolean( parameters, QStringLiteral(
"TOLERANT" ), context );
167 std::unique_ptr< QgsStyle >importedStyle;
168 if ( !styleFile.isEmpty() )
170 importedStyle = qgis::make_unique< QgsStyle >();
171 if ( !importedStyle->importXml( styleFile ) )
173 throw QgsProcessingException( QObject::tr(
"An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
175 style = importedStyle.get();
183 nonMatchingCategoryFields.
append(
QgsField( QStringLiteral(
"category" ), QVariant::String ) );
184 QString nonMatchingCategoriesDest;
185 std::unique_ptr< QgsFeatureSink > nonMatchingCategoriesSink( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING_CATEGORIES" ), context, nonMatchingCategoriesDest, nonMatchingCategoryFields,
QgsWkbTypes::NoGeometry ) );
186 if ( !nonMatchingCategoriesSink && parameters.contains( QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ) && parameters.value( QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ).isValid() )
187 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral(
"NON_MATCHING_CATEGORIES" ) ) );
190 nonMatchingSymbolFields.
append(
QgsField( QStringLiteral(
"name" ), QVariant::String ) );
191 QString nonMatchingSymbolsDest;
192 std::unique_ptr< QgsFeatureSink > nonMatchingSymbolsSink( parameterAsSink( parameters, QStringLiteral(
"NON_MATCHING_SYMBOLS" ), context, nonMatchingSymbolsDest, nonMatchingSymbolFields,
QgsWkbTypes::NoGeometry ) );
193 if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral(
"NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral(
"NON_MATCHING_SYMBOLS" ) ).isValid() )
196 QSet<QVariant> uniqueVals;
198 while ( mIterator.nextFeature( feature ) )
200 mExpressionContext.setFeature( feature );
201 QVariant value = mExpression.evaluate( &mExpressionContext );
202 if ( uniqueVals.contains( value ) )
207 QVariantList sortedUniqueVals = qgis::setToList( uniqueVals );
208 std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
211 cats.reserve( uniqueVals.count() );
213 for (
const QVariant &val : qgis::as_const( sortedUniqueVals ) )
218 mRenderer = qgis::make_unique< QgsCategorizedSymbolRenderer >( mField, cats );
224 QVariantList unmatchedCategories;
225 QStringList unmatchedSymbols;
226 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
230 feedback->
pushInfo( QObject::tr(
"Matched %1 categories to symbols from file." ).arg( matched ) );
234 feedback->
reportError( QObject::tr(
"No categories could be matched to symbols in file." ) );
237 if ( !unmatchedCategories.empty() )
239 feedback->
pushInfo( QObject::tr(
"\n%1 categories could not be matched:" ).arg( unmatchedCategories.count() ) );
240 std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
241 for (
const QVariant &cat : qgis::as_const( unmatchedCategories ) )
243 feedback->
pushInfo( QStringLiteral(
"∙ “%1”" ).arg( cat.toString() ) );
244 if ( nonMatchingCategoriesSink )
253 if ( !unmatchedSymbols.empty() )
255 feedback->
pushInfo( QObject::tr(
"\n%1 symbols in style were not matched:" ).arg( unmatchedSymbols.count() ) );
256 std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
257 for (
const QString &name : qgis::as_const( unmatchedSymbols ) )
259 feedback->
pushInfo( QStringLiteral(
"∙ “%1”" ).arg( name ) );
260 if ( nonMatchingSymbolsSink )
273 results.insert( QStringLiteral(
"OUTPUT" ), mLayerId );
274 if ( nonMatchingCategoriesSink )
275 results.insert( QStringLiteral(
"NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
276 if ( nonMatchingSymbolsSink )
277 results.insert( QStringLiteral(
"NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );