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