Skip to content
Permalink
Browse files

Algorithms don't have to be split to prepare/process/postProcess

Since it's safe to evaluate parameters in background threads
now, it's usually going to be ok to evaluate everything in
the processAlgorithm step.

This keeps the algorithm code as simple as possible, and will
make porting faster.

Note that the prepare/postProcess virtual methods still exist
and can be used when an algorithm MUST do setup/cleanup work
in the main thread.
  • Loading branch information
nyalldawson committed Jul 6, 2017
1 parent f39b7a0 commit 8a84e134cc671db7cd969d2af2282ee137de5e85
Showing with 817 additions and 1,353 deletions.
  1. +5 −5 python/core/processing/qgsprocessingalgorithm.sip
  2. +1 −5 python/core/processing/qgsprocessingmodelalgorithm.sip
  3. +15 −28 python/plugins/processing/algs/qgis/AddTableField.py
  4. +9 −17 python/plugins/processing/algs/qgis/Aspect.py
  5. +9 −17 python/plugins/processing/algs/qgis/AutoincrementalField.py
  6. +19 −29 python/plugins/processing/algs/qgis/BasicStatistics.py
  7. +9 −18 python/plugins/processing/algs/qgis/Boundary.py
  8. +8 −16 python/plugins/processing/algs/qgis/BoundingBox.py
  9. +43 −59 python/plugins/processing/algs/qgis/CheckValidity.py
  10. +9 −17 python/plugins/processing/algs/qgis/CreateAttributeIndex.py
  11. +16 −26 python/plugins/processing/algs/qgis/DeleteColumn.py
  12. +12 −21 python/plugins/processing/algs/qgis/DeleteHoles.py
  13. +10 −19 python/plugins/processing/algs/qgis/DensifyGeometries.py
  14. +10 −19 python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py
  15. +8 −17 python/plugins/processing/algs/qgis/DropGeometry.py
  16. +9 −18 python/plugins/processing/algs/qgis/ExtentFromLayer.py
  17. +9 −17 python/plugins/processing/algs/qgis/FixGeometry.py
  18. +26 −41 python/plugins/processing/algs/qgis/GridPolygon.py
  19. +47 −63 python/plugins/processing/algs/qgis/ImportIntoPostGIS.py
  20. +43 −61 python/plugins/processing/algs/qgis/ImportIntoSpatialite.py
  21. +26 −38 python/plugins/processing/algs/qgis/Merge.py
  22. +23 −35 python/plugins/processing/algs/qgis/PointsLayerFromTable.py
  23. +5 −13 python/plugins/processing/algs/qgis/PostGISExecuteSQL.py
  24. +17 −29 python/plugins/processing/algs/qgis/RandomExtract.py
  25. +19 −31 python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py
  26. +24 −38 python/plugins/processing/algs/qgis/RegularPoints.py
  27. +8 −16 python/plugins/processing/algs/qgis/SaveSelectedFeatures.py
  28. +21 −34 python/plugins/processing/algs/qgis/SelectByAttribute.py
  29. +10 −22 python/plugins/processing/algs/qgis/SelectByExpression.py
  30. +15 −26 python/plugins/processing/algs/qgis/SimplifyGeometries.py
  31. +12 −23 python/plugins/processing/algs/qgis/Smooth.py
  32. +5 −13 python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py
  33. +19 −28 python/plugins/processing/algs/qgis/SymmetricalDifference.py
  34. +20 −31 python/plugins/processing/algs/qgis/VectorSplit.py
  35. +1 −4 python/plugins/processing/algs/qgis/ZonalStatistics.py
  36. +1 −4 python/plugins/processing/script/ScriptAlgorithm.py
  37. +1 −1 python/plugins/processing/tests/AlgorithmsTestBase.py
  38. +218 −304 src/core/processing/qgsnativealgorithms.cpp
  39. +20 −112 src/core/processing/qgsnativealgorithms.h
  40. +20 −9 src/core/processing/qgsprocessingalgorithm.cpp
  41. +3 −3 src/core/processing/qgsprocessingalgorithm.h
  42. +7 −4 src/core/processing/qgsprocessingalgrunnertask.cpp
  43. +1 −0 src/core/processing/qgsprocessingalgrunnertask.h
  44. +2 −13 src/core/processing/qgsprocessingmodelalgorithm.cpp
  45. +1 −6 src/core/processing/qgsprocessingmodelalgorithm.h
  46. +1 −3 tests/src/core/testqgsprocessing.cpp
@@ -259,7 +259,7 @@ class QgsProcessingAlgorithm
:rtype: bool
%End

bool runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback );
QVariantMap runPrepared( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback );
%Docstring
Runs the algorithm, which has been prepared by an earlier call to prepare().
This method is safe to call from any thread. Returns true if the algorithm was successfully executed.
@@ -272,7 +272,7 @@ class QgsProcessingAlgorithm
This method modifies the algorithm instance, so it is not safe to call
on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy
of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy.
:rtype: bool
:rtype: QVariantMap
%End

QVariantMap postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback );
@@ -374,7 +374,7 @@ class QgsProcessingAlgorithm
:rtype: bool
%End

virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/;
virtual QVariantMap processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/;
%Docstring
Runs the algorithm using the specified ``parameters``. Algorithms should implement
their custom processing logic here.
@@ -389,10 +389,10 @@ class QgsProcessingAlgorithm
values such as statistical calculations.
.. seealso:: prepareAlgorithm()
.. seealso:: postProcessAlgorithm()
:rtype: bool
:rtype: QVariantMap
%End

virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/;
virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) /VirtualErrorHandler=processing_exception_handler/;
%Docstring
Allows the algorithm to perform any required cleanup tasks. The returned variant map
includes the results evaluated by the algorithm. These may be output layer references, or calculated
@@ -835,11 +835,7 @@ Copies are protected to avoid slicing

protected:

virtual bool prepareAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback );
virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback );

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


};
@@ -74,38 +74,28 @@ def __init__(self):
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added')))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr('Added')))

self.source = None
self.fieldType = None
self.fieldLength = None
self.fieldName = None
self.fieldPrecision = None
self.sink = None
self.dest_id = None

def name(self):
return 'addfieldtoattributestable'

def displayName(self):
return self.tr('Add field to attributes table')

def prepareAlgorithm(self, parameters, context, feedback):
self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)

self.fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
self.fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context)
self.fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
self.fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context)
fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)

fields = self.source.fields()
fields.append(QgsField(self.fieldName, self.TYPES[self.fieldType], '',
self.fieldLength, self.fieldPrecision))
(self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
fields, self.source.wkbType(), self.source.sourceCrs())
return True
fields = source.fields()
fields.append(QgsField(fieldName, self.TYPES[fieldType], '',
fieldLength, fieldPrecision))
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
fields, source.wkbType(), source.sourceCrs())

def processAlgorithm(self, context, feedback):
features = self.source.getFeatures()
total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0

for current, input_feature in enumerate(features):
if feedback.isCanceled():
@@ -116,10 +106,7 @@ def processAlgorithm(self, context, feedback):
attributes.append(None)
output_feature.setAttributes(attributes)

self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))

return True

def postProcessAlgorithm(self, context, feedback):
return {self.OUTPUT_LAYER: self.dest_id}
return {self.OUTPUT_LAYER: dest_id}
@@ -68,30 +68,22 @@ def __init__(self):
self.addParameter(QgsProcessingParameterRasterOutput(self.OUTPUT, self.tr('Aspect')))
self.addOutput(QgsProcessingOutputRasterLayer(self.OUTPUT, self.tr('Aspect')))

self.inputFile = None
self.outputFile = None
self.outputFormat = None
self.zFactor = None

def name(self):
return 'aspect'

def displayName(self):
return self.tr('Aspect')

def prepareAlgorithm(self, parameters, context, feedback):
self.inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT, context))
self.zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context)
def processAlgorithm(self, parameters, context, feedback):
inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT, context))
zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context)

self.outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT, context)
outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT, context)

self.outputFormat = raster.formatShortNameFromFileName(self.outputFile)
return True
outputFormat = raster.formatShortNameFromFileName(outputFile)

def processAlgorithm(self, context, feedback):
aspect = QgsAspectFilter(self.inputFile, self.outputFile, self.outputFormat)
aspect.setZFactor(self.zFactor)
return aspect.processRaster(feedback) == 0
aspect = QgsAspectFilter(inputFile, outputFile, outputFormat)
aspect.setZFactor(zFactor)
aspect.processRaster(feedback)

def postProcessAlgorithm(self, context, feedback):
return {self.OUTPUT: self.outputFile}
return {self.OUTPUT: outputFile}
@@ -51,10 +51,6 @@ def __init__(self):
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented')))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Incremented')))

self.source = None
self.sink = None
self.dest_id = None

def group(self):
return self.tr('Vector table tools')

@@ -64,18 +60,16 @@ def name(self):
def displayName(self):
return self.tr('Add autoincremental field')

def prepareAlgorithm(self, parameters, context, feedback):
self.source = self.parameterAsSource(parameters, self.INPUT, context)
fields = self.source.fields()
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
fields = source.fields()
fields.append(QgsField('AUTO', QVariant.Int))

(self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, self.source.wkbType(), self.source.sourceCrs())
return True
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, source.wkbType(), source.sourceCrs())

def processAlgorithm(self, context, feedback):
features = self.source.getFeatures()
total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, input_feature in enumerate(features):
if feedback.isCanceled():
break
@@ -85,9 +79,7 @@ def processAlgorithm(self, context, feedback):
attributes.append(current)
output_feature.setAttributes(attributes)

self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return True

def postProcessAlgorithm(self, context, feedback):
return {self.OUTPUT: self.dest_id}
return {self.OUTPUT: dest_id}
@@ -119,52 +119,42 @@ def __init__(self):
self.addOutput(QgsProcessingOutputNumber(self.THIRDQUARTILE, self.tr('Third quartile')))
self.addOutput(QgsProcessingOutputNumber(self.IQR, self.tr('Interquartile Range (IQR)')))

self.source = None
self.field = None
self.field_name = None
self.output_file = None
self.results = {}

def name(self):
return 'basicstatisticsforfields'

def displayName(self):
return self.tr('Basic statistics for fields')

def prepareAlgorithm(self, parameters, context, feedback):
self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
self.field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
self.field = self.source.fields().at(self.source.fields().lookupField(self.field_name))
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
field = source.fields().at(source.fields().lookupField(field_name))

self.output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context)
return True
output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context)

def processAlgorithm(self, context, feedback):
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([self.field_name], self.source.fields())
features = self.source.getFeatures(request)
count = self.source.featureCount()
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([field_name], source.fields())
features = source.getFeatures(request)
count = source.featureCount()

data = []
data.append(self.tr('Analyzed field: {}').format(self.field_name))
data.append(self.tr('Analyzed field: {}').format(field_name))
results = {}

if self.field.isNumeric():
d, self.results = self.calcNumericStats(features, feedback, self.field, count)
if field.isNumeric():
d, results = self.calcNumericStats(features, feedback, field, count)
data.extend(d)
elif self.field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime):
d, self.results = self.calcDateTimeStats(features, feedback, self.field, count)
elif field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime):
d, results = self.calcDateTimeStats(features, feedback, field, count)
data.extend(d)
else:
d, self.results = self.calcStringStats(features, feedback, self.field, count)
d, results = self.calcStringStats(features, feedback, field, count)
data.extend(d)

if self.output_file:
self.createHTML(self.output_file, data)
self.results[self.OUTPUT_HTML_FILE] = self.output_file

return True
if output_file:
self.createHTML(output_file, data)
results[self.OUTPUT_HTML_FILE] = output_file

def postProcessAlgorithm(self, context, feedback):
return self.results
return results

def calcNumericStats(self, features, feedback, field, count):
total = 100.0 / count if count else 0
@@ -58,10 +58,6 @@ def __init__(self):
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Boundary')))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Boundaries")))

self.source = None
self.sink = None
self.dest_id = None

def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png'))

@@ -74,11 +70,10 @@ def name(self):
def displayName(self):
return self.tr('Boundary')

def prepareAlgorithm(self, parameters, context, feedback):
self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)

input_wkb = self.source.wkbType()
output_wkb = None
input_wkb = source.wkbType()
if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry:
output_wkb = QgsWkbTypes.MultiPoint
elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry:
@@ -88,13 +83,11 @@ def prepareAlgorithm(self, parameters, context, feedback):
if QgsWkbTypes.hasM(input_wkb):
output_wkb = QgsWkbTypes.addM(output_wkb)

(self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
self.source.fields(), output_wkb, self.source.sourceCrs())
return True
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
source.fields(), output_wkb, source.sourceCrs())

def processAlgorithm(self, context, feedback):
features = self.source.getFeatures()
total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0

for current, input_feature in enumerate(features):
if feedback.isCanceled():
@@ -109,9 +102,7 @@ def processAlgorithm(self, context, feedback):

output_feature.setGeometry(output_geometry)

self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return True

def postProcessAlgorithm(self, context, feedback):
return {self.OUTPUT_LAYER: self.dest_id}
return {self.OUTPUT_LAYER: dest_id}
@@ -66,26 +66,20 @@ def __init__(self):
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Bounds'), QgsProcessingParameterDefinition.TypeVectorPolygon))
self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Bounds")))

self.source = None
self.sink = None
self.dest_id = None

def name(self):
return 'boundingboxes'

def displayName(self):
return self.tr('Bounding boxes')

def prepareAlgorithm(self, parameters, context, feedback):
self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)

(self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
self.source.fields(), QgsWkbTypes.Polygon, self.source.sourceCrs())
return True
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
source.fields(), QgsWkbTypes.Polygon, source.sourceCrs())

def processAlgorithm(self, context, feedback):
features = self.source.getFeatures()
total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0

for current, input_feature in enumerate(features):
if feedback.isCanceled():
@@ -100,9 +94,7 @@ def processAlgorithm(self, context, feedback):

output_feature.setGeometry(output_geometry)

self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return True

def postProcessAlgorithm(self, context, feedback):
return {self.OUTPUT_LAYER: self.dest_id}
return {self.OUTPUT_LAYER: dest_id}

0 comments on commit 8a84e13

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