mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-04 00:04:03 -04:00
port List unique values algorithm to C++
This commit is contained in:
parent
78482ef736
commit
91f1808cc9
@ -63,7 +63,6 @@ from .StatisticsByCategories import StatisticsByCategories
|
||||
from .TextToFloat import TextToFloat
|
||||
from .TinInterpolation import TinInterpolation
|
||||
from .TopoColors import TopoColor
|
||||
from .UniqueValues import UniqueValues
|
||||
from .VariableDistanceBuffer import VariableDistanceBuffer
|
||||
from .VectorLayerHistogram import VectorLayerHistogram
|
||||
from .VectorLayerScatterplot import VectorLayerScatterplot
|
||||
@ -121,7 +120,6 @@ class QgisAlgorithmProvider(QgsProcessingProvider):
|
||||
TextToFloat(),
|
||||
TinInterpolation(),
|
||||
TopoColor(),
|
||||
UniqueValues(),
|
||||
VariableDistanceBuffer(),
|
||||
VectorLayerHistogram(),
|
||||
VectorLayerScatterplot(),
|
||||
|
@ -1,211 +0,0 @@
|
||||
"""
|
||||
***************************************************************************
|
||||
UniqueValues.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"
|
||||
|
||||
import os
|
||||
import codecs
|
||||
|
||||
from qgis.PyQt.QtGui import QIcon
|
||||
|
||||
from qgis.core import (
|
||||
QgsApplication,
|
||||
QgsCoordinateReferenceSystem,
|
||||
QgsWkbTypes,
|
||||
QgsFeature,
|
||||
QgsFeatureSink,
|
||||
QgsFeatureRequest,
|
||||
QgsFields,
|
||||
QgsProcessing,
|
||||
QgsProcessingException,
|
||||
QgsProcessingParameterField,
|
||||
QgsProcessingParameterFeatureSource,
|
||||
QgsProcessingParameterFeatureSink,
|
||||
QgsProcessingOutputNumber,
|
||||
QgsProcessingOutputString,
|
||||
QgsProcessingFeatureSource,
|
||||
QgsProcessingParameterFileDestination,
|
||||
)
|
||||
|
||||
from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm
|
||||
|
||||
pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0]
|
||||
|
||||
|
||||
class UniqueValues(QgisAlgorithm):
|
||||
INPUT = "INPUT"
|
||||
FIELDS = "FIELDS"
|
||||
TOTAL_VALUES = "TOTAL_VALUES"
|
||||
UNIQUE_VALUES = "UNIQUE_VALUES"
|
||||
OUTPUT = "OUTPUT"
|
||||
OUTPUT_HTML_FILE = "OUTPUT_HTML_FILE"
|
||||
|
||||
def icon(self):
|
||||
return QgsApplication.getThemeIcon("/algorithms/mAlgorithmUniqueValues.svg")
|
||||
|
||||
def svgIconPath(self):
|
||||
return QgsApplication.iconPath("/algorithms/mAlgorithmUniqueValues.svg")
|
||||
|
||||
def group(self):
|
||||
return self.tr("Vector analysis")
|
||||
|
||||
def groupId(self):
|
||||
return "vectoranalysis"
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
def initAlgorithm(self, config=None):
|
||||
self.addParameter(
|
||||
QgsProcessingParameterFeatureSource(
|
||||
self.INPUT,
|
||||
self.tr("Input layer"),
|
||||
types=[QgsProcessing.SourceType.TypeVector],
|
||||
)
|
||||
)
|
||||
self.addParameter(
|
||||
QgsProcessingParameterField(
|
||||
self.FIELDS,
|
||||
self.tr("Target field(s)"),
|
||||
parentLayerParameterName=self.INPUT,
|
||||
type=QgsProcessingParameterField.DataType.Any,
|
||||
allowMultiple=True,
|
||||
)
|
||||
)
|
||||
|
||||
self.addParameter(
|
||||
QgsProcessingParameterFeatureSink(
|
||||
self.OUTPUT, self.tr("Unique values"), optional=True, defaultValue=None
|
||||
)
|
||||
)
|
||||
|
||||
self.addParameter(
|
||||
QgsProcessingParameterFileDestination(
|
||||
self.OUTPUT_HTML_FILE,
|
||||
self.tr("HTML report"),
|
||||
self.tr("HTML files (*.html)"),
|
||||
None,
|
||||
True,
|
||||
)
|
||||
)
|
||||
self.addOutput(
|
||||
QgsProcessingOutputNumber(self.TOTAL_VALUES, self.tr("Total unique values"))
|
||||
)
|
||||
self.addOutput(
|
||||
QgsProcessingOutputString(self.UNIQUE_VALUES, self.tr("Unique values"))
|
||||
)
|
||||
|
||||
def name(self):
|
||||
return "listuniquevalues"
|
||||
|
||||
def displayName(self):
|
||||
return self.tr("List unique values")
|
||||
|
||||
def processAlgorithm(self, parameters, context, feedback):
|
||||
source = self.parameterAsSource(parameters, self.INPUT, context)
|
||||
if source is None:
|
||||
raise QgsProcessingException(
|
||||
self.invalidSourceError(parameters, self.INPUT)
|
||||
)
|
||||
|
||||
field_names = self.parameterAsFields(parameters, self.FIELDS, context)
|
||||
|
||||
fields = QgsFields()
|
||||
field_indices = []
|
||||
for field_name in field_names:
|
||||
field_index = source.fields().lookupField(field_name)
|
||||
if field_index < 0:
|
||||
feedback.reportError(
|
||||
self.tr("Invalid field name {}").format(field_name)
|
||||
)
|
||||
continue
|
||||
field = source.fields()[field_index]
|
||||
fields.append(field)
|
||||
field_indices.append(field_index)
|
||||
(sink, dest_id) = self.parameterAsSink(
|
||||
parameters,
|
||||
self.OUTPUT,
|
||||
context,
|
||||
fields,
|
||||
QgsWkbTypes.Type.NoGeometry,
|
||||
QgsCoordinateReferenceSystem(),
|
||||
)
|
||||
|
||||
results = {}
|
||||
values = set()
|
||||
if len(field_indices) == 1:
|
||||
# one field, can use provider optimised method
|
||||
values = tuple([v] for v in source.uniqueValues(field_indices[0]))
|
||||
else:
|
||||
# have to scan whole table
|
||||
# TODO - add this support to QgsVectorDataProvider so we can run it on
|
||||
# the backend
|
||||
request = QgsFeatureRequest().setFlags(QgsFeatureRequest.Flag.NoGeometry)
|
||||
request.setSubsetOfAttributes(field_indices)
|
||||
total = 100.0 / source.featureCount() if source.featureCount() else 0
|
||||
for current, f in enumerate(
|
||||
source.getFeatures(
|
||||
request,
|
||||
QgsProcessingFeatureSource.Flag.FlagSkipGeometryValidityChecks,
|
||||
)
|
||||
):
|
||||
if feedback.isCanceled():
|
||||
break
|
||||
|
||||
value = tuple(f.attribute(i) for i in field_indices)
|
||||
values.add(value)
|
||||
feedback.setProgress(int(current * total))
|
||||
|
||||
if sink:
|
||||
for value in values:
|
||||
if feedback.isCanceled():
|
||||
break
|
||||
|
||||
f = QgsFeature()
|
||||
f.setAttributes([attr for attr in value])
|
||||
sink.addFeature(f, QgsFeatureSink.Flag.FastInsert)
|
||||
sink.finalize()
|
||||
results[self.OUTPUT] = dest_id
|
||||
|
||||
output_file = self.parameterAsFileOutput(
|
||||
parameters, self.OUTPUT_HTML_FILE, context
|
||||
)
|
||||
if output_file:
|
||||
self.createHTML(output_file, values)
|
||||
results[self.OUTPUT_HTML_FILE] = output_file
|
||||
|
||||
results[self.TOTAL_VALUES] = len(values)
|
||||
results[self.UNIQUE_VALUES] = ";".join(
|
||||
",".join(str(attr) for attr in v) for v in values
|
||||
)
|
||||
return results
|
||||
|
||||
def createHTML(self, outputFile, algData):
|
||||
with codecs.open(outputFile, "w", encoding="utf-8") as f:
|
||||
f.write("<html><head>")
|
||||
f.write(
|
||||
'<meta http-equiv="Content-Type" content="text/html; \
|
||||
charset=utf-8" /></head><body>'
|
||||
)
|
||||
f.write(self.tr("<p>Total unique values: ") + str(len(algData)) + "</p>")
|
||||
f.write(self.tr("<p>Unique values:</p>"))
|
||||
f.write("<ul>")
|
||||
for s in algData:
|
||||
f.write("<li>" + ",".join(str(attr) for attr in s) + "</li>")
|
||||
f.write("</ul></body></html>")
|
@ -58,7 +58,7 @@ def initMenusAndToolbars():
|
||||
"qgis:distancematrix": analysisToolsMenu,
|
||||
"native:sumlinelengths": analysisToolsMenu,
|
||||
"native:countpointsinpolygon": analysisToolsMenu,
|
||||
"qgis:listuniquevalues": analysisToolsMenu,
|
||||
"native:listuniquevalues": analysisToolsMenu,
|
||||
"native:basicstatisticsforfields": analysisToolsMenu,
|
||||
"native:nearestneighbouranalysis": analysisToolsMenu,
|
||||
"native:meancoordinates": analysisToolsMenu,
|
||||
|
@ -556,7 +556,7 @@ tests:
|
||||
- 'Mean length: 3.0'
|
||||
- 'NULL \(missing\) values: 1'
|
||||
|
||||
- algorithm: qgis:listuniquevalues
|
||||
- algorithm: native:listuniquevalues
|
||||
name: Unique values
|
||||
params:
|
||||
INPUT:
|
||||
|
@ -289,6 +289,7 @@ set(QGIS_ANALYSIS_SRCS
|
||||
processing/qgsalgorithmtranslate.cpp
|
||||
processing/qgsalgorithmtruncatetable.cpp
|
||||
processing/qgsalgorithmunion.cpp
|
||||
processing/qgsalgorithmuniquevalues.cpp
|
||||
processing/qgsalgorithmuniquevalueindex.cpp
|
||||
processing/qgsalgorithmurlopener.cpp
|
||||
processing/qgsalgorithmhttprequest.cpp
|
||||
|
193
src/analysis/processing/qgsalgorithmuniquevalues.cpp
Normal file
193
src/analysis/processing/qgsalgorithmuniquevalues.cpp
Normal file
@ -0,0 +1,193 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmuniquevalues.cpp
|
||||
---------------------
|
||||
begin : May 2025
|
||||
copyright : (C) 2025 by Alexander Bruy
|
||||
email : alexander dot bruy 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 "qgsalgorithmuniquevalues.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
QString QgsUniqueValuesAlgorithm::name() const
|
||||
{
|
||||
return QStringLiteral( "listuniquevalues" );
|
||||
}
|
||||
|
||||
QString QgsUniqueValuesAlgorithm::displayName() const
|
||||
{
|
||||
return QObject::tr( "List unique values" );
|
||||
}
|
||||
|
||||
QStringList QgsUniqueValuesAlgorithm::tags() const
|
||||
{
|
||||
return QObject::tr( "count,unique,values" ).split( ',' );
|
||||
}
|
||||
|
||||
QString QgsUniqueValuesAlgorithm::group() const
|
||||
{
|
||||
return QObject::tr( "Vector analysis" );
|
||||
}
|
||||
|
||||
QString QgsUniqueValuesAlgorithm::groupId() const
|
||||
{
|
||||
return QStringLiteral( "vectoranalysis" );
|
||||
}
|
||||
|
||||
QString QgsUniqueValuesAlgorithm::shortHelpString() const
|
||||
{
|
||||
return QObject::tr( "Returns list of unique values in given field(s) of a vector layer." );
|
||||
}
|
||||
|
||||
QgsUniqueValuesAlgorithm *QgsUniqueValuesAlgorithm::createInstance() const
|
||||
{
|
||||
return new QgsUniqueValuesAlgorithm();
|
||||
}
|
||||
|
||||
void QgsUniqueValuesAlgorithm::initAlgorithm( const QVariantMap & )
|
||||
{
|
||||
addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) );
|
||||
addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS" ), QObject::tr( "Target field(s)" ), QVariant(), QStringLiteral( "INPUT" ), Qgis::ProcessingFieldParameterDataType::Any, true ) );
|
||||
addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Unique values" ), Qgis::ProcessingSourceType::Vector, QVariant(), true ) );
|
||||
addParameter( new QgsProcessingParameterFileDestination( QStringLiteral( "OUTPUT_HTML_FILE" ), QObject::tr( "HTML report" ), QObject::tr( "HTML files (*.html *.htm)" ), QVariant(), true ) );
|
||||
addOutput( new QgsProcessingOutputNumber( QStringLiteral( "TOTAL_VALUES" ), QObject::tr( "Total unique values" ) ) );
|
||||
addOutput( new QgsProcessingOutputString( QStringLiteral( "UNIQUE_VALUES" ), QObject::tr( "Unique values" ) ) );
|
||||
}
|
||||
|
||||
QVariantMap QgsUniqueValuesAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
|
||||
{
|
||||
std::unique_ptr<QgsProcessingFeatureSource> source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
|
||||
if ( !source )
|
||||
throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
|
||||
|
||||
const QStringList fieldNames = parameterAsStrings( parameters, QStringLiteral( "FIELDS" ), context );
|
||||
const QString outputHtml = parameterAsFileOutput( parameters, QStringLiteral( "OUTPUT_HTML_FILE" ), context );
|
||||
|
||||
QgsFields fields;
|
||||
QList<int> fieldIndices;
|
||||
|
||||
for ( auto &fieldName : fieldNames )
|
||||
{
|
||||
int fieldIndex = source->fields().lookupField( fieldName );
|
||||
if ( fieldIndex < 0 )
|
||||
{
|
||||
feedback->reportError( QObject::tr( "Invalid field name &1" ).arg( fieldName ) );
|
||||
continue;
|
||||
}
|
||||
fields.append( source->fields().at( fieldIndex ) );
|
||||
fieldIndices << fieldIndex;
|
||||
}
|
||||
|
||||
QString dest;
|
||||
std::unique_ptr<QgsFeatureSink> sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, fields, Qgis::WkbType::NoGeometry, QgsCoordinateReferenceSystem() ) );
|
||||
if ( !sink )
|
||||
{
|
||||
throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
|
||||
}
|
||||
|
||||
QSet<QgsAttributes> values;
|
||||
if ( fieldIndices.size() == 1 )
|
||||
{
|
||||
const QSet<QVariant> unique = source->uniqueValues( fieldIndices.at( 0 ) );
|
||||
for ( auto &v : unique )
|
||||
{
|
||||
values.insert( QgsAttributes() << v );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// we have to scan whole table
|
||||
// TODO: add this support to QgsVectorDataProvider, so we can run it on the backend
|
||||
QgsFeatureRequest request;
|
||||
request.setFlags( Qgis::FeatureRequestFlag::NoGeometry );
|
||||
request.setSubsetOfAttributes( fieldIndices );
|
||||
|
||||
const double step = source->featureCount() > 0 ? 100.0 / source->featureCount() : 0;
|
||||
QgsFeatureIterator features = source->getFeatures( request );
|
||||
QgsFeature f;
|
||||
long long i = 0;
|
||||
while ( features.nextFeature( f ) )
|
||||
{
|
||||
if ( feedback->isCanceled() )
|
||||
break;
|
||||
|
||||
QgsAttributes attrs;
|
||||
for ( auto &i : std::as_const( fieldIndices ) )
|
||||
{
|
||||
attrs << f.attribute( i );
|
||||
}
|
||||
values.insert( attrs );
|
||||
|
||||
i++;
|
||||
feedback->setProgress( i * step );
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap outputs;
|
||||
outputs.insert( QStringLiteral( "TOTAL_VALUES" ), values.size() );
|
||||
|
||||
QStringList valueList;
|
||||
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
|
||||
{
|
||||
QStringList s;
|
||||
for ( auto &v : std::as_const( *it ) )
|
||||
{
|
||||
s.append( v.toString() );
|
||||
}
|
||||
valueList.append( s );
|
||||
}
|
||||
outputs.insert( QStringLiteral( "UNIQUE_VALUES" ), valueList.join( ';' ) );
|
||||
|
||||
if ( sink )
|
||||
{
|
||||
for ( auto it = values.constBegin(); it != values.constEnd(); ++it )
|
||||
{
|
||||
if ( feedback->isCanceled() )
|
||||
break;
|
||||
|
||||
QgsFeature f;
|
||||
f.setAttributes( *it );
|
||||
if ( !sink->addFeature( f, QgsFeatureSink::Flag::FastInsert ) )
|
||||
throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
|
||||
}
|
||||
sink->finalize();
|
||||
outputs.insert( QStringLiteral( "OUTPUT" ), dest );
|
||||
}
|
||||
|
||||
if ( !outputHtml.isEmpty() )
|
||||
{
|
||||
QFile file( outputHtml );
|
||||
if ( file.open( QIODevice::WriteOnly | QIODevice::Truncate ) )
|
||||
{
|
||||
QTextStream out( &file );
|
||||
#if QT_VERSION < QT_VERSION_CHECK( 6, 0, 0 )
|
||||
out.setCodec( "UTF-8" );
|
||||
#endif
|
||||
out << QStringLiteral( "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/></head><body>\n" );
|
||||
out << QObject::tr( "<p>Total unique values: %1</p>" ).arg( values.size() );
|
||||
out << QObject::tr( "<p>Unique values:</p>" );
|
||||
out << QStringLiteral( "<ul>" );
|
||||
for ( auto &v : std::as_const( valueList ) )
|
||||
{
|
||||
out << QStringLiteral( "<li>%1</li>" ).arg( v );
|
||||
}
|
||||
out << QStringLiteral( "</ul></body></html>" );
|
||||
|
||||
outputs.insert( QStringLiteral( "OUTPUT_HTML_FILE" ), outputHtml );
|
||||
}
|
||||
}
|
||||
|
||||
return outputs;
|
||||
}
|
||||
|
||||
///@endcond
|
52
src/analysis/processing/qgsalgorithmuniquevalues.h
Normal file
52
src/analysis/processing/qgsalgorithmuniquevalues.h
Normal file
@ -0,0 +1,52 @@
|
||||
/***************************************************************************
|
||||
qgsalgorithmuniquevalues.h
|
||||
---------------------
|
||||
begin : May 2025
|
||||
copyright : (C) 2025 by Alexander Bruy
|
||||
email : alexander dot bruy 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 QGSALGORITHMUNIQUEVALUES_H
|
||||
#define QGSALGORITHMUNIQUEVALUES_H
|
||||
|
||||
#define SIP_NO_FILE
|
||||
|
||||
#include "qgis_sip.h"
|
||||
#include "qgsprocessingalgorithm.h"
|
||||
#include "qgsapplication.h"
|
||||
|
||||
///@cond PRIVATE
|
||||
|
||||
/**
|
||||
* Native unique values algorithm.
|
||||
*/
|
||||
class QgsUniqueValuesAlgorithm : public QgsProcessingAlgorithm
|
||||
{
|
||||
public:
|
||||
QgsUniqueValuesAlgorithm() = default;
|
||||
void initAlgorithm( const QVariantMap &configuration = QVariantMap() ) override;
|
||||
QIcon icon() const override { return QgsApplication::getThemeIcon( QStringLiteral( "/algorithms/mAlgorithmUniqueValues.svg" ) ); }
|
||||
QString svgIconPath() const override { return QgsApplication::iconPath( QStringLiteral( "/algorithms/mAlgorithmUniqueValues.svg" ) ); }
|
||||
QString name() const override;
|
||||
QString displayName() const override;
|
||||
QStringList tags() const override;
|
||||
QString group() const override;
|
||||
QString groupId() const override;
|
||||
QString shortHelpString() const override;
|
||||
QgsUniqueValuesAlgorithm *createInstance() const override SIP_FACTORY;
|
||||
|
||||
protected:
|
||||
QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override;
|
||||
};
|
||||
|
||||
///@endcond PRIVATE
|
||||
|
||||
#endif // QGSALGORITHMUNIQUEVALUES_H
|
@ -272,6 +272,7 @@
|
||||
#include "qgsalgorithmtranslate.h"
|
||||
#include "qgsalgorithmtruncatetable.h"
|
||||
#include "qgsalgorithmunion.h"
|
||||
#include "qgsalgorithmuniquevalues.h"
|
||||
#include "qgsalgorithmuniquevalueindex.h"
|
||||
#include "qgsalgorithmurlopener.h"
|
||||
#include "qgsalgorithmhttprequest.h"
|
||||
@ -634,6 +635,7 @@ void QgsNativeAlgorithms::loadAlgorithms()
|
||||
addAlgorithm( new QgsTranslateAlgorithm() );
|
||||
addAlgorithm( new QgsTruncateTableAlgorithm() );
|
||||
addAlgorithm( new QgsUnionAlgorithm() );
|
||||
addAlgorithm( new QgsUniqueValuesAlgorithm() );
|
||||
addAlgorithm( new QgsUpdateLayerMetadataAlgorithm() );
|
||||
addAlgorithm( new QgsOpenUrlAlgorithm() );
|
||||
addAlgorithm( new QgsHttpRequestAlgorithm() );
|
||||
|
Loading…
x
Reference in New Issue
Block a user