QGIS API Documentation  3.23.0-Master (dd0cd13a00)
qgsmemoryfeatureiterator.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsmemoryfeatureiterator.cpp
3  ---------------------
4  begin : Juli 2012
5  copyright : (C) 2012 by Martin Dobias
6  email : wonder dot sk at gmail dot com
7  ***************************************************************************
8  * *
9  * This program is free software; you can redistribute it and/or modify *
10  * it under the terms of the GNU General Public License as published by *
11  * the Free Software Foundation; either version 2 of the License, or *
12  * (at your option) any later version. *
13  * *
14  ***************************************************************************/
16 #include "qgsmemoryprovider.h"
17 
18 #include "qgsgeometry.h"
19 #include "qgsgeometryengine.h"
20 #include "qgslogger.h"
21 #include "qgsspatialindex.h"
22 #include "qgsmessagelog.h"
23 #include "qgsproject.h"
24 #include "qgsexception.h"
26 
28 
29 QgsMemoryFeatureIterator::QgsMemoryFeatureIterator( QgsMemoryFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
30  : QgsAbstractFeatureIteratorFromSource<QgsMemoryFeatureSource>( source, ownSource, request )
31 {
32  if ( mRequest.destinationCrs().isValid() && mRequest.destinationCrs() != mSource->mCrs )
33  {
34  mTransform = QgsCoordinateTransform( mSource->mCrs, mRequest.destinationCrs(), mRequest.transformContext() );
35  }
36  try
37  {
38  mFilterRect = filterRectToSourceCrs( mTransform );
39  }
40  catch ( QgsCsException & )
41  {
42  // can't reproject mFilterRect
43  close();
44  return;
45  }
46 
47  if ( !mSource->mSubsetString.isEmpty() )
48  {
49  mSubsetExpression = std::make_unique< QgsExpression >( mSource->mSubsetString );
50  mSubsetExpression->prepare( mSource->expressionContext() );
51  }
52 
53  // prepare spatial filter geometries for optimal speed
54  switch ( mRequest.spatialFilterType() )
55  {
57  break;
58 
60  if ( !mFilterRect.isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
61  {
62  mSelectRectGeom = QgsGeometry::fromRect( mFilterRect );
63  mSelectRectEngine.reset( QgsGeometry::createGeometryEngine( mSelectRectGeom.constGet() ) );
64  mSelectRectEngine->prepareGeometry();
65  }
66  break;
67 
69  if ( !mRequest.referenceGeometry().isEmpty() )
70  {
71  mDistanceWithinGeom = mRequest.referenceGeometry();
72  mDistanceWithinEngine.reset( QgsGeometry::createGeometryEngine( mDistanceWithinGeom.constGet() ) );
73  mDistanceWithinEngine->prepareGeometry();
74  }
75  break;
76  }
77 
78  // if there's spatial index, use it!
79  // (but don't use it when selection rect is not specified)
80  if ( !mFilterRect.isNull() && mSource->mSpatialIndex )
81  {
82  mUsingFeatureIdList = true;
83  mFeatureIdList = mSource->mSpatialIndex->intersects( mFilterRect );
84  QgsDebugMsgLevel( "Features returned by spatial index: " + QString::number( mFeatureIdList.count() ), 2 );
85  }
86  else if ( mRequest.filterType() == QgsFeatureRequest::FilterFid )
87  {
88  mUsingFeatureIdList = true;
89  const QgsFeatureMap::const_iterator it = mSource->mFeatures.constFind( mRequest.filterFid() );
90  if ( it != mSource->mFeatures.constEnd() )
91  mFeatureIdList.append( mRequest.filterFid() );
92  }
93  else if ( mRequest.filterType() == QgsFeatureRequest::FilterFids )
94  {
95  mUsingFeatureIdList = true;
96  mFeatureIdList = qgis::setToList( mRequest.filterFids() );
97  }
98  else
99  {
100  mUsingFeatureIdList = false;
101  }
102 
103  rewind();
104 }
105 
106 QgsMemoryFeatureIterator::~QgsMemoryFeatureIterator()
107 {
108  close();
109 }
110 
111 bool QgsMemoryFeatureIterator::fetchFeature( QgsFeature &feature )
112 {
113  feature.setValid( false );
114 
115  if ( mClosed )
116  return false;
117 
118  if ( mUsingFeatureIdList )
119  return nextFeatureUsingList( feature );
120  else
121  return nextFeatureTraverseAll( feature );
122 }
123 
124 
125 bool QgsMemoryFeatureIterator::nextFeatureUsingList( QgsFeature &feature )
126 {
127  bool hasFeature = false;
128 
129  // option 1: we have a list of features to traverse
130  while ( mFeatureIdListIterator != mFeatureIdList.constEnd() )
131  {
132  feature = mSource->mFeatures.value( *mFeatureIdListIterator );
133  if ( !mFilterRect.isNull() )
134  {
135  if ( mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
136  {
137  // do exact check in case we're doing intersection
138  if ( feature.hasGeometry() && mSelectRectEngine->intersects( feature.geometry().constGet() ) )
139  hasFeature = true;
140  }
141  else if ( mSource->mSpatialIndex )
142  {
143  // using a spatial index - so we already know that the bounding box intersects correctly
144  hasFeature = true;
145  }
146  else
147  {
148  // do bounding box check if we aren't using a spatial index
149  if ( feature.hasGeometry() && feature.geometry().boundingBoxIntersects( mFilterRect ) )
150  hasFeature = true;
151  }
152  }
153  else
154  hasFeature = true;
155 
156  if ( hasFeature )
157  feature.setFields( mSource->mFields ); // allow name-based attribute lookups
158 
159  if ( hasFeature && mSubsetExpression )
160  {
161  mSource->expressionContext()->setFeature( feature );
162  if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
163  hasFeature = false;
164  }
165 
166  if ( hasFeature )
167  {
168  // geometry must be in destination crs before we can perform distance within check
169  geometryToDestinationCrs( feature, mTransform );
170  }
171 
172  if ( hasFeature && mRequest.spatialFilterType() == Qgis::SpatialFilterType::DistanceWithin )
173  {
174  hasFeature = mDistanceWithinEngine->distance( feature.geometry().constGet() ) <= mRequest.distanceWithin();
175  }
176 
177  ++mFeatureIdListIterator;
178  if ( hasFeature )
179  break;
180  }
181 
182  feature.setValid( hasFeature );
183  if ( !hasFeature )
184  {
185  close();
186  }
187 
188  return hasFeature;
189 }
190 
191 
192 bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
193 {
194  bool hasFeature = false;
195 
196  // option 2: traversing the whole layer
197  while ( mSelectIterator != mSource->mFeatures.constEnd() )
198  {
199  hasFeature = false;
200  feature = *mSelectIterator;
201  if ( mFilterRect.isNull() )
202  {
203  // selection rect empty => using all features
204  hasFeature = true;
205  }
206  else
207  {
208  if ( mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
209  {
210  // using exact test when checking for intersection
211  if ( feature.hasGeometry() && mSelectRectEngine->intersects( feature.geometry().constGet() ) )
212  hasFeature = true;
213  }
214  else
215  {
216  // check just bounding box against rect when not using intersection
217  if ( feature.hasGeometry() && feature.geometry().boundingBox().intersects( mFilterRect ) )
218  hasFeature = true;
219  }
220  }
221 
222  if ( hasFeature && mSubsetExpression )
223  {
224  mSource->expressionContext()->setFeature( feature );
225  if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
226  hasFeature = false;
227  }
228 
229  if ( hasFeature )
230  {
231  // geometry must be in destination crs before we can perform distance within check
232  geometryToDestinationCrs( feature, mTransform );
233  }
234 
235  if ( hasFeature && mRequest.spatialFilterType() == Qgis::SpatialFilterType::DistanceWithin )
236  {
237  hasFeature = mDistanceWithinEngine->distance( feature.geometry().constGet() ) <= mRequest.distanceWithin();
238  }
239 
240  ++mSelectIterator;
241  if ( hasFeature )
242  break;
243  }
244 
245  // copy feature
246  if ( hasFeature )
247  {
248  feature.setValid( true );
249  feature.setFields( mSource->mFields ); // allow name-based attribute lookups
250  }
251  else
252  {
253  feature.setValid( false );
254  close();
255  }
256 
257  return hasFeature;
258 }
259 
260 bool QgsMemoryFeatureIterator::rewind()
261 {
262  if ( mClosed )
263  return false;
264 
265  if ( mUsingFeatureIdList )
266  mFeatureIdListIterator = mFeatureIdList.constBegin();
267  else
268  mSelectIterator = mSource->mFeatures.constBegin();
269 
270  return true;
271 }
272 
273 bool QgsMemoryFeatureIterator::close()
274 {
275  if ( mClosed )
276  return false;
277 
278  iteratorClosed();
279 
280  mClosed = true;
281  return true;
282 }
283 
284 // -------------------------
285 
286 QgsMemoryFeatureSource::QgsMemoryFeatureSource( const QgsMemoryProvider *p )
287  : mFields( p->mFields )
288  , mFeatures( p->mFeatures )
289  , mSpatialIndex( p->mSpatialIndex ? std::make_unique< QgsSpatialIndex >( *p->mSpatialIndex ) : nullptr ) // just shallow copy
290  , mSubsetString( p->mSubsetString )
291  , mCrs( p->mCrs )
292 {
293 }
294 
295 QgsFeatureIterator QgsMemoryFeatureSource::getFeatures( const QgsFeatureRequest &request )
296 {
297  return QgsFeatureIterator( new QgsMemoryFeatureIterator( this, false, request ) );
298 }
299 
300 QgsExpressionContext *QgsMemoryFeatureSource::expressionContext()
301 {
302  // lazy construct expression context -- it's not free to calculate, and is only used when
303  // iterating over a memory layer with a subset string set
304  if ( !mExpressionContext )
305  {
306  mExpressionContext = std::make_unique< QgsExpressionContext >(
307  QList<QgsExpressionContextScope *>()
310  mExpressionContext->setFields( mFields );
311  }
312  return mExpressionContext.get();
313 }
314 
@ DistanceWithin
Filter by distance to reference geometry.
@ BoundingBox
Filter using a bounding box.
@ NoFilter
No spatial filtering of features.
Helper template that cares of two things: 1.
Class for doing transforms between two map coordinate systems.
Custom exception class for Coordinate Reference System related exceptions.
Definition: qgsexception.h:66
static QgsExpressionContextScope * projectScope(const QgsProject *project)
Creates a new scope which contains variables and functions relating to a QGIS project.
static QgsExpressionContextScope * globalScope()
Creates a new scope which contains variables and functions relating to the global QGIS context.
Expression contexts are used to encapsulate the parameters around which a QgsExpression should be eva...
Wrapper for iterator of features from vector data provider or vector layer.
This class wraps a request for features to a vector layer (or directly its vector data provider).
@ ExactIntersect
Use exact geometry intersection (slower) instead of bounding boxes.
@ FilterFid
Filter using feature ID.
@ FilterFids
Filter using feature IDs.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
void setFields(const QgsFields &fields, bool initAttributes=false)
Assigns a field map with the feature to allow attribute access by attribute name.
Definition: qgsfeature.cpp:188
QgsGeometry geometry
Definition: qgsfeature.h:67
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:214
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:223
const QgsAbstractGeometry * constGet() const SIP_HOLDGIL
Returns a non-modifiable (const) reference to the underlying abstract geometry primitive.
bool boundingBoxIntersects(const QgsRectangle &rectangle) const
Returns true if the bounding box of this geometry intersects with a rectangle.
static QgsGeometry fromRect(const QgsRectangle &rect) SIP_HOLDGIL
Creates a new geometry from a QgsRectangle.
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.
static QgsProject * instance()
Returns the QgsProject singleton instance.
Definition: qgsproject.cpp:468
bool intersects(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle intersects with other rectangle.
Definition: qgsrectangle.h:349
A spatial index for QgsFeature objects.
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39