Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmremoveduplicatesbyattribute.cpp
3 : : ----------------------------------
4 : : begin : October 2018
5 : : copyright : (C) 2018 by Nyall Dawson
6 : : email : nyall dot dawson at gmail dot com
7 : : ***************************************************************************/
8 : :
9 : : /***************************************************************************
10 : : * *
11 : : * This program is free software; you can redistribute it and/or modify *
12 : : * it under the terms of the GNU General Public License as published by *
13 : : * the Free Software Foundation; either version 2 of the License, or *
14 : : * (at your option) any later version. *
15 : : * *
16 : : ***************************************************************************/
17 : :
18 : : #include "qgsalgorithmremoveduplicatesbyattribute.h"
19 : :
20 : : ///@cond PRIVATE
21 : :
22 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::name() const
23 : : {
24 : 0 : return QStringLiteral( "removeduplicatesbyattribute" );
25 : : }
26 : :
27 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::displayName() const
28 : : {
29 : 0 : return QObject::tr( "Delete duplicates by attribute" );
30 : : }
31 : :
32 : 0 : QStringList QgsRemoveDuplicatesByAttributeAlgorithm::tags() const
33 : : {
34 : 0 : return QObject::tr( "drop,remove,field,value,same,filter" ).split( ',' );
35 : 0 : }
36 : :
37 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::group() const
38 : : {
39 : 0 : return QObject::tr( "Vector general" );
40 : : }
41 : :
42 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::groupId() const
43 : : {
44 : 0 : return QStringLiteral( "vectorgeneral" );
45 : : }
46 : :
47 : 0 : void QgsRemoveDuplicatesByAttributeAlgorithm::initAlgorithm( const QVariantMap & )
48 : : {
49 : 0 : addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ),
50 : 0 : QList< int >() << QgsProcessing::TypeVector ) );
51 : 0 : addParameter( new QgsProcessingParameterField( QStringLiteral( "FIELDS" ), QObject::tr( "Field to match duplicates by" ), QVariant(), QStringLiteral( "INPUT" ), QgsProcessingParameterField::Any, true ) );
52 : :
53 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Filtered (no duplicates)" ) ) );
54 : 0 : QgsProcessingParameterFeatureSink *failOutput = new QgsProcessingParameterFeatureSink( QStringLiteral( "DUPLICATES" ), QObject::tr( "Filtered (duplicates)" ),
55 : 0 : QgsProcessing::TypeVectorAnyGeometry, QVariant(), true );
56 : 0 : failOutput->setCreateByDefault( false );
57 : 0 : addParameter( failOutput );
58 : :
59 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "RETAINED_COUNT" ), QObject::tr( "Count of retained records" ) ) );
60 : 0 : addOutput( new QgsProcessingOutputNumber( QStringLiteral( "DUPLICATE_COUNT" ), QObject::tr( "Count of discarded duplicate records" ) ) );
61 : 0 : }
62 : :
63 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::shortHelpString() const
64 : : {
65 : 0 : return QObject::tr( "Removes duplicate rows by a field value (or multiple field values). The first matching row will be retained, and duplicates will be discarded.\n\n"
66 : : "Optionally, these duplicate records can be saved to a separate output for analysis." );
67 : : }
68 : :
69 : 0 : QString QgsRemoveDuplicatesByAttributeAlgorithm::shortDescription() const
70 : : {
71 : 0 : return QObject::tr( "Removes duplicate rows by a field value (or multiple field values)." );
72 : : }
73 : :
74 : 0 : QgsRemoveDuplicatesByAttributeAlgorithm *QgsRemoveDuplicatesByAttributeAlgorithm::createInstance() const
75 : : {
76 : 0 : return new QgsRemoveDuplicatesByAttributeAlgorithm();
77 : : }
78 : :
79 : 0 : QVariantMap QgsRemoveDuplicatesByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
80 : : {
81 : 0 : std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
82 : 0 : if ( !source )
83 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
84 : :
85 : 0 : const QStringList fieldNames = parameterAsFields( parameters, QStringLiteral( "FIELDS" ), context );
86 : :
87 : 0 : QgsAttributeList attributes;
88 : 0 : for ( const QString &field : fieldNames )
89 : : {
90 : 0 : const int index = source->fields().lookupField( field );
91 : 0 : if ( index < 0 )
92 : 0 : feedback->reportError( QObject::tr( "Field %1 not found in INPUT layer, skipping" ).arg( field ) );
93 : : else
94 : 0 : attributes.append( index );
95 : : }
96 : 0 : if ( attributes.isEmpty() )
97 : 0 : throw QgsProcessingException( QObject::tr( "No input fields found" ) );
98 : :
99 : :
100 : 0 : QString noDupeSinkId;
101 : 0 : std::unique_ptr< QgsFeatureSink > noDupeSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, noDupeSinkId, source->fields(),
102 : 0 : source->wkbType(), source->sourceCrs() ) );
103 : 0 : if ( !noDupeSink )
104 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
105 : :
106 : 0 : QString dupeSinkId;
107 : 0 : std::unique_ptr< QgsFeatureSink > dupesSink( parameterAsSink( parameters, QStringLiteral( "DUPLICATES" ), context, dupeSinkId, source->fields(),
108 : 0 : source->wkbType(), source->sourceCrs() ) );
109 : :
110 : 0 : const long count = source->featureCount();
111 : 0 : double step = count > 0 ? 100.0 / count : 1;
112 : 0 : int current = 0;
113 : :
114 : 0 : long long keptCount = 0;
115 : 0 : long long discardedCount = 0;
116 : :
117 : 0 : QSet< QVariantList > matched;
118 : :
119 : 0 : QgsFeatureIterator it = source->getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
120 : 0 : QgsFeature f;
121 : :
122 : 0 : QVariantList dupeKey;
123 : 0 : dupeKey.reserve( attributes.size() );
124 : 0 : for ( int i : attributes )
125 : : {
126 : : ( void )i;
127 : 0 : dupeKey.append( QVariant() );
128 : : }
129 : :
130 : 0 : while ( it.nextFeature( f ) )
131 : : {
132 : 0 : if ( feedback->isCanceled() )
133 : : {
134 : 0 : break;
135 : : }
136 : :
137 : 0 : int i = 0;
138 : 0 : for ( int attr : attributes )
139 : 0 : dupeKey[i++] = f.attribute( attr );
140 : :
141 : 0 : if ( matched.contains( dupeKey ) )
142 : : {
143 : : // duplicate
144 : 0 : discardedCount++;
145 : 0 : if ( dupesSink )
146 : 0 : dupesSink->addFeature( f, QgsFeatureSink::FastInsert );
147 : 0 : }
148 : : else
149 : : {
150 : : // not duplicate
151 : 0 : keptCount++;
152 : 0 : matched.insert( dupeKey );
153 : 0 : noDupeSink->addFeature( f, QgsFeatureSink::FastInsert );
154 : : }
155 : :
156 : 0 : feedback->setProgress( current * step );
157 : 0 : current++;
158 : : }
159 : :
160 : 0 : QVariantMap outputs;
161 : 0 : outputs.insert( QStringLiteral( "RETAINED_COUNT" ), keptCount );
162 : 0 : outputs.insert( QStringLiteral( "DUPLICATE_COUNT" ), discardedCount );
163 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), noDupeSinkId );
164 : 0 : if ( dupesSink )
165 : 0 : outputs.insert( QStringLiteral( "DUPLICATES" ), dupeSinkId );
166 : 0 : return outputs;
167 : 0 : }
168 : :
169 : : ///@endcond
170 : :
171 : :
|