mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-14 00:07:35 -04:00
[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:
parent
40f09fab85
commit
b8e1f7707c
@ -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}
|
@ -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(),
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
"""
|
||||
***************************************************************************
|
||||
EquivalentNumField.py
|
||||
VectorLayerHistogram.py
|
||||
---------------------
|
||||
Date : January 2013
|
||||
Copyright : (C) 2013 by Victor Olaya
|
||||
|
27
python/plugins/processing/tests/testdata/expected/add_unique_field_summary.gml
vendored
Normal file
27
python/plugins/processing/tests/testdata/expected/add_unique_field_summary.gml
vendored
Normal 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>
|
36
python/plugins/processing/tests/testdata/expected/add_unique_field_summary.xsd
vendored
Normal file
36
python/plugins/processing/tests/testdata/expected/add_unique_field_summary.xsd
vendored
Normal 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>
|
@ -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:
|
||||
|
@ -65,6 +65,7 @@ SET(QGIS_ANALYSIS_SRCS
|
||||
processing/qgsalgorithmtransect.cpp
|
||||
processing/qgsalgorithmtransform.cpp
|
||||
processing/qgsalgorithmtranslate.cpp
|
||||
processing/qgsalgorithmuniquevalueindex.cpp
|
||||
|
||||
processing/qgsnativealgorithms.cpp
|
||||
|
||||
|
172
src/analysis/processing/qgsalgorithmuniquevalueindex.cpp
Normal file
172
src/analysis/processing/qgsalgorithmuniquevalueindex.cpp
Normal 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 ¶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
|
58
src/analysis/processing/qgsalgorithmuniquevalueindex.h
Normal file
58
src/analysis/processing/qgsalgorithmuniquevalueindex.h
Normal 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 ¶meters,
|
||||
QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
#endif // QGSALGORITHMUNIQUEVALUEINDEX_H
|
||||
|
||||
|
@ -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() );
|
||||
|
Loading…
x
Reference in New Issue
Block a user