QGIS API Documentation  3.26.3-Buenos Aires (65e4edfdad)
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 
26 QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() = default;
27 
28 QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() = default;
29 
30 void QgsCategorizeUsingStyleAlgorithm::initAlgorithm( const QVariantMap & )
31 {
32  addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
33  QList< int >() << QgsProcessing::TypeVector ) );
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)" ), QgsProcessingParameterFile::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  QgsProcessing::TypeVector, QVariant(), true, false );
44  // not supported for outputs yet!
45  //failCategories->setFlags( failCategories->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
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  QgsProcessing::TypeVector, QVariant(), true, false );
50  //failSymbols->setFlags( failSymbols->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
51  addParameter( failSymbols.release() );
52 }
53 
54 QgsProcessingAlgorithm::Flags QgsCategorizeUsingStyleAlgorithm::flags() const
55 {
57  f |= FlagNotAvailableInStandaloneTool;
58  return f;
59 }
60 
61 QString QgsCategorizeUsingStyleAlgorithm::name() const
62 {
63  return QStringLiteral( "categorizeusingstyle" );
64 }
65 
66 QString QgsCategorizeUsingStyleAlgorithm::displayName() const
67 {
68  return QObject::tr( "Create categorized renderer from styles" );
69 }
70 
71 QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
72 {
73  return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
74 }
75 
76 QString QgsCategorizeUsingStyleAlgorithm::group() const
77 {
78  return QObject::tr( "Cartography" );
79 }
80 
81 QString QgsCategorizeUsingStyleAlgorithm::groupId() const
82 {
83  return QStringLiteral( "cartography" );
84 }
85 
86 QString 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 
102 QString 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 
107 QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
108 {
109  return new QgsCategorizeUsingStyleAlgorithm();
110 }
111 
112 class 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 
137 bool 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 
157  QgsFeatureRequest req;
158  req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
159  if ( !mExpression.needsGeometry() )
161 
162  mIterator = layer->getFeatures( req );
163 
164  return true;
165 }
166 
167 QVariantMap 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, 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" ) ) );
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, QgsWkbTypes::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 == QgsWkbTypes::PointGeometry ? Qgis::SymbolType::Marker
228  : mLayerGeometryType == QgsWkbTypes::LineGeometry ? 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 
QgsFeatureRequest::NoGeometry
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition: qgsfeaturerequest.h:115
QgsVectorLayer::getFeatures
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
Definition: qgsvectorlayer.cpp:1052
qgsexpressioncontextutils.h
QgsProcessingContext::LayerDetails::setPostProcessor
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Definition: qgsprocessingcontext.cpp:220
Qgis::SymbolType::Fill
@ Fill
Fill symbol.
Qgis::SymbolType::Line
@ Line
Line symbol.
QgsExpressionContextUtils::globalScope
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Definition: qgsexpressioncontextutils.cpp:40
QgsSymbol::defaultSymbol
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:673
qgscategorizedsymbolrenderer.h
QgsProcessingContext::project
QgsProject * project() const
Returns the project in which the algorithm is being executed.
Definition: qgsprocessingcontext.h:121
QgsProcessingFeedback
Base class for providing feedback from a processing algorithm.
Definition: qgsprocessingfeedback.h:37
QgsProcessingFeedback::pushInfo
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
Definition: qgsprocessingfeedback.cpp:77
QgsProcessingFeedback::reportError
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
Definition: qgsprocessingfeedback.cpp:59
QgsFields
Container of fields for a vector layer.
Definition: qgsfields.h:44
QgsProcessingOutputVectorLayer
A vector layer output for processing algorithms.
Definition: qgsprocessingoutputs.h:179
QgsExpressionContextUtils::layerScope
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Definition: qgsexpressioncontextutils.cpp:334
QgsRendererCategory
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
Definition: qgscategorizedsymbolrenderer.h:35
QgsFeatureRequest::setSubsetOfAttributes
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
Definition: qgsfeaturerequest.cpp:228
QgsFields::append
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
QgsStyle::defaultStyle
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:145
QgsProcessingParameterFile::File
@ File
Parameter is a single file.
Definition: qgsprocessingparameters.h:1914
QgsVectorLayer::fields
QgsFields fields() const FINAL
Returns the list of fields of this layer.
Definition: qgsvectorlayer.cpp:3436
QgsFeatureRequest
This class wraps a request for features to a vector layer (or directly its vector data provider).
Definition: qgsfeaturerequest.h:83
QgsExpressionContextUtils::projectScope
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
Definition: qgsexpressioncontextutils.cpp:291
QgsProcessing::TypeVector
@ TypeVector
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:54
Qgis::SymbolType
SymbolType
Symbol types.
Definition: qgis.h:205
QgsCategoryList
QList< QgsRendererCategory > QgsCategoryList
Definition: qgscategorizedsymbolrenderer.h:158
QgsProcessingContext
Contains information about the context in which a processing algorithm is executed.
Definition: qgsprocessingcontext.h:46
QgsMapLayer::id
QString id() const
Returns the layer's unique ID, which is used to access this layer from QgsProject.
Definition: qgsmaplayer.cpp:169
QgsProcessingLayerPostProcessorInterface
An interface for layer post-processing handlers for execution following a processing algorithm operat...
Definition: qgsprocessingcontext.h:737
qgsalgorithmcategorizeusingstyle.h
QgsProcessingParameterFile
An input file or folder parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1907
QgsProcessingParameterVectorLayer
A vector layer (with or without geometry) parameter for processing algorithms. Consider using the mor...
Definition: qgsprocessingparameters.h:2827
QgsProcessingContext::LayerDetails
Details for layers to load into projects.
Definition: qgsprocessingcontext.h:262
qgsstyle.h
qgsvectorlayer.h
QgsWkbTypes::LineGeometry
@ LineGeometry
Definition: qgswkbtypes.h:143
QgsProcessingParameterBoolean
A boolean parameter for processing algorithms.
Definition: qgsprocessingparameters.h:1709
QgsWkbTypes::PointGeometry
@ PointGeometry
Definition: qgswkbtypes.h:142
QgsProcessingContext::addLayerToLoadOnCompletion
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.
Definition: qgsprocessingcontext.cpp:54
QgsWkbTypes::NoGeometry
@ NoGeometry
Definition: qgswkbtypes.h:85
QgsStyle
Definition: qgsstyle.h:159
QgsVectorLayer
Represents a vector layer which manages a vector based data sets.
Definition: qgsvectorlayer.h:391
QgsProcessingParameterExpression
An expression parameter for processing algorithms.
Definition: qgsprocessingparameters.h:2739
QgsMapLayer
Base class for all map layer types. This is the base class for all map layer types (vector,...
Definition: qgsmaplayer.h:72
QgsProcessingContext::layerToLoadOnCompletionDetails
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...
Definition: qgsprocessingcontext.h:398
QgsMapLayer::name
QString name
Definition: qgsmaplayer.h:76
QgsProcessingLayerPostProcessorInterface::postProcessLayer
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm.
QgsAttributes
A vector of attributes. Mostly equal to QVector<QVariant>.
Definition: qgsattributes.h:57
QgsFeature
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:55
QgsFeature::setAttributes
void setAttributes(const QgsAttributes &attrs)
Sets the feature's attributes.
Definition: qgsfeature.cpp:160
QgsProcessingAlgorithm::flags
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
Definition: qgsprocessingalgorithm.cpp:90
QgsExpression
Class for parsing and evaluation of expressions (formerly called "search strings")....
Definition: qgsexpression.h:102
QgsVectorLayer::geometryType
Q_INVOKABLE QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
Definition: qgsvectorlayer.cpp:720
QgsFeatureRequest::setFlags
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
Definition: qgsfeaturerequest.cpp:222
QgsProcessingException
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
Qgis::SymbolType::Marker
@ Marker
Marker symbol.
QgsFeatureSink::FastInsert
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Definition: qgsfeaturesink.h:70
QgsField
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:50