[processing] Port 'Add unique value index field' to c++

And implement some fixes/improvements (refs discussion on the dev mailing list):
- allow user to specify created field name
- allow optional creation of a summary table showing new class value vs original
value
This commit is contained in:
Nyall Dawson 2018-01-23 10:04:02 +10:00
parent 40f09fab85
commit b8e1f7707c
10 changed files with 316 additions and 102 deletions

View File

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

View File

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

View File

@ -2,7 +2,7 @@
"""
***************************************************************************
EquivalentNumField.py
VectorLayerHistogram.py
---------------------
Date : January 2013
Copyright : (C) 2013 by Victor Olaya

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8" ?>
<ogr:FeatureCollection
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://ogr.maptools.org/ add_unique_field_summary.xsd"
xmlns:ogr="http://ogr.maptools.org/"
xmlns:gml="http://www.opengis.net/gml">
<gml:boundedBy><gml:null>missing</gml:null></gml:boundedBy>
<gml:featureMember>
<ogr:add_unique_field_summary fid="add_unique_field_summary.0">
<ogr:classes>0</ogr:classes>
<ogr:id2>2</ogr:id2>
</ogr:add_unique_field_summary>
</gml:featureMember>
<gml:featureMember>
<ogr:add_unique_field_summary fid="add_unique_field_summary.1">
<ogr:classes>1</ogr:classes>
<ogr:id2>1</ogr:id2>
</ogr:add_unique_field_summary>
</gml:featureMember>
<gml:featureMember>
<ogr:add_unique_field_summary fid="add_unique_field_summary.2">
<ogr:classes>2</ogr:classes>
<ogr:id2>0</ogr:id2>
</ogr:add_unique_field_summary>
</gml:featureMember>
</ogr:FeatureCollection>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema targetNamespace="http://ogr.maptools.org/" xmlns:ogr="http://ogr.maptools.org/" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:gml="http://www.opengis.net/gml" elementFormDefault="qualified" version="1.0">
<xs:import namespace="http://www.opengis.net/gml" schemaLocation="http://schemas.opengis.net/gml/2.1.2/feature.xsd"/>
<xs:element name="FeatureCollection" type="ogr:FeatureCollectionType" substitutionGroup="gml:_FeatureCollection"/>
<xs:complexType name="FeatureCollectionType">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureCollectionType">
<xs:attribute name="lockId" type="xs:string" use="optional"/>
<xs:attribute name="scope" type="xs:string" use="optional"/>
</xs:extension>
</xs:complexContent>
</xs:complexType>
<xs:element name="add_unique_field_summary" type="ogr:add_unique_field_summary_Type" substitutionGroup="gml:_Feature"/>
<xs:complexType name="add_unique_field_summary_Type">
<xs:complexContent>
<xs:extension base="gml:AbstractFeatureType">
<xs:sequence>
<xs:element name="classes" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
<xs:element name="id2" nillable="true" minOccurs="0" maxOccurs="1">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:totalDigits value="10"/>
</xs:restriction>
</xs:simpleType>
</xs:element>
</xs:sequence>
</xs:extension>
</xs:complexContent>
</xs:complexType>
</xs:schema>

View File

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

View File

@ -65,6 +65,7 @@ SET(QGIS_ANALYSIS_SRCS
processing/qgsalgorithmtransect.cpp
processing/qgsalgorithmtransform.cpp
processing/qgsalgorithmtranslate.cpp
processing/qgsalgorithmuniquevalueindex.cpp
processing/qgsnativealgorithms.cpp

View File

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

View File

@ -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 &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
};
///@endcond PRIVATE
#endif // QGSALGORITHMUNIQUEVALUEINDEX_H

View File

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