Skip to content

Commit 1e39db6

Browse files
committed
[FEATURE] New expression functions for $currentfeature (returns current feature) and 'attribute' (returns value stored in specified field within a feature)
1 parent 2595e02 commit 1e39db6

File tree

11 files changed

+163
-13
lines changed

11 files changed

+163
-13
lines changed

python/core/qgsexpression.sip

+9-2
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,13 @@ class QgsExpression
1919
//! Get the expression ready for evaluation - find out column indexes.
2020
bool prepare( const QgsFields &fields );
2121

22-
//! Get list of columns referenced by the expression
22+
/**Get list of columns referenced by the expression.
23+
* @note if the returned list contains the QgsFeatureRequest::AllAttributes constant then
24+
* all attributes from the layer are required for evaluation of the expression.
25+
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
26+
*/
2327
QStringList referencedColumns();
28+
2429
//! Returns true if the expression uses feature geometry for some computation
2530
bool needsGeometry();
2631

@@ -155,13 +160,15 @@ class QgsExpression
155160
class Function
156161
{
157162
public:
158-
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false );
163+
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() );
159164
/** The name of the function. */
160165
QString name();
161166
/** The number of parameters this function takes. */
162167
int params();
163168
/** Does this function use a geometry object. */
164169
bool usesgeometry();
170+
171+
virtual QStringList referencedColumns() const;
165172
/** The group the function belongs to. */
166173
QString group();
167174
/** The help text for the function. */

python/core/qgsfeaturerequest.sip

+2
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class QgsFeatureRequest
2424
FilterFids //!< Filter using feature IDs
2525
};
2626

27+
static const QString AllAttributes;
28+
2729
//! construct a default request: for all features get attributes and geometries
2830
QgsFeatureRequest();
2931
//! construct a request with feature ID filter
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<h3>$currentfeature function</h3>
2+
Returns the current feature being evaluated. This can be used with the 'attribute' function
3+
to evaluate attribute values from the current feature.
4+
5+
<h4>Syntax</h4>
6+
<pre>$currentfeature</pre>
7+
8+
<h4>Arguments</h4>
9+
None
10+
11+
<h4>Example</h4>
12+
<pre>attribute( $currentfeature, 'name' ) &rarr; returns value stored in 'name' attribute for the current feature</pre>
13+

resources/function_help/attribute

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<h3>attribute function</h3>
2+
Returns the value of a specified attribute from a feature.
3+
4+
<h4>Syntax</h4>
5+
<pre>attribute( feature, attribute_name )</pre>
6+
7+
<h4>Arguments</h4>
8+
feature &rarr; a feature<br />
9+
attribute_name &rarr; name of attribute to be returned
10+
11+
<h4>Example</h4>
12+
<pre>attribute( $currentfeature, 'name' ) &rarr; returns value stored in 'name' attribute for the current feature</pre>
13+

src/core/qgsexpression.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,7 @@ static QgsExpression::Interval getInterval( const QVariant& value, QgsExpression
310310

311311
return QgsExpression::Interval::invalidInterVal();
312312
}
313+
313314
static QgsGeometry getGeometry( const QVariant& value, QgsExpression* parent )
314315
{
315316
if ( value.canConvert<QgsGeometry>() )
@@ -319,6 +320,14 @@ static QgsGeometry getGeometry( const QVariant& value, QgsExpression* parent )
319320
return QgsGeometry();
320321
}
321322

323+
static QgsFeature getFeature( const QVariant& value, QgsExpression* parent )
324+
{
325+
if ( value.canConvert<QgsFeature>() )
326+
return value.value<QgsFeature>();
327+
328+
parent->setEvalErrorString( "Cannot convert to QgsFeature" );
329+
return 0;
330+
}
322331

323332
// this handles also NULL values
324333
static TVL getTVLValue( const QVariant& value, QgsExpression* parent )
@@ -786,6 +795,17 @@ static QVariant fcnFeatureId( const QVariantList& , const QgsFeature* f, QgsExpr
786795
return f ? QVariant(( int )f->id() ) : QVariant();
787796
}
788797

798+
static QVariant fcnFeature( const QVariantList& , const QgsFeature* f, QgsExpression* )
799+
{
800+
return f ? QVariant::fromValue( *f ) : QVariant();
801+
}
802+
static QVariant fcnAttribute( const QVariantList& values, const QgsFeature*, QgsExpression* parent )
803+
{
804+
QgsFeature feat = getFeature( values.at( 0 ), parent );
805+
QString attr = getStringValue( values.at( 1 ), parent );
806+
807+
return feat.attribute( attr );
808+
}
789809
static QVariant fcnConcat( const QVariantList& values, const QgsFeature* , QgsExpression *parent )
790810
{
791811
QString concat;
@@ -1660,8 +1680,15 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
16601680
<< new StaticFunction( "geomToWKT", 1, fcnGeomToWKT, "Geometry" )
16611681
<< new StaticFunction( "$rownum", 0, fcnRowNumber, "Record" )
16621682
<< new StaticFunction( "$id", 0, fcnFeatureId, "Record" )
1683+
<< new StaticFunction( "$currentfeature", 0, fcnFeature, "Features" )
16631684
<< new StaticFunction( "$scale", 0, fcnScale, "Record" )
16641685
<< new StaticFunction( "$uuid", 0, fcnUuid, "Record" )
1686+
1687+
//return all attributes string for referencedColumns - this is caught by
1688+
// QgsFeatureRequest::setSubsetOfAttributes and causes all attributes to be fetched by the
1689+
// feature request
1690+
<< new StaticFunction( "attribute", 2, fcnAttribute, "Features", QString(), false, QStringList( QgsFeatureRequest::AllAttributes ) )
1691+
16651692
<< new StaticFunction( "_specialcol_", 1, fcnSpecialColumn, "Special" )
16661693
;
16671694
}
@@ -1804,6 +1831,7 @@ QStringList QgsExpression::referencedColumns()
18041831
{
18051832
if ( !mRootNode )
18061833
return QStringList();
1834+
18071835
QStringList columns = mRootNode->referencedColumns();
18081836

18091837
// filter out duplicates
@@ -2428,6 +2456,26 @@ QString QgsExpression::NodeFunction::dump() const
24282456
return QString( "%1(%2)" ).arg( fd->name() ).arg( mArgs ? mArgs->dump() : QString() ); // function
24292457
}
24302458

2459+
QStringList QgsExpression::NodeFunction::referencedColumns() const
2460+
{
2461+
Function* fd = Functions()[mFnIndex];
2462+
QStringList functionColumns = fd->referencedColumns();
2463+
2464+
if ( !mArgs )
2465+
{
2466+
//no referenced columns in arguments, just return function's referenced columns
2467+
return functionColumns;
2468+
}
2469+
2470+
foreach ( Node* n, mArgs->list() )
2471+
{
2472+
functionColumns.append( n->referencedColumns() );
2473+
}
2474+
2475+
//remove duplicates and return
2476+
return functionColumns.toSet().toList();
2477+
}
2478+
24312479
//
24322480

24332481
QVariant QgsExpression::NodeLiteral::eval( QgsExpression* , const QgsFeature* )

src/core/qgsexpression.h

+15-6
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,13 @@ class CORE_EXPORT QgsExpression
105105
//! Get the expression ready for evaluation - find out column indexes.
106106
bool prepare( const QgsFields &fields );
107107

108-
//! Get list of columns referenced by the expression
108+
/**Get list of columns referenced by the expression.
109+
* @note if the returned list contains the QgsFeatureRequest::AllAttributes constant then
110+
* all attributes from the layer are required for evaluation of the expression.
111+
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
112+
*/
109113
QStringList referencedColumns();
114+
110115
//! Returns true if the expression uses feature geometry for some computation
111116
bool needsGeometry();
112117

@@ -244,14 +249,17 @@ class CORE_EXPORT QgsExpression
244249
class CORE_EXPORT Function
245250
{
246251
public:
247-
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false )
248-
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ) {}
252+
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
253+
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ), mReferencedColumns( referencedColumns ) {}
249254
/** The name of the function. */
250255
QString name() { return mName; }
251256
/** The number of parameters this function takes. */
252257
int params() { return mParams; }
253258
/** Does this function use a geometry object. */
254259
bool usesgeometry() { return mUsesGeometry; }
260+
261+
virtual QStringList referencedColumns() const { return mReferencedColumns; }
262+
255263
/** The group the function belongs to. */
256264
QString group() { return mGroup; }
257265
/** The help text for the function. */
@@ -273,13 +281,14 @@ class CORE_EXPORT QgsExpression
273281
bool mUsesGeometry;
274282
QString mGroup;
275283
QString mHelpText;
284+
QStringList mReferencedColumns;
276285
};
277286

278287
class StaticFunction : public Function
279288
{
280289
public:
281-
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false )
282-
: Function( fnname, params, group, helpText, usesGeometry ), mFnc( fcn ) {}
290+
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
291+
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns ), mFnc( fcn ) {}
283292

284293
virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent )
285294
{
@@ -497,7 +506,7 @@ class CORE_EXPORT QgsExpression
497506
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
498507
virtual QString dump() const;
499508

500-
virtual QStringList referencedColumns() const { QStringList lst; if ( !mArgs ) return lst; foreach ( Node* n, mArgs->list() ) lst.append( n->referencedColumns() ); return lst; }
509+
virtual QStringList referencedColumns() const;
501510
virtual bool needsGeometry() const { bool needs = Functions()[mFnIndex]->usesgeometry(); if ( mArgs ) { foreach ( Node* n, mArgs->list() ) needs |= n->needsGeometry(); } return needs; }
502511
virtual void accept( Visitor& v ) const { v.visit( *this ); }
503512

src/core/qgsfeature.h

+1
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,7 @@ typedef QMap<int, QString> QgsFieldNameMap;
329329

330330
typedef QList<QgsFeature> QgsFeatureList;
331331

332+
Q_DECLARE_METATYPE( QgsFeature )
332333
Q_DECLARE_METATYPE( QgsFeatureList )
333334

334335
#endif

src/core/qgsfeaturerequest.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@
1919

2020
#include <QStringList>
2121

22+
//constants
23+
const QString QgsFeatureRequest::AllAttributes = QString( "#!allattributes!#" );
24+
2225
QgsFeatureRequest::QgsFeatureRequest()
2326
: mFilter( FilterNone )
2427
, mFilterExpression( 0 )
@@ -123,6 +126,12 @@ QgsFeatureRequest& QgsFeatureRequest::setSubsetOfAttributes( const QgsAttributeL
123126

124127
QgsFeatureRequest& QgsFeatureRequest::setSubsetOfAttributes( const QStringList& attrNames, const QgsFields& fields )
125128
{
129+
if ( attrNames.contains( QgsFeatureRequest::AllAttributes ) )
130+
{
131+
//attribute string list contains the all attributes flag, so we must fetch all attributes
132+
return *this;
133+
}
134+
126135
mFlags |= SubsetOfAttributes;
127136
mAttrs.clear();
128137

src/core/qgsfeaturerequest.h

+2
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ class CORE_EXPORT QgsFeatureRequest
7676
FilterFids //!< Filter using feature IDs
7777
};
7878

79+
static const QString AllAttributes;
80+
7981
//! construct a default request: for all features get attributes and geometries
8082
QgsFeatureRequest();
8183
//! construct a request with feature ID filter

src/gui/editorwidgets/qgsvaluerelationwidget.cpp

+14-5
Original file line numberDiff line numberDiff line change
@@ -179,25 +179,34 @@ QgsValueRelationWidget::ValueRelationCache QgsValueRelationWidget::createCache(
179179

180180
QgsFeatureRequest::Flags flags = QgsFeatureRequest::NoGeometry;
181181

182+
bool requiresAllAttributes = false;
182183
if ( e )
183184
{
184185
if ( e->needsGeometry() )
185186
flags |= QgsFeatureRequest::NoGeometry;
186187

187188
Q_FOREACH( const QString& field, e->referencedColumns() )
188189
{
190+
if ( field == QgsFeatureRequest::AllAttributes )
191+
{
192+
requiresAllAttributes = true;
193+
break;
194+
}
189195
int idx = layer->fieldNameIndex( field );
190196
if ( idx < 0 )
191197
continue;
192198
attributes << idx;
193199
}
194200
}
195201

196-
QgsFeatureIterator fit = layer->getFeatures(
197-
QgsFeatureRequest()
198-
.setFlags( flags )
199-
.setSubsetOfAttributes( attributes.toList() )
200-
);
202+
QgsFeatureRequest fr = QgsFeatureRequest().setFlags( flags );
203+
if ( !requiresAllAttributes )
204+
{
205+
fr.setSubsetOfAttributes( attributes.toList() );
206+
}
207+
208+
QgsFeatureIterator fit = layer->getFeatures( fr );
209+
201210
QgsFeature f;
202211
while ( fit.nextFeature( f ) )
203212
{

tests/src/core/testqgsexpression.cpp

+37
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
//header for class being tested
2121
#include <qgsexpression.h>
2222
#include <qgsfeature.h>
23+
#include <qgsfeaturerequest.h>
2324
#include <qgsgeometry.h>
2425
#include <qgsrenderchecker.h>
2526

@@ -503,6 +504,32 @@ class TestQgsExpression: public QObject
503504
QCOMPARE( v.toInt(), 200 );
504505
}
505506

507+
void eval_current_feature()
508+
{
509+
QgsFeature f( 100 );
510+
QgsExpression exp( "$currentfeature" );
511+
QVariant v = exp.evaluate( &f );
512+
QgsFeature evalFeature = v.value<QgsFeature>();
513+
QCOMPARE( evalFeature.id(), f.id() );
514+
}
515+
516+
void eval_feature_attribute()
517+
{
518+
QgsFeature f( 100 );
519+
QgsFields fields;
520+
fields.append( QgsField( "col1" ) );
521+
fields.append( QgsField( "second_column", QVariant::Int ) );
522+
f.setFields( &fields, true );
523+
f.setAttribute( QString( "col1" ), QString( "test value" ) );
524+
f.setAttribute( QString( "second_column" ), 5 );
525+
QgsExpression exp( "attribute($currentfeature,'col1')" );
526+
QVariant v = exp.evaluate( &f );
527+
QCOMPARE( v.toString(), QString( "test value" ) );
528+
QgsExpression exp2( "attribute($currentfeature,'second'||'_column')" );
529+
v = exp2.evaluate( &f );
530+
QCOMPARE( v.toInt(), 5 );
531+
}
532+
506533
void eval_rand()
507534
{
508535
QgsExpression exp1( "rand(1,10)" );
@@ -552,6 +579,16 @@ class TestQgsExpression: public QObject
552579
QCOMPARE( refColsSet, expectedCols );
553580
}
554581

582+
void referenced_columns_all_attributes()
583+
{
584+
QgsExpression exp( "attribute($currentfeature,'test')" );
585+
QCOMPARE( exp.hasParserError(), false );
586+
QStringList refCols = exp.referencedColumns();
587+
// make sure we get the all attributes flag
588+
bool allAttributesFlag = refCols.contains( QgsFeatureRequest::AllAttributes );
589+
QCOMPARE( allAttributesFlag, true );
590+
}
591+
555592
void needs_geometry_data()
556593
{
557594
QTest::addColumn<QString>( "string" );

0 commit comments

Comments
 (0)