Skip to content

Commit

Permalink
Merge pull request #6308 from nyalldawson/multi_out
Browse files Browse the repository at this point in the history
 [processing] Add explicit output type for multiple layers
  • Loading branch information
nyalldawson committed Feb 11, 2018
2 parents dd3ba34 + 414c3a4 commit 367aba1
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 13 deletions.
38 changes: 38 additions & 0 deletions python/core/processing/qgsprocessingoutputs.sip.in
Expand Up @@ -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() )
Expand Down Expand Up @@ -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
Expand Down
7 changes: 6 additions & 1 deletion python/plugins/processing/algs/qgis/VectorSplit.py
Expand Up @@ -33,6 +33,7 @@
QgsProcessingParameterField,
QgsProcessingParameterFolderDestination,
QgsProcessingOutputFolder,
QgsProcessingOutputMultipleLayers,
QgsExpression,
QgsFeatureRequest)

Expand All @@ -47,6 +48,7 @@ class VectorSplit(QgisAlgorithm):
INPUT = 'INPUT'
FIELD = 'FIELD'
OUTPUT = 'OUTPUT'
OUTPUT_LAYERS = 'OUTPUT_LAYERS'

def group(self):
return self.tr('Vector general')
Expand All @@ -66,6 +68,7 @@ def initAlgorithm(self, config=None):

self.addParameter(QgsProcessingParameterFolderDestination(self.OUTPUT,
self.tr('Output directory')))
self.addOutput(QgsProcessingOutputMultipleLayers(self.OUTPUT_LAYERS, self.tr('Output layers')))

def name(self):
return 'splitvectorlayer'
Expand All @@ -89,6 +92,7 @@ def processAlgorithm(self, parameters, context, feedback):
geomType = source.wkbType()

total = 100.0 / len(uniqueValues) if uniqueValues else 1
output_layers = []

for current, i in enumerate(uniqueValues):
if feedback.isCanceled():
Expand All @@ -108,8 +112,9 @@ def processAlgorithm(self, parameters, context, feedback):
sink.addFeature(f, QgsFeatureSink.FastInsert)
count += 1
feedback.pushInfo(self.tr('Added {} features to layer').format(count))
output_layers.append(fName)
del sink

feedback.setProgress(int(current * total))

return {self.OUTPUT: directory}
return {self.OUTPUT: directory, self.OUTPUT_LAYERS: output_layers}
19 changes: 18 additions & 1 deletion python/plugins/processing/core/Processing.py
Expand Up @@ -42,7 +42,8 @@
QgsProcessingParameterDefinition,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputRasterLayer,
QgsProcessingOutputMapLayer)
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers)

import processing
from processing.core.ProcessingConfig import ProcessingConfig
Expand Down Expand Up @@ -163,6 +164,22 @@ def runAlgorithm(algOrName, parameters, onFinish=None, feedback=None, context=No
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)
Expand Down
5 changes: 4 additions & 1 deletion python/plugins/processing/core/outputs.py
Expand Up @@ -42,7 +42,8 @@
QgsProcessingOutputHtml,
QgsProcessingOutputNumber,
QgsProcessingOutputString,
QgsProcessingOutputFolder)
QgsProcessingOutputFolder,
QgsProcessingOutputMultipleLayers)


def getOutputFromString(s):
Expand All @@ -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':
Expand Down
32 changes: 25 additions & 7 deletions python/plugins/processing/gui/wrappers.py
Expand Up @@ -66,6 +66,7 @@
QgsProcessingOutputRasterLayer,
QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers,
QgsProcessingOutputFile,
QgsProcessingOutputString,
QgsProcessingOutputNumber,
Expand Down Expand Up @@ -544,47 +545,55 @@ def _getOptions(self):
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:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
QgsProcessingParameterVectorLayer,
QgsProcessingParameterMultipleLayers),
[QgsProcessingOutputVectorLayer,
QgsProcessingOutputMapLayer],
QgsProcessingOutputMapLayer,
QgsProcessingOutputMultipleLayers],
[QgsProcessing.TypeVectorLine,
QgsProcessing.TypeVectorAnyGeometry])
elif self.param.layerType() == QgsProcessing.TypeVectorPolygon:
options = self.dialog.getAvailableValuesOfType((QgsProcessingParameterFeatureSource,
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))
Expand All @@ -599,6 +608,9 @@ def createWidget(self):
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.param.layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.param.layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.layerType()],
False)
Expand All @@ -618,6 +630,9 @@ def refresh(self):
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.param.layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.param.layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.layerType()],
False)
Expand Down Expand Up @@ -658,6 +673,9 @@ def value(self):
options = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False)
elif self.param.layerType() in (QgsProcessing.TypeVectorAnyGeometry, QgsProcessing.TypeVector):
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
elif self.param.layerType() == QgsProcessing.TypeMapLayer:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [], False)
options.extend(QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance(), False))
else:
options = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance(), [self.param.layerType()],
False)
Expand Down
5 changes: 5 additions & 0 deletions src/analysis/processing/qgsalgorithmpackage.cpp
Expand Up @@ -53,6 +53,7 @@ void QgsPackageAlgorithm::initAlgorithm( const QVariantMap & )
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT" ), QObject::tr( "Destination GeoPackage" ), QObject::tr( "GeoPackage files (*.gpkg)" ) ) );
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "OVERWRITE" ), QObject::tr( "Overwrite existing GeoPackage" ), false ) );
addOutput( new QgsProcessingOutputFile( QStringLiteral( "OUTPUT" ), QObject::tr( "GeoPackage" ) ) );
addOutput( new QgsProcessingOutputMultipleLayers( QStringLiteral( "OUTPUT_LAYERS" ), QObject::tr( "Layers within new package" ) ) );
}

QString QgsPackageAlgorithm::shortHelpString() const
Expand Down Expand Up @@ -97,6 +98,7 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters

QgsProcessingMultiStepFeedback multiStepFeedback( layers.count(), feedback );

QStringList outputLayers;
int i = 0;
for ( QgsMapLayer *layer : layers )
{
Expand All @@ -123,6 +125,8 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters
if ( !packageVectorLayer( qobject_cast< QgsVectorLayer * >( layer ), packagePath,
context, &multiStepFeedback ) )
errored = true;
else
outputLayers.append( QStringLiteral( "%1|layername=%2" ).arg( packagePath, layer->name() ) );
break;
}

Expand All @@ -147,6 +151,7 @@ QVariantMap QgsPackageAlgorithm::processAlgorithm( const QVariantMap &parameters

QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), packagePath );
outputs.insert( QStringLiteral( "OUTPUT_LAYERS" ), outputLayers );
return outputs;
}

Expand Down
11 changes: 8 additions & 3 deletions src/core/processing/qgsprocessingoutputs.cpp
Expand Up @@ -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 QgsProcessingOutputMapLayer::type() const
QString QgsProcessingOutputMultipleLayers::type() const
{
return typeName();
}
32 changes: 32 additions & 0 deletions src/core/processing/qgsprocessingoutputs.h
Expand Up @@ -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() )
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 367aba1

Please sign in to comment.