diff --git a/python/plugins/processing/algs/qgis/EquivalentNumField.py b/python/plugins/processing/algs/qgis/EquivalentNumField.py deleted file mode 100644 index 42129863073..00000000000 --- a/python/plugins/processing/algs/qgis/EquivalentNumField.py +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 -*- - -""" -*************************************************************************** - EquivalentNumField.py - --------------------- - Date : August 2012 - Copyright : (C) 2012 by Victor Olaya - Email : volayaf at gmail dot com -*************************************************************************** -* * -* This program is free software; you can redistribute it and/or modify * -* it under the terms of the GNU General Public License as published by * -* the Free Software Foundation; either version 2 of the License, or * -* (at your option) any later version. * -* * -*************************************************************************** -""" - -__author__ = 'Victor Olaya' -__date__ = 'August 2012' -__copyright__ = '(C) 2012, Victor Olaya' - -# This will get replaced with a git SHA1 when you do a git archive - -__revision__ = '$Format:%H$' - -from qgis.PyQt.QtCore import QVariant -from qgis.core import (QgsField, - QgsFeatureSink, - QgsProcessingParameterFeatureSource, - QgsProcessingParameterField, - QgsProcessingParameterFeatureSink) -from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm - - -class EquivalentNumField(QgisAlgorithm): - - INPUT = 'INPUT' - OUTPUT = 'OUTPUT' - FIELD = 'FIELD' - - def group(self): - return self.tr('Vector table') - - def groupId(self): - return 'vectortable' - - def __init__(self): - super().__init__() - - def initAlgorithm(self, config=None): - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, - self.tr('Input layer'))) - self.addParameter(QgsProcessingParameterField(self.FIELD, - self.tr('Class field'), - None, self.INPUT, QgsProcessingParameterField.Any)) - - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Layer with index field'))) - - def name(self): - return 'adduniquevalueindexfield' - - def displayName(self): - return self.tr('Add unique value index field') - - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fields = source.fields() - fields.append(QgsField('NUM_FIELD', QVariant.Int)) - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, source.wkbType(), source.sourceCrs()) - - field_name = self.parameterAsString(parameters, self.FIELD, context) - field_index = source.fields().lookupField(field_name) - - classes = {} - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 - for current, feature in enumerate(features): - if feedback.isCanceled(): - break - - feedback.setProgress(int(current * total)) - - attributes = feature.attributes() - clazz = attributes[field_index] - - if clazz not in classes: - classes[clazz] = len(list(classes.keys())) - - attributes.append(classes[clazz]) - feature.setAttributes(attributes) - sink.addFeature(feature, QgsFeatureSink.FastInsert) - - return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py index c461fc249e8..6b3c0652ae5 100644 --- a/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithmProvider.py @@ -58,7 +58,6 @@ from .DensifyGeometries import DensifyGeometries from .DensifyGeometriesInterval import DensifyGeometriesInterval from .Difference import Difference from .EliminateSelection import EliminateSelection -from .EquivalentNumField import EquivalentNumField from .ExecuteSQL import ExecuteSQL from .Explode import Explode from .ExportGeometryInfo import ExportGeometryInfo @@ -180,7 +179,6 @@ class QgisAlgorithmProvider(QgsProcessingProvider): DensifyGeometriesInterval(), Difference(), EliminateSelection(), - EquivalentNumField(), ExecuteSQL(), Explode(), ExportGeometryInfo(), diff --git a/python/plugins/processing/algs/qgis/VectorLayerHistogram.py b/python/plugins/processing/algs/qgis/VectorLayerHistogram.py index cf5da168ecd..b87fec33329 100644 --- a/python/plugins/processing/algs/qgis/VectorLayerHistogram.py +++ b/python/plugins/processing/algs/qgis/VectorLayerHistogram.py @@ -2,7 +2,7 @@ """ *************************************************************************** - EquivalentNumField.py + VectorLayerHistogram.py --------------------- Date : January 2013 Copyright : (C) 2013 by Victor Olaya diff --git a/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.gml b/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.gml new file mode 100644 index 00000000000..f778b595610 --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.gml @@ -0,0 +1,27 @@ + + + missing + + + + 0 + 2 + + + + + 1 + 1 + + + + + 2 + 0 + + + diff --git a/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.xsd b/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.xsd new file mode 100644 index 00000000000..0e7d63820fb --- /dev/null +++ b/python/plugins/processing/tests/testdata/expected/add_unique_field_summary.xsd @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 9196e78b115..a0db954238e 100755 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2534,7 +2534,7 @@ tests: fid: skip - - algorithm: qgis:adduniquevalueindexfield + - algorithm: native:adduniquevalueindexfield name: add unique field based on another field params: FIELD: id2 @@ -2546,6 +2546,24 @@ tests: name: expected/add_unique_field.gml type: vector + - algorithm: native:adduniquevalueindexfield + name: Add unique field summary + params: + FIELD: id2 + FIELD_NAME: classes + INPUT: + name: points.gml + type: vector + results: + SUMMARY_OUTPUT: + name: expected/add_unique_field_summary.gml + type: vector + compare: + fields: + fid: skip + pk: + - id2 + - algorithm: qgis:linestopolygons name: convert lines to polygon params: diff --git a/src/analysis/CMakeLists.txt b/src/analysis/CMakeLists.txt index ac15ac8bf5b..19b4337de48 100755 --- a/src/analysis/CMakeLists.txt +++ b/src/analysis/CMakeLists.txt @@ -65,6 +65,7 @@ SET(QGIS_ANALYSIS_SRCS processing/qgsalgorithmtransect.cpp processing/qgsalgorithmtransform.cpp processing/qgsalgorithmtranslate.cpp + processing/qgsalgorithmuniquevalueindex.cpp processing/qgsnativealgorithms.cpp diff --git a/src/analysis/processing/qgsalgorithmuniquevalueindex.cpp b/src/analysis/processing/qgsalgorithmuniquevalueindex.cpp new file mode 100644 index 00000000000..cbe49646dc9 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmuniquevalueindex.cpp @@ -0,0 +1,172 @@ +/*************************************************************************** + qgsalgorithmuniquevalueindex.cpp + --------------------- + begin : January 2018 + copyright : (C) 2018 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsalgorithmuniquevalueindex.h" + +///@cond PRIVATE + + +QgsProcessingAlgorithm::Flags QgsAddUniqueValueIndexAlgorithm::flags() const +{ + return QgsProcessingAlgorithm::flags() | QgsProcessingAlgorithm::FlagCanRunInBackground; +} + +QString QgsAddUniqueValueIndexAlgorithm::name() const +{ + return QStringLiteral( "adduniquevalueindexfield" ); +} + +QString QgsAddUniqueValueIndexAlgorithm::displayName() const +{ + return QObject::tr( "Add unique value index field" ); +} + +QStringList QgsAddUniqueValueIndexAlgorithm::tags() const +{ + return QObject::tr( "categorize,categories,category,reclassify,classes,create" ).split( ',' ); +} + +QString QgsAddUniqueValueIndexAlgorithm::group() const +{ + return QObject::tr( "Vector table" ); +} + +QString QgsAddUniqueValueIndexAlgorithm::groupId() const +{ + return QStringLiteral( "vectortable" ); +} + +void QgsAddUniqueValueIndexAlgorithm::initAlgorithm( const QVariantMap & ) +{ + addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); + addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELD" ), QObject::tr( "Class field" ), QVariant(), + QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any ) ); + addParameter( new QgsProcessingParameterString( QStringLiteral( "FIELD_NAME" ), + QObject::tr( "Output field name" ), QStringLiteral( "NUM_FIELD" ) ) ); + + std::unique_ptr< QgsProcessingParameterFeatureSink > classedOutput = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "OUTPUT" ), QObject::tr( "Layer with index field" ), QgsProcessing::TypeVectorAnyGeometry, QVariant(), true ); + classedOutput->setCreateByDefault( true ); + addParameter( classedOutput.release() ); + + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Layer with index field" ), QgsProcessing::TypeVector, QVariant(), true ) ); + + std::unique_ptr< QgsProcessingParameterFeatureSink > summaryOutput = qgis::make_unique< QgsProcessingParameterFeatureSink >( QStringLiteral( "SUMMARY_OUTPUT" ), QObject::tr( "Class summary" ), + QgsProcessing::TypeVector, QVariant(), true ); + summaryOutput->setCreateByDefault( false ); + addParameter( summaryOutput.release() ); +} + +QString QgsAddUniqueValueIndexAlgorithm::shortHelpString() const +{ + return QObject::tr( "This algorithm takes a vector layer and an attribute and adds a new numeric field. Values in this field correspond to values in the specified attribute, so features with the same " + "value for the attribute will have the same value in the new numeric field. This creates a numeric equivalent of the specified attribute, which defines the same classes.\n\n" + "The new attribute is not added to the input layer but a new layer is generated instead.\n\n" + "Optionally, a separate table can be output which contains a summary of the class field values mapped to the new unique numeric value." ); +} + +QgsAddUniqueValueIndexAlgorithm *QgsAddUniqueValueIndexAlgorithm::createInstance() const +{ + return new QgsAddUniqueValueIndexAlgorithm(); +} + +QVariantMap QgsAddUniqueValueIndexAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); + + QString newFieldName = parameterAsString( parameters, QStringLiteral( "FIELD_NAME" ), context ); + QgsFields fields = source->fields(); + QgsField newField = QgsField( newFieldName, QVariant::Int ); + fields.append( newField ); + + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, source->wkbType(), source->sourceCrs() ) ); + + QString sourceFieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); + int fieldIndex = source->fields().lookupField( sourceFieldName ); + if ( fieldIndex < 0 ) + throw QgsProcessingException( QObject::tr( "Invalid field name %1" ).arg( sourceFieldName ) ); + + QString summaryDest; + QgsFields summaryFields; + summaryFields.append( newField ); + summaryFields.append( source->fields().at( fieldIndex ) ); + std::unique_ptr< QgsFeatureSink > summarySink( parameterAsSink( parameters, QStringLiteral( "SUMMARY_OUTPUT" ), context, summaryDest, summaryFields, QgsWkbTypes::NoGeometry ) ); + + QHash< QVariant, int > classes; + + QgsFeatureIterator it = source->getFeatures(); + + long count = source->featureCount(); + double step = count > 0 ? 100.0 / count : 1; + int current = 0; + QgsFeature feature; + while ( it.nextFeature( feature ) ) + { + if ( feedback->isCanceled() ) + { + break; + } + + QgsAttributes attributes = feature.attributes(); + QVariant clazz = attributes.at( fieldIndex ); + + int thisValue = classes.value( clazz, -1 ); + if ( thisValue == -1 ) + { + thisValue = classes.count(); + classes.insert( clazz, thisValue ); + } + + if ( sink ) + { + attributes.append( thisValue ); + feature.setAttributes( attributes ); + sink->addFeature( feature, QgsFeatureSink::FastInsert ); + } + + feedback->setProgress( current * step ); + current++; + } + + if ( summarySink ) + { + //generate summary table - first we make a sorted version of the classes + QMap< int, QVariant > sorted; + for ( auto classIt = classes.constBegin(); classIt != classes.constEnd(); ++classIt ) + { + sorted.insert( classIt.value(), classIt.key() ); + } + // now save them + for ( auto sortedIt = sorted.constBegin(); sortedIt != sorted.constEnd(); ++sortedIt ) + { + QgsFeature f; + f.setAttributes( QgsAttributes() << sortedIt.key() << sortedIt.value() ); + summarySink->addFeature( f, QgsFeatureSink::FastInsert ); + } + } + + QVariantMap results; + if ( sink ) + results.insert( QStringLiteral( "OUTPUT" ), dest ); + if ( summarySink ) + results.insert( QStringLiteral( "SUMMARY_OUTPUT" ), summaryDest ); + return results; +} + +///@endcond diff --git a/src/analysis/processing/qgsalgorithmuniquevalueindex.h b/src/analysis/processing/qgsalgorithmuniquevalueindex.h new file mode 100644 index 00000000000..e5f2ec30670 --- /dev/null +++ b/src/analysis/processing/qgsalgorithmuniquevalueindex.h @@ -0,0 +1,58 @@ +/*************************************************************************** + qgsalgorithmuniquevalueindex.h + ------------------------------ + begin : January 2018 + copyright : (C) 2018 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSALGORITHMUNIQUEVALUEINDEX_H +#define QGSALGORITHMUNIQUEVALUEINDEX_H + +#define SIP_NO_FILE + +#include "qgis.h" +#include "qgsprocessingalgorithm.h" + +///@cond PRIVATE + +/** + * Native add unique value index field algorithm. + */ +class QgsAddUniqueValueIndexAlgorithm : public QgsProcessingAlgorithm +{ + + public: + + QgsAddUniqueValueIndexAlgorithm() = default; + QgsProcessingAlgorithm::Flags flags() const override; + void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override; + QString name() const override; + QString displayName() const override; + QStringList tags() const override; + QString group() const override; + QString groupId() const override; + QString shortHelpString() const override; + QgsAddUniqueValueIndexAlgorithm *createInstance() const override SIP_FACTORY; + + protected: + + QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + +}; + +///@endcond PRIVATE + +#endif // QGSALGORITHMUNIQUEVALUEINDEX_H + + diff --git a/src/analysis/processing/qgsnativealgorithms.cpp b/src/analysis/processing/qgsnativealgorithms.cpp index a0445807a27..ed268f4f847 100644 --- a/src/analysis/processing/qgsnativealgorithms.cpp +++ b/src/analysis/processing/qgsnativealgorithms.cpp @@ -62,6 +62,7 @@ #include "qgsalgorithmtransect.h" #include "qgsalgorithmtransform.h" #include "qgsalgorithmtranslate.h" +#include "qgsalgorithmuniquevalueindex.h" ///@cond PRIVATE @@ -98,6 +99,7 @@ bool QgsNativeAlgorithms::supportsNonFileBasedOutput() const void QgsNativeAlgorithms::loadAlgorithms() { addAlgorithm( new QgsAddIncrementalFieldAlgorithm() ); + addAlgorithm( new QgsAddUniqueValueIndexAlgorithm() ); addAlgorithm( new QgsAssignProjectionAlgorithm() ); addAlgorithm( new QgsBoundaryAlgorithm() ); addAlgorithm( new QgsBoundingBoxAlgorithm() );