Create class for encapsulating settings relating to a feature source

input to a processing algorithm.

This allows parameter inputs to encapsulate extra information
relating to a feature source input, such as whether only
selected features from the source layer should be used.
This commit is contained in:
Nyall Dawson 2017-06-05 15:17:31 +10:00
parent 0e991bf62c
commit ed09a8a727
4 changed files with 143 additions and 27 deletions

View File

@ -11,6 +11,40 @@
class QgsProcessingFeatureSourceDefinition
{
%Docstring
Encapsulates settings relating to a feature source input to a processing algorithm.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsprocessingparameters.h"
%End
public:
QgsProcessingFeatureSourceDefinition( const QVariant &source = QVariant(), bool selectedFeaturesOnly = false );
%Docstring
Constructor for QgsProcessingFeatureSourceDefinition.
%End
QVariant source;
%Docstring
Source definition. Usually set to a source layer's ID or file name.
%End
bool selectedFeaturesOnly;
%Docstring
True if only selected features in the source should be used by algorithms.
%End
};
class QgsProcessingFeatureSink
{
%Docstring
@ -52,6 +86,7 @@ class QgsProcessingFeatureSink
class QgsProcessingParameterDefinition
{
%Docstring

View File

@ -253,9 +253,31 @@ QgsFeatureSource *QgsProcessingParameters::parameterAsSource( const QgsProcessin
if ( !definition )
return nullptr;
QString layerRef = parameterAsString( definition, parameters, context );
if ( layerRef.isEmpty() )
QVariant val = parameters.value( definition->name() );
bool selectedFeaturesOnly = false;
if ( val.canConvert<QgsProcessingFeatureSourceDefinition>() )
{
// input is a QgsProcessingFeatureSourceDefinition - get extra properties from it
QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( val );
selectedFeaturesOnly = fromVar.selectedFeaturesOnly;
val = fromVar.source;
}
QString layerRef;
if ( val.canConvert<QgsProperty>() )
{
layerRef = val.value< QgsProperty >().valueAsString( context.expressionContext(), definition->defaultValue().toString() );
}
else if ( !val.isValid() || val.toString().isEmpty() )
{
// fall back to default
layerRef = definition->defaultValue().toString();
}
else
{
layerRef = val.toString();
}
if ( layerRef.isEmpty() )
return nullptr;
@ -264,7 +286,7 @@ QgsFeatureSource *QgsProcessingParameters::parameterAsSource( const QgsProcessin
if ( !vl )
return nullptr;
if ( context.flags() & QgsProcessingContext::UseSelectionIfPresent && vl->selectedFeatureCount() > 0 )
if ( selectedFeaturesOnly )
{
return new QgsProcessingFeatureSource( new QgsVectorLayerSelectedFeatureSource( vl ), context, true );
}

View File

@ -31,6 +31,42 @@ class QgsVectorLayer;
class QgsFeatureSink;
class QgsFeatureSource;
/**
* \class QgsProcessingFeatureSourceDefinition
* \ingroup core
*
* Encapsulates settings relating to a feature source input to a processing algorithm.
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsProcessingFeatureSourceDefinition
{
public:
/**
* Constructor for QgsProcessingFeatureSourceDefinition.
*/
QgsProcessingFeatureSourceDefinition( const QVariant &source = QVariant(), bool selectedFeaturesOnly = false )
: source( source )
, selectedFeaturesOnly( selectedFeaturesOnly )
{}
/**
* Source definition. Usually set to a source layer's ID or file name.
*/
QVariant source;
/**
* True if only selected features in the source should be used by algorithms.
*/
bool selectedFeaturesOnly;
};
Q_DECLARE_METATYPE( QgsProcessingFeatureSourceDefinition )
/**
* \class QgsProcessingFeatureSink
* \ingroup core
@ -73,6 +109,7 @@ Q_DECLARE_METATYPE( QgsProcessingFeatureSink )
//
// Parameter definitions
//

View File

@ -217,6 +217,7 @@ class TestQgsProcessing: public QObject
void parameterOutputVectorLayer();
void checkParamValues();
void combineLayerExtent();
void processingFeatureSource();
void processingFeatureSink();
private:
@ -452,8 +453,6 @@ void TestQgsProcessing::context()
context.setDefaultEncoding( "my_enc" );
QCOMPARE( context.defaultEncoding(), QStringLiteral( "my_enc" ) );
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
QCOMPARE( context.flags(), QgsProcessingContext::UseSelectionIfPresent );
context.setFlags( QgsProcessingContext::Flags( 0 ) );
QCOMPARE( context.flags(), QgsProcessingContext::Flags( 0 ) );
@ -695,9 +694,9 @@ void TestQgsProcessing::features()
return ids;
};
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "string" ) );
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "layer" ) );
QVariantMap params;
params.insert( QStringLiteral( "string" ), layer->id() );
params.insert( QStringLiteral( "layer" ), layer->id() );
std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def, params, context ) );
@ -707,7 +706,7 @@ void TestQgsProcessing::features()
QCOMPARE( source->featureCount(), 5L );
// test with selected features
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
layer->selectByIds( QgsFeatureIds() << 2 << 4 );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures() );
@ -715,7 +714,7 @@ void TestQgsProcessing::features()
QCOMPARE( source->featureCount(), 2L );
// selection, but not using selected features
context.setFlags( QgsProcessingContext::Flags( 0 ) );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
layer->selectByIds( QgsFeatureIds() << 2 << 4 );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures() );
@ -723,16 +722,16 @@ void TestQgsProcessing::features()
QCOMPARE( source->featureCount(), 5L );
// using selected features, but no selection
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
layer->removeSelection();
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures() );
QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 );
QCOMPARE( source->featureCount(), 5L );
QVERIFY( ids.isEmpty() );
QCOMPARE( source->featureCount(), 0L );
// test that feature request is honored
context.setFlags( QgsProcessingContext::Flags( 0 ) );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures( QgsFeatureRequest().setFilterFids( QgsFeatureIds() << 1 << 3 << 5 ) ) );
QCOMPARE( ids, QgsFeatureIds() << 1 << 3 << 5 );
@ -741,7 +740,7 @@ void TestQgsProcessing::features()
QCOMPARE( source->featureCount(), 5L );
//test that feature request is honored when using selections
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
layer->selectByIds( QgsFeatureIds() << 2 << 4 );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures( QgsFeatureRequest().setFlags( QgsFeatureRequest::NoGeometry ) ) );
@ -754,7 +753,6 @@ void TestQgsProcessing::features()
encountered = true;
};
context.setFlags( QgsProcessingContext::Flags( 0 ) );
context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid );
context.setInvalidGeometryCallback( callback );
QgsVectorLayer *polyLayer = new QgsVectorLayer( "Polygon", "v2", "memory" );
@ -762,7 +760,7 @@ void TestQgsProcessing::features()
f.setGeometry( QgsGeometry::fromWkt( QStringLiteral( "Polygon((0 0, 1 0, 0 1, 1 1, 0 0))" ) ) );
polyLayer->dataProvider()->addFeatures( QgsFeatureList() << f );
p.addMapLayer( polyLayer );
params.insert( QStringLiteral( "string" ), polyLayer->id() );
params.insert( QStringLiteral( "layer" ), polyLayer->id() );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
ids = getIds( source->getFeatures() );
@ -793,9 +791,9 @@ void TestQgsProcessing::uniqueValues()
p.addMapLayer( layer );
context.setProject( &p );
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "string" ) );
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "layer" ) );
QVariantMap params;
params.insert( QStringLiteral( "string" ), layer->id() );
params.insert( QStringLiteral( "layer" ), layer->id() );
std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def, params, context ) );
@ -831,7 +829,7 @@ void TestQgsProcessing::uniqueValues()
QVERIFY( vals.contains( QString( "C" ) ) );
// selection and using selection
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
QVERIFY( source->uniqueValues( -1 ).isEmpty() );
QVERIFY( source->uniqueValues( 10001 ).isEmpty() );
@ -860,23 +858,31 @@ void TestQgsProcessing::createIndex()
p.addMapLayer( layer );
context.setProject( &p );
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "string" ) );
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "layer" ) );
QVariantMap params;
params.insert( QStringLiteral( "string" ), layer->id() );
params.insert( QStringLiteral( "layer" ), layer->id() );
// disable selected features check
context.setFlags( QgsProcessingContext::Flags( 0 ) );
std::unique_ptr< QgsFeatureSource > source( QgsProcessingParameters::parameterAsSource( def, params, context ) );
QVERIFY( source.get() );
QgsSpatialIndex index( *source.get() );
QList<QgsFeatureId> ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
QCOMPARE( ids, QList<QgsFeatureId>() << 2 );
// selected features check, but none selected
context.setFlags( QgsProcessingContext::UseSelectionIfPresent );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), true ) ) );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
index = QgsSpatialIndex( *source.get() );
ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
QCOMPARE( ids, QList<QgsFeatureId>() << 2 );
bool caught;
try
{
index = QgsSpatialIndex( *source.get() );
ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
}
catch ( ... )
{
caught = true;
}
QVERIFY( caught );
// create selection
layer->selectByIds( QgsFeatureIds() << 4 << 5 );
@ -886,7 +892,7 @@ void TestQgsProcessing::createIndex()
QCOMPARE( ids, QList<QgsFeatureId>() << 4 );
// selection but not using selection mode
context.setFlags( QgsProcessingContext::Flags( 0 ) );
params.insert( QStringLiteral( "layer" ), QVariant::fromValue( QgsProcessingFeatureSourceDefinition( layer->id(), false ) ) );
source.reset( QgsProcessingParameters::parameterAsSource( def, params, context ) );
index = QgsSpatialIndex( *source.get() );
ids = index.nearestNeighbor( QgsPointXY( 2.1, 2 ), 1 );
@ -2305,6 +2311,22 @@ void TestQgsProcessing::combineLayerExtent()
QGSCOMPARENEAR( ext.yMaximum(), 3536664, 10 );
}
void TestQgsProcessing::processingFeatureSource()
{
QVariant source( QStringLiteral( "test.shp" ) );
QgsProcessingFeatureSourceDefinition fs( source, true );
QCOMPARE( fs.source, source );
QVERIFY( fs.selectedFeaturesOnly );
// test storing QgsProcessingFeatureSource in variant and retrieving
QVariant fsInVariant = QVariant::fromValue( fs );
QVERIFY( fsInVariant.isValid() );
QgsProcessingFeatureSourceDefinition fromVar = qvariant_cast<QgsProcessingFeatureSourceDefinition>( fsInVariant );
QCOMPARE( fromVar.source, source );
QVERIFY( fromVar.selectedFeaturesOnly );
}
void TestQgsProcessing::processingFeatureSink()
{
QVariant sink( QStringLiteral( "test.shp" ) );