Merge pull request #4873 from nyalldawson/processing_feature_alg

Add QgsProcessingFeatureBasedAlgorithm subclass
This commit is contained in:
Nyall Dawson 2017-07-19 05:31:30 +10:00 committed by GitHub
commit 857f8437d1
46 changed files with 934 additions and 282 deletions

View File

@ -30,6 +30,8 @@ class QgsProcessingAlgorithm
%ConvertToSubClassCode
if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingModelAlgorithm;
else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingFeatureBasedAlgorithm;
else
sipType = sipType_QgsProcessingAlgorithm;
%End
@ -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 *

View File

@ -160,6 +160,9 @@ qgis:distancetonearesthub: >
qgis:dropgeometries: >
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: >
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.
@ -503,6 +506,10 @@ qgis:selectbyexpression: >
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.
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: >
This algorithm sets the style of a raster layer. The style must be defined in a QML file.
@ -510,6 +517,11 @@ qgis:setstyleforrasterlayer: >
qgis:setstyleforvectorlayer: >
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: >
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.

View File

@ -27,19 +27,14 @@ __revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField,
QgsFeatureSink,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterString,
QgsProcessingParameterNumber,
QgsProcessingParameterEnum,
QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
QgsProcessingParameterEnum)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
class AddTableField(QgisAlgorithm):
class AddTableField(QgisFeatureBasedAlgorithm):
OUTPUT_LAYER = 'OUTPUT_LAYER'
INPUT_LAYER = 'INPUT_LAYER'
FIELD_NAME = 'FIELD_NAME'
FIELD_TYPE = 'FIELD_TYPE'
FIELD_LENGTH = 'FIELD_LENGTH'
@ -55,10 +50,9 @@ class AddTableField(QgisAlgorithm):
self.type_names = [self.tr('Integer'),
self.tr('Float'),
self.tr('String')]
self.field = None
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER,
self.tr('Input layer')))
def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
self.tr('Field name')))
self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE,
@ -68,7 +62,6 @@ class AddTableField(QgisAlgorithm):
10, False, 1, 255))
self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION,
self.tr('Field precision'), QgsProcessingParameterNumber.Integer, 0, False, 0, 10))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added')))
def name(self):
return 'addfieldtoattributestable'
@ -76,33 +69,25 @@ class AddTableField(QgisAlgorithm):
def displayName(self):
return self.tr('Add field to attributes table')
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
def outputName(self):
return self.tr('Added')
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)
def prepareAlgorithm(self, parameters, context, feedback):
field_type = self.parameterAsEnum(parameters, self.FIELD_TYPE, context)
field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
field_length = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
field_precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
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())
self.field = QgsField(field_name, self.TYPES[field_type], '',
field_length, field_precision)
return True
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
def outputFields(self, inputFields):
inputFields.append(self.field)
return inputFields
for current, input_feature in enumerate(features):
if feedback.isCanceled():
break
output_feature = input_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}
def processFeature(self, feature, feedback):
attributes = feature.attributes()
attributes.append(None)
feature.setAttributes(attributes)
return feature

View File

@ -26,26 +26,15 @@ __copyright__ = '(C) 2012, Victor Olaya'
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QVariant
from qgis.core import (QgsField,
QgsFeatureSink,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from qgis.core import (QgsField)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
class AutoincrementalField(QgisAlgorithm):
INPUT = 'INPUT'
OUTPUT = 'OUTPUT'
class AutoincrementalField(QgisFeatureBasedAlgorithm):
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT,
self.tr('Input layer')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented')))
self.current = 0
def group(self):
return self.tr('Vector table tools')
@ -56,26 +45,16 @@ class AutoincrementalField(QgisAlgorithm):
def displayName(self):
return self.tr('Add autoincremental field')
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT, context)
fields = source.fields()
fields.append(QgsField('AUTO', QVariant.Int))
def outputName(self):
return self.tr('Incremented')
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, source.wkbType(), source.sourceCrs())
def outputFields(self, inputFields):
inputFields.append(QgsField('AUTO', QVariant.Int))
return inputFields
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)
feedback.setProgress(int(current * total))
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

View File

@ -29,24 +29,17 @@ import os
from qgis.core import (QgsGeometry,
QgsWkbTypes,
QgsFeatureSink,
QgsProcessing,
QgsProcessingException,
QgsProcessingParameterFeatureSource,
QgsProcessingParameterFeatureSink)
QgsProcessingException)
from qgis.PyQt.QtGui import QIcon
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class BoundingBox(QgisAlgorithm):
INPUT_LAYER = 'INPUT_LAYER'
OUTPUT_LAYER = 'OUTPUT_LAYER'
class BoundingBox(QgisFeatureBasedAlgorithm):
def icon(self):
return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'matrix.png'))
@ -57,39 +50,26 @@ class BoundingBox(QgisAlgorithm):
def __init__(self):
super().__init__()
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Bounds'), QgsProcessing.TypeVectorPolygon))
def name(self):
return 'boundingboxes'
def displayName(self):
return self.tr('Bounding boxes')
def processAlgorithm(self, parameters, context, feedback):
source = self.parameterAsSource(parameters, self.INPUT_LAYER, context)
def outputName(self):
return self.tr('Bounds')
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context,
source.fields(), QgsWkbTypes.Polygon, source.sourceCrs())
def outputWkbType(self, inputWkb):
return QgsWkbTypes.Polygon
features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0
def processFeature(self, feature, feedback):
input_geometry = feature.geometry()
if input_geometry:
output_geometry = QgsGeometry.fromRect(input_geometry.boundingBox())
if not output_geometry:
raise QgsProcessingException(
self.tr('Error calculating bounding box'))
for current, input_feature in enumerate(features):
if feedback.isCanceled():
break
output_feature = input_feature
input_geometry = input_feature.geometry()
if input_geometry:
output_geometry = QgsGeometry.fromRect(input_geometry.boundingBox())
if not output_geometry:
raise QgsProcessingException(
self.tr('Error calculating bounding box'))
feature.setGeometry(output_geometry)
output_feature.setGeometry(output_geometry)
sink.addFeature(output_feature, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
return {self.OUTPUT_LAYER: dest_id}
return feature

View File

@ -0,0 +1,94 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
DropMZValues.py
--------------
Date : July 2017
Copyright : (C) 2017 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Nyall Dawson'
__date__ = 'July 2017'
__copyright__ = '(C) 2017, Nyall Dawson'
# This will get replaced with a git SHA1 when you do a git archive323
__revision__ = '$Format:%H$'
import os
from qgis.core import (QgsGeometry,
QgsWkbTypes,
QgsProcessingParameterBoolean)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class DropMZValues(QgisFeatureBasedAlgorithm):
DROP_M_VALUES = 'DROP_M_VALUES'
DROP_Z_VALUES = 'DROP_Z_VALUES'
def group(self):
return self.tr('Vector geometry tools')
def __init__(self):
super().__init__()
self.drop_m = False
self.drop_z = False
def name(self):
return 'dropmzvalues'
def displayName(self):
return self.tr('Drop M/Z Values')
def outputName(self):
return self.tr('Z/M Dropped')
def tags(self):
return self.tr('drop,set,convert,m,measure,z,25d,3d,values').split(',')
def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterBoolean(self.DROP_M_VALUES,
self.tr('Drop M Values'), defaultValue=False))
self.addParameter(QgsProcessingParameterBoolean(self.DROP_Z_VALUES,
self.tr('Drop Z Values'), defaultValue=False))
def outputWkbType(self, inputWkb):
wkb = inputWkb
if self.drop_m:
wkb = QgsWkbTypes.dropM(wkb)
if self.drop_z:
wkb = QgsWkbTypes.dropZ(wkb)
return wkb
def prepareAlgorithm(self, parameters, context, feedback):
self.drop_m = self.parameterAsBool(parameters, self.DROP_M_VALUES, context)
self.drop_z = self.parameterAsBool(parameters, self.DROP_Z_VALUES, context)
return True
def processFeature(self, feature, feedback):
input_geometry = feature.geometry()
if input_geometry:
new_geom = input_geometry.geometry().clone()
if self.drop_m:
new_geom.dropMValue()
if self.drop_z:
new_geom.dropZValue()
feature.setGeometry(QgsGeometry(new_geom))
return feature

View File

@ -56,6 +56,7 @@ from .DensifyGeometries import DensifyGeometries
from .DensifyGeometriesInterval import DensifyGeometriesInterval
from .Difference import Difference
from .DropGeometry import DropGeometry
from .DropMZValues import DropMZValues
from .ExtentFromLayer import ExtentFromLayer
from .ExtractNodes import ExtractNodes
from .FixGeometry import FixGeometry
@ -89,6 +90,8 @@ from .SelectByAttribute import SelectByAttribute
from .SelectByExpression import SelectByExpression
from .ServiceAreaFromLayer import ServiceAreaFromLayer
from .ServiceAreaFromPoint import ServiceAreaFromPoint
from .SetMValue import SetMValue
from .SetZValue import SetZValue
from .ShortestPathLayerToPoint import ShortestPathLayerToPoint
from .ShortestPathPointToLayer import ShortestPathPointToLayer
from .ShortestPathPointToPoint import ShortestPathPointToPoint
@ -243,6 +246,7 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
DensifyGeometriesInterval(),
Difference(),
DropGeometry(),
DropMZValues(),
ExtentFromLayer(),
ExtractNodes(),
FixGeometry(),
@ -276,6 +280,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider):
SelectByExpression(),
ServiceAreaFromLayer(),
ServiceAreaFromPoint(),
SetMValue(),
SetZValue(),
ShortestPathLayerToPoint(),
ShortestPathPointToLayer(),
ShortestPathPointToPoint(),

View File

@ -25,7 +25,7 @@ __copyright__ = '(C) 2017, Nyall Dawson'
__revision__ = '$Format:%H$'
from qgis.core import QgsProcessingAlgorithm
from qgis.core import QgsProcessingAlgorithm, QgsProcessingFeatureBasedAlgorithm
from qgis.PyQt.QtCore import QCoreApplication
from processing.algs.help import shortHelp
@ -50,3 +50,25 @@ class QgisAlgorithm(QgsProcessingAlgorithm):
def createInstance(self):
return type(self)()
class QgisFeatureBasedAlgorithm(QgsProcessingFeatureBasedAlgorithm):
def __init__(self):
super().__init__()
def shortHelpString(self):
return shortHelp.get(self.id(), None)
def tr(self, string, context=''):
if context == '':
context = self.__class__.__name__
return QCoreApplication.translate(context, string)
def trAlgorithm(self, string, context=''):
if context == '':
context = self.__class__.__name__
return string, QCoreApplication.translate(context, string)
def createInstance(self):
return type(self)()

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
SetMValue.py
--------------
Date : July 2017
Copyright : (C) 2017 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Nyall Dawson'
__date__ = 'July 2017'
__copyright__ = '(C) 2017, Nyall Dawson'
# This will get replaced with a git SHA1 when you do a git archive323
__revision__ = '$Format:%H$'
import os
from qgis.core import (QgsGeometry,
QgsWkbTypes,
QgsProcessingParameterNumber)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class SetMValue(QgisFeatureBasedAlgorithm):
M_VALUE = 'M_VALUE'
def group(self):
return self.tr('Vector geometry tools')
def __init__(self):
super().__init__()
self.m_value = 0
def name(self):
return 'setmvalue'
def displayName(self):
return self.tr('Set M Value')
def outputName(self):
return self.tr('M Added')
def tags(self):
return self.tr('set,add,m,measure,values').split(',')
def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterNumber(self.M_VALUE,
self.tr('M Value'), QgsProcessingParameterNumber.Double, defaultValue=0.0))
def outputWkbType(self, inputWkb):
return QgsWkbTypes.addM(inputWkb)
def prepareAlgorithm(self, parameters, context, feedback):
self.m_value = self.parameterAsDouble(parameters, self.M_VALUE, context)
return True
def processFeature(self, feature, feedback):
input_geometry = feature.geometry()
if input_geometry:
new_geom = input_geometry.geometry().clone()
if QgsWkbTypes.hasM(new_geom.wkbType()):
# addMValue won't alter existing M values, so drop them first
new_geom.dropMValue()
new_geom.addMValue(self.m_value)
feature.setGeometry(QgsGeometry(new_geom))
return feature

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""
***************************************************************************
SetZValue.py
--------------
Date : July 2017
Copyright : (C) 2017 by Nyall Dawson
Email : nyall dot dawson at gmail dot com
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************
"""
__author__ = 'Nyall Dawson'
__date__ = 'July 2017'
__copyright__ = '(C) 2017, Nyall Dawson'
# This will get replaced with a git SHA1 when you do a git archive323
__revision__ = '$Format:%H$'
import os
from qgis.core import (QgsGeometry,
QgsWkbTypes,
QgsProcessingParameterNumber)
from processing.algs.qgis.QgisAlgorithm import QgisFeatureBasedAlgorithm
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
class SetZValue(QgisFeatureBasedAlgorithm):
Z_VALUE = 'Z_VALUE'
def group(self):
return self.tr('Vector geometry tools')
def __init__(self):
super().__init__()
self.z_value = 0
def name(self):
return 'setzvalue'
def displayName(self):
return self.tr('Set Z Value')
def outputName(self):
return self.tr('Z Added')
def tags(self):
return self.tr('set,add,z,25d,3d,values').split(',')
def initParameters(self, config=None):
self.addParameter(QgsProcessingParameterNumber(self.Z_VALUE,
self.tr('Z Value'), QgsProcessingParameterNumber.Double, defaultValue=0.0))
def outputWkbType(self, inputWkb):
return QgsWkbTypes.addZ(inputWkb)
def prepareAlgorithm(self, parameters, context, feedback):
self.z_value = self.parameterAsDouble(parameters, self.Z_VALUE, context)
return True
def processFeature(self, feature, feedback):
input_geometry = feature.geometry()
if input_geometry:
new_geom = input_geometry.geometry().clone()
if QgsWkbTypes.hasZ(new_geom.wkbType()):
# addZValue won't alter existing Z values, so drop them first
new_geom.dropZValue()
new_geom.addZValue(self.z_value)
feature.setGeometry(QgsGeometry(new_geom))
return feature

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation=""
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy>
<gml:Box>
<gml:coord><gml:X>0</gml:X><gml:Y>-5</gml:Y></gml:coord>
<gml:coord><gml:X>8</gml:X><gml:Y>3</gml:Y></gml:coord>
</gml:Box>
</gml:boundedBy>
<gml:featureMember>
<ogr:set_m_value fid="points.0">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>1,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>1</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.1">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>3,3</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>2</ogr:id>
<ogr:id2>1</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.2">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>2,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>3</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.3">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>5,2</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>4</ogr:id>
<ogr:id2>2</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.4">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>4,1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>5</ogr:id>
<ogr:id2>1</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.5">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-5</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>6</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.6">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>8,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>7</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.7">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>7,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>8</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
<gml:featureMember>
<ogr:set_m_value fid="points.8">
<ogr:geometryProperty><gml:Point srsName="EPSG:4326"><gml:coordinates>0,-1</gml:coordinates></gml:Point></ogr:geometryProperty>
<ogr:id>9</ogr:id>
<ogr:id2>0</ogr:id2>
</ogr:set_m_value>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]]

View File

@ -0,0 +1 @@
GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]

Binary file not shown.

Binary file not shown.

View File

@ -415,66 +415,66 @@ tests:
- algorithm: qgis:boundingboxes
name: Bounding boxes for lines
params:
INPUT_LAYER:
INPUT:
name: lines.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/lines_bounds.gml
type: vector
- algorithm: qgis:boundingboxes
name: Bounding boxes for multilines
params:
INPUT_LAYER:
INPUT:
name: multilines.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/multiline_bounds.gml
type: vector
- algorithm: qgis:boundingboxes
name: Bounding boxes for multipolygons
params:
INPUT_LAYER:
INPUT:
name: multipolys.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/multipoly_bounds.gml
type: vector
- algorithm: qgis:boundingboxes
name: Bounding boxes for points
params:
INPUT_LAYER:
INPUT:
name: points.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/point_bounds.gml
type: vector
- algorithm: qgis:boundingboxes
name: Bounding boxes for polygons
params:
INPUT_LAYER:
INPUT:
name: polys.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/poly_bounds.gml
type: vector
- algorithm: qgis:boundingboxes
name: Bounding boxes for multipoints
params:
INPUT_LAYER:
INPUT:
name: multipoints.gml
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/multipoint_bounds.gml
type: vector
@ -522,6 +522,69 @@ tests:
name: expected/multiline_boundary.gml
type: vector
- algorithm: qgis:setmvalue
name: Set M Value
params:
INPUT:
name: points.gml
type: vector
M_VALUE: 7
results:
OUTPUT:
name: expected/set_m_value.shp
type: vector
- algorithm: qgis:setzvalue
name: Set Z Value
params:
INPUT:
name: points.gml
type: vector
Z_VALUE: 6
results:
OUTPUT:
name: expected/set_z_value.shp
type: vector
- algorithm: qgis:dropmzvalues
name: Drop M Value
params:
INPUT:
name: custom/pointszm.shp
type: vector
DROP_Z_VALUES: False
DROP_M_VALUES: True
results:
OUTPUT:
name: expected/m_dropped.shp
type: vector
- algorithm: qgis:dropmzvalues
name: Drop Z Value
params:
INPUT:
name: custom/pointszm.shp
type: vector
DROP_Z_VALUES: True
DROP_M_VALUES: False
results:
OUTPUT:
name: expected/z_dropped.shp
type: vector
- algorithm: qgis:dropmzvalues
name: Drop ZM Value
params:
INPUT:
name: custom/pointszm.shp
type: vector
DROP_Z_VALUES: True
DROP_M_VALUES: True
results:
OUTPUT:
name: expected/zm_dropped.shp
type: vector
- algorithm: qgis:pointonsurface
name: Point on polygon surface
params:
@ -2556,11 +2619,11 @@ tests:
FIELD_NAME: field
FIELD_PRECISION: 2
FIELD_TYPE: '1'
INPUT_LAYER:
INPUT:
name: custom/points.shp
type: vector
results:
OUTPUT_LAYER:
OUTPUT:
name: expected/add_field.gml
type: vector

View File

@ -87,53 +87,19 @@ QgsCentroidAlgorithm *QgsCentroidAlgorithm::createInstance() const
return new QgsCentroidAlgorithm();
}
QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
QgsFeature QgsCentroidAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback )
{
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
return QVariantMap();
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::Point, source->sourceCrs() ) );
if ( !sink )
return QVariantMap();
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureIterator it = source->getFeatures();
double step = 100.0 / count;
int current = 0;
while ( it.nextFeature( f ) )
QgsFeature feature = f;
if ( feature.hasGeometry() )
{
if ( feedback->isCanceled() )
feature.setGeometry( feature.geometry().centroid() );
if ( !feature.geometry() )
{
break;
feedback->pushInfo( QObject::tr( "Error calculating centroid for feature %1" ).arg( feature.id() ) );
}
QgsFeature out = f;
if ( out.hasGeometry() )
{
out.setGeometry( f.geometry().centroid() );
if ( !out.geometry() )
{
QgsMessageLog::logMessage( QObject::tr( "Error calculating centroid for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
}
}
sink->addFeature( out, QgsFeatureSink::FastInsert );
feedback->setProgress( current * step );
current++;
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
return feature;
}
//
// QgsBufferAlgorithm
//
@ -188,13 +154,11 @@ QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap &parameters,
const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) );
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureIterator it = source->getFeatures();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
QList< QgsGeometry > bufferedGeometriesForDissolve;
@ -289,13 +253,11 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap &parameter
QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context );
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureIterator it = source->getFeatures();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
if ( fields.isEmpty() )
@ -456,8 +418,11 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap &parameters, Q
clipGeoms << f.geometry();
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
if ( clipGeoms.isEmpty() )
return QVariantMap();
return outputs;
// are we clipping against a single feature? if so, we can show finer progress reports
bool singleClipFeature = false;
@ -557,17 +522,13 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap &parameters, Q
}
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}
void QgsTransformAlgorithm::initAlgorithm( const QVariantMap & )
void QgsTransformAlgorithm::initParameters( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
addParameter( new QgsProcessingParameterCrs( QStringLiteral( "TARGET_CRS" ), QObject::tr( "Target CRS" ), QStringLiteral( "EPSG:4326" ) ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Reprojected" ) ) );
}
QString QgsTransformAlgorithm::shortHelpString() const
@ -582,57 +543,41 @@ QgsTransformAlgorithm *QgsTransformAlgorithm::createInstance() const
return new QgsTransformAlgorithm();
}
QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
return QVariantMap();
mDestCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context );
return true;
}
QgsCoordinateReferenceSystem targetCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context );
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), source->wkbType(), targetCrs ) );
if ( !sink )
return QVariantMap();
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureRequest req;
// perform reprojection in the iterators...
req.setDestinationCrs( targetCrs );
QgsFeatureIterator it = source->getFeatures( req );
double step = 100.0 / count;
int current = 0;
while ( it.nextFeature( f ) )
QgsFeature QgsTransformAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback * )
{
QgsFeature feature = f;
if ( !mCreatedTransform )
{
if ( feedback->isCanceled() )
{
break;
}
sink->addFeature( f, QgsFeatureSink::FastInsert );
feedback->setProgress( current * step );
current++;
mCreatedTransform = true;
mTransform = QgsCoordinateTransform( sourceCrs(), mDestCrs );
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
if ( feature.hasGeometry() )
{
QgsGeometry g = feature.geometry();
if ( g.transform( mTransform ) == 0 )
{
feature.setGeometry( g );
}
else
{
feature.clearGeometry();
}
}
return feature;
}
void QgsSubdivideAlgorithm::initAlgorithm( const QVariantMap & )
void QgsSubdivideAlgorithm::initParameters( const QVariantMap & )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MAX_NODES" ), QObject::tr( "Maximum nodes in parts" ), QgsProcessingParameterNumber::Integer,
256, false, 8, 100000 ) );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Subdivided" ) ) );
}
QString QgsSubdivideAlgorithm::shortHelpString() const
@ -650,53 +595,29 @@ QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::createInstance() const
return new QgsSubdivideAlgorithm();
}
QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
QgsWkbTypes::Type QgsSubdivideAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
{
std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
return QVariantMap();
return QgsWkbTypes::multiType( inputWkbType );
}
int maxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context );
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(),
QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) );
if ( !sink )
return QVariantMap();
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureIterator it = source->getFeatures();
double step = 100.0 / count;
int current = 0;
while ( it.nextFeature( f ) )
QgsFeature QgsSubdivideAlgorithm::processFeature( const QgsFeature &f, QgsProcessingFeedback *feedback )
{
QgsFeature feature = f;
if ( feature.hasGeometry() )
{
if ( feedback->isCanceled() )
feature.setGeometry( feature.geometry().subdivide( mMaxNodes ) );
if ( !feature.geometry() )
{
break;
feedback->reportError( QObject::tr( "Error calculating subdivision for feature %1" ).arg( feature.id() ) );
}
QgsFeature out = f;
if ( out.hasGeometry() )
{
out.setGeometry( f.geometry().subdivide( maxNodes ) );
if ( !out.geometry() )
{
QgsMessageLog::logMessage( QObject::tr( "Error calculating subdivision for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
}
}
sink->addFeature( out, QgsFeatureSink::FastInsert );
feedback->setProgress( current * step );
current++;
}
return feature;
}
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
mMaxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context );
return true;
}
@ -734,13 +655,11 @@ QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantM
return QVariantMap();
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
QgsFeature f;
QgsFeatureIterator it = source->getFeatures();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
while ( it.nextFeature( f ) )
{
@ -833,10 +752,8 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap
QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
if ( !nonMatchingSink )
@ -1032,10 +949,8 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap
QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
if ( !nonMatchingSink )
@ -1137,10 +1052,8 @@ QVariantMap QgsRemoveNullGeometryAlgorithm::processAlgorithm( const QVariantMap
std::unique_ptr< QgsFeatureSink > nullSink( parameterAsSink( parameters, QStringLiteral( "NULL_OUTPUT" ), context, nullSinkId, source->fields() ) );
long count = source->featureCount();
if ( count <= 0 )
return QVariantMap();
double step = 100.0 / count;
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
QgsFeature f;

View File

@ -48,7 +48,7 @@ class QgsNativeAlgorithms: public QgsProcessingProvider
/**
* Native centroid algorithm.
*/
class QgsCentroidAlgorithm : public QgsProcessingAlgorithm
class QgsCentroidAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
@ -57,28 +57,29 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "centroids" ); }
QString displayName() const override { return QObject::tr( "Centroids" ); }
virtual QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); }
QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector geometry tools" ); }
QString shortHelpString() const override;
QgsCentroidAlgorithm *createInstance() const override SIP_FACTORY;
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QString outputName() const override { return QObject::tr( "Centroids" ); }
QgsProcessing::LayerType outputLayerType() const override { return QgsProcessing::TypeVectorPoint; }
QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override { Q_UNUSED( inputWkbType ); return QgsWkbTypes::Point; }
QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override;
};
/**
* Native transform algorithm.
*/
class QgsTransformAlgorithm : public QgsProcessingAlgorithm
class QgsTransformAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsTransformAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "reprojectlayer" ); }
QString displayName() const override { return QObject::tr( "Reproject layer" ); }
virtual QStringList tags() const override { return QObject::tr( "transform,reproject,crs,srs,warp" ).split( ',' ); }
@ -88,8 +89,18 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm
protected:
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem & ) const override { return mDestCrs; }
QString outputName() const override { return QObject::tr( "Reprojected" ); }
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override;
private:
bool mCreatedTransform = false;
QgsCoordinateReferenceSystem mDestCrs;
QgsCoordinateTransform mTransform;
};
@ -233,13 +244,13 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm
/**
* Native subdivide algorithm.
*/
class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm
class QgsSubdivideAlgorithm : public QgsProcessingFeatureBasedAlgorithm
{
public:
QgsSubdivideAlgorithm() = default;
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
QString name() const override { return QStringLiteral( "subdivide" ); }
QString displayName() const override { return QObject::tr( "Subdivide" ); }
virtual QStringList tags() const override { return QObject::tr( "subdivide,segmentize,split,tesselate" ).split( ',' ); }
@ -248,9 +259,16 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm
QgsSubdivideAlgorithm *createInstance() const override SIP_FACTORY;
protected:
QString outputName() const override { return QObject::tr( "Subdivided" ); }
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const override;
QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) override;
bool prepareAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
private:
int mMaxNodes = -1;
};

View File

@ -608,3 +608,64 @@ bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParamete
}
//
// QgsProcessingFeatureBasedAlgorithm
//
void QgsProcessingFeatureBasedAlgorithm::initAlgorithm( const QVariantMap &config )
{
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
initParameters( config );
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), outputName(), outputLayerType() ) );
}
QgsCoordinateReferenceSystem QgsProcessingFeatureBasedAlgorithm::sourceCrs() const
{
if ( mSource )
return mSource->sourceCrs();
else
return QgsCoordinateReferenceSystem();
}
QVariantMap QgsProcessingFeatureBasedAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
{
mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !mSource )
return QVariantMap();
QString dest;
std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest,
outputFields( mSource->fields() ),
outputWkbType( mSource->wkbType() ),
outputCrs( mSource->sourceCrs() ) ) );
if ( !sink )
return QVariantMap();
long count = mSource->featureCount();
QgsFeature f;
QgsFeatureIterator it = mSource->getFeatures();
double step = count > 0 ? 100.0 / count : 1;
int current = 0;
while ( it.nextFeature( f ) )
{
if ( feedback->isCanceled() )
{
break;
}
QgsFeature transformed = processFeature( f, feedback );
if ( transformed.isValid() )
sink->addFeature( transformed, QgsFeatureSink::FastInsert );
feedback->setProgress( current * step );
current++;
}
mSource.reset();
QVariantMap outputs;
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
return outputs;
}

View File

@ -53,6 +53,8 @@ class CORE_EXPORT QgsProcessingAlgorithm
SIP_CONVERT_TO_SUBCLASS_CODE
if ( dynamic_cast< QgsProcessingModelAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingModelAlgorithm;
else if ( dynamic_cast< QgsProcessingFeatureBasedAlgorithm * >( sipCpp ) != NULL )
sipType = sipType_QgsProcessingFeatureBasedAlgorithm;
else
sipType = sipType_QgsProcessingAlgorithm;
SIP_END
@ -697,6 +699,130 @@ Q_DECLARE_OPERATORS_FOR_FLAGS( QgsProcessingAlgorithm::Flags )
/**
* \class QgsProcessingFeatureBasedAlgorithm
* \ingroup core
* 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).
*
* \since QGIS 3.0
*/
class CORE_EXPORT QgsProcessingFeatureBasedAlgorithm : public QgsProcessingAlgorithm
{
public:
/**
* Constructor for QgsProcessingFeatureBasedAlgorithm.
*/
QgsProcessingFeatureBasedAlgorithm() = default;
protected:
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
/**
* 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.
*/
virtual QString outputName() const = 0;
/**
* Returns the layer type for layers generated by this algorithm, if
* this is possible to determine in advance.
*/
virtual QgsProcessing::LayerType outputLayerType() const { return QgsProcessing::TypeVectorAny; }
/**
* Maps the input WKB geometry type (\a 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).
*/
virtual QgsWkbTypes::Type outputWkbType( QgsWkbTypes::Type inputWkbType ) const { return inputWkbType; }
/**
* Maps the input source fields (\a 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).
*/
virtual QgsFields outputFields( const QgsFields &inputFields ) const { return inputFields; }
/**
* Maps the input source coordinate reference system (\a 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).
*/
virtual QgsCoordinateReferenceSystem outputCrs( const QgsCoordinateReferenceSystem &inputCrs ) const { return inputCrs; }
/**
* 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.
*/
virtual void initParameters( const QVariantMap &configuration = QVariantMap() ) { Q_UNUSED( configuration ); }
/**
* Returns the source's coordinate reference system. This will only return a valid CRS when
* called from a subclasses' processFeature() implementation.
*/
QgsCoordinateReferenceSystem sourceCrs() const;
/**
* Processes an individual input \a 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 \a 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
* \a feedback to instead report non-fatal processing failures for features instead.
*/
virtual QgsFeature processFeature( const QgsFeature &feature, QgsProcessingFeedback *feedback ) = 0;
virtual QVariantMap processAlgorithm( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
private:
std::unique_ptr< QgsFeatureSource > mSource;
};
#endif // QGSPROCESSINGALGORITHM_H