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