QGIS API Documentation 3.41.0-Master (af5edcb665c)
Loading...
Searching...
No Matches
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" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Vector ) ) );
33 addParameter( new QgsProcessingParameterExpression( QStringLiteral( "FIELD" ), QObject::tr( "Categorize using expression" ), QVariant(), QStringLiteral( "INPUT" ) ) );
34
35 addParameter( new QgsProcessingParameterFile( QStringLiteral( "STYLE" ), QObject::tr( "Style database (leave blank to use saved symbols)" ), Qgis::ProcessingFileParameterBehavior::File, QStringLiteral( "xml" ), QVariant(), true ) );
36 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "CASE_SENSITIVE" ), QObject::tr( "Use case-sensitive match to symbol names" ), false ) );
37 addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "TOLERANT" ), QObject::tr( "Ignore non-alphanumeric characters while matching" ), false ) );
38
39 addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Categorized layer" ) ) );
40
41 std::unique_ptr<QgsProcessingParameterFeatureSink> failCategories = std::make_unique<QgsProcessingParameterFeatureSink>( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QObject::tr( "Non-matching categories" ), Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
42 // not supported for outputs yet!
43 //failCategories->setFlags( failCategories->flags() | Qgis::ProcessingParameterFlag::Advanced );
44 addParameter( failCategories.release() );
45
46 std::unique_ptr<QgsProcessingParameterFeatureSink> failSymbols = std::make_unique<QgsProcessingParameterFeatureSink>( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QObject::tr( "Non-matching symbol names" ), Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
47 //failSymbols->setFlags( failSymbols->flags() | Qgis::ProcessingParameterFlag::Advanced );
48 addParameter( failSymbols.release() );
49}
50
51Qgis::ProcessingAlgorithmFlags QgsCategorizeUsingStyleAlgorithm::flags() const
52{
55 return f;
56}
57
58QString QgsCategorizeUsingStyleAlgorithm::name() const
59{
60 return QStringLiteral( "categorizeusingstyle" );
61}
62
63QString QgsCategorizeUsingStyleAlgorithm::displayName() const
64{
65 return QObject::tr( "Create categorized renderer from styles" );
66}
67
68QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
69{
70 return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
71}
72
73QString QgsCategorizeUsingStyleAlgorithm::group() const
74{
75 return QObject::tr( "Cartography" );
76}
77
78QString QgsCategorizeUsingStyleAlgorithm::groupId() const
79{
80 return QStringLiteral( "cartography" );
81}
82
83QString QgsCategorizeUsingStyleAlgorithm::shortHelpString() const
84{
85 return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. If no "
86 "style file is specified, symbols from the user's current style library are used instead.\n\n"
87 "The specified expression (or field name) is used to create categories for the renderer. A category will be "
88 "created for each unique value within the layer.\n\n"
89 "Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
90 "a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
91 "The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
92 "Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
93 "while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
94 "If desired, tables can also be output containing lists of the categories which could not be matched "
95 "to symbols, and symbols which were not matched to categories."
96 );
97}
98
99QString QgsCategorizeUsingStyleAlgorithm::shortDescription() const
100{
101 return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
102}
103
104QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
105{
106 return new QgsCategorizeUsingStyleAlgorithm();
107}
108
109class SetCategorizedRendererPostProcessor : public QgsProcessingLayerPostProcessorInterface
110{
111 public:
112 SetCategorizedRendererPostProcessor( std::unique_ptr<QgsCategorizedSymbolRenderer> renderer )
113 : mRenderer( std::move( renderer ) )
114 {}
115
117 {
118 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
119 {
120 vl->setRenderer( mRenderer.release() );
121 vl->triggerRepaint();
122 }
123 }
124
125 private:
126 std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
127};
128
129// Do most of the heavy lifting in a background thread, but save the thread-sensitive stuff for main thread execution!
130
131bool QgsCategorizeUsingStyleAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
132{
133 QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
134 if ( !layer )
135 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
136
137 mField = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
138
139 mLayerId = layer->id();
140 mLayerName = layer->name();
141 mLayerGeometryType = layer->geometryType();
142 mLayerFields = layer->fields();
143
144 mExpressionContext << QgsExpressionContextUtils::globalScope()
147
148 mExpression = QgsExpression( mField );
149 mExpression.prepare( &mExpressionContext );
150
152 req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
153 if ( !mExpression.needsGeometry() )
155
156 mIterator = layer->getFeatures( req );
157
158 return true;
159}
160
161QVariantMap QgsCategorizeUsingStyleAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
162{
163 const QString styleFile = parameterAsFile( parameters, QStringLiteral( "STYLE" ), context );
164 const bool caseSensitive = parameterAsBoolean( parameters, QStringLiteral( "CASE_SENSITIVE" ), context );
165 const bool tolerant = parameterAsBoolean( parameters, QStringLiteral( "TOLERANT" ), context );
166
167 QgsStyle *style = nullptr;
168 std::unique_ptr<QgsStyle> importedStyle;
169 if ( !styleFile.isEmpty() )
170 {
171 importedStyle = std::make_unique<QgsStyle>();
172 if ( !importedStyle->importXml( styleFile ) )
173 {
174 throw QgsProcessingException( QObject::tr( "An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
175 }
176 style = importedStyle.get();
177 }
178 else
179 {
180 style = QgsStyle::defaultStyle();
181 }
182
183 QgsFields nonMatchingCategoryFields;
184 nonMatchingCategoryFields.append( QgsField( QStringLiteral( "category" ), QMetaType::Type::QString ) );
185 QString nonMatchingCategoriesDest;
186 std::unique_ptr<QgsFeatureSink> nonMatchingCategoriesSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ), context, nonMatchingCategoriesDest, nonMatchingCategoryFields, Qgis::WkbType::NoGeometry ) );
187 if ( !nonMatchingCategoriesSink && parameters.contains( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).isValid() )
188 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) );
189
190 QgsFields nonMatchingSymbolFields;
191 nonMatchingSymbolFields.append( QgsField( QStringLiteral( "name" ), QMetaType::Type::QString ) );
192 QString nonMatchingSymbolsDest;
193 std::unique_ptr<QgsFeatureSink> nonMatchingSymbolsSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ), context, nonMatchingSymbolsDest, nonMatchingSymbolFields, Qgis::WkbType::NoGeometry ) );
194 if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).isValid() )
195 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
196
197 QSet<QVariant> uniqueVals;
198 QgsFeature feature;
199 while ( mIterator.nextFeature( feature ) )
200 {
201 mExpressionContext.setFeature( feature );
202 QVariant value = mExpression.evaluate( &mExpressionContext );
203 if ( uniqueVals.contains( value ) )
204 continue;
205 uniqueVals << value;
206 }
207
208 QVariantList sortedUniqueVals = qgis::setToList( uniqueVals );
209 std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
210
211 QgsCategoryList cats;
212 cats.reserve( uniqueVals.count() );
213 std::unique_ptr<QgsSymbol> defaultSymbol( QgsSymbol::defaultSymbol( mLayerGeometryType ) );
214 for ( const QVariant &val : std::as_const( sortedUniqueVals ) )
215 {
216 cats.append( QgsRendererCategory( val, defaultSymbol->clone(), val.toString() ) );
217 }
218
219 mRenderer = std::make_unique<QgsCategorizedSymbolRenderer>( mField, cats );
220
221 const Qgis::SymbolType type = mLayerGeometryType == Qgis::GeometryType::Point ? Qgis::SymbolType::Marker
222 : mLayerGeometryType == Qgis::GeometryType::Line ? Qgis::SymbolType::Line
224
225 QVariantList unmatchedCategories;
226 QStringList unmatchedSymbols;
227 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
228
229 if ( matched > 0 )
230 {
231 feedback->pushInfo( QObject::tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
232 }
233 else
234 {
235 feedback->reportError( QObject::tr( "No categories could be matched to symbols in file." ) );
236 }
237
238 if ( !unmatchedCategories.empty() )
239 {
240 feedback->pushInfo( QObject::tr( "\n%n categories could not be matched:", nullptr, unmatchedCategories.count() ) );
241 std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
242 for ( const QVariant &cat : std::as_const( unmatchedCategories ) )
243 {
244 feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( cat.toString() ) );
245 if ( nonMatchingCategoriesSink )
246 {
247 QgsFeature f;
248 f.setAttributes( QgsAttributes() << cat.toString() );
249 if ( !nonMatchingCategoriesSink->addFeature( f, QgsFeatureSink::FastInsert ) )
250 throw QgsProcessingException( writeFeatureError( nonMatchingCategoriesSink.get(), parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) );
251 }
252 }
253 }
254
255 if ( !unmatchedSymbols.empty() )
256 {
257 feedback->pushInfo( QObject::tr( "\n%n symbol(s) in style were not matched:", nullptr, unmatchedSymbols.count() ) );
258 std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
259 for ( const QString &name : std::as_const( unmatchedSymbols ) )
260 {
261 feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( name ) );
262 if ( nonMatchingSymbolsSink )
263 {
264 QgsFeature f;
265 f.setAttributes( QgsAttributes() << name );
266 if ( !nonMatchingSymbolsSink->addFeature( f, QgsFeatureSink::FastInsert ) )
267 throw QgsProcessingException( writeFeatureError( nonMatchingSymbolsSink.get(), parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
268 }
269 }
270 }
271
272 context.addLayerToLoadOnCompletion( mLayerId, QgsProcessingContext::LayerDetails( mLayerName, context.project(), mLayerName ) );
273 context.layerToLoadOnCompletionDetails( mLayerId ).setPostProcessor( new SetCategorizedRendererPostProcessor( std::move( mRenderer ) ) );
274
275 if ( nonMatchingCategoriesSink )
276 nonMatchingCategoriesSink->finalize();
277 if ( nonMatchingSymbolsSink )
278 nonMatchingSymbolsSink->finalize();
279
280 QVariantMap results;
281 results.insert( QStringLiteral( "OUTPUT" ), mLayerId );
282 if ( nonMatchingCategoriesSink )
283 results.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
284 if ( nonMatchingSymbolsSink )
285 results.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );
286 return results;
287}
288
@ 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:3410
SymbolType
Symbol types.
Definition qgis.h:574
@ 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.
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:58
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:53
Container of fields for a vector layer.
Definition qgsfields.h:46
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:70
Base class for all map layer types.
Definition qgsmaplayer.h:76
QString name
Definition qgsmaplayer.h:80
QString id
Definition qgsmaplayer.h:79
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.
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:146
static QgsSymbol * defaultSymbol(Qgis::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
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.
Q_INVOKABLE Qgis::GeometryType geometryType() const
Returns point, line or polygon.
QList< QgsRendererCategory > QgsCategoryList