Skip to content
Permalink
Browse files

Resurrect Field Calculator algorithm, add test

  • Loading branch information
nyalldawson committed Aug 20, 2017
1 parent d2a90f4 commit 6144b1c5d96088d7b98a54baca9c5ca4285c0776
@@ -29,35 +29,31 @@
from qgis.core import (QgsExpression,
QgsExpressionContext,
QgsExpressionContextUtils,
QgsFeature,
QgsFeatureSink,
QgsField,
QgsDistanceArea,
QgsProject,
QgsApplication,
QgsProcessingUtils)
QgsProcessingParameterFeatureSource,
QgsProcessingParameterEnum,
QgsProcessingParameterNumber,
QgsProcessingParameterBoolean,
QgsProcessingParameterExpression,
QgsProcessingParameterString,
QgsProcessingParameterFeatureSink,
QgsProcessingException)
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException
from processing.core.parameters import ParameterVector
from processing.core.parameters import ParameterString
from processing.core.parameters import ParameterNumber
from processing.core.parameters import ParameterBoolean
from processing.core.parameters import ParameterSelection
from processing.core.outputs import OutputVector

from .ui.FieldsCalculatorDialog import FieldsCalculatorDialog


class FieldsCalculator(QgisAlgorithm):

INPUT_LAYER = 'INPUT_LAYER'
INPUT = 'INPUT'
NEW_FIELD = 'NEW_FIELD'
FIELD_NAME = 'FIELD_NAME'
FIELD_TYPE = 'FIELD_TYPE'
FIELD_LENGTH = 'FIELD_LENGTH'
FIELD_PRECISION = 'FIELD_PRECISION'
FORMULA = 'FORMULA'
OUTPUT_LAYER = 'OUTPUT_LAYER'
OUTPUT = 'OUTPUT'

TYPES = [QVariant.Double, QVariant.Int, QVariant.String, QVariant.Date]

@@ -66,27 +62,26 @@ def group(self):

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

def initAlgorithm(self, config=None):
self.type_names = [self.tr('Float'),
self.tr('Integer'),
self.tr('String'),
self.tr('Date')]

self.addParameter(ParameterVector(self.INPUT_LAYER,
self.tr('Input layer')))
self.addParameter(ParameterString(self.FIELD_NAME,
self.tr('Result field name')))
self.addParameter(ParameterSelection(self.FIELD_TYPE,
self.tr('Field type'), self.type_names))
self.addParameter(ParameterNumber(self.FIELD_LENGTH,
self.tr('Field length'), 1, 255, 10))
self.addParameter(ParameterNumber(self.FIELD_PRECISION,
self.tr('Field precision'), 0, 15, 3))
self.addParameter(ParameterBoolean(self.NEW_FIELD,
self.tr('Create new field'), True))
self.addParameter(ParameterString(self.FORMULA, self.tr('Formula')))
self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Calculated')))
def initAlgorithm(self, config=None):
self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer')))
self.addParameter(QgsProcessingParameterString(self.FIELD_NAME,
self.tr('Result field name')))
self.addParameter(QgsProcessingParameterEnum(self.FIELD_TYPE,
self.tr('Field type'), options=self.type_names))
self.addParameter(QgsProcessingParameterNumber(self.FIELD_LENGTH,
self.tr('Field length'), minValue=1, maxValue=255, defaultValue=10))
self.addParameter(QgsProcessingParameterNumber(self.FIELD_PRECISION,
self.tr('Field precision'), minValue=0, maxValue=15, defaultValue=3))
self.addParameter(QgsProcessingParameterBoolean(self.NEW_FIELD,
self.tr('Create new field'), defaultValue=True))
self.addParameter(QgsProcessingParameterExpression(self.FORMULA, self.tr('Formula')))
self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT,
self.tr('Calculated')))

def name(self):
return 'fieldcalculator'
@@ -95,77 +90,70 @@ def displayName(self):
return self.tr('Field calculator')

def processAlgorithm(self, parameters, context, feedback):
layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context)
fieldName = self.getParameterValue(self.FIELD_NAME)
fieldType = self.TYPES[self.getParameterValue(self.FIELD_TYPE)]
width = self.getParameterValue(self.FIELD_LENGTH)
precision = self.getParameterValue(self.FIELD_PRECISION)
newField = self.getParameterValue(self.NEW_FIELD)
formula = self.getParameterValue(self.FORMULA)

output = self.getOutputFromName(self.OUTPUT_LAYER)

fields = layer.fields()
if newField:
fields.append(QgsField(fieldName, fieldType, '', width, precision))

writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context)

exp = QgsExpression(formula)

source = self.parameterAsSource(parameters, self.INPUT, context)
layer = self.parameterAsVectorLayer(parameters, self.INPUT, context)
field_name = self.parameterAsString(parameters, self.FIELD_NAME, context)
field_type = self.TYPES[self.parameterAsEnum(parameters, self.FIELD_TYPE, context)]
width = self.parameterAsInt(parameters, self.FIELD_LENGTH, context)
precision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context)
new_field = self.parameterAsBool(parameters, self.NEW_FIELD, context)
formula = self.parameterAsString(parameters, self.FORMULA, context)

expression = QgsExpression(formula)
da = QgsDistanceArea()
da.setSourceCrs(layer.crs())
da.setSourceCrs(source.sourceCrs())
da.setEllipsoid(context.project().ellipsoid())
exp.setGeomCalculator(da)
exp.setDistanceUnits(context.project().distanceUnits())
exp.setAreaUnits(context.project().areaUnits())
expression.setGeomCalculator(da)

exp_context = QgsExpressionContext(QgsExpressionContextUtils.globalProjectLayerScopes(layer))
expression.setDistanceUnits(context.project().distanceUnits())
expression.setAreaUnits(context.project().areaUnits())

if not exp.prepare(exp_context):
raise GeoAlgorithmExecutionException(
self.tr('Evaluation error: {0}').format(exp.evalErrorString()))
fields = source.fields()
field_index = fields.lookupField(field_name)
if new_field or field_index < 0:
fields.append(QgsField(field_name, field_type, '', width, precision))

outFeature = QgsFeature()
outFeature.initAttributes(len(fields))
outFeature.setFields(fields)
(sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
fields, source.wkbType(), source.sourceCrs())

error = ''
calculationSuccess = True
exp_context = self.createExpressionContext(parameters, context)
if layer is not None:
exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))

features = QgsProcessingUtils.getFeatures(layer, context)
total = 100.0 / layer.featureCount() if layer.featureCount() else 0
if not expression.prepare(exp_context):
raise QgsProcessingException(
self.tr('Evaluation error: {0}').format(expression.parserErrorString()))

features = source.getFeatures()
total = 100.0 / source.featureCount() if source.featureCount() else 0

rownum = 1
for current, f in enumerate(features):
if feedback.isCanceled():
break

rownum = current + 1
exp_context.setFeature(f)
exp_context.lastScope().setVariable("row_number", rownum)
value = exp.evaluate(exp_context)
if exp.hasEvalError():
calculationSuccess = False
error = exp.evalErrorString()
break
value = expression.evaluate(exp_context)
if expression.hasEvalError():
feedback.reportError(expression.evalErrorString())
else:
outFeature.setGeometry(f.geometry())
for fld in f.fields():
outFeature[fld.name()] = f[fld.name()]
outFeature[fieldName] = value
writer.addFeature(outFeature, QgsFeatureSink.FastInsert)

attrs = f.attributes()
if new_field or field_index < 0:
attrs.append(value)
else:
attrs[field_index] = value
f.setAttributes(attrs)
sink.addFeature(f, QgsFeatureSink.FastInsert)
feedback.setProgress(int(current * total))
del writer

if not calculationSuccess:
raise GeoAlgorithmExecutionException(
self.tr('An error occurred while evaluating the calculation '
'string:\n{0}').format(error))
return {self.OUTPUT: dest_id}

def checkParameterValues(self, parameters, context):
newField = self.getParameterValue(self.NEW_FIELD)
fieldName = self.getParameterValue(self.FIELD_NAME).strip()
newField = self.parameterAsBool(parameters, self.NEW_FIELD, context)
fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context).strip()
if newField and len(fieldName) == 0:
return self.tr('Field name is not set. Please enter a field name')
return False, self.tr('Field name is not set. Please enter a field name')
return super(FieldsCalculator, self).checkParameterValues(parameters, context)

def createCustomParametersWidget(self, parent):
@@ -70,6 +70,7 @@
from .ExtentFromLayer import ExtentFromLayer
from .ExtractNodes import ExtractNodes
from .ExtractSpecificNodes import ExtractSpecificNodes
from .FieldsCalculator import FieldsCalculator
from .FieldsMapper import FieldsMapper
from .FixedDistanceBuffer import FixedDistanceBuffer
from .FixGeometry import FixGeometry
@@ -166,7 +167,6 @@
# from .SelectByLocation import SelectByLocation
# from .SpatialJoin import SpatialJoin
# from .GeometryConvert import GeometryConvert
# from .FieldsCalculator import FieldsCalculator
# from .FieldPyculator import FieldsPyculator
# from .SelectByAttributeSum import SelectByAttributeSum
# from .DefineProjection import DefineProjection
@@ -190,7 +190,7 @@ def getAlgs(self):
# SelectByLocation(),
# ExtractByLocation(),
# SpatialJoin(),
# GeometryConvert(), FieldsCalculator(),
# GeometryConvert(),
# FieldsPyculator(),
# FieldsMapper(), SelectByAttributeSum()
# DefineProjection(),
@@ -227,6 +227,7 @@ def getAlgs(self):
ExtentFromLayer(),
ExtractNodes(),
ExtractSpecificNodes(),
FieldsCalculator(),
FieldsMapper(),
FixedDistanceBuffer(),
FixGeometry(),
@@ -36,9 +36,11 @@
from qgis.core import (QgsExpressionContextUtils,
QgsProcessingFeedback,
QgsSettings,
QgsProcessingUtils,
QgsMapLayerProxyModel,
QgsMessageLog)
QgsProperty,
QgsProject,
QgsMessageLog,
QgsProcessingOutputLayerDefinition)
from qgis.gui import QgsEncodingFileDialog
from qgis.utils import OverrideCursor

@@ -47,6 +49,8 @@
from processing.gui.AlgorithmExecutor import execute
from processing.tools import dataobjects
from processing.gui.Postprocessing import handleAlgorithmResults
from processing.gui.PostgisTableSelector import PostgisTableSelector
from processing.gui.ParameterGuiUtils import getFileFilter

pluginPath = os.path.dirname(__file__)
WIDGET, BASE = uic.loadUiType(
@@ -73,9 +77,6 @@ def __init__(self, alg):
super(FieldsCalculatorDialog, self).__init__(None)
self.setupUi(self)

self.feedback = FieldCalculatorFeedback(self)
self.feedback.progressChanged.connect(self.setPercentage)

self.executed = False
self.alg = alg
self.layer = None
@@ -144,8 +145,8 @@ def setupSpinboxes(self, index):
self.mOutputFieldPrecisionSpinBox.setEnabled(False)

def selectFile(self):
output = self.alg.getOutputFromName('OUTPUT_LAYER')
fileFilter = output.getFileFilter(self.alg)
output = self.alg.parameterDefinition('OUTPUT')
fileFilter = getFileFilter(output)

settings = QgsSettings()
if settings.contains('/Processing/LastOutputPath'):
@@ -200,17 +201,23 @@ def getParamValues(self):

layer = self.cmbInputLayer.currentLayer()

context = dataobjects.createContext()

parameters = {}
parameters['INPUT_LAYER'] = layer
parameters['INPUT'] = layer
parameters['FIELD_NAME'] = fieldName
parameters['FIELD_TYPE'] = self.mOutputFieldTypeComboBox.currentIndex()
parameters['FIELD_LENGTH'] = self.mOutputFieldWidthSpinBox.value()
parameters['FIELD_PRECISION'] = self.mOutputFieldPrecisionSpinBox.value()
parameters['NEW_FIELD'] = self.mNewFieldGroupBox.isChecked()
parameters['FORMULA'] = self.builder.expressionText()
parameters['OUTPUT_LAYER'] = self.leOutputFile.text().strip() or None

context = dataobjects.createContext()
output = QgsProcessingOutputLayerDefinition()
if self.leOutputFile.text().strip():
output.sink = QgsProperty.fromValue(self.leOutputFile.text().strip())
else:
output.sink = QgsProperty.fromValue('memory:')
output.destinationProject = context.project()
parameters['OUTPUT'] = output

ok, msg = self.alg.checkParameterValues(parameters, context)
if not ok:
@@ -224,10 +231,15 @@ def accept(self):
parameters = self.getParamValues()
if parameters:
with OverrideCursor(Qt.WaitCursor):
self.feedback = FieldCalculatorFeedback(self)
self.feedback.progressChanged.connect(self.setPercentage)

context = dataobjects.createContext()
ProcessingLog.addToLog(self.alg.asPythonCommand(parameters, context))

self.executed, results = execute(self.alg, parameters, context, self.feedback)
self.setPercentage(0)

if self.executed:
handleAlgorithmResults(self.alg,
context,
@@ -51,7 +51,8 @@
QgsProcessingParameterRasterDestination,
QgsProcessingParameterFeatureSink,
QgsProcessingParameterVectorDestination,
QgsProcessingParameterFileDestination)
QgsProcessingParameterFileDestination,
QgsProcessingParameterEnum)
from qgis.PyQt.QtCore import QCoreApplication, QMetaObject
from qgis.PyQt.QtWidgets import QDialog, QVBoxLayout, QTextEdit, QMessageBox

@@ -223,6 +224,8 @@ def createTest(text):
params[param.name()] = int(token)
else:
params[param.name()] = float(token)
elif isinstance(param, QgsProcessingParameterEnum):
params[param.name()] = int(token)
else:
if token[0] == '"':
token = token[1:]
@@ -0,0 +1,31 @@
<GMLFeatureClassList>
<GMLFeatureClass>
<Name>field_calculator_points</Name>
<ElementPath>field_calculator_points</ElementPath>
<!--POINT-->
<GeometryType>1</GeometryType>
<SRSName>EPSG:4326</SRSName>
<DatasetSpecificInfo>
<FeatureCount>9</FeatureCount>
<ExtentXMin>0.00000</ExtentXMin>
<ExtentXMax>8.00000</ExtentXMax>
<ExtentYMin>-5.00000</ExtentYMin>
<ExtentYMax>3.00000</ExtentYMax>
</DatasetSpecificInfo>
<PropertyDefn>
<Name>id</Name>
<ElementPath>id</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>id2</Name>
<ElementPath>id2</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
<PropertyDefn>
<Name>calc</Name>
<ElementPath>calc</ElementPath>
<Type>Integer</Type>
</PropertyDefn>
</GMLFeatureClass>
</GMLFeatureClassList>

0 comments on commit 6144b1c

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