QGIS API Documentation 3.99.0-Master (2fe06baccd8)
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
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
18#include <memory>
19
20#include "qgscolorrampimpl.h"
21#include "qgsproject.h"
22#include "qgsproviderregistry.h"
23#include "qgssymbollayerutils.h"
24#include "qgsvariantutils.h"
25#include "qgsvectorlayer.h"
27
29
30QgsExpressionUtils::TVL QgsExpressionUtils::AND[3][3] =
31{
32 // false true unknown
33 { False, False, False }, // false
34 { False, True, Unknown }, // true
35 { False, Unknown, Unknown } // unknown
36};
37QgsExpressionUtils::TVL QgsExpressionUtils::OR[3][3] =
38{
39 { False, True, Unknown }, // false
40 { True, True, True }, // true
41 { Unknown, True, Unknown } // unknown
42};
43
44QgsExpressionUtils::TVL QgsExpressionUtils::NOT[3] = { True, False, Unknown };
45
46
47QColor QgsExpressionUtils::getColorValue( const QVariant &value, QgsExpression *parent, bool &isQColor )
48{
49 isQColor = value.userType() == QMetaType::Type::QColor;
50 QColor color = isQColor ? value.value<QColor>() : QgsSymbolLayerUtils::decodeColor( value.toString() );
51 if ( ! color.isValid() )
52 {
53 parent->setEvalErrorString( isQColor ? QObject::tr( "Input color is invalid" )
54 : QObject::tr( "Cannot convert '%1' to color" ).arg( value.toString() ) );
55 }
56
57 return color;
58}
59
60QgsGradientColorRamp QgsExpressionUtils::getRamp( const QVariant &value, QgsExpression *parent, bool report_error )
61{
62 if ( value.userType() == qMetaTypeId<QgsGradientColorRamp>() )
63 return value.value<QgsGradientColorRamp>();
64
65 // If we get here then we can't convert so we just error and return invalid.
66 if ( report_error )
67 parent->setEvalErrorString( QObject::tr( "Cannot convert '%1' to gradient ramp" ).arg( value.toString() ) );
68
69 return QgsGradientColorRamp();
70}
71
72QgsMapLayer *QgsExpressionUtils::getMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
73{
74 return getMapLayerPrivate( value, context, parent );
75}
76
77QgsMapLayer *QgsExpressionUtils::getMapLayerPrivate( const QVariant &value, const QgsExpressionContext *context, QgsExpression * )
78{
79 // First check if we already received a layer pointer
80 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
81
82 // clang analyzer gets this function absolutely 100% wrong
83#ifdef __clang_analyzer__
84 ( void )context;
85#else
86
87 if ( !ml )
88 {
89 ml = value.value< QgsMapLayer * >();
90#ifdef QGISDEBUG
91 if ( ml )
92 {
93 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
94 }
95#endif
96 }
97 if ( ml )
98 return ml;
99
100 const QString identifier = value.toString();
101
102 // check through layer stores from context
103 if ( context )
104 {
105 const QList< QgsMapLayerStore * > stores = context->layerStores();
106 for ( QgsMapLayerStore *store : stores )
107 {
108 auto findLayerInStoreFunction = [ storePointer = QPointer< QgsMapLayerStore >( store ), &ml, identifier ]
109 {
110 if ( QgsMapLayerStore *store = storePointer.data() )
111 {
112 // look for matching layer by id
113 ml = store->mapLayer( identifier );
114
115 if ( ml )
116 return;
117
118 // Still nothing? Check for layer name
119 ml = store->mapLayersByName( identifier ).value( 0 );
120 }
121 };
122
123 // Make sure we only deal with the store on the thread where it lives.
124 // Anything else risks a crash.
125 if ( QThread::currentThread() == store->thread() )
126 findLayerInStoreFunction();
127 else
128 QMetaObject::invokeMethod( store, std::move( findLayerInStoreFunction ), Qt::BlockingQueuedConnection );
129 if ( ml )
130 return ml;
131 }
132 }
133
134 // last resort - QgsProject instance. This is bad, we need to remove this!
135 auto getMapLayerFromProjectInstance = [ &ml, identifier ]
136 {
137 QgsProject *project = QgsProject::instance(); // skip-keyword-check
138
139 // No pointer yet, maybe it's a layer id?
140 ml = project->mapLayer( identifier );
141 if ( ml )
142 return;
143
144 // Still nothing? Check for layer name
145 ml = project->mapLayersByName( identifier ).value( 0 );
146 };
147
148 if ( QThread::currentThread() == qApp->thread() )
149 getMapLayerFromProjectInstance();
150 else
151 QMetaObject::invokeMethod( qApp, std::move( getMapLayerFromProjectInstance ), Qt::BlockingQueuedConnection );
152#endif
153
154 return ml;
155}
156
157QgsCoordinateReferenceSystem QgsExpressionUtils::getCrsValue( const QVariant &value, QgsExpression *parent )
158{
159 if ( QgsVariantUtils::isNull( value ) )
160 {
161 return QgsCoordinateReferenceSystem();
162 }
163
164 const bool isCrs = value.userType() == qMetaTypeId<QgsCoordinateReferenceSystem>();
165
166 if ( !isCrs && value.toString().isEmpty() )
167 {
168 return QgsCoordinateReferenceSystem();
169 }
170
171 QgsCoordinateReferenceSystem crs = isCrs ? value.value<QgsCoordinateReferenceSystem>() : QgsCoordinateReferenceSystem( value.toString() );
172 if ( !crs.isValid() )
173 {
174 parent->setEvalErrorString( isCrs ? QObject::tr( "Input CRS is invalid" )
175 : QObject::tr( "Cannot convert '%1' to CRS" ).arg( value.toString() ) );
176 }
177
178 return crs;
179}
180
181QTimeZone QgsExpressionUtils::getTimeZoneValue( const QVariant &value, QgsExpression *parent )
182{
183 if ( QgsVariantUtils::isNull( value ) )
184 {
185 return QTimeZone();
186 }
187
188 QTimeZone tz;
189 bool isTz = false;
190 if ( value.userType() == qMetaTypeId< QTimeZone>() )
191 {
192 isTz = true;
193 tz = value.value<QTimeZone>();
194 }
195
196 if ( !tz.isValid() )
197 {
198 parent->setEvalErrorString( isTz ? QObject::tr( "Input time zone is invalid" )
199 : QObject::tr( "Cannot convert '%1' to a time zone" ).arg( value.toString() ) );
200 }
201 return tz;
202}
203
204void QgsExpressionUtils::executeLambdaForMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<void ( QgsMapLayer * )> &function, bool &foundLayer )
205{
206 foundLayer = false;
207
208 // clang analyzer gets this function absolutely 100% wrong
209#ifndef __clang_analyzer__
210
211 // First check if we already received a layer pointer
212 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
213 if ( !ml )
214 {
215 ml = value.value< QgsMapLayer * >();
216#ifdef QGISDEBUG
217 if ( ml )
218 {
219 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
220 }
221#endif
222 }
223 if ( ml )
224 {
225 auto runFunction = [ layerPointer = QPointer< QgsMapLayer >( ml ), &function, &foundLayer ]
226 {
227 if ( QgsMapLayer *layer = layerPointer.data() )
228 {
229 foundLayer = true;
230 function( layer );
231 }
232 };
233
234 // Make sure we only deal with the layer on the thread where it lives.
235 // Anything else risks a crash.
236
237 if ( QThread::currentThread() == ml->thread() )
238 runFunction();
239 else
240 QMetaObject::invokeMethod( ml, std::move( runFunction ), Qt::BlockingQueuedConnection );
241
242 return;
243 }
244
245 if ( !context || context->layerStores().empty() )
246 {
247 // if no layer stores, then this is only for layers in project and therefore associated with the main thread
248 auto runFunction = [ value, context, expression, &function, &foundLayer ]
249 {
250 if ( QgsMapLayer *layer = getMapLayerPrivate( value, context, expression ) )
251 {
252 foundLayer = true;
253 function( layer );
254 }
255 else
256 {
257 foundLayer = false;
258 }
259 };
260
261 // Make sure we only deal with the project on the thread where it lives.
262 // Anything else risks a crash.
263 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
264 runFunction();
265 else
266 QMetaObject::invokeMethod( QgsProject::instance(), runFunction, Qt::BlockingQueuedConnection ); // skip-keyword-check
267 }
268 else
269 {
270 // if layer stores, then we can't be certain in advance of which thread the layer will have affinity with.
271 // So we need to fetch the layer and then run the function on the layer's thread.
272
273 const QString identifier = value.toString();
274
275 // check through layer stores from context
276 const QList< QgsMapLayerStore * > stores = context->layerStores();
277
278 for ( QgsMapLayerStore *store : stores )
279 {
280 QPointer< QgsMapLayerStore > storePointer( store );
281 auto findLayerInStoreFunction = [ storePointer = std::move( storePointer ), identifier, function, &foundLayer ]
282 {
283 QgsMapLayer *ml = nullptr;
284 if ( QgsMapLayerStore *store = storePointer.data() )
285 {
286 // look for matching layer by id
287 ml = store->mapLayer( identifier );
288 if ( !ml )
289 {
290 // Still nothing? Check for layer name
291 ml = store->mapLayersByName( identifier ).value( 0 );
292 }
293
294 if ( ml )
295 {
296 function( ml );
297 foundLayer = true;
298 }
299 }
300 };
301
302 // Make sure we only deal with the store on the thread where it lives.
303 // Anything else risks a crash.
304 if ( QThread::currentThread() == store->thread() )
305 findLayerInStoreFunction();
306 else
307 QMetaObject::invokeMethod( store, std::move( findLayerInStoreFunction ), Qt::BlockingQueuedConnection );
308
309 if ( foundLayer )
310 return;
311 }
312
313 // last resort - QgsProject instance. This is bad, we need to remove this!
314 auto getMapLayerFromProjectInstance = [ value, identifier, &function, &foundLayer ]
315 {
316 QgsProject *project = QgsProject::instance(); // skip-keyword-check
317
318 // maybe it's a layer id?
319 QgsMapLayer *ml = project->mapLayer( identifier );
320
321 // Still nothing? Check for layer name
322 if ( !ml )
323 {
324 ml = project->mapLayersByName( identifier ).value( 0 );
325 }
326
327 if ( ml )
328 {
329 foundLayer = true;
330 function( ml );
331 }
332 };
333
334 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
335 getMapLayerFromProjectInstance();
336 else
337 QMetaObject::invokeMethod( QgsProject::instance(), getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection ); // skip-keyword-check
338 }
339#endif
340}
341
342QVariant QgsExpressionUtils::runMapLayerFunctionThreadSafe( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<QVariant( QgsMapLayer * )> &function, bool &foundLayer )
343{
344 QVariant res;
345 foundLayer = false;
346
347 executeLambdaForMapLayer( value, context, expression, [&res, function]( QgsMapLayer * layer )
348 {
349 if ( layer )
350 res = function( layer );
351 }, foundLayer );
352
353 return res;
354}
355
356std::unique_ptr<QgsVectorLayerFeatureSource> QgsExpressionUtils::getFeatureSource( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e, bool &foundLayer )
357{
358 std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;
359
360 executeLambdaForMapLayer( value, context, e, [&featureSource]( QgsMapLayer * layer )
361 {
362 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( layer ) )
363 {
364 featureSource = std::make_unique<QgsVectorLayerFeatureSource>( vl );
365 }
366 }, foundLayer );
367
368 return featureSource;
369}
370
371QgsVectorLayer *QgsExpressionUtils::getVectorLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e )
372{
373 return qobject_cast<QgsVectorLayer *>( getMapLayerPrivate( value, context, e ) );
374}
375
376QString QgsExpressionUtils::getFilePathValue( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
377{
378 // if it's a map layer, return the file path of that layer...
379 QString res;
381 if ( QgsMapLayer *layer = getMapLayer( value, context, parent ) )
382 {
383 const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
384 res = parts.value( QStringLiteral( "path" ) ).toString();
385 }
387
388 if ( res.isEmpty() )
389 res = value.toString();
390
391 if ( res.isEmpty() && !QgsVariantUtils::isNull( value ) )
392 {
393 parent->setEvalErrorString( QObject::tr( "Cannot convert value to a file path" ) );
394 }
395 return res;
396}
397
399
400std::tuple<QMetaType::Type, int> QgsExpressionUtils::determineResultType( const QString &expression, const QgsVectorLayer *layer, const QgsFeatureRequest &r, const QgsExpressionContext &c, bool *foundFeatures )
401{
402 QgsExpressionContext context = c;
403 QgsFeatureRequest request = r;
404 QgsExpression exp( expression );
405 request.setFlags( ( exp.needsGeometry() ) ?
408 request.setLimit( 10 );
409 request.setExpressionContext( context );
410
411 // avoid endless recursion by removing virtual fields while going through features
412 // to determine result type
413 QgsAttributeList attributes;
414 const QgsFields fields = layer->fields();
415 for ( int i = 0; i < fields.count(); i++ )
416 {
417 if ( fields.fieldOrigin( i ) != Qgis::FieldOrigin::Expression )
418 attributes << i;
419 }
420 request.setSubsetOfAttributes( attributes );
421
422 QVariant value;
423 QgsFeature f;
424 QgsFeatureIterator it = layer->getFeatures( request );
425 bool hasFeature = it.nextFeature( f );
426 if ( foundFeatures )
427 *foundFeatures = hasFeature;
428 while ( hasFeature )
429 {
430 context.setFeature( f );
431 const QVariant value = exp.evaluate( &context );
432 if ( !QgsVariantUtils::isNull( value ) )
433 {
434 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
435 }
436 hasFeature = it.nextFeature( f );
437 }
438 value = QVariant();
439 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
440}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2196
@ NoFlags
No flags are set.
Definition qgis.h:2195
@ Expression
Field is calculated from an expression.
Definition qgis.h:1709
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.
Base class for all map layer types.
Definition qgsmaplayer.h:80
QString source() const
Returns the source for the layer.
QString providerType() const
Returns the provider type (provider key) for this layer.
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.
static bool isNull(const QVariant &variant, bool silenceNullWarnings=false)
Returns true if the specified variant should be considered a NULL value.
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.
Definition qgswmsutils.h:42
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:7170
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:7169
QList< int > QgsAttributeList
Definition qgsfield.h:28
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.