Skip to content

Commit

Permalink
[FEATURE][processing] Add algorithm to filter by layer type
Browse files Browse the repository at this point in the history
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 c1470c4
Show file tree
Hide file tree
Showing 5 changed files with 297 additions and 1 deletion.
97 changes: 97 additions & 0 deletions src/analysis/processing/qgsalgorithmfilterbygeometry.cpp
Expand Up @@ -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" ),
Expand Down Expand Up @@ -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

31 changes: 30 additions & 1 deletion src/analysis/processing/qgsalgorithmfilterbygeometry.h
Expand Up @@ -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;
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions src/analysis/processing/qgsnativealgorithms.cpp
Expand Up @@ -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() );
Expand Down
129 changes: 129 additions & 0 deletions tests/src/analysis/testqgsprocessing.cpp
Expand Up @@ -580,6 +580,7 @@ class TestQgsProcessing: public QObject
void asPythonCommand();
void modelerAlgorithm();
void modelExecution();
void modelBranchPruning();
void modelWithProviderWithLimitedTypes();
void modelVectorOutputIsCompatibleType();
void modelAcceptableValues();
Expand Down Expand Up @@ -9064,6 +9065,134 @@ void TestQgsProcessing::modelExecution()
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() );
Expand Down
40 changes: 40 additions & 0 deletions tests/src/analysis/testqgsprocessingalgs.cpp
Expand Up @@ -112,6 +112,8 @@ class TestQgsProcessingAlgs: public QObject
void raiseException();
void raiseWarning();

void filterByLayerType();

private:

QString mPointLayerPath;
Expand Down Expand Up @@ -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.