QGIS API Documentation  2.99.0-Master (716ff6c)
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 
22 QgsVectorLayerCache::QgsVectorLayerCache( QgsVectorLayer *layer, int cacheSize, QObject *parent )
23  : QObject( parent )
24  , mLayer( layer )
25  , mFullCache( false )
26 {
27  mCache.setMaxCost( cacheSize );
28 
29  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayerCache::featureDeleted );
30  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsVectorLayerCache::onFeatureAdded );
31  connect( mLayer, &QgsVectorLayer::destroyed, this, &QgsVectorLayerCache::layerDeleted );
32 
33  setCacheGeometry( true );
36 
37  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsVectorLayerCache::attributeDeleted );
38  connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerCache::invalidate );
39  connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsVectorLayerCache::invalidate );
40  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onAttributeValueChanged );
41 }
42 
44 {
45  qDeleteAll( mCacheIndices );
46  mCacheIndices.clear();
47 }
48 
50 {
51  mCache.setMaxCost( cacheSize );
52 }
53 
55 {
56  return mCache.maxCost();
57 }
58 
60 {
61  bool shouldCacheGeometry = cacheGeometry && mLayer->hasGeometryType();
62  bool mustInvalidate = shouldCacheGeometry && !mCacheGeometry; // going from no geometry -> geometry, so have to clear existing cache entries
63  mCacheGeometry = shouldCacheGeometry;
64  if ( cacheGeometry )
65  {
66  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
67  }
68  else
69  {
70  disconnect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
71  }
72  if ( mustInvalidate )
73  {
74  invalidate();
75  }
76 }
77 
79 {
80  mCachedAttributes = attributes;
81 }
82 
83 void QgsVectorLayerCache::setFullCache( bool fullCache )
84 {
85  mFullCache = fullCache;
86 
87  if ( mFullCache )
88  {
89  // Add a little more than necessary...
90  setCacheSize( mLayer->featureCount() + 100 );
91 
92  // Initialize the cache...
94  .setSubsetOfAttributes( mCachedAttributes )
95  .setFlags( mCacheGeometry ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ) );
96 
97  int i = 0;
98 
99  QTime t;
100  t.start();
101 
102  QgsFeature f;
103  while ( it.nextFeature( f ) )
104  {
105  ++i;
106 
107  if ( t.elapsed() > 1000 )
108  {
109  bool cancel = false;
110  emit progress( i, cancel );
111  if ( cancel )
112  break;
113 
114  t.restart();
115  }
116  }
117 
118  it.close();
119 
120  emit finished();
121  }
122 }
123 
125 {
126  mCacheIndices.append( cacheIndex );
127 }
128 
129 void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
130 {
131  if ( cacheAddedAttributes )
132  {
133  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
134  }
135  else
136  {
137  disconnect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
138  }
139 }
140 
141 bool QgsVectorLayerCache::featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache )
142 {
143  bool featureFound = false;
144 
145  QgsCachedFeature *cachedFeature = nullptr;
146 
147  if ( !skipCache )
148  {
149  cachedFeature = mCache[ featureId ];
150  }
151 
152  if ( cachedFeature )
153  {
154  feature = QgsFeature( *cachedFeature->feature() );
155  featureFound = true;
156  }
157  else if ( mLayer->getFeatures( QgsFeatureRequest()
158  .setFilterFid( featureId )
159  .setSubsetOfAttributes( mCachedAttributes )
160  .setFlags( !mCacheGeometry ? QgsFeatureRequest::NoGeometry : QgsFeatureRequest::Flags( nullptr ) ) )
161  .nextFeature( feature ) )
162  {
163  cacheFeature( feature );
164  featureFound = true;
165  }
166 
167  return featureFound;
168 }
169 
171 {
172  return mCache.remove( fid );
173 }
174 
176 {
177  return mLayer;
178 }
179 
181 {
182  // If a request is too large for the cache don't notify to prevent from indexing incomplete requests
183  if ( fids.count() <= mCache.size() )
184  {
185  Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
186  {
187  idx->requestCompleted( featureRequest, fids );
188  }
189  if ( featureRequest.filterType() == QgsFeatureRequest::FilterNone )
190  {
191  mFullCache = true;
192  }
193  }
194 }
195 
197 {
198  Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
199  {
200  idx->flushFeature( fid );
201  }
202 }
203 
204 void QgsVectorLayerCache::onAttributeValueChanged( QgsFeatureId fid, int field, const QVariant &value )
205 {
206  QgsCachedFeature *cachedFeat = mCache[ fid ];
207 
208  if ( cachedFeat )
209  {
210  cachedFeat->mFeature->setAttribute( field, value );
211  }
212 
213  emit attributeValueChanged( fid, field, value );
214 }
215 
216 void QgsVectorLayerCache::featureDeleted( QgsFeatureId fid )
217 {
218  mCache.remove( fid );
219 }
220 
221 void QgsVectorLayerCache::onFeatureAdded( QgsFeatureId fid )
222 {
223  if ( mFullCache )
224  {
225  if ( cacheSize() <= mLayer->featureCount() )
226  {
227  setCacheSize( mLayer->featureCount() + 100 );
228  }
229 
230  QgsFeature feat;
231  featureAtId( fid, feat );
232  }
233  emit featureAdded( fid );
234 }
235 
236 void QgsVectorLayerCache::attributeAdded( int field )
237 {
238  Q_UNUSED( field )
239  mCachedAttributes.append( field );
240  invalidate();
241 }
242 
243 void QgsVectorLayerCache::attributeDeleted( int field )
244 {
245  QgsAttributeList attrs = mCachedAttributes;
246  mCachedAttributes.clear();
247 
248  Q_FOREACH ( int attr, attrs )
249  {
250  if ( attr < field )
251  mCachedAttributes << attr;
252  else if ( attr > field )
253  mCachedAttributes << attr - 1;
254  }
255 }
256 
257 void QgsVectorLayerCache::geometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
258 {
259  QgsCachedFeature *cachedFeat = mCache[ fid ];
260 
261  if ( cachedFeat )
262  {
263  cachedFeat->mFeature->setGeometry( geom );
264  }
265 }
266 
267 void QgsVectorLayerCache::layerDeleted()
268 {
269  emit cachedLayerDeleted();
270  mLayer = nullptr;
271 }
272 
273 void QgsVectorLayerCache::invalidate()
274 {
275  mCache.clear();
276  mFullCache = false;
277  emit invalidated();
278 }
279 
280 bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featureRequest, QgsFeatureIterator &it )
281 {
282  // check first for available indices
283  Q_FOREACH ( QgsAbstractCacheIndex *idx, mCacheIndices )
284  {
285  if ( idx->getCacheIterator( it, featureRequest ) )
286  {
287  return true;
288  }
289  }
290 
291  // no indexes available, but maybe we have already cached all required features anyway?
292  switch ( featureRequest.filterType() )
293  {
295  {
296  if ( mCache.contains( featureRequest.filterFid() ) )
297  {
298  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
299  return true;
300  }
301  break;
302  }
304  {
305  if ( mCache.keys().toSet().contains( featureRequest.filterFids() ) )
306  {
307  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
308  return true;
309  }
310  break;
311  }
314  {
315  if ( mFullCache )
316  {
317  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
318  return true;
319  }
320  break;
321  }
322 
323  }
324  return false;
325 }
326 
328 {
330  bool requiresWriterIt = true; // If a not yet cached, but cachable request is made, this stays true.
331 
332  if ( checkInformationCovered( featureRequest ) )
333  {
334  // If we have a full cache available, run on this
335  if ( mFullCache )
336  {
337  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
338  requiresWriterIt = false;
339  }
340  else
341  {
342  // may still be able to satisfy request using cache
343  requiresWriterIt = !canUseCacheForRequest( featureRequest, it );
344  }
345  }
346  else
347  {
348  // Let the layer answer the request, so no caching of requests
349  // we don't want to cache is done
350  requiresWriterIt = false;
351  it = mLayer->getFeatures( featureRequest );
352  }
353 
354  if ( requiresWriterIt && mLayer->dataProvider() )
355  {
356  // No index was able to satisfy the request
357  QgsFeatureRequest myRequest = QgsFeatureRequest( featureRequest );
358 
359  // Make sure if we cache the geometry, it gets fetched
360  if ( mCacheGeometry && mLayer->hasGeometryType() )
361  myRequest.setFlags( featureRequest.flags() & ~QgsFeatureRequest::NoGeometry );
362 
363  // Make sure, all the cached attributes are requested as well
364  QSet<int> attrs = featureRequest.subsetOfAttributes().toSet() + mCachedAttributes.toSet();
365  myRequest.setSubsetOfAttributes( attrs.toList() );
366 
367  it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
368  }
369 
370  return it;
371 }
372 
374 {
375  return mCache.contains( fid );
376 }
377 
379 {
380  QgsAttributeList requestedAttributes;
381 
382  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::SubsetOfAttributes ) )
383  {
384  requestedAttributes = mLayer->attributeList();
385  }
386  else
387  {
388  requestedAttributes = featureRequest.subsetOfAttributes();
389  }
390 
391  // Check if we even cache the information requested
392  Q_FOREACH ( int attr, requestedAttributes )
393  {
394  if ( !mCachedAttributes.contains( attr ) )
395  {
396  return false;
397  }
398  }
399 
400  // If the request needs geometry but we don't cache this...
401  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::NoGeometry )
402  && !mCacheGeometry )
403  {
404  return false;
405  }
406 
407  return true;
408 }
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)
const Flags & flags() const
void invalidated()
The cache has been invalidated and cleared.
bool cacheGeometry() const
Returns true if the cache will fetch and cache feature geometries.
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeature.h:517
bool removeCachedFeature(QgsFeatureId fid)
Removes the feature identified by fid from the cache if present.
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:79
void requestCompleted(const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids)
Gets called, whenever the full list of feature ids for a certain request is known.
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:61
void attributeValueChanged(QgsFeatureId fid, int field, const QVariant &value)
Is emitted when an attribute is changed.
void featureDeleted(QgsFeatureId fid)
Emitted when a feature has been deleted.
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...
void featureAdded(QgsFeatureId fid)
Is emitted, when a new feature has been added to the layer and this cache.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const
Query the layer for features specified in request.
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)...
QList< int > QgsAttributeList
void geometryChanged(QgsFeatureId fid, const QgsGeometry &geometry)
Is emitted whenever a geometry change is done in the edit buffer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Is emitted whenever an attribute value change is done in the edit buffer.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer.
Fetch only a subset of attributes (setSubsetOfAttributes sets this flag)
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.
virtual bool getCacheIterator(QgsFeatureIterator &featureIterator, const QgsFeatureRequest &featureRequest)=0
Is called, when a feature request is issued on a cached layer.
void attributeAdded(int idx)
Will be emitted, when a new attribute has been added to this vector layer.
Abstract base class for cache indices.
Definition: qgscacheindex.h:30
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.
QgsVectorDataProvider * dataProvider() override
Returns the layer&#39;s data provider.
qint64 QgsFeatureId
Definition: qgsfeature.h:37
void dataChanged()
Data of layer changed.
void featureRemoved(QgsFeatureId fid)
Gets called, whenever a feature has been removed.
int cacheSize()
Returns the maximum number of features this cache will hold.
bool nextFeature(QgsFeature &f)
void attributeDeleted(int idx)
Will be emitted, when an attribute has been deleted from this vector layer.
Geometry is not required. It may still be returned if e.g. required for a filter condition.
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.
void updatedFields()
Is emitted, whenever the fields available from this layer have been changed.
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...
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Set flags that affect how features will be fetched.