QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
qgsalgorithmcategorizeusingstyle.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmcategorizeusingstyle.cpp
3 ---------------------
4 begin : August 2018
5 copyright : (C) 2018 by Nyall Dawson
6 email : nyall dot dawson at gmail dot com
7 ***************************************************************************/
8
9/***************************************************************************
10 * *
11 * This program is free software; you can redistribute it and/or modify *
12 * it under the terms of the GNU General Public License as published by *
13 * the Free Software Foundation; either version 2 of the License, or *
14 * (at your option) any later version. *
15 * *
16 ***************************************************************************/
17
19#include "qgsstyle.h"
21#include "qgsvectorlayer.h"
23
25
26QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() = default;
27
28QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() = default;
29
30void QgsCategorizeUsingStyleAlgorithm::initAlgorithm( const QVariantMap & )
31{
32 addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
33 QList< int >() << static_cast< int >( Qgis::ProcessingSourceType::Vector ) ) );
34 addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FIELD" ), QObject::tr( "Categorize using expression" ), QVariant(), QStringLiteral( "INPUT" ) ) );
35
36 addParameter( new QgsProcessingParameterFile( QStringLiteral( "STYLE" ), QObject::tr( "Style database (leave blank to use saved symbols)" ), Qgis::ProcessingFileParameterBehavior::File, QStringLiteral( "xml" ), QVariant(), true ) );
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 ) );
39
40 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Categorized layer" ) ) );
41
42 std::unique_ptr< QgsProcessingParameterFeatureSink > failCategories = std::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QObject::tr( "Non-matching categories" ),
43 Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
44 // not supported for outputs yet!
45 //failCategories->setFlags( failCategories->flags() | Qgis::ProcessingParameterFlag::Advanced );
46 addParameter( failCategories.release() );
47
48 std::unique_ptr< QgsProcessingParameterFeatureSink > failSymbols = std::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QObject::tr( "Non-matching symbol names" ),
49 Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
50 //failSymbols->setFlags( failSymbols->flags() | Qgis::ProcessingParameterFlag::Advanced );
51 addParameter( failSymbols.release() );
52}
53
54Qgis::ProcessingAlgorithmFlags QgsCategorizeUsingStyleAlgorithm::flags() const
55{
58 return f;
59}
60
61QString QgsCategorizeUsingStyleAlgorithm::name() const
62{
63 return QStringLiteral( "categorizeusingstyle" );
64}
65
66QString QgsCategorizeUsingStyleAlgorithm::displayName() const
67{
68 return QObject::tr( "Create categorized renderer from styles" );
69}
70
71QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
72{
73 return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
74}
75
76QString QgsCategorizeUsingStyleAlgorithm::group() const
77{
78 return QObject::tr( "Cartography" );
79}
80
81QString QgsCategorizeUsingStyleAlgorithm::groupId() const
82{
83 return QStringLiteral( "cartography" );
84}
85
86QString QgsCategorizeUsingStyleAlgorithm::shortHelpString() const
87{
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."
99 );
100}
101
102QString QgsCategorizeUsingStyleAlgorithm::shortDescription() const
103{
104 return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
105}
106
107QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
108{
109 return new QgsCategorizeUsingStyleAlgorithm();
110}
111
112class SetCategorizedRendererPostProcessor : public QgsProcessingLayerPostProcessorInterface
113{
114 public:
115
116 SetCategorizedRendererPostProcessor( std::unique_ptr< QgsCategorizedSymbolRenderer > renderer )
117 : mRenderer( std::move( renderer ) )
118 {}
119
121 {
122 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
123 {
124
125 vl->setRenderer( mRenderer.release() );
126 vl->triggerRepaint();
127 }
128 }
129
130 private:
131
132 std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
133};
134
135// Do most of the heavy lifting in a background thread, but save the thread-sensitive stuff for main thread execution!
136
137bool QgsCategorizeUsingStyleAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
138{
139 QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
140 if ( !layer )
141 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
142
143 mField = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
144
145 mLayerId = layer->id();
146 mLayerName = layer->name();
147 mLayerGeometryType = layer->geometryType();
148 mLayerFields = layer->fields();
149
150 mExpressionContext << QgsExpressionContextUtils::globalScope()
153
154 mExpression = QgsExpression( mField );
155 mExpression.prepare( &mExpressionContext );
156
158 req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
159 if ( !mExpression.needsGeometry() )
161
162 mIterator = layer->getFeatures( req );
163
164 return true;
165}
166
167QVariantMap QgsCategorizeUsingStyleAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
168{
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 );
172
173 QgsStyle *style = nullptr;
174 std::unique_ptr< QgsStyle >importedStyle;
175 if ( !styleFile.isEmpty() )
176 {
177 importedStyle = std::make_unique< QgsStyle >();
178 if ( !importedStyle->importXml( styleFile ) )
179 {
180 throw QgsProcessingException( QObject::tr( "An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
181 }
182 style = importedStyle.get();
183 }
184 else
185 {
186 style = QgsStyle::defaultStyle();
187 }
188
189 QgsFields nonMatchingCategoryFields;
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, Qgis::WkbType::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" ) ) );
195
196 QgsFields nonMatchingSymbolFields;
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, Qgis::WkbType::NoGeometry ) );
200 if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).isValid() )
201 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
202
203 QSet<QVariant> uniqueVals;
204 QgsFeature feature;
205 while ( mIterator.nextFeature( feature ) )
206 {
207 mExpressionContext.setFeature( feature );
208 QVariant value = mExpression.evaluate( &mExpressionContext );
209 if ( uniqueVals.contains( value ) )
210 continue;
211 uniqueVals << value;
212 }
213
214 QVariantList sortedUniqueVals = qgis::setToList( uniqueVals );
215 std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
216
217 QgsCategoryList cats;
218 cats.reserve( uniqueVals.count() );
219 std::unique_ptr< QgsSymbol > defaultSymbol( QgsSymbol::defaultSymbol( mLayerGeometryType ) );
220 for ( const QVariant &val : std::as_const( sortedUniqueVals ) )
221 {
222 cats.append( QgsRendererCategory( val, defaultSymbol->clone(), val.toString() ) );
223 }
224
225 mRenderer = std::make_unique< QgsCategorizedSymbolRenderer >( mField, cats );
226
227 const Qgis::SymbolType type = mLayerGeometryType == Qgis::GeometryType::Point ? Qgis::SymbolType::Marker
228 : mLayerGeometryType == Qgis::GeometryType::Line ? Qgis::SymbolType::Line
230
231 QVariantList unmatchedCategories;
232 QStringList unmatchedSymbols;
233 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
234
235 if ( matched > 0 )
236 {
237 feedback->pushInfo( QObject::tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
238 }
239 else
240 {
241 feedback->reportError( QObject::tr( "No categories could be matched to symbols in file." ) );
242 }
243
244 if ( !unmatchedCategories.empty() )
245 {
246 feedback->pushInfo( QObject::tr( "\n%n categorie(s) could not be matched:", nullptr, unmatchedCategories.count() ) );
247 std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
248 for ( const QVariant &cat : std::as_const( unmatchedCategories ) )
249 {
250 feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( cat.toString() ) );
251 if ( nonMatchingCategoriesSink )
252 {
253 QgsFeature f;
254 f.setAttributes( QgsAttributes() << cat.toString() );
255 if ( !nonMatchingCategoriesSink->addFeature( f, QgsFeatureSink::FastInsert ) )
256 throw QgsProcessingException( writeFeatureError( nonMatchingCategoriesSink.get(), parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) );
257 }
258 }
259 }
260
261 if ( !unmatchedSymbols.empty() )
262 {
263 feedback->pushInfo( QObject::tr( "\n%n symbol(s) in style were not matched:", nullptr, unmatchedSymbols.count() ) );
264 std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
265 for ( const QString &name : std::as_const( unmatchedSymbols ) )
266 {
267 feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( name ) );
268 if ( nonMatchingSymbolsSink )
269 {
270 QgsFeature f;
271 f.setAttributes( QgsAttributes() << name );
272 if ( !nonMatchingSymbolsSink->addFeature( f, QgsFeatureSink::FastInsert ) )
273 throw QgsProcessingException( writeFeatureError( nonMatchingSymbolsSink.get(), parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
274 }
275 }
276 }
277
278 context.addLayerToLoadOnCompletion( mLayerId, QgsProcessingContext::LayerDetails( mLayerName, context.project(), mLayerName ) );
279 context.layerToLoadOnCompletionDetails( mLayerId ).setPostProcessor( new SetCategorizedRendererPostProcessor( std::move( mRenderer ) ) );
280
281 QVariantMap results;
282 results.insert( QStringLiteral( "OUTPUT" ), mLayerId );
283 if ( nonMatchingCategoriesSink )
284 results.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
285 if ( nonMatchingSymbolsSink )
286 results.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );
287 return results;
288}
289
291
292
293
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
@ File
Parameter is a single file.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition: qgis.h:2934
SymbolType
Symbol types.
Definition: qgis.h:401
@ Marker
Marker symbol.
@ Line
Line symbol.
@ Fill
Fill symbol.
@ NoGeometry
No geometry.
@ NotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
A vector of attributes.
Definition: qgsattributes.h:59
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Class for parsing and evaluation of expressions (formerly called "search strings").
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setFlags(Qgis::FeatureRequestFlags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:53
Container of fields for a vector layer.
Definition: qgsfields.h:45
bool append(const QgsField &field, FieldOrigin origin=OriginProvider, int originIndex=-1)
Appends a field. The field must have unique name, otherwise it is rejected (returns false)
Definition: qgsfields.cpp:59
Base class for all map layer types.
Definition: qgsmaplayer.h:75
QString name
Definition: qgsmaplayer.h:78
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Details for layers to load into projects.
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Contains information about the context in which a processing algorithm is executed.
QgsProcessingContext::LayerDetails & layerToLoadOnCompletionDetails(const QString &layer)
Returns a reference to the details for a given layer which is loaded on completion of the algorithm o...
void addLayerToLoadOnCompletion(const QString &layer, const QgsProcessingContext::LayerDetails &details)
Adds a layer to load (by ID or datasource) into the canvas upon completion of the algorithm or model.
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
An interface for layer post-processing handlers for execution following a processing algorithm operat...
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm.
A vector layer output for processing algorithms.
A boolean parameter for processing algorithms.
An expression parameter for processing algorithms.
An input file or folder parameter for processing algorithms.
A vector layer (with or without geometry) parameter for processing algorithms.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition: qgsstyle.cpp:145
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:705
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
QList< QgsRendererCategory > QgsCategoryList