QGIS API Documentation 3.41.0-Master (88383c3d16f)
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
156QgsCoordinateReferenceSystem QgsExpressionUtils::getCrsValue( const QVariant &value, QgsExpression *parent )
157{
158 if ( QgsVariantUtils::isNull( value ) )
159 {
161 }
162
163 const bool isCrs = value.userType() == qMetaTypeId<QgsCoordinateReferenceSystem>();
164
165 if ( !isCrs && value.toString().isEmpty() )
166 {
168 }
169
171 if ( !crs.isValid() )
172 {
173 parent->setEvalErrorString( isCrs ? QObject::tr( "Input CRS is invalid" )
174 : QObject::tr( "Cannot convert '%1' to CRS" ).arg( value.toString() ) );
175 }
176
177 return crs;
178}
179
180
181void QgsExpressionUtils::executeLambdaForMapLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<void ( QgsMapLayer * )> &function, bool &foundLayer )
182{
183 foundLayer = false;
184
185 // clang analyzer gets this function absolutely 100% wrong
186#ifndef __clang_analyzer__
187
188 // First check if we already received a layer pointer
189 QPointer< QgsMapLayer > ml = value.value< QgsWeakMapLayerPointer >().data();
190 if ( !ml )
191 {
192 ml = value.value< QgsMapLayer * >();
193#ifdef QGISDEBUG
194 if ( ml )
195 {
196 qWarning( "Raw map layer pointer stored in expression evaluation, switch to QgsWeakMapLayerPointer instead" );
197 }
198#endif
199 }
200 if ( ml )
201 {
202 QPointer< QgsMapLayer > layerPointer( ml );
203 auto runFunction = [ layerPointer, &function, &foundLayer ]
204 {
205 if ( QgsMapLayer *layer = layerPointer.data() )
206 {
207 foundLayer = true;
208 function( layer );
209 }
210 };
211
212 // Make sure we only deal with the layer on the thread where it lives.
213 // Anything else risks a crash.
214
215 if ( QThread::currentThread() == ml->thread() )
216 runFunction();
217 else
218 QMetaObject::invokeMethod( ml, runFunction, Qt::BlockingQueuedConnection );
219
220 return;
221 }
222
223 if ( !context || context->layerStores().empty() )
224 {
225 // if no layer stores, then this is only for layers in project and therefore associated with the main thread
226 auto runFunction = [ value, context, expression, &function, &foundLayer ]
227 {
228 if ( QgsMapLayer *layer = getMapLayerPrivate( value, context, expression ) )
229 {
230 foundLayer = true;
231 function( layer );
232 }
233 else
234 {
235 foundLayer = false;
236 }
237 };
238
239 // Make sure we only deal with the project on the thread where it lives.
240 // Anything else risks a crash.
241 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
242 runFunction();
243 else
244 QMetaObject::invokeMethod( QgsProject::instance(), runFunction, Qt::BlockingQueuedConnection ); // skip-keyword-check
245 }
246 else
247 {
248 // if layer stores, then we can't be certain in advance of which thread the layer will have affinity with.
249 // So we need to fetch the layer and then run the function on the layer's thread.
250
251 const QString identifier = value.toString();
252
253 // check through layer stores from context
254 const QList< QgsMapLayerStore * > stores = context->layerStores();
255
256 for ( QgsMapLayerStore *store : stores )
257 {
258 QPointer< QgsMapLayerStore > storePointer( store );
259 auto findLayerInStoreFunction = [ storePointer, identifier, function, &foundLayer ]
260 {
261 QgsMapLayer *ml = nullptr;
262 if ( QgsMapLayerStore *store = storePointer.data() )
263 {
264 // look for matching layer by id
265 ml = store->mapLayer( identifier );
266 if ( !ml )
267 {
268 // Still nothing? Check for layer name
269 ml = store->mapLayersByName( identifier ).value( 0 );
270 }
271
272 if ( ml )
273 {
274 function( ml );
275 foundLayer = true;
276 }
277 }
278 };
279
280 // Make sure we only deal with the store on the thread where it lives.
281 // Anything else risks a crash.
282 if ( QThread::currentThread() == store->thread() )
283 findLayerInStoreFunction();
284 else
285 QMetaObject::invokeMethod( store, findLayerInStoreFunction, Qt::BlockingQueuedConnection );
286
287 if ( foundLayer )
288 return;
289 }
290
291 // last resort - QgsProject instance. This is bad, we need to remove this!
292 auto getMapLayerFromProjectInstance = [ value, identifier, &function, &foundLayer ]
293 {
294 QgsProject *project = QgsProject::instance(); // skip-keyword-check
295
296 // maybe it's a layer id?
297 QgsMapLayer *ml = project->mapLayer( identifier );
298
299 // Still nothing? Check for layer name
300 if ( !ml )
301 {
302 ml = project->mapLayersByName( identifier ).value( 0 );
303 }
304
305 if ( ml )
306 {
307 foundLayer = true;
308 function( ml );
309 }
310 };
311
312 if ( QThread::currentThread() == QgsProject::instance()->thread() ) // skip-keyword-check
313 getMapLayerFromProjectInstance();
314 else
315 QMetaObject::invokeMethod( QgsProject::instance(), getMapLayerFromProjectInstance, Qt::BlockingQueuedConnection ); // skip-keyword-check
316 }
317#endif
318}
319
320QVariant QgsExpressionUtils::runMapLayerFunctionThreadSafe( const QVariant &value, const QgsExpressionContext *context, QgsExpression *expression, const std::function<QVariant( QgsMapLayer * )> &function, bool &foundLayer )
321{
322 QVariant res;
323 foundLayer = false;
324
325 executeLambdaForMapLayer( value, context, expression, [&res, function]( QgsMapLayer * layer )
326 {
327 if ( layer )
328 res = function( layer );
329 }, foundLayer );
330
331 return res;
332}
333
334std::unique_ptr<QgsVectorLayerFeatureSource> QgsExpressionUtils::getFeatureSource( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e, bool &foundLayer )
335{
336 std::unique_ptr<QgsVectorLayerFeatureSource> featureSource;
337
338 executeLambdaForMapLayer( value, context, e, [&featureSource]( QgsMapLayer * layer )
339 {
340 if ( QgsVectorLayer *vl = qobject_cast< QgsVectorLayer *>( layer ) )
341 {
342 featureSource.reset( new QgsVectorLayerFeatureSource( vl ) );
343 }
344 }, foundLayer );
345
346 return featureSource;
347}
348
349QgsVectorLayer *QgsExpressionUtils::getVectorLayer( const QVariant &value, const QgsExpressionContext *context, QgsExpression *e )
350{
351 return qobject_cast<QgsVectorLayer *>( getMapLayerPrivate( value, context, e ) );
352}
353
354QString QgsExpressionUtils::getFilePathValue( const QVariant &value, const QgsExpressionContext *context, QgsExpression *parent )
355{
356 // if it's a map layer, return the file path of that layer...
357 QString res;
359 if ( QgsMapLayer *layer = getMapLayer( value, context, parent ) )
360 {
361 const QVariantMap parts = QgsProviderRegistry::instance()->decodeUri( layer->providerType(), layer->source() );
362 res = parts.value( QStringLiteral( "path" ) ).toString();
363 }
365
366 if ( res.isEmpty() )
367 res = value.toString();
368
369 if ( res.isEmpty() && !QgsVariantUtils::isNull( value ) )
370 {
371 parent->setEvalErrorString( QObject::tr( "Cannot convert value to a file path" ) );
372 }
373 return res;
374}
375
377
378std::tuple<QMetaType::Type, int> QgsExpressionUtils::determineResultType( const QString &expression, const QgsVectorLayer *layer, const QgsFeatureRequest &r, const QgsExpressionContext &c, bool *foundFeatures )
379{
380 QgsExpressionContext context = c;
381 QgsFeatureRequest request = r;
382 QgsExpression exp( expression );
383 request.setFlags( ( exp.needsGeometry() ) ?
386 request.setLimit( 10 );
387 request.setExpressionContext( context );
388
389 // avoid endless recursion by removing virtual fields while going through features
390 // to determine result type
391 QgsAttributeList attributes;
392 const QgsFields fields = layer->fields();
393 for ( int i = 0; i < fields.count(); i++ )
394 {
395 if ( fields.fieldOrigin( i ) != Qgis::FieldOrigin::Expression )
396 attributes << i;
397 }
398 request.setSubsetOfAttributes( attributes );
399
400 QVariant value;
401 QgsFeature f;
402 QgsFeatureIterator it = layer->getFeatures( request );
403 bool hasFeature = it.nextFeature( f );
404 if ( foundFeatures )
405 *foundFeatures = hasFeature;
406 while ( hasFeature )
407 {
408 context.setFeature( f );
409 const QVariant value = exp.evaluate( &context );
410 if ( !QgsVariantUtils::isNull( value ) )
411 {
412 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
413 }
414 hasFeature = it.nextFeature( f );
415 }
416 value = QVariant();
417 return std::make_tuple( static_cast<QMetaType::Type>( value.userType() ), value.userType() );
418}
@ 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.
This class 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.
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.
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:6702
#define Q_NOWARN_DEPRECATED_PUSH
Definition qgis.h:6701
QList< int > QgsAttributeList
Definition qgsfield.h:27
QPointer< QgsMapLayer > QgsWeakMapLayerPointer
Weak pointer for QgsMapLayer.
const QgsCoordinateReferenceSystem & crs