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