QGIS API Documentation 3.37.0-Master (fdefdf9c27f)
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() == Qgis::WkbType::Point &&
72 targetSource->wkbType() != Qgis::WkbType::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 const QgsCoordinateReferenceSystem targetSourceCrs = targetSource->sourceCrs();
122 int current = 0;
123 QgsFeature f;
124 while ( fIt.nextFeature( f ) )
125 {
126 if ( feedback->isCanceled() )
127 break;
128
129 if ( !f.hasGeometry() )
130 continue;
131
132 double currentDistance = distance;
133 if ( dynamicDistance )
134 {
135 expressionContext.setFeature( f );
136 currentDistance = distanceProperty.valueAsDouble( expressionContext, currentDistance );
137 }
138
139 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), currentDistance ).setNoAttributes().setDestinationCrs( targetSourceCrs, context.transformContext() );
140 // we only care IF there's ANY features within the target distance here, so fetch at most 1 feature
141 request.setLimit( 1 );
142
143 QgsFeatureIterator testFeatureIt = referenceSource->getFeatures( request );
144 QgsFeature testFeature;
145 if ( testFeatureIt.nextFeature( testFeature ) )
146 {
147 foundSet.insert( f.id() );
148 handleFeatureFunction( f );
149 }
150
151 current += 1;
152 feedback->setProgress( current * step );
153 }
154}
155
156void QgsDistanceWithinAlgorithm::processByIteratingOverReferenceSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
157 QgsFeatureSource *referenceSource,
158 const double distance,
159 const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
160 bool onlyRequireTargetIds,
161 QgsProcessingFeedback *feedback )
162{
164 feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
165
166 QgsFeatureIds foundSet;
167
169 QgsFeatureIterator fIt = referenceSource->getFeatures( request );
170 const double step = referenceSource->featureCount() > 0 ? 100.0 / referenceSource->featureCount() : 1;
171 int current = 0;
172 QgsFeature f;
173 while ( fIt.nextFeature( f ) )
174 {
175 if ( feedback->isCanceled() )
176 break;
177
178 if ( !f.hasGeometry() )
179 continue;
180
181 request = QgsFeatureRequest().setDistanceWithin( f.geometry(), distance );
182 if ( onlyRequireTargetIds )
183 request.setNoAttributes();
184
185 QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
186 QgsFeature testFeature;
187 while ( testFeatureIt.nextFeature( testFeature ) )
188 {
189 if ( feedback->isCanceled() )
190 break;
191
192 if ( foundSet.contains( testFeature.id() ) )
193 {
194 // already added this one, no need for further tests
195 continue;
196 }
197
198 foundSet.insert( testFeature.id() );
199 handleFeatureFunction( testFeature );
200 }
201
202 current += 1;
203 feedback->setProgress( current * step );
204 }
205}
206
207
208//
209// QgsSelectWithinDistanceAlgorithm
210//
211
212void QgsSelectWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
213{
214 const QStringList methods = QStringList() << QObject::tr( "creating new selection" )
215 << QObject::tr( "adding to current selection" )
216 << QObject::tr( "selecting within current selection" )
217 << QObject::tr( "removing from current selection" );
218
219 addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
220 QList< int >() << static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
221
222 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ),
223 QObject::tr( "By comparing to the features from" ),
224 QList< int >() << static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
225 addDistanceParameter();
226
227 addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
228 QObject::tr( "Modify current selection by" ),
229 methods, false, 0 ) );
230}
231
232QString QgsSelectWithinDistanceAlgorithm::name() const
233{
234 return QStringLiteral( "selectwithindistance" );
235}
236
237Qgis::ProcessingAlgorithmFlags QgsSelectWithinDistanceAlgorithm::flags() const
238{
240}
241
242QString QgsSelectWithinDistanceAlgorithm::displayName() const
243{
244 return QObject::tr( "Select within distance" );
245}
246
247QStringList QgsSelectWithinDistanceAlgorithm::tags() const
248{
249 return QObject::tr( "select,by,maximum,buffer" ).split( ',' );
250}
251
252QString QgsSelectWithinDistanceAlgorithm::group() const
253{
254 return QObject::tr( "Vector selection" );
255}
256
257QString QgsSelectWithinDistanceAlgorithm::groupId() const
258{
259 return QStringLiteral( "vectorselection" );
260}
261
262QString QgsSelectWithinDistanceAlgorithm::shortHelpString() const
263{
264 return QObject::tr( "This algorithm creates a selection in a vector layer. Features are selected wherever they are within "
265 "the specified maximum distance from the features in an additional reference layer." );
266}
267
268QgsSelectWithinDistanceAlgorithm *QgsSelectWithinDistanceAlgorithm::createInstance() const
269{
270 return new QgsSelectWithinDistanceAlgorithm();
271}
272
273QVariantMap QgsSelectWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
274{
275 QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
276 if ( !selectLayer )
277 throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
278
279 const Qgis::SelectBehavior method = static_cast< Qgis::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
280 const std::unique_ptr< QgsFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
281 if ( !referenceSource )
282 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
283
284 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
285 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
286 QgsProperty distanceProperty;
287 if ( dynamicDistance )
288 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();
289 QgsExpressionContext expressionContext = createExpressionContext( parameters, context );
290 expressionContext.appendScope( selectLayer->createExpressionContextScope() );
291
292 QgsFeatureIds selectedIds;
293 auto addToSelection = [&]( const QgsFeature & feature )
294 {
295 selectedIds.insert( feature.id() );
296 };
297 process( context, selectLayer, referenceSource.get(), distance, distanceProperty, addToSelection, true, feedback, expressionContext );
298
299 selectLayer->selectByIds( selectedIds, method );
300 QVariantMap results;
301 results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
302 return results;
303}
304
305
306//
307// QgsExtractWithinDistanceAlgorithm
308//
309
310void QgsExtractWithinDistanceAlgorithm::initAlgorithm( const QVariantMap & )
311{
312 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
313 QObject::tr( "Extract features from" ),
314 QList< int >() << static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
315 addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "REFERENCE" ),
316 QObject::tr( "By comparing to the features from" ),
317 QList< int >() << static_cast< int >( Qgis::ProcessingSourceType::VectorAnyGeometry ) ) );
318 addDistanceParameter();
319
320 addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
321}
322
323QString QgsExtractWithinDistanceAlgorithm::name() const
324{
325 return QStringLiteral( "extractwithindistance" );
326}
327
328QString QgsExtractWithinDistanceAlgorithm::displayName() const
329{
330 return QObject::tr( "Extract within distance" );
331}
332
333QStringList QgsExtractWithinDistanceAlgorithm::tags() const
334{
335 return QObject::tr( "extract,by,filter,select,maximum,buffer" ).split( ',' );
336}
337
338QString QgsExtractWithinDistanceAlgorithm::group() const
339{
340 return QObject::tr( "Vector selection" );
341}
342
343QString QgsExtractWithinDistanceAlgorithm::groupId() const
344{
345 return QStringLiteral( "vectorselection" );
346}
347
348QString QgsExtractWithinDistanceAlgorithm::shortHelpString() const
349{
350 return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
351 "input layer. Features are copied wherever they are within "
352 "the specified maximum distance from the features in an additional reference layer." );
353}
354
355QgsExtractWithinDistanceAlgorithm *QgsExtractWithinDistanceAlgorithm::createInstance() const
356{
357 return new QgsExtractWithinDistanceAlgorithm();
358}
359
360QVariantMap QgsExtractWithinDistanceAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
361{
362 std::unique_ptr< QgsProcessingFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
363 if ( !input )
364 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
365 const std::unique_ptr< QgsFeatureSource > referenceSource( parameterAsSource( parameters, QStringLiteral( "REFERENCE" ), context ) );
366 if ( !referenceSource )
367 throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "REFERENCE" ) ) );
368
369 const double distance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
370 const bool dynamicDistance = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );
371 QgsProperty distanceProperty;
372 if ( dynamicDistance )
373 distanceProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >();
374 QgsExpressionContext expressionContext = createExpressionContext( parameters, context, input.get() );
375
376 QString dest;
377 std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
378
379 if ( !sink )
380 throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
381
382 auto addToSink = [&]( const QgsFeature & feature )
383 {
384 QgsFeature f = feature;
385 if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
386 throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
387 };
388 process( context, input.get(), referenceSource.get(), distance, distanceProperty, addToSink, false, feedback, expressionContext );
389
390 QVariantMap results;
391 results.insert( QStringLiteral( "OUTPUT" ), dest );
392 return results;
393}
394
396
397
@ VectorAnyGeometry
Any vector layer with geometry.
@ NotPresent
No spatial index exists for the source.
QFlags< ProcessingAlgorithmFlag > ProcessingAlgorithmFlags
Flags indicating how and when an algorithm operates and should be exposed to users.
Definition: qgis.h:2934
@ NotAvailableInStandaloneTool
Algorithm should not be available from the standalone "qgis_process" tool. Used to flag algorithms wh...
@ NoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
SelectBehavior
Specifies how a selection should be applied.
Definition: qgis.h:1358
This class represents a coordinate reference system (CRS).
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)
Fetch next feature and stores in f, returns true on success.
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.
virtual Qgis::WkbType wkbType() const =0
Returns the geometry type for features returned by this source.
virtual Qgis::SpatialIndexPresence hasSpatialIndex() const
Returns an enum value representing the presence of a valid spatial index on the 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.
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:230
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:53
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:61
virtual Qgis::ProcessingAlgorithmFlags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users.
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...
Definition for a property.
Definition: qgsproperty.h:45
@ DoublePositive
Positive double value (including 0)
Definition: qgsproperty.h:56
A store for object properties.
Definition: qgsproperty.h:228
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