Skip to content

Commit

Permalink
[FEATURE] New expression functions for $currentfeature (returns curre…
Browse files Browse the repository at this point in the history
…nt feature) and 'attribute' (returns value stored in specified field within a feature)
  • Loading branch information
nyalldawson committed Jul 10, 2014
1 parent 2595e02 commit 1e39db6
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 13 deletions.
11 changes: 9 additions & 2 deletions python/core/qgsexpression.sip
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ class QgsExpression
//! Get the expression ready for evaluation - find out column indexes.
bool prepare( const QgsFields &fields );

//! Get list of columns referenced by the expression
/**Get list of columns referenced by the expression.
* @note if the returned list contains the QgsFeatureRequest::AllAttributes constant then
* all attributes from the layer are required for evaluation of the expression.
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
*/
QStringList referencedColumns();

//! Returns true if the expression uses feature geometry for some computation
bool needsGeometry();

Expand Down Expand Up @@ -155,13 +160,15 @@ class QgsExpression
class Function
{
public:
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false );
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() );
/** The name of the function. */
QString name();
/** The number of parameters this function takes. */
int params();
/** Does this function use a geometry object. */
bool usesgeometry();

virtual QStringList referencedColumns() const;
/** The group the function belongs to. */
QString group();
/** The help text for the function. */
Expand Down
2 changes: 2 additions & 0 deletions python/core/qgsfeaturerequest.sip
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ class QgsFeatureRequest
FilterFids //!< Filter using feature IDs
};

static const QString AllAttributes;

//! construct a default request: for all features get attributes and geometries
QgsFeatureRequest();
//! construct a request with feature ID filter
Expand Down
13 changes: 13 additions & 0 deletions resources/function_help/$currentfeature
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<h3>$currentfeature function</h3>
Returns the current feature being evaluated. This can be used with the 'attribute' function
to evaluate attribute values from the current feature.

<h4>Syntax</h4>
<pre>$currentfeature</pre>

<h4>Arguments</h4>
None

<h4>Example</h4>
<pre>attribute( $currentfeature, 'name' ) &rarr; returns value stored in 'name' attribute for the current feature</pre>

13 changes: 13 additions & 0 deletions resources/function_help/attribute
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<h3>attribute function</h3>
Returns the value of a specified attribute from a feature.

<h4>Syntax</h4>
<pre>attribute( feature, attribute_name )</pre>

<h4>Arguments</h4>
feature &rarr; a feature<br />
attribute_name &rarr; name of attribute to be returned

<h4>Example</h4>
<pre>attribute( $currentfeature, 'name' ) &rarr; returns value stored in 'name' attribute for the current feature</pre>

48 changes: 48 additions & 0 deletions src/core/qgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ static QgsExpression::Interval getInterval( const QVariant& value, QgsExpression

return QgsExpression::Interval::invalidInterVal();
}

static QgsGeometry getGeometry( const QVariant& value, QgsExpression* parent )
{
if ( value.canConvert<QgsGeometry>() )
Expand All @@ -319,6 +320,14 @@ static QgsGeometry getGeometry( const QVariant& value, QgsExpression* parent )
return QgsGeometry();
}

static QgsFeature getFeature( const QVariant& value, QgsExpression* parent )
{
if ( value.canConvert<QgsFeature>() )
return value.value<QgsFeature>();

parent->setEvalErrorString( "Cannot convert to QgsFeature" );
return 0;
}

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

static QVariant fcnFeature( const QVariantList& , const QgsFeature* f, QgsExpression* )
{
return f ? QVariant::fromValue( *f ) : QVariant();
}
static QVariant fcnAttribute( const QVariantList& values, const QgsFeature*, QgsExpression* parent )
{
QgsFeature feat = getFeature( values.at( 0 ), parent );
QString attr = getStringValue( values.at( 1 ), parent );

return feat.attribute( attr );
}
static QVariant fcnConcat( const QVariantList& values, const QgsFeature* , QgsExpression *parent )
{
QString concat;
Expand Down Expand Up @@ -1660,8 +1680,15 @@ const QList<QgsExpression::Function*> &QgsExpression::Functions()
<< new StaticFunction( "geomToWKT", 1, fcnGeomToWKT, "Geometry" )
<< new StaticFunction( "$rownum", 0, fcnRowNumber, "Record" )
<< new StaticFunction( "$id", 0, fcnFeatureId, "Record" )
<< new StaticFunction( "$currentfeature", 0, fcnFeature, "Features" )
<< new StaticFunction( "$scale", 0, fcnScale, "Record" )
<< new StaticFunction( "$uuid", 0, fcnUuid, "Record" )

//return all attributes string for referencedColumns - this is caught by
// QgsFeatureRequest::setSubsetOfAttributes and causes all attributes to be fetched by the
// feature request
<< new StaticFunction( "attribute", 2, fcnAttribute, "Features", QString(), false, QStringList( QgsFeatureRequest::AllAttributes ) )

<< new StaticFunction( "_specialcol_", 1, fcnSpecialColumn, "Special" )
;
}
Expand Down Expand Up @@ -1804,6 +1831,7 @@ QStringList QgsExpression::referencedColumns()
{
if ( !mRootNode )
return QStringList();

QStringList columns = mRootNode->referencedColumns();

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

QStringList QgsExpression::NodeFunction::referencedColumns() const
{
Function* fd = Functions()[mFnIndex];
QStringList functionColumns = fd->referencedColumns();

if ( !mArgs )
{
//no referenced columns in arguments, just return function's referenced columns
return functionColumns;
}

foreach ( Node* n, mArgs->list() )
{
functionColumns.append( n->referencedColumns() );
}

//remove duplicates and return
return functionColumns.toSet().toList();
}

//

QVariant QgsExpression::NodeLiteral::eval( QgsExpression* , const QgsFeature* )
Expand Down
21 changes: 15 additions & 6 deletions src/core/qgsexpression.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,13 @@ class CORE_EXPORT QgsExpression
//! Get the expression ready for evaluation - find out column indexes.
bool prepare( const QgsFields &fields );

//! Get list of columns referenced by the expression
/**Get list of columns referenced by the expression.
* @note if the returned list contains the QgsFeatureRequest::AllAttributes constant then
* all attributes from the layer are required for evaluation of the expression.
* QgsFeatureRequest::setSubsetOfAttributes automatically handles this case.
*/
QStringList referencedColumns();

//! Returns true if the expression uses feature geometry for some computation
bool needsGeometry();

Expand Down Expand Up @@ -244,14 +249,17 @@ class CORE_EXPORT QgsExpression
class CORE_EXPORT Function
{
public:
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false )
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ) {}
Function( QString fnname, int params, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
: mName( fnname ), mParams( params ), mUsesGeometry( usesGeometry ), mGroup( group ), mHelpText( helpText ), mReferencedColumns( referencedColumns ) {}
/** The name of the function. */
QString name() { return mName; }
/** The number of parameters this function takes. */
int params() { return mParams; }
/** Does this function use a geometry object. */
bool usesgeometry() { return mUsesGeometry; }

virtual QStringList referencedColumns() const { return mReferencedColumns; }

/** The group the function belongs to. */
QString group() { return mGroup; }
/** The help text for the function. */
Expand All @@ -273,13 +281,14 @@ class CORE_EXPORT QgsExpression
bool mUsesGeometry;
QString mGroup;
QString mHelpText;
QStringList mReferencedColumns;
};

class StaticFunction : public Function
{
public:
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false )
: Function( fnname, params, group, helpText, usesGeometry ), mFnc( fcn ) {}
StaticFunction( QString fnname, int params, FcnEval fcn, QString group, QString helpText = QString(), bool usesGeometry = false, QStringList referencedColumns = QStringList() )
: Function( fnname, params, group, helpText, usesGeometry, referencedColumns ), mFnc( fcn ) {}

virtual QVariant func( const QVariantList& values, const QgsFeature* f, QgsExpression* parent )
{
Expand Down Expand Up @@ -497,7 +506,7 @@ class CORE_EXPORT QgsExpression
virtual QVariant eval( QgsExpression* parent, const QgsFeature* f );
virtual QString dump() const;

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

Expand Down
1 change: 1 addition & 0 deletions src/core/qgsfeature.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ typedef QMap<int, QString> QgsFieldNameMap;

typedef QList<QgsFeature> QgsFeatureList;

Q_DECLARE_METATYPE( QgsFeature )
Q_DECLARE_METATYPE( QgsFeatureList )

#endif
9 changes: 9 additions & 0 deletions src/core/qgsfeaturerequest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

#include <QStringList>

//constants
const QString QgsFeatureRequest::AllAttributes = QString( "#!allattributes!#" );

QgsFeatureRequest::QgsFeatureRequest()
: mFilter( FilterNone )
, mFilterExpression( 0 )
Expand Down Expand Up @@ -123,6 +126,12 @@ QgsFeatureRequest& QgsFeatureRequest::setSubsetOfAttributes( const QgsAttributeL

QgsFeatureRequest& QgsFeatureRequest::setSubsetOfAttributes( const QStringList& attrNames, const QgsFields& fields )
{
if ( attrNames.contains( QgsFeatureRequest::AllAttributes ) )
{
//attribute string list contains the all attributes flag, so we must fetch all attributes
return *this;
}

mFlags |= SubsetOfAttributes;
mAttrs.clear();

Expand Down
2 changes: 2 additions & 0 deletions src/core/qgsfeaturerequest.h
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ class CORE_EXPORT QgsFeatureRequest
FilterFids //!< Filter using feature IDs
};

static const QString AllAttributes;

//! construct a default request: for all features get attributes and geometries
QgsFeatureRequest();
//! construct a request with feature ID filter
Expand Down
19 changes: 14 additions & 5 deletions src/gui/editorwidgets/qgsvaluerelationwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -179,25 +179,34 @@ QgsValueRelationWidget::ValueRelationCache QgsValueRelationWidget::createCache(

QgsFeatureRequest::Flags flags = QgsFeatureRequest::NoGeometry;

bool requiresAllAttributes = false;
if ( e )
{
if ( e->needsGeometry() )
flags |= QgsFeatureRequest::NoGeometry;

Q_FOREACH( const QString& field, e->referencedColumns() )
{
if ( field == QgsFeatureRequest::AllAttributes )
{
requiresAllAttributes = true;
break;
}
int idx = layer->fieldNameIndex( field );
if ( idx < 0 )
continue;
attributes << idx;
}
}

QgsFeatureIterator fit = layer->getFeatures(
QgsFeatureRequest()
.setFlags( flags )
.setSubsetOfAttributes( attributes.toList() )
);
QgsFeatureRequest fr = QgsFeatureRequest().setFlags( flags );
if ( !requiresAllAttributes )
{
fr.setSubsetOfAttributes( attributes.toList() );
}

QgsFeatureIterator fit = layer->getFeatures( fr );

QgsFeature f;
while ( fit.nextFeature( f ) )
{
Expand Down
37 changes: 37 additions & 0 deletions tests/src/core/testqgsexpression.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
//header for class being tested
#include <qgsexpression.h>
#include <qgsfeature.h>
#include <qgsfeaturerequest.h>
#include <qgsgeometry.h>
#include <qgsrenderchecker.h>

Expand Down Expand Up @@ -503,6 +504,32 @@ class TestQgsExpression: public QObject
QCOMPARE( v.toInt(), 200 );
}

void eval_current_feature()
{
QgsFeature f( 100 );
QgsExpression exp( "$currentfeature" );
QVariant v = exp.evaluate( &f );
QgsFeature evalFeature = v.value<QgsFeature>();
QCOMPARE( evalFeature.id(), f.id() );
}

void eval_feature_attribute()
{
QgsFeature f( 100 );
QgsFields fields;
fields.append( QgsField( "col1" ) );
fields.append( QgsField( "second_column", QVariant::Int ) );
f.setFields( &fields, true );
f.setAttribute( QString( "col1" ), QString( "test value" ) );
f.setAttribute( QString( "second_column" ), 5 );
QgsExpression exp( "attribute($currentfeature,'col1')" );
QVariant v = exp.evaluate( &f );
QCOMPARE( v.toString(), QString( "test value" ) );
QgsExpression exp2( "attribute($currentfeature,'second'||'_column')" );
v = exp2.evaluate( &f );
QCOMPARE( v.toInt(), 5 );
}

void eval_rand()
{
QgsExpression exp1( "rand(1,10)" );
Expand Down Expand Up @@ -552,6 +579,16 @@ class TestQgsExpression: public QObject
QCOMPARE( refColsSet, expectedCols );
}

void referenced_columns_all_attributes()
{
QgsExpression exp( "attribute($currentfeature,'test')" );
QCOMPARE( exp.hasParserError(), false );
QStringList refCols = exp.referencedColumns();
// make sure we get the all attributes flag
bool allAttributesFlag = refCols.contains( QgsFeatureRequest::AllAttributes );
QCOMPARE( allAttributesFlag, true );
}

void needs_geometry_data()
{
QTest::addColumn<QString>( "string" );
Expand Down

0 comments on commit 1e39db6

Please sign in to comment.