Skip to content

Commit

Permalink
Create class for encapsulating settings relating to a feature source
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nyalldawson committed Jun 5, 2017
1 parent 0e991bf commit ed09a8a
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 27 deletions.
35 changes: 35 additions & 0 deletions python/core/processing/qgsprocessingparameters.sip
Expand Up @@ -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
Expand Down Expand Up @@ -52,6 +86,7 @@ class QgsProcessingFeatureSink




class QgsProcessingParameterDefinition
{
%Docstring
Expand Down
28 changes: 25 additions & 3 deletions src/core/processing/qgsprocessingparameters.cpp
Expand Up @@ -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;
Expand All @@ -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 );
}
Expand Down
37 changes: 37 additions & 0 deletions src/core/processing/qgsprocessingparameters.h
Expand Up @@ -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
Expand Down Expand Up @@ -73,6 +109,7 @@ Q_DECLARE_METATYPE( QgsProcessingFeatureSink )




//
// Parameter definitions
//
Expand Down
70 changes: 46 additions & 24 deletions tests/src/core/testqgsprocessing.cpp
Expand Up @@ -217,6 +217,7 @@ class TestQgsProcessing: public QObject
void parameterOutputVectorLayer();
void checkParamValues();
void combineLayerExtent();
void processingFeatureSource();
void processingFeatureSink();

private:
Expand Down Expand Up @@ -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 ) );

Expand Down Expand Up @@ -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 ) );

Expand All @@ -707,32 +706,32 @@ 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() );
QCOMPARE( ids, QgsFeatureIds() << 2 << 4 );
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() );
QCOMPARE( ids, QgsFeatureIds() << 1 << 2 << 3 << 4 << 5 );
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 );
Expand All @@ -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 ) ) );
Expand All @@ -754,15 +753,14 @@ void TestQgsProcessing::features()
encountered = true;
};

context.setFlags( QgsProcessingContext::Flags( 0 ) );
context.setInvalidGeometryCheck( QgsFeatureRequest::GeometryAbortOnInvalid );
context.setInvalidGeometryCallback( callback );
QgsVectorLayer *polyLayer = new QgsVectorLayer( "Polygon", "v2", "memory" );
QgsFeature f;
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() );
Expand Down Expand Up @@ -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 ) );

Expand Down Expand Up @@ -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() );
Expand Down Expand Up @@ -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 );
Expand All @@ -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 );
Expand Down Expand Up @@ -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" ) );
Expand Down

0 comments on commit ed09a8a

Please sign in to comment.