diff --git a/python/plugins/processing/algs/help/qgis.yaml b/python/plugins/processing/algs/help/qgis.yaml
index 71791026acc..e03acd2d54d 100644
--- a/python/plugins/processing/algs/help/qgis.yaml
+++ b/python/plugins/processing/algs/help/qgis.yaml
@@ -114,9 +114,6 @@ qgis:extractbyexpression: >
For more information about expressions see the user manual
-qgis:fieldcalculator: >
- This algorithm computes a new vector layer with the same features of the input layer, but with an additional attribute. The values of this new attribute are computed from each feature using a mathematical formula, based on the properties and attributes of the feature.
-
qgis:findprojection: >
This algorithm allows creation of a shortlist of possible candidate coordinate reference systems for a layer with an unknown projection.
diff --git a/python/plugins/processing/algs/qgis/FieldsCalculator.py b/python/plugins/processing/algs/qgis/FieldsCalculator.py
deleted file mode 100644
index 07ecc1c81d0..00000000000
--- a/python/plugins/processing/algs/qgis/FieldsCalculator.py
+++ /dev/null
@@ -1,164 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- FieldsCalculator.py
- ---------------------
- Date : August 2012
- Copyright : (C) 2012 by Victor Olaya
- Email : volayaf 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__ = 'Victor Olaya'
-__date__ = 'August 2012'
-__copyright__ = '(C) 2012, Victor Olaya'
-
-from qgis.PyQt.QtCore import QVariant
-from qgis.core import (QgsExpression,
- QgsExpressionContext,
- QgsExpressionContextUtils,
- QgsFeatureSink,
- QgsField,
- QgsDistanceArea,
- QgsProcessing,
- QgsProcessingParameterFeatureSource,
- QgsProcessingParameterEnum,
- QgsProcessingParameterNumber,
- QgsProcessingParameterBoolean,
- QgsProcessingParameterExpression,
- QgsProcessingParameterString,
- QgsProcessingParameterFeatureSink,
- QgsProcessingException)
-from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
-
-from .ui.FieldsCalculatorDialog import FieldsCalculatorDialog
-
-
-class FieldsCalculator(QgisAlgorithm):
- 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 = 'OUTPUT'
-
- TYPES = [QVariant.Double, QVariant.Int, QVariant.String, QVariant.Date]
-
- def group(self):
- return self.tr('Vector table')
-
- def groupId(self):
- return 'vectortable'
-
- def __init__(self):
- super().__init__()
- self.type_names = [self.tr('Float'),
- self.tr('Integer'),
- self.tr('String'),
- self.tr('Date')]
-
- def initAlgorithm(self, config=None):
- self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'),
- types=[QgsProcessing.TypeVector]))
- 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=0, 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'
-
- def displayName(self):
- return self.tr('Field calculator')
-
- def processAlgorithm(self, parameters, context, feedback):
- source = self.parameterAsSource(parameters, self.INPUT, context)
- if source is None:
- raise QgsProcessingException(self.invalidSourceError(parameters, self.INPUT))
-
- 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.parameterAsBoolean(parameters, self.NEW_FIELD, context)
- formula = self.parameterAsString(parameters, self.FORMULA, context)
-
- expression = QgsExpression(formula)
- da = QgsDistanceArea()
- da.setSourceCrs(source.sourceCrs(), context.transformContext())
- da.setEllipsoid(context.ellipsoid())
- expression.setGeomCalculator(da)
-
- expression.setDistanceUnits(context.distanceUnit())
- expression.setAreaUnits(context.areaUnit())
-
- 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))
-
- (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context,
- fields, source.wkbType(), source.sourceCrs())
- if sink is None:
- raise QgsProcessingException(self.invalidSinkError(parameters, self.OUTPUT))
-
- exp_context = self.createExpressionContext(parameters, context)
- if layer is not None:
- exp_context.appendScope(QgsExpressionContextUtils.layerScope(layer))
-
- expression.prepare(exp_context)
-
- features = source.getFeatures()
- total = 100.0 / source.featureCount() if source.featureCount() else 0
-
- 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 = expression.evaluate(exp_context)
- if expression.hasEvalError():
- feedback.reportError(expression.evalErrorString())
- else:
- 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))
-
- return {self.OUTPUT: dest_id}
-
- def checkParameterValues(self, parameters, context):
- newField = self.parameterAsBoolean(parameters, self.NEW_FIELD, context)
- fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context).strip()
- if newField and len(fieldName) == 0:
- 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):
- return FieldsCalculatorDialog(self)
diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py
index 1d201c9fc16..1022b624886 100644
--- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py
+++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py
@@ -42,7 +42,6 @@ from .EliminateSelection import EliminateSelection
from .ExecuteSQL import ExecuteSQL
from .ExportGeometryInfo import ExportGeometryInfo
from .FieldPyculator import FieldsPyculator
-from .FieldsCalculator import FieldsCalculator
from .FindProjection import FindProjection
from .GeometryConvert import GeometryConvert
from .Heatmap import Heatmap
@@ -112,7 +111,6 @@ class QgisAlgorithmProvider(QgsProcessingProvider):
EliminateSelection(),
ExecuteSQL(),
ExportGeometryInfo(),
- FieldsCalculator(),
FieldsPyculator(),
FindProjection(),
GeometryConvert(),
diff --git a/python/plugins/processing/algs/qgis/ui/DlgFieldsCalculator.ui b/python/plugins/processing/algs/qgis/ui/DlgFieldsCalculator.ui
deleted file mode 100644
index 2184d3038e3..00000000000
--- a/python/plugins/processing/algs/qgis/ui/DlgFieldsCalculator.ui
+++ /dev/null
@@ -1,276 +0,0 @@
-
-
- FieldsCalculator
-
-
-
- 0
- 0
- 681
- 681
-
-
-
- Field calculator
-
-
- -
-
-
-
- 0
- 0
-
-
-
- Create a new field
-
-
- true
-
-
- true
-
-
- true
-
-
-
- QLayout::SetMinimumSize
-
-
- 3
-
-
- 3
-
-
- 3
-
-
- 0
-
-
- 3
-
-
-
-
-
- Output field name
-
-
- mOutputFieldNameLineEdit
-
-
-
- -
-
-
- -
-
-
- Output field type
-
-
- mOutputFieldTypeComboBox
-
-
-
- -
-
-
- -
-
-
- Output field width
-
-
- mOutputFieldWidthSpinBox
-
-
-
- -
-
-
- Width of complete output. For example 123,456 means 6 as field width.
-
-
- 0
-
-
- 15
-
-
-
- -
-
-
- Precision
-
-
- mOutputFieldPrecisionSpinBox
-
-
-
- -
-
-
- 2
-
-
-
-
-
-
- -
-
-
-
- 3
- 0
-
-
-
- Qt::Horizontal
-
-
- QDialogButtonBox::Cancel|QDialogButtonBox::Ok
-
-
-
- -
-
-
-
-
-
- Input layer
-
-
-
- -
-
-
-
- 0
- 0
-
-
-
-
-
-
- -
-
-
- Update existing field
-
-
- true
-
-
- true
-
-
- false
-
-
-
-
-
-
-
-
-
- -
-
-
- false
-
-
-
- -
-
-
-
-
-
- Output file
-
-
-
- -
-
-
- -
-
-
- …
-
-
-
-
-
- -
-
-
- 0
-
-
-
-
-
-
-
- QgsExpressionBuilderWidget
- QWidget
-
- 1
-
-
- QgsMapLayerComboBox
- QComboBox
-
- 0
-
-
-
- mOutputFieldNameLineEdit
- mOutputFieldTypeComboBox
- mOutputFieldWidthSpinBox
- mOutputFieldPrecisionSpinBox
- mButtonBox
-
-
-
-
- mButtonBox
- accepted()
- FieldsCalculator
- accept()
-
-
- 679
- 559
-
-
- 157
- 274
-
-
-
-
- mButtonBox
- rejected()
- FieldsCalculator
- reject()
-
-
- 679
- 559
-
-
- 286
- 274
-
-
-
-
-
diff --git a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
deleted file mode 100644
index b14b53553e6..00000000000
--- a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py
+++ /dev/null
@@ -1,270 +0,0 @@
-# -*- coding: utf-8 -*-
-
-"""
-***************************************************************************
- FieldsCalculatorDialog.py
- ---------------------
- Date : October 2013
- Copyright : (C) 2013 by Alexander Bruy
- Email : alexander dot bruy 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__ = 'Alexander Bruy'
-__date__ = 'October 2013'
-__copyright__ = '(C) 2013, Alexander Bruy'
-
-import os
-import re
-import warnings
-
-from qgis.PyQt import uic
-from qgis.PyQt.QtCore import Qt
-from qgis.PyQt.QtWidgets import QDialog, QFileDialog, QApplication, QMessageBox
-from qgis.PyQt.QtGui import QCursor
-from qgis.core import (Qgis,
- QgsExpressionContextUtils,
- QgsProcessingFeedback,
- QgsSettings,
- QgsMapLayerProxyModel,
- QgsProperty,
- QgsProject,
- QgsMessageLog,
- QgsMapLayerType,
- QgsProcessingOutputLayerDefinition)
-from qgis.gui import QgsEncodingFileDialog, QgsGui
-from qgis.utils import OverrideCursor, iface
-
-from processing.core.ProcessingConfig import ProcessingConfig
-from processing.core.ProcessingLog import ProcessingLog
-from processing.gui.AlgorithmExecutor import execute
-from processing.tools import dataobjects
-from processing.gui.Postprocessing import handleAlgorithmResults
-
-pluginPath = os.path.dirname(__file__)
-with warnings.catch_warnings():
- warnings.filterwarnings("ignore", category=DeprecationWarning)
- WIDGET, BASE = uic.loadUiType(
- os.path.join(pluginPath, 'DlgFieldsCalculator.ui'))
-
-
-class FieldCalculatorFeedback(QgsProcessingFeedback):
- """
- Directs algorithm feedback to an algorithm dialog
- """
-
- def __init__(self, dialog):
- QgsProcessingFeedback.__init__(self)
- self.dialog = dialog
-
- def reportError(self, msg, fatalError=False):
- self.dialog.error(msg)
-
-
-class FieldsCalculatorDialog(BASE, WIDGET):
-
- def __init__(self, alg):
- super(FieldsCalculatorDialog, self).__init__(None)
- self.setupUi(self)
-
- self.executed = False
- self._wasExecuted = False
- self.alg = alg
- self.layer = None
-
- self.cmbInputLayer.setFilters(QgsMapLayerProxyModel.VectorLayer)
- try:
- if iface.activeLayer().type() == QgsMapLayerType.VectorLayer:
- self.cmbInputLayer.setLayer(iface.activeLayer())
- except:
- pass
-
- self.cmbInputLayer.layerChanged.connect(self.updateLayer)
- self.btnBrowse.clicked.connect(self.selectFile)
- self.mNewFieldGroupBox.toggled.connect(self.toggleExistingGroup)
- self.mUpdateExistingGroupBox.toggled.connect(self.toggleNewGroup)
- self.mOutputFieldTypeComboBox.currentIndexChanged.connect(self.setupSpinboxes)
-
- # Default values for field width and precision
- self.mOutputFieldWidthSpinBox.setValue(10)
- self.mOutputFieldPrecisionSpinBox.setValue(3)
-
- # Output is a shapefile, so limit maximum field name length
- self.mOutputFieldNameLineEdit.setMaxLength(10)
-
- self.manageGui()
-
- def manageGui(self):
- if hasattr(self.leOutputFile, 'setPlaceholderText'):
- self.leOutputFile.setPlaceholderText(
- self.tr('[Save to temporary file]'))
-
- self.mOutputFieldTypeComboBox.blockSignals(True)
- for t in self.alg.type_names:
- self.mOutputFieldTypeComboBox.addItem(t)
- self.mOutputFieldTypeComboBox.blockSignals(False)
- self.builder.loadRecent('fieldcalc')
-
- self.updateLayer(self.cmbInputLayer.currentLayer())
-
- def initContext(self):
- exp_context = self.builder.expressionContext()
- exp_context.appendScopes(QgsExpressionContextUtils.globalProjectLayerScopes(self.layer))
- exp_context.lastScope().setVariable("row_number", 1)
- exp_context.setHighlightedVariables(["row_number"])
- self.builder.setExpressionContext(exp_context)
-
- def updateLayer(self, layer):
- self.layer = layer
- self.builder.setLayer(self.layer)
- self.initContext()
- self.populateFields()
-
- def setupSpinboxes(self, index):
- if index != 0:
- self.mOutputFieldPrecisionSpinBox.setEnabled(False)
- else:
- self.mOutputFieldPrecisionSpinBox.setEnabled(True)
-
- if index == 0:
- self.mOutputFieldWidthSpinBox.setRange(1, 20)
- self.mOutputFieldWidthSpinBox.setValue(10)
- self.mOutputFieldPrecisionSpinBox.setRange(0, 15)
- self.mOutputFieldPrecisionSpinBox.setValue(3)
- elif index == 1:
- self.mOutputFieldWidthSpinBox.setRange(1, 10)
- self.mOutputFieldWidthSpinBox.setValue(10)
- elif index == 2:
- self.mOutputFieldWidthSpinBox.setRange(1, 255)
- self.mOutputFieldWidthSpinBox.setValue(80)
- else:
- self.mOutputFieldWidthSpinBox.setEnabled(False)
- self.mOutputFieldPrecisionSpinBox.setEnabled(False)
-
- def selectFile(self):
- output = self.alg.parameterDefinition('OUTPUT')
- fileFilter = output.createFileFilter()
-
- settings = QgsSettings()
- if settings.contains('/Processing/LastOutputPath'):
- path = settings.value('/Processing/LastOutputPath')
- else:
- path = ProcessingConfig.getSetting(ProcessingConfig.OUTPUT_FOLDER)
- lastEncoding = settings.value('/Processing/encoding', 'System')
- fileDialog = QgsEncodingFileDialog(self,
- self.tr('Save file'),
- path,
- fileFilter,
- lastEncoding)
- fileDialog.setFileMode(QFileDialog.AnyFile)
- fileDialog.setAcceptMode(QFileDialog.AcceptSave)
- fileDialog.setOption(QFileDialog.DontConfirmOverwrite, False)
- if fileDialog.exec_() == QDialog.Accepted:
- files = fileDialog.selectedFiles()
- encoding = str(fileDialog.encoding())
- output.encoding = encoding
- filename = str(files[0])
- selectedFileFilter = str(fileDialog.selectedNameFilter())
- if not filename.lower().endswith(
- tuple(re.findall("\\*(\\.[a-z]{1,10})", fileFilter))):
- ext = re.search("\\*(\\.[a-z]{1,10})", selectedFileFilter)
- if ext:
- filename = filename + ext.group(1)
- self.leOutputFile.setText(filename)
- settings.setValue('/Processing/LastOutputPath',
- os.path.dirname(filename))
- settings.setValue('/Processing/encoding', encoding)
-
- def toggleExistingGroup(self, toggled):
- self.mUpdateExistingGroupBox.setChecked(not toggled)
-
- def toggleNewGroup(self, toggled):
- self.mNewFieldGroupBox.setChecked(not toggled)
-
- def populateFields(self):
- if self.layer is None:
- return
-
- self.mExistingFieldComboBox.clear()
- fields = self.layer.fields()
- for f in fields:
- self.mExistingFieldComboBox.addItem(f.name())
-
- def getParamValues(self):
- if self.mUpdateExistingGroupBox.isChecked():
- fieldName = self.mExistingFieldComboBox.currentText()
- else:
- fieldName = self.mOutputFieldNameLineEdit.text()
-
- layer = self.cmbInputLayer.currentLayer()
-
- context = dataobjects.createContext()
-
- parameters = {}
- 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()
- 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:
- QMessageBox.warning(
- self, self.tr('Unable to execute algorithm'), msg)
- return {}
- return parameters
-
- def accept(self):
- keepOpen = ProcessingConfig.getSetting(ProcessingConfig.KEEP_DIALOG_OPEN)
- 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))
- QgsGui.instance().processingRecentAlgorithmLog().push(self.alg.id())
-
- self.executed, results = execute(self.alg, parameters, context, self.feedback)
- self.setPercentage(0)
-
- if self.executed:
- handleAlgorithmResults(self.alg,
- context,
- self.feedback,
- not keepOpen,
- parameters)
- self._wasExecuted = self.executed or self._wasExecuted
- if not keepOpen:
- QDialog.reject(self)
-
- def reject(self):
- self.executed = False
- QDialog.reject(self)
-
- def setPercentage(self, i):
- self.progressBar.setValue(i)
-
- def error(self, text):
- QMessageBox.critical(self, "Error", text)
- QgsMessageLog.logMessage(text, self.tr('Processing'), Qgis.Critical)
-
- def wasExecuted(self):
- return self._wasExecuted
diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
index 39dfb342cb3..34424de16a2 100755
--- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
+++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests3.yaml
@@ -915,6 +915,22 @@ tests:
name: expected/field_calculator_points.gml
type: vector
+ - algorithm: native:fieldcalculator
+ name: Test field calculator points (new API)
+ params:
+ FIELD_LENGTH: 10
+ FIELD_PRECISION: 3
+ FIELD_TYPE: 0
+ FORMULA: "\"id2\" *2"
+ INPUT:
+ name: points.gml
+ type: vector
+ NEW_FIELD_NAME: test
+ results:
+ OUTPUT:
+ name: expected/field_calculator_points.gml
+ type: vector
+
- algorithm: qgis:advancedpythonfieldcalculator
name: Test advanced python calculator
params:
diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt
index 52f78ce0641..e3f43007a9c 100644
--- a/src/analysis/CMakeLists.txt
+++ b/src/analysis/CMakeLists.txt
@@ -72,6 +72,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmextractzmvalues.cpp
processing/qgsalgorithmextractvertices.cpp
processing/qgsalgorithmextractspecificvertices.cpp
+ processing/qgsalgorithmfieldcalculator.cpp
processing/qgsalgorithmfiledownloader.cpp
processing/qgsalgorithmfillnodata.cpp
processing/qgsalgorithmfilter.cpp
diff --git a/src/analysis/processing/qgsalgorithmfieldcalculator.cpp b/src/analysis/processing/qgsalgorithmfieldcalculator.cpp
new file mode 100644
index 00000000000..2eba43fc58b
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmfieldcalculator.cpp
@@ -0,0 +1,225 @@
+/***************************************************************************
+ qgsalgorithmfieldcalculator.h
+ ----------------------
+ begin : September 2020
+ copyright : (C) 2020 by Ivan Ivanov
+ email : ivan@opengis.ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+
+#include "qgsalgorithmfieldcalculator.h"
+#include "qgsexpressioncontextutils.h"
+
+///@cond PRIVATE
+
+QString QgsFieldCalculatorAlgorithm::name() const
+{
+ return QStringLiteral( "fieldcalculator" );
+}
+
+QString QgsFieldCalculatorAlgorithm::displayName() const
+{
+ return QObject::tr( "Field calculator" );
+}
+
+QStringList QgsFieldCalculatorAlgorithm::tags() const
+{
+ return QObject::tr( "field,calculator,vector" ).split( ',' );
+}
+
+QString QgsFieldCalculatorAlgorithm::group() const
+{
+ return QObject::tr( "Vector table" );
+}
+
+QString QgsFieldCalculatorAlgorithm::groupId() const
+{
+ return QStringLiteral( "vectortable" );
+}
+
+QString QgsFieldCalculatorAlgorithm::outputName() const
+{
+ return QObject::tr( "Calculated" );
+}
+
+QList QgsFieldCalculatorAlgorithm::inputLayerTypes() const
+{
+ return QList() << QgsProcessing::TypeVector;
+}
+
+QgsProcessingFeatureSource::Flag QgsFieldCalculatorAlgorithm::sourceFlags() const
+{
+ return QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks;
+}
+
+void QgsFieldCalculatorAlgorithm::initParameters( const QVariantMap &configuration )
+{
+ Q_UNUSED( configuration );
+
+ QStringList fieldTypes = QStringList( {QObject::tr( "Float" ), QObject::tr( "Integer" ), QObject::tr( "String" ), QObject::tr( "Date" ) } );
+
+ std::unique_ptr< QgsProcessingParameterField > existingFieldName = qgis::make_unique< QgsProcessingParameterField > ( QStringLiteral( "EXISTING_FIELD_NAME" ), QObject::tr( "Result in existing field" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, false, true );
+ std::unique_ptr< QgsProcessingParameterString > newFieldName = qgis::make_unique< QgsProcessingParameterString > ( QStringLiteral( "NEW_FIELD_NAME" ), QObject::tr( "Result in new field" ), QVariant(), false, true );
+ std::unique_ptr< QgsProcessingParameterEnum > fieldType = qgis::make_unique< QgsProcessingParameterEnum > ( QStringLiteral( "FIELD_TYPE" ), QObject::tr( "Result field type" ), fieldTypes, false, 0 );
+ std::unique_ptr< QgsProcessingParameterNumber > fieldLength = qgis::make_unique< QgsProcessingParameterNumber > ( QStringLiteral( "FIELD_LENGTH" ), QObject::tr( "Result field length" ), QgsProcessingParameterNumber::Integer, QVariant( 0 ), false, 0 );
+ std::unique_ptr< QgsProcessingParameterNumber > fieldPrecision = qgis::make_unique< QgsProcessingParameterNumber > ( QStringLiteral( "FIELD_PRECISION" ), QObject::tr( "Result field precision" ), QgsProcessingParameterNumber::Integer, QVariant( 0 ), false, 0 );
+ std::unique_ptr< QgsProcessingParameterExpression > expression = qgis::make_unique< QgsProcessingParameterExpression> ( QStringLiteral( "FORMULA" ), QObject::tr( "Formula" ), QVariant(), QStringLiteral( "INPUT" ), false );
+
+ expression->setMetadata( QVariantMap( {{"inlineEditor", true}} ) );
+
+ addParameter( existingFieldName.release() );
+ addParameter( newFieldName.release() );
+ addParameter( fieldType.release() );
+ addParameter( fieldLength.release() );
+ addParameter( fieldPrecision.release() );
+ addParameter( expression.release() );
+}
+
+QgsFields QgsFieldCalculatorAlgorithm::outputFields( const QgsFields & ) const
+{
+ return mFields;
+}
+
+QString QgsFieldCalculatorAlgorithm::shortHelpString() const
+{
+ return QObject::tr( "This algorithm computes a new vector layer with the same features of the input layer, "
+ "but either overwriting an existing attribute or adding an additional attribute. The values of this field"
+ "are computed from each feature using an expression, based on the properties and attributes of the feature."
+ "Note that selecting a value in \"Result in existing field\" will ignore all the rest of the "
+ "field settings." );
+}
+
+QgsFieldCalculatorAlgorithm *QgsFieldCalculatorAlgorithm::createInstance() const
+{
+ return new QgsFieldCalculatorAlgorithm();
+}
+
+
+bool QgsFieldCalculatorAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * )
+{
+ std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
+
+ if ( !source )
+ throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
+
+ QList fieldTypes( {QVariant::Double, QVariant::Int, QVariant::String, QVariant::Date} );
+
+ // prepare fields
+ const int fieldTypeIdx = parameterAsInt( parameters, QStringLiteral( "FIELD_TYPE" ), context );
+ const int fieldLength = parameterAsInt( parameters, QStringLiteral( "FIELD_LENGTH" ), context );
+ const int fieldPrecision = parameterAsInt( parameters, QStringLiteral( "FIELD_PRECISION" ), context );
+ const QString existingFieldName = parameterAsString( parameters, QStringLiteral( "EXISTING_FIELD_NAME" ), context );
+ const QString newFieldName = parameterAsString( parameters, QStringLiteral( "NEW_FIELD_NAME" ), context );
+
+ QVariant::Type fieldType = fieldTypes[fieldTypeIdx];
+
+ // this is to keep backwards compatibility, "NEW_FIELD" flags what how "FIELD_NAME" should be treated
+ // since they are not defined parameters, they should be accessed directly from `parameters`
+ bool isNewField = parameters.value( QStringLiteral( "NEW_FIELD" ) ).toBool();
+ QString fieldName = parameters.value( QStringLiteral( "FIELD_NAME" ) ).toString();
+
+ // In a perfect universe there would be only "EXISTING_FIELD_NAME" and "NEW_FIELD_NAME"
+ if ( !parameters.contains( QStringLiteral( "NEW_FIELD" ) ) )
+ {
+ isNewField = existingFieldName.isEmpty();
+ fieldName = isNewField ? newFieldName : existingFieldName;
+ }
+
+ if ( fieldName.isEmpty() )
+ throw QgsProcessingException( QObject::tr( "Field name must not be an empty string" ) );
+
+ const QgsField field(
+ fieldName,
+ fieldType,
+ QString(),
+ fieldLength,
+ fieldPrecision
+ );
+
+ mFields = source->fields();
+
+ int fieldIdx = mFields.lookupField( field.name() );
+
+ if ( isNewField || fieldIdx < 0 )
+ mFields.append( field );
+
+ QString dest;
+
+ mFieldIdx = mFields.lookupField( field.name() );
+
+ // prepare expression
+ QString expressionString = parameterAsString( parameters, QStringLiteral( "FORMULA" ), context );
+ mExpressionContext = createExpressionContext( parameters, context, source.get() );
+ mExpression = QgsExpression( expressionString );
+ mDa.setSourceCrs( source->sourceCrs(), context.transformContext() );
+ mDa.setEllipsoid( context.ellipsoid() );
+
+ mExpression.setGeomCalculator( &mDa );
+ mExpression.setDistanceUnits( context.distanceUnit() );
+ mExpression.setAreaUnits( context.areaUnit() );
+
+ if ( mExpression.hasParserError() )
+ throw QgsProcessingException( QObject::tr( "Parser error with formula expression \"%2\": %3" )
+ .arg( expressionString, mExpression.parserErrorString() ) );
+
+ mExpression.prepare( &mExpressionContext );
+
+ return true;
+}
+
+QgsFeatureList QgsFieldCalculatorAlgorithm::processFeature( const QgsFeature &feature, QgsProcessingContext &, QgsProcessingFeedback * )
+{
+ QgsAttributes attributes( mFields.size() );
+ const QStringList fieldNames = mFields.names();
+ for ( const QString &fieldName : fieldNames )
+ {
+ const int attributeIndex = feature.fieldNameIndex( fieldName );
+
+ if ( attributeIndex >= 0 )
+ attributes[attributeIndex] = feature.attribute( fieldName );
+ }
+
+ if ( mExpression.isValid() )
+ {
+ mExpressionContext.setFeature( feature );
+ mExpressionContext.lastScope()->setVariable( QStringLiteral( "row_number" ), mRowNumber );
+
+ const QVariant value = mExpression.evaluate( &mExpressionContext );
+
+ if ( mExpression.hasEvalError() )
+ {
+ throw QgsProcessingException( QObject::tr( "Evaluation error in expression \"%1\": %2" )
+ .arg( mExpression.expression(), mExpression.evalErrorString() ) );
+ }
+
+ attributes[mFieldIdx] = value;
+ attributes.append( value );
+ }
+ else
+ {
+ attributes.append( QVariant() );
+ }
+
+ QgsFeature f = feature;
+ f.setAttributes( attributes );
+ mRowNumber++;
+ return QgsFeatureList() << f;
+}
+
+bool QgsFieldCalculatorAlgorithm::supportInPlaceEdit( const QgsMapLayer *layer ) const
+{
+ Q_UNUSED( layer )
+ return false;
+}
+
+///@endcond
+
diff --git a/src/analysis/processing/qgsalgorithmfieldcalculator.h b/src/analysis/processing/qgsalgorithmfieldcalculator.h
new file mode 100644
index 00000000000..e85478ca9af
--- /dev/null
+++ b/src/analysis/processing/qgsalgorithmfieldcalculator.h
@@ -0,0 +1,68 @@
+/***************************************************************************
+ qgsalgorithmfieldcalculator.h
+ ----------------------
+ begin : September 2020
+ copyright : (C) 2020 by Ivan Ivanov
+ email : ivan@opengis.ch
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * 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. *
+ * *
+ ***************************************************************************/
+
+
+#ifndef QGSALGORITHMFIELDSCALCULATOR_H
+#define QGSALGORITHMFIELDSCALCULATOR_H
+
+#define SIP_NO_FILE
+
+#include "qgis_sip.h"
+#include "qgsprocessingalgorithm.h"
+
+///@cond PRIVATE
+
+/**
+ * Native field calculator algorithm.
+ */
+class QgsFieldCalculatorAlgorithm : public QgsProcessingFeatureBasedAlgorithm
+{
+
+ public:
+
+ QgsFieldCalculatorAlgorithm() = default;
+ QString name() const override;
+ QString displayName() const override;
+ QStringList tags() const override;
+ QString group() const override;
+ QString groupId() const override;
+ QString shortHelpString() const override;
+ QList inputLayerTypes() const override;
+ QgsFieldCalculatorAlgorithm *createInstance() const override SIP_FACTORY;
+
+ protected:
+ void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
+ QString outputName() const override;
+ QgsFields outputFields( const QgsFields &inputFields ) const override;
+ QgsProcessingFeatureSource::Flag sourceFlags() const override;
+
+ bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
+ QgsFeatureList processFeature( const QgsFeature &feature, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
+ bool supportInPlaceEdit( const QgsMapLayer *layer ) const override;
+
+ private:
+ QgsFields mFields;
+ int mFieldIdx;
+ QgsExpression mExpression;
+ QgsExpressionContext mExpressionContext;
+ QgsDistanceArea mDa;
+ int mRowNumber;
+};
+
+///@endcond PRIVATE
+
+#endif // QGSALGORITHMFIELDSCALCULATOR_H
diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp
index fbcb60f0643..261ec47382c 100644
--- a/src/analysis/processing/qgsnativealgorithms.cpp
+++ b/src/analysis/processing/qgsnativealgorithms.cpp
@@ -68,6 +68,7 @@
#include "qgsalgorithmextractvertices.h"
#include "qgsalgorithmextractspecificvertices.h"
#include "qgsalgorithmextractzmvalues.h"
+#include "qgsalgorithmfieldcalculator.h"
#include "qgsalgorithmfiledownloader.h"
#include "qgsalgorithmfillnodata.h"
#include "qgsalgorithmfilter.h"
@@ -292,6 +293,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
addAlgorithm( new QgsExtractVerticesAlgorithm() );
addAlgorithm( new QgsExtractSpecificVerticesAlgorithm() );
addAlgorithm( new QgsExtractZValuesAlgorithm() );
+ addAlgorithm( new QgsFieldCalculatorAlgorithm() );
addAlgorithm( new QgsFileDownloaderAlgorithm() );
addAlgorithm( new QgsFillNoDataAlgorithm() );
addAlgorithm( new QgsFilterAlgorithm() );
diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
index 00801d90b16..8bb095ebf88 100644
--- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
+++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.cpp
@@ -30,6 +30,7 @@
#include "qgssettings.h"
#include "qgsexpressionlineedit.h"
#include "qgsfieldexpressionwidget.h"
+#include "qgsexpressionbuilderwidget.h"
#include "qgsprocessingmultipleselectiondialog.h"
#include "qgslayoutmanager.h"
#include "qgsproject.h"
@@ -1943,15 +1944,30 @@ QWidget *QgsProcessingExpressionWidgetWrapper::createWidget()
}
else
{
- mFieldExpWidget = new QgsFieldExpressionWidget();
- mFieldExpWidget->setToolTip( parameterDefinition()->toolTip() );
- mFieldExpWidget->setExpressionDialogTitle( parameterDefinition()->description() );
- mFieldExpWidget->registerExpressionContextGenerator( this );
- connect( mFieldExpWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, [ = ]( const QString & )
+ if ( expParam->metadata().value( QStringLiteral( "inlineEditor" ) ).toBool() )
{
- emit widgetValueHasChanged( this );
- } );
- return mFieldExpWidget;
+ mExpBuilderWidget = new QgsExpressionBuilderWidget();
+ mExpBuilderWidget->setToolTip( parameterDefinition()->toolTip() );
+ mExpBuilderWidget->init( createExpressionContext() );
+ connect( mExpBuilderWidget, &QgsExpressionBuilderWidget::expressionParsed, this, [ = ]( bool changed )
+ {
+ Q_UNUSED( changed );
+ emit widgetValueHasChanged( this );
+ } );
+ return mExpBuilderWidget;
+ }
+ else
+ {
+ mFieldExpWidget = new QgsFieldExpressionWidget();
+ mFieldExpWidget->setToolTip( parameterDefinition()->toolTip() );
+ mFieldExpWidget->setExpressionDialogTitle( parameterDefinition()->description() );
+ mFieldExpWidget->registerExpressionContextGenerator( this );
+ connect( mFieldExpWidget, static_cast < void ( QgsFieldExpressionWidget::* )( const QString & ) >( &QgsFieldExpressionWidget::fieldChanged ), this, [ = ]( const QString & )
+ {
+ emit widgetValueHasChanged( this );
+ } );
+ return mFieldExpWidget;
+ }
}
}
}
@@ -2005,6 +2021,8 @@ void QgsProcessingExpressionWidgetWrapper::setParentLayerWrapperValue( const Qgs
{
if ( mFieldExpWidget )
mFieldExpWidget->setLayer( nullptr );
+ else if ( mExpBuilderWidget )
+ mExpBuilderWidget->setLayer( nullptr );
else if ( mExpLineEdit )
mExpLineEdit->setLayer( nullptr );
return;
@@ -2025,6 +2043,8 @@ void QgsProcessingExpressionWidgetWrapper::setParentLayerWrapperValue( const Qgs
if ( mFieldExpWidget )
mFieldExpWidget->setLayer( layer );
+ if ( mExpBuilderWidget )
+ mExpBuilderWidget->setLayer( layer );
else if ( mExpLineEdit )
mExpLineEdit->setLayer( layer );
}
@@ -2034,6 +2054,8 @@ void QgsProcessingExpressionWidgetWrapper::setWidgetValue( const QVariant &value
const QString v = QgsProcessingParameters::parameterAsString( parameterDefinition(), value, context );
if ( mFieldExpWidget )
mFieldExpWidget->setExpression( v );
+ else if ( mExpBuilderWidget )
+ mExpBuilderWidget->setExpressionText( v );
else if ( mExpLineEdit )
mExpLineEdit->setExpression( v );
}
@@ -2042,6 +2064,8 @@ QVariant QgsProcessingExpressionWidgetWrapper::widgetValue() const
{
if ( mFieldExpWidget )
return mFieldExpWidget->expression();
+ if ( mExpBuilderWidget )
+ return mExpBuilderWidget->expressionText();
else if ( mExpLineEdit )
return mExpLineEdit->expression();
else
@@ -2076,6 +2100,9 @@ const QgsVectorLayer *QgsProcessingExpressionWidgetWrapper::linkedVectorLayer()
if ( mFieldExpWidget && mFieldExpWidget->layer() )
return mFieldExpWidget->layer();
+ if ( mExpBuilderWidget && mExpBuilderWidget->layer() )
+ return mExpBuilderWidget->layer();
+
return QgsAbstractProcessingParameterWidgetWrapper::linkedVectorLayer();
}
diff --git a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h
index 793b3502721..6b9e5180fcb 100644
--- a/src/gui/processing/qgsprocessingwidgetwrapperimpl.h
+++ b/src/gui/processing/qgsprocessingwidgetwrapperimpl.h
@@ -41,6 +41,7 @@ class QgsAuthConfigSelect;
class QgsProcessingMatrixParameterPanel;
class QgsFileWidget;
class QgsFieldExpressionWidget;
+class QgsExpressionBuilderWidget;
class QgsExpressionLineEdit;
class QgsProcessingParameterEnum;
class QgsLayoutComboBox;
@@ -677,6 +678,7 @@ class GUI_EXPORT QgsProcessingExpressionWidgetWrapper : public QgsAbstractProces
private:
QgsFieldExpressionWidget *mFieldExpWidget = nullptr;
+ QgsExpressionBuilderWidget *mExpBuilderWidget = nullptr;
QgsExpressionLineEdit *mExpLineEdit = nullptr;
std::unique_ptr< QgsVectorLayer > mParentLayer;