QGIS API Documentation  3.23.0-Master (c716e02dd3)
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 
24 void 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 
35 void 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 
102 void 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 {
109  if ( referenceSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent )
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 
155 void 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 
211 void 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 
231 QString QgsSelectWithinDistanceAlgorithm::name() const
232 {
233  return QStringLiteral( "selectwithindistance" );
234 }
235 
236 QgsProcessingAlgorithm::Flags QgsSelectWithinDistanceAlgorithm::flags() const
237 {
239 }
240 
241 QString QgsSelectWithinDistanceAlgorithm::displayName() const
242 {
243  return QObject::tr( "Select within distance" );
244 }
245 
246 QStringList QgsSelectWithinDistanceAlgorithm::tags() const
247 {
248  return QObject::tr( "select,maximum,buffer" ).split( ',' );
249 }
250 
251 QString QgsSelectWithinDistanceAlgorithm::group() const
252 {
253  return QObject::tr( "Vector selection" );
254 }
255 
256 QString QgsSelectWithinDistanceAlgorithm::groupId() const
257 {
258  return QStringLiteral( "vectorselection" );
259 }
260 
261 QString 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 
267 QgsSelectWithinDistanceAlgorithm *QgsSelectWithinDistanceAlgorithm::createInstance() const
268 {
269  return new QgsSelectWithinDistanceAlgorithm();
270 }
271 
272 QVariantMap 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 
309 void 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 
322 QString QgsExtractWithinDistanceAlgorithm::name() const
323 {
324  return QStringLiteral( "extractwithindistance" );
325 }
326 
327 QString QgsExtractWithinDistanceAlgorithm::displayName() const
328 {
329  return QObject::tr( "Extract within distance" );
330 }
331 
332 QStringList QgsExtractWithinDistanceAlgorithm::tags() const
333 {
334  return QObject::tr( "extract,filter,select,maximum,buffer" ).split( ',' );
335 }
336 
337 QString QgsExtractWithinDistanceAlgorithm::group() const
338 {
339  return QObject::tr( "Vector selection" );
340 }
341 
342 QString QgsExtractWithinDistanceAlgorithm::groupId() const
343 {
344  return QStringLiteral( "vectorselection" );
345 }
346 
347 QString 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 
354 QgsExtractWithinDistanceAlgorithm *QgsExtractWithinDistanceAlgorithm::createInstance() const
355 {
356  return new QgsExtractWithinDistanceAlgorithm();
357 }
358 
359 QVariantMap 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:559
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:223
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:47
@ DoublePositive
Positive double value (including 0)
Definition: qgsproperty.h:58
A store for object properties.
Definition: qgsproperty.h:231
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