QGIS API Documentation  3.19.0-Master (26212d215f)
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 QgsVectorLayerCache::QgsVectorLayerCache( QgsVectorLayer *layer, int cacheSize, QObject *parent )
26  : QObject( parent )
27  , mLayer( layer )
28 {
29  mCache.setMaxCost( cacheSize );
30 
31  connect( mLayer, &QgsVectorLayer::featureDeleted, this, &QgsVectorLayerCache::featureDeleted );
32  connect( mLayer, &QgsVectorLayer::featureAdded, this, &QgsVectorLayerCache::onFeatureAdded );
33  connect( mLayer, &QgsVectorLayer::destroyed, this, &QgsVectorLayerCache::layerDeleted );
34 
35  setCacheGeometry( true );
38 
39  connect( mLayer, &QgsVectorLayer::attributeDeleted, this, &QgsVectorLayerCache::attributeDeleted );
40  connect( mLayer, &QgsVectorLayer::updatedFields, this, &QgsVectorLayerCache::invalidate );
41  connect( mLayer, &QgsVectorLayer::dataChanged, this, &QgsVectorLayerCache::invalidate );
42  connect( mLayer, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onAttributeValueChanged );
43 
44  connectJoinedLayers();
45 }
46 
48 {
49  qDeleteAll( mCacheIndices );
50  mCacheIndices.clear();
51 }
52 
53 void QgsVectorLayerCache::setCacheSize( int cacheSize )
54 {
55  mCache.setMaxCost( cacheSize );
56 }
57 
59 {
60  return mCache.maxCost();
61 }
62 
63 void QgsVectorLayerCache::setCacheGeometry( bool cacheGeometry )
64 {
65  bool shouldCacheGeometry = cacheGeometry && mLayer->isSpatial();
66  bool mustInvalidate = shouldCacheGeometry && !mCacheGeometry; // going from no geometry -> geometry, so have to clear existing cache entries
67  mCacheGeometry = shouldCacheGeometry;
68  if ( cacheGeometry )
69  {
70  connect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
71  }
72  else
73  {
74  disconnect( mLayer, &QgsVectorLayer::geometryChanged, this, &QgsVectorLayerCache::geometryChanged );
75  }
76  if ( mustInvalidate )
77  {
78  invalidate();
79  }
80 }
81 
83 {
84  mCachedAttributes = attributes;
85 }
86 
87 void QgsVectorLayerCache::setFullCache( bool fullCache )
88 {
89  mFullCache = fullCache;
90 
91  if ( mFullCache )
92  {
93  // Add a little more than necessary...
94  setCacheSize( mLayer->featureCount() + 100 );
95 
96  // Initialize the cache...
98  .setSubsetOfAttributes( mCachedAttributes )
99  .setFlags( mCacheGeometry ? QgsFeatureRequest::NoFlags : QgsFeatureRequest::NoGeometry ) ) );
100 
101  int i = 0;
102 
103  QElapsedTimer t;
104  t.start();
105 
106  QgsFeature f;
107  while ( it.nextFeature( f ) )
108  {
109  ++i;
110 
111  if ( t.elapsed() > 1000 )
112  {
113  bool cancel = false;
114  emit progress( i, cancel );
115  if ( cancel )
116  break;
117 
118  t.restart();
119  }
120  }
121 
122  it.close();
123 
124  emit finished();
125  }
126 }
127 
129 {
130  mCacheIndices.append( cacheIndex );
131 }
132 
133 void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
134 {
135  if ( cacheAddedAttributes )
136  {
137  connect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
138  }
139  else
140  {
141  disconnect( mLayer, &QgsVectorLayer::attributeAdded, this, &QgsVectorLayerCache::attributeAdded );
142  }
143 }
144 
145 bool QgsVectorLayerCache::featureAtId( QgsFeatureId featureId, QgsFeature &feature, bool skipCache )
146 {
147  bool featureFound = false;
148 
149  QgsCachedFeature *cachedFeature = nullptr;
150 
151  if ( !skipCache )
152  {
153  cachedFeature = mCache[ featureId ];
154  }
155 
156  if ( cachedFeature )
157  {
158  feature = QgsFeature( *cachedFeature->feature() );
159  featureFound = true;
160  }
161  else if ( mLayer->getFeatures( QgsFeatureRequest()
162  .setFilterFid( featureId )
163  .setSubsetOfAttributes( mCachedAttributes )
164  .setFlags( !mCacheGeometry ? QgsFeatureRequest::NoGeometry : QgsFeatureRequest::Flags() ) )
165  .nextFeature( feature ) )
166  {
167  cacheFeature( feature );
168  featureFound = true;
169  }
170 
171  return featureFound;
172 }
173 
175 {
176  return mCache.remove( fid );
177 }
178 
180 {
181  return mLayer;
182 }
183 
185 {
186  return mLayer->crs();
187 }
188 
190 {
191  return mLayer->wkbType();
192 }
193 
195 {
196  return mLayer->fields();
197 }
198 
200 {
201  return mLayer->featureCount();
202 }
203 
205 {
206  // If a request is too large for the cache don't notify to prevent from indexing incomplete requests
207  if ( fids.count() <= mCache.size() )
208  {
209  for ( const auto &idx : qgis::as_const( mCacheIndices ) )
210  {
211  idx->requestCompleted( featureRequest, fids );
212  }
213  if ( featureRequest.filterType() == QgsFeatureRequest::FilterNone &&
214  ( featureRequest.filterRect().isNull() || featureRequest.filterRect().contains( mLayer->extent() ) ) )
215  {
216  mFullCache = true;
217  }
218  }
219 }
220 
222 {
223  const auto constMCacheIndices = mCacheIndices;
224  for ( QgsAbstractCacheIndex *idx : constMCacheIndices )
225  {
226  idx->flushFeature( fid );
227  }
228 }
229 
230 void QgsVectorLayerCache::onAttributeValueChanged( QgsFeatureId fid, int field, const QVariant &value )
231 {
232  QgsCachedFeature *cachedFeat = mCache[ fid ];
233 
234  if ( cachedFeat )
235  {
236  cachedFeat->mFeature->setAttribute( field, value );
237  }
238 
239  emit attributeValueChanged( fid, field, value );
240 }
241 
242 void QgsVectorLayerCache::onJoinAttributeValueChanged( QgsFeatureId fid, int field, const QVariant &value )
243 {
244  const QgsVectorLayer *joinLayer = qobject_cast<const QgsVectorLayer *>( sender() );
245 
246  const auto constVectorJoins = mLayer->vectorJoins();
247  for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
248  {
249  if ( joinLayer == info.joinLayer() )
250  {
251  const QgsFeature feature = mLayer->joinBuffer()->targetedFeatureOf( &info, joinLayer->getFeature( fid ) );
252 
253  const QString fieldName = info.prefixedFieldName( joinLayer->fields().field( field ) );
254  const int fieldIndex = mLayer->fields().indexFromName( fieldName );
255 
256  if ( feature.isValid() && fieldIndex != -1 )
257  {
258  onAttributeValueChanged( feature.id(), fieldIndex, value );
259  return;
260  }
261  }
262  }
263 }
264 
265 void QgsVectorLayerCache::featureDeleted( QgsFeatureId fid )
266 {
267  mCache.remove( fid );
268 }
269 
270 void QgsVectorLayerCache::onFeatureAdded( QgsFeatureId fid )
271 {
272  if ( mFullCache )
273  {
274  if ( cacheSize() <= mLayer->featureCount() )
275  {
276  setCacheSize( mLayer->featureCount() + 100 );
277  }
278 
279  QgsFeature feat;
280  featureAtId( fid, feat );
281  }
282  emit featureAdded( fid );
283 }
284 
285 void QgsVectorLayerCache::attributeAdded( int field )
286 {
287  Q_UNUSED( field )
288  mCachedAttributes.append( field );
289  invalidate();
290 }
291 
292 void QgsVectorLayerCache::attributeDeleted( int field )
293 {
294  QgsAttributeList attrs = mCachedAttributes;
295  mCachedAttributes.clear();
296 
297  const auto constAttrs = attrs;
298  for ( int attr : constAttrs )
299  {
300  if ( attr < field )
301  mCachedAttributes << attr;
302  else if ( attr > field )
303  mCachedAttributes << attr - 1;
304  }
305 }
306 
307 void QgsVectorLayerCache::geometryChanged( QgsFeatureId fid, const QgsGeometry &geom )
308 {
309  QgsCachedFeature *cachedFeat = mCache[ fid ];
310 
311  if ( cachedFeat )
312  {
313  cachedFeat->mFeature->setGeometry( geom );
314  }
315 }
316 
317 void QgsVectorLayerCache::layerDeleted()
318 {
319  emit cachedLayerDeleted();
320  mLayer = nullptr;
321 }
322 
323 void QgsVectorLayerCache::invalidate()
324 {
325  mCache.clear();
326  mFullCache = false;
327  emit invalidated();
328 }
329 
330 bool QgsVectorLayerCache::canUseCacheForRequest( const QgsFeatureRequest &featureRequest, QgsFeatureIterator &it )
331 {
332  // check first for available indices
333  const auto constMCacheIndices = mCacheIndices;
334  for ( QgsAbstractCacheIndex *idx : constMCacheIndices )
335  {
336  if ( idx->getCacheIterator( it, featureRequest ) )
337  {
338  return true;
339  }
340  }
341 
342  // no indexes available, but maybe we have already cached all required features anyway?
343  switch ( featureRequest.filterType() )
344  {
346  {
347  if ( mCache.contains( featureRequest.filterFid() ) )
348  {
349  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
350  return true;
351  }
352  break;
353  }
355  {
356  if ( qgis::listToSet( mCache.keys() ).contains( featureRequest.filterFids() ) )
357  {
358  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
359  return true;
360  }
361  break;
362  }
365  {
366  if ( mFullCache )
367  {
368  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
369  return true;
370  }
371  break;
372  }
373 
374  }
375  return false;
376 }
377 
379 {
381  bool requiresWriterIt = true; // If a not yet cached, but cacheable request is made, this stays true.
382 
383  if ( checkInformationCovered( featureRequest ) )
384  {
385  // If we have a full cache available, run on this
386  if ( mFullCache )
387  {
388  it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest ) );
389  requiresWriterIt = false;
390  }
391  else
392  {
393  // may still be able to satisfy request using cache
394  requiresWriterIt = !canUseCacheForRequest( featureRequest, it );
395  }
396  }
397  else
398  {
399  // Let the layer answer the request, so no caching of requests
400  // we don't want to cache is done
401  requiresWriterIt = false;
402  it = mLayer->getFeatures( featureRequest );
403  }
404 
405  if ( requiresWriterIt && mLayer->dataProvider() )
406  {
407  // No index was able to satisfy the request
408  QgsFeatureRequest myRequest = QgsFeatureRequest( featureRequest );
409 
410  // Make sure if we cache the geometry, it gets fetched
411  if ( mCacheGeometry && mLayer->isSpatial() )
412  myRequest.setFlags( featureRequest.flags() & ~QgsFeatureRequest::NoGeometry );
413 
414  // Make sure, all the cached attributes are requested as well
415  QSet<int> attrs = qgis::listToSet( featureRequest.subsetOfAttributes() ) + qgis::listToSet( mCachedAttributes );
416  myRequest.setSubsetOfAttributes( qgis::setToList( attrs ) );
417 
418  it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
419  }
420 
421  return it;
422 }
423 
425 {
426  return mCache.contains( fid );
427 }
428 
430 {
431  QgsAttributeList requestedAttributes;
432 
433  if ( !featureRequest.flags().testFlag( QgsFeatureRequest::SubsetOfAttributes ) )
434  {
435  requestedAttributes = mLayer->attributeList();
436  }
437  else
438  {
439  requestedAttributes = featureRequest.subsetOfAttributes();
440  }
441 
442  // Check if we even cache the information requested
443  const auto constRequestedAttributes = requestedAttributes;
444  for ( int attr : constRequestedAttributes )
445  {
446  if ( !mCachedAttributes.contains( attr ) )
447  {
448  return false;
449  }
450  }
451 
452  // If the request needs geometry but we don't cache this...
453  return !( !featureRequest.flags().testFlag( QgsFeatureRequest::NoGeometry )
454  && !mCacheGeometry );
455 }
456 
457 void QgsVectorLayerCache::connectJoinedLayers() const
458 {
459  const auto constVectorJoins = mLayer->vectorJoins();
460  for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
461  {
462  const QgsVectorLayer *vl = info.joinLayer();
463  if ( vl )
464  connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onJoinAttributeValueChanged );
465  }
466 }
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).
QgsFeatureRequest & setFlags(QgsFeatureRequest::Flags flags)
Sets flags that affect how features will be 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.
QgsAttributeList subsetOfAttributes() const
Returns the subset of attributes which at least need to be fetched.
const QgsRectangle & filterRect() const
Returns the rectangle from which features will be taken.
FilterType filterType() const
Returns the filter type which is currently set on this request.
const QgsFeatureIds & filterFids() const
Gets 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.
const Flags & flags() const
QgsFeatureId filterFid() const
Gets the feature ID that should be fetched.
The feature class encapsulates a single feature including its id, geometry and a list of field/values...
Definition: qgsfeature.h:56
bool isValid() const
Returns the validity of this feature.
Definition: qgsfeature.cpp:190
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
Gets 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:91
void dataChanged()
Data of layer changed.
bool contains(const QgsRectangle &rect) const
Returns true when rectangle contains other rectangle.
Definition: qgsrectangle.h:363
bool isNull() const
Test if the rectangle is null (all coordinates zero or after call to setMinimal()).
Definition: qgsrectangle.h:468
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.
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.
long featureCount() const
Returns the number of features contained in the source, or -1 if the feature count is unknown.
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.
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.
long featureCount(const QString &legendKey) const
Number of features rendered with specified legend key.
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