QGIS API Documentation  2.18.21-Las Palmas (9fba24a)
qgsvectorlayercache.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgsvectorlayercache.cpp
3  Cache features of a vector layer
4  -------------------
5  begin : January 2013
6  copyright : (C) Matthias Kuhn
7  email : matthias at opengis dot ch
8 
9  ***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 
18 #include "qgsvectorlayercache.h"
19 #include "qgscacheindex.h"
21 
23  : QObject( parent )
24  , mLayer( layer )
25  , mCacheGeometry( true )
26  , mFullCache( false )
27 {
28  mCache.setMaxCost( cacheSize );
29 
30  connect( mLayer, SIGNAL( featureDeleted( QgsFeatureId ) ), SLOT( featureDeleted( QgsFeatureId ) ) );
31  connect( mLayer, SIGNAL( featureAdded( QgsFeatureId ) ), SLOT( onFeatureAdded( QgsFeatureId ) ) );
32  connect( mLayer, SIGNAL( layerDeleted() ), SLOT( layerDeleted() ) );
33 
34  setCacheGeometry( true );
37 
38  connect( mLayer, SIGNAL( attributeDeleted( int ) ), SLOT( attributeDeleted( int ) ) );
39  connect( mLayer, SIGNAL( updatedFields() ), SLOT( invalidate() ) );
40  connect( mLayer, SIGNAL( dataChanged() ), SLOT( invalidate() ) );
41  connect( mLayer, SIGNAL( attributeValueChanged( QgsFeatureId, int, const QVariant& ) ), SLOT( onAttributeValueChanged( QgsFeatureId, int, const QVariant& ) ) );
42 }
43 
45 {
46  qDeleteAll( mCacheIndices );
47  mCacheIndices.clear();
48 }
49 
51 {
52  mCache.setMaxCost( cacheSize );
53 }
54 
56 {
57  return mCache.maxCost();
58 }
59 
61 {
62  bool shouldCacheGeometry = cacheGeometry && mLayer->hasGeometryType();
63  bool mustInvalidate = shouldCacheGeometry && !mCacheGeometry; // going from no geometry -> geometry, so have to clear existing cache entries
64  mCacheGeometry = shouldCacheGeometry;
65  if ( cacheGeometry )
66  {
67  connect( mLayer, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), SLOT( geometryChanged( QgsFeatureId, QgsGeometry& ) ) );
68  }
69  else
70  {
71  disconnect( mLayer, SIGNAL( geometryChanged( QgsFeatureId, QgsGeometry& ) ), this, SLOT( geometryChanged( QgsFeatureId, QgsGeometry& ) ) );
72  }
73  if ( mustInvalidate )
74  {
75  invalidate();
76  }
77 }
78 
80 {
81  mCachedAttributes = attributes;
82 }
83 
84 void QgsVectorLayerCache::setFullCache( bool fullCache )
85 {
86  mFullCache = fullCache;
87 
88  if ( mFullCache )
89  {
90  // Add a little more than necessary...
91  setCacheSize( mLayer->featureCount() + 100 );
92 
93  // Initialize the cache...
95  .setSubsetOfAttributes( mCachedAttributes )
96  .setFlags( mCacheGeometry ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ) );
97 
98  int i = 0;
99 
100  QTime t;
101  t.start();
102 
103  QgsFeature f;
104  while ( it.nextFeature( f ) )
105  {
106  ++i;
107 
108  if ( t.elapsed() > 1000 )
109  {
110  bool cancel = false;
111  emit progress( i, cancel );
112  if ( cancel )
113  break;
114 
115  t.restart();
116  }
117  }
118 
119  it.close();
120 
121  emit finished();
122  }
123 }
124 
126 {
127  mCacheIndices.append( cacheIndex );
128 }
129 
130 void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
131 {
132  if ( cacheAddedAttributes )
133  {
134  connect( mLayer, SIGNAL( attributeAdded( int ) ), SLOT( attributeAdded( int ) ) );
135  }
136  else
137  {
138  disconnect( mLayer, SIGNAL( attributeAdded( int ) ), this, SLOT( attributeAdded( int ) ) );
139  }
140 }
141 
142 bool QgsVectorLayerCache::featureAtId( QgsFeatureId featureId, QgsFeature& feature, bool skipCache )
143 {
144  bool featureFound = false;
145 
146  QgsCachedFeature* cachedFeature = nullptr;
147 
148  if ( !skipCache )
149  {
150  cachedFeature = mCache[ featureId ];
151  }
152 
153  if ( cachedFeature )
154  {
155  feature = QgsFeature( *cachedFeature->feature() );
156  featureFound = true;
157  }
158  else if ( mLayer->getFeatures( QgsFeatureRequest()
159  .setFilterFid( featureId )
160  .setSubsetOfAttributes( mCachedAttributes )
161  .setFlags( !mCacheGeometry ? QgsFeatureRequest::NoGeometry : QgsFeatureRequest::Flags( nullptr ) ) )
162  .nextFeature( feature ) )
163  {
164  cacheFeature( feature );
165  featureFound = true;
166  }
167 
168  return featureFound;
169 }
170 
172 {
173  return mCache.remove( fid );
174 }
175 
177 {
178  return mLayer;
179 }
180 
182 {
183  // If a request is too large for the cache don't notify to prevent from indexing incomplete requests
184  if ( fids.count() <= mCache.size() )
185  {
186  Q_FOREACH ( QgsAbstractCacheIndex* idx, mCacheIndices )
187  {
188  idx->requestCompleted( featureRequest, fids );
189  }
190  if ( featureRequest.filterType() == QgsFeatureRequest::FilterNone )
191  {
192  mFullCache = true;
193  }
194  }
195 }
196 
198 {
199  Q_FOREACH ( QgsAbstractCacheIndex* idx, mCacheIndices )
200  {
201  idx->flushFeature( fid );
202  }
203 }
204 
205 void QgsVectorLayerCache::onAttributeValueChanged( QgsFeatureId fid, int field, const QVariant& value )
206 {
207  QgsCachedFeature* cachedFeat = mCache[ fid ];
208 
209  if ( cachedFeat )
210  {
211  cachedFeat->mFeature->setAttribute( field, value );
212  }
213 
214  emit attributeValueChanged( fid, field, value );
215 }
216 
217 void QgsVectorLayerCache::featureDeleted( QgsFeatureId fid )
218 {
219  mCache.remove( fid );
220 }
221 
222 void QgsVectorLayerCache::onFeatureAdded( QgsFeatureId fid )
223 {
224  if ( mFullCache )
225  {
226  if ( cacheSize() <= mLayer->featureCount() )
227  {
228  setCacheSize( mLayer->featureCount() + 100 );
229  }
230 
231  QgsFeature feat;
232  featureAtId( fid, feat );
233  }
234  emit featureAdded( fid );
235 }
236 
237 void QgsVectorLayerCache::attributeAdded( int field )
238 {
239  Q_UNUSED( field )
240  mCachedAttributes.append( field );
241  invalidate();
242 }
243 
244 void QgsVectorLayerCache::attributeDeleted( int field )
245 {
246  QgsAttributeList attrs = mCachedAttributes;
247  mCachedAttributes.clear();
248 
249  Q_FOREACH ( int attr, attrs )
250  {
251  if ( attr < field )
252  mCachedAttributes << attr;
253  else if ( attr > field )
254  mCachedAttributes << attr - 1;
255  }
256 }
257 
258 void QgsVectorLayerCache::geometryChanged( QgsFeatureId fid, QgsGeometry& geom )
259 {
260  QgsCachedFeature* cachedFeat = mCache[ fid ];
261 
262  if ( cachedFeat )
263  {
264  cachedFeat->mFeature->setGeometry( geom );
265  }
266 }
267 
268 void QgsVectorLayerCache::layerDeleted()
269 {
270  emit cachedLayerDeleted();
271  mLayer = nullptr;
272 }
273 
274 void QgsVectorLayerCache::invalidate()
275 {
276  mCache.clear();
277  mFullCache = false;
278  emit invalidated();
279 }
280 
281 bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featureRequest, QgsFeatureIterator& it )
282 {
283  // check first for available indices
284  Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
285  {
286  if ( idx->getCacheIterator( it, featureRequest ) )
287  {
288  return true;
289  }
290  }
291 
292  // no indexes available, but maybe we have already cached all required features anyway?
293  switch ( featureRequest.filterType() )
294  {
296  {
297  if ( mCache.contains( featureRequest.filterFid() ) )
298  {
299  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
300  return true;
301  }
302  break;
303  }
305  {
306  if ( mCache.keys().toSet().contains( featureRequest.filterFids() ) )
307  {
308  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
309  return true;
310  }
311  break;
312  }
316  {
317  if ( mFullCache )
318  {
319  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
320  return true;
321  }
322  break;
323  }
324 
325  }
326  return false;
327 }
328 
330 {
332  bool requiresWriterIt = true; // If a not yet cached, but cachable request is made, this stays true.
333 
334  if ( checkInformationCovered( featureRequest ) )
335  {
336  // If we have a full cache available, run on this
337  if ( mFullCache )
338  {
339  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
340  requiresWriterIt = false;
341  }
342  else
343  {
344  // may still be able to satisfy request using cache
345  requiresWriterIt = !canUseCacheForRequest( featureRequest, it );
346  }
347  }
348  else
349  {
350  // Let the layer answer the request, so no caching of requests
351  // we don't want to cache is done
352  requiresWriterIt = false;
353  it = mLayer->getFeatures( featureRequest );
354  }
355 
356  if ( requiresWriterIt && mLayer->dataProvider() )
357  {
358  // No index was able to satisfy the request
359  QgsFeatureRequest myRequest = QgsFeatureRequest( featureRequest );
360 
361  // Make sure if we cache the geometry, it gets fetched
362  if ( mCacheGeometry && mLayer->hasGeometryType() )
363  myRequest.setFlags( featureRequest.flags() & ~QgsFeatureRequest::NoGeometry );
364 
365  // Make sure, all the cached attributes are requested as well
366  QSet<int> attrs = featureRequest.subsetOfAttributes().toSet() + mCachedAttributes.toSet();
367  myRequest.setSubsetOfAttributes( attrs.toList() );
368 
369  it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
370  }
371 
372  return it;
373 }
374 
376 {
377  return mCache.contains( fid );
378 }
379 
381 {
382  QgsAttributeList requestedAttributes;
383 
384  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::SubsetOfAttributes ) )
385  {
386  requestedAttributes = mLayer->attributeList();
387  }
388  else
389  {
390  requestedAttributes = featureRequest.subsetOfAttributes();
391  }
392 
393  // Check if we even cache the information requested
394  Q_FOREACH ( int attr, requestedAttributes )
395  {
396  if ( !mCachedAttributes.contains( attr ) )
397  {
398  return false;
399  }
400  }
401 
402  // If the request needs geometry but we don't cache this...
403  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::NoGeometry )
404  && !mCacheGeometry )
405  {
406  return false;
407  }
408 
409  return true;
410 }
void clear()
Wrapper for iterator of features from vector data provider or vector layer.
friend class QgsCachedFeatureWriterIterator
Filter using feature ID.
Filter using feature IDs.
QgsVectorLayerCache(QgsVectorLayer *layer, int cacheSize, QObject *parent=nullptr)
int size() const
const Flags & flags() const
void invalidated()
The cache has been invalidated and cleared.
bool contains(const Key &key) const
bool cacheGeometry() const
Returns true if the cache will fetch and cache feature geometries.
bool removeCachedFeature(QgsFeatureId fid)
Removes the feature identified by fid from the cache if present.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest())
Query the provider for features specified in request.
QgsFeatureId filterFid() const
Get the feature ID that should be fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
FilterType filterType() const
Return the filter type which is currently set on this request.
const QgsFeatureIds & filterFids() const
Get feature IDs that should be fetched.
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:76
void requestCompleted(const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids)
Gets called, whenever the full list of feature ids for a certain request is known.
int maxCost() const
QSet< T > toSet() const
void setMaxCost(int cost)
QgsVectorLayer * layer()
Returns the layer to which this cache belongs.
bool isFidCached(const QgsFeatureId fid) const
Check if a certain feature id is cached.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:187
void attributeValueChanged(QgsFeatureId fid, int field, const QVariant &value)
Is emitted when an attribute is changed.
bool disconnect(const QObject *sender, const char *signal, const QObject *receiver, const char *method)
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the subset of attributes to be cached.
virtual void requestCompleted(const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids)
Implement this method to update the the indices, in case you need information contained by the reques...
bool hasGeometryType() const
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
long featureCount(QgsSymbolV2 *symbol)
Number of features rendered with specified symbol.
void featureAdded(QgsFeatureId fid)
Is emitted, when a new feature has been added to the layer and this cache.
int elapsed() const
void append(const T &value)
void progress(int i, bool &cancel)
When filling the cache, this signal gets emitted periodically to notify about the progress and to be ...
This class wraps a request for features to a vector layer (or directly its vector data provider)...
int restart()
Obsolete, will be ignored. If a filterRect is set it will be used anyway. Filter using a rectangle...
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsFeatureRequest & setFlags(const QgsFeatureRequest::Flags &flags)
Set flags that affect how features will be fetched.
Fetch only a subset of attributes (setSubsetOfAttributes sets this flag)
int count() const
void cachedLayerDeleted()
Is emitted when the cached layer is deleted.
bool checkInformationCovered(const QgsFeatureRequest &featureRequest)
Checks if the information required to complete the request is cached.
QgsAttributeList subsetOfAttributes() const
Return the subset of attributes which at least need to be fetched.
bool contains(const T &value) const
virtual bool getCacheIterator(QgsFeatureIterator &featureIterator, const QgsFeatureRequest &featureRequest)=0
Is called, when a feature request is issued on a cached layer.
Abstract base class for cache indices.
Definition: qgscacheindex.h:29
QList< Key > keys() const
No filter is applied.
void setCacheGeometry(bool cacheGeometry)
Enable or disable the caching of geometries.
void setFullCache(bool fullCache)
This enables or disables full caching.
virtual void flushFeature(const QgsFeatureId fid)=0
Is called, whenever a feature is removed from the cache.
bool featureAtId(QgsFeatureId featureId, QgsFeature &feature, bool skipCache=false)
Gets the feature at the given feature id.
void setCacheSize(int cacheSize)
Sets the maximum number of features to keep in the cache.
qint64 QgsFeatureId
Definition: qgsfeature.h:31
QList< T > toList() const
void start()
void featureRemoved(QgsFeatureId fid)
Gets called, whenever a feature has been removed.
int cacheSize()
Returns the maximum number of features this cache will hold.
QgsVectorDataProvider * dataProvider()
Returns the data provider.
bool nextFeature(QgsFeature &f)
Geometry is not required. It may still be returned if e.g. required for a filter condition.
bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void addCacheIndex(QgsAbstractCacheIndex *cacheIndex)
Adds a QgsAbstractCacheIndex to this cache.
Represents a vector layer which manages a vector based data sets.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
bool remove(const Key &key)
void finished()
When filling the cache, this signal gets emitted once the cache is fully initialized.
friend class QgsCachedFeatureIterator
void setCacheAddedAttributes(bool cacheAddedAttributes)
If this is enabled, the subset of cached attributes will automatically be extended to also include ne...
void clear()