QGIS API Documentation  3.21.0-Master (5b68dc587e)
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 #include "qgsvectorlayerjoininfo.h"
23 #include "qgsvectorlayer.h"
24 
25 #include <QElapsedTimer>
26 
27 QgsVectorLayerCache::QgsVectorLayerCache( QgsVectorLayer *layer, int cacheSize, QObject *parent )
28  : QObject( parent )
29  , mLayer( layer )
30 {
31  mCache.setMaxCost( cacheSize );
32 
33  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayerCache::featureDeleted );
34  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsVectorLayerCache::onFeatureAdded );
35  connect( mLayer, &QgsVectorLayer::destroyed, this, &QgsVectorLayerCache::layerDeleted );
36 
37  setCacheGeometry( true );
40 
41  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsVectorLayerCache::attributeDeleted );
42  connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerCache::invalidate );
43  connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsVectorLayerCache::invalidate );
44  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onAttributeValueChanged );
45 
46  connectJoinedLayers();
47 }
48 
50 {
51  qDeleteAll( mCacheIndices );
52  mCacheIndices.clear();
53 }
54 
55 void QgsVectorLayerCache::setCacheSize( int cacheSize )
56 {
57  mCache.setMaxCost( cacheSize );
58 }
59 
61 {
62  return mCache.maxCost();
63 }
64 
65 void QgsVectorLayerCache::setCacheGeometry( bool cacheGeometry )
66 {
67  bool shouldCacheGeometry = cacheGeometry && mLayer->isSpatial();
68  bool mustInvalidate = shouldCacheGeometry && !mCacheGeometry; // going from no geometry -> geometry, so have to clear existing cache entries
69  mCacheGeometry = shouldCacheGeometry;
70  if ( cacheGeometry )
71  {
72  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged, Qt::UniqueConnection );
73  }
74  else
75  {
76  disconnect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
77  }
78  if ( mustInvalidate )
79  {
80  invalidate();
81  }
82 }
83 
85 {
86  mCachedAttributes = attributes;
87 }
88 
89 void QgsVectorLayerCache::setFullCache( bool fullCache )
90 {
91  mFullCache = fullCache;
92 
93  if ( mFullCache )
94  {
95  // Add a little more than necessary...
96  setCacheSize( mLayer->featureCount() + 100 );
97 
98  // Initialize the cache...
100  .setSubsetOfAttributes( mCachedAttributes )
101  .setFlags( mCacheGeometry ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ) );
102 
103  int i = 0;
104 
105  QElapsedTimer t;
106  t.start();
107 
108  QgsFeature f;
109  while ( it.nextFeature( f ) )
110  {
111  ++i;
112 
113  if ( t.elapsed() > 1000 )
114  {
115  bool cancel = false;
116  emit progress( i, cancel );
117  if ( cancel )
118  break;
119 
120  t.restart();
121  }
122  }
123 
124  it.close();
125 
126  emit finished();
127  }
128 }
129 
131 {
132  mCacheIndices.append( cacheIndex );
133 }
134 
135 void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
136 {
137  if ( cacheAddedAttributes )
138  {
139  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
140  }
141  else
142  {
143  disconnect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
144  }
145 }
146 
147 bool QgsVectorLayerCache::featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache )
148 {
149  bool featureFound = false;
150 
151  QgsCachedFeature *cachedFeature = nullptr;
152 
153  if ( !skipCache )
154  {
155  cachedFeature = mCache[ featureId ];
156  }
157 
158  if ( cachedFeature )
159  {
160  feature = QgsFeature( *cachedFeature->feature() );
161  featureFound = true;
162  }
163  else if ( mLayer->getFeatures( QgsFeatureRequest()
164  .setFilterFid( featureId )
165  .setSubsetOfAttributes( mCachedAttributes )
166  .setFlags( !mCacheGeometry ? QgsFeatureRequest::NoGeometry : QgsFeatureRequest::Flags() ) )
167  .nextFeature( feature ) )
168  {
169  cacheFeature( feature );
170  featureFound = true;
171  }
172 
173  return featureFound;
174 }
175 
177 {
178  bool removed = mCache.remove( fid );
179  if ( removed )
180  {
181  if ( auto unorderedIt = std::find( mCacheUnorderedKeys.begin(), mCacheUnorderedKeys.end(), fid ); unorderedIt != mCacheUnorderedKeys.end() )
182  {
183  mCacheUnorderedKeys.erase( unorderedIt );
184 
185  if ( auto orderedIt = std::find( mCacheOrderedKeys.begin(), mCacheOrderedKeys.end(), fid ); orderedIt != mCacheOrderedKeys.end() )
186  mCacheOrderedKeys.erase( orderedIt );
187  }
188  }
189  return removed;
190 }
191 
193 {
194  return mLayer;
195 }
196 
198 {
199  return mLayer->crs();
200 }
201 
203 {
204  return mLayer->wkbType();
205 }
206 
208 {
209  return mLayer->fields();
210 }
211 
213 {
214  return mLayer->featureCount();
215 }
216 
218 {
219  // If a request is too large for the cache don't notify to prevent from indexing incomplete requests
220  if ( fids.count() <= mCache.size() )
221  {
222  for ( const auto &idx : std::as_const( mCacheIndices ) )
223  {
224  idx->requestCompleted( featureRequest, fids );
225  }
226  if ( featureRequest.filterType() == QgsFeatureRequest::FilterNone &&
227  ( featureRequest.spatialFilterType() == Qgis::SpatialFilterType::NoFilter || featureRequest.filterRect().contains( mLayer->extent() ) ) )
228  {
229  mFullCache = true;
230  }
231  }
232 }
233 
235 {
236  const auto constMCacheIndices = mCacheIndices;
237  for ( QgsAbstractCacheIndex *idx : constMCacheIndices )
238  {
239  idx->flushFeature( fid );
240  }
241 }
242 
243 void QgsVectorLayerCache::onAttributeValueChanged( QgsFeatureId fid, int field, const QVariant &value )
244 {
245  QgsCachedFeature *cachedFeat = mCache[ fid ];
246 
247  if ( cachedFeat )
248  {
249  cachedFeat->mFeature->setAttribute( field, value );
250  }
251 
252  emit attributeValueChanged( fid, field, value );
253 }
254 
255 void QgsVectorLayerCache::onJoinAttributeValueChanged( QgsFeatureId fid, int field, const QVariant &value )
256 {
257  const QgsVectorLayer *joinLayer = qobject_cast<const QgsVectorLayer *>( sender() );
258 
259  const auto constVectorJoins = mLayer->vectorJoins();
260  for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
261  {
262  if ( joinLayer == info.joinLayer() )
263  {
264  const QgsFeature feature = mLayer->joinBuffer()->targetedFeatureOf( &info, joinLayer->getFeature( fid ) );
265 
266  const QString fieldName = info.prefixedFieldName( joinLayer->fields().field( field ) );
267  const int fieldIndex = mLayer->fields().indexFromName( fieldName );
268 
269  if ( feature.isValid() && fieldIndex != -1 )
270  {
271  onAttributeValueChanged( feature.id(), fieldIndex, value );
272  return;
273  }
274  }
275  }
276 }
277 
278 void QgsVectorLayerCache::featureDeleted( QgsFeatureId fid )
279 {
280  mCache.remove( fid );
281 
282  if ( auto it = mCacheUnorderedKeys.find( fid ); it != mCacheUnorderedKeys.end() )
283  {
284  mCacheUnorderedKeys.erase( it );
285  if ( auto orderedIt = std::find( mCacheOrderedKeys.begin(), mCacheOrderedKeys.end(), fid ); orderedIt != mCacheOrderedKeys.end() )
286  mCacheOrderedKeys.erase( orderedIt );
287  }
288 }
289 
290 void QgsVectorLayerCache::onFeatureAdded( QgsFeatureId fid )
291 {
292  if ( mFullCache )
293  {
294  if ( cacheSize() <= mLayer->featureCount() )
295  {
296  setCacheSize( mLayer->featureCount() + 100 );
297  }
298 
299  QgsFeature feat;
300  featureAtId( fid, feat );
301  }
302  emit featureAdded( fid );
303 }
304 
305 void QgsVectorLayerCache::attributeAdded( int field )
306 {
307  Q_UNUSED( field )
308  mCachedAttributes.append( field );
309  invalidate();
310 }
311 
312 void QgsVectorLayerCache::attributeDeleted( int field )
313 {
314  QgsAttributeList attrs = mCachedAttributes;
315  mCachedAttributes.clear();
316 
317  const auto constAttrs = attrs;
318  for ( int attr : constAttrs )
319  {
320  if ( attr < field )
321  mCachedAttributes << attr;
322  else if ( attr > field )
323  mCachedAttributes << attr - 1;
324  }
325 }
326 
327 void QgsVectorLayerCache::geometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
328 {
329  QgsCachedFeature *cachedFeat = mCache[ fid ];
330 
331  if ( cachedFeat )
332  {
333  cachedFeat->mFeature->setGeometry( geom );
334  }
335 }
336 
337 void QgsVectorLayerCache::layerDeleted()
338 {
339  emit cachedLayerDeleted();
340  mLayer = nullptr;
341 }
342 
343 void QgsVectorLayerCache::invalidate()
344 {
345  mCache.clear();
346  mCacheOrderedKeys.clear();
347  mCacheUnorderedKeys.clear();
348  mFullCache = false;
349  emit invalidated();
350 }
351 
352 bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featureRequest, QgsFeatureIterator &it )
353 {
354  // check first for available indices
355  const auto constMCacheIndices = mCacheIndices;
356  for ( QgsAbstractCacheIndex *idx : constMCacheIndices )
357  {
358  if ( idx->getCacheIterator( it, featureRequest ) )
359  {
360  return true;
361  }
362  }
363 
364  // no indexes available, but maybe we have already cached all required features anyway?
365  switch ( featureRequest.filterType() )
366  {
368  {
369  if ( mCache.contains( featureRequest.filterFid() ) )
370  {
371  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
372  return true;
373  }
374  break;
375  }
377  {
378  if ( qgis::listToSet( mCache.keys() ).contains( featureRequest.filterFids() ) )
379  {
380  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
381  return true;
382  }
383  break;
384  }
387  {
388  if ( mFullCache )
389  {
390  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
391  return true;
392  }
393  break;
394  }
395 
396  }
397  return false;
398 }
399 
401 {
403  bool requiresWriterIt = true; // If a not yet cached, but cacheable request is made, this stays true.
404 
405  if ( checkInformationCovered( featureRequest ) )
406  {
407  // If we have a full cache available, run on this
408  if ( mFullCache )
409  {
410  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
411  requiresWriterIt = false;
412  }
413  else
414  {
415  // may still be able to satisfy request using cache
416  requiresWriterIt = !canUseCacheForRequest( featureRequest, it );
417  }
418  }
419  else
420  {
421  // Let the layer answer the request, so no caching of requests
422  // we don't want to cache is done
423  requiresWriterIt = false;
424  it = mLayer->getFeatures( featureRequest );
425  }
426 
427  if ( requiresWriterIt && mLayer->dataProvider() )
428  {
429  // No index was able to satisfy the request
430  QgsFeatureRequest myRequest = QgsFeatureRequest( featureRequest );
431 
432  // Make sure if we cache the geometry, it gets fetched
433  if ( mCacheGeometry && mLayer->isSpatial() )
434  myRequest.setFlags( featureRequest.flags() & ~QgsFeatureRequest::NoGeometry );
435 
436  // Make sure, all the cached attributes are requested as well
437  QSet<int> attrs = qgis::listToSet( featureRequest.subsetOfAttributes() ) + qgis::listToSet( mCachedAttributes );
438  myRequest.setSubsetOfAttributes( qgis::setToList( attrs ) );
439 
440  it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
441  }
442 
443  return it;
444 }
445 
447 {
448  return mCache.contains( fid );
449 }
450 
452 {
453  QgsAttributeList requestedAttributes;
454 
455  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::SubsetOfAttributes ) )
456  {
457  requestedAttributes = mLayer->attributeList();
458  }
459  else
460  {
461  requestedAttributes = featureRequest.subsetOfAttributes();
462  }
463 
464  // Check if we even cache the information requested
465  const auto constRequestedAttributes = requestedAttributes;
466  for ( int attr : constRequestedAttributes )
467  {
468  if ( !mCachedAttributes.contains( attr ) )
469  {
470  return false;
471  }
472  }
473 
474  // If the request needs geometry but we don't cache this...
475  return !( !featureRequest.flags().testFlag( QgsFeatureRequest::NoGeometry )
476  && !mCacheGeometry );
477 }
478 
479 void QgsVectorLayerCache::connectJoinedLayers() const
480 {
481  const auto constVectorJoins = mLayer->vectorJoins();
482  for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
483  {
484  const QgsVectorLayer *vl = info.joinLayer();
485  if ( vl )
486  connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onJoinAttributeValueChanged );
487  }
488 }
@ NoFilter
No spatial filtering of features.
Abstract base class for cache indices.
Definition: qgscacheindex.h:32
This class represents a coordinate reference system (CRS).
Wrapper for iterator of features from vector data provider or vector layer.
bool nextFeature(QgsFeature &f)
This class wraps a request for features to a vector layer (or directly its vector data provider).
QgsRectangle filterRect() const
Returns the rectangle from which features will be taken.
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be fetched.
Flags flags() const
Returns the flags which affect how features are fetched.
QgsFeatureRequest & setSubsetOfAttributes(const QgsAttributeList &attrs)
Set a subset of attributes that will be fetched.
@ SubsetOfAttributes
Fetch only a subset of attributes (setSubsetOfAttributes sets this flag)
@ NoGeometry
Geometry is not required. It may still be returned if e.g. required for a filter condition.
Qgis::SpatialFilterType spatialFilterType() const
Returns the spatial filter type which is currently set on this request.
QgsAttributeList subsetOfAttributes() const
Returns the subset of attributes which at least need to be fetched.
FilterType filterType() const
Returns the attribute/ID filter type which is currently set on this request.
const QgsFeatureIds & filterFids() const
Returns the feature IDs that should be fetched.
@ FilterFid
Filter using feature ID.
@ FilterFids
Filter using feature IDs.
@ FilterNone
No filter is applied.
@ FilterExpression
Filter using expression.
QgsFeatureId filterFid() const
Returns the feature ID that should be fetched.
The feature class encapsulates a single feature including its unique ID, geometry and a list of field...
Definition: qgsfeature.h:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:191
Q_GADGET QgsFeatureId id
Definition: qgsfeature.h:64
Container of fields for a vector layer.
Definition: qgsfields.h:45
int indexFromName(const QString &fieldName) const
Gets the field index from the field name.
Definition: qgsfields.cpp:202
QgsField field(int fieldIdx) const
Returns the field at particular index (must be in range 0..N-1).
Definition: qgsfields.cpp:168
A geometry is the spatial representation of a feature.
Definition: qgsgeometry.h:124
QgsCoordinateReferenceSystem crs
Definition: qgsmaplayer.h:79
void dataChanged()
Data of layer changed.
bool contains(const QgsRectangle &rect) const SIP_HOLDGIL
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
bool isFidCached(QgsFeatureId fid) const
Check if a certain feature id is cached.
void setFullCache(bool fullCache)
This enables or disables full caching.
void finished()
When filling the cache, this signal gets emitted once the cache is fully initialized.
void featureRemoved(QgsFeatureId fid)
Gets called, whenever a feature has been removed.
void setCacheAddedAttributes(bool cacheAddedAttributes)
If this is enabled, the subset of cached attributes will automatically be extended to also include ne...
void invalidated()
The cache has been invalidated and cleared.
void setCacheSize(int cacheSize)
Sets the maximum number of features to keep in the cache.
void setCacheSubsetOfAttributes(const QgsAttributeList &attributes)
Set the subset of attributes to be cached.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer and this cache.
QgsFields fields() const
Returns the fields associated with features in the cache.
void cachedLayerDeleted()
Is emitted when the cached layer is deleted.
friend class QgsCachedFeatureWriterIterator
QgsWkbTypes::Type wkbType() const
Returns the geometry type for features in the cache.
void requestCompleted(const QgsFeatureRequest &featureRequest, const QgsFeatureIds &fids)
Gets called, whenever the full list of feature ids for a certain request is known.
void attributeValueChanged(QgsFeatureId fid, int field, const QVariant &value)
Emitted when an attribute is changed.
bool removeCachedFeature(QgsFeatureId fid)
Removes the feature identified by fid from the cache if present.
void progress(int i, bool &cancel)
When filling the cache, this signal gets emitted periodically to notify about the progress and to be ...
QgsVectorLayer * layer()
Returns the layer to which this cache belongs.
long long featureCount() const
Returns the number of features contained in the source, or -1 if the feature count is unknown.
QgsCoordinateReferenceSystem sourceCrs() const
Returns the coordinate reference system for features in the cache.
bool checkInformationCovered(const QgsFeatureRequest &featureRequest)
Checks if the information required to complete the request is cached.
bool cacheGeometry() const
Returns true if the cache will fetch and cache feature geometries.
int cacheSize()
Returns the maximum number of features this cache will hold.
friend class QgsCachedFeatureIterator
void addCacheIndex(QgsAbstractCacheIndex *cacheIndex)
Adds a QgsAbstractCacheIndex to this cache.
QgsVectorLayerCache(QgsVectorLayer *layer, int cacheSize, QObject *parent=nullptr)
void setCacheGeometry(bool cacheGeometry)
Enable or disable the caching of geometries.
QgsFeatureIterator getFeatures(const QgsFeatureRequest &featureRequest=QgsFeatureRequest())
Query this VectorLayerCache for features.
bool featureAtId(QgsFeatureId featureId, QgsFeature &feature, bool skipCache=false)
Gets the feature at the given feature id.
QgsFeature targetedFeatureOf(const QgsVectorLayerJoinInfo *info, const QgsFeature &feature) const
Returns the targeted feature corresponding to the joined feature.
Defines left outer join from our vector layer to some other vector layer.
Represents a vector layer which manages a vector based data sets.
Q_INVOKABLE QgsWkbTypes::Type wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
void attributeAdded(int idx)
Will be emitted, when a new attribute has been added to this vector layer.
long long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
bool isSpatial() const FINAL
Returns true if this is a geometry layer and false in case of NoGeometry (table only) or UnknownGeome...
QgsFeatureIterator getFeatures(const QgsFeatureRequest &request=QgsFeatureRequest()) const FINAL
Queries the layer for features specified in request.
QgsFields fields() const FINAL
Returns the list of fields of this layer.
QgsAttributeList attributeList() const
Returns list of attribute indexes.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
void attributeDeleted(int idx)
Will be emitted, when an attribute has been deleted from this vector layer.
QgsVectorDataProvider * dataProvider() FINAL
Returns the layer's data provider, it may be nullptr.
QgsFeature getFeature(QgsFeatureId fid) const
Queries the layer for the feature with the given id.
void attributeValueChanged(QgsFeatureId fid, int idx, const QVariant &value)
Emitted whenever an attribute value change is done in the edit buffer.
QgsRectangle extent() const FINAL
Returns the extent of the layer.
void updatedFields()
Emitted whenever the fields available from this layer have been changed.
void featureAdded(QgsFeatureId fid)
Emitted when a new feature has been added to the layer.
void featureDeleted(QgsFeatureId fid)
Emitted when a feature has been deleted.
const QList< QgsVectorLayerJoinInfo > vectorJoins() const
void geometryChanged(QgsFeatureId fid, const QgsGeometry &geometry)
Emitted whenever a geometry change is done in the edit buffer.
Type
The WKB type describes the number of dimensions a geometry has.
Definition: qgswkbtypes.h:70
QSet< QgsFeatureId > QgsFeatureIds
Definition: qgsfeatureid.h:37
qint64 QgsFeatureId
64 bit feature ids negative numbers are used for uncommitted/newly added features
Definition: qgsfeatureid.h:28
QList< int > QgsAttributeList
Definition: qgsfield.h:26
const QgsField & field
Definition: qgsfield.h:463