QGIS API Documentation 3.27.0-Master (f261cc1f8b)
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
29QgsMemoryFeatureIterator::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
106QgsMemoryFeatureIterator::~QgsMemoryFeatureIterator()
107{
108 close();
109}
110
111bool 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
125bool 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
192bool 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
260bool 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
273bool QgsMemoryFeatureIterator::close()
274{
275 if ( mClosed )
276 return false;
277
278 iteratorClosed();
279
280 mClosed = true;
281 return true;
282}
283
284// -------------------------
285
286QgsMemoryFeatureSource::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
295QgsFeatureIterator QgsMemoryFeatureSource::getFeatures( const QgsFeatureRequest &request )
296{
297 return QgsFeatureIterator( new QgsMemoryFeatureIterator( this, false, request ) );
298}
299
300QgsExpressionContext *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:195
QgsGeometry geometry
Definition: qgsfeature.h:67
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:221
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:230
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:480
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