QGIS API Documentation  3.12.1-București (121cc00ff0)
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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->reportError( 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->reportError( 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  for ( Predicate predicate : qgis::as_const( predicates ) )
281  {
282  bool isMatch = false;
283  switch ( predicate )
284  {
285  case Intersects:
286  isMatch = engine->intersects( testFeature.geometry().constGet() );
287  break;
288  case Contains:
289  isMatch = engine->contains( testFeature.geometry().constGet() );
290  break;
291  case Disjoint:
292  if ( engine->intersects( testFeature.geometry().constGet() ) )
293  {
294  disjointSet.remove( testFeature.id() );
295  }
296  break;
297  case IsEqual:
298  isMatch = engine->isEqual( testFeature.geometry().constGet() );
299  break;
300  case Touches:
301  isMatch = engine->touches( testFeature.geometry().constGet() );
302  break;
303  case Overlaps:
304  isMatch = engine->overlaps( testFeature.geometry().constGet() );
305  break;
306  case Within:
307  isMatch = engine->within( testFeature.geometry().constGet() );
308  break;
309  case Crosses:
310  isMatch = engine->crosses( testFeature.geometry().constGet() );
311  break;
312  }
313  if ( isMatch )
314  {
315  foundSet.insert( testFeature.id() );
316  handleFeatureFunction( testFeature );
317  }
318  }
319 
320  }
321 
322  current += 1;
323  feedback->setProgress( current * step );
324  }
325 
326  if ( predicates.contains( Disjoint ) )
327  {
328  disjointSet = disjointSet.subtract( foundSet );
329  QgsFeatureRequest disjointReq = QgsFeatureRequest().setFilterFids( disjointSet );
330  if ( onlyRequireTargetIds )
332  QgsFeatureIterator disjointIt = targetSource->getFeatures( disjointReq );
333  QgsFeature f;
334  while ( disjointIt.nextFeature( f ) )
335  {
336  handleFeatureFunction( f );
337  }
338  }
339 }
340 
341 
342 //
343 // QgsSelectByLocationAlgorithm
344 //
345 
346 void QgsSelectByLocationAlgorithm::initAlgorithm( const QVariantMap & )
347 {
348  QStringList methods = QStringList() << QObject::tr( "creating new selection" )
349  << QObject::tr( "adding to current selection" )
350  << QObject::tr( "selecting within current selection" )
351  << QObject::tr( "removing from current selection" );
352 
353  addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Select features from" ),
354  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
355  addPredicateParameter();
356  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
357  QObject::tr( "By comparing to the features from" ),
358  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
359 
360  addParameter( new QgsProcessingParameterEnum( QStringLiteral( "METHOD" ),
361  QObject::tr( "Modify current selection by" ),
362  methods, false, 0 ) );
363 }
364 
365 QString QgsSelectByLocationAlgorithm::name() const
366 {
367  return QStringLiteral( "selectbylocation" );
368 }
369 
370 QgsProcessingAlgorithm::Flags QgsSelectByLocationAlgorithm::flags() const
371 {
373 }
374 
375 QString QgsSelectByLocationAlgorithm::displayName() const
376 {
377  return QObject::tr( "Select by location" );
378 }
379 
380 QStringList QgsSelectByLocationAlgorithm::tags() const
381 {
382  return QObject::tr( "select,intersects,intersecting,disjoint,touching,within,contains,overlaps,relation" ).split( ',' );
383 }
384 
385 QString QgsSelectByLocationAlgorithm::group() const
386 {
387  return QObject::tr( "Vector selection" );
388 }
389 
390 QString QgsSelectByLocationAlgorithm::groupId() const
391 {
392  return QStringLiteral( "vectorselection" );
393 }
394 
395 QString QgsSelectByLocationAlgorithm::shortHelpString() const
396 {
397  return QObject::tr( "This algorithm creates a selection in a vector layer. The criteria for selecting "
398  "features is based on the spatial relationship between each feature and the features in an additional layer." );
399 }
400 
401 QgsSelectByLocationAlgorithm *QgsSelectByLocationAlgorithm::createInstance() const
402 {
403  return new QgsSelectByLocationAlgorithm();
404 }
405 
406 QVariantMap QgsSelectByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
407 {
408  QgsVectorLayer *selectLayer = parameterAsVectorLayer( parameters, QStringLiteral( "INPUT" ), context );
409  if ( !selectLayer )
410  throw QgsProcessingException( QObject::tr( "Could not load source layer for INPUT" ) );
411 
412  QgsVectorLayer::SelectBehavior method = static_cast< QgsVectorLayer::SelectBehavior >( parameterAsEnum( parameters, QStringLiteral( "METHOD" ), context ) );
413  std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
414  if ( !intersectSource )
415  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INTERSECT" ) ) );
416 
417  const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
418 
419  QgsFeatureIds selectedIds;
420  auto addToSelection = [&]( const QgsFeature & feature )
421  {
422  selectedIds.insert( feature.id() );
423  };
424  process( context, selectLayer, intersectSource.get(), selectedPredicates, addToSelection, true, feedback );
425 
426  selectLayer->selectByIds( selectedIds, method );
427  QVariantMap results;
428  results.insert( QStringLiteral( "OUTPUT" ), parameters.value( QStringLiteral( "INPUT" ) ) );
429  return results;
430 }
431 
432 
433 //
434 // QgsExtractByLocationAlgorithm
435 //
436 
437 void QgsExtractByLocationAlgorithm::initAlgorithm( const QVariantMap & )
438 {
439  addParameter( new QgsProcessingParameterVectorLayer( QStringLiteral( "INPUT" ), QObject::tr( "Extract features from" ),
440  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
441  addPredicateParameter();
442  addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INTERSECT" ),
443  QObject::tr( "By comparing to the features from" ),
444  QList< int >() << QgsProcessing::TypeVectorAnyGeometry ) );
445 
446  addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Extracted (location)" ) ) );
447 }
448 
449 QString QgsExtractByLocationAlgorithm::name() const
450 {
451  return QStringLiteral( "extractbylocation" );
452 }
453 
454 QString QgsExtractByLocationAlgorithm::displayName() const
455 {
456  return QObject::tr( "Extract by location" );
457 }
458 
459 QStringList QgsExtractByLocationAlgorithm::tags() const
460 {
461  return QObject::tr( "extract,filter,intersects,intersecting,disjoint,touching,within,contains,overlaps,relation" ).split( ',' );
462 }
463 
464 QString QgsExtractByLocationAlgorithm::group() const
465 {
466  return QObject::tr( "Vector selection" );
467 }
468 
469 QString QgsExtractByLocationAlgorithm::groupId() const
470 {
471  return QStringLiteral( "vectorselection" );
472 }
473 
474 QString QgsExtractByLocationAlgorithm::shortHelpString() const
475 {
476  return QObject::tr( "This algorithm creates a new vector layer that only contains matching features from an "
477  "input layer. The criteria for adding features to the resulting layer is defined "
478  "based on the spatial relationship between each feature and the features in an additional layer." );
479 }
480 
481 QgsExtractByLocationAlgorithm *QgsExtractByLocationAlgorithm::createInstance() const
482 {
483  return new QgsExtractByLocationAlgorithm();
484 }
485 
486 QVariantMap QgsExtractByLocationAlgorithm::processAlgorithm( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback )
487 {
488  std::unique_ptr< QgsFeatureSource > input( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) );
489  if ( !input )
490  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INPUT" ) ) );
491  std::unique_ptr< QgsFeatureSource > intersectSource( parameterAsSource( parameters, QStringLiteral( "INTERSECT" ), context ) );
492  if ( !intersectSource )
493  throw QgsProcessingException( invalidSourceError( parameters, QStringLiteral( "INTERSECT" ) ) );
494 
495  const QList< int > selectedPredicates = parameterAsEnums( parameters, QStringLiteral( "PREDICATE" ), context );
496  QString dest;
497  std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, input->fields(), input->wkbType(), input->sourceCrs() ) );
498 
499  if ( !sink )
500  throw QgsProcessingException( invalidSinkError( parameters, QStringLiteral( "OUTPUT" ) ) );
501 
502  auto addToSink = [&]( const QgsFeature & feature )
503  {
504  QgsFeature f = feature;
505  sink->addFeature( f, QgsFeatureSink::FastInsert );
506  };
507  process( context, input.get(), intersectSource.get(), selectedPredicates, addToSink, false, feedback );
508 
509  QVariantMap results;
510  results.insert( QStringLiteral( "OUTPUT" ), dest );
511  return results;
512 }
513 
515 
516 
517 
QgsFeatureRequest & setDestinationCrs(const QgsCoordinateReferenceSystem &crs, const QgsCoordinateTransformContext &context)
Sets the destination crs for feature&#39;s geometries.
QgsFeatureId id
Definition: qgsfeature.h:64
Wrapper for iterator of features from vector data provider or vector layer.
No spatial index exists for the source.
Use faster inserts, at the cost of updating the passed features to reflect changes made at the provid...
A rectangle specified with double values.
Definition: qgsrectangle.h:41
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:34
Base class for providing feedback from a processing algorithm.
void setProgress(double progress)
Sets the current progress for the feedback object.
Definition: qgsfeedback.h:64
virtual Flags flags() const
Returns the flags indicating how and when the algorithm operates and should be exposed to users...
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:55
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:197
A feature sink output for processing algorithms.
An enum based parameter for processing algorithms, allowing for selection from predefined values...
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
QgsFeatureRequest & setNoAttributes()
Set that no attributes will be fetched.
This class wraps a request for features to a vector layer (or directly its vector data provider)...
Custom exception class for processing related exceptions.
Definition: qgsexception.h:82
virtual SpatialIndexPresence hasSpatialIndex() const
Returns an enum value representing the presence of a valid spatial index on the source, if it can be determined.
QgsFeatureRequest & setFilterRect(const QgsRectangle &rectangle)
Sets the rectangle from which features will be taken.
QgsCoordinateTransformContext transformContext() const
Returns the coordinate transform context.
A vector layer (with or without geometry) parameter for processing algorithms.
virtual QgsCoordinateReferenceSystem sourceCrs() const =0
Returns the coordinate reference system for features in the source.
static QgsGeometryEngine * createGeometryEngine(const QgsAbstractGeometry *geometry)
Creates and returns a new geometry engine.
const QgsAbstractGeometry * constGet() const
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
SelectBehavior
Selection behavior.
QgsFeatureRequest & setFilterFids(const QgsFeatureIds &fids)
Sets feature IDs that should be fetched.
bool isCanceled() const
Tells whether the operation has been canceled already.
Definition: qgsfeedback.h:55
An interface for objects which provide features via a getFeatures method.
An input feature source (such as vector layers) parameter for processing algorithms.
QgsRectangle boundingBox() const
Returns the bounding box of the geometry.
Q_INVOKABLE void selectByIds(const QgsFeatureIds &ids, QgsVectorLayer::SelectBehavior behavior=QgsVectorLayer::SetSelection)
Selects matching features using a list of feature IDs.
virtual void reportError(const QString &error, bool fatalError=false)
Reports that the algorithm encountered an error while executing.
QgsGeometry geometry
Definition: qgsfeature.h:67
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Represents a vector layer which manages a vector based data sets.
virtual QgsFeatureIds allFeatureIds() const
Returns a list of all feature IDs for features present in the source.
Contains information about the context in which a processing algorithm is executed.
Any vector layer with geometry.
Definition: qgsprocessing.h:47
virtual QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const =0
Returns an iterator for the features in the source.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
virtual long featureCount() const =0
Returns the number of features contained in the source, or -1 if the feature count is unknown...