diff --git a/python/plugins/processing/algs/qgis/DefineProjection.py b/python/plugins/processing/algs/qgis/DefineProjection.py index 492e6a383959..520ef50fd49f 100644 --- a/python/plugins/processing/algs/qgis/DefineProjection.py +++ b/python/plugins/processing/algs/qgis/DefineProjection.py @@ -28,15 +28,12 @@ import os import re -from qgis.core import (QgsCoordinateReferenceSystem, - QgsApplication, - QgsProcessingUtils) -from qgis.utils import iface +from qgis.core import (QgsProcessing, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterCrs, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterCrs -from processing.core.outputs import OutputVector pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -45,7 +42,6 @@ class DefineProjection(QgisAlgorithm): INPUT = 'INPUT' CRS = 'CRS' - OUTPUT = 'OUTPUT' def group(self): return self.tr('Vector general tools') @@ -54,11 +50,11 @@ def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input Layer'))) - self.addParameter(ParameterCrs(self.CRS, 'Output CRS')) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Layer with projection'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input Layer'), types=[QgsProcessing.TypeVectorAnyGeometry])) + self.addParameter(QgsProcessingParameterCrs(self.CRS, 'Output CRS')) + self.addOutput(QgsProcessingOutputVectorLayer(self.INPUT, + self.tr('Layer with projection'))) def name(self): return 'definecurrentprojection' @@ -67,9 +63,8 @@ def displayName(self): return self.tr('Define current projection') def processAlgorithm(self, parameters, context, feedback): - fileName = self.getParameterValue(self.INPUT) - layer = QgsProcessingUtils.mapLayerFromString(fileName, context) - crs = QgsCoordinateReferenceSystem(self.getParameterValue(self.CRS)) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + crs = self.parameterAsCrs(parameters, self.CRS, context) provider = layer.dataProvider() ds = provider.dataSourceUri() @@ -89,6 +84,6 @@ def processAlgorithm(self, parameters, context, feedback): f.write(wkt) layer.setCrs(crs) - iface.mapCanvas().refresh() + layer.triggerRepaint() - self.setOutputValue(self.OUTPUT, fileName) + return {self.INPUT: layer} diff --git a/python/plugins/processing/algs/qgis/ExecuteSQL.py b/python/plugins/processing/algs/qgis/ExecuteSQL.py index 92f8d5260a22..6054d7860c9e 100644 --- a/python/plugins/processing/algs/qgis/ExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/ExecuteSQL.py @@ -25,21 +25,18 @@ __revision__ = '$Format:%H$' -from qgis.core import (QgsFeature, - QgsVirtualLayerDefinition, +from qgis.core import (QgsVirtualLayerDefinition, QgsVectorLayer, - QgsCoordinateReferenceSystem, QgsWkbTypes, - QgsApplication, - QgsProcessingUtils) + QgsProcessingParameterMultipleLayers, + QgsProcessingParameterString, + QgsProcessingParameterEnum, + QgsProcessingParameterCrs, + QgsProcessingParameterFeatureSink, + QgsFeatureSink, + QgsProcessingException) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterString -from processing.core.parameters import ParameterMultipleInput -from processing.core.parameters import ParameterCrs -from processing.core.parameters import ParameterSelection -from processing.core.outputs import OutputVector class ExecuteSQL(QgisAlgorithm): @@ -54,7 +51,7 @@ class ExecuteSQL(QgisAlgorithm): INPUT_GEOMETRY_FIELD = 'INPUT_GEOMETRY_FIELD' INPUT_GEOMETRY_TYPE = 'INPUT_GEOMETRY_TYPE' INPUT_GEOMETRY_CRS = 'INPUT_GEOMETRY_CRS' - OUTPUT_LAYER = 'OUTPUT_LAYER' + OUTPUT = 'OUTPUT' def group(self): return self.tr('Vector general tools') @@ -63,19 +60,19 @@ def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterMultipleInput(name=self.INPUT_DATASOURCES, - description=self.tr('Additional input datasources (called input1, .., inputN in the query)'), - optional=True)) + self.addParameter(QgsProcessingParameterMultipleLayers(name=self.INPUT_DATASOURCES, + description=self.tr('Additional input datasources (called input1, .., inputN in the query)'), + optional=True)) - self.addParameter(ParameterString(name=self.INPUT_QUERY, - description=self.tr('SQL query'), - multiline=True)) + self.addParameter(QgsProcessingParameterString(name=self.INPUT_QUERY, + description=self.tr('SQL query'), + multiLine=True)) - self.addParameter(ParameterString(name=self.INPUT_UID_FIELD, - description=self.tr('Unique identifier field'), optional=True)) + self.addParameter(QgsProcessingParameterString(name=self.INPUT_UID_FIELD, + description=self.tr('Unique identifier field'), optional=True)) - self.addParameter(ParameterString(name=self.INPUT_GEOMETRY_FIELD, - description=self.tr('Geometry field'), optional=True)) + self.addParameter(QgsProcessingParameterString(name=self.INPUT_GEOMETRY_FIELD, + description=self.tr('Geometry field'), optional=True)) self.geometryTypes = [ self.tr('Autodetect'), @@ -86,13 +83,13 @@ def initAlgorithm(self, config=None): 'MultiPoint', 'MultiLineString', 'MultiPolygon'] - self.addParameter(ParameterSelection(self.INPUT_GEOMETRY_TYPE, - self.tr('Geometry type'), self.geometryTypes, optional=True)) + self.addParameter(QgsProcessingParameterEnum(self.INPUT_GEOMETRY_TYPE, + self.tr('Geometry type'), options=self.geometryTypes, optional=True)) - self.addParameter(ParameterCrs(self.INPUT_GEOMETRY_CRS, - self.tr('CRS'), optional=True)) + self.addParameter(QgsProcessingParameterCrs(self.INPUT_GEOMETRY_CRS, + self.tr('CRS'), optional=True)) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('SQL Output'))) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('SQL Output'))) def name(self): return 'executesql' @@ -101,24 +98,19 @@ def displayName(self): return self.tr('Execute SQL') def processAlgorithm(self, parameters, context, feedback): - layers = self.getParameterValue(self.INPUT_DATASOURCES) - query = self.getParameterValue(self.INPUT_QUERY) - uid_field = self.getParameterValue(self.INPUT_UID_FIELD) - geometry_field = self.getParameterValue(self.INPUT_GEOMETRY_FIELD) - geometry_type = self.getParameterValue(self.INPUT_GEOMETRY_TYPE) - geometry_crs = self.getParameterValue(self.INPUT_GEOMETRY_CRS) + layers = self.parameterAsLayerList(parameters, self.INPUT_DATASOURCES, context) + query = self.parameterAsString(parameters, self.INPUT_QUERY, context) + uid_field = self.parameterAsString(parameters, self.INPUT_UID_FIELD, context) + geometry_field = self.parameterAsString(parameters, self.INPUT_GEOMETRY_FIELD, context) + geometry_type = self.parameterAsEnum(parameters, self.INPUT_GEOMETRY_TYPE, context) + geometry_crs = self.parameterAsCrs(parameters, self.INPUT_GEOMETRY_CRS, context) df = QgsVirtualLayerDefinition() - layerIdx = 1 - if layers: - for layerSource in layers.split(';'): - layer = QgsProcessingUtils.mapLayerFromString(layerSource, context) - if layer: - df.addSource('input{}'.format(layerIdx), layer.id()) - layerIdx += 1 + for layerIdx, layer in enumerate(layers): + df.addSource('input{}'.format(layerIdx + 1), layer.id()) if query == '': - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr('Empty SQL. Please enter valid SQL expression and try again.')) else: df.setQuery(query) @@ -133,26 +125,22 @@ def processAlgorithm(self, parameters, context, feedback): df.setGeometryField(geometry_field) if geometry_type > 1: df.setGeometryWkbType(geometry_type - 1) - if geometry_crs: - crs = QgsCoordinateReferenceSystem(geometry_crs) - if crs.isValid(): - df.setGeometrySrid(crs.postgisSrid()) + if geometry_crs.isValid(): + df.setGeometrySrid(geometry_crs.postgisSrid()) vLayer = QgsVectorLayer(df.toString(), "temp_vlayer", "virtual") if not vLayer.isValid(): - raise GeoAlgorithmExecutionException(vLayer.dataProvider().error().message()) + raise QgsProcessingException(vLayer.dataProvider().error().message()) - writer = self.getOutputFromName(self.OUTPUT_LAYER).getVectorWriter(vLayer.fields(), - vLayer.wkbType() if geometry_type != 1 else 1, - vLayer.crs(), context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + vLayer.fields(), vLayer.wkbType() if geometry_type != 1 else 1, vLayer.crs()) - features = QgsProcessingUtils.getFeatures(vLayer, context) + features = vLayer.getFeatures() total = 100.0 / vLayer.featureCount() if vLayer.featureCount() else 0 - outFeat = QgsFeature() for current, inFeat in enumerate(features): - outFeat.setAttributes(inFeat.attributes()) - if geometry_type != 1: - outFeat.setGeometry(inFeat.geometry()) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + if feedback.isCanceled(): + break + + sink.addFeature(inFeat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/FieldPyculator.py b/python/plugins/processing/algs/qgis/FieldPyculator.py index 0203897bb577..eb00cfcad049 100644 --- a/python/plugins/processing/algs/qgis/FieldPyculator.py +++ b/python/plugins/processing/algs/qgis/FieldPyculator.py @@ -29,30 +29,27 @@ import sys from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsFeature, +from qgis.core import (QgsProcessingException, QgsField, QgsFeatureSink, - QgsApplication, - QgsProcessingUtils) + QgsProcessingParameterFeatureSource, + QgsProcessingParameterString, + QgsProcessingParameterEnum, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink) 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 ParameterSelection -from processing.core.outputs import OutputVector class FieldsPyculator(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' + INPUT = 'INPUT' FIELD_NAME = 'FIELD_NAME' FIELD_TYPE = 'FIELD_TYPE' FIELD_LENGTH = 'FIELD_LENGTH' FIELD_PRECISION = 'FIELD_PRECISION' GLOBAL = 'GLOBAL' FORMULA = 'FORMULA' - OUTPUT_LAYER = 'OUTPUT_LAYER' + OUTPUT = 'OUTPUT' RESULT_VAR_NAME = 'value' TYPES = [QVariant.Int, QVariant.Double, QVariant.String] @@ -68,21 +65,21 @@ def initAlgorithm(self, config=None): self.tr('Float'), self.tr('String')] - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'))) - self.addParameter(ParameterString(self.FIELD_NAME, - self.tr('Result field name'), 'NewField')) - 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, 10, 0)) - self.addParameter(ParameterString(self.GLOBAL, - self.tr('Global expression'), multiline=True, optional=True)) - self.addParameter(ParameterString(self.FORMULA, - self.tr('Formula'), 'value = ', multiline=True)) - self.addOutput(OutputVector(self.OUTPUT_LAYER, self.tr('Calculated'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterString(self.FIELD_NAME, + self.tr('Result field name'), defaultValue='NewField')) + 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(QgsProcessingParameterString(self.GLOBAL, + self.tr('Global expression'), multiLine=True, optional=True)) + self.addParameter(QgsProcessingParameterString(self.FORMULA, + self.tr('Formula'), defaultValue='value = ', multiLine=True)) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Calculated'))) def name(self): return 'advancedpythonfieldcalculator' @@ -91,33 +88,33 @@ def displayName(self): return self.tr('Advanced Python field calculator') def processAlgorithm(self, parameters, context, feedback): - fieldName = self.getParameterValue(self.FIELD_NAME) - fieldType = self.getParameterValue(self.FIELD_TYPE) - fieldLength = self.getParameterValue(self.FIELD_LENGTH) - fieldPrecision = self.getParameterValue(self.FIELD_PRECISION) - code = self.getParameterValue(self.FORMULA) - globalExpression = self.getParameterValue(self.GLOBAL) - output = self.getOutputFromName(self.OUTPUT_LAYER) - - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) - fields = layer.fields() - fields.append(QgsField(fieldName, self.TYPES[fieldType], '', - fieldLength, fieldPrecision)) - writer = output.getVectorWriter(fields, layer.wkbType(), layer.crs(), context) - outFeat = QgsFeature() + source = self.parameterAsSource(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) + code = self.parameterAsString(parameters, self.FORMULA, context) + globalExpression = self.parameterAsString(parameters, self.GLOBAL, context) + + fields = source.fields() + fields.append(QgsField(field_name, self.TYPES[field_type], '', + width, precision)) new_ns = {} + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, source.wkbType(), source.sourceCrs()) + # Run global code if globalExpression.strip() != '': try: bytecode = compile(globalExpression, '', 'exec') exec(bytecode, new_ns) except: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr("FieldPyculator code execute error.Global code block can't be executed!\n{0}\n{1}").format(str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]))) # Replace all fields tags - fields = layer.fields() + fields = source.fields() num = 0 for field in fields: field_name = str(field.name()) @@ -136,13 +133,17 @@ def processAlgorithm(self, parameters, context, feedback): try: bytecode = compile(code, '', 'exec') except: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr("FieldPyculator code execute error. Field code block can't be executed!\n{0}\n{1}").format(str(sys.exc_info()[0].__name__), str(sys.exc_info()[1]))) # Run - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 + for current, feat in enumerate(features): + if feedback.isCanceled(): + break + feedback.setProgress(int(current * total)) attrs = feat.attributes() feat_id = feat.id() @@ -168,18 +169,17 @@ def processAlgorithm(self, parameters, context, feedback): # Check result if self.RESULT_VAR_NAME not in new_ns: - raise GeoAlgorithmExecutionException( + raise QgsProcessingException( self.tr("FieldPyculator code execute error\n" "Field code block does not return '{0}' variable! " "Please declare this variable in your code!").format(self.RESULT_VAR_NAME)) # Write feature - outFeat.setGeometry(feat.geometry()) attrs.append(new_ns[self.RESULT_VAR_NAME]) - outFeat.setAttributes(attrs) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + feat.setAttributes(attrs) + sink.addFeature(feat, QgsFeatureSink.FastInsert) - del writer + return {self.OUTPUT: dest_id} def checkParameterValues(self, parameters, context): # TODO check that formula is correct and fields exist diff --git a/python/plugins/processing/algs/qgis/FieldsCalculator.py b/python/plugins/processing/algs/qgis/FieldsCalculator.py index 87e88abff1b9..6d05e60dc9ae 100644 --- a/python/plugins/processing/algs/qgis/FieldsCalculator.py +++ b/python/plugins/processing/algs/qgis/FieldsCalculator.py @@ -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): diff --git a/python/plugins/processing/algs/qgis/FindProjection.py b/python/plugins/processing/algs/qgis/FindProjection.py index 94bfa8e34605..62f61f6772bf 100644 --- a/python/plugins/processing/algs/qgis/FindProjection.py +++ b/python/plugins/processing/algs/qgis/FindProjection.py @@ -26,31 +26,32 @@ __revision__ = '$Format:%H$' import os -import codecs -from qgis.core import (QgsApplication, - QgsGeometry, +from qgis.core import (QgsGeometry, + QgsFeature, QgsFeatureSink, - QgsRectangle, + QgsField, + QgsFields, QgsCoordinateReferenceSystem, QgsCoordinateTransform, - QgsProcessingUtils) + QgsWkbTypes, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterExtent, + QgsProcessingParameterCrs, + QgsProcessingParameterFeatureSink) +from qgis.PyQt.QtCore import QVariant from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterCrs -from processing.core.parameters import ParameterExtent -from processing.core.outputs import OutputHTML pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class FindProjection(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' + INPUT = 'INPUT' TARGET_AREA = 'TARGET_AREA' TARGET_AREA_CRS = 'TARGET_AREA_CRS' - OUTPUT_HTML_FILE = 'OUTPUT_HTML_FILE' + OUTPUT = 'OUTPUT' def tags(self): return self.tr('crs,srs,coordinate,reference,system,guess,estimate,finder,determine').split(',') @@ -62,17 +63,16 @@ def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'))) - extent_parameter = ParameterExtent(self.TARGET_AREA, - self.tr('Target area for layer'), - self.INPUT_LAYER) - extent_parameter.skip_crs_check = True + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'))) + extent_parameter = QgsProcessingParameterExtent(self.TARGET_AREA, + self.tr('Target area for layer')) + #extent_parameter.skip_crs_check = True self.addParameter(extent_parameter) - self.addParameter(ParameterCrs(self.TARGET_AREA_CRS, 'Target area CRS')) + self.addParameter(QgsProcessingParameterCrs(self.TARGET_AREA_CRS, 'Target area CRS')) - self.addOutput(OutputHTML(self.OUTPUT_HTML_FILE, - self.tr('Candidates'))) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('CRS candidates'))) def name(self): return 'findprojection' @@ -81,27 +81,34 @@ def displayName(self): return self.tr('Find projection') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) + source = self.parameterAsSource(parameters, self.INPUT, context) - extent = self.getParameterValue(self.TARGET_AREA).split(',') - if not extent: - extent = QgsProcessingUtils.combineLayerExtents([layer]) - target_crs = QgsCoordinateReferenceSystem(self.getParameterValue(self.TARGET_AREA_CRS)) + extent = self.parameterAsExtent(parameters, self.TARGET_AREA, context) + target_crs = self.parameterAsCrs(parameters, self.TARGET_AREA_CRS, context) - target_geom = QgsGeometry.fromRect(QgsRectangle(float(extent[0]), float(extent[2]), - float(extent[1]), float(extent[3]))) + target_geom = QgsGeometry.fromRect(extent) - output_file = self.getOutputValue(self.OUTPUT_HTML_FILE) + fields = QgsFields() + fields.append(QgsField('auth_id', QVariant.String, '', 20)) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) # make intersection tests nice and fast engine = QgsGeometry.createGeometryEngine(target_geom.geometry()) engine.prepareGeometry() - layer_bounds = QgsGeometry.fromRect(layer.extent()) + layer_bounds = QgsGeometry.fromRect(source.sourceExtent()) + + crses_to_check = QgsCoordinateReferenceSystem.validSrsIds() + total = 100.0 / len(crses_to_check) + + found_results = 0 - results = [] + for current, srs_id in enumerate(crses_to_check): + if feedback.isCanceled(): + break - for srs_id in QgsCoordinateReferenceSystem.validSrsIds(): candidate_crs = QgsCoordinateReferenceSystem.fromSrsId(srs_id) if not candidate_crs.isValid(): continue @@ -115,15 +122,15 @@ def processAlgorithm(self, parameters, context, feedback): continue if engine.intersects(transformed_bounds.geometry()): - results.append(candidate_crs.authid()) - - self.createHTML(output_file, results) - - def createHTML(self, outputFile, candidates): - with codecs.open(outputFile, 'w', encoding='utf-8') as f: - f.write('\n') - f.write('\n') - for c in candidates: - f.write('

' + c + '

\n') - f.write('\n') + feedback.pushInfo(self.tr('Found candidate CRS: {}').format(candidate_crs.authid())) + f = QgsFeature(fields) + f.setAttributes([candidate_crs.authid()]) + sink.addFeature(f, QgsFeatureSink.FastInsert) + found_results += 1 + + feedback.setProgress(int(current * total)) + + if found_results == 0: + feedback.reportError(self.tr('No matching projections found')) + + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/GeometryConvert.py b/python/plugins/processing/algs/qgis/GeometryConvert.py index bbf7258a7e78..5bf3e9d42044 100644 --- a/python/plugins/processing/algs/qgis/GeometryConvert.py +++ b/python/plugins/processing/algs/qgis/GeometryConvert.py @@ -27,15 +27,17 @@ from qgis.core import (QgsFeature, QgsGeometry, + QgsMultiPointV2, + QgsMultiLineString, + QgsLineString, + QgsPolygonV2, QgsFeatureSink, QgsWkbTypes, - QgsApplication, - QgsProcessingUtils) + QgsProcessingException, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterEnum, + QgsProcessingParameterFeatureSink) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterSelection -from processing.core.outputs import OutputVector class GeometryConvert(QgisAlgorithm): @@ -56,12 +58,13 @@ def initAlgorithm(self, config=None): self.tr('Multilinestrings'), self.tr('Polygons')] - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'))) - self.addParameter(ParameterSelection(self.TYPE, - self.tr('New geometry type'), self.types)) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'))) + self.addParameter(QgsProcessingParameterEnum(self.TYPE, + self.tr('New geometry type'), options=self.types)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Converted'))) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, + self.tr('Converted'))) def name(self): return 'convertgeometrytype' @@ -70,159 +73,162 @@ def displayName(self): return self.tr('Convert geometry type') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - index = self.getParameterValue(self.TYPE) + source = self.parameterAsSource(parameters, self.INPUT, context) + index = self.parameterAsEnum(parameters, self.TYPE, context) - splitNodes = False if index == 0: newType = QgsWkbTypes.Point elif index == 1: newType = QgsWkbTypes.Point - splitNodes = True + if QgsWkbTypes.hasM(source.wkbType()): + newType = QgsWkbTypes.addM(newType) + if QgsWkbTypes.hasZ(source.wkbType()): + newType = QgsWkbTypes.addZ(newType) elif index == 2: newType = QgsWkbTypes.LineString + if QgsWkbTypes.hasM(source.wkbType()): + newType = QgsWkbTypes.addM(newType) + if QgsWkbTypes.hasZ(source.wkbType()): + newType = QgsWkbTypes.addZ(newType) elif index == 3: newType = QgsWkbTypes.MultiLineString - elif index == 4: - newType = QgsWkbTypes.Polygon + if QgsWkbTypes.hasM(source.wkbType()): + newType = QgsWkbTypes.addM(newType) + if QgsWkbTypes.hasZ(source.wkbType()): + newType = QgsWkbTypes.addZ(newType) else: - newType = QgsWkbTypes.Point + newType = QgsWkbTypes.Polygon + if QgsWkbTypes.hasM(source.wkbType()): + newType = QgsWkbTypes.addM(newType) + if QgsWkbTypes.hasZ(source.wkbType()): + newType = QgsWkbTypes.addZ(newType) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), newType, layer.crs(), context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), newType, source.sourceCrs()) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): - geom = f.geometry() - geomType = geom.wkbType() - - if geomType in [QgsWkbTypes.Point, QgsWkbTypes.Point25D]: - if newType == QgsWkbTypes.Point: - writer.addFeature(f, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) - elif geomType in [QgsWkbTypes.MultiPoint, QgsWkbTypes.MultiPoint25D]: - if newType == QgsWkbTypes.Point and splitNodes: - points = geom.asMultiPoint() - for p in points: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPoint(p)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Point: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(geom.centroid()) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) - elif geomType in [QgsWkbTypes.LineString, QgsWkbTypes.LineString25D]: - if newType == QgsWkbTypes.Point and splitNodes: - points = geom.asPolyline() - for p in points: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPoint(p)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Point: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(geom.centroid()) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.LineString: - writer.addFeature(f, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) - elif geomType in [QgsWkbTypes.MultiLineString, QgsWkbTypes.MultiLineString25D]: - if newType == QgsWkbTypes.Point and splitNodes: - lines = geom.asMultiPolyline() - for line in lines: - for p in line: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPoint(p)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Point: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(geom.centroid()) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.LineString: - lines = geom.asMultiPolyline() - for line in lines: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPolyline(line)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.MultiLineString: - writer.addFeature(f, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) - elif geomType in [QgsWkbTypes.Polygon, QgsWkbTypes.Polygon25D]: - if newType == QgsWkbTypes.Point and splitNodes: - rings = geom.asPolygon() - for ring in rings: - for p in ring: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPoint(p)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Point: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(geom.centroid()) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.MultiLineString: - rings = geom.asPolygon() - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromMultiPolyline(rings)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Polygon: - writer.addFeature(f, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) - elif geomType in [QgsWkbTypes.MultiPolygon, QgsWkbTypes.MultiPolygon25D]: - if newType == QgsWkbTypes.Point and splitNodes: - polygons = geom.asMultiPolygon() - for polygon in polygons: - for line in polygon: - for p in line: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPoint(p)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Point: + if feedback.isCanceled(): + break + + if not f.hasGeometry(): + sink.addFeature(f, QgsFeatureSink.FastInsert) + else: + for p in self.convertGeometry(f.geometry(), index): feat = QgsFeature() feat.setAttributes(f.attributes()) - feat.setGeometry(geom.centroid()) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.LineString: - polygons = geom.asMultiPolygon() - for polygons in polygons: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPolyline(polygon)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType == QgsWkbTypes.Polygon: - polygons = geom.asMultiPolygon() - for polygon in polygons: - feat = QgsFeature() - feat.setAttributes(f.attributes()) - feat.setGeometry(QgsGeometry.fromPolygon(polygon)) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - elif newType in [QgsWkbTypes.MultiLineString, QgsWkbTypes.MultiPolygon]: - writer.addFeature(f, QgsFeatureSink.FastInsert) - else: - raise GeoAlgorithmExecutionException( - self.tr('Cannot convert from {0} to {1}').format(geomType, newType)) + feat.setGeometry(p) + sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} + + def convertGeometry(self, geom, target_type): + # returns an array of output geometries for the input geometry + if target_type == 0: + #centroid + return self.convertToCentroid(geom) + elif target_type == 1: + #nodes + return self.convertToNodes(geom) + elif target_type == 2: + #linestrings + return self.convertToLineStrings(geom) + elif target_type == 3: + #multilinestrings + return self.convertToMultiLineStrings(geom) + elif target_type == 4: + #polygon + return self.convertToPolygon(geom) + + def convertToCentroid(self, geom): + return [geom.centroid()] + + def convertToNodes(self, geom): + mp = QgsMultiPointV2() + # TODO: mega inefficient - needs rework when geometry iterators land + # (but at least it doesn't lose Z/M values) + for g in geom.geometry().coordinateSequence(): + for r in g: + for p in r: + mp.addGeometry(p) + return [QgsGeometry(mp)] + + def convertToLineStrings(self, geom): + if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: + raise QgsProcessingException( + self.tr('Cannot convert from {0} to LineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) + elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: + if QgsWkbTypes.isMultiType(geom.wkbType()): + return geom.asGeometryCollection() + else: + #line to line + return [geom] + else: + # polygons to lines + # we just use the boundary here - that consists of all rings in the (multi)polygon + boundary = QgsGeometry(geom.geometry().boundary()) + # boundary will be multipart + return boundary.asGeometryCollection() + + def convertToMultiLineStrings(self, geom): + if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: + raise QgsProcessingException( + self.tr('Cannot convert from {0} to MultiLineStrings').format(QgsWkbTypes.displayString(geom.wkbType()))) + elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: + if QgsWkbTypes.isMultiType(geom.wkbType()): + return [geom] + else: + # line to multiLine + ml = QgsMultiLineString() + ml.addGeometry(geom.geometry().clone()) + return [QgsGeometry(ml)] + else: + # polygons to multilinestring + # we just use the boundary here - that consists of all rings in the (multi)polygon + return [QgsGeometry(geom.geometry().boundary())] + + def convertToPolygon(self, geom): + if QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry and geom.geometry().nCoordinates() < 3: + raise QgsProcessingException( + self.tr('Cannot convert from Point to Polygon').format(QgsWkbTypes.displayString(geom.wkbType()))) + elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.PointGeometry: + # multipoint with at least 3 points + # TODO: mega inefficient - needs rework when geometry iterators land + # (but at least it doesn't lose Z/M values) + points = [] + for g in geom.geometry().coordinateSequence(): + for r in g: + for p in r: + points.append(p) + linestring = QgsLineString(points) + linestring.close() + p = QgsPolygonV2() + p.setExteriorRing(linestring) + return [QgsGeometry(p)] + elif QgsWkbTypes.geometryType(geom.wkbType()) == QgsWkbTypes.LineGeometry: + if QgsWkbTypes.isMultiType(geom): + parts = [] + for i in range(geom.geometry().numGeometries()): + p = QgsPolygonV2() + linestring = geom.geometry().geometryN(i).clone() + linestring.close() + p.setExteriorRing(linestring) + parts.append(QgsGeometry(p)) + return QgsGeometry.collectGeometry(parts) + else: + # linestring to polygon + p = QgsPolygonV2() + linestring = geom.geometry().clone() + linestring.close() + p.setExteriorRing(linestring) + return [QgsGeometry(p)] + else: + #polygon + if QgsWkbTypes.isMultiType(geom): + return geom.asGeometryCollection() + else: + return [geom] diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 3f9e953b177b..0f2621d936b1 100644 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -53,6 +53,7 @@ from .CreateAttributeIndex import CreateAttributeIndex from .CreateConstantRaster import CreateConstantRaster from .Datasources2Vrt import Datasources2Vrt +from .DefineProjection import DefineProjection from .Delaunay import Delaunay from .DeleteColumn import DeleteColumn from .DeleteDuplicateGeometries import DeleteDuplicateGeometries @@ -64,15 +65,20 @@ from .DropMZValues import DropMZValues from .EliminateSelection import EliminateSelection from .EquivalentNumField import EquivalentNumField +from .ExecuteSQL import ExecuteSQL from .Explode import Explode from .ExportGeometryInfo import ExportGeometryInfo from .ExtendLines import ExtendLines from .ExtentFromLayer import ExtentFromLayer from .ExtractNodes import ExtractNodes from .ExtractSpecificNodes import ExtractSpecificNodes +from .FieldPyculator import FieldsPyculator +from .FieldsCalculator import FieldsCalculator from .FieldsMapper import FieldsMapper +from .FindProjection import FindProjection from .FixedDistanceBuffer import FixedDistanceBuffer from .FixGeometry import FixGeometry +from .GeometryConvert import GeometryConvert from .GeometryByExpression import GeometryByExpression from .Gridify import Gridify from .GridLine import GridLine @@ -119,6 +125,7 @@ from .RandomSelection import RandomSelection from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets from .Rasterize import RasterizeAlgorithm +from .RasterCalculator import RasterCalculator from .RasterLayerStatistics import RasterLayerStatistics from .RectanglesOvalsDiamondsFixed import RectanglesOvalsDiamondsFixed from .RectanglesOvalsDiamondsVariable import RectanglesOvalsDiamondsVariable @@ -165,14 +172,7 @@ # from .ExtractByLocation import ExtractByLocation # 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 -# from .RasterCalculator import RasterCalculator -# from .ExecuteSQL import ExecuteSQL -# from .FindProjection import FindProjection pluginPath = os.path.normpath(os.path.join( os.path.split(os.path.dirname(__file__))[0], os.pardir)) @@ -190,12 +190,7 @@ def getAlgs(self): # SelectByLocation(), # ExtractByLocation(), # SpatialJoin(), - # GeometryConvert(), FieldsCalculator(), - # FieldsPyculator(), - # FieldsMapper(), SelectByAttributeSum() - # DefineProjection(), - # RasterCalculator(), - # ExecuteSQL(), FindProjection(), + # SelectByAttributeSum() # ] algs = [AddTableField(), Aggregate(), @@ -210,6 +205,7 @@ def getAlgs(self): CreateAttributeIndex(), CreateConstantRaster(), Datasources2Vrt(), + DefineProjection(), Delaunay(), DeleteColumn(), DeleteDuplicateGeometries(), @@ -221,16 +217,21 @@ def getAlgs(self): DropMZValues(), EliminateSelection(), EquivalentNumField(), + ExecuteSQL(), Explode(), ExportGeometryInfo(), ExtendLines(), ExtentFromLayer(), ExtractNodes(), ExtractSpecificNodes(), + FieldsCalculator(), FieldsMapper(), + FieldsPyculator(), + FindProjection(), FixedDistanceBuffer(), FixGeometry(), GeometryByExpression(), + GeometryConvert(), Gridify(), GridLine(), GridPolygon(), @@ -275,6 +276,7 @@ def getAlgs(self): RandomPointsPolygons(), RandomSelection(), RandomSelectionWithinSubsets(), + RasterCalculator(), RasterizeAlgorithm(), RasterLayerStatistics(), RectanglesOvalsDiamondsFixed(), diff --git a/python/plugins/processing/algs/qgis/RasterCalculator.py b/python/plugins/processing/algs/qgis/RasterCalculator.py index 87a0b31e3f30..6944729e5006 100644 --- a/python/plugins/processing/algs/qgis/RasterCalculator.py +++ b/python/plugins/processing/algs/qgis/RasterCalculator.py @@ -16,7 +16,7 @@ * * *************************************************************************** """ -from processing.modeler.ModelerAlgorithm import ValueFromInput, ValueFromOutput + import os __author__ = 'Victor Olaya' @@ -29,17 +29,18 @@ import math from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterMultipleInput, ParameterExtent, ParameterString, ParameterRaster, ParameterNumber -from processing.core.outputs import OutputRaster -from processing.tools import dataobjects from processing.algs.gdal.GdalUtils import GdalUtils -from qgis.core import (QgsApplication, - QgsRectangle, +from qgis.core import (QgsProcessing, + QgsProcessingException, QgsProcessingUtils, - QgsProject) + QgsProcessingParameterMultipleLayers, + QgsProcessingParameterNumber, + QgsProcessingParameterExtent, + QgsProcessingParameterRasterDestination, + QgsProcessingParameterRasterLayer, + QgsProcessingOutputRasterLayer, + QgsProcessingParameterString) from qgis.analysis import QgsRasterCalculator, QgsRasterCalculatorEntry -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.algs.qgis.ui.RasterCalculatorWidgets import LayersListWidgetWrapper, ExpressionWidgetWrapper class RasterCalculator(QgisAlgorithm): @@ -57,25 +58,38 @@ def __init__(self): super().__init__() def initAlgorithm(self, config=None): - self.addParameter(ParameterMultipleInput(self.LAYERS, - self.tr('Input layers'), - datatype=dataobjects.TYPE_RASTER, - optional=True, - metadata={'widget_wrapper': LayersListWidgetWrapper})) + layer_param = QgsProcessingParameterMultipleLayers(self.LAYERS, + self.tr('Input layers'), + layerType=QgsProcessing.TypeRaster, + optional=True) + layer_param.setMetadata({'widget_wrapper': 'processing.algs.qgis.ui.RasterCalculatorWidgets.LayersListWidgetWrapper'}) + self.addParameter(layer_param) + + class ParameterRasterCalculatorExpression(QgsProcessingParameterString): + + def __init__(self, name='', description='', multiLine=False): + super().__init__(name, description, multiLine=multiLine) + self.setMetadata({ + 'widget_wrapper': 'processing.algs.qgis.ui.RasterCalculatorWidgets.ExpressionWidgetWrapper' + }) - class ParameterRasterCalculatorExpression(ParameterString): + def type(self): + return 'raster_calc_expression' + + def clone(self): + return ParameterRasterCalculatorExpression(self.name(), self.description(), self.multiLine()) def evaluateForModeler(self, value, model): for i in list(model.inputs.values()): param = i.param - if isinstance(param, ParameterRaster): + if isinstance(param, QgsProcessingParameterRasterLayer): new = "{}@".format(os.path.basename(param.value)) old = "{}@".format(param.name()) value = value.replace(old, new) for alg in list(model.algs.values()): for out in alg.algorithm.outputs: - if isinstance(out, OutputRaster): + if isinstance(out, QgsProcessingOutputRasterLayer): if out.value: new = "{}@".format(os.path.basename(out.value)) old = "{}:{}@".format(alg.modeler_name, out.name) @@ -83,15 +97,15 @@ def evaluateForModeler(self, value, model): return value self.addParameter(ParameterRasterCalculatorExpression(self.EXPRESSION, self.tr('Expression'), - multiline=True, - metadata={'widget_wrapper': ExpressionWidgetWrapper})) - self.addParameter(ParameterNumber(self.CELLSIZE, - self.tr('Cell size (use 0 or empty to set it automatically)'), - minValue=0.0, default=0.0, optional=True)) - self.addParameter(ParameterExtent(self.EXTENT, - self.tr('Output extent'), - optional=True)) - self.addOutput(OutputRaster(self.OUTPUT, self.tr('Output'))) + multiLine=True)) + self.addParameter(QgsProcessingParameterNumber(self.CELLSIZE, + self.tr('Cell size (use 0 or empty to set it automatically)'), + type=QgsProcessingParameterNumber.Double, + minValue=0.0, defaultValue=0.0, optional=True)) + self.addParameter(QgsProcessingParameterExtent(self.EXTENT, + self.tr('Output extent'), + optional=True)) + self.addParameter(QgsProcessingParameterRasterDestination(self.OUTPUT, self.tr('Output'))) def name(self): return 'rastercalculator' @@ -100,11 +114,10 @@ def displayName(self): return self.tr('Raster calculator') def processAlgorithm(self, parameters, context, feedback): - expression = self.getParameterValue(self.EXPRESSION) - layersValue = self.getParameterValue(self.LAYERS) + expression = self.parameterAsString(parameters, self.EXPRESSION, context) + layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layersDict = {} - if layersValue: - layers = [QgsProcessingUtils.mapLayerFromString(f, context) for f in layersValue.split(";")] + if layers: layersDict = {os.path.basename(lyr.source().split(".")[0]): lyr for lyr in layers} for lyr in QgsProcessingUtils.compatibleRasterLayers(context.project()): @@ -121,26 +134,22 @@ def processAlgorithm(self, parameters, context, feedback): entry.bandNumber = n + 1 entries.append(entry) - output = self.getOutputValue(self.OUTPUT) - extentValue = self.getParameterValue(self.EXTENT) - if not extentValue: - extentValue = QgsProcessingUtils.combineLayerExtents(layersValue) + output = self.parameterAsOutputLayer(parameters, self.OUTPUT, context) + bbox = self.parameterAsExtent(parameters, self.EXTENT, context) + if bbox.isNull(): + bbox = QgsProcessingUtils.combineLayerExtents(layers) - if extentValue: - extent = extentValue.split(',') - bbox = QgsRectangle(float(extent[0]), float(extent[2]), - float(extent[1]), float(extent[3])) - else: + if bbox.isNull(): if layersDict: bbox = list(layersDict.values())[0].extent() for lyr in layersDict.values(): bbox.combineExtentWith(lyr.extent()) else: - raise GeoAlgorithmExecutionException(self.tr("No layers selected")) + raise QgsProcessingException(self.tr("No layers selected")) def _cellsize(layer): return (layer.extent().xMaximum() - layer.extent().xMinimum()) / layer.width() - cellsize = self.getParameterValue(self.CELLSIZE) or min([_cellsize(lyr) for lyr in layersDict.values()]) + cellsize = self.parameterAsDouble(parameters, self.CELLSIZE, context) or min([_cellsize(lyr) for lyr in layersDict.values()]) width = math.floor((bbox.xMaximum() - bbox.xMinimum()) / cellsize) height = math.floor((bbox.yMaximum() - bbox.yMinimum()) / cellsize) driverName = GdalUtils.getFormatShortNameFromFilename(output) @@ -154,14 +163,16 @@ def _cellsize(layer): res = calc.processCalculation() if res == QgsRasterCalculator.ParserError: - raise GeoAlgorithmExecutionException(self.tr("Error parsing formula")) + raise QgsProcessingException(self.tr("Error parsing formula")) + + return {self.OUTPUT: output} def processBeforeAddingToModeler(self, algorithm, model): values = [] expression = algorithm.params[self.EXPRESSION] for i in list(model.inputs.values()): param = i.param - if isinstance(param, ParameterRaster) and "{}@".format(param.name) in expression: + if isinstance(param, QgsProcessingParameterRasterLayer) and "{}@".format(param.name) in expression: values.append(ValueFromInput(param.name())) if algorithm.name: @@ -171,7 +182,7 @@ def processBeforeAddingToModeler(self, algorithm, model): for alg in list(model.algs.values()): if alg.modeler_name not in dependent: for out in alg.algorithm.outputs: - if (isinstance(out, OutputRaster) and + if (isinstance(out, QgsProcessingOutputRasterLayer) and "{}:{}@".format(alg.modeler_name, out.name) in expression): values.append(ValueFromOutput(alg.modeler_name, out.name)) diff --git a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py index a43b3a4cb498..a17f2bfa5f4d 100644 --- a/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py +++ b/python/plugins/processing/algs/qgis/ui/FieldsCalculatorDialog.py @@ -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, diff --git a/python/plugins/processing/gui/TestTools.py b/python/plugins/processing/gui/TestTools.py index 4d986d7ea3de..1d8bf7cdd10f 100644 --- a/python/plugins/processing/gui/TestTools.py +++ b/python/plugins/processing/gui/TestTools.py @@ -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 @@ -152,6 +153,9 @@ def createTest(text): if param.flags() & QgsProcessingParameterDefinition.FlagHidden or param.isDestination(): continue + if not param.name() in parameters: + continue + i += 1 token = parameters[param.name()] # Handle empty parameters that are optionals @@ -179,7 +183,7 @@ def createTest(text): params[param.name()] = p elif isinstance(param, QgsProcessingParameterMultipleLayers): - multiparams = token.split(';') + multiparams = token newparam = [] # Handle datatype detection @@ -223,6 +227,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:] diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gfs b/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gfs index 0e86d87d8dc7..b8342ff5b413 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gfs +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gfs @@ -6,7 +6,7 @@ 1 EPSG:4326 - 5 + 6 0.65385 8.00000 -1.00000 diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gml b/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gml index e6e316c98819..8bbbe087f943 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gml +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_centroid.gml @@ -41,6 +41,12 @@ 0 + + + 120 + -100291.43213 + + 4.08045977011494,-0.218390804597701 diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gfs b/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gfs index 5f351afd3968..ead068d29117 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gfs +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gfs @@ -6,7 +6,7 @@ 5 EPSG:4326 - 5 + 6 -1.00000 10.00000 -3.00000 diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gml b/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gml index 644fda625a27..91dcf63b231c 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gml +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_multiline.gml @@ -41,6 +41,12 @@ 0 + + + 120 + -100291.43213 + + 3,2 6,1 6,-3 2,-1 2,2 3,2 diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gfs b/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gfs index 0afc6f5fb200..089512fb3c28 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gfs +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gfs @@ -2,11 +2,11 @@ convert_poly_nodes convert_poly_nodes - - 1 + + 4 EPSG:4326 - 32 + 6 -1.00000 10.00000 -3.00000 diff --git a/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gml b/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gml index 98c0a47817b6..c092423a4002 100644 --- a/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gml +++ b/python/plugins/processing/tests/testdata/expected/convert_poly_nodes.gml @@ -13,55 +13,7 @@ - -1,-1 - aaaaa - 33 - 44.123456 - - - - - -1,3 - aaaaa - 33 - 44.123456 - - - - - 3,3 - aaaaa - 33 - 44.123456 - - - - - 3,2 - aaaaa - 33 - 44.123456 - - - - - 2,2 - aaaaa - 33 - 44.123456 - - - - - 2,-1 - aaaaa - 33 - 44.123456 - - - - - -1,-1 + -1,-1-1,33,33,22,22,-1-1,-1 aaaaa 33 44.123456 @@ -69,184 +21,35 @@ - 5,5 + 5,56,44,45,5 Aaaaa -33 0 - - - 6,4 - Aaaaa - -33 - 0 - - - - - 4,4 - Aaaaa - -33 - 0 - - - - - 5,5 - Aaaaa - -33 - 0 - - - - - 2,5 - bbaaa - 0.123 - - - - - 2,6 - bbaaa - 0.123 - - - - - 3,6 - bbaaa - 0.123 - - - - - 3,5 - bbaaa - 0.123 - - - 2,5 + 2,52,63,63,52,5 bbaaa 0.123 - 6,1 + 6,110,110,-36,-36,17,07,-29,-29,07,0 ASDF 0 - - 10,1 - ASDF - 0 - - - - - 10,-3 - ASDF - 0 - - - - - 6,-3 - ASDF - 0 - - - - - 6,1 - ASDF - 0 - - - - - 7,0 - ASDF - 0 - - - - - 7,-2 - ASDF - 0 - - - - - 9,-2 - ASDF - 0 - - - - - 9,0 - ASDF - 0 - - - - - 7,0 - ASDF - 0 - - - - - 3,2 - elim - 2 - 3.33 - - - - - 6,1 - elim - 2 - 3.33 - - - - - 6,-3 - elim - 2 - 3.33 - - - - - 2,-1 - elim - 2 - 3.33 - - - - - 2,2 - elim - 2 - 3.33 + + 120 + -100291.43213 - 3,2 + 3,26,16,-32,-12,23,2 elim 2 3.33 diff --git a/python/plugins/processing/tests/testdata/expected/execute_sql.gfs b/python/plugins/processing/tests/testdata/expected/execute_sql.gfs new file mode 100644 index 000000000000..30e19534a91f --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/execute_sql.gfs @@ -0,0 +1,26 @@ + + + execute_sql + execute_sql + + 1 + EPSG:4326 + + 2 + 1.00000 + 5.00000 + 1.00000 + 2.00000 + + + id + id + Integer + + + id2 + id2 + Integer + + + diff --git a/python/plugins/processing/tests/testdata/expected/execute_sql.gml b/python/plugins/processing/tests/testdata/expected/execute_sql.gml new file mode 100644 index 000000000000..053470e6a703 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/execute_sql.gml @@ -0,0 +1,28 @@ + + + + + 11 + 52 + + + + + + 1,1 + 1 + 2 + + + + + 5,2 + 4 + 2 + + + diff --git a/python/plugins/processing/tests/testdata/expected/field_calculator_points.gfs b/python/plugins/processing/tests/testdata/expected/field_calculator_points.gfs new file mode 100644 index 000000000000..a1baefadabe6 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/field_calculator_points.gfs @@ -0,0 +1,31 @@ + + + field_calculator_points + field_calculator_points + + 1 + EPSG:4326 + + 9 + 0.00000 + 8.00000 + -5.00000 + 3.00000 + + + id + id + Integer + + + id2 + id2 + Integer + + + test + test + Integer + + + diff --git a/python/plugins/processing/tests/testdata/expected/field_calculator_points.gml b/python/plugins/processing/tests/testdata/expected/field_calculator_points.gml new file mode 100644 index 000000000000..1f008e02c313 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/field_calculator_points.gml @@ -0,0 +1,86 @@ + + + + + 0-5 + 83 + + + + + + 1,1 + 1 + 2 + 4 + + + + + 3,3 + 2 + 1 + 2 + + + + + 2,2 + 3 + 0 + 0 + + + + + 5,2 + 4 + 2 + 4 + + + + + 4,1 + 5 + 1 + 2 + + + + + 0,-5 + 6 + 0 + 0 + + + + + 8,-1 + 7 + 0 + 0 + + + + + 7,-1 + 8 + 0 + 0 + + + + + 0,-1 + 9 + 0 + 0 + + + diff --git a/python/plugins/processing/tests/testdata/expected/projection_candidates.gfs b/python/plugins/processing/tests/testdata/expected/projection_candidates.gfs new file mode 100644 index 000000000000..5dd5db8b754a --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/projection_candidates.gfs @@ -0,0 +1,16 @@ + + + projection_candidates + projection_candidates + 100 + + 8 + + + auth_id + auth_id + String + 14 + + + diff --git a/python/plugins/processing/tests/testdata/expected/projection_candidates.gml b/python/plugins/processing/tests/testdata/expected/projection_candidates.gml new file mode 100644 index 000000000000..f46f2f4b864f --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/projection_candidates.gml @@ -0,0 +1,49 @@ + + + missing + + + + EPSG:20256 + + + + + EPSG:20356 + + + + + EPSG:28356 + + + + + EPSG:32356 + + + + + EPSG:32556 + + + + + EPSG:32756 + + + + + IGNF:UTM56SW84 + + + + + EPSG:5552 + + + diff --git a/python/plugins/processing/tests/testdata/expected/pycalculator_points.gfs b/python/plugins/processing/tests/testdata/expected/pycalculator_points.gfs new file mode 100644 index 000000000000..3b46eed3ef52 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/pycalculator_points.gfs @@ -0,0 +1,31 @@ + + + pycalculator_points + pycalculator_points + + 1 + EPSG:4326 + + 9 + 0.00000 + 8.00000 + -5.00000 + 3.00000 + + + id + id + Integer + + + id2 + id2 + Integer + + + new_field + new_field + Integer + + + diff --git a/python/plugins/processing/tests/testdata/expected/pycalculator_points.gml b/python/plugins/processing/tests/testdata/expected/pycalculator_points.gml new file mode 100644 index 000000000000..d1a7caadaa72 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/pycalculator_points.gml @@ -0,0 +1,86 @@ + + + + + 0-5 + 83 + + + + + + 1,1 + 1 + 2 + 4 + + + + + 3,3 + 2 + 1 + 2 + + + + + 2,2 + 3 + 0 + 0 + + + + + 5,2 + 4 + 2 + 4 + + + + + 4,1 + 5 + 1 + 2 + + + + + 0,-5 + 6 + 0 + 0 + + + + + 8,-1 + 7 + 0 + 0 + + + + + 7,-1 + 8 + 0 + 0 + + + + + 0,-1 + 9 + 0 + 0 + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 8fa3bf5dd9ec..aed4606b5619 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2210,35 +2210,35 @@ tests: - 'Maximum value: 15:29:22' - 'NULL \(missing\) values: 1' -# - algorithm: qgis:rastercalculator -# name: Raster Calculator with cellsize -# params: -# LAYERS: -# params: -# - name: dem.tif -# type: raster -# type: multi -# CELLSIZE: 0.001 -# EXPRESSION: dem@1 -# results: -# OUTPUT: -# hash: ef97a22ee16e0e28bbdc0341449777b1527e37febc3c4339b2c057c9 -# type: rasterhash -# -# - algorithm: qgis:rastercalculator -# name: Raster Calculator -# params: -# LAYERS: -# params: -# - name: dem.tif -# type: raster -# type: multi -# CELLSIZE: 0.0 -# EXPRESSION: dem@1 * 2 -# results: -# OUTPUT: -# hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939 -# type: rasterhash + - algorithm: qgis:rastercalculator + name: Raster Calculator with cellsize + params: + LAYERS: + params: + - name: dem.tif + type: raster + type: multi + CELLSIZE: 0.001 + EXPRESSION: dem@1 + results: + OUTPUT: + hash: ef97a22ee16e0e28bbdc0341449777b1527e37febc3c4339b2c057c9 + type: rasterhash + + - algorithm: qgis:rastercalculator + name: Raster Calculator + params: + LAYERS: + params: + - name: dem.tif + type: raster + type: multi + CELLSIZE: 0.0 + EXPRESSION: dem@1 * 2 + results: + OUTPUT: + hash: fe6e018be13c5a3c17f3f4d0f0dc7686c628cb440b74c4642aa0c939 + type: rasterhash - algorithm: qgis:orientedminimumboundingbox name: Oriented minimum bounding box polys @@ -2827,19 +2827,19 @@ tests: fields: fid: skip -# - algorithm: qgis:findprojection -# name: Find projection -# params: -# INPUT_LAYER: -# name: custom/find_projection.gml -# type: vector -# TARGET_AREA: 151.1198,151.1368,-33.9118,-33.9003 -# TARGET_AREA_CRS: EPSG:4326 -# results: -# OUTPUT_HTML_FILE: -# name: expected/find_projection.html -# type: file -# + - algorithm: qgis:findprojection + name: Find projection + params: + INPUT: + name: custom/find_projection.gml + type: vector + TARGET_AREA: 151.1198,151.1368,-33.9118,-33.9003 + TARGET_AREA_CRS: EPSG:4326 + results: + OUTPUT: + name: expected/projection_candidates.gml + type: vector + - algorithm: qgis:polygonfromlayerextent name: Standard polygon from layer extent params: @@ -2957,42 +2957,42 @@ tests: name: expected/create_grid_lines.gml type: vector -# - algorithm: qgis:convertgeometrytype -# name: polygon to centroid -# params: -# INPUT: -# name: polys.gml -# type: vector -# TYPE: '0' -# results: -# OUTPUT: -# name: expected/convert_poly_centroid.gml -# type: vector -# -# - algorithm: qgis:convertgeometrytype -# name: polygon to multilinestring -# params: -# INPUT: -# name: polys.gml -# type: vector -# TYPE: '3' -# results: -# OUTPUT: -# name: expected/convert_poly_multiline.gml -# type: vector -# -# - algorithm: qgis:convertgeometrytype -# name: polygon to nodes -# params: -# INPUT: -# name: polys.gml -# type: vector -# TYPE: '1' -# results: -# OUTPUT: -# name: expected/convert_poly_nodes.gml -# type: vector -# + - algorithm: qgis:convertgeometrytype + name: polygon to centroid + params: + INPUT: + name: polys.gml + type: vector + TYPE: '0' + results: + OUTPUT: + name: expected/convert_poly_centroid.gml + type: vector + + - algorithm: qgis:convertgeometrytype + name: polygon to multilinestring + params: + INPUT: + name: polys.gml + type: vector + TYPE: '3' + results: + OUTPUT: + name: expected/convert_poly_multiline.gml + type: vector + + - algorithm: qgis:convertgeometrytype + name: polygon to nodes + params: + INPUT: + name: polys.gml + type: vector + TYPE: '1' + results: + OUTPUT: + name: expected/convert_poly_nodes.gml + type: vector + # # - algorithm: qgis:extractbylocation # # name: polygon intersecting points # # params: @@ -3176,3 +3176,54 @@ tests: OUTPUT: name: expected/pixel_centroids_lines.gml type: vector + + - algorithm: qgis:fieldcalculator + name: Test field calculator points + params: + FIELD_LENGTH: 10 + FIELD_NAME: test + FIELD_PRECISION: 3 + FIELD_TYPE: 1 + FORMULA: ' "id2" *2' + INPUT: + name: points.gml + type: vector + NEW_FIELD: true + results: + OUTPUT: + name: expected/field_calculator_points.gml + type: vector + + - algorithm: qgis:advancedpythonfieldcalculator + name: Test advanced python calculator + params: + FIELD_LENGTH: 10 + FIELD_NAME: new_field + FIELD_PRECISION: 3 + FIELD_TYPE: 0 + FORMULA: value = __attr[2]*2 + GLOBAL: '' + INPUT: + name: points.gml + type: vector + results: + OUTPUT: + name: expected/pycalculator_points.gml + type: vector + + - algorithm: qgis:executesql + name: Test execute SQL + params: + INPUT_DATASOURCES: + params: + - name: points.gml + type: vector + type: multi + INPUT_GEOMETRY_FIELD: '' + INPUT_GEOMETRY_TYPE: 0 + INPUT_QUERY: select * from input1 where id2=2 + INPUT_UID_FIELD: '' + results: + OUTPUT: + name: expected/execute_sql.gml + type: vector diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index d9bb6361e0cd..15eac0250bda 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -2565,6 +2565,10 @@ QString QgsProcessingParameterFeatureSource::valueAsPythonString( const QVariant } } } + else if ( QgsVectorLayer *layer = qobject_cast< QgsVectorLayer * >( qvariant_cast( value ) ) ) + { + return layer->source().prepend( '\'' ).append( '\'' ); + } return value.toString().prepend( '\'' ).append( '\'' ); } diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 7ea91298df02..5772677c27ea 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -3442,6 +3442,7 @@ void TestQgsProcessing::parameterFeatureSource() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromValue( "abc" ), true ) ), context ), QStringLiteral( "QgsProcessingFeatureSourceDefinition('abc', True)" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingFeatureSourceDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"abc\" || \"def\"')" ) ); QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v2 ), context ), QStringLiteral( "'%1'" ).arg( vector2 ) ); QVariantMap map = def->toVariantMap(); QgsProcessingParameterFeatureSource fromMap( "x" );