mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-30 00:04:26 -04:00
Fix attributetable and vectorlayercache
* In the attributetable there was a mess with references and pointers, originating from 66fadee8ef. * QgsVectorLayerCache did sometimes cache features which did not contain all information which needs to be cached and therefore corrupting the cache and leading to incomplete cache hits. * Add a unit test for the cache problem * Fix QgsCacheIndexFeatureId * QgsAbstractCacheIndex::getCacheIterator now produces a QgsFeatureIterator (instead of a list of Feature Ids). This allows to combine a mixed response, partly satisfied by the cache and partly by an additional query to the backend.
This commit is contained in:
parent
92d24fb120
commit
31cecdbc51
@ -19,6 +19,7 @@
|
|||||||
#include "qgsfeature.h" // QgsFeatureIds
|
#include "qgsfeature.h" // QgsFeatureIds
|
||||||
|
|
||||||
class QgsFeatureRequest;
|
class QgsFeatureRequest;
|
||||||
|
class QgsFeatureIterator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief
|
* @brief
|
||||||
@ -60,14 +61,15 @@ class QgsAbstractCacheIndex
|
|||||||
* and write the list of feature ids of cached features to cachedFeatures. If it is not able
|
* and write the list of feature ids of cached features to cachedFeatures. If it is not able
|
||||||
* it will return false and the cachedFeatures state is undefined.
|
* it will return false and the cachedFeatures state is undefined.
|
||||||
*
|
*
|
||||||
* @param cachedFeatures A reference to {@link QgsFeatureIds}, where a list of ids is written to,
|
* @param featureIterator A reference to a {@link QgsFeatureIterator}. A valid featureIterator will
|
||||||
* in case this index is able to answer the request.
|
* be assigned in case this index is able to answer the request and the return
|
||||||
|
* value is true.
|
||||||
* @param featureRequest The feature request, for which this index is queried.
|
* @param featureRequest The feature request, for which this index is queried.
|
||||||
*
|
*
|
||||||
* @return True, if this index holds the information to answer the request.
|
* @return True, if this index holds the information to answer the request.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
virtual bool getCachedIds( QgsFeatureIds& cachedFeatures, const QgsFeatureRequest& featureRequest ) = 0;
|
virtual bool getCacheIterator( QgsFeatureIterator& featureIterator, const QgsFeatureRequest& featureRequest ) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QGSCACHEINDEX_H
|
#endif // QGSCACHEINDEX_H
|
||||||
|
@ -14,11 +14,45 @@
|
|||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "qgscacheindexfeatureid.h"
|
#include "qgscacheindexfeatureid.h"
|
||||||
|
#include "qgsfeaturerequest.h"
|
||||||
|
#include "qgscachedfeatureiterator.h"
|
||||||
|
#include "qgsvectorlayercache.h"
|
||||||
|
|
||||||
QgsCacheIndexFeatureId::QgsCacheIndexFeatureId( QgsCachedVectorLayer* cachedVectorLayer )
|
QgsCacheIndexFeatureId::QgsCacheIndexFeatureId( QgsVectorLayerCache* cachedVectorLayer )
|
||||||
: QgsAbstractCacheIndex()
|
: QgsAbstractCacheIndex()
|
||||||
, C( cachedVectorLayer )
|
, C( cachedVectorLayer )
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsCacheIndexFeatureId::flushFeature(const QgsFeatureId fid)
|
||||||
|
{
|
||||||
|
Q_UNUSED( fid )
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCacheIndexFeatureId::flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void QgsCacheIndexFeatureId::requestCompleted( QgsFeatureRequest featureRequest, QgsFeatureIds fids )
|
||||||
|
{
|
||||||
|
Q_UNUSED( featureRequest )
|
||||||
|
Q_UNUSED( fids )
|
||||||
|
}
|
||||||
|
|
||||||
|
bool QgsCacheIndexFeatureId::getCacheIterator( QgsFeatureIterator &featureIterator, const QgsFeatureRequest &featureRequest )
|
||||||
|
{
|
||||||
|
if( featureRequest.filterType() == QgsFeatureRequest::FilterFid )
|
||||||
|
{
|
||||||
|
if ( C->isFidCached( featureRequest.filterFid() ) )
|
||||||
|
{
|
||||||
|
QgsFeatureIds fids;
|
||||||
|
fids << featureRequest.filterFid();
|
||||||
|
featureIterator = QgsFeatureIterator( new QgsCachedFeatureIterator( C, featureRequest, fids ) );
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ -18,19 +18,57 @@
|
|||||||
|
|
||||||
#include "qgscacheindex.h"
|
#include "qgscacheindex.h"
|
||||||
|
|
||||||
class QgsCachedVectorLayer;
|
class QgsVectorLayerCache;
|
||||||
|
|
||||||
class QgsCacheIndexFeatureId : public QgsAbstractCacheIndex
|
class QgsCacheIndexFeatureId : public QgsAbstractCacheIndex
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
QgsCacheIndexFeatureId( QgsCachedVectorLayer* cachedVectorLayer );
|
QgsCacheIndexFeatureId( QgsVectorLayerCache* );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is called, whenever a feature is removed from the cache. You should update your indexes, so
|
||||||
|
* they become invalid in case this feature was required to successfuly answer a request.
|
||||||
|
*/
|
||||||
|
virtual void flushFeature( const QgsFeatureId fid );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sometimes, the whole cache changes its state and its easier to just withdraw everything.
|
||||||
|
* In this case, this method is issued. Be sure to clear all cache information in here.
|
||||||
|
*/
|
||||||
|
virtual void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief
|
||||||
|
* Implement this method to update the the indices, in case you need information contained by the request
|
||||||
|
* to properly index. (E.g. spatial index)
|
||||||
|
* Does nothing by default
|
||||||
|
*
|
||||||
|
* @param featureRequest The feature request that was answered
|
||||||
|
* @param fids The feature ids that have been returned
|
||||||
|
*/
|
||||||
|
virtual void requestCompleted( QgsFeatureRequest featureRequest, QgsFeatureIds fids );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Is called, when a feature request is issued on a cached layer.
|
||||||
|
* If this cache index is able to completely answer the feature request, it will return true
|
||||||
|
* and write the list of feature ids of cached features to cachedFeatures. If it is not able
|
||||||
|
* it will return false and the cachedFeatures state is undefined.
|
||||||
|
*
|
||||||
|
* @param cachedFeatures A reference to {@link QgsFeatureIds}, where a list of ids is written to,
|
||||||
|
* in case this index is able to answer the request.
|
||||||
|
* @param featureRequest The feature request, for which this index is queried.
|
||||||
|
*
|
||||||
|
* @return True, if this index holds the information to answer the request.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
virtual bool getCacheIterator( QgsFeatureIterator& featureIterator, const QgsFeatureRequest& featureRequest );
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgsCachedVectorLayer* C;
|
QgsVectorLayerCache* C;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // QGSCACHEINDEXFEATUREID_H
|
#endif // QGSCACHEINDEXFEATUREID_H
|
||||||
|
@ -38,6 +38,15 @@ QgsFeatureRequest::QgsFeatureRequest( const QgsRectangle& rect )
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QgsFeatureRequest::QgsFeatureRequest( const QgsFeatureRequest &rh )
|
||||||
|
{
|
||||||
|
mFlags = rh.mFlags;
|
||||||
|
mFilter = rh.mFilter;
|
||||||
|
mFilterRect = rh.mFilterRect;
|
||||||
|
mFilterFid = rh.mFilterFid;
|
||||||
|
mAttrs = rh.mAttrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
QgsFeatureRequest& QgsFeatureRequest::setFilterRect( const QgsRectangle& rect )
|
QgsFeatureRequest& QgsFeatureRequest::setFilterRect( const QgsRectangle& rect )
|
||||||
{
|
{
|
||||||
|
@ -78,6 +78,8 @@ class CORE_EXPORT QgsFeatureRequest
|
|||||||
//! construct a request with rectangle filter
|
//! construct a request with rectangle filter
|
||||||
explicit QgsFeatureRequest( const QgsRectangle& rect );
|
explicit QgsFeatureRequest( const QgsRectangle& rect );
|
||||||
|
|
||||||
|
QgsFeatureRequest( const QgsFeatureRequest& rh );
|
||||||
|
|
||||||
FilterType filterType() const { return mFilter; }
|
FilterType filterType() const { return mFilter; }
|
||||||
|
|
||||||
//! Set rectangle from which features will be taken. Empty rectangle removes the filter.
|
//! Set rectangle from which features will be taken. Empty rectangle removes the filter.
|
||||||
|
@ -105,6 +105,11 @@ void QgsVectorLayerCache::setFullCache( bool fullCache )
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QgsVectorLayerCache::addCacheIndex( QgsAbstractCacheIndex* cacheIndex )
|
||||||
|
{
|
||||||
|
mCacheIndices.append( cacheIndex );
|
||||||
|
}
|
||||||
|
|
||||||
void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
|
void QgsVectorLayerCache::setCacheAddedAttributes( bool cacheAddedAttributes )
|
||||||
{
|
{
|
||||||
if ( cacheAddedAttributes )
|
if ( cacheAddedAttributes )
|
||||||
@ -251,11 +256,8 @@ QgsFeatureIterator QgsVectorLayerCache::getFeatures( const QgsFeatureRequest &fe
|
|||||||
// Check if an index is able to deliver the requested features
|
// Check if an index is able to deliver the requested features
|
||||||
foreach ( QgsAbstractCacheIndex *idx, mCacheIndices )
|
foreach ( QgsAbstractCacheIndex *idx, mCacheIndices )
|
||||||
{
|
{
|
||||||
QgsFeatureIds featureIds;
|
if ( idx->getCacheIterator( it, featureRequest ) )
|
||||||
|
|
||||||
if ( idx->getCachedIds( featureIds, featureRequest ) )
|
|
||||||
{
|
{
|
||||||
it = QgsFeatureIterator( new QgsCachedFeatureIterator( this, featureRequest, featureIds ) );
|
|
||||||
requiresWriterIt = false;
|
requiresWriterIt = false;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -272,12 +274,27 @@ QgsFeatureIterator QgsVectorLayerCache::getFeatures( const QgsFeatureRequest &fe
|
|||||||
if ( requiresWriterIt && mLayer->dataProvider() )
|
if ( requiresWriterIt && mLayer->dataProvider() )
|
||||||
{
|
{
|
||||||
// No index was able to satisfy the request
|
// No index was able to satisfy the request
|
||||||
it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, featureRequest ) );
|
QgsFeatureRequest myRequest = QgsFeatureRequest( myRequest );
|
||||||
|
|
||||||
|
// Make sure if we cache the geometry, it gets fetched
|
||||||
|
if ( mCacheGeometry )
|
||||||
|
myRequest.setFlags( featureRequest.flags() & ~QgsFeatureRequest::NoGeometry );
|
||||||
|
|
||||||
|
// Make sure, all the cached attributes are requested as well
|
||||||
|
QSet<int> attrs = featureRequest.subsetOfAttributes().toSet() + mCachedAttributes.toSet();
|
||||||
|
myRequest.setSubsetOfAttributes( attrs.toList() );
|
||||||
|
|
||||||
|
it = QgsFeatureIterator( new QgsCachedFeatureWriterIterator( this, myRequest ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool QgsVectorLayerCache::isFidCached( const QgsFeatureId fid )
|
||||||
|
{
|
||||||
|
return mCache.contains( fid );
|
||||||
|
}
|
||||||
|
|
||||||
bool QgsVectorLayerCache::checkInformationCovered( const QgsFeatureRequest& featureRequest )
|
bool QgsVectorLayerCache::checkInformationCovered( const QgsFeatureRequest& featureRequest )
|
||||||
{
|
{
|
||||||
QgsAttributeList requestedAttributes;
|
QgsAttributeList requestedAttributes;
|
||||||
|
@ -139,10 +139,26 @@ class CORE_EXPORT QgsVectorLayerCache : public QObject
|
|||||||
*
|
*
|
||||||
* @param cacheIndex The cache index to add.
|
* @param cacheIndex The cache index to add.
|
||||||
*/
|
*/
|
||||||
void addCacheIndex( const QgsAbstractCacheIndex& cacheIndex );
|
void addCacheIndex( QgsAbstractCacheIndex *cacheIndex );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query this VectorLayerCache for features.
|
||||||
|
* If the VectorLayerCache (and moreover any of its indices) is able to satisfy
|
||||||
|
* the request, the returned {@link QgsFeatureIterator} will iterate over cached features.
|
||||||
|
* If it's not possible to fully satisfy the request from the cache, part or all of the features
|
||||||
|
* will be requested from the data provider.
|
||||||
|
* @param featureRequest The request specifying filter and required data.
|
||||||
|
* @return An iterator over the requested data.
|
||||||
|
*/
|
||||||
QgsFeatureIterator getFeatures( const QgsFeatureRequest& featureRequest );
|
QgsFeatureIterator getFeatures( const QgsFeatureRequest& featureRequest );
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a certain feature id is cached.
|
||||||
|
* @param fid The feature id to look for
|
||||||
|
* @return True if this id is in the cache
|
||||||
|
*/
|
||||||
|
bool isFidCached(const QgsFeatureId fid );
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features
|
* Gets the feature at the given feature id. Considers the changed, added, deleted and permanent features
|
||||||
* @param featureId The id of the feature to query
|
* @param featureId The id of the feature to query
|
||||||
|
@ -486,31 +486,27 @@ QVariant QgsAttributeTableModel::data( const QModelIndex &index, int role ) cons
|
|||||||
return QVariant( Qt::AlignLeft );
|
return QVariant( Qt::AlignLeft );
|
||||||
}
|
}
|
||||||
|
|
||||||
const QVariant* pVal = NULL;
|
QVariant val;
|
||||||
|
|
||||||
// if we don't have the row in current cache, load it from layer first
|
// if we don't have the row in current cache, load it from layer first
|
||||||
if ( mFeat.id() != rowId || !mFeat.isValid() )
|
if ( mCachedField == fieldId )
|
||||||
{
|
{
|
||||||
if ( mCachedField == fieldId )
|
val = mFieldCache[ rowId ];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ( mFeat.id() != rowId || !mFeat.isValid() )
|
||||||
{
|
{
|
||||||
const QVariant& val = mFieldCache[rowId];
|
if ( !loadFeatureAtId( rowId ) )
|
||||||
pVal = &val;
|
return QVariant( "ERROR" );
|
||||||
|
|
||||||
|
if ( mFeat.id() != rowId )
|
||||||
|
return QVariant( "ERROR" );
|
||||||
}
|
}
|
||||||
else if ( !loadFeatureAtId( rowId ) )
|
|
||||||
return QVariant( "ERROR" );
|
val = mFeat.attribute( fieldId );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( pVal == NULL && mFeat.id() != rowId )
|
|
||||||
return QVariant( "ERROR" );
|
|
||||||
|
|
||||||
if ( !pVal )
|
|
||||||
{
|
|
||||||
const QVariant& val = mFeat.attribute( fieldId );
|
|
||||||
pVal =&val;
|
|
||||||
}
|
|
||||||
|
|
||||||
const QVariant& val = *pVal;
|
|
||||||
|
|
||||||
// For sorting return unprocessed value
|
// For sorting return unprocessed value
|
||||||
if ( SortRole == role )
|
if ( SortRole == role )
|
||||||
{
|
{
|
||||||
|
@ -261,7 +261,7 @@ class GUI_EXPORT QgsAttributeTableModel: public QAbstractTableModel
|
|||||||
/** The currently cached column */
|
/** The currently cached column */
|
||||||
int mCachedField;
|
int mCachedField;
|
||||||
/** Allows to cache one specific column (used for sorting) */
|
/** Allows to cache one specific column (used for sorting) */
|
||||||
QMap<QgsFeatureId, QVariant> mFieldCache;
|
QHash<QgsFeatureId, QVariant> mFieldCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,6 +23,8 @@
|
|||||||
#include <qgsvectordataprovider.h>
|
#include <qgsvectordataprovider.h>
|
||||||
#include <qgsapplication.h>
|
#include <qgsapplication.h>
|
||||||
#include <qgsvectorlayereditbuffer.h>
|
#include <qgsvectorlayereditbuffer.h>
|
||||||
|
#include <qgscacheindexfeatureid.h>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
/** @ingroup UnitTests
|
/** @ingroup UnitTests
|
||||||
* This is a unit test for the vector layer cache
|
* This is a unit test for the vector layer cache
|
||||||
@ -36,17 +38,19 @@ class TestVectorLayerCache: public QObject
|
|||||||
private slots:
|
private slots:
|
||||||
void initTestCase(); // will be called before the first testfunction is executed.
|
void initTestCase(); // will be called before the first testfunction is executed.
|
||||||
void cleanupTestCase(); // will be called after the last testfunction was executed.
|
void cleanupTestCase(); // will be called after the last testfunction was executed.
|
||||||
void init() {}; // will be called before each testfunction is executed.
|
void init(); // will be called before each testfunction is executed.
|
||||||
void cleanup() {}; // will be called after every testfunction.
|
void cleanup(); // will be called after every testfunction.
|
||||||
|
|
||||||
void testCacheOverflow(); // Test cache will work if too many features to cache them all are present
|
void testCacheOverflow(); // Test cache will work if too many features to cache them all are present
|
||||||
void testCacheAttrActions(); // Test attribute add/ attribute delete
|
void testCacheAttrActions(); // Test attribute add/ attribute delete
|
||||||
void testFeatureActions(); // Test adding/removing features works
|
void testFeatureActions(); // Test adding/removing features works
|
||||||
|
void testSubsetRequest();
|
||||||
|
|
||||||
void onCommittedFeaturesAdded( QString, QgsFeatureList );
|
void onCommittedFeaturesAdded( QString, QgsFeatureList );
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QgsVectorLayerCache* mVectorLayerCache;
|
QgsVectorLayerCache* mVectorLayerCache;
|
||||||
|
QgsCacheIndexFeatureId* mFeatureIdIndex;
|
||||||
QgsVectorLayer* mPointsLayer;
|
QgsVectorLayer* mPointsLayer;
|
||||||
QgsFeatureList mAddedFeatures;
|
QgsFeatureList mAddedFeatures;
|
||||||
QMap<QString, QString> mTmpFiles;
|
QMap<QString, QString> mTmpFiles;
|
||||||
@ -88,8 +92,19 @@ void TestVectorLayerCache::initTestCase()
|
|||||||
QFileInfo myPointFileInfo( myPointsFileName );
|
QFileInfo myPointFileInfo( myPointsFileName );
|
||||||
mPointsLayer = new QgsVectorLayer( myPointFileInfo.filePath(),
|
mPointsLayer = new QgsVectorLayer( myPointFileInfo.filePath(),
|
||||||
myPointFileInfo.completeBaseName(), "ogr" );
|
myPointFileInfo.completeBaseName(), "ogr" );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestVectorLayerCache::init()
|
||||||
|
{
|
||||||
mVectorLayerCache = new QgsVectorLayerCache( mPointsLayer, 10 );
|
mVectorLayerCache = new QgsVectorLayerCache( mPointsLayer, 10 );
|
||||||
|
mFeatureIdIndex = new QgsCacheIndexFeatureId( mVectorLayerCache );
|
||||||
|
mVectorLayerCache->addCacheIndex( mFeatureIdIndex );
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestVectorLayerCache::cleanup()
|
||||||
|
{
|
||||||
|
delete mVectorLayerCache;
|
||||||
|
delete mFeatureIdIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
//runs after all tests
|
//runs after all tests
|
||||||
@ -107,8 +122,6 @@ void TestVectorLayerCache::cleanupTestCase()
|
|||||||
mAddedFeatures.clear();
|
mAddedFeatures.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
delete mVectorLayerCache;
|
|
||||||
mVectorLayerCache = NULL;
|
|
||||||
|
|
||||||
delete mPointsLayer;
|
delete mPointsLayer;
|
||||||
mPointsLayer = NULL;
|
mPointsLayer = NULL;
|
||||||
@ -190,9 +203,28 @@ void TestVectorLayerCache::testFeatureActions()
|
|||||||
// Delete feature...
|
// Delete feature...
|
||||||
mPointsLayer->startEditing();
|
mPointsLayer->startEditing();
|
||||||
QVERIFY( mPointsLayer->deleteFeature( fid ) );
|
QVERIFY( mPointsLayer->deleteFeature( fid ) );
|
||||||
mPointsLayer->commitChanges();
|
|
||||||
|
|
||||||
QVERIFY( false == mVectorLayerCache->featureAtId( fid, f ) );
|
QVERIFY( false == mVectorLayerCache->featureAtId( fid, f ) );
|
||||||
|
mPointsLayer->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TestVectorLayerCache::testSubsetRequest()
|
||||||
|
{
|
||||||
|
QgsFeature f;
|
||||||
|
|
||||||
|
QgsFields fields = mPointsLayer->pendingFields();
|
||||||
|
QStringList requiredFields;
|
||||||
|
requiredFields << "Class" << "Cabin Crew";
|
||||||
|
|
||||||
|
mVectorLayerCache->featureAtId( 16, f );
|
||||||
|
QVariant a = f.attribute( 3 );
|
||||||
|
|
||||||
|
QgsFeatureIterator itSubset = mVectorLayerCache->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( requiredFields, fields) );
|
||||||
|
while ( itSubset.nextFeature( f ) ) {}
|
||||||
|
itSubset.close();
|
||||||
|
|
||||||
|
mVectorLayerCache->featureAtId( 16, f );
|
||||||
|
QVERIFY( a == f.attribute( 3 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestVectorLayerCache::onCommittedFeaturesAdded( QString layerId, QgsFeatureList features )
|
void TestVectorLayerCache::onCommittedFeaturesAdded( QString layerId, QgsFeatureList features )
|
||||||
|
Loading…
x
Reference in New Issue
Block a user