QGIS API Documentation 4.3.0-Master (b46232e4f9a)
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
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 // TODO QGIS 5.0 -- remove this fallback to QgsProject.instance(). Layers should only be resolved through the layer stores
135 // in the context
136 auto getMapLayerFromProjectInstance = [&ml, identifier] {
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" ) : QObject::tr( "Cannot convert '%1' to CRS" ).arg( value.toString() ) );
175 }
176
177 return crs;
178}
179
180QTimeZone QgsExpressionUtils::getTimeZoneValue( const QVariant &value, QgsExpression *parent )
181{
182 if ( QgsVariantUtils::isNull( value ) )
183 {
184 return QTimeZone();
185 }
186
187 QTimeZone tz;
188 bool isTz = false;
189 if ( value.userType() == qMetaTypeId< QTimeZone>() )
190 {
191 isTz = true;
192 tz = value.value<QTimeZone>();
193 }
194
195 if ( !tz.isValid() )
196 {
197 parent->setEvalErrorString( isTz ? QObject::tr( "Input time zone is invalid" ) : QObject::tr( "Cannot convert '%1' to a time zone" ).arg( value.toString() ) );
198 }
199 return tz;
200}
201
202void QgsExpressionUtils::executeLambdaForMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<void( QgsMapLayer * )> &function, bool &foundLayer )
203{
204 foundLayer = false;
205
206 // clang analyzer gets this function absolutely 100% wrong
207#ifndef __clang_analyzer__
208
209 // First check if we already received a layer pointer
210 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
211 if ( !ml )
212 {
213 ml = value.value< QgsMapLayer * >();
214#ifdef QGISDEBUG
215 if ( ml )
216 {
217 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
218 }
219#endif
220 }
221 if ( ml )
222 {
223 auto runFunction = [layerPointer = QPointer< QgsMapLayer >( ml ), &function, &foundLayer] {
224 if ( QgsMapLayer *layer = layerPointer.data() )
225 {
226 foundLayer = true;
227 function( layer );
228 }
229 };
230
231 // Make sure we only deal with the layer on the thread where it lives.
232 // Anything else risks a crash.
233
234 if ( QThread::currentThread() == ml->thread() )
235 runFunction();
236 else
237 QMetaObject::invokeMethod( ml, std::move( runFunction ), Qt::BlockingQueuedConnection );
238
239 return;
240 }
241
242 if ( !context || context->layerStores().empty() )
243 {
244 // TODO QGIS 5.0 -- once the QgsProject.instance() fallback in getMapLayerPrivate() is removed, nothing can be resolved
245 // without layer stores, so this branch should just return with foundLayer = false
246 auto runFunction = [value, context, expression, &function, &foundLayer] {
247 if ( QgsMapLayer *layer = getMapLayerPrivate( value, context, expression ) )
248 {
249 foundLayer = true;
250 function( layer );
251 }
252 else
253 {
254 foundLayer = false;
255 }
256 };
257
258 // Make sure we only deal with the project on the thread where it lives.
259 // Anything else risks a crash.
260 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
261 runFunction();
262 else
263 QMetaObject::invokeMethod( QgsProject::instance(), runFunction, Qt::BlockingQueuedConnection ); // skip-keyword-check
264 }
265 else
266 {
267 // if layer stores, then we can't be certain in advance of which thread the layer will have affinity with.
268 // So we need to fetch the layer and then run the function on the layer's thread.
269
270 const QString identifier = value.toString();
271
272 // check through layer stores from context
273 const QList< QgsMapLayerStore * > stores = context->layerStores();
274
275 for ( QgsMapLayerStore *store : stores )
276 {
277 QPointer< QgsMapLayerStore > storePointer( store );
278 auto findLayerInStoreFunction = [storePointer = std::move( storePointer ), identifier, function, &foundLayer] {
279 QgsMapLayer *ml = nullptr;
280 if ( QgsMapLayerStore *store = storePointer.data() )
281 {
282 // look for matching layer by id
283 ml = store->mapLayer( identifier );
284 if ( !ml )
285 {
286 // Still nothing? Check for layer name
287 ml = store->mapLayersByName( identifier ).value( 0 );
288 }
289
290 if ( ml )
291 {
292 function( ml );
293 foundLayer = true;
294 }
295 }
296 };
297
298 // Make sure we only deal with the store on the thread where it lives.
299 // Anything else risks a crash.
300 if ( QThread::currentThread() == store->thread() )
301 findLayerInStoreFunction();
302 else
303 QMetaObject::invokeMethod( store, std::move( findLayerInStoreFunction ), Qt::BlockingQueuedConnection );
304
305 if ( foundLayer )
306 return;
307 }
308
309 // TODO QGIS 5.0 -- remove this fallback to QgsProject.instance(). Layers should only be resolved through the layer stores
310 // in the context
311 auto getMapLayerFromProjectInstance = [value, identifier, &function, &foundLayer] {
312 QgsProject *project = QgsProject::instance(); // skip-keyword-check
313
314 // maybe it's a layer id?
315 QgsMapLayer *ml = project->mapLayer( identifier );
316
317 // Still nothing? Check for layer name
318 if ( !ml )
319 {
320 ml = project->mapLayersByName( identifier ).value( 0 );
321 }
322
323 if ( ml )
324 {
325 foundLayer = true;
326 function( ml );
327 }
328 };
329
330 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
331 getMapLayerFromProjectInstance();
332 else
333 QMetaObject::invokeMethod( QgsProject::instance(), getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection ); // skip-keyword-check
334 }
335#endif
336}
337
338QVariant QgsExpressionUtils::runMapLayerFunctionThreadSafe(
339 const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<QVariant( QgsMapLayer * )> &function, bool &foundLayer
340)
341{
342 QVariant res;
343 foundLayer = false;
344
345 executeLambdaForMapLayer(
346 value,
347 context,
348 expression,
349 [&res, function]( QgsMapLayer *layer ) {
350 if ( layer )
351 res = function( layer );
352 },
353 foundLayer
354 );
355
356 return res;
357}
358
359std::unique_ptr<QgsVectorLayerFeatureSource> QgsExpressionUtils::getFeatureSource( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e, bool &foundLayer )
360{
361 std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;
362
363 executeLambdaForMapLayer(
364 value,
365 context,
366 e,
367 [&featureSource]( QgsMapLayer *layer ) {
368 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( layer ) )
369 {
370 featureSource = std::make_unique<QgsVectorLayerFeatureSource>( vl );
371 }
372 },
373 foundLayer
374 );
375
376 return featureSource;
377}
378
379QgsVectorLayer *QgsExpressionUtils::getVectorLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e )
380{
381 return qobject_cast<QgsVectorLayer *>( getMapLayerPrivate( value, context, e ) );
382}
383
384QString QgsExpressionUtils::getFilePathValue( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
385{
386 // if it's a map layer, return the file path of that layer...
387 QString res;
389 if ( QgsMapLayer *layer = getMapLayer( value, context, parent ) )
390 {
391 const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
392 res = parts.value( u"path"_s ).toString();
393 }
395
396 if ( res.isEmpty() )
397 res = value.toString();
398
399 if ( res.isEmpty() && !QgsVariantUtils::isNull( value ) )
400 {
401 parent->setEvalErrorString( QObject::tr( "Cannot convert value to a file path" ) );
402 }
403 return res;
404}
405
407
408std::tuple<QMetaType::Type, int> QgsExpressionUtils::determineResultType( const QString &expression, const QgsVectorLayer *layer, const QgsFeatureRequest &r, const QgsExpressionContext &c, bool *foundFeatures )
409{
410 QgsExpressionContext context = c;
411 QgsFeatureRequest request = r;
412 QgsExpression exp( expression );
414 request.setLimit( 10 );
415 request.setExpressionContext( context );
416
417 // avoid endless recursion by removing virtual fields while going through features
418 // to determine result type
419 QgsAttributeList attributes;
420 const QgsFields fields = layer->fields();
421 for ( int i = 0; i < fields.count(); i++ )
422 {
423 if ( fields.fieldOrigin( i ) != Qgis::FieldOrigin::Expression )
424 attributes << i;
425 }
426 request.setSubsetOfAttributes( attributes );
427
428 QVariant value;
429 QgsFeature f;
430 QgsFeatureIterator it = layer->getFeatures( request );
431 bool hasFeature = it.nextFeature( f );
432 if ( foundFeatures )
433 *foundFeatures = hasFeature;
434 while ( hasFeature )
435 {
436 context.setFeature( f );
437 const QVariant value = exp.evaluate( &context );
438 if ( !QgsVariantUtils::isNull( value ) )
439 {
440 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
441 }
442 hasFeature = it.nextFeature( f );
443 }
444 value = QVariant();
445 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
446}
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Definition qgis.h:2380
@ NoFlags
No flags are set.
Definition qgis.h:2379
@ Expression
Field is calculated from an expression.
Definition qgis.h:1879
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:45
int count
Definition qgsfields.h:49
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:44
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:8361
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:8360
QList< int > QgsAttributeList
Definition qgsfield.h:30
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.