QGIS API Documentation 3.30.0-'s-Hertogenbosch (f186b8efe0)
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"
23#include "qgsvectorlayer.h"
24
25#include <QElapsedTimer>
26
27QgsVectorLayerCache::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
56{
57 mCache.setMaxCost( cacheSize );
58}
59
61{
62 return mCache.maxCost();
63}
64
65void 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
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
135void 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
147bool 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
243void 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
255void 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
278void 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
290void 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
305void QgsVectorLayerCache::attributeAdded( int field )
306{
307 Q_UNUSED( field )
308 mCachedAttributes.append( field );
309 invalidate();
310}
311
312void 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
327void 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
337void QgsVectorLayerCache::layerDeleted()
338{
339 emit cachedLayerDeleted();
340 mLayer = nullptr;
341}
342
343void QgsVectorLayerCache::invalidate()
344{
345 mCache.clear();
346 mCacheOrderedKeys.clear();
347 mCacheUnorderedKeys.clear();
348 mFullCache = false;
349 emit invalidated();
350}
351
352bool 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 ( cachedFeatureIds().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 const QgsAttributeList requestSubset = featureRequest.subsetOfAttributes();
438 QSet<int> attrs( requestSubset.begin(), requestSubset.end() );
439 for ( int attr : std::as_const( mCachedAttributes ) )
440 attrs.insert( attr );
441 myRequest.setSubsetOfAttributes( QgsAttributeList( attrs.begin(), attrs.end() ) );
442
443 it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
444 }
445
446 return it;
447}
448
450{
451 return mCache.contains( fid );
452}
453
455{
456 const QList< QgsFeatureId > keys = mCache.keys();
457 return QgsFeatureIds( keys.begin(), keys.end() );
458}
459
461{
462 QgsAttributeList requestedAttributes;
463
464 if ( !featureRequest.flags().testFlag( QgsFeatureRequest::SubsetOfAttributes ) )
465 {
466 requestedAttributes = mLayer->attributeList();
467 }
468 else
469 {
470 requestedAttributes = featureRequest.subsetOfAttributes();
471 }
472
473 // Check if we even cache the information requested
474 const auto constRequestedAttributes = requestedAttributes;
475 for ( int attr : constRequestedAttributes )
476 {
477 if ( !mCachedAttributes.contains( attr ) )
478 {
479 return false;
480 }
481 }
482
483 // If the request needs geometry but we don't cache this...
484 return !( !featureRequest.flags().testFlag( QgsFeatureRequest::NoGeometry )
485 && !mCacheGeometry );
486}
487
488void QgsVectorLayerCache::connectJoinedLayers() const
489{
490 const auto constVectorJoins = mLayer->vectorJoins();
491 for ( const QgsVectorLayerJoinInfo &info : constVectorJoins )
492 {
493 const QgsVectorLayer *vl = info.joinLayer();
494 if ( vl )
495 connect( vl, &QgsVectorLayer::attributeValueChanged, this, &QgsVectorLayerCache::onJoinAttributeValueChanged );
496 }
497}
@ NoFilter
No spatial filtering of features.
WkbType
The WKB type describes the number of dimensions a geometry has.
Definition: qgis.h:155
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.
@ FilterFid
Filter using feature ID.
@ FilterFids
Filter using feature IDs.
@ FilterNone
No filter is applied.
@ FilterExpression
Filter using expression.
const QgsFeatureIds & filterFids() const
Returns the feature IDs that should be fetched.
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:219
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:164
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
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.
QgsFeatureIds cachedFeatureIds() const
Returns the set of feature IDs for features which are cached.
bool cacheGeometry() const
Returns true if the cache will fetch and cache feature geometries.
Qgis::WkbType wkbType() const
Returns the geometry type for features in the cache.
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.
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.
Q_INVOKABLE Qgis::WkbType wkbType() const FINAL
Returns the WKBType or WKBUnknown in case of error.
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.
QgsVectorLayerJoinBuffer * joinBuffer()
Returns the join buffer object.
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.
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:501