Skip to content

Commit 857f843

Browse files
authored
Merge pull request #4873 from nyalldawson/processing_feature_alg
Add QgsProcessingFeatureBasedAlgorithm subclass
2 parents 0b263f9 + 11cfc78 commit 857f843

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+932
-280
lines changed

python/core/processing/qgsprocessingalgorithm.sip

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class QgsProcessingAlgorithm
3030
%ConvertToSubClassCode
3131
if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL )
3232
sipType = sipType_QgsProcessingModelAlgorithm;
33+
else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL )
34+
sipType = sipType_QgsProcessingFeatureBasedAlgorithm;
3335
else
3436
sipType = sipType_QgsProcessingAlgorithm;
3537
%End
@@ -717,6 +719,136 @@ QFlags<QgsProcessingAlgorithm::Flag> operator|(QgsProcessingAlgorithm::Flag f1,
717719

718720

719721

722+
class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm
723+
{
724+
%Docstring
725+
An abstract QgsProcessingAlgorithm base class for processing algorithms which operate "feature-by-feature".
726+
727+
Feature based algorithms are algorithms which operate on individual features in isolation. These
728+
are algorithms where one feature is output for each input feature, and the output feature result
729+
for each input feature is not dependent on any other features present in the source.
730+
731+
For instance, algorithms like "centroids" and "buffers" are feature based algorithms since the centroid
732+
or buffer of a feature is calculated for each feature in isolation. An algorithm like "dissolve"
733+
is NOT suitable for a feature based algorithm as the dissolved output depends on multiple input features
734+
and these features cannot be processed in isolation.
735+
736+
Using QgsProcessingFeatureBasedAlgorithm as the base class for feature based algorithms allows
737+
shortcutting much of the common algorithm code for handling iterating over sources and pushing
738+
features to output sinks. It also allows the algorithm execution to be optimised in future
739+
(for instance allowing automatic multi-thread processing of the algorithm, or use of the
740+
algorithm in "chains", avoiding the need for temporary outputs in multi-step models).
741+
742+
.. versionadded:: 3.0
743+
%End
744+
745+
%TypeHeaderCode
746+
#include "qgsprocessingalgorithm.h"
747+
%End
748+
public:
749+
750+
QgsProcessingFeatureBasedAlgorithm();
751+
%Docstring
752+
Constructor for QgsProcessingFeatureBasedAlgorithm.
753+
%End
754+
755+
protected:
756+
757+
virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() );
758+
759+
760+
virtual QString outputName() const = 0;
761+
%Docstring
762+
Returns the translated, user visible name for any layers created by this algorithm.
763+
This name will be used as the default name when loading the resultant layer into a
764+
QGIS project.
765+
:rtype: str
766+
%End
767+
768+
virtual QgsProcessing::LayerType outputLayerType() const;
769+
%Docstring
770+
Returns the layer type for layers generated by this algorithm, if
771+
this is possible to determine in advance.
772+
:rtype: QgsProcessing.LayerType
773+
%End
774+
775+
virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const;
776+
%Docstring
777+
Maps the input WKB geometry type (``inputWkbType``) to the corresponding
778+
output WKB type generated by the algorithm. The default behavior is that the algorithm maintains
779+
the same WKB type.
780+
This is called once by the base class when creating the output sink for the algorithm (i.e. it is
781+
not called once per feature processed).
782+
:rtype: QgsWkbTypes.Type
783+
%End
784+
785+
virtual QgsFields outputFields( const QgsFields &inputFields ) const;
786+
%Docstring
787+
Maps the input source fields (``inputFields``) to corresponding
788+
output fields generated by the algorithm. The default behavior is that the algorithm maintains
789+
the same fields as are input.
790+
Algorithms which add, remove or modify existing fields should override this method and
791+
implement logic here to indicate which fields are output by the algorithm.
792+
793+
This is called once by the base class when creating the output sink for the algorithm (i.e. it is
794+
not called once per feature processed).
795+
:rtype: QgsFields
796+
%End
797+
798+
virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const;
799+
%Docstring
800+
Maps the input source coordinate reference system (``inputCrs``) to a corresponding
801+
output CRS generated by the algorithm. The default behavior is that the algorithm maintains
802+
the same CRS as the input source.
803+
804+
This is called once by the base class when creating the output sink for the algorithm (i.e. it is
805+
not called once per feature processed).
806+
:rtype: QgsCoordinateReferenceSystem
807+
%End
808+
809+
virtual void initParameters( const QVariantMap &configuration = QVariantMap() );
810+
%Docstring
811+
Initializes any extra parameters added by the algorithm subclass. There is no need
812+
to declare the input source or output sink, as these are automatically created by
813+
QgsProcessingFeatureBasedAlgorithm.
814+
%End
815+
816+
QgsCoordinateReferenceSystem sourceCrs() const;
817+
%Docstring
818+
Returns the source's coordinate reference system. This will only return a valid CRS when
819+
called from a subclasses' processFeature() implementation.
820+
:rtype: QgsCoordinateReferenceSystem
821+
%End
822+
823+
virtual QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0;
824+
%Docstring
825+
Processes an individual input ``feature`` from the source. Algorithms should implement their
826+
logic in this method for performing the algorithm's operation (e.g. replacing the feature's
827+
geometry with the centroid of the original feature geometry for a 'centroid' type
828+
algorithm).
829+
830+
Implementations should return the modified feature. Returning an invalid feature (e.g.
831+
a default constructed QgsFeature) will indicate that this feature should be 'skipped',
832+
and will not be added to the algorithm's output. Subclasses can use this approach to
833+
filter the incoming features as desired.
834+
835+
The provided ``feedback`` object can be used to push messages to the log and for giving feedback
836+
to users. Note that handling of progress reports and algorithm cancelation is handled by
837+
the base class and subclasses do not need to reimplement this logic.
838+
839+
Algorithms can throw a QgsProcessingException if a fatal error occurred which should
840+
prevent the algorithm execution from continuing. This can be annoying for users though as it
841+
can break valid model execution - so use with extreme caution, and consider using
842+
``feedback`` to instead report non-fatal processing failures for features instead.
843+
:rtype: QgsFeature
844+
%End
845+
846+
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
847+
QgsProcessingContext &context, QgsProcessingFeedback *feedback );
848+
849+
};
850+
851+
720852

721853
/************************************************************************
722854
* This file has been generated automatically from *

python/plugins/processing/algs/help/qgis.yaml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,9 @@ qgis:distancetonearesthub: >
160160
qgis:dropgeometries: >
161161
This algorithm removes any geometries from an input layer and returns a layer containing only the feature attributes.
162162

163+
qgis:dropmzvalues: >
164+
This algorithm can remove any measure (M) or Z values from input geometries.
165+
163166
qgis:eliminateselectedpolygons: >
164167
This algorithm combines selected polygons of the input layer with certain adjacent polygons by erasing their common boundary. The adjacent polygon can be either the one with the largest or smallest area or the one sharing the largest common boundary with the polygon to be eliminated. The selected features will always be eliminated whether the option "Use only selected features" is set or not.
165168
Eliminate is normally used to get rid of sliver polygons, i.e. tiny polygons that are a result of polygon intersection processes where boundaries of the inputs are similar but not identical.
@@ -503,13 +506,22 @@ qgis:selectbyexpression: >
503506
qgis:selectbylocation: >
504507
This algorithm creates a selection in a vector layer. The criteria for selecting features is based on the spatial relationship between each feature and the features in an additional layer.
505508

509+
qgis:setmvalue: >
510+
This algorithm sets the M value for geometries in a layer.
511+
512+
If M values already exist in the layer, they will be overwritten with the new value. If no M values exist, the geometry will be upgraded to include M values and the specified value used as the initial M value for all geometries.
506513

507514
qgis:setstyleforrasterlayer: >
508515
This algorithm sets the style of a raster layer. The style must be defined in a QML file.
509516

510517
qgis:setstyleforvectorlayer: >
511518
This algorithm sets the style of a vector layer. The style must be defined in a QML file.
512519

520+
qgis:setzvalue: >
521+
This algorithm sets the Z value for geometries in a layer.
522+
523+
If Z values already exist in the layer, they will be overwritten with the new value. If no Z values exist, the geometry will be upgraded to include Z values and the specified value used as the initial Z value for all geometries.
524+
513525
qgis:simplifygeometries: >
514526
This algorithm simplifies the geometries in a line or polygon layer. It creates a new layer with the same features as the ones in the input layer, but with geometries containing a lower number of vertices.
515527

python/plugins/processing/algs/qgis/AddTableField.py

Lines changed: 23 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,19 +27,14 @@
2727

2828
from qgis.PyQt.QtCore import QVariant
2929
from qgis.core import (QgsField,
30-
QgsFeatureSink,
31-
QgsProcessingParameterFeatureSource,
3230
QgsProcessingParameterString,
3331
QgsProcessingParameterNumber,
34-
QgsProcessingParameterEnum,
35-
QgsProcessingParameterFeatureSink)
36-
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
32+
QgsProcessingParameterEnum)
33+
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
3734

3835

39-
class AddTableField(QgisAlgorithm):
36+
class AddTableField(QgisFeatureBasedAlgorithm):
4037

41-
OUTPUT_LAYER = 'OUTPUT_LAYER'
42-
INPUT_LAYER = 'INPUT_LAYER'
4338
FIELD_NAME = 'FIELD_NAME'
4439
FIELD_TYPE = 'FIELD_TYPE'
4540
FIELD_LENGTH = 'FIELD_LENGTH'
@@ -55,10 +50,9 @@ def __init__(self):
5550
self.type_names = [self.tr('Integer'),
5651
self.tr('Float'),
5752
self.tr('String')]
53+
self.field = None
5854

59-
def initAlgorithm(self, config=None):
60-
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
61-
self.tr('Input layer')))
55+
def initParameters(self, config=None):
6256
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
6357
self.tr('Field name')))
6458
self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE,
@@ -68,41 +62,32 @@ def initAlgorithm(self, config=None):
6862
10, False, 1, 255))
6963
self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION,
7064
self.tr('Field precision'), QgsProcessingParameterNumber.Integer, 0, False, 0, 10))
71-
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added')))
7265

7366
def name(self):
7467
return 'addfieldtoattributestable'
7568

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

79-
def processAlgorithm(self, parameters, context, feedback):
80-
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
72+
def outputName(self):
73+
return self.tr('Added')
8174

82-
fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
83-
fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context)
84-
fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
85-
fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
75+
def prepareAlgorithm(self, parameters, context, feedback):
76+
field_type = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
77+
field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
78+
field_length = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
79+
field_precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
8680

87-
fields = source.fields()
88-
fields.append(QgsField(fieldName, self.TYPES[fieldType], '',
89-
fieldLength, fieldPrecision))
90-
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
91-
fields, source.wkbType(), source.sourceCrs())
81+
self.field = QgsField(field_name, self.TYPES[field_type], '',
82+
field_length, field_precision)
83+
return True
9284

93-
features = source.getFeatures()
94-
total = 100.0 / source.featureCount() if source.featureCount() else 0
85+
def outputFields(self, inputFields):
86+
inputFields.append(self.field)
87+
return inputFields
9588

96-
for current, input_feature in enumerate(features):
97-
if feedback.isCanceled():
98-
break
99-
100-
output_feature = input_feature
101-
attributes = input_feature.attributes()
102-
attributes.append(None)
103-
output_feature.setAttributes(attributes)
104-
105-
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
106-
feedback.setProgress(int(current * total))
107-
108-
return {self.OUTPUT_LAYER: dest_id}
89+
def processFeature(self, feature, feedback):
90+
attributes = feature.attributes()
91+
attributes.append(None)
92+
feature.setAttributes(attributes)
93+
return feature

python/plugins/processing/algs/qgis/AutoincrementalField.py

Lines changed: 15 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,15 @@
2626
__revision__ = '$Format:%H$'
2727

2828
from qgis.PyQt.QtCore import QVariant
29-
from qgis.core import (QgsField,
30-
QgsFeatureSink,
31-
QgsProcessingParameterFeatureSource,
32-
QgsProcessingParameterFeatureSink)
33-
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
29+
from qgis.core import (QgsField)
30+
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
3431

3532

36-
class AutoincrementalField(QgisAlgorithm):
37-
38-
INPUT = 'INPUT'
39-
OUTPUT = 'OUTPUT'
33+
class AutoincrementalField(QgisFeatureBasedAlgorithm):
4034

4135
def __init__(self):
4236
super().__init__()
43-
44-
def initAlgorithm(self, config=None):
45-
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
46-
self.tr('Input layer')))
47-
48-
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented')))
37+
self.current = 0
4938

5039
def group(self):
5140
return self.tr('Vector table tools')
@@ -56,26 +45,16 @@ def name(self):
5645
def displayName(self):
5746
return self.tr('Add autoincremental field')
5847

59-
def processAlgorithm(self, parameters, context, feedback):
60-
source = self.parameterAsSource(parameters, self.INPUT, context)
61-
fields = source.fields()
62-
fields.append(QgsField('AUTO', QVariant.Int))
63-
64-
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
65-
fields, source.wkbType(), source.sourceCrs())
66-
67-
features = source.getFeatures()
68-
total = 100.0 / source.featureCount() if source.featureCount() else 0
69-
for current, input_feature in enumerate(features):
70-
if feedback.isCanceled():
71-
break
72-
73-
output_feature = input_feature
74-
attributes = input_feature.attributes()
75-
attributes.append(current)
76-
output_feature.setAttributes(attributes)
48+
def outputName(self):
49+
return self.tr('Incremented')
7750

78-
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
79-
feedback.setProgress(int(current * total))
51+
def outputFields(self, inputFields):
52+
inputFields.append(QgsField('AUTO', QVariant.Int))
53+
return inputFields
8054

81-
return {self.OUTPUT: dest_id}
55+
def processFeature(self, feature, feedback):
56+
attributes = feature.attributes()
57+
attributes.append(self.current)
58+
self.current += 1
59+
feature.setAttributes(attributes)
60+
return feature

0 commit comments

Comments
 (0)