QGIS API Documentation 4.3.0-Master (ffcfc20b9b4)
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
22#include "qgsstyle.h"
23#include "qgsvectorlayer.h"
24
25#include <QString>
26
27using namespace Qt::StringLiterals;
28
30
31QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() = default;
32
33QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() = default;
34
35void QgsCategorizeUsingStyleAlgorithm::initAlgorithm( const QVariantMap & )
36{
37 addParameter( new QgsProcessingParameterVectorLayer( u"INPUT"_s, QObject::tr( "Input layer" ), QList<int>() << static_cast<int>( Qgis::ProcessingSourceType::Vector ) ) );
38 addParameter( new QgsProcessingParameterExpression( u"FIELD"_s, QObject::tr( "Categorize using expression" ), QVariant(), u"INPUT"_s ) );
39
40 addParameter( new QgsProcessingParameterFile( u"STYLE"_s, QObject::tr( "Style database (leave blank to use saved symbols)" ), Qgis::ProcessingFileParameterBehavior::File, u"xml"_s, QVariant(), true ) );
41 addParameter( new QgsProcessingParameterBoolean( u"CASE_SENSITIVE"_s, QObject::tr( "Use case-sensitive match to symbol names" ), false ) );
42 addParameter( new QgsProcessingParameterBoolean( u"TOLERANT"_s, QObject::tr( "Ignore non-alphanumeric characters while matching" ), false ) );
43
44 addOutput( new QgsProcessingOutputVectorLayer( u"OUTPUT"_s, QObject::tr( "Categorized layer" ) ) );
45
46 auto failCategories
47 = std::make_unique<QgsProcessingParameterFeatureSink>( u"NON_MATCHING_CATEGORIES"_s, QObject::tr( "Non-matching categories" ), Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
48 // not supported for outputs yet!
49 //failCategories->setFlags( failCategories->flags() | Qgis::ProcessingParameterFlag::Advanced );
50 addParameter( failCategories.release() );
51
52 auto failSymbols = std::make_unique<QgsProcessingParameterFeatureSink>( u"NON_MATCHING_SYMBOLS"_s, QObject::tr( "Non-matching symbol names" ), Qgis::ProcessingSourceType::Vector, QVariant(), true, false );
53 //failSymbols->setFlags( failSymbols->flags() | Qgis::ProcessingParameterFlag::Advanced );
54 addParameter( failSymbols.release() );
55}
56
57Qgis::ProcessingAlgorithmFlags QgsCategorizeUsingStyleAlgorithm::flags() const
58{
61 return f;
62}
63
64QString QgsCategorizeUsingStyleAlgorithm::name() const
65{
66 return u"categorizeusingstyle"_s;
67}
68
69QString QgsCategorizeUsingStyleAlgorithm::displayName() const
70{
71 return QObject::tr( "Create categorized renderer from styles" );
72}
73
74QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
75{
76 return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
77}
78
79QString QgsCategorizeUsingStyleAlgorithm::group() const
80{
81 return QObject::tr( "Cartography" );
82}
83
84QString QgsCategorizeUsingStyleAlgorithm::groupId() const
85{
86 return u"cartography"_s;
87}
88
89QString QgsCategorizeUsingStyleAlgorithm::shortHelpString() const
90{
91 return QObject::tr(
92 "This algorithm sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. If no "
93 "style file is specified, symbols from the user's current style library are used instead.\n\n"
94 "The specified expression (or field name) is used to create categories for the renderer. A category will be "
95 "created for each unique value within the layer.\n\n"
96 "Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
97 "a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
98 "The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
99 "Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
100 "while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
101 "If desired, tables can also be output containing lists of the categories which could not be matched "
102 "to symbols, and symbols which were not matched to categories."
103 );
104}
105
106QString QgsCategorizeUsingStyleAlgorithm::shortDescription() const
107{
108 return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
109}
110
111QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
112{
113 return new QgsCategorizeUsingStyleAlgorithm();
114}
115
116class SetCategorizedRendererPostProcessor : public QgsProcessingLayerPostProcessorInterface
117{
118 public:
119 SetCategorizedRendererPostProcessor( std::unique_ptr<QgsCategorizedSymbolRenderer> renderer )
120 : mRenderer( std::move( renderer ) )
121 {}
122
123 void postProcessLayer( QgsMapLayer *layer, QgsProcessingContext &, QgsProcessingFeedback * ) override
124 {
125 if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( layer ) )
126 {
127 vl->setRenderer( mRenderer.release() );
128 vl->triggerRepaint();
129 }
130 }
131
132 private:
133 std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
134};
135
136// Do most of the heavy lifting in a background thread, but save the thread-sensitive stuff for main thread execution!
137
138bool QgsCategorizeUsingStyleAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
139{
140 QgsVectorLayer *layer = parameterAsVectorLayer( parameters, u"INPUT"_s, context );
141 if ( !layer )
142 throw QgsProcessingException( invalidSourceError( parameters, u"INPUT"_s ) );
143
144 mField = parameterAsString( parameters, u"FIELD"_s, context );
145
146 mLayerId = layer->id();
147 mLayerName = layer->name();
148 mLayerGeometryType = layer->geometryType();
149 mLayerFields = layer->fields();
150
152
153 mExpression = QgsExpression( mField );
154 mExpression.prepare( &mExpressionContext );
155
157 req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
158 if ( !mExpression.needsGeometry() )
160
161 mIterator = layer->getFeatures( req );
162
163 return true;
164}
165
166QVariantMap QgsCategorizeUsingStyleAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
167{
168 QGS_MARK_ALGORITHM_SOURCE
169
170 const QString styleFile = parameterAsFile( parameters, u"STYLE"_s, context );
171 const bool caseSensitive = parameterAsBoolean( parameters, u"CASE_SENSITIVE"_s, context );
172 const bool tolerant = parameterAsBoolean( parameters, u"TOLERANT"_s, context );
173
174 QgsStyle *style = nullptr;
175 std::unique_ptr<QgsStyle> importedStyle;
176 if ( !styleFile.isEmpty() )
177 {
178 importedStyle = std::make_unique<QgsStyle>();
179 if ( !importedStyle->importXml( styleFile ) )
180 {
181 throw QgsProcessingException( QObject::tr( "An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
182 }
183 style = importedStyle.get();
184 }
185 else
186 {
187 style = QgsStyle::defaultStyle();
188 }
189
190 QgsFields nonMatchingCategoryFields;
191 nonMatchingCategoryFields.append( QgsField( u"category"_s, QMetaType::Type::QString ) );
192 QString nonMatchingCategoriesDest;
193 std::unique_ptr<QgsFeatureSink> nonMatchingCategoriesSink(
194 parameterAsSink( parameters, u"NON_MATCHING_CATEGORIES"_s, context, nonMatchingCategoriesDest, nonMatchingCategoryFields, Qgis::WkbType::NoGeometry )
195 );
196 if ( !nonMatchingCategoriesSink && parameters.contains( u"NON_MATCHING_CATEGORIES"_s ) && parameters.value( u"NON_MATCHING_CATEGORIES"_s ).isValid() )
197 throw QgsProcessingException( invalidSinkError( parameters, u"NON_MATCHING_CATEGORIES"_s ) );
198
199 QgsFields nonMatchingSymbolFields;
200 nonMatchingSymbolFields.append( QgsField( u"name"_s, QMetaType::Type::QString ) );
201 QString nonMatchingSymbolsDest;
202 std::unique_ptr<QgsFeatureSink> nonMatchingSymbolsSink( parameterAsSink( parameters, u"NON_MATCHING_SYMBOLS"_s, context, nonMatchingSymbolsDest, nonMatchingSymbolFields, Qgis::WkbType::NoGeometry ) );
203 if ( !nonMatchingSymbolsSink && parameters.contains( u"NON_MATCHING_SYMBOLS"_s ) && parameters.value( u"NON_MATCHING_SYMBOLS"_s ).isValid() )
204 throw QgsProcessingException( invalidSinkError( parameters, u"NON_MATCHING_SYMBOLS"_s ) );
205
206 QSet<QVariant> uniqueVals;
207 QgsFeature feature;
208 while ( mIterator.nextFeature( feature ) )
209 {
210 mExpressionContext.setFeature( feature );
211 QVariant value = mExpression.evaluate( &mExpressionContext );
212 if ( uniqueVals.contains( value ) )
213 continue;
214 uniqueVals << value;
215 }
216
217 QVariantList sortedUniqueVals = qgis::setToList( uniqueVals );
218 std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
219
220 QgsCategoryList cats;
221 cats.reserve( uniqueVals.count() );
222 std::unique_ptr<QgsSymbol> defaultSymbol( QgsSymbol::defaultSymbol( mLayerGeometryType ) );
223 for ( const QVariant &val : std::as_const( sortedUniqueVals ) )
224 {
225 cats.append( QgsRendererCategory( val, defaultSymbol->clone(), val.toString() ) );
226 }
227
228 mRenderer = std::make_unique<QgsCategorizedSymbolRenderer>( mField, cats );
229
230 const Qgis::SymbolType type = mLayerGeometryType == Qgis::GeometryType::Point ? Qgis::SymbolType::Marker
231 : mLayerGeometryType == Qgis::GeometryType::Line ? Qgis::SymbolType::Line
233
234 QVariantList unmatchedCategories;
235 QStringList unmatchedSymbols;
236 const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
237
238 if ( matched > 0 )
239 {
240 feedback->pushInfo( QObject::tr( "Matched %n categories to symbols from file.", nullptr, matched ) );
241 }
242 else
243 {
244 feedback->reportError( QObject::tr( "No categories could be matched to symbols in file." ) );
245 }
246
247 if ( !unmatchedCategories.empty() )
248 {
249 feedback->pushInfo( QObject::tr( "\n%n categories could not be matched:", nullptr, unmatchedCategories.count() ) );
250 std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
251 for ( const QVariant &cat : std::as_const( unmatchedCategories ) )
252 {
253 feedback->pushInfo( u"∙ “%1”"_s.arg( cat.toString() ) );
254 if ( nonMatchingCategoriesSink )
255 {
256 QgsFeature f;
257 f.setAttributes( QgsAttributes() << cat.toString() );
258 if ( !nonMatchingCategoriesSink->addFeature( f, QgsFeatureSink::FastInsert ) )
259 throw QgsProcessingException( writeFeatureError( nonMatchingCategoriesSink.get(), parameters, u"NON_MATCHING_CATEGORIES"_s ) );
260 else
261 feedback->featureAddedToSink( u"NON_MATCHING_CATEGORIES"_s );
262 }
263 }
264 }
265
266 if ( !unmatchedSymbols.empty() )
267 {
268 feedback->pushInfo( QObject::tr( "\n%n symbol(s) in style were not matched:", nullptr, unmatchedSymbols.count() ) );
269 std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
270 for ( const QString &name : std::as_const( unmatchedSymbols ) )
271 {
272 feedback->pushInfo( u"∙ “%1”"_s.arg( name ) );
273 if ( nonMatchingSymbolsSink )
274 {
275 QgsFeature f;
276 f.setAttributes( QgsAttributes() << name );
277 if ( !nonMatchingSymbolsSink->addFeature( f, QgsFeatureSink::FastInsert ) )
278 throw QgsProcessingException( writeFeatureError( nonMatchingSymbolsSink.get(), parameters, u"NON_MATCHING_SYMBOLS"_s ) );
279 else
280 feedback->featureAddedToSink( u"NON_MATCHING_SYMBOLS"_s );
281 }
282 }
283 }
284
285 context.addLayerToLoadOnCompletion( mLayerId, QgsProcessingContext::LayerDetails( mLayerName, context.project(), mLayerName ) );
286 context.layerToLoadOnCompletionDetails( mLayerId ).setPostProcessor( new SetCategorizedRendererPostProcessor( std::move( mRenderer ) ) );
287
288 if ( nonMatchingCategoriesSink )
289 {
290 nonMatchingCategoriesSink->finalize();
291 feedback->featureSinkFinalized( u"NON_MATCHING_CATEGORIES"_s );
292 }
293 if ( nonMatchingSymbolsSink )
294 {
295 nonMatchingSymbolsSink->finalize();
296 feedback->featureSinkFinalized( u"NON_MATCHING_SYMBOLS"_s );
297 }
298
299 QVariantMap results;
300 results.insert( u"OUTPUT"_s, mLayerId );
301 if ( nonMatchingCategoriesSink )
302 results.insert( u"NON_MATCHING_CATEGORIES"_s, nonMatchingCategoriesDest );
303 if ( nonMatchingSymbolsSink )
304 results.insert( u"NON_MATCHING_SYMBOLS"_s, nonMatchingSymbolsDest );
305 return results;
306}
307
@ Vector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition qgis.h:3755
@ File
Parameter is a single file.
Definition qgis.h:4008
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2360
@ Point
Points.
Definition qgis.h:380
@ Line
Lines.
Definition qgis.h:381
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition qgis.h:3826
SymbolType
Symbol types.
Definition qgis.h:651
@ Marker
Marker symbol.
Definition qgis.h:652
@ Line
Line symbol.
Definition qgis.h:653
@ Fill
Fill symbol.
Definition qgis.h:654
@ NoGeometry
No geometry.
Definition qgis.h:312
@ NotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
Definition qgis.h:3812
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.
Handles parsing and evaluation of expressions (formerly called "search strings").
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:60
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Encapsulate a field in an attribute table or data source.
Definition qgsfield.h:56
Container of fields for a vector layer.
Definition qgsfields.h:45
bool append(const QgsField &field, Qgis::FieldOrigin origin=Qgis::FieldOrigin::Provider, int originIndex=-1)
Appends a field.
Definition qgsfields.cpp:75
QString name
Definition qgsmaplayer.h:87
QString id
Definition qgsmaplayer.h:86
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.
void featureAddedToSink(const QString &output)
Reports that a feature was added to the the sink associated with the specified algorithm output.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
void featureSinkFinalized(const QString &output)
Reports that a feature sink has been finalized.
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.
A database of saved style entities, including symbols, color ramps, text formats and others.
Definition qgsstyle.h:91
static QgsStyle * defaultStyle(bool initialize=true)
Returns the default application-wide style.
Definition qgsstyle.cpp:164
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 dataset.
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