QGIS API Documentation 3.28.0-Firenze (ed3ad0430f)
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 const QgsFeatureIds filterFids = mRequest.filterFids();
97 mFeatureIdList = QList<QgsFeatureId>( filterFids.begin(), filterFids.end() );
98 }
99 else
100 {
101 mUsingFeatureIdList = false;
102 }
103
104 rewind();
105}
106
107QgsMemoryFeatureIterator::~QgsMemoryFeatureIterator()
108{
109 close();
110}
111
112bool QgsMemoryFeatureIterator::fetchFeature( QgsFeature &feature )
113{
114 feature.setValid( false );
115
116 if ( mClosed )
117 return false;
118
119 if ( mUsingFeatureIdList )
120 return nextFeatureUsingList( feature );
121 else
122 return nextFeatureTraverseAll( feature );
123}
124
125
126bool QgsMemoryFeatureIterator::nextFeatureUsingList( QgsFeature &feature )
127{
128 bool hasFeature = false;
129
130 // option 1: we have a list of features to traverse
131 while ( mFeatureIdListIterator != mFeatureIdList.constEnd() )
132 {
133 feature = mSource->mFeatures.value( *mFeatureIdListIterator );
134 if ( !mFilterRect.isNull() )
135 {
136 if ( mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
137 {
138 // do exact check in case we're doing intersection
139 if ( feature.hasGeometry() && mSelectRectEngine->intersects( feature.geometry().constGet() ) )
140 hasFeature = true;
141 }
142 else if ( mSource->mSpatialIndex )
143 {
144 // using a spatial index - so we already know that the bounding box intersects correctly
145 hasFeature = true;
146 }
147 else
148 {
149 // do bounding box check if we aren't using a spatial index
150 if ( feature.hasGeometry() && feature.geometry().boundingBoxIntersects( mFilterRect ) )
151 hasFeature = true;
152 }
153 }
154 else
155 hasFeature = true;
156
157 if ( hasFeature )
158 feature.setFields( mSource->mFields ); // allow name-based attribute lookups
159
160 if ( hasFeature && mSubsetExpression )
161 {
162 mSource->expressionContext()->setFeature( feature );
163 if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
164 hasFeature = false;
165 }
166
167 if ( hasFeature )
168 {
169 // geometry must be in destination crs before we can perform distance within check
170 geometryToDestinationCrs( feature, mTransform );
171 }
172
173 if ( hasFeature && mRequest.spatialFilterType() == Qgis::SpatialFilterType::DistanceWithin )
174 {
175 hasFeature = mDistanceWithinEngine->distance( feature.geometry().constGet() ) <= mRequest.distanceWithin();
176 }
177
178 ++mFeatureIdListIterator;
179 if ( hasFeature )
180 break;
181 }
182
183 feature.setValid( hasFeature );
184 if ( !hasFeature )
185 {
186 close();
187 }
188
189 return hasFeature;
190}
191
192
193bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
194{
195 bool hasFeature = false;
196
197 // option 2: traversing the whole layer
198 while ( mSelectIterator != mSource->mFeatures.constEnd() )
199 {
200 hasFeature = false;
201 feature = *mSelectIterator;
202 if ( mFilterRect.isNull() )
203 {
204 // selection rect empty => using all features
205 hasFeature = true;
206 }
207 else
208 {
209 if ( mRequest.spatialFilterType() == Qgis::SpatialFilterType::BoundingBox && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
210 {
211 // using exact test when checking for intersection
212 if ( feature.hasGeometry() && mSelectRectEngine->intersects( feature.geometry().constGet() ) )
213 hasFeature = true;
214 }
215 else
216 {
217 // check just bounding box against rect when not using intersection
218 if ( feature.hasGeometry() && feature.geometry().boundingBox().intersects( mFilterRect ) )
219 hasFeature = true;
220 }
221 }
222
223 if ( hasFeature && mSubsetExpression )
224 {
225 mSource->expressionContext()->setFeature( feature );
226 if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
227 hasFeature = false;
228 }
229
230 if ( hasFeature )
231 {
232 // geometry must be in destination crs before we can perform distance within check
233 geometryToDestinationCrs( feature, mTransform );
234 }
235
236 if ( hasFeature && mRequest.spatialFilterType() == Qgis::SpatialFilterType::DistanceWithin )
237 {
238 hasFeature = mDistanceWithinEngine->distance( feature.geometry().constGet() ) <= mRequest.distanceWithin();
239 }
240
241 ++mSelectIterator;
242 if ( hasFeature )
243 break;
244 }
245
246 // copy feature
247 if ( hasFeature )
248 {
249 feature.setValid( true );
250 feature.setFields( mSource->mFields ); // allow name-based attribute lookups
251 }
252 else
253 {
254 feature.setValid( false );
255 close();
256 }
257
258 return hasFeature;
259}
260
261bool QgsMemoryFeatureIterator::rewind()
262{
263 if ( mClosed )
264 return false;
265
266 if ( mUsingFeatureIdList )
267 mFeatureIdListIterator = mFeatureIdList.constBegin();
268 else
269 mSelectIterator = mSource->mFeatures.constBegin();
270
271 return true;
272}
273
274bool QgsMemoryFeatureIterator::close()
275{
276 if ( mClosed )
277 return false;
278
279 iteratorClosed();
280
281 mClosed = true;
282 return true;
283}
284
285// -------------------------
286
287QgsMemoryFeatureSource::QgsMemoryFeatureSource( const QgsMemoryProvider *p )
288 : mFields( p->mFields )
289 , mFeatures( p->mFeatures )
290 , mSpatialIndex( p->mSpatialIndex ? std::make_unique< QgsSpatialIndex >( *p->mSpatialIndex ) : nullptr ) // just shallow copy
291 , mSubsetString( p->mSubsetString )
292 , mCrs( p->mCrs )
293{
294}
295
296QgsFeatureIterator QgsMemoryFeatureSource::getFeatures( const QgsFeatureRequest &request )
297{
298 return QgsFeatureIterator( new QgsMemoryFeatureIterator( this, false, request ) );
299}
300
301QgsExpressionContext *QgsMemoryFeatureSource::expressionContext()
302{
303 // lazy construct expression context -- it's not free to calculate, and is only used when
304 // iterating over a memory layer with a subset string set
305 if ( !mExpressionContext )
306 {
307 mExpressionContext = std::make_unique< QgsExpressionContext >(
308 QList<QgsExpressionContextScope *>()
311 mExpressionContext->setFields( mFields );
312 }
313 return mExpressionContext.get();
314}
315
@ 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:198
QgsGeometry geometry
Definition: qgsfeature.h:67
void setValid(bool validity)
Sets the validity of the feature.
Definition: qgsfeature.cpp:224
bool hasGeometry() const
Returns true if the feature has an associated geometry.
Definition: qgsfeature.cpp:233
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:477
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.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
#define QgsDebugMsgLevel(str, level)
Definition: qgslogger.h:39