[processing] Add explicit output type for multiple layers

This was a missing capability in the processing API - while algorithms
could declare multiple layer input parameters, there was no corresponding
multi-layer output. This meant that algorithms (such as Package Layers,
Vector Split) which create a set of layers which cannot be determined
in advance had no way to pass these generated layers on for further model
processing steps.

It's also useful for algorithms which operate on a specified folder,
processing all layers found there, and allowing these generated
outputs to be utilised in other model steps (e.g. packaging
all of them, merging them, etc)
This commit is contained in:
Nyall Dawson 2018-02-11 10:57:44 +10:00
parent 779fe1a91d
commit 4bcc9df5b4
6 changed files with 118 additions and 14 deletions

View File

@ -33,6 +33,8 @@ as generated layers or calculated values.
sipType = sipType_QgsProcessingOutputRasterLayer;
else if ( sipCpp->type() == QgsProcessingOutputMapLayer::typeName() )
sipType = sipType_QgsProcessingOutputMapLayer;
else if ( sipCpp->type() == QgsProcessingOutputMultipleLayers::typeName() )
sipType = sipType_QgsProcessingOutputMultipleLayers;
else if ( sipCpp->type() == QgsProcessingOutputHtml::typeName() )
sipType = sipType_QgsProcessingOutputHtml;
else if ( sipCpp->type() == QgsProcessingOutputNumber::typeName() )
@ -196,6 +198,42 @@ Returns the type name for the output class.
virtual QString type() const;
};
class QgsProcessingOutputMultipleLayers : QgsProcessingOutputDefinition
{
%Docstring
A multi-layer output for processing algorithms which create map layers, when
the number and nature of the output layers is not predefined.
.. note::
Always prefer to explicitly define QgsProcessingOutputVectorLayer,
QgsProcessingOutputRasterLayer or QgsProcessingOutputMapLayer where possible. :py:class:`QgsProcessingOutputMultipleLayers`
should only ever be used when the number of output layers is not
fixed - e.g. as a result of processing all layers in a specified
folder.
.. versionadded:: 3.0
%End
%TypeHeaderCode
#include "qgsprocessingoutputs.h"
%End
public:
QgsProcessingOutputMultipleLayers( const QString &name, const QString &description = QString() );
%Docstring
Constructor for QgsProcessingOutputMultipleLayers.
%End
static QString typeName();
%Docstring
Returns the type name for the output class.
%End
virtual QString type() const;
};
class QgsProcessingOutputHtml : QgsProcessingOutputDefinition

View File

@ -42,7 +42,8 @@ from qgis.core import (QgsMessageLog,
QgsProcessingParameterDefinition,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputRasterLayer,
QgsProcessingOutputMapLayer)
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers)
import processing
from processing.core.ProcessingConfig import ProcessingConfig
@ -163,6 +164,22 @@ class Processing(object):
layer = context.takeResultLayer(result) # transfer layer ownership out of context
if layer:
results[out.name()] = layer # replace layer string ref with actual layer (+ownership)
elif isinstance(out, QgsProcessingOutputMultipleLayers):
result = results[out.name()]
if result:
layers_result = []
for l in result:
if not isinstance(result, QgsMapLayer):
layer = context.takeResultLayer(l) # transfer layer ownership out of context
if layer:
layers_result.append(layer)
else:
layers_result.append(l)
else:
layers_result.append(l)
results[out.name()] = layers_result # replace layers strings ref with actual layers (+ownership)
else:
msg = Processing.tr("There were errors executing the algorithm.")
feedback.reportError(msg)

View File

@ -42,7 +42,8 @@ from qgis.core import (QgsExpressionContext,
QgsProcessingOutputHtml,
QgsProcessingOutputNumber,
QgsProcessingOutputString,
QgsProcessingOutputFolder)
QgsProcessingOutputFolder,
QgsProcessingOutputMultipleLayers)
def getOutputFromString(s):
@ -69,6 +70,8 @@ def getOutputFromString(s):
out = QgsProcessingOutputVectorLayer(name, description)
elif token.lower().strip() == 'outputlayer':
out = QgsProcessingOutputMapLayer(name, description)
elif token.lower().strip() == 'outputmultilayers':
out = QgsProcessingOutputMultipleLayers(name, description)
# elif token.lower().strip() == 'vector point':
# out = OutputVector(datatype=[dataobjects.TYPE_VECTOR_POINT])
# elif token.lower().strip() == 'vector line':

View File

@ -66,6 +66,7 @@ from qgis.core import (
QgsProcessingOutputRasterLayer,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers,
QgsProcessingOutputFile,
QgsProcessingOutputString,
QgsProcessingOutputNumber,
@ -544,20 +545,23 @@ class MultipleLayerWidgetWrapper(WidgetWrapper):
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer])
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers])
elif self.param.layerType() == QgsProcessing.TypeVector:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer],
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVector])
elif self.param.layerType() == QgsProcessing.TypeVectorPoint:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer],
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorPoint,
QgsProcessing.TypeVectorAnyGeometry])
elif self.param.layerType() == QgsProcessing.TypeVectorLine:
@ -565,7 +569,8 @@ class MultipleLayerWidgetWrapper(WidgetWrapper):
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer],
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorLine,
QgsProcessing.TypeVectorAnyGeometry])
elif self.param.layerType() == QgsProcessing.TypeVectorPolygon:
@ -573,18 +578,22 @@ class MultipleLayerWidgetWrapper(WidgetWrapper):
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer],
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorPolygon,
QgsProcessing.TypeVectorAnyGeometry])
elif self.param.layerType() == QgsProcessing.TypeRaster:
options = self.dialog.getAvailableValuesOfType(
(QgsProcessingParameterRasterLayer, QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputRasterLayer,
QgsProcessingOutputMapLayer])
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers])
elif self.param.layerType() == QgsProcessing.TypeVector:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers), QgsProcessingOutputVectorLayer)
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMultipleLayers])
else:
options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterFile, QgsProcessingOutputFile)
options = sorted(options, key=lambda opt: self.dialog.resolveValueDescription(opt))

View File

@ -65,13 +65,18 @@ QgsProcessingOutputFile::QgsProcessingOutputFile( const QString &name, const QSt
QgsProcessingOutputMapLayer::QgsProcessingOutputMapLayer( const QString &name, const QString &description )
: QgsProcessingOutputDefinition( name, description )
{
}
{}
QString QgsProcessingOutputMapLayer::type() const
{
return typeName();
}
QgsProcessingOutputMultipleLayers::QgsProcessingOutputMultipleLayers( const QString &name, const QString &description )
: QgsProcessingOutputDefinition( name, description )
{}
QString QgsProcessingOutputMultipleLayers::type() const
{
return typeName();
}

View File

@ -49,6 +49,8 @@ class CORE_EXPORT QgsProcessingOutputDefinition
sipType = sipType_QgsProcessingOutputRasterLayer;
else if ( sipCpp->type() == QgsProcessingOutputMapLayer::typeName() )
sipType = sipType_QgsProcessingOutputMapLayer;
else if ( sipCpp->type() == QgsProcessingOutputMultipleLayers::typeName() )
sipType = sipType_QgsProcessingOutputMultipleLayers;
else if ( sipCpp->type() == QgsProcessingOutputHtml::typeName() )
sipType = sipType_QgsProcessingOutputHtml;
else if ( sipCpp->type() == QgsProcessingOutputNumber::typeName() )
@ -209,6 +211,36 @@ class CORE_EXPORT QgsProcessingOutputRasterLayer : public QgsProcessingOutputDef
};
/**
* \class QgsProcessingOutputMultipleLayers
* \ingroup core
* A multi-layer output for processing algorithms which create map layers, when
* the number and nature of the output layers is not predefined.
*
* \note Always prefer to explicitly define QgsProcessingOutputVectorLayer,
* QgsProcessingOutputRasterLayer or QgsProcessingOutputMapLayer where possible. QgsProcessingOutputMultipleLayers
* should only ever be used when the number of output layers is not
* fixed - e.g. as a result of processing all layers in a specified
* folder.
* \since QGIS 3.0
*/
class CORE_EXPORT QgsProcessingOutputMultipleLayers : public QgsProcessingOutputDefinition
{
public:
/**
* Constructor for QgsProcessingOutputMultipleLayers.
*/
QgsProcessingOutputMultipleLayers( const QString &name, const QString &description = QString() );
/**
* Returns the type name for the output class.
*/
static QString typeName() { return QStringLiteral( "outputMultilayer" ); }
QString type() const override;
};
/**
* \class QgsProcessingOutputHtml
* \ingroup core