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