QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
qgsalgorithmdistancewithin.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsalgorithmdistancewithin.cpp
3 ---------------------
4 begin : August 2021
5 copyright : (C) 2021 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
19#include "qgsgeometryengine.h"
20#include "qgsvectorlayer.h"
21
23
24void QgsDistanceWithinAlgorithm::addDistanceParameter()
25{
26 std::unique_ptr< QgsProcessingParameterDistance > distanceParam( new QgsProcessingParameterDistance( QStringLiteral( "DISTANCE" ),
27 QObject::tr( "Where the features are within" ), 100, QStringLiteral( "INPUT" ), false, 0 ) );
28 distanceParam->setIsDynamic( true );
29 distanceParam->setDynamicPropertyDefinition( QgsPropertyDefinition( QStringLiteral( "Distance" ), QObject::tr( "Distance within" ), QgsPropertyDefinition::DoublePositive ) );
30 distanceParam->setDynamicLayerParameterName( QStringLiteral( "INPUT" ) );
31
32 addParameter( distanceParam.release() );
33}
34
35void QgsDistanceWithinAlgorithm::process( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
36 QgsFeatureSource *referenceSource,
37 double distance, const QgsProperty &distanceProperty,
38 const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
39 bool onlyRequireTargetIds,
40 QgsProcessingFeedback *feedback, QgsExpressionContext &expressionContext )
41{
42 // By default we will iterate over the reference source and match back
43 // to the target source. We do this on the assumption that the most common
44 // use case is joining a points layer to a polygon layer (e.g. findings
45 // points near a polygon), so by iterating
46 // over the polygons we can take advantage of prepared geometries for
47 // the spatial relationship test.
48 bool iterateOverTarget = false;
49
50 //
51 // Possible reasons to iterate over target are considered here
52 //
53 do
54 {
55 // If distance is dynamic, we MUST iterate over target
56 if ( distanceProperty.isActive() )
57 {
58 iterateOverTarget = true;
59 break;
60 }
61
62 // If reference needs reprojection, we MUST iterate over target
63 if ( targetSource->sourceCrs() != referenceSource->sourceCrs() )
64 {
65 iterateOverTarget = true;
66 break;
67 }
68
69 // if reference is POINTs and target is not, we prefer iterating
70 // over target, to benefit from preparation
71 if ( referenceSource->wkbType() == QgsWkbTypes::Point &&
72 targetSource->wkbType() != QgsWkbTypes::Point )
73 {
74 iterateOverTarget = true;
75 break;
76 }
77
78 // neither source nor target or both of them are POINTs, we will
79 // iterate over the source with FEWER features to prepare less
80 if ( targetSource->featureCount() < referenceSource->featureCount() )
81 {
82 iterateOverTarget = true;
83 break;
84 }
85 }
86 while ( 0 );
87
88 if ( iterateOverTarget )
89 {
90 processByIteratingOverTargetSource( context, targetSource, referenceSource,
91 distance, distanceProperty, handleFeatureFunction,
92 onlyRequireTargetIds, feedback, expressionContext );
93 }
94 else
95 {
96 processByIteratingOverReferenceSource( context, targetSource, referenceSource,
97 distance, handleFeatureFunction,
98 onlyRequireTargetIds, feedback );
99 }
100}
101
102void QgsDistanceWithinAlgorithm::processByIteratingOverTargetSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
103 QgsFeatureSource *referenceSource,
104 const double distance, const QgsProperty &distanceProperty,
105 const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
106 bool onlyRequireTargetIds,
107 QgsProcessingFeedback *feedback, QgsExpressionContext &expressionContext )
108{
110 feedback->pushWarning( QObject::tr( "No spatial index exists for intersect layer, performance will be severely degraded" ) );
111
112 QgsFeatureIds foundSet;
114 if ( onlyRequireTargetIds )
115 request.setNoAttributes();
116
117 const bool dynamicDistance = distanceProperty.isActive();
118
119 QgsFeatureIterator fIt = targetSource->getFeatures( request );
120 const double step = targetSource->featureCount() > 0 ? 100.0 / targetSource->featureCount() : 1;
121 int current = 0;
122 QgsFeature f;
123 while ( fIt.nextFeature( f ) )
124 {
125 if ( feedback->isCanceled() )
126 break;
127
128 if ( !f.hasGeometry() )
129 continue;
130
131 double currentDistance = distance;
132 if ( dynamicDistance )
133 {
134 expressionContext.setFeature( f );
135 currentDistance = distanceProperty.valueAsDouble( expressionContext, currentDistance );
136 }
137
138 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), currentDistance ).setNoAttributes().setDestinationCrs( targetSource->sourceCrs(), context.transformContext() );
139 // we only care IF there's ANY features within the target distance here, so fetch at most 1 feature
140 request.setLimit( 1 );
141
142 QgsFeatureIterator testFeatureIt = referenceSource->getFeatures( request );
143 QgsFeature testFeature;
144 if ( testFeatureIt.nextFeature( testFeature ) )
145 {
146 foundSet.insert( f.id() );
147 handleFeatureFunction( f );
148 }
149
150 current += 1;
151 feedback->setProgress( current * step );
152 }
153}
154
155void QgsDistanceWithinAlgorithm::processByIteratingOverReferenceSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
156 QgsFeatureSource *referenceSource,
157 const double distance,
158 const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
159 bool onlyRequireTargetIds,
160 QgsProcessingFeedback *feedback )
161{
163 feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
164
165 QgsFeatureIds foundSet;
166
168 QgsFeatureIterator fIt = referenceSource->getFeatures( request );
169 const double step = referenceSource->featureCount() > 0 ? 100.0 / referenceSource->featureCount() : 1;
170 int current = 0;
171 QgsFeature f;
172 while ( fIt.nextFeature( f ) )
173 {
174 if ( feedback->isCanceled() )
175 break;
176
177 if ( !f.hasGeometry() )
178 continue;
179
180 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), distance );
181 if ( onlyRequireTargetIds )
182 request.setNoAttributes();
183
184 QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
185 QgsFeature testFeature;
186 while ( testFeatureIt.nextFeature( testFeature ) )
187 {
188 if ( feedback->isCanceled() )
189 break;
190
191 if ( foundSet.contains( testFeature.id() ) )
192 {
193 // already added this one, no need for further tests
194 continue;
195 }
196
197 foundSet.insert( testFeature.id() );
198 handleFeatureFunction( testFeature );
199 }
200
201 current += 1;
202 feedback->setProgress( current * step );
203 }
204}
205
206
207//
208// QgsSelectWithinDistanceAlgorithm
209//
210
211void QgsSelectWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
212{
213 const QStringList methods = QStringList() << QObject::tr( "creating new selection" )
214 << QObject::tr( "adding to current selection" )
215 << QObject::tr( "selecting within current selection" )
216 << QObject::tr( "removing from current selection" );
217
218 addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
219 QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
220
221 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ),
222 QObject::tr( "By comparing to the features from" ),
223 QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
224 addDistanceParameter();
225
226 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
227 QObject::tr( "Modify current selection by" ),
228 methods, false, 0 ) );
229}
230
231QString QgsSelectWithinDistanceAlgorithm::name() const
232{
233 return QStringLiteral( "selectwithindistance" );
234}
235
236QgsProcessingAlgorithm::Flags QgsSelectWithinDistanceAlgorithm::flags() const
237{
239}
240
241QString QgsSelectWithinDistanceAlgorithm::displayName() const
242{
243 return QObject::tr( "Select within distance" );
244}
245
246QStringList QgsSelectWithinDistanceAlgorithm::tags() const
247{
248 return QObject::tr( "select,by,maximum,buffer" ).split( ',' );
249}
250
251QString QgsSelectWithinDistanceAlgorithm::group() const
252{
253 return QObject::tr( "Vector selection" );
254}
255
256QString QgsSelectWithinDistanceAlgorithm::groupId() const
257{
258 return QStringLiteral( "vectorselection" );
259}
260
261QString QgsSelectWithinDistanceAlgorithm::shortHelpString() const
262{
263 return QObject::tr( "This algorithm creates a selection in a vector layer. Features are selected wherever they are within "
264 "the specified maximum distance from the features in an additional reference layer." );
265}
266
267QgsSelectWithinDistanceAlgorithm *QgsSelectWithinDistanceAlgorithm::createInstance() const
268{
269 return new QgsSelectWithinDistanceAlgorithm();
270}
271
272QVariantMap QgsSelectWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
273{
274 QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
275 if ( !selectLayer )
276 throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
277
278 const Qgis::SelectBehavior method = static_cast< Qgis::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
279 const std::unique_ptr< QgsFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
280 if ( !referenceSource )
281 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
282
283 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
284 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
285 QgsProperty distanceProperty;
286 if ( dynamicDistance )
287 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();
288 QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
289 expressionContext.appendScope( selectLayer->createExpressionContextScope() );
290
291 QgsFeatureIds selectedIds;
292 auto addToSelection = [&]( const QgsFeature & feature )
293 {
294 selectedIds.insert( feature.id() );
295 };
296 process( context, selectLayer, referenceSource.get(), distance, distanceProperty, addToSelection, true, feedback, expressionContext );
297
298 selectLayer->selectByIds( selectedIds, method );
299 QVariantMap results;
300 results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
301 return results;
302}
303
304
305//
306// QgsExtractWithinDistanceAlgorithm
307//
308
309void QgsExtractWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
310{
311 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
312 QObject::tr( "Extract features from" ),
313 QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
314 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ),
315 QObject::tr( "By comparing to the features from" ),
316 QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
317 addDistanceParameter();
318
319 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
320}
321
322QString QgsExtractWithinDistanceAlgorithm::name() const
323{
324 return QStringLiteral( "extractwithindistance" );
325}
326
327QString QgsExtractWithinDistanceAlgorithm::displayName() const
328{
329 return QObject::tr( "Extract within distance" );
330}
331
332QStringList QgsExtractWithinDistanceAlgorithm::tags() const
333{
334 return QObject::tr( "extract,by,filter,select,maximum,buffer" ).split( ',' );
335}
336
337QString QgsExtractWithinDistanceAlgorithm::group() const
338{
339 return QObject::tr( "Vector selection" );
340}
341
342QString QgsExtractWithinDistanceAlgorithm::groupId() const
343{
344 return QStringLiteral( "vectorselection" );
345}
346
347QString QgsExtractWithinDistanceAlgorithm::shortHelpString() const
348{
349 return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
350 "input layer. Features are copied wherever they are within "
351 "the specified maximum distance from the features in an additional reference layer." );
352}
353
354QgsExtractWithinDistanceAlgorithm *QgsExtractWithinDistanceAlgorithm::createInstance() const
355{
356 return new QgsExtractWithinDistanceAlgorithm();
357}
358
359QVariantMap QgsExtractWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
360{
361 std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
362 if ( !input )
363 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
364 const std::unique_ptr< QgsFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
365 if ( !referenceSource )
366 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
367
368 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
369 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
370 QgsProperty distanceProperty;
371 if ( dynamicDistance )
372 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();
373 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, input.get() );
374
375 QString dest;
376 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
377
378 if ( !sink )
379 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
380
381 auto addToSink = [&]( const QgsFeature & feature )
382 {
383 QgsFeature f = feature;
384 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
385 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
386 };
387 process( context, input.get(), referenceSource.get(), distance, distanceProperty, addToSink, false, feedback, expressionContext );
388
389 QVariantMap results;
390 results.insert( QStringLiteral( "OUTPUT" ), dest );
391 return results;
392}
393
395
396
SelectBehavior
Specifies how a selection should be applied.
Definition: qgis.h:798
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
void setFeature(const QgsFeature &feature)
Convenience function for setting a feature for the context.
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsFeatureRequest & setLimit(long long limit)
Set the maximum number of features to request.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setDistanceWithin(const QgsGeometry &geometry, double distance)
Sets a reference geometry and a maximum distance from this geometry to retrieve features within.
@ FastInsert
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
An interface for objects which provide features via a getFeatures method.
virtual QgsCoordinateReferenceSystem sourceCrs() const =0
Returns the coordinate reference system for features in the source.
@ SpatialIndexNotPresent
No spatial index exists for the source.
virtual QgsWkbTypes::Type wkbType() const =0
Returns the geometry type for features returned by this source.
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
virtual long long featureCount() const =0
Returns the number of features contained in the source, or -1 if the feature count is unknown.
virtual SpatialIndexPresence hasSpatialIndex() const
Returns an enum value representing the presence of a valid spatial index on the source,...
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
QgsGeometry geometry
Definition: qgsfeature.h:67
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:233
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
bool isCanceled() const SIP_HOLDGIL
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:54
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:63
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
@ FlagNotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
@ FlagNoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
Contains information about the context in which a processing algorithm is executed.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
Custom exception class for processing related exceptions.
Definition: qgsexception.h:83
Base class for providing feedback from a processing algorithm.
virtual void pushWarning(const QString &warning)
Pushes a warning informational message from the algorithm.
A double numeric parameter for distance values.
An enum based parameter for processing algorithms, allowing for selection from predefined values.
A feature sink output for processing algorithms.
An input feature source (such as vector layers) parameter for processing algorithms.
A vector layer (with or without geometry) parameter for processing algorithms.
static bool isDynamic(const QVariantMap &parameters, const QString &name)
Returns true if the parameter with matching name is a dynamic parameter, and must be evaluated once f...
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:48
Definition for a property.
Definition: qgsproperty.h:46
@ DoublePositive
Positive double value (including 0)
Definition: qgsproperty.h:57
A store for object properties.
Definition: qgsproperty.h:230
QVariant value(const QgsExpressionContext &context, const QVariant &defaultValue=QVariant(), bool *ok=nullptr) const
Calculates the current value of the property, including any transforms which are set for the property...
bool isActive() const
Returns whether the property is currently active.
double valueAsDouble(const QgsExpressionContext &context, double defaultValue=0.0, bool *ok=nullptr) const
Calculates the current value of the property and interprets it as a double.
Represents a vector layer which manages a vector based data sets.
QgsExpressionContextScope * createExpressionContextScope() const FINAL
This method needs to be reimplemented in all classes which implement this interface and return an exp...
Q_INVOKABLE void selectByIds(const QgsFeatureIds &ids, Qgis::SelectBehavior behavior=Qgis::SelectBehavior::SetSelection)
Selects matching features using a list of feature IDs.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37