QGIS API Documentation 3.41.0-Master (45a0abf3bec)
Loading...
Searching...
No Matches
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
106 QPointer< QgsMapLayerStore > storePointer( store );
107 auto findLayerInStoreFunction = [ storePointer, &ml, identifier ]
108 {
109 if ( QgsMapLayerStore *store = storePointer.data() )
110 {
111 // look for matching layer by id
112 ml = store->mapLayer( identifier );
113
114 if ( ml )
115 return;
116
117 // Still nothing? Check for layer name
118 ml = store->mapLayersByName( identifier ).value( 0 );
119 }
120 };
121
122 // Make sure we only deal with the store on the thread where it lives.
123 // Anything else risks a crash.
124 if ( QThread::currentThread() == store->thread() )
125 findLayerInStoreFunction();
126 else
127 QMetaObject::invokeMethod( store, findLayerInStoreFunction, Qt::BlockingQueuedConnection );
128 if ( ml )
129 return ml;
130 }
131 }
132
133 // last resort - QgsProject instance. This is bad, we need to remove this!
134 auto getMapLayerFromProjectInstance = [ &ml, identifier ]
135 {
136 QgsProject *project = QgsProject::instance(); // skip-keyword-check
137
138 // No pointer yet, maybe it's a layer id?
139 ml = project->mapLayer( identifier );
140 if ( ml )
141 return;
142
143 // Still nothing? Check for layer name
144 ml = project->mapLayersByName( identifier ).value( 0 );
145 };
146
147 if ( QThread::currentThread() == qApp->thread() )
148 getMapLayerFromProjectInstance();
149 else
150 QMetaObject::invokeMethod( qApp, getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection );
151#endif
152
153 return ml;
154}
155
156void QgsExpressionUtils::executeLambdaForMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<void ( QgsMapLayer * )> &function, bool &foundLayer )
157{
158 foundLayer = false;
159
160 // clang analyzer gets this function absolutely 100% wrong
161#ifndef __clang_analyzer__
162
163 // First check if we already received a layer pointer
164 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
165 if ( !ml )
166 {
167 ml = value.value< QgsMapLayer * >();
168#ifdef QGISDEBUG
169 if ( ml )
170 {
171 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
172 }
173#endif
174 }
175 if ( ml )
176 {
177 QPointer< QgsMapLayer > layerPointer( ml );
178 auto runFunction = [ layerPointer, &function, &foundLayer ]
179 {
180 if ( QgsMapLayer *layer = layerPointer.data() )
181 {
182 foundLayer = true;
183 function( layer );
184 }
185 };
186
187 // Make sure we only deal with the layer on the thread where it lives.
188 // Anything else risks a crash.
189
190 if ( QThread::currentThread() == ml->thread() )
191 runFunction();
192 else
193 QMetaObject::invokeMethod( ml, runFunction, Qt::BlockingQueuedConnection );
194
195 return;
196 }
197
198 if ( !context || context->layerStores().empty() )
199 {
200 // if no layer stores, then this is only for layers in project and therefore associated with the main thread
201 auto runFunction = [ value, context, expression, &function, &foundLayer ]
202 {
203 if ( QgsMapLayer *layer = getMapLayerPrivate( value, context, expression ) )
204 {
205 foundLayer = true;
206 function( layer );
207 }
208 else
209 {
210 foundLayer = false;
211 }
212 };
213
214 // Make sure we only deal with the project on the thread where it lives.
215 // Anything else risks a crash.
216 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
217 runFunction();
218 else
219 QMetaObject::invokeMethod( QgsProject::instance(), runFunction, Qt::BlockingQueuedConnection ); // skip-keyword-check
220 }
221 else
222 {
223 // if layer stores, then we can't be certain in advance of which thread the layer will have affinity with.
224 // So we need to fetch the layer and then run the function on the layer's thread.
225
226 const QString identifier = value.toString();
227
228 // check through layer stores from context
229 const QList< QgsMapLayerStore * > stores = context->layerStores();
230
231 for ( QgsMapLayerStore *store : stores )
232 {
233 QPointer< QgsMapLayerStore > storePointer( store );
234 auto findLayerInStoreFunction = [ storePointer, identifier, function, &foundLayer ]
235 {
236 QgsMapLayer *ml = nullptr;
237 if ( QgsMapLayerStore *store = storePointer.data() )
238 {
239 // look for matching layer by id
240 ml = store->mapLayer( identifier );
241 if ( !ml )
242 {
243 // Still nothing? Check for layer name
244 ml = store->mapLayersByName( identifier ).value( 0 );
245 }
246
247 if ( ml )
248 {
249 function( ml );
250 foundLayer = true;
251 }
252 }
253 };
254
255 // Make sure we only deal with the store on the thread where it lives.
256 // Anything else risks a crash.
257 if ( QThread::currentThread() == store->thread() )
258 findLayerInStoreFunction();
259 else
260 QMetaObject::invokeMethod( store, findLayerInStoreFunction, Qt::BlockingQueuedConnection );
261
262 if ( foundLayer )
263 return;
264 }
265
266 // last resort - QgsProject instance. This is bad, we need to remove this!
267 auto getMapLayerFromProjectInstance = [ value, identifier, &function, &foundLayer ]
268 {
269 QgsProject *project = QgsProject::instance(); // skip-keyword-check
270
271 // maybe it's a layer id?
272 QgsMapLayer *ml = project->mapLayer( identifier );
273
274 // Still nothing? Check for layer name
275 if ( !ml )
276 {
277 ml = project->mapLayersByName( identifier ).value( 0 );
278 }
279
280 if ( ml )
281 {
282 foundLayer = true;
283 function( ml );
284 }
285 };
286
287 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
288 getMapLayerFromProjectInstance();
289 else
290 QMetaObject::invokeMethod( QgsProject::instance(), getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection ); // skip-keyword-check
291 }
292#endif
293}
294
295QVariant QgsExpressionUtils::runMapLayerFunctionThreadSafe( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<QVariant( QgsMapLayer * )> &function, bool &foundLayer )
296{
297 QVariant res;
298 foundLayer = false;
299
300 executeLambdaForMapLayer( value, context, expression, [&res, function]( QgsMapLayer * layer )
301 {
302 if ( layer )
303 res = function( layer );
304 }, foundLayer );
305
306 return res;
307}
308
309std::unique_ptr<QgsVectorLayerFeatureSource> QgsExpressionUtils::getFeatureSource( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e, bool &foundLayer )
310{
311 std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;
312
313 executeLambdaForMapLayer( value, context, e, [&featureSource]( QgsMapLayer * layer )
314 {
315 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( layer ) )
316 {
317 featureSource.reset( new QgsVectorLayerFeatureSource( vl ) );
318 }
319 }, foundLayer );
320
321 return featureSource;
322}
323
324QgsVectorLayer *QgsExpressionUtils::getVectorLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e )
325{
326 return qobject_cast<QgsVectorLayer *>( getMapLayerPrivate( value, context, e ) );
327}
328
329QString QgsExpressionUtils::getFilePathValue( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
330{
331 // if it's a map layer, return the file path of that layer...
332 QString res;
334 if ( QgsMapLayer *layer = getMapLayer( value, context, parent ) )
335 {
336 const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
337 res = parts.value( QStringLiteral( "path" ) ).toString();
338 }
340
341 if ( res.isEmpty() )
342 res = value.toString();
343
344 if ( res.isEmpty() && !QgsVariantUtils::isNull( value ) )
345 {
346 parent->setEvalErrorString( QObject::tr( "Cannot convert value to a file path" ) );
347 }
348 return res;
349}
350
352
353std::tuple<QMetaType::Type, int> QgsExpressionUtils::determineResultType( const QString &expression, const QgsVectorLayer *layer, QgsFeatureRequest request, QgsExpressionContext context, bool *foundFeatures )
354{
355 QgsExpression exp( expression );
356 request.setFlags( ( exp.needsGeometry() ) ?
359 request.setLimit( 10 );
360 request.setExpressionContext( context );
361
362 // avoid endless recursion by removing virtual fields while going through features
363 // to determine result type
364 QgsAttributeList attributes;
365 const QgsFields fields = layer->fields();
366 for ( int i = 0; i < fields.count(); i++ )
367 {
368 if ( fields.fieldOrigin( i ) != Qgis::FieldOrigin::Expression )
369 attributes << i;
370 }
371 request.setSubsetOfAttributes( attributes );
372
373 QVariant value;
374 QgsFeature f;
375 QgsFeatureIterator it = layer->getFeatures( request );
376 bool hasFeature = it.nextFeature( f );
377 if ( foundFeatures )
378 *foundFeatures = hasFeature;
379 while ( hasFeature )
380 {
381 context.setFeature( f );
382 const QVariant value = exp.evaluate( &context );
383 if ( !QgsVariantUtils::isNull( value ) )
384 {
385 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
386 }
387 hasFeature = it.nextFeature( f );
388 }
389 value = QVariant();
390 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
391}
@ 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.
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, QgsFeatureRequest request=QgsFeatureRequest(), QgsExpressionContext context=QgsExpressionContext(), bool *foundFeatures=nullptr)
Returns a value type and user type for a given expression.
Class for 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.
This class 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:76
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 data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
@ Unknown
Unknown/invalid format.
#define Q_NOWARN_DEPRECATED_POP
Definition qgis.h:6535
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6534
QList< int > QgsAttributeList
Definition qgsfield.h:27
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.