Skip to content
Permalink
Browse files

[FEATURE][processing] Add algorithm to filter by layer type

This algorithm allows conditional model branching based on an input
layer type. For instance, it allows a model to adapt to the actual
layer type of a generic "map layer" parameter input, and decide
which branch of the model to run as a result.
  • Loading branch information
nyalldawson committed Mar 17, 2020
1 parent dee6f3f commit c1470c48304db1d5fa46537a47ee327c5f4cbb7b
@@ -44,6 +44,13 @@ QString QgsFilterByGeometryAlgorithm::groupId() const
return QStringLiteral( "vectorselection" );
}

QgsProcessingAlgorithm::Flags QgsFilterByGeometryAlgorithm::flags() const
{
Flags f = QgsProcessingAlgorithm::flags();
f |= QgsProcessingAlgorithm::FlagHideFromToolbox;
return f;
}

void QgsFilterByGeometryAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
@@ -212,5 +219,95 @@ QVariantMap QgsFilterByGeometryAlgorithm::processAlgorithm( const QVariantMap &p
return outputs;
}



//
// QgsFilterByLayerTypeAlgorithm
//

QString QgsFilterByLayerTypeAlgorithm::name() const
{
return QStringLiteral( "filterlayersbytype" );
}

QString QgsFilterByLayerTypeAlgorithm::displayName() const
{
return QObject::tr( "Filter layers by type" );
}

QStringList QgsFilterByLayerTypeAlgorithm::tags() const
{
return QObject::tr( "filter,vector,raster,select" ).split( ',' );
}

QString QgsFilterByLayerTypeAlgorithm::group() const
{
return QObject::tr( "Layer tools" );
}

QString QgsFilterByLayerTypeAlgorithm::groupId() const
{
return QStringLiteral( "layertools" );
}

QgsProcessingAlgorithm::Flags QgsFilterByLayerTypeAlgorithm::flags() const
{
Flags f = QgsProcessingAlgorithm::flags();
f |= FlagHideFromToolbox | FlagPruneModelBranchesBasedOnAlgorithmResults;
return f;
}

void QgsFilterByLayerTypeAlgorithm::initAlgorithm( const QVariantMap & )
{
addParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );

addParameter( new QgsProcessingParameterVectorDestination( QStringLiteral( "VECTOR" ), QObject::tr( "Vector features" ),
QgsProcessing::TypeVectorAnyGeometry, QVariant(), true, false ) );

addParameter( new QgsProcessingParameterRasterDestination( QStringLiteral( "RASTER" ), QObject::tr( "Raster layer" ), QVariant(), true, false ) );
}

QString QgsFilterByLayerTypeAlgorithm::shortHelpString() const
{
return QObject::tr( "This algorithm filters layer by their type. Incoming layers will be directed to different "
"outputs based on whether they are a vector or raster layer." );
}

QString QgsFilterByLayerTypeAlgorithm::shortDescription() const
{
return QObject::tr( "Filters layers by type" );
}

QgsFilterByLayerTypeAlgorithm *QgsFilterByLayerTypeAlgorithm::createInstance() const
{
return new QgsFilterByLayerTypeAlgorithm();
}

QVariantMap QgsFilterByLayerTypeAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
const QgsMapLayer *layer = parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context );
if ( !layer )
throw QgsProcessingException( QObject::tr( "Could not load input layer" ) );

QVariantMap outputs;

switch ( layer->type() )
{
case QgsMapLayerType::VectorLayer:
outputs.insert( QStringLiteral( "VECTOR" ), parameters.value( QStringLiteral( "INPUT" ) ) );
break;

case QgsMapLayerType::RasterLayer:
outputs.insert( QStringLiteral( "RASTER" ), parameters.value( QStringLiteral( "INPUT" ) ) );
break;

case QgsMapLayerType::PluginLayer:
case QgsMapLayerType::MeshLayer:
break;
}

return outputs;
}

///@endcond

@@ -26,14 +26,15 @@
///@cond PRIVATE

/**
* Native extract by expression algorithm.
* Native filter by geometry type algorithm.
*/
class QgsFilterByGeometryAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsFilterByGeometryAlgorithm() = default;
Flags flags() const override;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
@@ -51,6 +52,34 @@ class QgsFilterByGeometryAlgorithm : public QgsProcessingAlgorithm

};


/**
* Native filter by layer type algorithm.
*/
class QgsFilterByLayerTypeAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsFilterByLayerTypeAlgorithm() = default;
Flags flags() const override;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override;
QString displayName() const override;
QStringList tags() const override;
QString group() const override;
QString groupId() const override;
QString shortHelpString() const override;
QString shortDescription() const override;
QgsFilterByLayerTypeAlgorithm *createInstance() const override SIP_FACTORY;

protected:

QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;

};

///@endcond PRIVATE

#endif // QGSALGORITHMFILTERBYGEOMETRY_H
@@ -253,6 +253,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsFillNoDataAlgorithm() );
addAlgorithm( new QgsFilterAlgorithm() );
addAlgorithm( new QgsFilterByGeometryAlgorithm() );
addAlgorithm( new QgsFilterByLayerTypeAlgorithm() );
addAlgorithm( new QgsFilterVerticesByM() );
addAlgorithm( new QgsFilterVerticesByZ() );
addAlgorithm( new QgsFixGeometriesAlgorithm() );
@@ -580,6 +580,7 @@ class TestQgsProcessing: public QObject
void asPythonCommand();
void modelerAlgorithm();
void modelExecution();
void modelBranchPruning();
void modelWithProviderWithLimitedTypes();
void modelVectorOutputIsCompatibleType();
void modelAcceptableValues();
QCOMPARE( actualParts, expectedParts );
}

void TestQgsProcessing::modelBranchPruning()
{
QgsVectorLayer *layer3111 = new QgsVectorLayer( "Point?crs=epsg:3111", "v1", "memory" );
QgsProject p;
p.addMapLayer( layer3111 );

QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt
QString raster1 = testDataDir + "landsat_4326.tif";
QFileInfo fi1( raster1 );
QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" );
QVERIFY( r1->isValid() );
p.addMapLayer( r1 );

QgsProcessingContext context;
context.setProject( &p );

// test that model branches are trimmed for algorithms which return the FlagPruneModelBranchesBasedOnAlgorithmResults flag
QgsProcessingModelAlgorithm model1;

// first add the filter by layer type alg
QgsProcessingModelChildAlgorithm algc1;
algc1.setChildId( "filter" );
algc1.setAlgorithmId( "native:filterlayersbytype" );
QgsProcessingModelParameter param;
param.setParameterName( QStringLiteral( "LAYER" ) );
model1.addModelParameter( new QgsProcessingParameterMapLayer( QStringLiteral( "LAYER" ) ), param );
algc1.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromModelParameter( QStringLiteral( "LAYER" ) ) );
model1.addChildAlgorithm( algc1 );

//then create some branches which come off this, depending on the layer type
QgsProcessingModelChildAlgorithm algc2;
algc2.setChildId( "buffer" );
algc2.setAlgorithmId( "native:buffer" );
algc2.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "filter" ), QStringLiteral( "VECTOR" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsc2;
QgsProcessingModelOutput outc2( "BUFFER_OUTPUT" );
outc2.setChildOutputName( "OUTPUT" );
outputsc2.insert( QStringLiteral( "BUFFER_OUTPUT" ), outc2 );
algc2.setModelOutputs( outputsc2 );
model1.addChildAlgorithm( algc2 );
// ...we want a complex branch, so add some more bits to the branch
QgsProcessingModelChildAlgorithm algc3;
algc3.setChildId( "buffer2" );
algc3.setAlgorithmId( "native:buffer" );
algc3.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "buffer" ), QStringLiteral( "OUTPUT" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsc3;
QgsProcessingModelOutput outc3( "BUFFER2_OUTPUT" );
outc3.setChildOutputName( "OUTPUT" );
outputsc3.insert( QStringLiteral( "BUFFER2_OUTPUT" ), outc3 );
algc3.setModelOutputs( outputsc3 );
model1.addChildAlgorithm( algc3 );
QgsProcessingModelChildAlgorithm algc4;
algc4.setChildId( "buffer3" );
algc4.setAlgorithmId( "native:buffer" );
algc4.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "buffer" ), QStringLiteral( "OUTPUT" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsc4;
QgsProcessingModelOutput outc4( "BUFFER3_OUTPUT" );
outc4.setChildOutputName( "OUTPUT" );
outputsc4.insert( QStringLiteral( "BUFFER3_OUTPUT" ), outc4 );
algc4.setModelOutputs( outputsc4 );
model1.addChildAlgorithm( algc4 );

// now add some bits to the raster branch
QgsProcessingModelChildAlgorithm algr2;
algr2.setChildId( "fill2" );
algr2.setAlgorithmId( "native:fillnodata" );
algr2.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "filter" ), QStringLiteral( "RASTER" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsr2;
QgsProcessingModelOutput outr2( "RASTER_OUTPUT" );
outr2.setChildOutputName( "OUTPUT" );
outputsr2.insert( QStringLiteral( "RASTER_OUTPUT" ), outr2 );
algr2.setModelOutputs( outputsr2 );
model1.addChildAlgorithm( algr2 );

// some more bits on the raster branch
QgsProcessingModelChildAlgorithm algr3;
algr3.setChildId( "fill3" );
algr3.setAlgorithmId( "native:fillnodata" );
algr3.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "fill2" ), QStringLiteral( "OUTPUT" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsr3;
QgsProcessingModelOutput outr3( "RASTER_OUTPUT2" );
outr3.setChildOutputName( "OUTPUT" );
outputsr3.insert( QStringLiteral( "RASTER_OUTPUT2" ), outr3 );
algr3.setModelOutputs( outputsr3 );
model1.addChildAlgorithm( algr3 );

QgsProcessingModelChildAlgorithm algr4;
algr4.setChildId( "fill4" );
algr4.setAlgorithmId( "native:fillnodata" );
algr4.addParameterSources( QStringLiteral( "INPUT" ), QList< QgsProcessingModelChildParameterSource >() << QgsProcessingModelChildParameterSource::fromChildOutput( QStringLiteral( "fill2" ), QStringLiteral( "OUTPUT" ) ) );
QMap<QString, QgsProcessingModelOutput> outputsr4;
QgsProcessingModelOutput outr4( "RASTER_OUTPUT3" );
outr4.setChildOutputName( "OUTPUT" );
outputsr4.insert( QStringLiteral( "RASTER_OUTPUT3" ), outr4 );
algr4.setModelOutputs( outputsr4 );
model1.addChildAlgorithm( algr4 );

QgsProcessingFeedback feedback;
QVariantMap params;
// vector input
params.insert( QStringLiteral( "LAYER" ), QStringLiteral( "v1" ) );
params.insert( QStringLiteral( "buffer:BUFFER_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "fill2:RASTER_OUTPUT" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "fill3:RASTER_OUTPUT2" ), QgsProcessing::TEMPORARY_OUTPUT );
params.insert( QStringLiteral( "fill4:RASTER_OUTPUT3" ), QgsProcessing::TEMPORARY_OUTPUT );
QVariantMap results = model1.run( params, context, &feedback );
// we should get the vector branch outputs only
QVERIFY( !results.value( QStringLiteral( "buffer:BUFFER_OUTPUT" ) ).toString().isEmpty() );
QVERIFY( !results.value( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ) ).toString().isEmpty() );
QVERIFY( !results.value( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ) ).toString().isEmpty() );
QVERIFY( !results.contains( QStringLiteral( "fill2:RASTER_OUTPUT" ) ) );
QVERIFY( !results.contains( QStringLiteral( "fill3:RASTER_OUTPUT2" ) ) );
QVERIFY( !results.contains( QStringLiteral( "fill4:RASTER_OUTPUT3" ) ) );

// raster input
params.insert( QStringLiteral( "LAYER" ), QStringLiteral( "R1" ) );
results = model1.run( params, context, &feedback );
// we should get the raster branch outputs only
QVERIFY( !results.value( QStringLiteral( "fill2:RASTER_OUTPUT" ) ).toString().isEmpty() );
QVERIFY( !results.value( QStringLiteral( "fill3:RASTER_OUTPUT2" ) ).toString().isEmpty() );
QVERIFY( !results.value( QStringLiteral( "fill4:RASTER_OUTPUT3" ) ).toString().isEmpty() );
QVERIFY( !results.contains( QStringLiteral( "buffer:BUFFER_OUTPUT" ) ) );
QVERIFY( !results.contains( QStringLiteral( "buffer2:BUFFER2_OUTPUT" ) ) );
QVERIFY( !results.contains( QStringLiteral( "buffer3:BUFFER3_OUTPUT" ) ) );
}

void TestQgsProcessing::modelWithProviderWithLimitedTypes()
{
QgsApplication::processingRegistry()->addProvider( new DummyProvider4() );
@@ -112,6 +112,8 @@ class TestQgsProcessingAlgs: public QObject
void raiseException();
void raiseWarning();

void filterByLayerType();

private:

QString mPointLayerPath;
@@ -2339,5 +2341,43 @@ void TestQgsProcessingAlgs::raiseWarning()
QCOMPARE( feedback.errors, QStringList() << QStringLiteral( "you mighta screwed up boy, but i aint so sure" ) );
}

void TestQgsProcessingAlgs::filterByLayerType()
{
QgsProject p;
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "Point?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
QVERIFY( vl->isValid() );
p.addMapLayer( vl );
// raster layer
QgsRasterLayer *rl = new QgsRasterLayer( QStringLiteral( TEST_DATA_DIR ) + "/tenbytenraster.asc", QStringLiteral( "rl" ) );
QVERIFY( rl->isValid() );
p.addMapLayer( rl );


std::unique_ptr< QgsProcessingAlgorithm > alg( QgsApplication::processingRegistry()->createAlgorithmById( QStringLiteral( "native:filterlayersbytype" ) ) );
QVERIFY( alg != nullptr );

QVariantMap parameters;
// vector input
parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "vl" ) );

bool ok = false;
std::unique_ptr< QgsProcessingContext > context = qgis::make_unique< QgsProcessingContext >();
context->setProject( &p );
QgsProcessingFeedback feedback;
QVariantMap results;
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );
QVERIFY( !results.value( QStringLiteral( "VECTOR" ) ).toString().isEmpty() );
QVERIFY( !results.contains( QStringLiteral( "RASTER" ) ) );

// raster input
parameters.insert( QStringLiteral( "INPUT" ), QStringLiteral( "rl" ) );
ok = false;
results = alg->run( parameters, *context, &feedback, &ok );
QVERIFY( ok );
QVERIFY( !results.value( QStringLiteral( "RASTER" ) ).toString().isEmpty() );
QVERIFY( !results.contains( QStringLiteral( "VECTOR" ) ) );
}

QGSTEST_MAIN( TestQgsProcessingAlgs )
#include "testqgsprocessingalgs.moc"

0 comments on commit c1470c4

Please sign in to comment.
You can’t perform that action at this time.