QGIS API Documentation 3.43.0-Master (5df50c54ce9)
qgsexpressionutils.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsexpressionutils.cpp
3 -------------------
4 begin : May 2017
5 copyright : (C) 2017 Matthias Kuhn
6 email : matthias@opengis.ch
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
16#include "qgsexpressionutils.h"
17#include "qgsvectorlayer.h"
18#include "qgscolorrampimpl.h"
19#include "qgsproviderregistry.h"
20#include "qgsvariantutils.h"
21#include "qgsproject.h"
23#include "qgssymbollayerutils.h"
24
26
27QgsExpressionUtils::TVL QgsExpressionUtils::AND[3][3] =
28{
29 // false true unknown
30 { False, False, False }, // false
31 { False, True, Unknown }, // true
32 { False, Unknown, Unknown } // unknown
33};
34QgsExpressionUtils::TVL QgsExpressionUtils::OR[3][3] =
35{
36 { False, True, Unknown }, // false
37 { True, True, True }, // true
38 { Unknown, True, Unknown } // unknown
39};
40
41QgsExpressionUtils::TVL QgsExpressionUtils::NOT[3] = { True, False, Unknown };
42
43
44QColor QgsExpressionUtils::getColorValue( const QVariant &value, QgsExpression *parent, bool &isQColor )
45{
46 isQColor = value.userType() == QMetaType::Type::QColor;
47 QColor color = isQColor ? value.value<QColor>() : QgsSymbolLayerUtils::decodeColor( value.toString() );
48 if ( ! color.isValid() )
49 {
50 parent->setEvalErrorString( isQColor ? QObject::tr( "Input color is invalid" )
51 : QObject::tr( "Cannot convert '%1' to color" ).arg( value.toString() ) );
52 }
53
54 return color;
55}
56
57QgsGradientColorRamp QgsExpressionUtils::getRamp( const QVariant &value, QgsExpression *parent, bool report_error )
58{
59 if ( value.userType() == qMetaTypeId<QgsGradientColorRamp>() )
60 return value.value<QgsGradientColorRamp>();
61
62 // If we get here then we can't convert so we just error and return invalid.
63 if ( report_error )
64 parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to gradient ramp" ).arg( value.toString() ) );
65
66 return QgsGradientColorRamp();
67}
68
69QgsMapLayer *QgsExpressionUtils::getMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
70{
71 return getMapLayerPrivate( value, context, parent );
72}
73
74QgsMapLayer *QgsExpressionUtils::getMapLayerPrivate( const QVariant &value, const QgsExpressionContext *context, QgsExpression * )
75{
76 // First check if we already received a layer pointer
77 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
78
79 // clang analyzer gets this function absolutely 100% wrong
80#ifdef __clang_analyzer__
81 ( void )context;
82#else
83
84 if ( !ml )
85 {
86 ml = value.value< QgsMapLayer * >();
87#ifdef QGISDEBUG
88 if ( ml )
89 {
90 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
91 }
92#endif
93 }
94 if ( ml )
95 return ml;
96
97 const QString identifier = value.toString();
98
99 // check through layer stores from context
100 if ( context )
101 {
102 const QList< QgsMapLayerStore * > stores = context->layerStores();
103 for ( QgsMapLayerStore *store : stores )
104 {
105 auto findLayerInStoreFunction = [ storePointer = QPointer< QgsMapLayerStore >( store ), &ml, identifier ]
106 {
107 if ( QgsMapLayerStore *store = storePointer.data() )
108 {
109 // look for matching layer by id
110 ml = store->mapLayer( identifier );
111
112 if ( ml )
113 return;
114
115 // Still nothing? Check for layer name
116 ml = store->mapLayersByName( identifier ).value( 0 );
117 }
118 };
119
120 // Make sure we only deal with the store on the thread where it lives.
121 // Anything else risks a crash.
122 if ( QThread::currentThread() == store->thread() )
123 findLayerInStoreFunction();
124 else
125 QMetaObject::invokeMethod( store, findLayerInStoreFunction, Qt::BlockingQueuedConnection );
126 if ( ml )
127 return ml;
128 }
129 }
130
131 // last resort - QgsProject instance. This is bad, we need to remove this!
132 auto getMapLayerFromProjectInstance = [ &ml, identifier ]
133 {
134 QgsProject *project = QgsProject::instance(); // skip-keyword-check
135
136 // No pointer yet, maybe it's a layer id?
137 ml = project->mapLayer( identifier );
138 if ( ml )
139 return;
140
141 // Still nothing? Check for layer name
142 ml = project->mapLayersByName( identifier ).value( 0 );
143 };
144
145 if ( QThread::currentThread() == qApp->thread() )
146 getMapLayerFromProjectInstance();
147 else
148 QMetaObject::invokeMethod( qApp, std::move( getMapLayerFromProjectInstance ), Qt::BlockingQueuedConnection );
149#endif
150
151 return ml;
152}
153
154QgsCoordinateReferenceSystem QgsExpressionUtils::getCrsValue( const QVariant &value, QgsExpression *parent )
155{
156 if ( QgsVariantUtils::isNull( value ) )
157 {
159 }
160
161 const bool isCrs = value.userType() == qMetaTypeId<QgsCoordinateReferenceSystem>();
162
163 if ( !isCrs && value.toString().isEmpty() )
164 {
166 }
167
169 if ( !crs.isValid() )
170 {
171 parent->setEvalErrorString( isCrs ? QObject::tr( "Input CRS is invalid" )
172 : QObject::tr( "Cannot convert '%1' to CRS" ).arg( value.toString() ) );
173 }
174
175 return crs;
176}
177
178
179void QgsExpressionUtils::executeLambdaForMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<void ( QgsMapLayer * )> &function, bool &foundLayer )
180{
181 foundLayer = false;
182
183 // clang analyzer gets this function absolutely 100% wrong
184#ifndef __clang_analyzer__
185
186 // First check if we already received a layer pointer
187 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
188 if ( !ml )
189 {
190 ml = value.value< QgsMapLayer * >();
191#ifdef QGISDEBUG
192 if ( ml )
193 {
194 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
195 }
196#endif
197 }
198 if ( ml )
199 {
200 auto runFunction = [ layerPointer = QPointer< QgsMapLayer >( ml ), &function, &foundLayer ]
201 {
202 if ( QgsMapLayer *layer = layerPointer.data() )
203 {
204 foundLayer = true;
205 function( layer );
206 }
207 };
208
209 // Make sure we only deal with the layer on the thread where it lives.
210 // Anything else risks a crash.
211
212 if ( QThread::currentThread() == ml->thread() )
213 runFunction();
214 else
215 QMetaObject::invokeMethod( ml, std::move( runFunction ), Qt::BlockingQueuedConnection );
216
217 return;
218 }
219
220 if ( !context || context->layerStores().empty() )
221 {
222 // if no layer stores, then this is only for layers in project and therefore associated with the main thread
223 auto runFunction = [ value, context, expression, &function, &foundLayer ]
224 {
225 if ( QgsMapLayer *layer = getMapLayerPrivate( value, context, expression ) )
226 {
227 foundLayer = true;
228 function( layer );
229 }
230 else
231 {
232 foundLayer = false;
233 }
234 };
235
236 // Make sure we only deal with the project on the thread where it lives.
237 // Anything else risks a crash.
238 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
239 runFunction();
240 else
241 QMetaObject::invokeMethod( QgsProject::instance(), runFunction, Qt::BlockingQueuedConnection ); // skip-keyword-check
242 }
243 else
244 {
245 // if layer stores, then we can't be certain in advance of which thread the layer will have affinity with.
246 // So we need to fetch the layer and then run the function on the layer's thread.
247
248 const QString identifier = value.toString();
249
250 // check through layer stores from context
251 const QList< QgsMapLayerStore * > stores = context->layerStores();
252
253 for ( QgsMapLayerStore *store : stores )
254 {
255 QPointer< QgsMapLayerStore > storePointer( store );
256 auto findLayerInStoreFunction = [ storePointer, identifier, function, &foundLayer ]
257 {
258 QgsMapLayer *ml = nullptr;
259 if ( QgsMapLayerStore *store = storePointer.data() )
260 {
261 // look for matching layer by id
262 ml = store->mapLayer( identifier );
263 if ( !ml )
264 {
265 // Still nothing? Check for layer name
266 ml = store->mapLayersByName( identifier ).value( 0 );
267 }
268
269 if ( ml )
270 {
271 function( ml );
272 foundLayer = true;
273 }
274 }
275 };
276
277 // Make sure we only deal with the store on the thread where it lives.
278 // Anything else risks a crash.
279 if ( QThread::currentThread() == store->thread() )
280 findLayerInStoreFunction();
281 else
282 QMetaObject::invokeMethod( store, std::move( findLayerInStoreFunction ), Qt::BlockingQueuedConnection );
283
284 if ( foundLayer )
285 return;
286 }
287
288 // last resort - QgsProject instance. This is bad, we need to remove this!
289 auto getMapLayerFromProjectInstance = [ value, identifier, &function, &foundLayer ]
290 {
291 QgsProject *project = QgsProject::instance(); // skip-keyword-check
292
293 // maybe it's a layer id?
294 QgsMapLayer *ml = project->mapLayer( identifier );
295
296 // Still nothing? Check for layer name
297 if ( !ml )
298 {
299 ml = project->mapLayersByName( identifier ).value( 0 );
300 }
301
302 if ( ml )
303 {
304 foundLayer = true;
305 function( ml );
306 }
307 };
308
309 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
310 getMapLayerFromProjectInstance();
311 else
312 QMetaObject::invokeMethod( QgsProject::instance(), getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection ); // skip-keyword-check
313 }
314#endif
315}
316
317QVariant QgsExpressionUtils::runMapLayerFunctionThreadSafe( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<QVariant( QgsMapLayer * )> &function, bool &foundLayer )
318{
319 QVariant res;
320 foundLayer = false;
321
322 executeLambdaForMapLayer( value, context, expression, [&res, function]( QgsMapLayer * layer )
323 {
324 if ( layer )
325 res = function( layer );
326 }, foundLayer );
327
328 return res;
329}
330
331std::unique_ptr<QgsVectorLayerFeatureSource> QgsExpressionUtils::getFeatureSource( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e, bool &foundLayer )
332{
333 std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;
334
335 executeLambdaForMapLayer( value, context, e, [&featureSource]( QgsMapLayer * layer )
336 {
337 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( layer ) )
338 {
339 featureSource.reset( new QgsVectorLayerFeatureSource( vl ) );
340 }
341 }, foundLayer );
342
343 return featureSource;
344}
345
346QgsVectorLayer *QgsExpressionUtils::getVectorLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e )
347{
348 return qobject_cast<QgsVectorLayer *>( getMapLayerPrivate( value, context, e ) );
349}
350
351QString QgsExpressionUtils::getFilePathValue( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
352{
353 // if it's a map layer, return the file path of that layer...
354 QString res;
356 if ( QgsMapLayer *layer = getMapLayer( value, context, parent ) )
357 {
358 const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
359 res = parts.value( QStringLiteral( "path" ) ).toString();
360 }
362
363 if ( res.isEmpty() )
364 res = value.toString();
365
366 if ( res.isEmpty() && !QgsVariantUtils::isNull( value ) )
367 {
368 parent->setEvalErrorString( QObject::tr( "Cannot convert value to a file path" ) );
369 }
370 return res;
371}
372
374
375std::tuple<QMetaType::Type, int> QgsExpressionUtils::determineResultType( const QString &expression, const QgsVectorLayer *layer, const QgsFeatureRequest &r, const QgsExpressionContext &c, bool *foundFeatures )
376{
377 QgsExpressionContext context = c;
378 QgsFeatureRequest request = r;
379 QgsExpression exp( expression );
380 request.setFlags( ( exp.needsGeometry() ) ?
383 request.setLimit( 10 );
384 request.setExpressionContext( context );
385
386 // avoid endless recursion by removing virtual fields while going through features
387 // to determine result type
388 QgsAttributeList attributes;
389 const QgsFields fields = layer->fields();
390 for ( int i = 0; i < fields.count(); i++ )
391 {
392 if ( fields.fieldOrigin( i ) != Qgis::FieldOrigin::Expression )
393 attributes << i;
394 }
395 request.setSubsetOfAttributes( attributes );
396
397 QVariant value;
398 QgsFeature f;
399 QgsFeatureIterator it = layer->getFeatures( request );
400 bool hasFeature = it.nextFeature( f );
401 if ( foundFeatures )
402 *foundFeatures = hasFeature;
403 while ( hasFeature )
404 {
405 context.setFeature( f );
406 const QVariant value = exp.evaluate( &context );
407 if ( !QgsVariantUtils::isNull( value ) )
408 {
409 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
410 }
411 hasFeature = it.nextFeature( f );
412 }
413 value = QVariant();
414 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
415}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
@ NoFlags
No flags are set.
@ Expression
Field is calculated from an expression.
Represents a coordinate reference system (CRS).
bool isValid() const
Returns whether this CRS is correctly initialized and usable.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
QList< QgsMapLayerStore * > layerStores() const
Returns the list of layer stores associated with the context.
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
static std::tuple< QMetaType::Type, int > determineResultType(const QString &expression, const QgsVectorLayer *layer, const QgsFeatureRequest &request=QgsFeatureRequest(), const QgsExpressionContext &context=QgsExpressionContext(), bool *foundFeatures=nullptr)
Returns a value type and user type for a given expression.
Handles parsing and evaluation of expressions (formerly called "search strings").
void setEvalErrorString(const QString &str)
Sets evaluation error (used internally by evaluation functions)
bool needsGeometry() const
Returns true if the expression uses feature geometry for some computation.
QVariant evaluate()
Evaluate the feature and return the result.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
Fetch next feature and stores in f, returns true on success.
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 & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
QgsFeatureRequest & setExpressionContext(const QgsExpressionContext &context)
Sets the expression context used to evaluate filter expressions.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition qgsfeature.h:58
Container of fields for a vector layer.
Definition qgsfields.h:46
int count
Definition qgsfields.h:50
Qgis::FieldOrigin fieldOrigin(int fieldIdx) const
Returns the field's origin (value from an enumeration).
Gradient color ramp, which smoothly interpolates between two colors and also supports optional extra ...
double value(int index) const override
Returns relative value between [0,1] of color at specified index.
A storage object for map layers, in which the layers are owned by the store and have their lifetime b...
Base class for all map layer types.
Definition qgsmaplayer.h:77
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
Encapsulates a QGIS project, including sets of map layers and their styles, layouts,...
Definition qgsproject.h:107
static QgsProject * instance()
Returns the QgsProject singleton instance.
Q_INVOKABLE QgsMapLayer * mapLayer(const QString &layerId) const
Retrieve a pointer to a registered layer by layer ID.
Q_INVOKABLE QList< QgsMapLayer * > mapLayersByName(const QString &layerName) const
Retrieve a list of matching registered layers by layer name.
QVariantMap decodeUri(const QString &providerKey, const QString &uri)
Breaks a provider data source URI into its component paths (e.g.
static QgsProviderRegistry * instance(const QString &pluginPath=QString())
Means of accessing canonical single instance.
Contains utility functions for working with symbols and symbol layers.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
Partial snapshot of vector layer's state (only the members necessary for access to features).
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.
@ Unknown
Unknown/invalid format.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into c
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6820
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6819
QList< int > QgsAttributeList
Definition qgsfield.h:27
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
const QgsCoordinateReferenceSystem & crs