Merge pull request #38695 from suricactus/alg_fieldcalc_cpp_simple

Translate qgis::fieldcalculator to C++
This commit is contained in:
Matthias Kuhn 2020-09-11 15:41:24 +02:00 committed by GitHub
commit 3bea2e702e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 349 additions and 723 deletions

View File

@ -114,9 +114,6 @@ qgis:extractbyexpression: >
For more information about expressions see the <a href ="{qgisdocs}/user_manual/working_with_vector/expression.html">user manual</a>
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.

View File

@ -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)

View File

@ -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(),

View File

@ -1,276 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>FieldsCalculator</class>
<widget class="QDialog" name="FieldsCalculator">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>681</width>
<height>681</height>
</rect>
</property>
<property name="windowTitle">
<string>Field calculator</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QGroupBox" name="mNewFieldGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Create a new field</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>true</bool>
</property>
<layout class="QGridLayout">
<property name="sizeConstraint">
<enum>QLayout::SetMinimumSize</enum>
</property>
<property name="leftMargin">
<number>3</number>
</property>
<property name="topMargin">
<number>3</number>
</property>
<property name="rightMargin">
<number>3</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<property name="verticalSpacing">
<number>3</number>
</property>
<item row="0" column="0">
<widget class="QLabel" name="mFieldNameLabel">
<property name="text">
<string>Output field name</string>
</property>
<property name="buddy">
<cstring>mOutputFieldNameLineEdit</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QLineEdit" name="mOutputFieldNameLineEdit"/>
</item>
<item row="1" column="0">
<widget class="QLabel" name="mOutputFieldTypeLabel">
<property name="text">
<string>Output field type</string>
</property>
<property name="buddy">
<cstring>mOutputFieldTypeComboBox</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QComboBox" name="mOutputFieldTypeComboBox"/>
</item>
<item row="2" column="0">
<widget class="QLabel" name="mOutputFieldWidthLabel">
<property name="text">
<string>Output field width</string>
</property>
<property name="buddy">
<cstring>mOutputFieldWidthSpinBox</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="mOutputFieldWidthSpinBox">
<property name="toolTip">
<string>Width of complete output. For example 123,456 means 6 as field width.</string>
</property>
<property name="minimum">
<number>0</number>
</property>
<property name="value">
<number>15</number>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QLabel" name="mOutputFieldPrecisionLabel">
<property name="text">
<string>Precision</string>
</property>
<property name="buddy">
<cstring>mOutputFieldPrecisionSpinBox</cstring>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QSpinBox" name="mOutputFieldPrecisionSpinBox">
<property name="value">
<number>2</number>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item row="5" column="0" colspan="2">
<widget class="QDialogButtonBox" name="mButtonBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>3</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
<item row="0" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Input layer</string>
</property>
</widget>
</item>
<item>
<widget class="QgsMapLayerComboBox" name="cmbInputLayer">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="1">
<widget class="QGroupBox" name="mUpdateExistingGroupBox">
<property name="title">
<string>Update existing field</string>
</property>
<property name="flat">
<bool>true</bool>
</property>
<property name="checkable">
<bool>true</bool>
</property>
<property name="checked">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QComboBox" name="mExistingFieldComboBox"/>
</item>
</layout>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QgsExpressionBuilderWidget" name="builder" native="true">
<property name="autoFillBackground">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Output file</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="leOutputFile"/>
</item>
<item>
<widget class="QToolButton" name="btnBrowse">
<property name="text">
<string>…</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0" colspan="2">
<widget class="QProgressBar" name="progressBar">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QgsExpressionBuilderWidget</class>
<extends>QWidget</extends>
<header>qgis.gui</header>
<container>1</container>
</customwidget>
<customwidget>
<class>QgsMapLayerComboBox</class>
<extends>QComboBox</extends>
<header>qgis.gui</header>
<container>0</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>mOutputFieldNameLineEdit</tabstop>
<tabstop>mOutputFieldTypeComboBox</tabstop>
<tabstop>mOutputFieldWidthSpinBox</tabstop>
<tabstop>mOutputFieldPrecisionSpinBox</tabstop>
<tabstop>mButtonBox</tabstop>
</tabstops>
<resources/>
<connections>
<connection>
<sender>mButtonBox</sender>
<signal>accepted()</signal>
<receiver>FieldsCalculator</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>679</x>
<y>559</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>mButtonBox</sender>
<signal>rejected()</signal>
<receiver>FieldsCalculator</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>679</x>
<y>559</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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<int> QgsFieldCalculatorAlgorithm::inputLayerTypes() const
{
return QList<int>() << 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 &parameters, QgsProcessingContext &context, QgsProcessingFeedback * )
{
std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !source )
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
QList<QVariant::Type> 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

View File

@ -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<int> 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 &parameters, 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

View File

@ -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() );

View File

@ -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();
}

View File

@ -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;