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