Skip to content

Commit

Permalink
Merge pull request #4873 from nyalldawson/processing_feature_alg
Browse files Browse the repository at this point in the history
Add QgsProcessingFeatureBasedAlgorithm subclass
  • Loading branch information
nyalldawson committed Jul 18, 2017
2 parents 0b263f9 + 11cfc78 commit 857f843
Show file tree
Hide file tree
Showing 46 changed files with 932 additions and 280 deletions.
132 changes: 132 additions & 0 deletions python/core/processing/qgsprocessingalgorithm.sip
Expand Up @@ -30,6 +30,8 @@ class QgsProcessingAlgorithm
%ConvertToSubClassCode %ConvertToSubClassCode
if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL ) if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingModelAlgorithm; sipType = sipType_QgsProcessingModelAlgorithm;
else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingFeatureBasedAlgorithm;
else else
sipType = sipType_QgsProcessingAlgorithm; sipType = sipType_QgsProcessingAlgorithm;
%End %End
Expand Down Expand Up @@ -717,6 +719,136 @@ QFlags<QgsProcessingAlgorithm::Flag> operator|(QgsProcessingAlgorithm::Flag f1,






class QgsProcessingFeatureBasedAlgorithm : QgsProcessingAlgorithm
{
%Docstring
An abstract QgsProcessingAlgorithm base class for processing algorithms which operate "feature-by-feature".

Feature based algorithms are algorithms which operate on individual features in isolation. These
are algorithms where one feature is output for each input feature, and the output feature result
for each input feature is not dependent on any other features present in the source.

For instance, algorithms like "centroids" and "buffers" are feature based algorithms since the centroid
or buffer of a feature is calculated for each feature in isolation. An algorithm like "dissolve"
is NOT suitable for a feature based algorithm as the dissolved output depends on multiple input features
and these features cannot be processed in isolation.

Using QgsProcessingFeatureBasedAlgorithm as the base class for feature based algorithms allows
shortcutting much of the common algorithm code for handling iterating over sources and pushing
features to output sinks. It also allows the algorithm execution to be optimised in future
(for instance allowing automatic multi-thread processing of the algorithm, or use of the
algorithm in "chains", avoiding the need for temporary outputs in multi-step models).

.. versionadded:: 3.0
%End

%TypeHeaderCode
#include "qgsprocessingalgorithm.h"
%End
public:

QgsProcessingFeatureBasedAlgorithm();
%Docstring
Constructor for QgsProcessingFeatureBasedAlgorithm.
%End

protected:

virtual void initAlgorithm( const QVariantMap &configuration = QVariantMap() );


virtual QString outputName() const = 0;
%Docstring
Returns the translated, user visible name for any layers created by this algorithm.
This name will be used as the default name when loading the resultant layer into a
QGIS project.
:rtype: str
%End

virtual QgsProcessing::LayerType outputLayerType() const;
%Docstring
Returns the layer type for layers generated by this algorithm, if
this is possible to determine in advance.
:rtype: QgsProcessing.LayerType
%End

virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const;
%Docstring
Maps the input WKB geometry type (``inputWkbType``) to the corresponding
output WKB type generated by the algorithm. The default behavior is that the algorithm maintains
the same WKB type.
This is called once by the base class when creating the output sink for the algorithm (i.e. it is
not called once per feature processed).
:rtype: QgsWkbTypes.Type
%End

virtual QgsFields outputFields( const QgsFields &inputFields ) const;
%Docstring
Maps the input source fields (``inputFields``) to corresponding
output fields generated by the algorithm. The default behavior is that the algorithm maintains
the same fields as are input.
Algorithms which add, remove or modify existing fields should override this method and
implement logic here to indicate which fields are output by the algorithm.

This is called once by the base class when creating the output sink for the algorithm (i.e. it is
not called once per feature processed).
:rtype: QgsFields
%End

virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const;
%Docstring
Maps the input source coordinate reference system (``inputCrs``) to a corresponding
output CRS generated by the algorithm. The default behavior is that the algorithm maintains
the same CRS as the input source.

This is called once by the base class when creating the output sink for the algorithm (i.e. it is
not called once per feature processed).
:rtype: QgsCoordinateReferenceSystem
%End

virtual void initParameters( const QVariantMap &configuration = QVariantMap() );
%Docstring
Initializes any extra parameters added by the algorithm subclass. There is no need
to declare the input source or output sink, as these are automatically created by
QgsProcessingFeatureBasedAlgorithm.
%End

QgsCoordinateReferenceSystem sourceCrs() const;
%Docstring
Returns the source's coordinate reference system. This will only return a valid CRS when
called from a subclasses' processFeature() implementation.
:rtype: QgsCoordinateReferenceSystem
%End

virtual QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0;
%Docstring
Processes an individual input ``feature`` from the source. Algorithms should implement their
logic in this method for performing the algorithm's operation (e.g. replacing the feature's
geometry with the centroid of the original feature geometry for a 'centroid' type
algorithm).

Implementations should return the modified feature. Returning an invalid feature (e.g.
a default constructed QgsFeature) will indicate that this feature should be 'skipped',
and will not be added to the algorithm's output. Subclasses can use this approach to
filter the incoming features as desired.

The provided ``feedback`` object can be used to push messages to the log and for giving feedback
to users. Note that handling of progress reports and algorithm cancelation is handled by
the base class and subclasses do not need to reimplement this logic.

Algorithms can throw a QgsProcessingException if a fatal error occurred which should
prevent the algorithm execution from continuing. This can be annoying for users though as it
can break valid model execution - so use with extreme caution, and consider using
``feedback`` to instead report non-fatal processing failures for features instead.
:rtype: QgsFeature
%End

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

};




/************************************************************************ /************************************************************************
* This file has been generated automatically from * * This file has been generated automatically from *
Expand Down
12 changes: 12 additions & 0 deletions python/plugins/processing/algs/help/qgis.yaml
Expand Up @@ -160,6 +160,9 @@ qgis:distancetonearesthub: >
qgis:dropgeometries: > qgis:dropgeometries: >
This algorithm removes any geometries from an input layer and returns a layer containing only the feature attributes. This algorithm removes any geometries from an input layer and returns a layer containing only the feature attributes.


qgis:dropmzvalues: >
This algorithm can remove any measure (M) or Z values from input geometries.

qgis:eliminateselectedpolygons: > qgis:eliminateselectedpolygons: >
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. 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.
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. 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.
Expand Down Expand Up @@ -503,13 +506,22 @@ qgis:selectbyexpression: >
qgis:selectbylocation: > qgis:selectbylocation: >
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. 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.


qgis:setmvalue: >
This algorithm sets the M value for geometries in a layer.

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.


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


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


qgis:setzvalue: >
This algorithm sets the Z value for geometries in a layer.

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.

qgis:simplifygeometries: > qgis:simplifygeometries: >
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. 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.


Expand Down
61 changes: 23 additions & 38 deletions python/plugins/processing/algs/qgis/AddTableField.py
Expand Up @@ -27,19 +27,14 @@


from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField, from qgis.core import (QgsField,
QgsFeatureSink,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterString, QgsProcessingParameterString,
QgsProcessingParameterNumber, QgsProcessingParameterNumber,
QgsProcessingParameterEnum, QgsProcessingParameterEnum)
QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm




class AddTableField(QgisAlgorithm): class AddTableField(QgisFeatureBasedAlgorithm):


OUTPUT_LAYER = 'OUTPUT_LAYER'
INPUT_LAYER = 'INPUT_LAYER'
FIELD_NAME = 'FIELD_NAME' FIELD_NAME = 'FIELD_NAME'
FIELD_TYPE = 'FIELD_TYPE' FIELD_TYPE = 'FIELD_TYPE'
FIELD_LENGTH = 'FIELD_LENGTH' FIELD_LENGTH = 'FIELD_LENGTH'
Expand All @@ -55,10 +50,9 @@ def __init__(self):
self.type_names = [self.tr('Integer'), self.type_names = [self.tr('Integer'),
self.tr('Float'), self.tr('Float'),
self.tr('String')] self.tr('String')]
self.field = None


def initAlgorithm(self, config=None): def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME, self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
self.tr('Field name'))) self.tr('Field name')))
self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE, self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE,
Expand All @@ -68,41 +62,32 @@ def initAlgorithm(self, config=None):
10, False, 1, 255)) 10, False, 1, 255))
self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION, self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION,
self.tr('Field precision'), QgsProcessingParameterNumber.Integer, 0, False, 0, 10)) self.tr('Field precision'), QgsProcessingParameterNumber.Integer, 0, False, 0, 10))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added')))


def name(self): def name(self):
return 'addfieldtoattributestable' return 'addfieldtoattributestable'


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


def processAlgorithm(self, parameters, context, feedback): def outputName(self):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) return self.tr('Added')


fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) def prepareAlgorithm(self, parameters, context, feedback):
fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) field_type = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) field_length = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
field_precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)


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


features = source.getFeatures() def outputFields(self, inputFields):
total = 100.0 / source.featureCount() if source.featureCount() else 0 inputFields.append(self.field)
return inputFields


for current, input_feature in enumerate(features): def processFeature(self, feature, feedback):
if feedback.isCanceled(): attributes = feature.attributes()
break attributes.append(None)

feature.setAttributes(attributes)
output_feature = input_feature return feature
attributes = input_feature.attributes()
attributes.append(None)
output_feature.setAttributes(attributes)

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

return {self.OUTPUT_LAYER: dest_id}
51 changes: 15 additions & 36 deletions python/plugins/processing/algs/qgis/AutoincrementalField.py
Expand Up @@ -26,26 +26,15 @@
__revision__ = '$Format:%H$' __revision__ = '$Format:%H$'


from qgis.PyQt.QtCore import QVariant from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField, from qgis.core import (QgsField)
QgsFeatureSink, from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm




class AutoincrementalField(QgisAlgorithm): class AutoincrementalField(QgisFeatureBasedAlgorithm):

INPUT = 'INPUT'
OUTPUT = 'OUTPUT'


def __init__(self): def __init__(self):
super().__init__() super().__init__()

self.current = 0
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer')))

self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented')))


def group(self): def group(self):
return self.tr('Vector table tools') return self.tr('Vector table tools')
Expand All @@ -56,26 +45,16 @@ def name(self):
def displayName(self): def displayName(self):
return self.tr('Add autoincremental field') return self.tr('Add autoincremental field')


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

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

features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
for current, input_feature in enumerate(features):
if feedback.isCanceled():
break

output_feature = input_feature
attributes = input_feature.attributes()
attributes.append(current)
output_feature.setAttributes(attributes)


sink.addFeature(output_feature, QgsFeatureSink.FastInsert) def outputFields(self, inputFields):
feedback.setProgress(int(current * total)) inputFields.append(QgsField('AUTO', QVariant.Int))
return inputFields


return {self.OUTPUT: dest_id} def processFeature(self, feature, feedback):
attributes = feature.attributes()
attributes.append(self.current)
self.current += 1
feature.setAttributes(attributes)
return feature

0 comments on commit 857f843

Please sign in to comment.