Skip to content

Commit

Permalink
Merge pull request #43352 from roya0045/improve_get_feature
Browse files Browse the repository at this point in the history
Add support for filtering by multiple attributes to get_feature expression
  • Loading branch information
m-kuhn committed Dec 9, 2021
2 parents 14436df + bf25489 commit 5f956a3
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 21 deletions.
19 changes: 15 additions & 4 deletions resources/function_help/json/get_feature
Expand Up @@ -3,8 +3,19 @@
"type": "function",
"groups": ["Record and Attributes"],
"description": "Returns the first feature of a layer matching a given attribute value.",
"arguments": [ {"arg":"layer","description":"layer name or ID"},
{"arg":"attribute","description":"attribute name"},
{"arg":"value","description":"attribute value to match"}],
"examples": [ { "expression":"get_feature('streets','name','main st')", "returns":"first feature found in \"streets\" layer with \"main st\" value in the \"name\" field"}]
"variants": [
{ "variant": "Single value variant",
"variant_description": "Along with the layer ID, a single column and value are specified.",
"arguments": [ {"arg":"layer","description":"layer name or ID"},
{"arg":"attribute","description":"attribute name to use for the match"},
{"arg":"value","description":"attribute value to match"}],
"examples": [ { "expression":"get_feature('streets','name','main st')", "returns":"first feature found in \"streets\" layer with \"main st\" value in the \"name\" field"} ]
},
{
"variant": "Map variant",
"variant_description": "Along with the layer ID, a map containing the columns (key) and their respective value to be used.",
"arguments": [ {"arg":"layer","description":"layer name or ID"},
{"arg":"map","description":"Map containing the column and value pairs to use"}],
"examples": [ { "expression":"get_feature('streets',map('name','main st','lane_num','4'))", "returns":"first feature found in \"streets\" layer with \"main st\" value in the \"name\" field and \"4\" value in the \"lane_num\" field"} ]
} ]
}
55 changes: 39 additions & 16 deletions src/core/expression/qgsexpressionfunction.cpp
Expand Up @@ -5475,25 +5475,48 @@ static QVariant fcnGetFeature( const QVariantList &values, const QgsExpressionCo
{
return QVariant();
}

QString attribute = QgsExpressionUtils::getStringValue( values.at( 1 ), parent );
int attributeId = featureSource->fields().lookupField( attribute );
if ( attributeId == -1 )
QgsFeatureRequest req;
QString cacheValueKey;
if ( values.at( 1 ).type() == QVariant::Map )
{
return QVariant();
QVariantMap attributeMap = QgsExpressionUtils::getMapValue( values.at( 1 ), parent );

QMap <QString, QVariant>::const_iterator i = attributeMap.constBegin();
QString filterString;
for ( ; i != attributeMap.constEnd(); ++i )
{
if ( !filterString.isEmpty() )
{
filterString.append( " AND " );
}
filterString.append( QgsExpression::createFieldEqualityExpression( i.key(), i.value() ) );
}
cacheValueKey = QStringLiteral( "getfeature:%1:%2" ).arg( featureSource->id(), filterString );
if ( context && context->hasCachedValue( cacheValueKey ) )
{
return context->cachedValue( cacheValueKey );
}
req.setFilterExpression( filterString );
}
else
{
QString attribute = QgsExpressionUtils::getStringValue( values.at( 1 ), parent );
int attributeId = featureSource->fields().lookupField( attribute );
if ( attributeId == -1 )
{
return QVariant();
}

const QVariant &attVal = values.at( 2 );
const QVariant &attVal = values.at( 2 );

const QString cacheValueKey = QStringLiteral( "getfeature:%1:%2:%3" ).arg( featureSource->id(), QString::number( attributeId ), attVal.toString() );
if ( context && context->hasCachedValue( cacheValueKey ) )
{
return context->cachedValue( cacheValueKey );
}
cacheValueKey = QStringLiteral( "getfeature:%1:%2:%3" ).arg( featureSource->id(), QString::number( attributeId ), attVal.toString() );
if ( context && context->hasCachedValue( cacheValueKey ) )
{
return context->cachedValue( cacheValueKey );
}

QgsFeatureRequest req;
req.setFilterExpression( QStringLiteral( "%1=%2" ).arg( QgsExpression::quotedColumnRef( attribute ),
QgsExpression::quotedString( attVal.toString() ) ) );
req.setFilterExpression( QgsExpression::createFieldEqualityExpression( attribute, attVal ) );
}
req.setLimit( 1 );
req.setTimeout( 10000 );
req.setRequestMayBeNested( true );
Expand Down Expand Up @@ -7775,8 +7798,8 @@ const QList<QgsExpressionFunction *> &QgsExpression::Functions()

functions
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "attribute" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "value" ) ),
<< QgsExpressionFunction::Parameter( QStringLiteral( "attribute(s)" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "value" ), true ),
fcnGetFeature, QStringLiteral( "Record and Attributes" ), QString(), false, QSet<QString>(), false, QStringList() << QStringLiteral( "QgsExpressionUtils::getFeature" ) )
<< new QgsStaticExpressionFunction( QStringLiteral( "get_feature_by_id" ), QgsExpressionFunction::ParameterList() << QgsExpressionFunction::Parameter( QStringLiteral( "layer" ) )
<< QgsExpressionFunction::Parameter( QStringLiteral( "feature_id" ) ),
Expand Down
17 changes: 16 additions & 1 deletion tests/src/core/testqgsexpression.cpp
Expand Up @@ -130,20 +130,24 @@ class TestQgsExpression: public QObject
QgsProject::instance()->addMapLayer( mMeshLayer );

// test memory layer for get_feature tests
mMemoryLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:string" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
mMemoryLayer = new QgsVectorLayer( QStringLiteral( "Point?field=col1:integer&field=col2:string&field=datef:date(0,0)" ), QStringLiteral( "test" ), QStringLiteral( "memory" ) );
QVERIFY( mMemoryLayer->isValid() );
QgsFeature f1( mMemoryLayer->dataProvider()->fields(), 1 );
f1.setAttribute( QStringLiteral( "col1" ), 10 );
f1.setAttribute( QStringLiteral( "col2" ), "test1" );
f1.setAttribute( QStringLiteral( "datef" ), QDate( 2021, 9, 23 ) );
QgsFeature f2( mMemoryLayer->dataProvider()->fields(), 2 );
f2.setAttribute( QStringLiteral( "col1" ), 11 );
f2.setAttribute( QStringLiteral( "col2" ), "test2" );
f2.setAttribute( QStringLiteral( "datef" ), QDate( 2022, 9, 23 ) );
QgsFeature f3( mMemoryLayer->dataProvider()->fields(), 3 );
f3.setAttribute( QStringLiteral( "col1" ), 3 );
f3.setAttribute( QStringLiteral( "col2" ), "test3" );
f3.setAttribute( QStringLiteral( "datef" ), QDate( 2021, 9, 23 ) );
QgsFeature f4( mMemoryLayer->dataProvider()->fields(), 4 );
f4.setAttribute( QStringLiteral( "col1" ), 41 );
f4.setAttribute( QStringLiteral( "col2" ), "test4" );
f4.setAttribute( QStringLiteral( "datef" ), QDate( 2022, 9, 23 ) );
mMemoryLayer->dataProvider()->addFeatures( QgsFeatureList() << f1 << f2 << f3 << f4 );
QgsProject::instance()->addMapLayer( mMemoryLayer );

Expand Down Expand Up @@ -2246,6 +2250,17 @@ class TestQgsExpression: public QObject

// get_feature_by_id
QTest::newRow( "get_feature_by_id" ) << "get_feature_by_id('test', 1)" << true << 1;

// multi-param
QTest::newRow( "get_feature multi1" ) << "get_feature('test',map('col1','11','col2','test2'))" << true << 2;
QTest::newRow( "get_feature multi2" ) << "get_feature('test',map('col1',3,'col2','test3'))" << true << 3;
QTest::newRow( "get_feature multi2" ) << "get_feature('test',map('col1','41','datef',to_date('2022-09-23')))" << true << 4;

// multi-param no match
QTest::newRow( "get_feature no match-multi1" ) << "get_feature('test',map('col1','col2'),'no match!')" << false << -1;
QTest::newRow( "get_feature no match-multi2" ) << "get_feature('test',map('col2','10','col4','test3'))" << false << -1;
QTest::newRow( "get_feature no match-multi2" ) << "get_feature('test',map('col1',10,'datef',to_date('2021-09-24')))" << false << -1;

}

void eval_get_feature()
Expand Down

0 comments on commit 5f956a3

Please sign in to comment.