QGIS API Documentation  3.4.15-Madeira (e83d02e274)
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"
22 
24 
25 QgsCategorizeUsingStyleAlgorithm::QgsCategorizeUsingStyleAlgorithm() = default;
26 
27 QgsCategorizeUsingStyleAlgorithm::~QgsCategorizeUsingStyleAlgorithm() = default;
28 
29 void QgsCategorizeUsingStyleAlgorithm::initAlgorithm( const QVariantMap & )
30 {
31  addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
32  QList< int >() << QgsProcessing::TypeVector ) );
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)" ), QgsProcessingParameterFile::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 = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_CATEGORIES" ), QObject::tr( "Non-matching categories" ),
42  QgsProcessing::TypeVector, QVariant(), true, false );
43  // not supported for outputs yet!
44  //failCategories->setFlags( failCategories->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
45  addParameter( failCategories.release() );
46 
47  std::unique_ptr< QgsProcessingParameterFeatureSink > failSymbols = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "NON_MATCHING_SYMBOLS" ), QObject::tr( "Non-matching symbol names" ),
48  QgsProcessing::TypeVector, QVariant(), true, false );
49  //failSymbols->setFlags( failSymbols->flags() | QgsProcessingParameterDefinition::FlagAdvanced );
50  addParameter( failSymbols.release() );
51 }
52 
53 QString QgsCategorizeUsingStyleAlgorithm::name() const
54 {
55  return QStringLiteral( "categorizeusingstyle" );
56 }
57 
58 QString QgsCategorizeUsingStyleAlgorithm::displayName() const
59 {
60  return QObject::tr( "Create categorized renderer from styles" );
61 }
62 
63 QStringList QgsCategorizeUsingStyleAlgorithm::tags() const
64 {
65  return QObject::tr( "file,database,symbols,names,category,categories" ).split( ',' );
66 }
67 
68 QString QgsCategorizeUsingStyleAlgorithm::group() const
69 {
70  return QObject::tr( "Cartography" );
71 }
72 
73 QString QgsCategorizeUsingStyleAlgorithm::groupId() const
74 {
75  return QStringLiteral( "cartography" );
76 }
77 
78 QString QgsCategorizeUsingStyleAlgorithm::shortHelpString() const
79 {
80  return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using matching symbols from a style database. If no "
81  "style file is specified, symbols from the user's current style library are used instead.\n\n"
82  "The specified expression (or field name) is used to create categories for the renderer. A category will be "
83  "created for each unique value within the layer.\n\n"
84  "Each category is individually matched to the symbols which exist within the specified QGIS XML style database. Whenever "
85  "a matching symbol name is found, the category's symbol will be set to this matched symbol.\n\n"
86  "The matching is case-insensitive by default, but can be made case-sensitive if required.\n\n"
87  "Optionally, non-alphanumeric characters in both the category value and symbol name can be ignored "
88  "while performing the match. This allows for greater tolerance when matching categories to symbols.\n\n"
89  "If desired, tables can also be output containing lists of the categories which could not be matched "
90  "to symbols, and symbols which were not matched to categories."
91  );
92 }
93 
94 QString QgsCategorizeUsingStyleAlgorithm::shortDescription() const
95 {
96  return QObject::tr( "Sets a vector layer's renderer to a categorized renderer using symbols from a style database." );
97 }
98 
99 QgsCategorizeUsingStyleAlgorithm *QgsCategorizeUsingStyleAlgorithm::createInstance() const
100 {
101  return new QgsCategorizeUsingStyleAlgorithm();
102 }
103 
104 class SetCategorizedRendererPostProcessor : public QgsProcessingLayerPostProcessorInterface
105 {
106  public:
107 
108  SetCategorizedRendererPostProcessor( std::unique_ptr< QgsCategorizedSymbolRenderer > renderer )
109  : mRenderer( std::move( renderer ) )
110  {}
111 
113  {
114  if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer * >( layer ) )
115  {
116 
117  vl->setRenderer( mRenderer.release() );
118  vl->triggerRepaint();
119  }
120  }
121 
122  private:
123 
124  std::unique_ptr<QgsCategorizedSymbolRenderer> mRenderer;
125 };
126 
127 // Do most of the heavy lifting in a background thread, but save the thread-sensitive stuff for main thread execution!
128 
129 bool QgsCategorizeUsingStyleAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
130 {
131  QgsVectorLayer *layer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
132  if ( !layer )
133  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
134 
135  mField = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
136 
137  mLayerId = layer->id();
138  mLayerName = layer->name();
139  mLayerGeometryType = layer->geometryType();
140  mLayerFields = layer->fields();
141 
142  mExpressionContext << QgsExpressionContextUtils::globalScope()
145 
146  mExpression = QgsExpression( mField );
147  mExpression.prepare( &mExpressionContext );
148 
149  QgsFeatureRequest req;
150  req.setSubsetOfAttributes( mExpression.referencedColumns(), mLayerFields );
151  if ( !mExpression.needsGeometry() )
153 
154  mIterator = layer->getFeatures( req );
155 
156  return true;
157 }
158 
159 QVariantMap QgsCategorizeUsingStyleAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
160 {
161  const QString styleFile = parameterAsFile( parameters, QStringLiteral( "STYLE" ), context );
162  const bool caseSensitive = parameterAsBool( parameters, QStringLiteral( "CASE_SENSITIVE" ), context );
163  const bool tolerant = parameterAsBool( parameters, QStringLiteral( "TOLERANT" ), context );
164 
165  QgsStyle *style = nullptr;
166  std::unique_ptr< QgsStyle >importedStyle;
167  if ( !styleFile.isEmpty() )
168  {
169  importedStyle = qgis::make_unique< QgsStyle >();
170  if ( !importedStyle->importXml( styleFile ) )
171  {
172  throw QgsProcessingException( QObject::tr( "An error occurred while reading style file: %1" ).arg( importedStyle->errorString() ) );
173  }
174  style = importedStyle.get();
175  }
176  else
177  {
178  style = QgsStyle::defaultStyle();
179  }
180 
181  QgsFields nonMatchingCategoryFields;
182  nonMatchingCategoryFields.append( QgsField( QStringLiteral( "category" ), QVariant::String ) );
183  QString nonMatchingCategoriesDest;
184  std::unique_ptr< QgsFeatureSink > nonMatchingCategoriesSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ), context, nonMatchingCategoriesDest, nonMatchingCategoryFields, QgsWkbTypes::NoGeometry ) );
185  if ( !nonMatchingCategoriesSink && parameters.contains( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_CATEGORIES" ) ).isValid() )
186  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_CATEGORIES" ) ) );
187 
188  QgsFields nonMatchingSymbolFields;
189  nonMatchingSymbolFields.append( QgsField( QStringLiteral( "name" ), QVariant::String ) );
190  QString nonMatchingSymbolsDest;
191  std::unique_ptr< QgsFeatureSink > nonMatchingSymbolsSink( parameterAsSink( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ), context, nonMatchingSymbolsDest, nonMatchingSymbolFields, QgsWkbTypes::NoGeometry ) );
192  if ( !nonMatchingSymbolsSink && parameters.contains( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) && parameters.value( QStringLiteral( "NON_MATCHING_SYMBOLS" ) ).isValid() )
193  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "NON_MATCHING_SYMBOLS" ) ) );
194 
195  QSet<QVariant> uniqueVals;
196  QgsFeature feature;
197  while ( mIterator.nextFeature( feature ) )
198  {
199  mExpressionContext.setFeature( feature );
200  QVariant value = mExpression.evaluate( &mExpressionContext );
201  if ( uniqueVals.contains( value ) )
202  continue;
203  uniqueVals << value;
204  }
205 
206  QVariantList sortedUniqueVals = uniqueVals.toList();
207  std::sort( sortedUniqueVals.begin(), sortedUniqueVals.end() );
208 
209  QgsCategoryList cats;
210  cats.reserve( uniqueVals.count() );
211  std::unique_ptr< QgsSymbol > defaultSymbol( QgsSymbol::defaultSymbol( mLayerGeometryType ) );
212  for ( const QVariant &val : qgis::as_const( sortedUniqueVals ) )
213  {
214  cats.append( QgsRendererCategory( val, defaultSymbol->clone(), val.toString() ) );
215  }
216 
217  mRenderer = qgis::make_unique< QgsCategorizedSymbolRenderer >( mField, cats );
218 
219  const QgsSymbol::SymbolType type = mLayerGeometryType == QgsWkbTypes::PointGeometry ? QgsSymbol::Marker
220  : mLayerGeometryType == QgsWkbTypes::LineGeometry ? QgsSymbol::Line
221  : QgsSymbol::Fill;
222 
223  QVariantList unmatchedCategories;
224  QStringList unmatchedSymbols;
225  const int matched = mRenderer->matchToSymbols( style, type, unmatchedCategories, unmatchedSymbols, caseSensitive, tolerant );
226 
227  if ( matched > 0 )
228  {
229  feedback->pushInfo( QObject::tr( "Matched %1 categories to symbols from file." ).arg( matched ) );
230  }
231  else
232  {
233  feedback->reportError( QObject::tr( "No categories could be matched to symbols in file." ) );
234  }
235 
236  if ( !unmatchedCategories.empty() )
237  {
238  feedback->pushInfo( QObject::tr( "\n%1 categories could not be matched:" ).arg( unmatchedCategories.count() ) );
239  std::sort( unmatchedCategories.begin(), unmatchedCategories.end() );
240  for ( const QVariant &cat : qgis::as_const( unmatchedCategories ) )
241  {
242  feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( cat.toString() ) );
243  if ( nonMatchingCategoriesSink )
244  {
245  QgsFeature f;
246  f.setAttributes( QgsAttributes() << cat.toString() );
247  nonMatchingCategoriesSink->addFeature( f, QgsFeatureSink::FastInsert );
248  }
249  }
250  }
251 
252  if ( !unmatchedSymbols.empty() )
253  {
254  feedback->pushInfo( QObject::tr( "\n%1 symbols in style were not matched:" ).arg( unmatchedSymbols.count() ) );
255  std::sort( unmatchedSymbols.begin(), unmatchedSymbols.end() );
256  for ( const QString &name : qgis::as_const( unmatchedSymbols ) )
257  {
258  feedback->pushInfo( QStringLiteral( "∙ “%1”" ).arg( name ) );
259  if ( nonMatchingSymbolsSink )
260  {
261  QgsFeature f;
262  f.setAttributes( QgsAttributes() << name );
263  nonMatchingSymbolsSink->addFeature( f, QgsFeatureSink::FastInsert );
264  }
265  }
266  }
267 
268  context.addLayerToLoadOnCompletion( mLayerId, QgsProcessingContext::LayerDetails( mLayerName, context.project(), mLayerName ) );
269  context.layerToLoadOnCompletionDetails( mLayerId ).setPostProcessor( new SetCategorizedRendererPostProcessor( std::move( mRenderer ) ) );
270 
271  QVariantMap results;
272  results.insert( QStringLiteral( "OUTPUT" ), mLayerId );
273  if ( nonMatchingCategoriesSink )
274  results.insert( QStringLiteral( "NON_MATCHING_CATEGORIES" ), nonMatchingCategoriesDest );
275  if ( nonMatchingSymbolsSink )
276  results.insert( QStringLiteral( "NON_MATCHING_SYMBOLS" ), nonMatchingSymbolsDest );
277  return results;
278 }
279 
281 
282 
283 
A boolean parameter for processing algorithms.
Class for parsing and evaluation of expressions (formerly called "search strings").
QgsProject * project() const
Returns the project in which the algorithm is being executed.
An input file or folder parameter for processing algorithms.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
Base class for all map layer types.
Definition: qgsmaplayer.h:63
Base class for providing feedback from a processing algorithm.
Represents an individual category (class) from a QgsCategorizedSymbolRenderer.
An interface for layer post-processing handlers for execution following a processing algorithm operat...
An expression parameter for processing algorithms.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
void setPostProcessor(QgsProcessingLayerPostProcessorInterface *processor)
Sets the layer post-processor.
Container of fields for a vector layer.
Definition: qgsfields.h:42
void setAttributes(const QgsAttributes &attrs)
Sets the feature&#39;s attributes.
Definition: qgsfeature.cpp:127
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
Line symbol.
Definition: qgssymbol.h:86
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
A vector layer output for processing algorithms.
static QgsStyle * defaultStyle()
Returns default application-wide style.
Definition: qgsstyle.cpp:46
QList< QgsRendererCategory > QgsCategoryList
SymbolType
Type of the symbol.
Definition: qgssymbol.h:83
static QgsSymbol * defaultSymbol(QgsWkbTypes::GeometryType geomType)
Returns a new default symbol for the specified geometry type.
Definition: qgssymbol.cpp:275
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context...
QgsFields fields() const FINAL
Returns the list of fields of this layer.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
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
QString id() const
Returns the layer&#39;s unique ID, which is used to access this layer from QgsProject.
A vector layer (with or without geometry) parameter for processing algorithms.
Encapsulate a field in an attribute table or data source.
Definition: qgsfield.h:48
Details for layers to load into projects.
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...
Marker symbol.
Definition: qgssymbol.h:85
Fill symbol.
Definition: qgssymbol.h:87
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...
Tables (i.e. vector layers with or without geometry). When used for a sink this indicates the sink ha...
Definition: qgsprocessing.h:53
virtual void postProcessLayer(QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback)=0
Post-processes the specified layer, following successful execution of a processing algorithm...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Query the layer for features specified in request.
QString name
Definition: qgsmaplayer.h:67
QgsWkbTypes::GeometryType geometryType() const
Returns point, line or polygon.
static QgsExpressionContextScope * layerScope(const QgsMapLayer *layer)
Creates a new scope which contains variables and functions relating to a QgsMapLayer.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
A vector of attributes.
Definition: qgsattributes.h:57
Represents a vector layer which manages a vector based data sets.
Contains information about the context in which a processing algorithm is executed.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
virtual void pushInfo(const QString &info)
Pushes a general informational message from the algorithm.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.