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 ***************************************************************************/
15 #include "qgsmemoryfeatureiterator.h"
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"
25 #include "qgsexpressioncontextutils.h"
26
27 ///@cond PRIVATE
28
QgsMemoryFeatureIterator(QgsMemoryFeatureSource * source,bool ownSource,const QgsFeatureRequest & request)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 = qgis::make_unique< QgsExpression >( mSource->mSubsetString );
50 mSubsetExpression->prepare( mSource->expressionContext() );
51 }
52
53 if ( !mFilterRect.isNull() && mRequest.flags() & QgsFeatureRequest::ExactIntersect )
54 {
55 mSelectRectGeom = QgsGeometry::fromRect( mFilterRect );
56 mSelectRectEngine.reset( QgsGeometry::createGeometryEngine( mSelectRectGeom.constGet() ) );
57 mSelectRectEngine->prepareGeometry();
58 }
59
60 // if there's spatial index, use it!
61 // (but don't use it when selection rect is not specified)
62 if ( !mFilterRect.isNull() && mSource->mSpatialIndex )
63 {
64 mUsingFeatureIdList = true;
65 mFeatureIdList = mSource->mSpatialIndex->intersects( mFilterRect );
66 QgsDebugMsgLevel( "Features returned by spatial index: " + QString::number( mFeatureIdList.count() ), 2 );
67 }
68 else if ( mRequest.filterType() == QgsFeatureRequest::FilterFid )
69 {
70 mUsingFeatureIdList = true;
71 QgsFeatureMap::const_iterator it = mSource->mFeatures.constFind( mRequest.filterFid() );
72 if ( it != mSource->mFeatures.constEnd() )
73 mFeatureIdList.append( mRequest.filterFid() );
74 }
75 else if ( mRequest.filterType() == QgsFeatureRequest::FilterFids )
76 {
77 mUsingFeatureIdList = true;
78 mFeatureIdList = qgis::setToList( mRequest.filterFids() );
79 }
80 else
81 {
82 mUsingFeatureIdList = false;
83 }
84
85 rewind();
86 }
87
~QgsMemoryFeatureIterator()88 QgsMemoryFeatureIterator::~QgsMemoryFeatureIterator()
89 {
90 close();
91 }
92
fetchFeature(QgsFeature & feature)93 bool QgsMemoryFeatureIterator::fetchFeature( QgsFeature &feature )
94 {
95 feature.setValid( false );
96
97 if ( mClosed )
98 return false;
99
100 if ( mUsingFeatureIdList )
101 return nextFeatureUsingList( feature );
102 else
103 return nextFeatureTraverseAll( feature );
104 }
105
106
nextFeatureUsingList(QgsFeature & feature)107 bool QgsMemoryFeatureIterator::nextFeatureUsingList( QgsFeature &feature )
108 {
109 bool hasFeature = false;
110
111 // option 1: we have a list of features to traverse
112 QgsFeature candidate;
113 while ( mFeatureIdListIterator != mFeatureIdList.constEnd() )
114 {
115 candidate = mSource->mFeatures.value( *mFeatureIdListIterator );
116 if ( !mFilterRect.isNull() )
117 {
118 if ( mRequest.flags() & QgsFeatureRequest::ExactIntersect )
119 {
120 // do exact check in case we're doing intersection
121 if ( candidate.hasGeometry() && mSelectRectEngine->intersects( candidate.geometry().constGet() ) )
122 hasFeature = true;
123 }
124 else if ( mSource->mSpatialIndex )
125 {
126 // using a spatial index - so we already know that the bounding box intersects correctly
127 hasFeature = true;
128 }
129 else
130 {
131 // do bounding box check if we aren't using a spatial index
132 if ( candidate.hasGeometry() && candidate.geometry().boundingBoxIntersects( mFilterRect ) )
133 hasFeature = true;
134 }
135 }
136 else
137 hasFeature = true;
138
139 if ( hasFeature && mSubsetExpression )
140 {
141 mSource->expressionContext()->setFeature( candidate );
142 if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
143 hasFeature = false;
144 }
145
146 if ( hasFeature )
147 break;
148
149 ++mFeatureIdListIterator;
150 }
151
152 // copy feature
153 if ( hasFeature )
154 {
155 feature = candidate;
156 ++mFeatureIdListIterator;
157 }
158 else
159 close();
160
161 if ( hasFeature )
162 {
163 feature.setFields( mSource->mFields ); // allow name-based attribute lookups
164 geometryToDestinationCrs( feature, mTransform );
165 }
166
167 return hasFeature;
168 }
169
170
nextFeatureTraverseAll(QgsFeature & feature)171 bool QgsMemoryFeatureIterator::nextFeatureTraverseAll( QgsFeature &feature )
172 {
173 bool hasFeature = false;
174
175 // option 2: traversing the whole layer
176 while ( mSelectIterator != mSource->mFeatures.constEnd() )
177 {
178 if ( mFilterRect.isNull() )
179 {
180 // selection rect empty => using all features
181 hasFeature = true;
182 }
183 else
184 {
185 if ( mRequest.flags() & QgsFeatureRequest::ExactIntersect )
186 {
187 // using exact test when checking for intersection
188 if ( mSelectIterator->hasGeometry() && mSelectRectEngine->intersects( mSelectIterator->geometry().constGet() ) )
189 hasFeature = true;
190 }
191 else
192 {
193 // check just bounding box against rect when not using intersection
194 if ( mSelectIterator->hasGeometry() && mSelectIterator->geometry().boundingBox().intersects( mFilterRect ) )
195 hasFeature = true;
196 }
197 }
198
199 if ( mSubsetExpression )
200 {
201 mSource->expressionContext()->setFeature( *mSelectIterator );
202 if ( !mSubsetExpression->evaluate( mSource->expressionContext() ).toBool() )
203 hasFeature = false;
204 }
205
206 if ( hasFeature )
207 break;
208
209 ++mSelectIterator;
210 }
211
212 // copy feature
213 if ( hasFeature )
214 {
215 feature = mSelectIterator.value();
216 ++mSelectIterator;
217 feature.setValid( true );
218 feature.setFields( mSource->mFields ); // allow name-based attribute lookups
219 geometryToDestinationCrs( feature, mTransform );
220 }
221 else
222 close();
223
224 return hasFeature;
225 }
226
rewind()227 bool QgsMemoryFeatureIterator::rewind()
228 {
229 if ( mClosed )
230 return false;
231
232 if ( mUsingFeatureIdList )
233 mFeatureIdListIterator = mFeatureIdList.constBegin();
234 else
235 mSelectIterator = mSource->mFeatures.constBegin();
236
237 return true;
238 }
239
close()240 bool QgsMemoryFeatureIterator::close()
241 {
242 if ( mClosed )
243 return false;
244
245 iteratorClosed();
246
247 mClosed = true;
248 return true;
249 }
250
251 // -------------------------
252
QgsMemoryFeatureSource(const QgsMemoryProvider * p)253 QgsMemoryFeatureSource::QgsMemoryFeatureSource( const QgsMemoryProvider *p )
254 : mFields( p->mFields )
255 , mFeatures( p->mFeatures )
256 , mSpatialIndex( p->mSpatialIndex ? qgis::make_unique< QgsSpatialIndex >( *p->mSpatialIndex ) : nullptr ) // just shallow copy
257 , mSubsetString( p->mSubsetString )
258 , mCrs( p->mCrs )
259 {
260 }
261
getFeatures(const QgsFeatureRequest & request)262 QgsFeatureIterator QgsMemoryFeatureSource::getFeatures( const QgsFeatureRequest &request )
263 {
264 return QgsFeatureIterator( new QgsMemoryFeatureIterator( this, false, request ) );
265 }
266
expressionContext()267 QgsExpressionContext *QgsMemoryFeatureSource::expressionContext()
268 {
269 // lazy construct expression context -- it's not free to calculate, and is only used when
270 // iterating over a memory layer with a subset string set
271 if ( !mExpressionContext )
272 {
273 mExpressionContext = qgis::make_unique< QgsExpressionContext >(
274 QList<QgsExpressionContextScope *>()
275 << QgsExpressionContextUtils::globalScope()
276 << QgsExpressionContextUtils::projectScope( QgsProject::instance() ) );
277 mExpressionContext->setFields( mFields );
278 }
279 return mExpressionContext.get();
280 }
281
282 ///@endcond PRIVATE
283