Skip to content

Commit d4acdac

Browse files
authoredJun 9, 2017
Merge pull request #4701 from nyalldawson/processing_pt31
[processing] allow optional feature sink parameters
2 parents 57a6735 + 8c73bcb commit d4acdac

File tree

11 files changed

+398
-276
lines changed

11 files changed

+398
-276
lines changed
 

‎python/core/processing/qgsprocessingoutputs.sip

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class QgsProcessingOutputDefinition
3434
sipType = sipType_QgsProcessingOutputRasterLayer;
3535
else if ( sipCpp->type() == "outputHtml" )
3636
sipType = sipType_QgsProcessingOutputHtml;
37+
else if ( sipCpp->type() == "outputNumber" )
38+
sipType = sipType_QgsProcessingOutputNumber;
3739
%End
3840
public:
3941

@@ -162,6 +164,26 @@ class QgsProcessingOutputHtml : QgsProcessingOutputDefinition
162164
virtual QString type() const;
163165
};
164166

167+
class QgsProcessingOutputNumber : QgsProcessingOutputDefinition
168+
{
169+
%Docstring
170+
A numeric output for processing algorithms.
171+
.. versionadded:: 3.0
172+
%End
173+
174+
%TypeHeaderCode
175+
#include "qgsprocessingoutputs.h"
176+
%End
177+
public:
178+
179+
QgsProcessingOutputNumber( const QString &name, const QString &description = QString() );
180+
%Docstring
181+
Constructor for QgsProcessingOutputNumber.
182+
%End
183+
184+
virtual QString type() const;
185+
};
186+
165187

166188

167189
/************************************************************************

‎python/plugins/processing/algs/qgis/BasicStatistics.py

Lines changed: 95 additions & 80 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,14 @@
3535
QgsStringStatisticalSummary,
3636
QgsDateTimeStatisticalSummary,
3737
QgsFeatureRequest,
38-
QgsProcessingUtils)
38+
QgsProcessingUtils,
39+
QgsProcessingParameterFeatureSource,
40+
QgsProcessingParameterTableField,
41+
QgsProcessingParameterFileOutput,
42+
QgsProcessingOutputHtml,
43+
QgsProcessingOutputNumber)
3944

4045
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
41-
from processing.core.parameters import ParameterTable
42-
from processing.core.parameters import ParameterTableField
43-
from processing.core.outputs import OutputHTML
44-
from processing.core.outputs import OutputNumber
4546

4647
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
4748

@@ -85,35 +86,37 @@ def group(self):
8586

8687
def __init__(self):
8788
super().__init__()
88-
self.addParameter(ParameterTable(self.INPUT_LAYER,
89-
self.tr('Input table')))
90-
self.addParameter(ParameterTableField(self.FIELD_NAME,
91-
self.tr('Field to calculate statistics on'),
92-
self.INPUT_LAYER))
93-
94-
self.addOutput(OutputHTML(self.OUTPUT_HTML_FILE,
95-
self.tr('Statistics')))
96-
97-
self.addOutput(OutputNumber(self.COUNT, self.tr('Count')))
98-
self.addOutput(OutputNumber(self.UNIQUE, self.tr('Number of unique values')))
99-
self.addOutput(OutputNumber(self.EMPTY, self.tr('Number of empty (null) values')))
100-
self.addOutput(OutputNumber(self.FILLED, self.tr('Number of non-empty values')))
101-
self.addOutput(OutputNumber(self.MIN, self.tr('Minimum value')))
102-
self.addOutput(OutputNumber(self.MAX, self.tr('Maximum value')))
103-
self.addOutput(OutputNumber(self.MIN_LENGTH, self.tr('Minimum length')))
104-
self.addOutput(OutputNumber(self.MAX_LENGTH, self.tr('Maximum length')))
105-
self.addOutput(OutputNumber(self.MEAN_LENGTH, self.tr('Mean length')))
106-
self.addOutput(OutputNumber(self.CV, self.tr('Coefficient of Variation')))
107-
self.addOutput(OutputNumber(self.SUM, self.tr('Sum')))
108-
self.addOutput(OutputNumber(self.MEAN, self.tr('Mean value')))
109-
self.addOutput(OutputNumber(self.STD_DEV, self.tr('Standard deviation')))
110-
self.addOutput(OutputNumber(self.RANGE, self.tr('Range')))
111-
self.addOutput(OutputNumber(self.MEDIAN, self.tr('Median')))
112-
self.addOutput(OutputNumber(self.MINORITY, self.tr('Minority (rarest occurring value)')))
113-
self.addOutput(OutputNumber(self.MAJORITY, self.tr('Majority (most frequently occurring value)')))
114-
self.addOutput(OutputNumber(self.FIRSTQUARTILE, self.tr('First quartile')))
115-
self.addOutput(OutputNumber(self.THIRDQUARTILE, self.tr('Third quartile')))
116-
self.addOutput(OutputNumber(self.IQR, self.tr('Interquartile Range (IQR)')))
89+
90+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
91+
self.tr('Input layer')))
92+
93+
self.addParameter(QgsProcessingParameterTableField(self.FIELD_NAME,
94+
self.tr('Field to calculate statistics on'),
95+
None, self.INPUT_LAYER, QgsProcessingParameterTableField.Any))
96+
97+
self.addParameter(QgsProcessingParameterFileOutput(self.OUTPUT_HTML_FILE, self.tr('Statistics'), self.tr('HTML files (*.html)')))
98+
self.addOutput(QgsProcessingOutputHtml(self.OUTPUT_HTML_FILE, self.tr('Statistics')))
99+
100+
self.addOutput(QgsProcessingOutputNumber(self.COUNT, self.tr('Count')))
101+
self.addOutput(QgsProcessingOutputNumber(self.UNIQUE, self.tr('Number of unique values')))
102+
self.addOutput(QgsProcessingOutputNumber(self.EMPTY, self.tr('Number of empty (null) values')))
103+
self.addOutput(QgsProcessingOutputNumber(self.FILLED, self.tr('Number of non-empty values')))
104+
self.addOutput(QgsProcessingOutputNumber(self.MIN, self.tr('Minimum value')))
105+
self.addOutput(QgsProcessingOutputNumber(self.MAX, self.tr('Maximum value')))
106+
self.addOutput(QgsProcessingOutputNumber(self.MIN_LENGTH, self.tr('Minimum length')))
107+
self.addOutput(QgsProcessingOutputNumber(self.MAX_LENGTH, self.tr('Maximum length')))
108+
self.addOutput(QgsProcessingOutputNumber(self.MEAN_LENGTH, self.tr('Mean length')))
109+
self.addOutput(QgsProcessingOutputNumber(self.CV, self.tr('Coefficient of Variation')))
110+
self.addOutput(QgsProcessingOutputNumber(self.SUM, self.tr('Sum')))
111+
self.addOutput(QgsProcessingOutputNumber(self.MEAN, self.tr('Mean value')))
112+
self.addOutput(QgsProcessingOutputNumber(self.STD_DEV, self.tr('Standard deviation')))
113+
self.addOutput(QgsProcessingOutputNumber(self.RANGE, self.tr('Range')))
114+
self.addOutput(QgsProcessingOutputNumber(self.MEDIAN, self.tr('Median')))
115+
self.addOutput(QgsProcessingOutputNumber(self.MINORITY, self.tr('Minority (rarest occurring value)')))
116+
self.addOutput(QgsProcessingOutputNumber(self.MAJORITY, self.tr('Majority (most frequently occurring value)')))
117+
self.addOutput(QgsProcessingOutputNumber(self.FIRSTQUARTILE, self.tr('First quartile')))
118+
self.addOutput(QgsProcessingOutputNumber(self.THIRDQUARTILE, self.tr('Third quartile')))
119+
self.addOutput(QgsProcessingOutputNumber(self.IQR, self.tr('Interquartile Range (IQR)')))
117120

118121
def name(self):
119122
return 'basicstatisticsforfields'
@@ -122,56 +125,64 @@ def displayName(self):
122125
return self.tr('Basic statistics for fields')
123126

124127
def processAlgorithm(self, parameters, context, feedback):
125-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
126-
field_name = self.getParameterValue(self.FIELD_NAME)
127-
field = layer.fields().at(layer.fields().lookupField(field_name))
128+
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
129+
field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
130+
field = source.fields().at(source.fields().lookupField(field_name))
128131

129-
output_file = self.getOutputValue(self.OUTPUT_HTML_FILE)
132+
output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context)
130133

131-
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([field_name], layer.fields())
132-
features = QgsProcessingUtils.getFeatures(layer, context, request)
133-
count = QgsProcessingUtils.featureCount(layer, context)
134+
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([field_name], source.fields())
135+
features = source.getFeatures(request)
136+
count = source.featureCount()
134137

135138
data = []
136-
data.append(self.tr('Analyzed layer: {}').format(layer.name()))
137139
data.append(self.tr('Analyzed field: {}').format(field_name))
140+
results = {}
138141

139142
if field.isNumeric():
140-
data.extend(self.calcNumericStats(features, feedback, field, count))
143+
d, results = self.calcNumericStats(features, feedback, field, count)
144+
data.extend(d)
141145
elif field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime):
142-
data.extend(self.calcDateTimeStats(features, feedback, field, count))
146+
d, results = self.calcDateTimeStats(features, feedback, field, count)
147+
data.extend(d)
143148
else:
144-
data.extend(self.calcStringStats(features, feedback, field, count))
149+
d, results = self.calcStringStats(features, feedback, field, count)
150+
data.extend(d)
145151

146152
self.createHTML(output_file, data)
147153

154+
results[self.OUTPUT_HTML_FILE] = output_file
155+
return results
156+
148157
def calcNumericStats(self, features, feedback, field, count):
149158
total = 100.0 / float(count)
150159
stat = QgsStatisticalSummary()
151160
for current, ft in enumerate(features):
161+
if feedback.isCanceled():
162+
break
152163
stat.addVariant(ft[field.name()])
153164
feedback.setProgress(int(current * total))
154165
stat.finalize()
155166

156167
cv = stat.stDev() / stat.mean() if stat.mean() != 0 else 0
157168

158-
self.setOutputValue(self.COUNT, stat.count())
159-
self.setOutputValue(self.UNIQUE, stat.variety())
160-
self.setOutputValue(self.EMPTY, stat.countMissing())
161-
self.setOutputValue(self.FILLED, count - stat.countMissing())
162-
self.setOutputValue(self.MIN, stat.min())
163-
self.setOutputValue(self.MAX, stat.max())
164-
self.setOutputValue(self.RANGE, stat.range())
165-
self.setOutputValue(self.SUM, stat.sum())
166-
self.setOutputValue(self.MEAN, stat.mean())
167-
self.setOutputValue(self.MEDIAN, stat.median())
168-
self.setOutputValue(self.STD_DEV, stat.stDev())
169-
self.setOutputValue(self.CV, cv)
170-
self.setOutputValue(self.MINORITY, stat.minority())
171-
self.setOutputValue(self.MAJORITY, stat.majority())
172-
self.setOutputValue(self.FIRSTQUARTILE, stat.firstQuartile())
173-
self.setOutputValue(self.THIRDQUARTILE, stat.thirdQuartile())
174-
self.setOutputValue(self.IQR, stat.interQuartileRange())
169+
results = {self.COUNT: stat.count(),
170+
self.UNIQUE: stat.variety(),
171+
self.EMPTY: stat.countMissing(),
172+
self.FILLED: count - stat.countMissing(),
173+
self.MIN: stat.min(),
174+
self.MAX: stat.max(),
175+
self.RANGE: stat.range(),
176+
self.SUM: stat.sum(),
177+
self.MEAN: stat.mean(),
178+
self.MEDIAN: stat.median(),
179+
self.STD_DEV: stat.stDev(),
180+
self.CV: cv,
181+
self.MINORITY: stat.minority(),
182+
self.MAJORITY: stat.majority(),
183+
self.FIRSTQUARTILE: stat.firstQuartile(),
184+
self.THIRDQUARTILE: stat.thirdQuartile(),
185+
self.IQR: stat.interQuartileRange()}
175186

176187
data = []
177188
data.append(self.tr('Count: {}').format(stat.count()))
@@ -190,25 +201,27 @@ def calcNumericStats(self, features, feedback, field, count):
190201
data.append(self.tr('First quartile: {}').format(stat.firstQuartile()))
191202
data.append(self.tr('Third quartile: {}').format(stat.thirdQuartile()))
192203
data.append(self.tr('Interquartile Range (IQR): {}').format(stat.interQuartileRange()))
193-
return data
204+
return data, results
194205

195206
def calcStringStats(self, features, feedback, field, count):
196207
total = 100.0 / float(count)
197208
stat = QgsStringStatisticalSummary()
198209
for current, ft in enumerate(features):
210+
if feedback.isCanceled():
211+
break
199212
stat.addValue(ft[field.name()])
200213
feedback.setProgress(int(current * total))
201214
stat.finalize()
202215

203-
self.setOutputValue(self.COUNT, stat.count())
204-
self.setOutputValue(self.UNIQUE, stat.countDistinct())
205-
self.setOutputValue(self.EMPTY, stat.countMissing())
206-
self.setOutputValue(self.FILLED, stat.count() - stat.countMissing())
207-
self.setOutputValue(self.MIN, stat.min())
208-
self.setOutputValue(self.MAX, stat.max())
209-
self.setOutputValue(self.MIN_LENGTH, stat.minLength())
210-
self.setOutputValue(self.MAX_LENGTH, stat.maxLength())
211-
self.setOutputValue(self.MEAN_LENGTH, stat.meanLength())
216+
results = {self.COUNT: stat.count(),
217+
self.UNIQUE: stat.countDistinct(),
218+
self.EMPTY: stat.countMissing(),
219+
self.FILLED: stat.count() - stat.countMissing(),
220+
self.MIN: stat.min(),
221+
self.MAX: stat.max(),
222+
self.MIN_LENGTH: stat.minLength(),
223+
self.MAX_LENGTH: stat.maxLength(),
224+
self.MEAN_LENGTH: stat.meanLength()}
212225

213226
data = []
214227
data.append(self.tr('Count: {}').format(count))
@@ -220,22 +233,24 @@ def calcStringStats(self, features, feedback, field, count):
220233
data.append(self.tr('Maximum length: {}').format(stat.maxLength()))
221234
data.append(self.tr('Mean length: {}').format(stat.meanLength()))
222235

223-
return data
236+
return data, results
224237

225238
def calcDateTimeStats(self, features, feedback, field, count):
226239
total = 100.0 / float(count)
227240
stat = QgsDateTimeStatisticalSummary()
228241
for current, ft in enumerate(features):
242+
if feedback.isCanceled():
243+
break
229244
stat.addValue(ft[field.name()])
230245
feedback.setProgress(int(current * total))
231246
stat.finalize()
232247

233-
self.setOutputValue(self.COUNT, stat.count())
234-
self.setOutputValue(self.UNIQUE, stat.countDistinct())
235-
self.setOutputValue(self.EMPTY, stat.countMissing())
236-
self.setOutputValue(self.FILLED, stat.count() - stat.countMissing())
237-
self.setOutputValue(self.MIN, stat.statistic(QgsDateTimeStatisticalSummary.Min))
238-
self.setOutputValue(self.MAX, stat.statistic(QgsDateTimeStatisticalSummary.Max))
248+
results = {self.COUNT: stat.count(),
249+
self.UNIQUE: stat.countDistinct(),
250+
self.EMPTY: stat.countMissing(),
251+
self.FILLED: stat.count() - stat.countMissing(),
252+
self.MIN: stat.statistic(QgsDateTimeStatisticalSummary.Min),
253+
self.MAX: stat.statistic(QgsDateTimeStatisticalSummary.Max)}
239254

240255
data = []
241256
data.append(self.tr('Count: {}').format(count))
@@ -244,7 +259,7 @@ def calcDateTimeStats(self, features, feedback, field, count):
244259
data.append(self.tr('Minimum value: {}').format(field.displayString(stat.statistic(QgsDateTimeStatisticalSummary.Min))))
245260
data.append(self.tr('Maximum value: {}').format(field.displayString(stat.statistic(QgsDateTimeStatisticalSummary.Max))))
246261

247-
return data
262+
return data, results
248263

249264
def createHTML(self, outputFile, algData):
250265
with codecs.open(outputFile, 'w', encoding='utf-8') as f:

‎python/plugins/processing/algs/qgis/CheckValidity.py

Lines changed: 60 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,14 @@
3636
QgsField,
3737
QgsWkbTypes,
3838
QgsProcessingUtils,
39-
QgsFields)
39+
QgsFields,
40+
QgsProcessingParameterFeatureSource,
41+
QgsProcessingParameterEnum,
42+
QgsProcessingParameterFeatureSink,
43+
QgsProcessingOutputVectorLayer,
44+
QgsProcessingParameterDefinition
45+
)
4046
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
41-
from processing.core.parameters import ParameterVector
42-
from processing.core.parameters import ParameterSelection
43-
from processing.core.outputs import OutputVector
4447

4548
settings_method_key = "/qgis/digitizing/validate_geometries"
4649
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
@@ -66,26 +69,17 @@ def __init__(self):
6669
'QGIS',
6770
'GEOS']
6871

69-
self.addParameter(ParameterVector(
70-
self.INPUT_LAYER,
71-
self.tr('Input layer')))
72+
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
73+
self.tr('Input layer')))
74+
self.addParameter(QgsProcessingParameterEnum(self.METHOD,
75+
self.tr('Method'), self.methods))
7276

73-
self.addParameter(ParameterSelection(
74-
self.METHOD,
75-
self.tr('Method'),
76-
self.methods))
77-
78-
self.addOutput(OutputVector(
79-
self.VALID_OUTPUT,
80-
self.tr('Valid output')))
81-
82-
self.addOutput(OutputVector(
83-
self.INVALID_OUTPUT,
84-
self.tr('Invalid output')))
85-
86-
self.addOutput(OutputVector(
87-
self.ERROR_OUTPUT,
88-
self.tr('Error output')))
77+
self.addParameter(QgsProcessingParameterFeatureSink(self.VALID_OUTPUT, self.tr('Valid output'), QgsProcessingParameterDefinition.TypeVectorAny, '', True))
78+
self.addOutput(QgsProcessingOutputVectorLayer(self.VALID_OUTPUT, self.tr('Valid output')))
79+
self.addParameter(QgsProcessingParameterFeatureSink(self.INVALID_OUTPUT, self.tr('Invalid output'), QgsProcessingParameterDefinition.TypeVectorAny, '', True))
80+
self.addOutput(QgsProcessingOutputVectorLayer(self.INVALID_OUTPUT, self.tr('Invalid output')))
81+
self.addParameter(QgsProcessingParameterFeatureSink(self.ERROR_OUTPUT, self.tr('Error output'), QgsProcessingParameterDefinition.TypeVectorAny, '', True))
82+
self.addOutput(QgsProcessingOutputVectorLayer(self.ERROR_OUTPUT, self.tr('Error output')))
8983

9084
def name(self):
9185
return 'checkvalidity'
@@ -94,51 +88,48 @@ def displayName(self):
9488
return self.tr('Check validity')
9589

9690
def processAlgorithm(self, parameters, context, feedback):
97-
settings = QgsSettings()
98-
initial_method_setting = settings.value(settings_method_key, 1)
99-
100-
method = self.getParameterValue(self.METHOD)
101-
if method != 0:
102-
settings.setValue(settings_method_key, method)
103-
try:
104-
self.doCheck(context, feedback)
105-
finally:
106-
settings.setValue(settings_method_key, initial_method_setting)
107-
108-
def doCheck(self, context, feedback):
109-
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
110-
111-
settings = QgsSettings()
112-
method = int(settings.value(settings_method_key, 1))
113-
114-
valid_output = self.getOutputFromName(self.VALID_OUTPUT)
115-
valid_fields = layer.fields()
116-
valid_writer = valid_output.getVectorWriter(valid_fields, layer.wkbType(), layer.crs(), context)
91+
method_param = self.parameterAsEnum(parameters, self.METHOD, context)
92+
if method_param == 0:
93+
settings = QgsSettings()
94+
method = int(settings.value(settings_method_key, 0)) - 1
95+
if method < 0:
96+
method = 0
97+
else:
98+
method = method_param - 1
99+
100+
results = self.doCheck(method, parameters, context, feedback)
101+
return results
102+
103+
def doCheck(self, method, parameters, context, feedback):
104+
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
105+
106+
(valid_output_sink, valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, context,
107+
source.fields(), source.wkbType(), source.sourceCrs())
117108
valid_count = 0
118109

119-
invalid_output = self.getOutputFromName(self.INVALID_OUTPUT)
120-
invalid_fields = layer.fields()
121-
invalid_fields.append(QgsField('_errors',
122-
QVariant.String,
123-
255))
124-
invalid_writer = invalid_output.getVectorWriter(invalid_fields, layer.wkbType(), layer.crs(), context)
110+
invalid_fields = source.fields()
111+
invalid_fields.append(QgsField('_errors', QVariant.String, 'string', 255))
112+
(invalid_output_sink, invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, context,
113+
invalid_fields, source.wkbType(), source.sourceCrs())
125114
invalid_count = 0
126115

127-
error_output = self.getOutputFromName(self.ERROR_OUTPUT)
128116
error_fields = QgsFields()
129-
error_fields.append(QgsField('message', QVariant.String, 255))
130-
error_writer = error_output.getVectorWriter(error_fields, QgsWkbTypes.Point, layer.crs(), context)
117+
error_fields.append(QgsField('message', QVariant.String, 'string', 255))
118+
(error_output_sink, error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context,
119+
error_fields, QgsWkbTypes.Point, source.sourceCrs())
131120
error_count = 0
132121

133-
features = QgsProcessingUtils.getFeatures(layer, context)
134-
total = 100.0 / QgsProcessingUtils.featureCount(layer, context)
122+
features = source.getFeatures()
123+
total = 100.0 / source.featureCount()
135124
for current, inFeat in enumerate(features):
125+
if feedback.isCanceled():
126+
break
136127
geom = inFeat.geometry()
137128
attrs = inFeat.attributes()
138129

139130
valid = True
140131
if not geom.isNull() and not geom.isEmpty():
141-
errors = list(geom.validateGeometry())
132+
errors = list(geom.validateGeometry(method))
142133
if errors:
143134
# QGIS method return a summary at the end
144135
if method == 1:
@@ -150,7 +141,8 @@ def doCheck(self, context, feedback):
150141
error_geom = QgsGeometry.fromPoint(error.where())
151142
errFeat.setGeometry(error_geom)
152143
errFeat.setAttributes([error.what()])
153-
error_writer.addFeature(errFeat)
144+
if error_output_sink:
145+
error_output_sink.addFeature(errFeat)
154146
error_count += 1
155147

156148
reasons.append(error.what())
@@ -165,22 +157,22 @@ def doCheck(self, context, feedback):
165157
outFeat.setAttributes(attrs)
166158

167159
if valid:
168-
valid_writer.addFeature(outFeat)
160+
if valid_output_sink:
161+
valid_output_sink.addFeature(outFeat)
169162
valid_count += 1
170163

171164
else:
172-
invalid_writer.addFeature(outFeat)
165+
if invalid_output_sink:
166+
invalid_output_sink.addFeature(outFeat)
173167
invalid_count += 1
174168

175169
feedback.setProgress(int(current * total))
176170

177-
del valid_writer
178-
del invalid_writer
179-
del error_writer
180-
181-
if valid_count != 0:
182-
context.addLayerToLoadOnCompletion(valid_output.value)
183-
if invalid_count != 0:
184-
context.addLayerToLoadOnCompletion(invalid_output.value)
185-
if error_count != 0:
186-
context.addLayerToLoadOnCompletion(error_output.value)
171+
results = {}
172+
if valid_output_sink:
173+
results[self.VALID_OUTPUT] = valid_output_dest_id
174+
if invalid_output_sink:
175+
results[self.INVALID_OUTPUT] = invalid_output_dest_id
176+
if error_output_sink:
177+
results[self.ERROR_OUTPUT] = error_output_dest_id
178+
return results

‎python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@
136136
# from .SplitLinesWithLines import SplitLinesWithLines
137137
# from .FieldsMapper import FieldsMapper
138138
# from .Datasources2Vrt import Datasources2Vrt
139-
# from .CheckValidity import CheckValidity
139+
from .CheckValidity import CheckValidity
140140
# from .OrientedMinimumBoundingBox import OrientedMinimumBoundingBox
141141
# from .Smooth import Smooth
142142
# from .ReverseLineDirection import ReverseLineDirection
@@ -170,7 +170,7 @@
170170
# from .RasterCalculator import RasterCalculator
171171
# from .CreateAttributeIndex import CreateAttributeIndex
172172
# from .DropGeometry import DropGeometry
173-
# from .BasicStatistics import BasicStatisticsForField
173+
from .BasicStatistics import BasicStatisticsForField
174174
# from .Heatmap import Heatmap
175175
# from .Orthogonalize import Orthogonalize
176176
# from .ShortestPathPointToPoint import ShortestPathPointToPoint
@@ -237,7 +237,7 @@ def getAlgs(self):
237237
# SelectByExpression(), HypsometricCurves(),
238238
# SplitWithLines(), SplitLinesWithLines(), CreateConstantRaster(),
239239
# FieldsMapper(), SelectByAttributeSum(), Datasources2Vrt(),
240-
# CheckValidity(), OrientedMinimumBoundingBox(), Smooth(),
240+
# OrientedMinimumBoundingBox(), Smooth(),
241241
# ReverseLineDirection(), SpatialIndex(), DefineProjection(),
242242
# RectanglesOvalsDiamondsVariable(),
243243
# RectanglesOvalsDiamondsFixed(), MergeLines(),
@@ -251,7 +251,7 @@ def getAlgs(self):
251251
# ExtendLines(), ExtractSpecificNodes(),
252252
# GeometryByExpression(), SnapGeometriesToLayer(),
253253
# PoleOfInaccessibility(), CreateAttributeIndex(),
254-
# DropGeometry(), BasicStatisticsForField(),
254+
# DropGeometry(),
255255
# RasterCalculator(), Heatmap(), Orthogonalize(),
256256
# ShortestPathPointToPoint(), ShortestPathPointToLayer(),
257257
# ShortestPathLayerToPoint(), ServiceAreaFromPoint(),
@@ -262,8 +262,10 @@ def getAlgs(self):
262262
algs = [AddTableField(),
263263
Aspect(),
264264
AutoincrementalField(),
265+
BasicStatisticsForField(),
265266
Boundary(),
266267
BoundingBox(),
268+
CheckValidity(),
267269
Clip(),
268270
DeleteColumn(),
269271
ExtentFromLayer()

‎python/plugins/processing/gui/AlgorithmDialog.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,9 @@ def getParamValues(self):
118118
dest_project = QgsProject.instance()
119119

120120
value = self.mainWidget.outputWidgets[param.name()].getValue()
121-
value.destinationProject = dest_project
122-
parameters[param.name()] = value
121+
if value:
122+
value.destinationProject = dest_project
123+
parameters[param.name()] = value
123124

124125
return parameters
125126

‎python/plugins/processing/gui/DestinationSelectionPanel.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
QgsExpression,
4040
QgsSettings,
4141
QgsProcessingParameterFeatureSink,
42-
QgsProcessingOutputLayerDefinition)
42+
QgsProcessingOutputLayerDefinition,
43+
QgsProcessingParameterDefinition)
4344
from processing.core.ProcessingConfig import ProcessingConfig
4445
from processing.core.outputs import OutputVector
4546
from processing.core.outputs import OutputDirectory
@@ -58,6 +59,8 @@ class DestinationSelectionPanel(BASE, WIDGET):
5859
'DestinationSelectionPanel', '[Save to temporary file]')
5960
SAVE_TO_TEMP_LAYER = QCoreApplication.translate(
6061
'DestinationSelectionPanel', '[Create temporary layer]')
62+
SKIP_OUTPUT = QCoreApplication.translate(
63+
'DestinationSelectionPanel', '[Skip output]')
6164

6265
def __init__(self, parameter, alg):
6366
super(DestinationSelectionPanel, self).__init__(None)
@@ -67,23 +70,42 @@ def __init__(self, parameter, alg):
6770
self.alg = alg
6871
settings = QgsSettings()
6972
self.encoding = settings.value('/Processing/encoding', 'System')
73+
self.use_temporary = True
7074

7175
if hasattr(self.leText, 'setPlaceholderText'):
72-
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
76+
if parameter.flags() & QgsProcessingParameterDefinition.FlagOptional and parameter.defaultValue() is None:
77+
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
78+
self.use_temporary = False
79+
elif isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
7380
and alg.provider().supportsNonFileBasedOutput():
7481
# use memory layers for temporary files if supported
7582
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
7683
else:
7784
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
7885

7986
self.btnSelect.clicked.connect(self.selectOutput)
87+
self.leText.textEdited.connect(self.textChanged)
88+
89+
def textChanged(self):
90+
self.use_temporary = False
91+
92+
def skipOutput(self):
93+
self.leText.setPlaceholderText(self.SKIP_OUTPUT)
94+
self.use_temporary = False
8095

8196
def selectOutput(self):
8297
if isinstance(self.parameter, OutputDirectory):
8398
self.selectDirectory()
8499
else:
85100
popupMenu = QMenu()
86101

102+
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
103+
and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
104+
actionSkipOutput = QAction(
105+
self.tr('Skip output'), self.btnSelect)
106+
actionSkipOutput.triggered.connect(self.skipOutput)
107+
popupMenu.addAction(actionSkipOutput)
108+
87109
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) \
88110
and self.alg.provider().supportsNonFileBasedOutput():
89111
# use memory layers for temporary layers if supported
@@ -133,12 +155,18 @@ def showExpressionsBuilder(self):
133155
self.leText.setText(expression.evaluate(context))
134156

135157
def saveToTemporary(self):
158+
if isinstance(self.parameter, QgsProcessingParameterFeatureSink) and self.alg.provider().supportsNonFileBasedOutput():
159+
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_LAYER)
160+
else:
161+
self.leText.setPlaceholderText(self.SAVE_TO_TEMP_FILE)
136162
self.leText.setText('')
163+
self.use_temporary = True
137164

138165
def saveToPostGIS(self):
139166
dlg = PostgisTableSelector(self, self.parameter.name().lower())
140167
dlg.exec_()
141168
if dlg.connection:
169+
self.use_temporary = False
142170
settings = QgsSettings()
143171
mySettings = '/PostgreSQL/connections/' + dlg.connection
144172
dbname = settings.value(mySettings + '/database')
@@ -173,6 +201,7 @@ def saveToSpatialite(self):
173201
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, True)
174202

175203
if fileDialog.exec_() == QDialog.Accepted:
204+
self.use_temporary = False
176205
files = fileDialog.selectedFiles()
177206
self.encoding = str(fileDialog.encoding())
178207
fileName = str(files[0])
@@ -208,6 +237,7 @@ def selectFile(self):
208237
fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)
209238

210239
if fileDialog.exec_() == QDialog.Accepted:
240+
self.use_temporary = False
211241
files = fileDialog.selectedFiles()
212242
self.encoding = str(fileDialog.encoding())
213243
fileName = str(files[0])
@@ -230,11 +260,14 @@ def selectDirectory(self):
230260

231261
def getValue(self):
232262
key = None
233-
if not self.leText.text():
234-
if isinstance(self.parameter, QgsProcessingParameterFeatureSink):
235-
key = 'memory:'
263+
if self.use_temporary and isinstance(self.parameter, QgsProcessingParameterFeatureSink):
264+
key = 'memory:'
236265
else:
237266
key = self.leText.text()
267+
268+
if not key and self.parameter.flags() & QgsProcessingParameterDefinition.FlagOptional:
269+
return None
270+
238271
value = QgsProcessingOutputLayerDefinition(key)
239272
value.createOptions = {'fileEncoding': self.encoding}
240273
return value

‎python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml

Lines changed: 107 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -153,58 +153,56 @@ tests:
153153
# fields:
154154
# fid: skip
155155
#
156-
# - algorithm: qgis:basicstatisticsforfields
157-
# name: Basic statistics for numeric fields
158-
# params:
159-
# - name: multipolys.gml
160-
# type: vector
161-
# - 'Bfloatval'
162-
# results:
163-
# OUTPUT_HTML_FILE:
164-
# name: basic_statistics_numeric_float.html
165-
# type: regex
166-
# rules:
167-
# - 'Analyzed layer: multipolys.gml'
168-
# - 'Analyzed field: Bfloatval'
169-
# - 'Count: 3'
170-
# - 'Unique values: 3'
171-
# - 'Minimum value: -0.123'
172-
# - 'Maximum value: 0.123'
173-
# - 'Range: 0.246'
174-
# - 'Sum: 0.0'
175-
# - 'Mean value: 0.0'
176-
# - 'Median value: 0.0'
177-
# - 'Standard deviation: 0.100429079454'
178-
# - 'Coefficient of Variation: 0'
179-
# - 'Minority \(rarest occurring value\): -0.123'
180-
# - 'Majority \(most frequently occurring value\): -0.123'
181-
# - 'First quartile: -0.0615'
182-
# - 'Third quartile: 0.0615'
183-
# - 'NULL \(missing\) values: 1'
184-
# - 'Interquartile Range \(IQR\): 0.123'
185-
#
186-
# - algorithm: qgis:basicstatisticsforfields
187-
# name: Basic statistics for text fields
188-
# params:
189-
# - name: multipolys.gml
190-
# type: vector
191-
# - 'Bname'
192-
# results:
193-
# OUTPUT_HTML_FILE:
194-
# name: expected/basic_statistics_string.html
195-
# type: regex
196-
# rules:
197-
# - 'Analyzed layer: multipolys.gml'
198-
# - 'Analyzed field: Bname'
199-
# - 'Count: 4'
200-
# - 'Unique values: 2'
201-
# - 'Minimum value: Test'
202-
# - 'Maximum value: Test'
203-
# - 'Minimum length: 0'
204-
# - 'Maximum length: 4'
205-
# - 'Mean length: 3.0'
206-
# - 'NULL \(missing\) values: 1'
207-
#
156+
- algorithm: qgis:basicstatisticsforfields
157+
name: Basic statistics for numeric fields
158+
params:
159+
- name: multipolys.gml
160+
type: vector
161+
- 'Bfloatval'
162+
results:
163+
OUTPUT_HTML_FILE:
164+
name: basic_statistics_numeric_float.html
165+
type: regex
166+
rules:
167+
- 'Analyzed field: Bfloatval'
168+
- 'Count: 3'
169+
- 'Unique values: 3'
170+
- 'Minimum value: -0.123'
171+
- 'Maximum value: 0.123'
172+
- 'Range: 0.246'
173+
- 'Sum: 0.0'
174+
- 'Mean value: 0.0'
175+
- 'Median value: 0.0'
176+
- 'Standard deviation: 0.100429079454'
177+
- 'Coefficient of Variation: 0'
178+
- 'Minority \(rarest occurring value\): -0.123'
179+
- 'Majority \(most frequently occurring value\): -0.123'
180+
- 'First quartile: -0.0615'
181+
- 'Third quartile: 0.0615'
182+
- 'NULL \(missing\) values: 1'
183+
- 'Interquartile Range \(IQR\): 0.123'
184+
185+
- algorithm: qgis:basicstatisticsforfields
186+
name: Basic statistics for text fields
187+
params:
188+
- name: multipolys.gml
189+
type: vector
190+
- 'Bname'
191+
results:
192+
OUTPUT_HTML_FILE:
193+
name: expected/basic_statistics_string.html
194+
type: regex
195+
rules:
196+
- 'Analyzed field: Bname'
197+
- 'Count: 4'
198+
- 'Unique values: 2'
199+
- 'Minimum value: Test'
200+
- 'Maximum value: Test'
201+
- 'Minimum length: 0'
202+
- 'Maximum length: 4'
203+
- 'Mean length: 3.0'
204+
- 'NULL \(missing\) values: 1'
205+
208206
# # Split lines with lines considers two cases
209207
# # case 1: two different layers
210208
# - algorithm: qgis:splitlineswithlines
@@ -1834,66 +1832,63 @@ tests:
18341832
# name: expected/removed_holes_min_area.gml
18351833
# type: vector
18361834
#
1837-
# - algorithm: qgis:basicstatisticsforfields
1838-
# name: Basic stats datetime
1839-
# params:
1840-
# FIELD_NAME: date_time
1841-
# INPUT_LAYER:
1842-
# name: custom/datetimes.tab
1843-
# type: table
1844-
# results:
1845-
# OUTPUT_HTML_FILE:
1846-
# name: expected/basic_statistics_datetime.html
1847-
# type: regex
1848-
# rules:
1849-
# - 'Analyzed layer: custom/datetimes.tab'
1850-
# - 'Analyzed field: date_time'
1851-
# - 'Count: 4'
1852-
# - 'Unique values: 3'
1853-
# - 'Minimum value: 2014-11-30T14:30:02'
1854-
# - 'Maximum value: 2016-11-30T14:29:22'
1855-
# - 'NULL \(missing\) values: 1'
1856-
#
1857-
# - algorithm: qgis:basicstatisticsforfields
1858-
# name: Basic stats date
1859-
# params:
1860-
# FIELD_NAME: date
1861-
# INPUT_LAYER:
1862-
# name: custom/datetimes.tab
1863-
# type: table
1864-
# results:
1865-
# OUTPUT_HTML_FILE:
1866-
# name: expected/basic_statistics_date.html
1867-
# type: regex
1868-
# rules:
1869-
# - 'Analyzed layer: custom/datetimes.tab'
1870-
# - 'Analyzed field: date'
1871-
# - 'Count: 4'
1872-
# - 'Unique values: 3'
1873-
# - 'Minimum value: 2014-11-30T00:00:00'
1874-
# - 'Maximum value: 2016-11-30T00:00:00'
1875-
# - 'NULL \(missing\) values: 1'
1876-
#
1877-
# - algorithm: qgis:basicstatisticsforfields
1878-
# name: Basic stats time
1879-
# params:
1880-
# FIELD_NAME: time
1881-
# INPUT_LAYER:
1882-
# name: custom/datetimes.tab
1883-
# type: table
1884-
# results:
1885-
# OUTPUT_HTML_FILE:
1886-
# name: expected/basic_statistics_time.html
1887-
# type: regex
1888-
# rules:
1889-
# - 'Analyzed layer: custom/datetimes.tab'
1890-
# - 'Analyzed field: time'
1891-
# - 'Count: 4'
1892-
# - 'Unique values: 3'
1893-
# - 'Minimum value: 03:29:40'
1894-
# - 'Maximum value: 15:29:22'
1895-
# - 'NULL \(missing\) values: 1'
1896-
#
1835+
- algorithm: qgis:basicstatisticsforfields
1836+
name: Basic stats datetime
1837+
params:
1838+
FIELD_NAME: date_time
1839+
INPUT_LAYER:
1840+
name: custom/datetimes.tab
1841+
type: table
1842+
results:
1843+
OUTPUT_HTML_FILE:
1844+
name: expected/basic_statistics_datetime.html
1845+
type: regex
1846+
rules:
1847+
- 'Analyzed field: date_time'
1848+
- 'Count: 4'
1849+
- 'Unique values: 3'
1850+
- 'Minimum value: 2014-11-30T14:30:02'
1851+
- 'Maximum value: 2016-11-30T14:29:22'
1852+
- 'NULL \(missing\) values: 1'
1853+
1854+
- algorithm: qgis:basicstatisticsforfields
1855+
name: Basic stats date
1856+
params:
1857+
FIELD_NAME: date
1858+
INPUT_LAYER:
1859+
name: custom/datetimes.tab
1860+
type: table
1861+
results:
1862+
OUTPUT_HTML_FILE:
1863+
name: expected/basic_statistics_date.html
1864+
type: regex
1865+
rules:
1866+
- 'Analyzed field: date'
1867+
- 'Count: 4'
1868+
- 'Unique values: 3'
1869+
- 'Minimum value: 2014-11-30T00:00:00'
1870+
- 'Maximum value: 2016-11-30T00:00:00'
1871+
- 'NULL \(missing\) values: 1'
1872+
1873+
- algorithm: qgis:basicstatisticsforfields
1874+
name: Basic stats time
1875+
params:
1876+
FIELD_NAME: time
1877+
INPUT_LAYER:
1878+
name: custom/datetimes.tab
1879+
type: table
1880+
results:
1881+
OUTPUT_HTML_FILE:
1882+
name: expected/basic_statistics_time.html
1883+
type: regex
1884+
rules:
1885+
- 'Analyzed field: time'
1886+
- 'Count: 4'
1887+
- 'Unique values: 3'
1888+
- 'Minimum value: 03:29:40'
1889+
- 'Maximum value: 15:29:22'
1890+
- 'NULL \(missing\) values: 1'
1891+
18971892
# - algorithm: qgis:rastercalculator
18981893
# name: Raster Calculator with cellsize
18991894
# params:

‎src/core/processing/qgsprocessingoutputs.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ QgsProcessingOutputRasterLayer::QgsProcessingOutputRasterLayer( const QString &n
4545

4646
QgsProcessingOutputHtml::QgsProcessingOutputHtml( const QString &name, const QString &description )
4747
: QgsProcessingOutputDefinition( name, description )
48-
{
48+
{}
4949

50-
}
50+
QgsProcessingOutputNumber::QgsProcessingOutputNumber( const QString &name, const QString &description )
51+
: QgsProcessingOutputDefinition( name, description )
52+
{}

‎src/core/processing/qgsprocessingoutputs.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ class CORE_EXPORT QgsProcessingOutputDefinition
4949
sipType = sipType_QgsProcessingOutputRasterLayer;
5050
else if ( sipCpp->type() == "outputHtml" )
5151
sipType = sipType_QgsProcessingOutputHtml;
52+
else if ( sipCpp->type() == "outputNumber" )
53+
sipType = sipType_QgsProcessingOutputNumber;
5254
SIP_END
5355
#endif
5456

@@ -177,6 +179,24 @@ class CORE_EXPORT QgsProcessingOutputHtml : public QgsProcessingOutputDefinition
177179
QString type() const override { return QStringLiteral( "outputHtml" ); }
178180
};
179181

182+
/**
183+
* \class QgsProcessingOutputNumber
184+
* \ingroup core
185+
* A numeric output for processing algorithms.
186+
* \since QGIS 3.0
187+
*/
188+
class CORE_EXPORT QgsProcessingOutputNumber : public QgsProcessingOutputDefinition
189+
{
190+
public:
191+
192+
/**
193+
* Constructor for QgsProcessingOutputNumber.
194+
*/
195+
QgsProcessingOutputNumber( const QString &name, const QString &description = QString() );
196+
197+
QString type() const override { return QStringLiteral( "outputNumber" ); }
198+
};
199+
180200
#endif // QGSPROCESSINGOUTPUTS_H
181201

182202

‎src/core/processing/qgsprocessingparameters.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,11 @@ QgsFeatureSink *QgsProcessingParameters::parameterAsSink( const QgsProcessingPar
231231
}
232232
else if ( !val.isValid() || val.toString().isEmpty() )
233233
{
234+
if ( definition && definition->flags() & QgsProcessingParameterDefinition::FlagOptional && !definition->defaultValue().isValid() )
235+
{
236+
// unset, optional sink, no default => no sink
237+
return nullptr;
238+
}
234239
// fall back to default
235240
dest = definition->defaultValue().toString();
236241
}

‎tests/src/core/testqgsprocessing.cpp

Lines changed: 38 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2562,23 +2562,58 @@ void TestQgsProcessing::processingFeatureSink()
25622562
context.setProject( &p );
25632563

25642564
// first using static string definition
2565-
QgsProcessingParameterDefinition *def = new QgsProcessingParameterString( QStringLiteral( "layer" ) );
2565+
std::unique_ptr< QgsProcessingParameterDefinition > def( new QgsProcessingParameterString( QStringLiteral( "layer" ) ) );
25662566
QVariantMap params;
25672567
params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( "memory:test", nullptr ) );
25682568
QString dest;
2569-
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def, params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) );
2569+
std::unique_ptr< QgsFeatureSink > sink( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3111" ), context, dest ) );
25702570
QVERIFY( sink.get() );
25712571
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
25722572
QVERIFY( layer );
25732573
QCOMPARE( layer->crs().authid(), QStringLiteral( "EPSG:3111" ) );
25742574

25752575
// next using property based definition
25762576
params.insert( QStringLiteral( "layer" ), QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( QStringLiteral( "trim('memory' + ':test2')" ) ), nullptr ) );
2577-
sink.reset( QgsProcessingParameters::parameterAsSink( def, params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
2577+
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
25782578
QVERIFY( sink.get() );
25792579
QgsVectorLayer *layer2 = qobject_cast< QgsVectorLayer *>( QgsProcessingUtils::mapLayerFromString( dest, context, false ) );
25802580
QVERIFY( layer2 );
25812581
QCOMPARE( layer2->crs().authid(), QStringLiteral( "EPSG:3113" ) );
2582+
2583+
2584+
// non optional sink
2585+
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QVariant(), false ) );
2586+
QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
2587+
QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
2588+
QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
2589+
QVERIFY( !def->checkValueIsAcceptable( QString() ) );
2590+
QVERIFY( !def->checkValueIsAcceptable( QVariant() ) );
2591+
QVERIFY( !def->checkValueIsAcceptable( 5 ) );
2592+
params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
2593+
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
2594+
QVERIFY( sink.get() );
2595+
2596+
// optional sink
2597+
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QVariant(), true ) );
2598+
QVERIFY( def->checkValueIsAcceptable( QStringLiteral( "memory:test" ) ) );
2599+
QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "memory:test" ) ) );
2600+
QVERIFY( def->checkValueIsAcceptable( QgsProperty::fromValue( "memory:test" ) ) );
2601+
QVERIFY( def->checkValueIsAcceptable( QString() ) );
2602+
QVERIFY( def->checkValueIsAcceptable( QVariant() ) );
2603+
QVERIFY( !def->checkValueIsAcceptable( 5 ) );
2604+
params.insert( QStringLiteral( "layer" ), QStringLiteral( "memory:test" ) );
2605+
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
2606+
QVERIFY( sink.get() );
2607+
// optional sink, not set - should be no sink
2608+
params.insert( QStringLiteral( "layer" ), QVariant() );
2609+
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
2610+
QVERIFY( !sink.get() );
2611+
2612+
//.... unless there's a default set
2613+
def.reset( new QgsProcessingParameterFeatureSink( QStringLiteral( "layer" ), QString(), QgsProcessingParameterDefinition::TypeAny, QStringLiteral( "memory:defaultlayer" ), true ) );
2614+
params.insert( QStringLiteral( "layer" ), QVariant() );
2615+
sink.reset( QgsProcessingParameters::parameterAsSink( def.get(), params, QgsFields(), QgsWkbTypes::Point, QgsCoordinateReferenceSystem( "EPSG:3113" ), context, dest ) );
2616+
QVERIFY( sink.get() );
25822617
}
25832618

25842619
void TestQgsProcessing::algorithmScope()

0 commit comments

Comments
 (0)
Please sign in to comment.