Branch data Line data Source code
1 : : /***************************************************************************
2 : : qgsalgorithmextractbyattribute.cpp
3 : : ----------------------------------
4 : : begin : April 2017
5 : : copyright : (C) 2017 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 "qgsalgorithmextractbyattribute.h"
19 : :
20 : : ///@cond PRIVATE
21 : :
22 : 0 : QString QgsExtractByAttributeAlgorithm::name() const
23 : : {
24 : 0 : return QStringLiteral( "extractbyattribute" );
25 : : }
26 : :
27 : 0 : QString QgsExtractByAttributeAlgorithm::displayName() const
28 : : {
29 : 0 : return QObject::tr( "Extract by attribute" );
30 : : }
31 : :
32 : 0 : QStringList QgsExtractByAttributeAlgorithm::tags() const
33 : : {
34 : 0 : return QObject::tr( "extract,filter,attribute,value,contains,null,field" ).split( ',' );
35 : 0 : }
36 : :
37 : 0 : QString QgsExtractByAttributeAlgorithm::group() const
38 : : {
39 : 0 : return QObject::tr( "Vector selection" );
40 : : }
41 : :
42 : 0 : QString QgsExtractByAttributeAlgorithm::groupId() const
43 : : {
44 : 0 : return QStringLiteral( "vectorselection" );
45 : : }
46 : :
47 : 0 : void QgsExtractByAttributeAlgorithm::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( "FIELD" ), QObject::tr( "Selection attribute" ), QVariant(), QStringLiteral( "INPUT" ) ) );
52 : 0 : addParameter( new QgsProcessingParameterEnum( QStringLiteral( "OPERATOR" ), QObject::tr( "Operator" ), QStringList()
53 : 0 : << QObject::tr( "=" )
54 : 0 : << QObject::tr( "≠" )
55 : 0 : << QObject::tr( ">" )
56 : 0 : << QObject::tr( "≥" )
57 : 0 : << QObject::tr( "<" )
58 : 0 : << QObject::tr( "≤" )
59 : 0 : << QObject::tr( "begins with" )
60 : 0 : << QObject::tr( "contains" )
61 : 0 : << QObject::tr( "is null" )
62 : 0 : << QObject::tr( "is not null" )
63 : 0 : << QObject::tr( "does not contain" ), false, 0 ) );
64 : 0 : addParameter( new QgsProcessingParameterString( QStringLiteral( "VALUE" ), QObject::tr( "Value" ), QVariant(), false, true ) );
65 : :
66 : 0 : addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (attribute)" ) ) );
67 : 0 : QgsProcessingParameterFeatureSink *failOutput = new QgsProcessingParameterFeatureSink( QStringLiteral( "FAIL_OUTPUT" ), QObject::tr( "Extracted (non-matching)" ),
68 : 0 : QgsProcessing::TypeVectorAnyGeometry, QVariant(), true );
69 : 0 : failOutput->setCreateByDefault( false );
70 : 0 : addParameter( failOutput );
71 : 0 : }
72 : :
73 : 0 : QString QgsExtractByAttributeAlgorithm::shortHelpString() const
74 : : {
75 : 0 : return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an input layer. "
76 : : "The criteria for adding features to the resulting layer is defined based on the values "
77 : : "of an attribute from the input layer." );
78 : : }
79 : :
80 : 0 : QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::createInstance() const
81 : : {
82 : 0 : return new QgsExtractByAttributeAlgorithm();
83 : : }
84 : :
85 : 0 : QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
86 : : {
87 : 0 : std::unique_ptr< QgsProcessingFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
88 : 0 : if ( !source )
89 : 0 : throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
90 : :
91 : 0 : QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context );
92 : 0 : Operation op = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) );
93 : 0 : QString value = parameterAsString( parameters, QStringLiteral( "VALUE" ), context );
94 : :
95 : 0 : QString matchingSinkId;
96 : 0 : std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(),
97 : 0 : source->wkbType(), source->sourceCrs() ) );
98 : 0 : if ( !matchingSink )
99 : 0 : throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
100 : :
101 : 0 : QString nonMatchingSinkId;
102 : 0 : std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(),
103 : 0 : source->wkbType(), source->sourceCrs() ) );
104 : :
105 : 0 : int idx = source->fields().lookupField( fieldName );
106 : 0 : if ( idx < 0 )
107 : 0 : throw QgsProcessingException( QObject::tr( "Field '%1' was not found in INPUT source" ).arg( fieldName ) );
108 : :
109 : 0 : QVariant::Type fieldType = source->fields().at( idx ).type();
110 : :
111 : 0 : if ( fieldType != QVariant::String && ( op == BeginsWith || op == Contains || op == DoesNotContain ) )
112 : : {
113 : 0 : QString method;
114 : 0 : switch ( op )
115 : : {
116 : : case BeginsWith:
117 : 0 : method = QObject::tr( "begins with" );
118 : 0 : break;
119 : : case Contains:
120 : 0 : method = QObject::tr( "contains" );
121 : 0 : break;
122 : : case DoesNotContain:
123 : 0 : method = QObject::tr( "does not contain" );
124 : 0 : break;
125 : :
126 : : default:
127 : 0 : break;
128 : : }
129 : :
130 : 0 : throw QgsProcessingException( QObject::tr( "Operator '%1' can be used only with string fields." ).arg( method ) );
131 : 0 : }
132 : :
133 : 0 : QString fieldRef = QgsExpression::quotedColumnRef( fieldName );
134 : 0 : QString quotedVal = QgsExpression::quotedValue( value );
135 : 0 : QString expr;
136 : 0 : switch ( op )
137 : : {
138 : : case Equals:
139 : 0 : expr = QStringLiteral( "%1 = %3" ).arg( fieldRef, quotedVal );
140 : 0 : break;
141 : : case NotEquals:
142 : 0 : expr = QStringLiteral( "%1 != %3" ).arg( fieldRef, quotedVal );
143 : 0 : break;
144 : : case GreaterThan:
145 : 0 : expr = QStringLiteral( "%1 > %3" ).arg( fieldRef, quotedVal );
146 : 0 : break;
147 : : case GreaterThanEqualTo:
148 : 0 : expr = QStringLiteral( "%1 >= %3" ).arg( fieldRef, quotedVal );
149 : 0 : break;
150 : : case LessThan:
151 : 0 : expr = QStringLiteral( "%1 < %3" ).arg( fieldRef, quotedVal );
152 : 0 : break;
153 : : case LessThanEqualTo:
154 : 0 : expr = QStringLiteral( "%1 <= %3" ).arg( fieldRef, quotedVal );
155 : 0 : break;
156 : : case BeginsWith:
157 : 0 : expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, value );
158 : 0 : break;
159 : : case Contains:
160 : 0 : expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, value );
161 : 0 : break;
162 : : case IsNull:
163 : 0 : expr = QStringLiteral( "%1 IS NULL" ).arg( fieldRef );
164 : 0 : break;
165 : : case IsNotNull:
166 : 0 : expr = QStringLiteral( "%1 IS NOT NULL" ).arg( fieldRef );
167 : 0 : break;
168 : : case DoesNotContain:
169 : 0 : expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, value );
170 : 0 : break;
171 : : }
172 : :
173 : 0 : QgsExpression expression( expr );
174 : 0 : if ( expression.hasParserError() )
175 : : {
176 : 0 : throw QgsProcessingException( expression.parserErrorString() );
177 : : }
178 : :
179 : 0 : QgsExpressionContext expressionContext = createExpressionContext( parameters, context, source.get() );
180 : :
181 : 0 : long count = source->featureCount();
182 : :
183 : 0 : double step = count > 0 ? 100.0 / count : 1;
184 : 0 : int current = 0;
185 : :
186 : 0 : if ( !nonMatchingSink )
187 : : {
188 : : // not saving failing features - so only fetch good features
189 : 0 : QgsFeatureRequest req;
190 : 0 : req.setFilterExpression( expr );
191 : 0 : req.setExpressionContext( expressionContext );
192 : :
193 : 0 : QgsFeatureIterator it = source->getFeatures( req, QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
194 : 0 : QgsFeature f;
195 : 0 : while ( it.nextFeature( f ) )
196 : : {
197 : 0 : if ( feedback->isCanceled() )
198 : : {
199 : 0 : break;
200 : : }
201 : :
202 : 0 : matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
203 : :
204 : 0 : feedback->setProgress( current * step );
205 : 0 : current++;
206 : : }
207 : 0 : }
208 : : else
209 : : {
210 : : // saving non-matching features, so we need EVERYTHING
211 : 0 : expressionContext.setFields( source->fields() );
212 : 0 : expression.prepare( &expressionContext );
213 : :
214 : 0 : QgsFeatureIterator it = source->getFeatures( QgsFeatureRequest(), QgsProcessingFeatureSource::FlagSkipGeometryValidityChecks );
215 : 0 : QgsFeature f;
216 : 0 : while ( it.nextFeature( f ) )
217 : : {
218 : 0 : if ( feedback->isCanceled() )
219 : : {
220 : 0 : break;
221 : : }
222 : :
223 : 0 : expressionContext.setFeature( f );
224 : 0 : if ( expression.evaluate( &expressionContext ).toBool() )
225 : : {
226 : 0 : matchingSink->addFeature( f, QgsFeatureSink::FastInsert );
227 : 0 : }
228 : : else
229 : : {
230 : 0 : nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert );
231 : : }
232 : :
233 : 0 : feedback->setProgress( current * step );
234 : 0 : current++;
235 : : }
236 : 0 : }
237 : :
238 : :
239 : 0 : QVariantMap outputs;
240 : 0 : outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId );
241 : 0 : if ( nonMatchingSink )
242 : 0 : outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId );
243 : 0 : return outputs;
244 : 0 : }
245 : :
246 : : ///@endcond
247 : :
248 : :
|