QGIS API Documentation  3.22.4-Białowieża (ce8e65e95e)
qgsalgorithmextractbylocation.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsalgorithmextractbylocation.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 
19 #include "qgsgeometryengine.h"
20 #include "qgsvectorlayer.h"
21 
23 
24 void QgsLocationBasedAlgorithm::addPredicateParameter()
25 {
26  std::unique_ptr< QgsProcessingParameterEnum > predicateParam( new QgsProcessingParameterEnum( QStringLiteral( "PREDICATE" ),
27  QObject::tr( "Where the features (geometric predicate)" ),
28  predicateOptionsList(), true, QVariant::fromValue( QList< int >() << 0 ) ) );
29 
30  QVariantMap predicateMetadata;
31  QVariantMap widgetMetadata;
32  widgetMetadata.insert( QStringLiteral( "useCheckBoxes" ), true );
33  widgetMetadata.insert( QStringLiteral( "columns" ), 2 );
34  predicateMetadata.insert( QStringLiteral( "widget_wrapper" ), widgetMetadata );
35  predicateParam->setMetadata( predicateMetadata );
36 
37  addParameter( predicateParam.release() );
38 }
39 
40 QgsLocationBasedAlgorithm::Predicate QgsLocationBasedAlgorithm::reversePredicate( QgsLocationBasedAlgorithm::Predicate predicate ) const
41 {
42  switch ( predicate )
43  {
44  case Intersects:
45  return Intersects;
46  case Contains:
47  return Within;
48  case Disjoint:
49  return Disjoint;
50  case IsEqual:
51  return IsEqual;
52  case Touches:
53  return Touches;
54  case Overlaps:
55  return Overlaps;
56  case Within:
57  return Contains;
58  case Crosses:
59  return Crosses;
60  }
61  // no warnings
62  return Intersects;
63 }
64 
65 QStringList QgsLocationBasedAlgorithm::predicateOptionsList() const
66 {
67  return QStringList() << QObject::tr( "intersect" )
68  << QObject::tr( "contain" )
69  << QObject::tr( "disjoint" )
70  << QObject::tr( "equal" )
71  << QObject::tr( "touch" )
72  << QObject::tr( "overlap" )
73  << QObject::tr( "are within" )
74  << QObject::tr( "cross" );
75 }
76 
77 void QgsLocationBasedAlgorithm::process( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
78  QgsFeatureSource *intersectSource,
79  const QList< int > &selectedPredicates,
80  const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
81  bool onlyRequireTargetIds,
82  QgsProcessingFeedback *feedback )
83 {
84 
85  if ( targetSource->featureCount() > 0 && intersectSource->featureCount() > 0
86  && targetSource->featureCount() < intersectSource->featureCount() )
87  {
88  // joining FEWER features to a layer with MORE features. So we iterate over the FEW features and find matches from the MANY
89  processByIteratingOverTargetSource( context, targetSource, intersectSource,
90  selectedPredicates, handleFeatureFunction,
91  onlyRequireTargetIds, feedback );
92  }
93  else
94  {
95  // default -- iterate over the intersect source and match back to the target source. We do this on the assumption that the most common
96  // use case is joining a points layer to a polygon layer (e.g. findings points within a polygon), so by iterating
97  // over the polygons we can take advantage of prepared geometries for the spatial relationship test.
98 
99  // TODO - consider using more heuristics to determine whether it's always best to iterate over the intersect
100  // source.
101  processByIteratingOverIntersectSource( context, targetSource, intersectSource,
102  selectedPredicates, handleFeatureFunction,
103  onlyRequireTargetIds, feedback );
104  }
105 }
106 
107 void QgsLocationBasedAlgorithm::processByIteratingOverTargetSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
108  QgsFeatureSource *intersectSource,
109  const QList< int > &selectedPredicates,
110  const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
111  bool onlyRequireTargetIds,
112  QgsProcessingFeedback *feedback )
113 {
114  if ( intersectSource->hasSpatialIndex() == QgsFeatureSource::SpatialIndexNotPresent )
115  feedback->pushWarning( QObject::tr( "No spatial index exists for intersect layer, performance will be severely degraded" ) );
116 
117  QgsFeatureIds foundSet;
119  if ( onlyRequireTargetIds )
120  request.setNoAttributes();
121 
122  QgsFeatureIterator fIt = targetSource->getFeatures( request );
123  double step = targetSource->featureCount() > 0 ? 100.0 / targetSource->featureCount() : 1;
124  int current = 0;
125  QgsFeature f;
126  std::unique_ptr< QgsGeometryEngine > engine;
127  while ( fIt.nextFeature( f ) )
128  {
129  if ( feedback->isCanceled() )
130  break;
131 
132  if ( !f.hasGeometry() )
133  continue;
134 
135  engine.reset();
136 
137  QgsRectangle bbox = f.geometry().boundingBox();
138  request = QgsFeatureRequest().setFilterRect( bbox ).setNoAttributes().setDestinationCrs( targetSource->sourceCrs(), context.transformContext() );
139 
140  QgsFeatureIterator testFeatureIt = intersectSource->getFeatures( request );
141  QgsFeature testFeature;
142  bool isMatch = false;
143  bool isDisjoint = true;
144  while ( testFeatureIt.nextFeature( testFeature ) )
145  {
146  if ( feedback->isCanceled() )
147  break;
148 
149  if ( !engine )
150  {
151  engine.reset( QgsGeometry::createGeometryEngine( f.geometry().constGet() ) );
152  engine->prepareGeometry();
153  }
154 
155  for ( int predicate : selectedPredicates )
156  {
157  switch ( static_cast< Predicate>( predicate ) )
158  {
159  case Intersects:
160  isMatch = engine->intersects( testFeature.geometry().constGet() );
161  break;
162  case Contains:
163  isMatch = engine->contains( testFeature.geometry().constGet() );
164  break;
165  case Disjoint:
166  if ( engine->intersects( testFeature.geometry().constGet() ) )
167  {
168  isDisjoint = false;
169  }
170  break;
171  case IsEqual:
172  isMatch = engine->isEqual( testFeature.geometry().constGet() );
173  break;
174  case Touches:
175  isMatch = engine->touches( testFeature.geometry().constGet() );
176  break;
177  case Overlaps:
178  isMatch = engine->overlaps( testFeature.geometry().constGet() );
179  break;
180  case Within:
181  isMatch = engine->within( testFeature.geometry().constGet() );
182  break;
183  case Crosses:
184  isMatch = engine->crosses( testFeature.geometry().constGet() );
185  break;
186  }
187 
188  if ( isMatch )
189  break;
190  }
191 
192  if ( isMatch )
193  {
194  foundSet.insert( f.id() );
195  handleFeatureFunction( f );
196  break;
197  }
198  }
199  if ( isDisjoint && selectedPredicates.contains( Disjoint ) )
200  {
201  foundSet.insert( f.id() );
202  handleFeatureFunction( f );
203  }
204 
205  current += 1;
206  feedback->setProgress( current * step );
207  }
208 }
209 
210 void QgsLocationBasedAlgorithm::processByIteratingOverIntersectSource( const QgsProcessingContext &context, QgsFeatureSource *targetSource,
211  QgsFeatureSource *intersectSource,
212  const QList< int > &selectedPredicates,
213  const std::function < void( const QgsFeature & ) > &handleFeatureFunction,
214  bool onlyRequireTargetIds,
215  QgsProcessingFeedback *feedback )
216 {
218  feedback->pushWarning( QObject::tr( "No spatial index exists for input layer, performance will be severely degraded" ) );
219 
220  // build a list of 'reversed' predicates, because in this function
221  // we actually test the reverse of what the user wants (allowing us
222  // to prepare geometries and optimise the algorithm)
223  QList< Predicate > predicates;
224  predicates.reserve( selectedPredicates.count() );
225  for ( int i : selectedPredicates )
226  {
227  predicates << reversePredicate( static_cast< Predicate >( i ) );
228  }
229 
230  QgsFeatureIds disjointSet;
231  if ( predicates.contains( Disjoint ) )
232  disjointSet = targetSource->allFeatureIds();
233 
234  QgsFeatureIds foundSet;
236  QgsFeatureIterator fIt = intersectSource->getFeatures( request );
237  double step = intersectSource->featureCount() > 0 ? 100.0 / intersectSource->featureCount() : 1;
238  int current = 0;
239  QgsFeature f;
240  std::unique_ptr< QgsGeometryEngine > engine;
241  while ( fIt.nextFeature( f ) )
242  {
243  if ( feedback->isCanceled() )
244  break;
245 
246  if ( !f.hasGeometry() )
247  continue;
248 
249  engine.reset();
250 
251  QgsRectangle bbox = f.geometry().boundingBox();
252  request = QgsFeatureRequest().setFilterRect( bbox );
253  if ( onlyRequireTargetIds )
254  request.setNoAttributes();
255 
256  QgsFeatureIterator testFeatureIt = targetSource->getFeatures( request );
257  QgsFeature testFeature;
258  while ( testFeatureIt.nextFeature( testFeature ) )
259  {
260  if ( feedback->isCanceled() )
261  break;
262 
263  if ( foundSet.contains( testFeature.id() ) )
264  {
265  // already added this one, no need for further tests
266  continue;
267  }
268  if ( predicates.count() == 1 && predicates.at( 0 ) == Disjoint && !disjointSet.contains( testFeature.id() ) )
269  {
270  // calculating only the disjoint set, and we've already eliminated this feature so no need for further tests
271  continue;
272  }
273 
274  if ( !engine )
275  {
276  engine.reset( QgsGeometry::createGeometryEngine( f.geometry().constGet() ) );
277  engine->prepareGeometry();
278  }
279 
280  bool isMatch = false;
281 
282  for ( Predicate predicate : std::as_const( predicates ) )
283  {
284  switch ( predicate )
285  {
286  case Intersects:
287  isMatch = engine->intersects( testFeature.geometry().constGet() );
288  break;
289  case Contains:
290  isMatch = engine->contains( testFeature.geometry().constGet() );
291  break;
292  case Disjoint:
293  if ( engine->intersects( testFeature.geometry().constGet() ) )
294  {
295  disjointSet.remove( testFeature.id() );
296  }
297  break;
298  case IsEqual:
299  isMatch = engine->isEqual( testFeature.geometry().constGet() );
300  break;
301  case Touches:
302  isMatch = engine->touches( testFeature.geometry().constGet() );
303  break;
304  case Overlaps:
305  isMatch = engine->overlaps( testFeature.geometry().constGet() );
306  break;
307  case Within:
308  isMatch = engine->within( testFeature.geometry().constGet() );
309  break;
310  case Crosses:
311  isMatch = engine->crosses( testFeature.geometry().constGet() );
312  break;
313  }
314  if ( isMatch )
315  break;
316  }
317 
318  if ( isMatch )
319  {
320  foundSet.insert( testFeature.id() );
321  handleFeatureFunction( testFeature );
322  }
323 
324  }
325 
326  current += 1;
327  feedback->setProgress( current * step );
328  }
329 
330  if ( predicates.contains( Disjoint ) )
331  {
332  disjointSet = disjointSet.subtract( foundSet );
333  QgsFeatureRequest disjointReq = QgsFeatureRequest().setFilterFids( disjointSet );
334  if ( onlyRequireTargetIds )
336  QgsFeatureIterator disjointIt = targetSource->getFeatures( disjointReq );
337  QgsFeature f;
338  while ( disjointIt.nextFeature( f ) )
339  {
340  handleFeatureFunction( f );
341  }
342  }
343 }
344 
345 
346 //
347 // QgsSelectByLocationAlgorithm
348 //
349 
350 void QgsSelectByLocationAlgorithm::initAlgorithm( const QVariantMap & )
351 {
352  QStringList methods = QStringList() << QObject::tr( "creating new selection" )
353  << QObject::tr( "adding to current selection" )
354  << QObject::tr( "selecting within current selection" )
355  << QObject::tr( "removing from current selection" );
356 
357  addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
358  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
359  addPredicateParameter();
360  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
361  QObject::tr( "By comparing to the features from" ),
362  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
363 
364  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
365  QObject::tr( "Modify current selection by" ),
366  methods, false, 0 ) );
367 }
368 
369 QString QgsSelectByLocationAlgorithm::name() const
370 {
371  return QStringLiteral( "selectbylocation" );
372 }
373 
374 QgsProcessingAlgorithm::Flags QgsSelectByLocationAlgorithm::flags() const
375 {
377 }
378 
379 QString QgsSelectByLocationAlgorithm::displayName() const
380 {
381  return QObject::tr( "Select by location" );
382 }
383 
384 QStringList QgsSelectByLocationAlgorithm::tags() const
385 {
386  return QObject::tr( "select,intersects,intersecting,disjoint,touching,within,contains,overlaps,relation" ).split( ',' );
387 }
388 
389 QString QgsSelectByLocationAlgorithm::group() const
390 {
391  return QObject::tr( "Vector selection" );
392 }
393 
394 QString QgsSelectByLocationAlgorithm::groupId() const
395 {
396  return QStringLiteral( "vectorselection" );
397 }
398 
399 QString QgsSelectByLocationAlgorithm::shortHelpString() const
400 {
401  return QObject::tr( "This algorithm creates a selection in a vector layer. The criteria for selecting "
402  "features is based on the spatial relationship between each feature and the features in an additional layer." );
403 }
404 
405 QgsSelectByLocationAlgorithm *QgsSelectByLocationAlgorithm::createInstance() const
406 {
407  return new QgsSelectByLocationAlgorithm();
408 }
409 
410 QVariantMap QgsSelectByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
411 {
412  QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
413  if ( !selectLayer )
414  throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
415 
416  Qgis::SelectBehavior method = static_cast< Qgis::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
417  std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
418  if ( !intersectSource )
419  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INTERSECT" ) ) );
420 
421  const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
422 
423  QgsFeatureIds selectedIds;
424  auto addToSelection = [&]( const QgsFeature & feature )
425  {
426  selectedIds.insert( feature.id() );
427  };
428  process( context, selectLayer, intersectSource.get(), selectedPredicates, addToSelection, true, feedback );
429 
430  selectLayer->selectByIds( selectedIds, method );
431  QVariantMap results;
432  results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
433  return results;
434 }
435 
436 
437 //
438 // QgsExtractByLocationAlgorithm
439 //
440 
441 void QgsExtractByLocationAlgorithm::initAlgorithm( const QVariantMap & )
442 {
443  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ),
444  QObject::tr( "Extract features from" ),
445  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
446  addPredicateParameter();
447  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
448  QObject::tr( "By comparing to the features from" ),
449  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
450 
451  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
452 }
453 
454 QString QgsExtractByLocationAlgorithm::name() const
455 {
456  return QStringLiteral( "extractbylocation" );
457 }
458 
459 QString QgsExtractByLocationAlgorithm::displayName() const
460 {
461  return QObject::tr( "Extract by location" );
462 }
463 
464 QStringList QgsExtractByLocationAlgorithm::tags() const
465 {
466  return QObject::tr( "extract,filter,intersects,intersecting,disjoint,touching,within,contains,overlaps,relation" ).split( ',' );
467 }
468 
469 QString QgsExtractByLocationAlgorithm::group() const
470 {
471  return QObject::tr( "Vector selection" );
472 }
473 
474 QString QgsExtractByLocationAlgorithm::groupId() const
475 {
476  return QStringLiteral( "vectorselection" );
477 }
478 
479 QString QgsExtractByLocationAlgorithm::shortHelpString() const
480 {
481  return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
482  "input layer. The criteria for adding features to the resulting layer is defined "
483  "based on the spatial relationship between each feature and the features in an additional layer." );
484 }
485 
486 QgsExtractByLocationAlgorithm *QgsExtractByLocationAlgorithm::createInstance() const
487 {
488  return new QgsExtractByLocationAlgorithm();
489 }
490 
491 QVariantMap QgsExtractByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
492 {
493  std::unique_ptr< QgsFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
494  if ( !input )
495  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
496  std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
497  if ( !intersectSource )
498  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INTERSECT" ) ) );
499 
500  const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
501  QString dest;
502  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
503 
504  if ( !sink )
505  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
506 
507  auto addToSink = [&]( const QgsFeature & feature )
508  {
509  QgsFeature f = feature;
510  if ( !sink->addFeature( f, QgsFeatureSink::FastInsert ) )
511  throw QgsProcessingException( writeFeatureError( sink.get(), parameters, QStringLiteral( "OUTPUT" ) ) );
512  };
513  process( context, input.get(), intersectSource.get(), selectedPredicates, addToSink, false, feedback );
514 
515  QVariantMap results;
516  results.insert( QStringLiteral( "OUTPUT" ), dest );
517  return results;
518 }
519 
521 
522 
SelectBehavior
Specifies how a selection should be applied.
Definition: qgis.h:545
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 & setFilterFids(const QgsFeatureIds &fids)
Sets the feature IDs that should be fetched.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature's geometries.
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
@ 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 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 QgsFeatureIds allFeatureIds() const
Returns a list of all feature IDs for features present in the source.
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
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine representing the specified geometry.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
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.
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.
@ TypeVectorAnyGeometry
Any vector layer with geometry.
Definition: qgsprocessing.h:48
A rectangle specified with double values.
Definition: qgsrectangle.h:42
Represents a vector layer which manages a vector based data sets.
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