diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip index edf161ae970..247b5147101 100644 --- a/python/core/processing/qgsprocessingparameters.sip +++ b/python/core/processing/qgsprocessingparameters.sip @@ -163,14 +163,16 @@ class QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterString; else if ( sipCpp->type() == "expression" ) sipType = sipType_QgsProcessingParameterExpression; - else if ( sipCpp->type() == "table" ) - sipType = sipType_QgsProcessingParameterTable; + else if ( sipCpp->type() == "vector" ) + sipType = sipType_QgsProcessingParameterVectorLayer; else if ( sipCpp->type() == "field" ) - sipType = sipType_QgsProcessingParameterTableField; + sipType = sipType_QgsProcessingParameterField; else if ( sipCpp->type() == "source" ) sipType = sipType_QgsProcessingParameterFeatureSource; else if ( sipCpp->type() == "sink" ) sipType = sipType_QgsProcessingParameterFeatureSink; + else if ( sipCpp->type() == "vectorOut" ) + sipType = sipType_QgsProcessingParameterVectorOutput; else if ( sipCpp->type() == "rasterOut" ) sipType = sipType_QgsProcessingParameterRasterOutput; else if ( sipCpp->type() == "fileOut" ) @@ -1153,10 +1155,11 @@ class QgsProcessingParameterExpression : QgsProcessingParameterDefinition }; -class QgsProcessingParameterTable : QgsProcessingParameterDefinition +class QgsProcessingParameterVectorLayer : QgsProcessingParameterDefinition { %Docstring - A table (i.e. vector layers with or without geometry) parameter for processing algorithms. + A vector layer (with or without geometry) parameter for processing algorithms. Consider using + the more versatile QgsProcessingParameterFeatureSource wherever possible. .. versionadded:: 3.0 %End @@ -1165,20 +1168,24 @@ class QgsProcessingParameterTable : QgsProcessingParameterDefinition %End public: - QgsProcessingParameterTable( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), - bool optional = false ); + QgsProcessingParameterVectorLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + bool optional = false ); %Docstring - Constructor for QgsProcessingParameterTable. + Constructor for QgsProcessingParameterVectorLayer. %End virtual QString type() const; + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + }; -class QgsProcessingParameterTableField : QgsProcessingParameterDefinition +class QgsProcessingParameterField : QgsProcessingParameterDefinition { %Docstring - A table field parameter for processing algorithms. + A vector layer or feature source field parameter for processing algorithms. .. versionadded:: 3.0 %End @@ -1195,13 +1202,13 @@ class QgsProcessingParameterTableField : QgsProcessingParameterDefinition DateTime }; - QgsProcessingParameterTableField( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), - const QString &parentLayerParameterName = QString(), - DataType type = Any, - bool allowMultiple = false, - bool optional = false ); + QgsProcessingParameterField( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + const QString &parentLayerParameterName = QString(), + DataType type = Any, + bool allowMultiple = false, + bool optional = false ); %Docstring - Constructor for QgsProcessingParameterTableField. + Constructor for QgsProcessingParameterField. %End virtual QString type() const; @@ -1354,6 +1361,60 @@ class QgsProcessingParameterFeatureSink : QgsProcessingParameterDefinition virtual bool fromVariantMap( const QVariantMap &map ); +}; + + +class QgsProcessingParameterVectorOutput : QgsProcessingParameterDefinition +{ +%Docstring + A vector layer output parameter. Consider using the more flexible QgsProcessingParameterFeatureSink wherever + possible. +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsprocessingparameters.h" +%End + public: + + QgsProcessingParameterVectorOutput( const QString &name, const QString &description = QString(), QgsProcessingParameterDefinition::LayerType type = QgsProcessingParameterDefinition::TypeVectorAny, const QVariant &defaultValue = QVariant(), + bool optional = false ); +%Docstring + Constructor for QgsProcessingParameterVectorOutput. +%End + + virtual QString type() const; + virtual bool isDestination() const; + virtual bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = 0 ) const; + + virtual QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const; + + + QgsProcessingParameterDefinition::LayerType dataType() const; +%Docstring + Returns the layer type for layers associated with the parameter. +.. seealso:: setDataType() + :rtype: QgsProcessingParameterDefinition.LayerType +%End + + bool hasGeometry() const; +%Docstring + Returns true if the layer is likely to include geometries. In cases were presence of geometry + cannot be reliably determined in advance, this method will default to returning true. + :rtype: bool +%End + + void setDataType( QgsProcessingParameterDefinition::LayerType type ); +%Docstring + Sets the layer ``type`` for the layers associated with the parameter. +.. seealso:: dataType() +%End + + virtual QVariantMap toVariantMap() const; + + virtual bool fromVariantMap( const QVariantMap &map ); + + }; class QgsProcessingParameterRasterOutput : QgsProcessingParameterDefinition diff --git a/python/plugins/processing/algs/qgis/BarPlot.py b/python/plugins/processing/algs/qgis/BarPlot.py index f4d6f6098f4..70ff00275f3 100644 --- a/python/plugins/processing/algs/qgis/BarPlot.py +++ b/python/plugins/processing/algs/qgis/BarPlot.py @@ -32,7 +32,7 @@ import plotly.graph_objs as go from qgis.core import (QgsApplication, QgsProcessingUtils, QgsProcessingParameterFeatureSource, - QgsProcessingParameterTableField, + QgsProcessingParameterField, QgsProcessingParameterFileOutput, QgsProcessingOutputHtml) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -60,12 +60,12 @@ class BarPlot(QgisAlgorithm): self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterTableField(self.NAME_FIELD, - self.tr('Category name field'), - None, self.INPUT, QgsProcessingParameterTableField.Any)) - self.addParameter(QgsProcessingParameterTableField(self.VALUE_FIELD, - self.tr('Value field'), - None, self.INPUT, QgsProcessingParameterTableField.Numeric)) + self.addParameter(QgsProcessingParameterField(self.NAME_FIELD, + self.tr('Category name field'), + None, self.INPUT, QgsProcessingParameterField.Any)) + self.addParameter(QgsProcessingParameterField(self.VALUE_FIELD, + self.tr('Value field'), + None, self.INPUT, QgsProcessingParameterField.Numeric)) self.addParameter(QgsProcessingParameterFileOutput(self.OUTPUT, self.tr('Added'), self.tr('HTML files (*.html)'))) diff --git a/python/plugins/processing/algs/qgis/BasicStatistics.py b/python/plugins/processing/algs/qgis/BasicStatistics.py index 645dc4f259a..cfc579bc853 100644 --- a/python/plugins/processing/algs/qgis/BasicStatistics.py +++ b/python/plugins/processing/algs/qgis/BasicStatistics.py @@ -37,7 +37,7 @@ from qgis.core import (QgsStatisticalSummary, QgsFeatureRequest, QgsProcessingUtils, QgsProcessingParameterFeatureSource, - QgsProcessingParameterTableField, + QgsProcessingParameterField, QgsProcessingParameterFileOutput, QgsProcessingOutputHtml, QgsProcessingOutputNumber) @@ -90,9 +90,9 @@ class BasicStatisticsForField(QgisAlgorithm): self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterTableField(self.FIELD_NAME, - self.tr('Field to calculate statistics on'), - None, self.INPUT_LAYER, QgsProcessingParameterTableField.Any)) + self.addParameter(QgsProcessingParameterField(self.FIELD_NAME, + self.tr('Field to calculate statistics on'), + None, self.INPUT_LAYER, QgsProcessingParameterField.Any)) self.addParameter(QgsProcessingParameterFileOutput(self.OUTPUT_HTML_FILE, self.tr('Statistics'), self.tr('HTML files (*.html)'), None, True)) self.addOutput(QgsProcessingOutputHtml(self.OUTPUT_HTML_FILE, self.tr('Statistics'))) diff --git a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py index 80b9dfffaba..4f7d320ddcd 100644 --- a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py +++ b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py @@ -28,12 +28,12 @@ __revision__ = '$Format:%H$' from qgis.core import (QgsVectorDataProvider, QgsFields, QgsApplication, - QgsProcessingUtils) + QgsProcessingParameterVectorLayer, + QgsProcessingParameterField, + QgsProcessingParameterDefinition, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterTable -from processing.core.parameters import ParameterTableField -from processing.core.outputs import OutputVector class CreateAttributeIndex(QgisAlgorithm): @@ -53,12 +53,11 @@ class CreateAttributeIndex(QgisAlgorithm): def __init__(self): super().__init__() - self.addParameter(ParameterTable(self.INPUT, - self.tr('Input Layer'))) - self.addParameter(ParameterTableField(self.FIELD, - self.tr('Attribute to index'), self.INPUT)) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Indexed layer'), True)) + self.addParameter(QgsProcessingParameterVectorLayer(self.INPUT, + self.tr('Input Layer'))) + self.addParameter(QgsProcessingParameterField(self.FIELD, + self.tr('Attribute to index'), None, self.INPUT)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Indexed layer'))) def name(self): return 'createattributeindex' @@ -67,9 +66,8 @@ class CreateAttributeIndex(QgisAlgorithm): return self.tr('Create attribute index') def processAlgorithm(self, parameters, context, feedback): - file_name = self.getParameterValue(self.INPUT) - layer = QgsProcessingUtils.mapLayerFromString(file_name, context) - field = self.getParameterValue(self.FIELD) + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + field = self.parameterAsString(parameters, self.FIELD, context) provider = layer.dataProvider() field_index = layer.fields().lookupField(field) @@ -84,4 +82,4 @@ class CreateAttributeIndex(QgisAlgorithm): feedback.pushInfo(self.tr("Layer's data provider does not support " "creating attribute indexes")) - self.setOutputValue(self.OUTPUT, file_name) + return {self.OUTPUT: layer.id()} diff --git a/python/plugins/processing/algs/qgis/DeleteColumn.py b/python/plugins/processing/algs/qgis/DeleteColumn.py index 49f93028d2b..5056e200ef7 100644 --- a/python/plugins/processing/algs/qgis/DeleteColumn.py +++ b/python/plugins/processing/algs/qgis/DeleteColumn.py @@ -29,7 +29,7 @@ from qgis.core import (QgsApplication, QgsProcessingUtils, QgsProcessingParameterFeatureSource, QgsProcessingParameterFeatureSink, - QgsProcessingParameterTableField, + QgsProcessingParameterField, QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm @@ -56,9 +56,9 @@ class DeleteColumn(QgisAlgorithm): super().__init__() self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterTableField(self.COLUMNS, - self.tr('Fields to delete'), - None, self.INPUT, QgsProcessingParameterTableField.Any, True)) + self.addParameter(QgsProcessingParameterField(self.COLUMNS, + self.tr('Fields to delete'), + None, self.INPUT, QgsProcessingParameterField.Any, True)) self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Output layer'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Output layer"))) diff --git a/python/plugins/processing/algs/qgis/DeleteHoles.py b/python/plugins/processing/algs/qgis/DeleteHoles.py index 1d994851d3a..1aecf4f5324 100644 --- a/python/plugins/processing/algs/qgis/DeleteHoles.py +++ b/python/plugins/processing/algs/qgis/DeleteHoles.py @@ -25,12 +25,13 @@ __copyright__ = '(C) 2015, Etienne Trimaille' __revision__ = '$Format:%H$' from qgis.core import (QgsApplication, - QgsProcessingUtils) + QgsProcessingUtils, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import (ParameterVector, - ParameterNumber) -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class DeleteHoles(QgisAlgorithm): @@ -53,12 +54,14 @@ class DeleteHoles(QgisAlgorithm): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POLYGON])) - self.addParameter(ParameterNumber(self.MIN_AREA, - self.tr('Remove holes with area less than'), 0, 10000000.0, default=0.0, optional=True)) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterNumber(self.MIN_AREA, + self.tr('Remove holes with area less than'), QgsProcessingParameterNumber.Double, + 0, True, 0.0, 10000000.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Cleaned'), datatype=[dataobjects.TYPE_VECTOR_POLYGON])) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) def name(self): return 'deleteholes' @@ -67,29 +70,24 @@ class DeleteHoles(QgisAlgorithm): return self.tr('Delete holes') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - min_area = self.getParameterValue(self.MIN_AREA) - if min_area is not None: - try: - min_area = float(min_area) - except: - pass + source = self.parameterAsSource(parameters, self.INPUT, context) + min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) if min_area == 0.0: min_area = -1.0 - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), - context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / QgsProcessingUtils.featureCount(layer, context) + features = source.getFeatures() + total = 100.0 / source.featureCount() for current, f in enumerate(features): + if feedback.isCanceled(): + break + if f.hasGeometry(): - if min_area is not None: - f.setGeometry(f.geometry().removeInteriorRings(min_area)) - else: - f.setGeometry(f.geometry().removeInteriorRings()) - writer.addFeature(f) + f.setGeometry(f.geometry().removeInteriorRings(min_area)) + sink.addFeature(f) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometries.py b/python/plugins/processing/algs/qgis/DensifyGeometries.py index 3f2122cb8d6..223ede5510c 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometries.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometries.py @@ -30,15 +30,12 @@ import os from qgis.core import (QgsWkbTypes, QgsApplication, - QgsProcessingUtils) - + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects - -pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] class DensifyGeometries(QgisAlgorithm): @@ -61,14 +58,15 @@ class DensifyGeometries(QgisAlgorithm): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), - [dataobjects.TYPE_VECTOR_POLYGON, dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.VERTICES, - self.tr('Vertices to add'), 1, 10000000, 1)) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Densified'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPolygon, QgsProcessingParameterDefinition.TypeVectorLine])) + self.addParameter(QgsProcessingParameterNumber(self.VERTICES, + self.tr('Vertices to add'), QgsProcessingParameterNumber.Integer, + 1, False, 1, 10000000)) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) def name(self): return 'densifygeometries' @@ -77,22 +75,24 @@ class DensifyGeometries(QgisAlgorithm): return self.tr('Densify geometries') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - vertices = self.getParameterValue(self.VERTICES) + source = self.parameterAsSource(parameters, self.INPUT, context) + vertices = self.parameterAsInt(parameters, self.VERTICES, context) - isPolygon = layer.geometryType() == QgsWkbTypes.PolygonGeometry + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - writer = self.getOutputFromName( - self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) + features = source.getFeatures() + total = 100.0 / source.featureCount() - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / QgsProcessingUtils.featureCount(layer, context) for current, f in enumerate(features): + if feedback.isCanceled(): + break + feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByCount(int(vertices)) + new_geometry = feature.geometry().densifyByCount(vertices) feature.setGeometry(new_geometry) - writer.addFeature(feature) + sink.addFeature(feature) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py index 6e860c023dd..f328c8eb6e6 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py @@ -31,13 +31,12 @@ from math import sqrt from qgis.core import (QgsWkbTypes, QgsApplication, - QgsProcessingUtils) - + QgsProcessingParameterFeatureSource, + QgsProcessingParameterNumber, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer, + QgsProcessingParameterDefinition) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class DensifyGeometriesInterval(QgisAlgorithm): @@ -57,13 +56,15 @@ class DensifyGeometriesInterval(QgisAlgorithm): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), - [dataobjects.TYPE_VECTOR_POLYGON, dataobjects.TYPE_VECTOR_LINE])) - self.addParameter(ParameterNumber(self.INTERVAL, - self.tr('Interval between vertices to add'), 0.0, 10000000.0, 1.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Densified'))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, + self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPolygon, QgsProcessingParameterDefinition.TypeVectorLine])) + self.addParameter(QgsProcessingParameterNumber(self.INTERVAL, + self.tr('Interval between vertices to add'), QgsProcessingParameterNumber.Double, + 1, False, 0, 10000000)) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) def name(self): return 'densifygeometriesgivenaninterval' @@ -72,23 +73,24 @@ class DensifyGeometriesInterval(QgisAlgorithm): return self.tr('Densify geometries given an interval') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - interval = self.getParameterValue(self.INTERVAL) + source = self.parameterAsSource(parameters, self.INPUT, context) + interval = self.parameterAsDouble(parameters, self.INTERVAL, context) - isPolygon = layer.geometryType() == QgsWkbTypes.PolygonGeometry + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - writer = self.getOutputFromName( - self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), context) - - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / QgsProcessingUtils.featureCount(layer, context) + features = source.getFeatures() + total = 100.0 / source.featureCount() for current, f in enumerate(features): + if feedback.isCanceled(): + break + feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByCount(float(interval)) + new_geometry = feature.geometry().densifyByDistance(float(interval)) feature.setGeometry(new_geometry) - writer.addFeature(feature) + sink.addFeature(feature) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DropGeometry.py b/python/plugins/processing/algs/qgis/DropGeometry.py index 1c68e076350..46171d9d4de 100644 --- a/python/plugins/processing/algs/qgis/DropGeometry.py +++ b/python/plugins/processing/algs/qgis/DropGeometry.py @@ -29,11 +29,11 @@ from qgis.core import (QgsFeatureRequest, QgsWkbTypes, QgsCoordinateReferenceSystem, QgsApplication, - QgsProcessingUtils) + QgsProcessingParameterDefinition, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class DropGeometry(QgisAlgorithm): @@ -55,11 +55,10 @@ class DropGeometry(QgisAlgorithm): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT_LAYER, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POINT, - dataobjects.TYPE_VECTOR_LINE, - dataobjects.TYPE_VECTOR_POLYGON])) - self.addOutput(OutputVector(self.OUTPUT_TABLE, self.tr('Dropped geometry'))) + + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint, QgsProcessingParameterDefinition.TypeVectorLine, QgsProcessingParameterDefinition.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_TABLE, self.tr('Dropped geometry'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_TABLE, self.tr("Dropped geometry"))) def name(self): return 'dropgeometries' @@ -68,17 +67,20 @@ class DropGeometry(QgisAlgorithm): return self.tr('Drop geometries') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT_LAYER), context) - writer = self.getOutputFromName( - self.OUTPUT_TABLE).getVectorWriter(layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(), - context) + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_TABLE, context, + source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = QgsProcessingUtils.getFeatures(layer, context, request) - total = 100.0 / QgsProcessingUtils.featureCount(layer, context) + features = source.getFeatures(request) + total = 100.0 / source.featureCount() for current, input_feature in enumerate(features): - writer.addFeature(input_feature) + if feedback.isCanceled(): + break + + input_feature.clearGeometry() + sink.addFeature(input_feature) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT_TABLE: dest_id} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index abd5a94d0f0..2fe26a26bc5 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -65,7 +65,8 @@ from .ExtractByExpression import ExtractByExpression # from .Centroids import Centroids # from .Delaunay import Delaunay # from .VoronoiPolygons import VoronoiPolygons -# from .DensifyGeometries import DensifyGeometries +from .DensifyGeometries import DensifyGeometries +from .DensifyGeometriesInterval import DensifyGeometriesInterval from .MultipartToSingleparts import MultipartToSingleparts # from .SimplifyGeometries import SimplifyGeometries # from .LinesToPolygons import LinesToPolygons @@ -84,10 +85,9 @@ from .ExtentFromLayer import ExtentFromLayer # from .RandomSelectionWithinSubsets import RandomSelectionWithinSubsets # from .SelectByLocation import SelectByLocation # from .Union import Union -# from .DensifyGeometriesInterval import DensifyGeometriesInterval # from .SpatialJoin import SpatialJoin from .DeleteColumn import DeleteColumn -# from .DeleteHoles import DeleteHoles +from .DeleteHoles import DeleteHoles # from .DeleteDuplicateGeometries import DeleteDuplicateGeometries # from .TextToFloat import TextToFloat # from .ExtractByAttribute import ExtractByAttribute @@ -168,8 +168,8 @@ from .Aspect import Aspect # from .SnapGeometries import SnapGeometriesToLayer # from .PoleOfInaccessibility import PoleOfInaccessibility # from .RasterCalculator import RasterCalculator -# from .CreateAttributeIndex import CreateAttributeIndex -# from .DropGeometry import DropGeometry +from .CreateAttributeIndex import CreateAttributeIndex +from .DropGeometry import DropGeometry from .BasicStatistics import BasicStatisticsForField # from .Heatmap import Heatmap # from .Orthogonalize import Orthogonalize @@ -205,14 +205,13 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # LinesIntersection(), UniqueValues(), PointDistance(), # ReprojectLayer(), ExportGeometryInfo(), Centroids(), # Delaunay(), VoronoiPolygons(), SimplifyGeometries(), - # DensifyGeometries(), DensifyGeometriesInterval(), # , SinglePartsToMultiparts(), # PolygonsToLines(), LinesToPolygons(), ExtractNodes(), # ConvexHull(), FixedDistanceBuffer(), # VariableDistanceBuffer(), Dissolve(), Difference(), # Intersection(), Union(), # RandomSelection(), RandomSelectionWithinSubsets(), - # SelectByLocation(), RandomExtract(), DeleteHoles(), + # SelectByLocation(), RandomExtract(), # RandomExtractWithinSubsets(), ExtractByLocation(), # SpatialJoin(), RegularPoints(), SymmetricalDifference(), # VectorSplit(), VectorGridLines(), VectorGridPolygons(), @@ -250,8 +249,8 @@ class QGISAlgorithmProvider(QgsProcessingProvider): # RemoveNullGeometry(), # ExtendLines(), ExtractSpecificNodes(), # GeometryByExpression(), SnapGeometriesToLayer(), - # PoleOfInaccessibility(), CreateAttributeIndex(), - # DropGeometry(), + # PoleOfInaccessibility(), + # # RasterCalculator(), Heatmap(), Orthogonalize(), # ShortestPathPointToPoint(), ShortestPathPointToLayer(), # ShortestPathLayerToPoint(), ServiceAreaFromPoint(), @@ -267,7 +266,12 @@ class QGISAlgorithmProvider(QgsProcessingProvider): BoundingBox(), CheckValidity(), Clip(), + CreateAttributeIndex(), DeleteColumn(), + DeleteHoles(), + DensifyGeometries(), + DensifyGeometriesInterval(), + DropGeometry(), ExtentFromLayer(), ExtractByExpression(), GridPolygon(), diff --git a/python/plugins/processing/gui/BatchInputSelectionPanel.py b/python/plugins/processing/gui/BatchInputSelectionPanel.py index 26d5c996999..46f21802465 100644 --- a/python/plugins/processing/gui/BatchInputSelectionPanel.py +++ b/python/plugins/processing/gui/BatchInputSelectionPanel.py @@ -40,7 +40,7 @@ from qgis.core import (QgsMapLayer, QgsProcessingParameterMultipleLayers, QgsProcessingParameterRasterLayer, QgsProcessingParameterDefinition, - QgsProcessingParameterTable, + QgsProcessingParameterVectorLayer, QgsProcessingParameterFeatureSource) from processing.gui.MultipleInputDialog import MultipleInputDialog @@ -105,7 +105,7 @@ class BatchInputSelectionPanel(QWidget): (isinstance(self.param, QgsProcessingParameterMultipleLayers) and self.param.layerType() == QgsProcessingParameterDefinition.TypeRaster)): layers = QgsProcessingUtils.compatibleRasterLayers(QgsProject.instance()) - elif isinstance(self.param, QgsProcessingParameterTable): + elif isinstance(self.param, QgsProcessingParameterVectorLayer): layers = QgsProcessingUtils.compatibleVectorLayers(QgsProject.instance()) else: datatypes = [QgsProcessingParameterDefinition.TypeVectorAny] diff --git a/python/plugins/processing/gui/BatchOutputSelectionPanel.py b/python/plugins/processing/gui/BatchOutputSelectionPanel.py index 9b7e9bebabd..588afcbeb8a 100644 --- a/python/plugins/processing/gui/BatchOutputSelectionPanel.py +++ b/python/plugins/processing/gui/BatchOutputSelectionPanel.py @@ -36,7 +36,7 @@ from qgis.core import (QgsMapLayer, QgsProcessingParameterFolderOutput, QgsProcessingParameterRasterLayer, QgsProcessingParameterFeatureSource, - QgsProcessingParameterTable, + QgsProcessingParameterVectorLayer, QgsProcessingParameterMultipleLayers, QgsProcessingParameterBoolean, QgsProcessingParameterEnum, @@ -114,7 +114,7 @@ class BatchOutputSelectionPanel(QWidget): param = self.alg.parameterDefinitions()[dlg.param_index] if isinstance(param, (QgsProcessingParameterRasterLayer, QgsProcessingParameterFeatureSource, - QgsProcessingParameterTable, + QgsProcessingParameterVectorLayer, QgsProcessingParameterMultipleLayers)): v = widget.value() if isinstance(v, QgsMapLayer): diff --git a/python/plugins/processing/gui/wrappers.py b/python/plugins/processing/gui/wrappers.py index e531415c8f9..9efb9ab4058 100644 --- a/python/plugins/processing/gui/wrappers.py +++ b/python/plugins/processing/gui/wrappers.py @@ -58,8 +58,8 @@ from qgis.core import ( QgsProcessingParameterEnum, QgsProcessingParameterString, QgsProcessingParameterExpression, - QgsProcessingParameterTable, - QgsProcessingParameterTableField, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterField, QgsProcessingParameterFeatureSource, QgsProcessingFeatureSourceDefinition, QgsProcessingOutputRasterLayer, @@ -531,7 +531,7 @@ class MultipleInputWidgetWrapper(WidgetWrapper): elif self.param.layerType() == QgsProcessingParameterDefinition.TypeRaster: options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterRasterLayer, QgsProcessingOutputRasterLayer) elif self.param.layerType() == QgsProcessingParameterDefinition.TypeTable: - options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterTable, OutputTable) + options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterVectorLayer, OutputTable) else: options = self.dialog.getAvailableValuesOfType(QgsProcessingParameterFile, OutputFile) options = sorted(options, key=lambda opt: self.dialog.resolveValueDescription(opt)) @@ -907,7 +907,7 @@ class StringWidgetWrapper(WidgetWrapper, ExpressionWidgetWrapperMixin): else: # strings, numbers, files and table fields are all allowed input types strings = self.dialog.getAvailableValuesOfType([QgsProcessingParameterString, QgsProcessingParameterNumber, QgsProcessingParameterFile, - QgsProcessingParameterTableField, QgsProcessingParameterExpression], QgsProcessingOutputString) + QgsProcessingParameterField, QgsProcessingParameterExpression], QgsProcessingOutputString) options = [(self.dialog.resolveValueDescription(s), s) for s in strings] if self.param.multiLine(): widget = MultilineTextPanel(options) @@ -1079,7 +1079,7 @@ class TableWidgetWrapper(WidgetWrapper): self.combo = QComboBox() layers = self.dialog.getAvailableValuesOfType(QgsProcessingParameterRasterLayer, QgsProcessingOutputRasterLayer) self.combo.setEditable(True) - tables = self.dialog.getAvailableValuesOfType(QgsProcessingParameterTable, OutputTable) + tables = self.dialog.getAvailableValuesOfType(QgsProcessingParameterVectorLayer, OutputTable) layers = self.dialog.getAvailableValuesOfType(QgsProcessingParameterFeatureSource, QgsProcessingOutputVectorLayer) if self.param.flags() & QgsProcessingParameterDefinition.FlagOptional: self.combo.addItem(self.NOT_SELECTED, None) @@ -1154,17 +1154,17 @@ class TableFieldWidgetWrapper(WidgetWrapper): widget = QgsFieldComboBox() widget.setAllowEmptyFieldName(self.param.flags() & QgsProcessingParameterDefinition.FlagOptional) widget.fieldChanged.connect(lambda: self.widgetValueHasChanged.emit(self)) - if self.param.dataType() == QgsProcessingParameterTableField.Numeric: + if self.param.dataType() == QgsProcessingParameterField.Numeric: widget.setFilters(QgsFieldProxyModel.Numeric) - elif self.param.dataType() == QgsProcessingParameterTableField.String: + elif self.param.dataType() == QgsProcessingParameterField.String: widget.setFilters(QgsFieldProxyModel.String) - elif self.param.dataType() == QgsProcessingParameterTableField.DateTime: + elif self.param.dataType() == QgsProcessingParameterField.DateTime: widget.setFilters(QgsFieldProxyModel.Date | QgsFieldProxyModel.Time) return widget else: widget = QComboBox() widget.setEditable(True) - fields = self.dialog.getAvailableValuesOfType([QgsProcessingParameterTableField, QgsProcessingParameterString], [QgsProcessingOutputString]) + fields = self.dialog.getAvailableValuesOfType([QgsProcessingParameterField, QgsProcessingParameterString], [QgsProcessingOutputString]) if self.param.flags() & QgsProcessingParameterDefinition.FlagOptional: widget.addItem(self.NOT_SET, None) for f in fields: @@ -1202,12 +1202,12 @@ class TableFieldWidgetWrapper(WidgetWrapper): if self._layer is None: return [] fieldTypes = [] - if self.param.dataType() == QgsProcessingParameterTableField.String: + if self.param.dataType() == QgsProcessingParameterField.String: fieldTypes = [QVariant.String] - elif self.param.dataType() == QgsProcessingParameterTableField.Numeric: + elif self.param.dataType() == QgsProcessingParameterField.Numeric: fieldTypes = [QVariant.Int, QVariant.Double, QVariant.LongLong, QVariant.UInt, QVariant.ULongLong] - elif self.param.dataType() == QgsProcessingParameterTableField.DateTime: + elif self.param.dataType() == QgsProcessingParameterField.DateTime: fieldTypes = [QVariant.Date, QVariant.Time, QVariant.DateTime] fieldNames = set() @@ -1303,7 +1303,7 @@ class WidgetWrapperFactory: wrapper = StringWidgetWrapper elif param.type() == 'expression': wrapper = ExpressionWidgetWrapper - elif param.type() == 'table': + elif param.type() == 'vector': wrapper = TableWidgetWrapper elif param.type() == 'field': wrapper = TableFieldWidgetWrapper diff --git a/python/plugins/processing/modeler/ModelerAlgorithm.py b/python/plugins/processing/modeler/ModelerAlgorithm.py index e12c40ba712..ba7be9b802a 100644 --- a/python/plugins/processing/modeler/ModelerAlgorithm.py +++ b/python/plugins/processing/modeler/ModelerAlgorithm.py @@ -52,8 +52,8 @@ from qgis.core import (QgsApplication, QgsProcessingParameterEnum, QgsProcessingParameterString, QgsProcessingParameterExpression, - QgsProcessingParameterTable, - QgsProcessingParameterTableField, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterField, QgsProcessingParameterFeatureSource, QgsProcessingModelAlgorithm) from qgis.gui import QgsMessageBar diff --git a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py index f9ecc84f349..05055506093 100644 --- a/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py +++ b/python/plugins/processing/modeler/ModelerParameterDefinitionDialog.py @@ -45,8 +45,8 @@ from qgis.core import (QgsCoordinateReferenceSystem, QgsProcessingParameterEnum, QgsProcessingParameterString, QgsProcessingParameterExpression, - QgsProcessingParameterTable, - QgsProcessingParameterTableField, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterField, QgsProcessingParameterFeatureSource) from qgis.PyQt.QtCore import Qt from qgis.PyQt.QtWidgets import (QDialog, @@ -123,13 +123,13 @@ class ModelerParameterDefinitionDialog(QDialog): self.state.setChecked(bool(self.param.defaultValue())) self.verticalLayout.addWidget(self.state) elif self.paramType == ModelerParameterDefinitionDialog.PARAMETER_TABLE_FIELD or \ - isinstance(self.param, QgsProcessingParameterTableField): + isinstance(self.param, QgsProcessingParameterField): self.verticalLayout.addWidget(QLabel(self.tr('Parent layer'))) self.parentCombo = QComboBox() idx = 0 for param in list(self.alg.parameterComponents().values()): definition = self.alg.parameterDefinition(param.parameterName()) - if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterTable)): + if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer)): self.parentCombo.addItem(definition.description(), definition.name()) if self.param is not None: if self.param.parentLayerParameter() == definition.name(): @@ -218,7 +218,7 @@ class ModelerParameterDefinitionDialog(QDialog): idx = 1 for param in list(self.alg.parameterComponents().values()): definition = self.alg.parameterDefinition(param.parameterName()) - if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterTable)): + if isinstance(definition, (QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer)): self.parentCombo.addItem(definition.description(), definition.name()) if self.param is not None: if self.param.parentLayerParameter() == definition.name(): @@ -301,21 +301,21 @@ class ModelerParameterDefinitionDialog(QDialog): isinstance(self.param, QgsProcessingParameterBoolean)): self.param = QgsProcessingParameterBoolean(name, description, self.state.isChecked()) elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_TABLE_FIELD or - isinstance(self.param, QgsProcessingParameterTableField)): + isinstance(self.param, QgsProcessingParameterField)): if self.parentCombo.currentIndex() < 0: QMessageBox.warning(self, self.tr('Unable to define parameter'), self.tr('Wrong or missing parameter values')) return parent = self.parentCombo.currentData() datatype = self.datatypeCombo.currentData() - self.param = QgsProcessingParameterTableField(name, description, None, parent, datatype, self.multipleCheck.isChecked()) + self.param = QgsProcessingParameterField(name, description, None, parent, datatype, self.multipleCheck.isChecked()) elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_RASTER or isinstance(self.param, QgsProcessingParameterRasterLayer)): self.param = QgsProcessingParameterRasterLayer( name, description) elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_TABLE or - isinstance(self.param, QgsProcessingParameterTable)): - self.param = QgsProcessingParameterTable( + isinstance(self.param, QgsProcessingParameterVectorLayer)): + self.param = QgsProcessingParameterVectorLayer( name, description) elif (self.paramType == ModelerParameterDefinitionDialog.PARAMETER_VECTOR or isinstance(self.param, QgsProcessingParameterFeatureSource)): diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 5b4a706d131..1799640ac72 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -50,7 +50,7 @@ from processing.script.ScriptAlgorithm import ScriptAlgorithm # NOQA from processing.modeler.ModelerAlgorithmProvider import ModelerAlgorithmProvider # NOQA from processing.algs.qgis.QGISAlgorithmProvider import QGISAlgorithmProvider # NOQA -from processing.algs.grass7.Grass7AlgorithmProvider import Grass7AlgorithmProvider # NOQA +#from processing.algs.grass7.Grass7AlgorithmProvider import Grass7AlgorithmProvider # NOQA from processing.algs.gdal.GdalAlgorithmProvider import GdalAlgorithmProvider # NOQA from processing.algs.saga.SagaAlgorithmProvider import SagaAlgorithmProvider # NOQA from processing.script.ScriptAlgorithmProvider import ScriptAlgorithmProvider # NOQA diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index deedd9fb0b3..aa9797da44c 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -15,16 +15,16 @@ tests: # geometry: # precision: 7 # -# - name: Delete Holes -# algorithm: qgis:deleteholes -# params: -# - name: polys.gml -# type: vector -# results: -# OUTPUT: -# name: expected/polys_deleteholes.gml -# type: vector -# + - name: Delete Holes + algorithm: qgis:deleteholes + params: + - name: polys.gml + type: vector + results: + OUTPUT: + name: expected/polys_deleteholes.gml + type: vector + - algorithm: qgis:clip name: Clip lines by polygons params: @@ -128,18 +128,18 @@ tests: # name: expected/intersection_collection_fallback.shp # type: vector # -# - name: Densify geometries -# algorithm: qgis:densifygeometries -# params: -# INPUT: -# name: multipolys.gml -# type: vector -# VERTICES: 4 -# results: -# OUTPUT: -# name: expected/multipolys_densify.gml -# type: vector -# + - name: Densify geometries + algorithm: qgis:densifygeometries + params: + INPUT: + name: multipolys.gml + type: vector + VERTICES: 4 + results: + OUTPUT: + name: expected/multipolys_densify.gml + type: vector + # - name: Polygons to Lines # algorithm: qgis:polygonstolines # params: @@ -1258,7 +1258,7 @@ tests: # name: expected/remove_null_polys.gml # type: vector # - - algorithm: qgis:extractbyexpression + - algorithm: native:extractbyexpression name: Extract by Expression params: EXPRESSION: left( "Name",1)='A' @@ -1484,88 +1484,88 @@ tests: # geometry: # precision: 7 # -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (is null) -# params: -# FIELD: intval -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '8' -# results: -# OUTPUT: -# name: expected/extract_by_attribute_null.gml -# type: vector -# -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (is not null) -# params: -# FIELD: intval -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '9' -# results: -# OUTPUT: -# name: expected/extract_by_attribute_not_null.gml -# type: vector -# -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (starts with) -# params: -# FIELD: name -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '6' -# VALUE: A -# results: -# OUTPUT: -# name: expected/extract_by_attribute_startswith.gml -# type: vector -# -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (contains) -# params: -# FIELD: name -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '7' -# VALUE: aaa -# results: -# OUTPUT: -# name: expected/extract_by_attribute_contains.gml -# type: vector -# -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (does not contain) -# params: -# FIELD: name -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '10' -# VALUE: a -# results: -# OUTPUT: -# name: expected/extract_by_attribute_does_not_contain.gml -# type: vector -# -# - algorithm: qgis:extractbyattribute -# name: Extract by attribute (greater) -# params: -# FIELD: floatval -# INPUT: -# name: polys.gml -# type: vector -# OPERATOR: '2' -# VALUE: '1' -# results: -# OUTPUT: -# name: expected/extract_by_attribute_greater.gml -# type: vector -# + - algorithm: native:extractbyattribute + name: Extract by attribute (is null) + params: + FIELD: intval + INPUT: + name: polys.gml + type: vector + OPERATOR: '8' + results: + OUTPUT: + name: expected/extract_by_attribute_null.gml + type: vector + + - algorithm: native:extractbyattribute + name: Extract by attribute (is not null) + params: + FIELD: intval + INPUT: + name: polys.gml + type: vector + OPERATOR: '9' + results: + OUTPUT: + name: expected/extract_by_attribute_not_null.gml + type: vector + + - algorithm: native:extractbyattribute + name: Extract by attribute (starts with) + params: + FIELD: name + INPUT: + name: polys.gml + type: vector + OPERATOR: '6' + VALUE: A + results: + OUTPUT: + name: expected/extract_by_attribute_startswith.gml + type: vector + + - algorithm: native:extractbyattribute + name: Extract by attribute (contains) + params: + FIELD: name + INPUT: + name: polys.gml + type: vector + OPERATOR: '7' + VALUE: aaa + results: + OUTPUT: + name: expected/extract_by_attribute_contains.gml + type: vector + + - algorithm: native:extractbyattribute + name: Extract by attribute (does not contain) + params: + FIELD: name + INPUT: + name: polys.gml + type: vector + OPERATOR: '10' + VALUE: a + results: + OUTPUT: + name: expected/extract_by_attribute_does_not_contain.gml + type: vector + + - algorithm: native:extractbyattribute + name: Extract by attribute (greater) + params: + FIELD: floatval + INPUT: + name: polys.gml + type: vector + OPERATOR: '2' + VALUE: '1' + results: + OUTPUT: + name: expected/extract_by_attribute_greater.gml + type: vector + # - algorithm: qgis:createattributeindex # name: Create Attribute Index (only tests for python errors, does not check result) # params: @@ -1665,18 +1665,18 @@ tests: # compare: # geometry: # precision: 7 -# -# - algorithm: qgis:dropgeometries -# name: Drop geometries -# params: -# INPUT_LAYER: -# name: polys.gml -# type: vector -# results: -# OUTPUT_TABLE: -# name: expected/dropped_geometry.csv -# type: vector -# + + - algorithm: qgis:dropgeometries + name: Drop geometries + params: + INPUT_LAYER: + name: polys.gml + type: vector + results: + OUTPUT_TABLE: + name: expected/dropped_geometry.csv + type: vector + # - algorithm: qgis:creategridlines # name: Create grid (lines) # params: @@ -1808,30 +1808,30 @@ tests: geometry: precision: 7 -# - algorithm: qgis:deleteholes -# name: Delete holes (no min) -# params: -# INPUT: -# name: custom/remove_holes.gml -# type: vector -# MIN_AREA: 0.0 -# results: -# OUTPUT: -# name: expected/removed_holes.gml -# type: vector -# -# - algorithm: qgis:deleteholes -# name: Delete holes (with min) -# params: -# INPUT: -# name: custom/remove_holes.gml -# type: vector -# MIN_AREA: 5.0 -# results: -# OUTPUT: -# name: expected/removed_holes_min_area.gml -# type: vector -# + - algorithm: qgis:deleteholes + name: Delete holes (no min) + params: + INPUT: + name: custom/remove_holes.gml + type: vector + MIN_AREA: 0.0 + results: + OUTPUT: + name: expected/removed_holes.gml + type: vector + + - algorithm: qgis:deleteholes + name: Delete holes (with min) + params: + INPUT: + name: custom/remove_holes.gml + type: vector + MIN_AREA: 5.0 + results: + OUTPUT: + name: expected/removed_holes_min_area.gml + type: vector + - algorithm: qgis:basicstatisticsforfields name: Basic stats datetime params: diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 5073bb61280..ebb05d6a16c 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -3620,7 +3620,7 @@ void QgisApp::updateRecentProjectPaths() { QAction *action = mRecentProjectsMenu->addAction( QStringLiteral( "%1 (%2)" ).arg( recentProject.title != recentProject.path ? recentProject.title : QFileInfo( recentProject.path ).baseName(), QDir::toNativeSeparators( recentProject.path ) ) ); - action->setEnabled( QFile::exists( ( recentProject.path ) ) ); + //action->setEnabled( QFile::exists( ( recentProject.path ) ) ); action->setData( recentProject.path ); } diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 2a6d6a479cc..9871f4d9b50 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -58,12 +58,15 @@ bool QgsNativeAlgorithms::supportsNonFileBasedOutput() const void QgsNativeAlgorithms::loadAlgorithms() { - addAlgorithm( new QgsCentroidAlgorithm() ); addAlgorithm( new QgsBufferAlgorithm() ); - addAlgorithm( new QgsDissolveAlgorithm() ); + addAlgorithm( new QgsCentroidAlgorithm() ); addAlgorithm( new QgsClipAlgorithm() ); - addAlgorithm( new QgsTransformAlgorithm() ); + addAlgorithm( new QgsDissolveAlgorithm() ); + addAlgorithm( new QgsExtractByAttributeAlgorithm() ); + addAlgorithm( new QgsExtractByExpressionAlgorithm() ); + addAlgorithm( new QgsMultipartToSinglepartAlgorithm() ); addAlgorithm( new QgsSubdivideAlgorithm() ); + addAlgorithm( new QgsTransformAlgorithm() ); } @@ -244,8 +247,8 @@ QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsDissolveAlgorithm::QgsDissolveAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); - addParameter( new QgsProcessingParameterTableField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(), - QStringLiteral( "INPUT" ), QgsProcessingParameterTableField::Any, true, true ) ); + addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Unique ID fields" ), QVariant(), + QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true, true ) ); addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) ); addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Dissolved" ) ) ); @@ -664,6 +667,380 @@ QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶mete return outputs; } + + +QgsMultipartToSinglepartAlgorithm::QgsMultipartToSinglepartAlgorithm() +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Single parts" ) ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Single parts" ) ) ); +} + +QString QgsMultipartToSinglepartAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm takes a vector layer with multipart geometries and generates a new one in which all geometries contain " + "a single part. Features with multipart geometries are divided in as many different features as parts the geometry " + "contain, and the same attributes are used for each of them." ); +} + +QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +{ + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); + + QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( source->wkbType() ); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, source->fields(), + sinkType, source->sourceCrs(), dest ) ); + if ( !sink ) + return QVariantMap(); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); + + QgsFeature f; + QgsFeatureIterator it = source->getFeatures(); + + double step = 100.0 / count; + int current = 0; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + QgsFeature out = f; + if ( out.hasGeometry() ) + { + QgsGeometry inputGeometry = f.geometry(); + if ( inputGeometry.isMultipart() ) + { + Q_FOREACH ( const QgsGeometry &g, inputGeometry.asGeometryCollection() ) + { + out.setGeometry( g ); + sink->addFeature( out ); + } + } + else + { + sink->addFeature( out ); + } + } + else + { + // feature with null geometry + sink->addFeature( out ); + } + + feedback->setProgress( current * step ); + current++; + } + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + return outputs; +} + + +QgsExtractByExpressionAlgorithm::QgsExtractByExpressionAlgorithm() +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterExpression( QStringLiteral( "EXPRESSION" ), QObject::tr( "Expression" ), QVariant(), QStringLiteral( "INPUT" ) ) ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Matching features" ) ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Matching (expression)" ) ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Non-matching" ), + QgsProcessingParameterDefinition::TypeVectorAny, QVariant(), true ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Non-matching (expression)" ) ) ); +} + +QString QgsExtractByExpressionAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an input layer. " + "The criteria for adding features to the resulting layer is based on a QGIS expression.\n\n" + "For more information about expressions see the user manual" ); +} + +QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +{ + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); + + QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + + QString matchingSinkId; + std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, source->fields(), + source->wkbType(), source->sourceCrs(), matchingSinkId ) ); + if ( !matchingSink ) + return QVariantMap(); + + QString nonMatchingSinkId; + std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, source->fields(), + source->wkbType(), source->sourceCrs(), nonMatchingSinkId ) ); + + QgsExpression expression( expressionString ); + if ( expression.hasParserError() ) + { + // raise GeoAlgorithmExecutionException(expression.parserErrorString()) + return QVariantMap(); + } + + QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); + + double step = 100.0 / count; + int current = 0; + + if ( !nonMatchingSink ) + { + // not saving failing features - so only fetch good features + QgsFeatureRequest req; + req.setFilterExpression( expressionString ); + req.setExpressionContext( expressionContext ); + + QgsFeatureIterator it = source->getFeatures( req ); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + matchingSink->addFeature( f ); + + feedback->setProgress( current * step ); + current++; + } + } + else + { + // saving non-matching features, so we need EVERYTHING + expressionContext.setFields( source->fields() ); + expression.prepare( &expressionContext ); + + QgsFeatureIterator it = source->getFeatures(); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + expressionContext.setFeature( f ); + if ( expression.evaluate( &expressionContext ).toBool() ) + { + matchingSink->addFeature( f ); + } + else + { + nonMatchingSink->addFeature( f ); + } + + feedback->setProgress( current * step ); + current++; + } + } + + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); + if ( nonMatchingSink ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); + return outputs; +} + + +QgsExtractByAttributeAlgorithm::QgsExtractByAttributeAlgorithm() +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Selection attribute" ), QVariant(), QStringLiteral( "INPUT" ) ) ); + addParameter( new QgsProcessingParameterEnum( QStringLiteral( "OPERATOR" ), QObject::tr( "Operator" ), QStringList() + << QObject::tr( "=" ) + << QObject::trUtf8( "≠" ) + << QObject::tr( ">" ) + << QObject::tr( ">=" ) + << QObject::tr( "<" ) + << QObject::tr( "<=" ) + << QObject::tr( "begins with" ) + << QObject::tr( "contains" ) + << QObject::tr( "is null" ) + << QObject::tr( "is not null" ) + << QObject::tr( "does not contain" ) ) ); + addParameter( new QgsProcessingParameterString( QStringLiteral( "VALUE" ), QObject::tr( "Value" ), QVariant(), false, true ) ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (attribute)" ) ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Matching (attribute)" ) ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Extracted (non-matching)" ), + QgsProcessingParameterDefinition::TypeVectorAny, QVariant(), true ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Non-matching (attribute)" ) ) ); +} + +QString QgsExtractByAttributeAlgorithm::shortHelpString() const +{ + return QObject::tr( " This algorithm creates a new vector layer that only contains matching features from an input layer. " + "The criteria for adding features to the resulting layer is defined based on the values " + "of an attribute from the input layer." ); +} + +QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +{ + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); + + QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); + Operation op = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) ); + QString value = parameterAsString( parameters, QStringLiteral( "VALUE" ), context ); + + QString matchingSinkId; + std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, source->fields(), + source->wkbType(), source->sourceCrs(), matchingSinkId ) ); + if ( !matchingSink ) + return QVariantMap(); + + QString nonMatchingSinkId; + std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, source->fields(), + source->wkbType(), source->sourceCrs(), nonMatchingSinkId ) ); + + + int idx = source->fields().lookupField( fieldName ); + QVariant::Type fieldType = source->fields().at( idx ).type(); + + if ( fieldType != QVariant::String && ( op == BeginsWith || op == Contains || op == DoesNotContain ) ) + { +#if 0 + op = ''.join( ['"%s", ' % o for o in self.STRING_OPERATORS] ) + raise GeoAlgorithmExecutionException( + self.tr( 'Operators {0} can be used only with string fields.' ).format( op ) ) +#endif + return QVariantMap(); + } + + QString fieldRef = QgsExpression::quotedColumnRef( fieldName ); + QString quotedVal = QgsExpression::quotedValue( value ); + QString expr; + switch ( op ) + { + case Equals: + expr = QStringLiteral( "%1 = %3" ).arg( fieldRef, quotedVal ); + break; + case NotEquals: + expr = QStringLiteral( "%1 != %3" ).arg( fieldRef, quotedVal ); + break; + case GreaterThan: + expr = QStringLiteral( "%1 > %3" ).arg( fieldRef, quotedVal ); + break; + case GreaterThanEqualTo: + expr = QStringLiteral( "%1 >= %3" ).arg( fieldRef, quotedVal ); + break; + case LessThan: + expr = QStringLiteral( "%1 < %3" ).arg( fieldRef, quotedVal ); + break; + case LessThanEqualTo: + expr = QStringLiteral( "%1 <= %3" ).arg( fieldRef, quotedVal ); + break; + case BeginsWith: + expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, value ); + break; + case Contains: + expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, value ); + break; + case IsNull: + expr = QStringLiteral( "%1 IS NULL" ).arg( fieldRef ); + break; + case IsNotNull: + expr = QStringLiteral( "%1 IS NOT NULL" ).arg( fieldRef ); + break; + case DoesNotContain: + expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, value ); + break; + } + + QgsExpression expression( expr ); + if ( expression.hasParserError() ) + { + // raise GeoAlgorithmExecutionException(expression.parserErrorString()) + return QVariantMap(); + } + + QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); + + double step = 100.0 / count; + int current = 0; + + if ( !nonMatchingSink ) + { + // not saving failing features - so only fetch good features + QgsFeatureRequest req; + req.setFilterExpression( expr ); + req.setExpressionContext( expressionContext ); + + QgsFeatureIterator it = source->getFeatures( req ); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + matchingSink->addFeature( f ); + + feedback->setProgress( current * step ); + current++; + } + } + else + { + // saving non-matching features, so we need EVERYTHING + expressionContext.setFields( source->fields() ); + expression.prepare( &expressionContext ); + + QgsFeatureIterator it = source->getFeatures(); + QgsFeature f; + while ( it.nextFeature( f ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + expressionContext.setFeature( f ); + if ( expression.evaluate( &expressionContext ).toBool() ) + { + matchingSink->addFeature( f ); + } + else + { + nonMatchingSink->addFeature( f ); + } + + feedback->setProgress( current * step ); + current++; + } + } + + + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); + if ( nonMatchingSink ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); + return outputs; +} + ///@endcond - - diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index 0b9de30f4f6..8669df80955 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -135,6 +135,67 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm }; +/** + * Native extract by attribute algorithm. + */ +class QgsExtractByAttributeAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + enum Operation + { + Equals, + NotEquals, + GreaterThan, + GreaterThanEqualTo, + LessThan, + LessThanEqualTo, + BeginsWith, + Contains, + IsNull, + IsNotNull, + DoesNotContain, + }; + + QgsExtractByAttributeAlgorithm(); + + QString name() const override { return QStringLiteral( "extractbyattribute" ); } + QString displayName() const override { return QObject::tr( "Extract by attribute" ); } + virtual QStringList tags() const override { return QObject::tr( "extract,filter,attribute,value,contains,null,field" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector selection tools" ); } + QString shortHelpString() const override; + + protected: + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + +}; + +/** + * Native extract by expression algorithm. + */ +class QgsExtractByExpressionAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsExtractByExpressionAlgorithm(); + + QString name() const override { return QStringLiteral( "extractbyexpression" ); } + QString displayName() const override { return QObject::tr( "Extract by expression" ); } + virtual QStringList tags() const override { return QObject::tr( "extract,filter,expression,field" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector selection tools" ); } + QString shortHelpString() const override; + + protected: + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + +}; + /** * Native clip algorithm. */ @@ -182,6 +243,29 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm }; +/** + * Native multipart to singlepart algorithm. + */ +class QgsMultipartToSinglepartAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsMultipartToSinglepartAlgorithm(); + + QString name() const override { return QStringLiteral( "multiparttosingleparts" ); } + QString displayName() const override { return QObject::tr( "Multipart to singleparts" ); } + virtual QStringList tags() const override { return QObject::tr( "multi,single,multiple,split,dump" ).split( ',' ); } + QString group() const override { return QObject::tr( "Vector geometry tools" ); } + QString shortHelpString() const override; + + protected: + + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + +}; + ///@endcond PRIVATE #endif // QGSNATIVEALGORITHMS_H diff --git a/src/core/processing/qgsprocessingparameters.cpp b/src/core/processing/qgsprocessingparameters.cpp index fa906a307a5..09eef8b0c73 100644 --- a/src/core/processing/qgsprocessingparameters.cpp +++ b/src/core/processing/qgsprocessingparameters.cpp @@ -744,14 +744,16 @@ QgsProcessingParameterDefinition *QgsProcessingParameters::parameterFromVariantM def.reset( new QgsProcessingParameterString( name ) ); else if ( type == QStringLiteral( "expression" ) ) def.reset( new QgsProcessingParameterExpression( name ) ); - else if ( type == QStringLiteral( "table" ) ) - def.reset( new QgsProcessingParameterTable( name ) ); + else if ( type == QStringLiteral( "vector" ) ) + def.reset( new QgsProcessingParameterVectorLayer( name ) ); else if ( type == QStringLiteral( "field" ) ) - def.reset( new QgsProcessingParameterTableField( name ) ); + def.reset( new QgsProcessingParameterField( name ) ); else if ( type == QStringLiteral( "source" ) ) def.reset( new QgsProcessingParameterFeatureSource( name ) ); else if ( type == QStringLiteral( "sink" ) ) def.reset( new QgsProcessingParameterFeatureSink( name ) ); + else if ( type == QStringLiteral( "vectorOut" ) ) + def.reset( new QgsProcessingParameterVectorOutput( name ) ); else if ( type == QStringLiteral( "rasterOut" ) ) def.reset( new QgsProcessingParameterRasterOutput( name ) ); else if ( type == QStringLiteral( "fileOut" ) ) @@ -1731,13 +1733,53 @@ bool QgsProcessingParameterExpression::fromVariantMap( const QVariantMap &map ) return true; } -QgsProcessingParameterTable::QgsProcessingParameterTable( const QString &name, const QString &description, const QVariant &defaultValue, bool optional ) +QgsProcessingParameterVectorLayer::QgsProcessingParameterVectorLayer( const QString &name, const QString &description, const QVariant &defaultValue, bool optional ) : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) { } -QgsProcessingParameterTableField::QgsProcessingParameterTableField( const QString &name, const QString &description, const QVariant &defaultValue, const QString &parentLayerParameterName, DataType type, bool allowMultiple, bool optional ) +bool QgsProcessingParameterVectorLayer::checkValueIsAcceptable( const QVariant &var, QgsProcessingContext *context ) const +{ + if ( !var.isValid() ) + return mFlags & FlagOptional; + + if ( var.canConvert() ) + { + return true; + } + + if ( qobject_cast< QgsVectorLayer * >( qvariant_cast( var ) ) ) + return true; + + if ( var.type() != QVariant::String || var.toString().isEmpty() ) + return mFlags & FlagOptional; + + if ( !context ) + { + // that's as far as we can get without a context + return true; + } + + // try to load as layer + if ( QgsProcessingUtils::mapLayerFromString( var.toString(), *context ) ) + return true; + + return false; +} + +QString QgsProcessingParameterVectorLayer::valueAsPythonString( const QVariant &val, QgsProcessingContext &context ) const +{ + if ( val.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( val.value< QgsProperty >().asExpression() ); + + QVariantMap p; + p.insert( name(), val ); + QgsVectorLayer *layer = QgsProcessingParameters::parameterAsVectorLayer( this, p, context ); + return layer ? QgsProcessingUtils::normalizeLayerSource( layer->source() ).prepend( '\'' ).append( '\'' ) : QString(); +} + +QgsProcessingParameterField::QgsProcessingParameterField( const QString &name, const QString &description, const QVariant &defaultValue, const QString &parentLayerParameterName, DataType type, bool allowMultiple, bool optional ) : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) , mParentLayerParameter( parentLayerParameterName ) , mDataType( type ) @@ -1746,7 +1788,7 @@ QgsProcessingParameterTableField::QgsProcessingParameterTableField( const QStrin } -bool QgsProcessingParameterTableField::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const +bool QgsProcessingParameterField::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const { if ( !input.isValid() ) return mFlags & FlagOptional; @@ -1778,7 +1820,7 @@ bool QgsProcessingParameterTableField::checkValueIsAcceptable( const QVariant &i return true; } -QString QgsProcessingParameterTableField::valueAsPythonString( const QVariant &value, QgsProcessingContext & ) const +QString QgsProcessingParameterField::valueAsPythonString( const QVariant &value, QgsProcessingContext & ) const { if ( value.canConvert() ) return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); @@ -1805,37 +1847,37 @@ QString QgsProcessingParameterTableField::valueAsPythonString( const QVariant &v return value.toString().prepend( '\'' ).append( '\'' ); } -QString QgsProcessingParameterTableField::parentLayerParameter() const +QString QgsProcessingParameterField::parentLayerParameter() const { return mParentLayerParameter; } -void QgsProcessingParameterTableField::setParentLayerParameter( const QString &parentLayerParameter ) +void QgsProcessingParameterField::setParentLayerParameter( const QString &parentLayerParameter ) { mParentLayerParameter = parentLayerParameter; } -QgsProcessingParameterTableField::DataType QgsProcessingParameterTableField::dataType() const +QgsProcessingParameterField::DataType QgsProcessingParameterField::dataType() const { return mDataType; } -void QgsProcessingParameterTableField::setDataType( const DataType &dataType ) +void QgsProcessingParameterField::setDataType( const DataType &dataType ) { mDataType = dataType; } -bool QgsProcessingParameterTableField::allowMultiple() const +bool QgsProcessingParameterField::allowMultiple() const { return mAllowMultiple; } -void QgsProcessingParameterTableField::setAllowMultiple( bool allowMultiple ) +void QgsProcessingParameterField::setAllowMultiple( bool allowMultiple ) { mAllowMultiple = allowMultiple; } -QVariantMap QgsProcessingParameterTableField::toVariantMap() const +QVariantMap QgsProcessingParameterField::toVariantMap() const { QVariantMap map = QgsProcessingParameterDefinition::toVariantMap(); map.insert( QStringLiteral( "parent_layer" ), mParentLayerParameter ); @@ -1844,7 +1886,7 @@ QVariantMap QgsProcessingParameterTableField::toVariantMap() const return map; } -bool QgsProcessingParameterTableField::fromVariantMap( const QVariantMap &map ) +bool QgsProcessingParameterField::fromVariantMap( const QVariantMap &map ) { QgsProcessingParameterDefinition::fromVariantMap( map ); mParentLayerParameter = map.value( QStringLiteral( "parent_layer" ) ).toString(); @@ -2208,3 +2250,100 @@ bool QgsProcessingParameterFolderOutput::checkValueIsAcceptable( const QVariant return true; } + +QgsProcessingParameterVectorOutput::QgsProcessingParameterVectorOutput( const QString &name, const QString &description, QgsProcessingParameterDefinition::LayerType type, const QVariant &defaultValue, bool optional ) + : QgsProcessingParameterDefinition( name, description, defaultValue, optional ) + , mDataType( type ) +{ + +} + +bool QgsProcessingParameterVectorOutput::checkValueIsAcceptable( const QVariant &input, QgsProcessingContext * ) const +{ + QVariant var = input; + if ( !var.isValid() ) + return mFlags & FlagOptional; + + if ( var.canConvert() ) + { + QgsProcessingOutputLayerDefinition fromVar = qvariant_cast( var ); + var = fromVar.sink; + } + + if ( var.canConvert() ) + { + return true; + } + + if ( var.type() != QVariant::String ) + return false; + + if ( var.toString().isEmpty() ) + return mFlags & FlagOptional; + + return true; +} + +QString QgsProcessingParameterVectorOutput::valueAsPythonString( const QVariant &value, QgsProcessingContext & ) const +{ + if ( value.canConvert() ) + return QStringLiteral( "QgsProperty.fromExpression('%1')" ).arg( value.value< QgsProperty >().asExpression() ); + + if ( value.canConvert() ) + { + QgsProcessingOutputLayerDefinition fromVar = qvariant_cast( value ); + if ( fromVar.sink.propertyType() == QgsProperty::StaticProperty ) + { + return QStringLiteral( "QgsProcessingOutputLayerDefinition('%1')" ).arg( fromVar.sink.staticValue().toString() ); + } + else + { + return QStringLiteral( "QgsProcessingOutputLayerDefinition(QgsProperty.fromExpression('%1'))" ).arg( fromVar.sink.asExpression() ); + } + } + + return value.toString().prepend( '\'' ).append( '\'' ); +} + +QgsProcessingParameterDefinition::LayerType QgsProcessingParameterVectorOutput::dataType() const +{ + return mDataType; +} + +bool QgsProcessingParameterVectorOutput::hasGeometry() const +{ + switch ( mDataType ) + { + case TypeAny: + case TypeVectorAny: + case TypeVectorPoint: + case TypeVectorLine: + case TypeVectorPolygon: + case TypeTable: + return true; + + case TypeRaster: + case TypeFile: + return false; + } + return true; +} + +void QgsProcessingParameterVectorOutput::setDataType( QgsProcessingParameterDefinition::LayerType type ) +{ + mDataType = type; +} + +QVariantMap QgsProcessingParameterVectorOutput::toVariantMap() const +{ + QVariantMap map = QgsProcessingParameterDefinition::toVariantMap(); + map.insert( QStringLiteral( "data_type" ), mDataType ); + return map; +} + +bool QgsProcessingParameterVectorOutput::fromVariantMap( const QVariantMap &map ) +{ + QgsProcessingParameterDefinition::fromVariantMap( map ); + mDataType = static_cast< QgsProcessingParameterDefinition::LayerType >( map.value( QStringLiteral( "data_type" ) ).toInt() ); + return true; +} diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index 97617caddb4..edcf74526b8 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -198,14 +198,16 @@ class CORE_EXPORT QgsProcessingParameterDefinition sipType = sipType_QgsProcessingParameterString; else if ( sipCpp->type() == "expression" ) sipType = sipType_QgsProcessingParameterExpression; - else if ( sipCpp->type() == "table" ) - sipType = sipType_QgsProcessingParameterTable; + else if ( sipCpp->type() == "vector" ) + sipType = sipType_QgsProcessingParameterVectorLayer; else if ( sipCpp->type() == "field" ) - sipType = sipType_QgsProcessingParameterTableField; + sipType = sipType_QgsProcessingParameterField; else if ( sipCpp->type() == "source" ) sipType = sipType_QgsProcessingParameterFeatureSource; else if ( sipCpp->type() == "sink" ) sipType = sipType_QgsProcessingParameterFeatureSink; + else if ( sipCpp->type() == "vectorOut" ) + sipType = sipType_QgsProcessingParameterVectorOutput; else if ( sipCpp->type() == "rasterOut" ) sipType = sipType_QgsProcessingParameterRasterOutput; else if ( sipCpp->type() == "fileOut" ) @@ -1121,32 +1123,35 @@ class CORE_EXPORT QgsProcessingParameterExpression : public QgsProcessingParamet }; /** - * \class QgsProcessingParameterTable + * \class QgsProcessingParameterVectorLayer * \ingroup core - * A table (i.e. vector layers with or without geometry) parameter for processing algorithms. + * A vector layer (with or without geometry) parameter for processing algorithms. Consider using + * the more versatile QgsProcessingParameterFeatureSource wherever possible. * \since QGIS 3.0 */ -class CORE_EXPORT QgsProcessingParameterTable : public QgsProcessingParameterDefinition +class CORE_EXPORT QgsProcessingParameterVectorLayer : public QgsProcessingParameterDefinition { public: /** - * Constructor for QgsProcessingParameterTable. + * Constructor for QgsProcessingParameterVectorLayer. */ - QgsProcessingParameterTable( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), - bool optional = false ); + QgsProcessingParameterVectorLayer( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + bool optional = false ); - QString type() const override { return QStringLiteral( "table" ); } + QString type() const override { return QStringLiteral( "vector" ); } + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; }; /** - * \class QgsProcessingParameterTableField + * \class QgsProcessingParameterField * \ingroup core - * A table field parameter for processing algorithms. + * A vector layer or feature source field parameter for processing algorithms. * \since QGIS 3.0 */ -class CORE_EXPORT QgsProcessingParameterTableField : public QgsProcessingParameterDefinition +class CORE_EXPORT QgsProcessingParameterField : public QgsProcessingParameterDefinition { public: @@ -1160,13 +1165,13 @@ class CORE_EXPORT QgsProcessingParameterTableField : public QgsProcessingParamet }; /** - * Constructor for QgsProcessingParameterTableField. + * Constructor for QgsProcessingParameterField. */ - QgsProcessingParameterTableField( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), - const QString &parentLayerParameterName = QString(), - DataType type = Any, - bool allowMultiple = false, - bool optional = false ); + QgsProcessingParameterField( const QString &name, const QString &description = QString(), const QVariant &defaultValue = QVariant(), + const QString &parentLayerParameterName = QString(), + DataType type = Any, + bool allowMultiple = false, + bool optional = false ); QString type() const override { return QStringLiteral( "field" ); } bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; @@ -1311,6 +1316,55 @@ class CORE_EXPORT QgsProcessingParameterFeatureSink : public QgsProcessingParame QgsProcessingParameterDefinition::LayerType mDataType = QgsProcessingParameterDefinition::TypeVectorAny; }; + +/** + * \class QgsProcessingParameterVectorOutput + * \ingroup core + * A vector layer output parameter. Consider using the more flexible QgsProcessingParameterFeatureSink wherever + * possible. + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsProcessingParameterVectorOutput : public QgsProcessingParameterDefinition +{ + public: + + /** + * Constructor for QgsProcessingParameterVectorOutput. + */ + QgsProcessingParameterVectorOutput( const QString &name, const QString &description = QString(), QgsProcessingParameterDefinition::LayerType type = QgsProcessingParameterDefinition::TypeVectorAny, const QVariant &defaultValue = QVariant(), + bool optional = false ); + + QString type() const override { return QStringLiteral( "vectorOut" ); } + bool isDestination() const override { return true; } + bool checkValueIsAcceptable( const QVariant &input, QgsProcessingContext *context = nullptr ) const override; + QString valueAsPythonString( const QVariant &value, QgsProcessingContext &context ) const override; + + /** + * Returns the layer type for layers associated with the parameter. + * \see setDataType() + */ + QgsProcessingParameterDefinition::LayerType dataType() const; + + /** + * Returns true if the layer is likely to include geometries. In cases were presence of geometry + * cannot be reliably determined in advance, this method will default to returning true. + */ + bool hasGeometry() const; + + /** + * Sets the layer \a type for the layers associated with the parameter. + * \see dataType() + */ + void setDataType( QgsProcessingParameterDefinition::LayerType type ); + + QVariantMap toVariantMap() const override; + bool fromVariantMap( const QVariantMap &map ) override; + + private: + + QgsProcessingParameterDefinition::LayerType mDataType = QgsProcessingParameterDefinition::TypeVectorAny; +}; + /** * \class QgsProcessingParameterRasterOutput * \ingroup core diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index c491fd01840..71c4285e83e 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -314,8 +314,10 @@ class TestQgsProcessing: public QObject void parameterString(); void parameterExpression(); void parameterField(); + void parameterVectorLayer(); void parameterFeatureSource(); void parameterFeatureSink(); + void parameterVectorOut(); void parameterRasterOut(); void parameterFileOut(); void parameterFolderOut(); @@ -2507,7 +2509,7 @@ void TestQgsProcessing::parameterField() QgsProcessingContext context; // not optional! - std::unique_ptr< QgsProcessingParameterTableField > def( new QgsProcessingParameterTableField( "non_optional", QString(), QString(), QString(), QgsProcessingParameterTableField::Any, false, false ) ); + std::unique_ptr< QgsProcessingParameterField > def( new QgsProcessingParameterField( "non_optional", QString(), QString(), QString(), QgsProcessingParameterField::Any, false, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); @@ -2525,7 +2527,7 @@ void TestQgsProcessing::parameterField() QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); // multiple - def.reset( new QgsProcessingParameterTableField( "non_optional", QString(), QString(), QString(), QgsProcessingParameterTableField::Any, true, false ) ); + def.reset( new QgsProcessingParameterField( "non_optional", QString(), QString(), QString(), QgsProcessingParameterField::Any, true, false ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); @@ -2544,7 +2546,7 @@ void TestQgsProcessing::parameterField() QCOMPARE( def->valueAsPythonString( QStringList() << "a" << "b", context ), QStringLiteral( "['a','b']" ) ); QVariantMap map = def->toVariantMap(); - QgsProcessingParameterTableField fromMap( "x" ); + QgsProcessingParameterField fromMap( "x" ); QVERIFY( fromMap.fromVariantMap( map ) ); QCOMPARE( fromMap.name(), def->name() ); QCOMPARE( fromMap.description(), def->description() ); @@ -2553,11 +2555,11 @@ void TestQgsProcessing::parameterField() QCOMPARE( fromMap.parentLayerParameter(), def->parentLayerParameter() ); QCOMPARE( fromMap.dataType(), def->dataType() ); QCOMPARE( fromMap.allowMultiple(), def->allowMultiple() ); - def.reset( dynamic_cast< QgsProcessingParameterTableField *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); - QVERIFY( dynamic_cast< QgsProcessingParameterTableField *>( def.get() ) ); + def.reset( dynamic_cast< QgsProcessingParameterField *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterField *>( def.get() ) ); // optional - def.reset( new QgsProcessingParameterTableField( "optional", QString(), QString( "def" ), QString(), QgsProcessingParameterTableField::Any, false, true ) ); + def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "def" ), QString(), QgsProcessingParameterField::Any, false, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( !def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); @@ -2570,13 +2572,13 @@ void TestQgsProcessing::parameterField() QCOMPARE( fields, QStringList() << "def" ); // optional, no default - def.reset( new QgsProcessingParameterTableField( "optional", QString(), QVariant(), QString(), QgsProcessingParameterTableField::Any, false, true ) ); + def.reset( new QgsProcessingParameterField( "optional", QString(), QVariant(), QString(), QgsProcessingParameterField::Any, false, true ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QVERIFY( fields.isEmpty() ); //optional with multiples - def.reset( new QgsProcessingParameterTableField( "optional", QString(), QString( "abc;def" ), QString(), QgsProcessingParameterTableField::Any, true, true ) ); + def.reset( new QgsProcessingParameterField( "optional", QString(), QString( "abc;def" ), QString(), QgsProcessingParameterField::Any, true, true ) ); QVERIFY( def->checkValueIsAcceptable( 1 ) ); QVERIFY( def->checkValueIsAcceptable( "test" ) ); QVERIFY( def->checkValueIsAcceptable( QStringList() << "a" << "b" ) ); @@ -2587,12 +2589,102 @@ void TestQgsProcessing::parameterField() params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "abc" << "def" ); - def.reset( new QgsProcessingParameterTableField( "optional", QString(), QVariantList() << "abc" << "def", QString(), QgsProcessingParameterTableField::Any, true, true ) ); + def.reset( new QgsProcessingParameterField( "optional", QString(), QVariantList() << "abc" << "def", QString(), QgsProcessingParameterField::Any, true, true ) ); params.insert( "optional", QVariant() ); fields = QgsProcessingParameters::parameterAsFields( def.get(), params, context ); QCOMPARE( fields, QStringList() << "abc" << "def" ); } +void TestQgsProcessing::parameterVectorLayer() +{ + // setup a context + QgsProject p; + p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); + QString testDataDir = QStringLiteral( TEST_DATA_DIR ) + '/'; //defined in CmakeLists.txt + QString vector1 = testDataDir + "multipoint.shp"; + QString raster = testDataDir + "landsat.tif"; + QFileInfo fi1( raster ); + QFileInfo fi2( vector1 ); + QgsRasterLayer *r1 = new QgsRasterLayer( fi1.filePath(), "R1" ); + QgsVectorLayer *v1 = new QgsVectorLayer( fi2.filePath(), "V4", "ogr" ); + p.addMapLayers( QList() << v1 << r1 ); + QgsProcessingContext context; + context.setProject( &p ); + + // not optional! + std::unique_ptr< QgsProcessingParameterVectorLayer > def( new QgsProcessingParameterVectorLayer( "non_optional", QString(), QString( "somelayer" ), false ) ); + QVERIFY( !def->checkValueIsAcceptable( false ) ); + QVERIFY( !def->checkValueIsAcceptable( true ) ); + QVERIFY( !def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( !def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant::fromValue( v1 ) ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant::fromValue( r1 ) ) ); + + // should be OK + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); + // ... unless we use context, when the check that the layer actually exists is performed + QVERIFY( !def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); + + // using existing map layer ID + QVariantMap params; + params.insert( "non_optional", v1->id() ); + QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); + + // using existing layer + params.insert( "non_optional", QVariant::fromValue( v1 ) ); + QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); + + // not vector layer + params.insert( "non_optional", r1->id() ); + QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); + + // using existing non-vector layer + params.insert( "non_optional", QVariant::fromValue( r1 ) ); + QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); + + // string representing a layer source + params.insert( "non_optional", vector1 ); + QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->publicSource(), vector1 ); + + // nonsense string + params.insert( "non_optional", QString( "i'm not a layer, and nothing you can do will make me one" ) ); + QVERIFY( !QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context ) ); + + QCOMPARE( def->valueAsPythonString( vector1, context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); + QCOMPARE( def->valueAsPythonString( v1->id(), context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( v1 ), context ), QString( "'" ) + testDataDir + QStringLiteral( "multipoint.shp'" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + QVariantMap map = def->toVariantMap(); + QgsProcessingParameterVectorLayer fromMap( "x" ); + QVERIFY( fromMap.fromVariantMap( map ) ); + QCOMPARE( fromMap.name(), def->name() ); + QCOMPARE( fromMap.description(), def->description() ); + QCOMPARE( fromMap.flags(), def->flags() ); + QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); + def.reset( dynamic_cast< QgsProcessingParameterVectorLayer *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterVectorLayer *>( def.get() ) ); + + // optional + def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), v1->id(), true ) ); + params.insert( "optional", QVariant() ); + QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); + QVERIFY( def->checkValueIsAcceptable( false ) ); + QVERIFY( def->checkValueIsAcceptable( true ) ); + QVERIFY( def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); + QVERIFY( def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( def->checkValueIsAcceptable( QgsProcessingFeatureSourceDefinition( "layer1231123" ) ) ); + + //optional with direct layer default + def.reset( new QgsProcessingParameterVectorLayer( "optional", QString(), QVariant::fromValue( v1 ), true ) ); + QCOMPARE( QgsProcessingParameters::parameterAsVectorLayer( def.get(), params, context )->id(), v1->id() ); +} + void TestQgsProcessing::parameterFeatureSource() { // setup a context @@ -2747,6 +2839,68 @@ void TestQgsProcessing::parameterFeatureSink() } +void TestQgsProcessing::parameterVectorOut() +{ + // setup a context + QgsProject p; + p.setCrs( QgsCoordinateReferenceSystem::fromEpsgId( 28353 ) ); + QgsProcessingContext context; + context.setProject( &p ); + + // not optional! + std::unique_ptr< QgsProcessingParameterVectorOutput > def( new QgsProcessingParameterVectorOutput( "non_optional", QString(), QgsProcessingParameterDefinition::TypeVectorAny, QString( "EPSG:3113" ), false ) ); + QVERIFY( !def->checkValueIsAcceptable( false ) ); + QVERIFY( !def->checkValueIsAcceptable( true ) ); + QVERIFY( !def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( !def->checkValueIsAcceptable( "" ) ); + QVERIFY( !def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); + + // should be OK with or without context - it's an output layer! + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp", &context ) ); + + QCOMPARE( def->valueAsPythonString( QStringLiteral( "abc" ), context ), QStringLiteral( "'abc'" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( "abc" ) ), context ), QStringLiteral( "QgsProcessingOutputLayerDefinition('abc')" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromValue( "abc" ) ) ), context ), QStringLiteral( "QgsProcessingOutputLayerDefinition('abc')" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProcessingOutputLayerDefinition( QgsProperty::fromExpression( "\"abc\" || \"def\"" ) ) ), context ), QStringLiteral( "QgsProcessingOutputLayerDefinition(QgsProperty.fromExpression('\"abc\" || \"def\"'))" ) ); + QCOMPARE( def->valueAsPythonString( QVariant::fromValue( QgsProperty::fromExpression( "\"a\"=1" ) ), context ), QStringLiteral( "QgsProperty.fromExpression('\"a\"=1')" ) ); + + QVariantMap map = def->toVariantMap(); + QgsProcessingParameterVectorOutput fromMap( "x" ); + QVERIFY( fromMap.fromVariantMap( map ) ); + QCOMPARE( fromMap.name(), def->name() ); + QCOMPARE( fromMap.description(), def->description() ); + QCOMPARE( fromMap.flags(), def->flags() ); + QCOMPARE( fromMap.defaultValue(), def->defaultValue() ); + QCOMPARE( fromMap.dataType(), def->dataType() ); + def.reset( dynamic_cast< QgsProcessingParameterVectorOutput *>( QgsProcessingParameters::parameterFromVariantMap( map ) ) ); + QVERIFY( dynamic_cast< QgsProcessingParameterVectorOutput *>( def.get() ) ); + + // optional + def.reset( new QgsProcessingParameterVectorOutput( "optional", QString(), QgsProcessingParameterDefinition::TypeVectorAny, QString(), true ) ); + QVERIFY( !def->checkValueIsAcceptable( false ) ); + QVERIFY( !def->checkValueIsAcceptable( true ) ); + QVERIFY( !def->checkValueIsAcceptable( 5 ) ); + QVERIFY( def->checkValueIsAcceptable( "layer12312312" ) ); + QVERIFY( def->checkValueIsAcceptable( "c:/Users/admin/Desktop/roads_clipped_transformed_v1_reprojected_final_clipped_aAAA.shp" ) ); + QVERIFY( def->checkValueIsAcceptable( "" ) ); + QVERIFY( def->checkValueIsAcceptable( QVariant() ) ); + QVERIFY( def->checkValueIsAcceptable( QgsProcessingOutputLayerDefinition( "layer1231123" ) ) ); + + // test hasGeometry + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeAny ).hasGeometry() ); + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeVectorAny ).hasGeometry() ); + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeVectorPoint ).hasGeometry() ); + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeVectorLine ).hasGeometry() ); + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeVectorPolygon ).hasGeometry() ); + QVERIFY( !QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeRaster ).hasGeometry() ); + QVERIFY( !QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeFile ).hasGeometry() ); + QVERIFY( QgsProcessingParameterVectorOutput( "test", QString(), QgsProcessingParameterDefinition::TypeTable ).hasGeometry() ); + +} + void TestQgsProcessing::parameterRasterOut() { // setup a context