Merge pull request #7805 from m-kuhn/vectordataproviderfeaturepool

Refactor QgsFeaturePool
This commit is contained in:
Matthias Kuhn 2018-09-10 07:53:55 +02:00 committed by GitHub
commit 273e99877b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 636 additions and 134 deletions

View File

@ -0,0 +1,69 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsreadwritelocker.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
class QgsReadWriteLocker
{
%Docstring
The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteLocks.
Locking and unlocking a QReadWriteLocks in complex functions and statements or in exception handling code
is error-prone and difficult to debug.
QgsReadWriteLocker can be used in such situations to ensure that the state of the lock is always well-defined.
QgsReadWriteLocker should be created within a function where a QReadWriteLock needs to be locked.
The lock may be locked when QgsReadWriteLocker is created or when changeMode is called.
You can unlock and relock the lock with unlock() and changeMode().
If locked, the lock will be unlocked when the QgsReadWriteLocker is destroyed.
.. versionadded:: 3.4
%End
%TypeHeaderCode
#include "qgsreadwritelocker.h"
%End
public:
enum Mode
{
Read,
Write,
Unlocked
};
QgsReadWriteLocker( QReadWriteLock &lock, Mode mode );
%Docstring
Create a new QgsReadWriteLocker for ``lock`` and initialize in ``mode``.
%End
void changeMode( Mode mode );
%Docstring
Change the mode of the lock to ``mode``.
The lock will be unlocked and relocked as required.
%End
void unlock();
%Docstring
Unlocks the lock.
Equivalent to doing ``changeMode( QgsReadWriteLocker.Unlock );``
%End
~QgsReadWriteLocker();
};
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsreadwritelocker.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/

View File

@ -107,6 +107,7 @@
%Include auto_generated/qgspythonrunner.sip
%Include auto_generated/qgsrange.sip
%Include auto_generated/qgsreadwritecontext.sip
%Include auto_generated/qgsreadwritelocker.sip
%Include auto_generated/qgsrenderchecker.sip
%Include auto_generated/qgsrendercontext.sip
%Include auto_generated/qgsrulebasedlabeling.sip

View File

@ -132,6 +132,7 @@ SET(QGIS_ANALYSIS_SRCS
network/qgsgraphanalyzer.cpp
vector/geometry_checker/qgsfeaturepool.cpp
vector/geometry_checker/qgsvectordataproviderfeaturepool.cpp
vector/geometry_checker/qgsgeometrychecker.cpp
vector/geometry_checker/qgsgeometryanglecheck.cpp
vector/geometry_checker/qgsgeometryareacheck.cpp
@ -232,6 +233,7 @@ SET(QGIS_ANALYSIS_HDRS
vector/qgszonalstatistics.h
vector/geometry_checker/qgsgeometrycheckerutils.h
vector/geometry_checker/qgsfeaturepool.h
vector/geometry_checker/qgsvectordataproviderfeaturepool.h
interpolation/qgsinterpolator.h
interpolation/qgsgridfilewriter.h

View File

@ -20,44 +20,26 @@
#include "qgsgeometry.h"
#include "qgsvectorlayer.h"
#include "qgsvectordataprovider.h"
#include "qgsvectorlayerutils.h"
#include "qgsreadwritelocker.h"
#include <QMutexLocker>
QgsFeaturePool::QgsFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform, bool selectedOnly )
QgsFeaturePool::QgsFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform )
: mFeatureCache( CACHE_SIZE )
, mLayer( layer )
, mLayerToMapUnits( layerToMapUnits )
, mLayerToMapTransform( layerToMapTransform )
, mSelectedOnly( selectedOnly )
, mLayerId( layer->id() )
, mGeometryType( layer->geometryType() )
{
// Build spatial index
QgsFeature feature;
QgsFeatureRequest req;
req.setSubsetOfAttributes( QgsAttributeList() );
if ( selectedOnly )
{
mFeatureIds = layer->selectedFeatureIds();
req.setFilterFids( mFeatureIds );
}
QgsFeatureIterator it = layer->getFeatures( req );
while ( it.nextFeature( feature ) )
{
if ( feature.geometry() )
{
mIndex.insertFeature( feature );
mFeatureIds.insert( feature.id() );
}
else
{
mFeatureIds.remove( feature.id() );
}
}
}
bool QgsFeaturePool::get( QgsFeatureId id, QgsFeature &feature )
{
QMutexLocker lock( &mLayerMutex );
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Read );
QgsFeature *cachedFeature = mFeatureCache.object( id );
if ( cachedFeature )
{
@ -66,78 +48,82 @@ bool QgsFeaturePool::get( QgsFeatureId id, QgsFeature &feature )
}
else
{
std::unique_ptr<QgsVectorLayerFeatureSource> source = QgsVectorLayerUtils::getFeatureSource( mLayer );
// Feature not in cache, retrieve from layer
// TODO: avoid always querying all attributes (attribute values are needed when merging by attribute)
if ( !mLayer->getFeatures( QgsFeatureRequest( id ) ).nextFeature( feature ) )
if ( !source->getFeatures( QgsFeatureRequest( id ) ).nextFeature( feature ) )
{
return false;
}
locker.changeMode( QgsReadWriteLocker::Write );
mFeatureCache.insert( id, new QgsFeature( feature ) );
mIndex.insertFeature( feature );
}
return true;
}
void QgsFeaturePool::addFeature( QgsFeature &feature )
QgsFeatureIds QgsFeaturePool::getFeatureIds() const
{
QgsFeatureList features;
features.append( feature );
mLayerMutex.lock();
mLayer->dataProvider()->addFeatures( features );
feature.setId( features.front().id() );
if ( mSelectedOnly )
{
QgsFeatureIds selectedFeatureIds = mLayer->selectedFeatureIds();
selectedFeatureIds.insert( feature.id() );
mLayer->selectByIds( selectedFeatureIds );
}
mLayerMutex.unlock();
mIndexMutex.lock();
mIndex.insertFeature( feature );
mIndexMutex.unlock();
}
void QgsFeaturePool::updateFeature( QgsFeature &feature )
{
QgsFeature origFeature;
get( feature.id(), origFeature );
QgsGeometryMap geometryMap;
geometryMap.insert( feature.id(), feature.geometry() );
QgsChangedAttributesMap changedAttributesMap;
QgsAttributeMap attribMap;
for ( int i = 0, n = feature.attributes().size(); i < n; ++i )
{
attribMap.insert( i, feature.attributes().at( i ) );
}
changedAttributesMap.insert( feature.id(), attribMap );
mLayerMutex.lock();
mFeatureCache.remove( feature.id() ); // Remove to force reload on next get()
mLayer->dataProvider()->changeGeometryValues( geometryMap );
mLayer->dataProvider()->changeAttributeValues( changedAttributesMap );
mLayerMutex.unlock();
mIndexMutex.lock();
mIndex.deleteFeature( origFeature );
mIndex.insertFeature( feature );
mIndexMutex.unlock();
}
void QgsFeaturePool::deleteFeature( QgsFeatureId fid )
{
QgsFeature origFeature;
if ( get( fid, origFeature ) )
{
mIndexMutex.lock();
mIndex.deleteFeature( origFeature );
mIndexMutex.unlock();
}
mLayerMutex.lock();
mFeatureCache.remove( origFeature.id() );
mLayer->dataProvider()->deleteFeatures( QgsFeatureIds() << fid );
mLayerMutex.unlock();
return mFeatureIds;
}
QgsFeatureIds QgsFeaturePool::getIntersects( const QgsRectangle &rect ) const
{
QMutexLocker lock( &mIndexMutex );
return QgsFeatureIds::fromList( mIndex.intersects( rect ) );
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Read );
QgsFeatureIds ids = QgsFeatureIds::fromList( mIndex.intersects( rect ) );
return ids;
}
QgsVectorLayer *QgsFeaturePool::layer() const
{
Q_ASSERT( QThread::currentThread() == qApp->thread() );
return mLayer.data();
}
void QgsFeaturePool::insertFeature( const QgsFeature &feature )
{
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Write );
mFeatureCache.insert( feature.id(), new QgsFeature( feature ) );
mIndex.insertFeature( feature );
}
void QgsFeaturePool::refreshCache( const QgsFeature &feature )
{
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Write );
mFeatureCache.remove( feature.id() );
mIndex.deleteFeature( feature );
locker.unlock();
QgsFeature tempFeature;
get( feature.id(), tempFeature );
}
void QgsFeaturePool::removeFeature( const QgsFeatureId featureId )
{
QgsFeature origFeature;
QgsReadWriteLocker locker( mCacheLock, QgsReadWriteLocker::Unlocked );
if ( get( featureId, origFeature ) )
{
locker.changeMode( QgsReadWriteLocker::Write );
mIndex.deleteFeature( origFeature );
}
locker.changeMode( QgsReadWriteLocker::Write );
mFeatureCache.remove( origFeature.id() );
}
void QgsFeaturePool::setFeatureIds( const QgsFeatureIds &ids )
{
mFeatureIds = ids;
}
QgsWkbTypes::GeometryType QgsFeaturePool::geometryType() const
{
return mGeometryType;
}
QString QgsFeaturePool::layerId() const
{
return mLayerId;
}

View File

@ -21,42 +21,124 @@
#include <QCache>
#include <QMutex>
#include <QPointer>
#include "qgis_analysis.h"
#include "qgsfeature.h"
#include "qgsspatialindex.h"
#include "qgsfeaturesink.h"
class QgsVectorLayer;
class ANALYSIS_EXPORT QgsFeaturePool
/**
* \ingroup analysis
* A feature pool is based on a vector layer and caches features.
*/
class ANALYSIS_EXPORT QgsFeaturePool : public QgsFeatureSink
{
public:
QgsFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform, bool selectedOnly = false );
QgsFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform );
virtual ~QgsFeaturePool() = default;
/**
* Retrieve the feature with the specified \a id into \a feature.
* It will be retrieved from the cache or from the underlying layer if unavailable.
* If the feature is neither available from the cache nor from the layer it will return false.
*/
bool get( QgsFeatureId id, QgsFeature &feature );
void addFeature( QgsFeature &feature );
void updateFeature( QgsFeature &feature );
void deleteFeature( QgsFeatureId fid );
/**
* Updates a feature in this pool.
* Implementations will update the feature on the layer or on the data provider.
*/
virtual void updateFeature( QgsFeature &feature ) = 0;
/**
* Removes a feature from this pool.
* Implementations will remove the feature from the layer or from the data provider.
*/
virtual void deleteFeature( QgsFeatureId fid ) = 0;
/**
* Returns the complete set of feature ids in this pool.
* Note that this concerns the features governed by this pool, which are not necessarily all cached.
*/
QgsFeatureIds getFeatureIds() const;
/**
* Get all feature ids in the bounding box \a rect. It will use a spatial index to
* determine the ids.
*/
QgsFeatureIds getIntersects( const QgsRectangle &rect ) const;
QgsVectorLayer *getLayer() const { return mLayer; }
const QgsFeatureIds &getFeatureIds() const { return mFeatureIds; }
/**
* The factor of layer units to map units.
* TODO: should this be removed and determined on runtime by checks that need it?
*/
double getLayerToMapUnits() const { return mLayerToMapUnits; }
/**
* A coordinate transform from layer to map CRS.
* TODO: should this be removed and determined on runtime by checks that need it?
*/
const QgsCoordinateTransform &getLayerToMapTransform() const { return mLayerToMapTransform; }
void clearLayer() { mLayer = nullptr; }
/**
* Get a pointer to the underlying layer.
* May return a ``nullptr`` if the layer has been deleted.
* This must only be called from the main thread.
*/
QgsVectorLayer *layer() const;
/**
* The layer id of the layer.
*/
QString layerId() const;
/**
* The geometry type of this layer.
*/
QgsWkbTypes::GeometryType geometryType() const;
protected:
/**
* Inserts a feature into the cache and the spatial index.
* To be used by implementations of ``addFeature``.
*/
void insertFeature( const QgsFeature &feature );
/**
* Changes a feature in the cache and the spatial index.
* To be used by implementations of ``updateFeature``.
*/
void refreshCache( const QgsFeature &feature );
/**
* Removes a feature from the cache and the spatial index.
* To be used by implementations of ``deleteFeature``.
*/
void removeFeature( const QgsFeatureId featureId );
/**
* Set all the feature ids governed by this feature pool.
* Should be called by subclasses constructor and whenever
* they insert a new feature.
*/
void setFeatureIds( const QgsFeatureIds &ids );
private:
static const int CACHE_SIZE = 1000;
QCache<QgsFeatureId, QgsFeature> mFeatureCache;
QgsVectorLayer *mLayer = nullptr;
QPointer<QgsVectorLayer> mLayer;
mutable QReadWriteLock mCacheLock;
QgsFeatureIds mFeatureIds;
QMutex mLayerMutex;
mutable QMutex mIndexMutex;
QgsSpatialIndex mIndex;
double mLayerToMapUnits = 1.0;
QgsCoordinateTransform mLayerToMapTransform;
bool mSelectedOnly = false;
QString mLayerId;
QgsWkbTypes::GeometryType mGeometryType;
};
#endif // QGS_FEATUREPOOL_H

View File

@ -149,7 +149,7 @@ QMap<QString, QgsFeatureIds> QgsGeometryCheck::allLayerFeatureIds() const
QMap<QString, QgsFeatureIds> featureIds;
for ( QgsFeaturePool *pool : mContext->featurePools )
{
featureIds.insert( pool->getLayer()->id(), pool->getFeatureIds() );
featureIds.insert( pool->layerId(), pool->getFeatureIds() );
}
return featureIds;
}

View File

@ -32,11 +32,11 @@ QgsGeometryChecker::QgsGeometryChecker( const QList<QgsGeometryCheck *> &checks,
{
for ( auto it = mContext->featurePools.constBegin(); it != mContext->featurePools.constEnd(); ++it )
{
if ( it.value()->getLayer() )
if ( it.value()->layer() )
{
it.value()->getLayer()->setReadOnly( true );
it.value()->layer()->setReadOnly( true );
// Enter update mode to defer ogr dataset repacking until the checker has finished
it.value()->getLayer()->dataProvider()->enterUpdateMode();
it.value()->layer()->dataProvider()->enterUpdateMode();
}
}
}
@ -47,10 +47,10 @@ QgsGeometryChecker::~QgsGeometryChecker()
qDeleteAll( mChecks );
for ( auto it = mContext->featurePools.constBegin(); it != mContext->featurePools.constEnd(); ++it )
{
if ( it.value()->getLayer() )
if ( it.value()->layer() )
{
it.value()->getLayer()->dataProvider()->leaveUpdateMode();
it.value()->getLayer()->setReadOnly( false );
it.value()->layer()->dataProvider()->leaveUpdateMode();
it.value()->layer()->setReadOnly( false );
}
delete it.value();
}
@ -68,7 +68,7 @@ QFuture<void> QgsGeometryChecker::execute( int *totalSteps )
{
if ( check->checkType() <= QgsGeometryCheck::FeatureCheck )
{
*totalSteps += check->isCompatible( it.value()->getLayer()->geometryType() ) ? it.value()->getFeatureIds().size() : 0;
*totalSteps += check->isCompatible( it.value()->layer()->geometryType() ) ? it.value()->getFeatureIds().size() : 0;
}
else
{
@ -145,7 +145,7 @@ bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, boo
{
const QMap<QgsFeatureId, QList<QgsGeometryCheck::Change>> &layerChanges = it.value();
QgsFeaturePool *featurePool = mContext->featurePools[it.key()];
QgsCoordinateTransform t( featurePool->getLayer()->crs(), mContext->mapCrs, QgsProject::instance() );
QgsCoordinateTransform t( featurePool->layer()->crs(), mContext->mapCrs, QgsProject::instance() );
for ( auto layerChangeIt = layerChanges.constBegin(); layerChangeIt != layerChanges.constEnd(); ++layerChangeIt )
{
bool removed = false;
@ -184,7 +184,7 @@ bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, boo
for ( const QString &layerId : mContext->featurePools.keys() )
{
QgsFeaturePool *featurePool = mContext->featurePools[layerId];
QgsCoordinateTransform t( mContext->mapCrs, featurePool->getLayer()->crs(), QgsProject::instance() );
QgsCoordinateTransform t( mContext->mapCrs, featurePool->layer()->crs(), QgsProject::instance() );
recheckAreaFeatures[layerId] = featurePool->getIntersects( t.transform( recheckArea ) );
}
@ -269,7 +269,7 @@ bool QgsGeometryChecker::fixError( QgsGeometryCheckError *error, int method, boo
{
for ( const QString &layerId : changes.keys() )
{
mContext->featurePools[layerId]->getLayer()->triggerRepaint();
mContext->featurePools[layerId]->layer()->triggerRepaint();
}
}

View File

@ -43,7 +43,7 @@ namespace QgsGeometryCheckerUtils
{
delete mGeometry;
}
const QgsVectorLayer &LayerFeature::layer() const { return *mFeaturePool->getLayer(); }
const QgsVectorLayer &LayerFeature::layer() const { return *mFeaturePool->layer(); }
double LayerFeature::layerToMapUnits() const { return mFeaturePool->getLayerToMapUnits(); }
const QgsCoordinateTransform &LayerFeature::layerToMapTransform() const { return mFeaturePool->getLayerToMapTransform(); }
@ -114,7 +114,7 @@ namespace QgsGeometryCheckerUtils
{
break;
}
if ( mParent->mGeometryTypes.contains( mParent->mFeaturePools[*mLayerIt]->getLayer()->geometryType() ) )
if ( mParent->mGeometryTypes.contains( mParent->mFeaturePools[*mLayerIt]->geometryType() ) )
{
mFeatureIt = mParent->mFeatureIds[*mLayerIt].constBegin();
return true;
@ -178,7 +178,7 @@ namespace QgsGeometryCheckerUtils
for ( const QString &layerId : layerIds )
{
const QgsFeaturePool *featurePool = featurePools[layerId];
if ( geometryTypes.contains( featurePool->getLayer()->geometryType() ) )
if ( geometryTypes.contains( featurePool->layer()->geometryType() ) )
{
mFeatureIds.insert( layerId, featurePool->getIntersects( featurePool->getLayerToMapTransform().transform( extent, QgsCoordinateTransform::ReverseTransform ) ) );
}

View File

@ -25,7 +25,7 @@ QString QgsGeometryDuplicateCheckError::duplicatesString( const QMap<QString, Qg
QStringList str;
for ( auto it = duplicates.constBegin(); it != duplicates.constEnd(); ++it )
{
str.append( featurePools[it.key()]->getLayer()->name() + ":" );
str.append( featurePools[it.key()]->layer()->name() + ":" );
QStringList ids;
for ( QgsFeatureId id : it.value() )
{

View File

@ -0,0 +1,183 @@
/***************************************************************************
qgsvectordataproviderfeaturepool.h
--------------------------------------
Date : 3.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsvectordataproviderfeaturepool.h"
#include "qgsfeaturerequest.h"
template <typename Func>
void runOnMainThread( const Func &func )
{
#if QT_VERSION >= QT_VERSION_CHECK( 5, 10, 0 )
// Make sure we only deal with the vector layer on the main thread where it lives.
// Anything else risks a crash.
if ( QThread::currentThread() == qApp->thread() )
func();
else
QMetaObject::invokeMethod( qApp, func, Qt::BlockingQueuedConnection );
#else
func();
#endif
}
QgsVectorDataProviderFeaturePool::QgsVectorDataProviderFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform, bool selectedOnly )
: QgsFeaturePool( layer, layerToMapUnits, layerToMapTransform )
, mSelectedOnly( selectedOnly )
{
// Build spatial index
QgsFeature feature;
QgsFeatureRequest req;
QgsFeatureIds featureIds;
if ( selectedOnly )
{
featureIds = layer->selectedFeatureIds();
req.setFilterFids( featureIds );
}
QgsFeatureIterator it = layer->getFeatures( req );
while ( it.nextFeature( feature ) )
{
if ( feature.geometry() )
{
insertFeature( feature );
featureIds.insert( feature.id() );
}
else
{
featureIds.remove( feature.id() );
}
}
setFeatureIds( featureIds );
}
bool QgsVectorDataProviderFeaturePool::addFeature( QgsFeature &feature, Flags flags )
{
Q_UNUSED( flags );
QgsFeatureList features;
features.append( feature );
bool res = false;
auto addFeatureSynchronized = [ this, &features, &res ]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
res = lyr->dataProvider()->addFeatures( features );
};
runOnMainThread( addFeatureSynchronized );
if ( !res )
return false;
feature.setId( features.front().id() );
if ( mSelectedOnly )
{
runOnMainThread( [ this, feature ]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
{
QgsFeatureIds selectedFeatureIds = lyr->selectedFeatureIds();
selectedFeatureIds.insert( feature.id() );
lyr->selectByIds( selectedFeatureIds );
}
} );
}
insertFeature( feature );
return res;
}
bool QgsVectorDataProviderFeaturePool::addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags )
{
Q_UNUSED( flags );
bool res = false;
auto addFeatureSynchronized = [ this, &features, &res ]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
res = lyr->dataProvider()->addFeatures( features );
};
runOnMainThread( addFeatureSynchronized );
if ( !res )
return false;
if ( mSelectedOnly )
{
runOnMainThread( [ this, features ]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
{
QgsFeatureIds selectedFeatureIds = lyr->selectedFeatureIds();
for ( const QgsFeature &feature : qgis::as_const( features ) )
selectedFeatureIds.insert( feature.id() );
lyr->selectByIds( selectedFeatureIds );
}
} );
}
for ( const QgsFeature &feature : qgis::as_const( features ) )
insertFeature( feature );
return res;
}
void QgsVectorDataProviderFeaturePool::updateFeature( QgsFeature &feature )
{
QgsFeature origFeature;
get( feature.id(), origFeature );
QgsGeometryMap geometryMap;
geometryMap.insert( feature.id(), feature.geometry() );
QgsChangedAttributesMap changedAttributesMap;
QgsAttributeMap attribMap;
for ( int i = 0, n = feature.attributes().size(); i < n; ++i )
{
attribMap.insert( i, feature.attributes().at( i ) );
}
changedAttributesMap.insert( feature.id(), attribMap );
runOnMainThread( [this, geometryMap, changedAttributesMap]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
{
lyr->dataProvider()->changeGeometryValues( geometryMap );
lyr->dataProvider()->changeAttributeValues( changedAttributesMap );
}
} );
refreshCache( feature );
}
void QgsVectorDataProviderFeaturePool::deleteFeature( QgsFeatureId fid )
{
removeFeature( fid );
runOnMainThread( [this, fid]()
{
QgsVectorLayer *lyr = layer();
if ( lyr )
{
lyr->dataProvider()->deleteFeatures( QgsFeatureIds() << fid );
}
} );
}

View File

@ -0,0 +1,44 @@
/***************************************************************************
qgsvectordataproviderfeaturepool.h
--------------------------------------
Date : 3.9.2018
Copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSVECTORDATAPROVIDERFEATUREPOOL_H
#define QGSVECTORDATAPROVIDERFEATUREPOOL_H
#include "qgsfeaturepool.h"
#include "qgsvectorlayer.h"
#define SIP_NO_FILE
/**
* \ingroup analysis
* A feature pool based on a vector data provider.
*
* \since QGIS 3.4
*/
class ANALYSIS_EXPORT QgsVectorDataProviderFeaturePool : public QgsFeaturePool
{
public:
QgsVectorDataProviderFeaturePool( QgsVectorLayer *layer, double layerToMapUnits, const QgsCoordinateTransform &layerToMapTransform, bool selectedOnly = false );
bool addFeature( QgsFeature &feature, QgsFeatureSink::Flags flags = nullptr ) override;
bool addFeatures( QgsFeatureList &features, QgsFeatureSink::Flags flags = nullptr ) override;
void updateFeature( QgsFeature &feature ) override;
void deleteFeature( QgsFeatureId fid ) override;
private:
bool mSelectedOnly = false;
};
#endif // QGSVECTORDATAPROVIDERFEATUREPOOL_H

View File

@ -281,6 +281,7 @@ SET(QGIS_CORE_SRCS
qgsproxyprogresstask.cpp
qgspythonrunner.cpp
qgsreadwritecontext.cpp
qgsreadwritelocker.cpp
qgsrelation.cpp
qgsrelationmanager.cpp
qgsrenderchecker.cpp
@ -921,6 +922,7 @@ SET(QGIS_CORE_HDRS
qgspythonrunner.h
qgsrange.h
qgsreadwritecontext.h
qgsreadwritelocker.h
qgsrenderchecker.h
qgsrendercontext.h
qgsrulebasedlabeling.h

View File

@ -0,0 +1,54 @@
/***************************************************************************
qgsreadwritelocker.cpp
-------------------------
begin : September 2018
copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#include "qgsreadwritelocker.h"
QgsReadWriteLocker::QgsReadWriteLocker( QReadWriteLock &lock, QgsReadWriteLocker::Mode mode )
: mLock( lock )
, mMode( mode )
{
if ( mode == Read )
mLock.lockForRead();
else if ( mode == Write )
mLock.lockForWrite();
}
void QgsReadWriteLocker::changeMode( QgsReadWriteLocker::Mode mode )
{
if ( mode == mMode )
return;
unlock();
if ( mMode == Read )
mLock.lockForRead();
else if ( mMode == Write )
mLock.lockForWrite();
}
void QgsReadWriteLocker::unlock()
{
if ( mMode != Unlocked )
mLock.unlock();
mMode = Unlocked;
}
QgsReadWriteLocker::~QgsReadWriteLocker()
{
unlock();
}

View File

@ -0,0 +1,78 @@
/***************************************************************************
qgsreadwritelocker.cpp
-------------------------
begin : September 2018
copyright : (C) 2018 by Matthias Kuhn
email : matthias@opengis.ch
***************************************************************************/
/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/
#ifndef QGSREADWRITELOCKER_H
#define QGSREADWRITELOCKER_H
#include "qgis_core.h"
#include <QReadWriteLock>
/**
* \ingroup core
* The QgsReadWriteLocker class is a convenience class that simplifies locking and unlocking QReadWriteLocks.
*
* Locking and unlocking a QReadWriteLocks in complex functions and statements or in exception handling code
* is error-prone and difficult to debug.
* QgsReadWriteLocker can be used in such situations to ensure that the state of the lock is always well-defined.
*
* QgsReadWriteLocker should be created within a function where a QReadWriteLock needs to be locked.
* The lock may be locked when QgsReadWriteLocker is created or when changeMode is called.
* You can unlock and relock the lock with unlock() and changeMode().
* If locked, the lock will be unlocked when the QgsReadWriteLocker is destroyed.
*
* \since QGIS 3.4
*/
class CORE_EXPORT QgsReadWriteLocker
{
public:
/**
* A QReadWriteLock can be in 3 different modes, read, write or unlocked.
*/
enum Mode
{
Read, //!< Lock for read
Write, //!< Lock for write
Unlocked //!< Unlocked
};
/**
* Create a new QgsReadWriteLocker for \a lock and initialize in \a mode.
*/
QgsReadWriteLocker( QReadWriteLock &lock, Mode mode );
/**
* Change the mode of the lock to \a mode.
* The lock will be unlocked and relocked as required.
*/
void changeMode( Mode mode );
/**
* Unlocks the lock.
* Equivalent to doing ``changeMode( QgsReadWriteLocker::Unlock );``
*/
void unlock();
~QgsReadWriteLocker();
private:
QReadWriteLock &mLock;
Mode mMode = Unlocked;
};
#endif // QGSREADWRITELOCKER_H

View File

@ -75,7 +75,7 @@ void QgsGeometryCheckerFixSummaryDialog::addError( QTableWidget *table, QgsGeome
int row = table->rowCount();
table->insertRow( row );
table->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->getContext()->featurePools[error->layerId()]->getLayer()->name() : "" ) );
table->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->getContext()->featurePools[error->layerId()]->layer()->name() : "" ) );
QTableWidgetItem *idItem = new QTableWidgetItem();
idItem->setData( Qt::EditRole, error->featureId() != FEATUREID_NULL ? QVariant( error->featureId() ) : QVariant() );
table->setItem( row, 1, idItem );

View File

@ -57,7 +57,7 @@ QgsGeometryCheckerResultTab::QgsGeometryCheckerResultTab( QgisInterface *iface,
for ( const QString &layerId : mChecker->getContext()->featurePools.keys() )
{
QgsVectorLayer *layer = mChecker->getContext()->featurePools[layerId]->getLayer();
QgsVectorLayer *layer = mChecker->getContext()->featurePools[layerId]->layer();
QTreeWidgetItem *item = new QTreeWidgetItem( ui.treeWidgetMergeAttribute, QStringList() << layer->name() << "" );
QComboBox *attribCombo = new QComboBox();
for ( int i = 0, n = layer->fields().count(); i < n; ++i )
@ -85,7 +85,7 @@ QgsGeometryCheckerResultTab::QgsGeometryCheckerResultTab( QgisInterface *iface,
bool allLayersEditable = true;
for ( const QgsFeaturePool *featurePool : mChecker->getContext()->featurePools.values() )
{
if ( ( featurePool->getLayer()->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeGeometries ) == 0 )
if ( ( featurePool->layer()->dataProvider()->capabilities() & QgsVectorDataProvider::ChangeGeometries ) == 0 )
{
allLayersEditable = false;
break;
@ -148,7 +148,7 @@ void QgsGeometryCheckerResultTab::addError( QgsGeometryCheckError *error )
ui.tableWidgetErrors->insertRow( row );
QTableWidgetItem *idItem = new QTableWidgetItem();
idItem->setData( Qt::EditRole, error->featureId() != FEATUREID_NULL ? QVariant( error->featureId() ) : QVariant() );
ui.tableWidgetErrors->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->getContext()->featurePools[error->layerId()]->getLayer()->name() : "" ) );
ui.tableWidgetErrors->setItem( row, 0, new QTableWidgetItem( !error->layerId().isEmpty() ? mChecker->getContext()->featurePools[error->layerId()]->layer()->name() : "" ) );
ui.tableWidgetErrors->setItem( row, 1, idItem );
ui.tableWidgetErrors->setItem( row, 2, new QTableWidgetItem( error->description() ) );
ui.tableWidgetErrors->setItem( row, 3, new QTableWidgetItem( posStr ) );
@ -221,7 +221,7 @@ void QgsGeometryCheckerResultTab::updateError( QgsGeometryCheckError *error, boo
void QgsGeometryCheckerResultTab::exportErrors()
{
QString initialdir;
QDir dir = QFileInfo( mChecker->getContext()->featurePools.first()->getLayer()->dataProvider()->dataSourceUri() ).dir();
QDir dir = QFileInfo( mChecker->getContext()->featurePools.first()->layer()->dataProvider()->dataSourceUri() ).dir();
if ( dir.exists() )
{
initialdir = dir.absolutePath();
@ -280,7 +280,7 @@ bool QgsGeometryCheckerResultTab::exportErrorsDo( const QString &file )
for ( int row = 0, nRows = ui.tableWidgetErrors->rowCount(); row < nRows; ++row )
{
QgsGeometryCheckError *error = ui.tableWidgetErrors->item( row, 0 )->data( Qt::UserRole ).value<QgsGeometryCheckError *>();
QgsVectorLayer *srcLayer = mChecker->getContext()->featurePools[error->layerId()]->getLayer();
QgsVectorLayer *srcLayer = mChecker->getContext()->featurePools[error->layerId()]->layer();
QgsFeature f( layer->fields() );
f.setAttribute( fieldLayer, srcLayer->name() );
f.setAttribute( fieldFeatureId, error->featureId() );
@ -454,7 +454,7 @@ void QgsGeometryCheckerResultTab::openAttributeTable()
{
mAttribTableDialogs[layerId]->close();
}
mAttribTableDialogs[layerId] = mIface->showAttributeTable( mChecker->getContext()->featurePools[layerId]->getLayer(), expr.join( QStringLiteral( " or " ) ) );
mAttribTableDialogs[layerId] = mIface->showAttributeTable( mChecker->getContext()->featurePools[layerId]->layer(), expr.join( QStringLiteral( " or " ) ) );
}
}
@ -528,7 +528,7 @@ void QgsGeometryCheckerResultTab::fixErrors( bool prompt )
}
for ( const QString &layerId : mChecker->getContext()->featurePools.keys() )
{
mChecker->getContext()->featurePools[layerId]->getLayer()->triggerRepaint();
mChecker->getContext()->featurePools[layerId]->layer()->triggerRepaint();
}
if ( mStatistics.itemCount() > 0 )
@ -618,7 +618,6 @@ void QgsGeometryCheckerResultTab::checkRemovedLayer( const QStringList &ids )
{
if ( ids.contains( layerId ) )
{
mChecker->getContext()->featurePools[layerId]->clearLayer();
if ( isEnabled() )
requiredLayersRemoved = true;
}

View File

@ -21,6 +21,7 @@
#include "qgsgeometrycheckfactory.h"
#include "qgsgeometrycheck.h"
#include "qgsfeaturepool.h"
#include "qgsvectordataproviderfeaturepool.h"
#include "qgsfeatureiterator.h"
#include "qgisinterface.h"
@ -236,7 +237,7 @@ void QgsGeometryCheckerSetupTab::selectOutputDirectory()
void QgsGeometryCheckerSetupTab::runChecks()
{
// Get selected layer
QList<QgsVectorLayer *> layers = getSelectedLayers();
const QList<QgsVectorLayer *> layers = getSelectedLayers();
if ( layers.isEmpty() )
return;
@ -416,7 +417,7 @@ void QgsGeometryCheckerSetupTab::runChecks()
{
double layerToMapUntis = mIface->mapCanvas()->mapSettings().layerToMapUnits( layer );
QgsCoordinateTransform layerToMapTransform( layer->crs(), QgsProject::instance()->crs(), QgsProject::instance() );
featurePools.insert( layer->id(), new QgsFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly ) );
featurePools.insert( layer->id(), new QgsVectorDataProviderFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly ) );
}
// LineLayerIntersection check is enabled, make sure there is also a feature pool for that layer
if ( ui.checkLineLayerIntersection->isChecked() && !featurePools.keys().contains( ui.comboLineLayerIntersection->currentData().toString() ) )
@ -425,7 +426,7 @@ void QgsGeometryCheckerSetupTab::runChecks()
Q_ASSERT( layer );
double layerToMapUntis = mIface->mapCanvas()->mapSettings().layerToMapUnits( layer );
QgsCoordinateTransform layerToMapTransform( layer->crs(), QgsProject::instance()->crs(), QgsProject::instance() );
featurePools.insert( layer->id(), new QgsFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly ) );
featurePools.insert( layer->id(), new QgsVectorDataProviderFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly ) );
}
QgsGeometryCheckerContext *context = new QgsGeometryCheckerContext( ui.spinBoxTolerance->value(), QgsProject::instance()->crs(), featurePools );

View File

@ -38,6 +38,7 @@
#include "qgsgeometryselfcontactcheck.h"
#include "qgsgeometryselfintersectioncheck.h"
#include "qgsgeometrysliverpolygoncheck.h"
#include "qgsvectordataproviderfeaturepool.h"
#include "qgsproject.h"
#include "qgsgeometrytypecheck.h"
@ -478,7 +479,7 @@ void TestQgsGeometryChecks::testFollowBoundariesCheck()
QList<QgsGeometryCheckError *> checkErrors;
QStringList messages;
QgsGeometryFollowBoundariesCheck( context, context->featurePools[layers["follow_ref.shp"]]->getLayer() ).collectErrors( checkErrors, messages );
QgsGeometryFollowBoundariesCheck( context, context->featurePools[layers["follow_ref.shp"]]->layer() ).collectErrors( checkErrors, messages );
listErrors( checkErrors, messages );
QCOMPARE( checkErrors.size(), 2 );
@ -640,9 +641,9 @@ void TestQgsGeometryChecks::testMultipartCheck()
QVERIFY( searchCheckErrors( checkErrors, layers["point_layer.shp"] ).isEmpty() );
// Easier to ensure that multipart features don't appear as errors than verifying each single-part multi-type feature
QVERIFY( QgsWkbTypes::isSingleType( context->featurePools[layers["point_layer.shp"]]->getLayer()->wkbType() ) );
QVERIFY( QgsWkbTypes::isMultiType( context->featurePools[layers["line_layer.shp"]]->getLayer()->wkbType() ) );
QVERIFY( QgsWkbTypes::isMultiType( context->featurePools[layers["polygon_layer.shp"]]->getLayer()->wkbType() ) );
QVERIFY( QgsWkbTypes::isSingleType( context->featurePools[layers["point_layer.shp"]]->layer()->wkbType() ) );
QVERIFY( QgsWkbTypes::isMultiType( context->featurePools[layers["line_layer.shp"]]->layer()->wkbType() ) );
QVERIFY( QgsWkbTypes::isMultiType( context->featurePools[layers["polygon_layer.shp"]]->layer()->wkbType() ) );
QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"] ).size() > 0 );
QVERIFY( searchCheckErrors( checkErrors, layers["polygon_layer.shp"] ).size() > 0 );
QVERIFY( searchCheckErrors( checkErrors, layers["line_layer.shp"], 0 ).isEmpty() );
@ -858,7 +859,7 @@ void TestQgsGeometryChecks::testSelfIntersectionCheck()
// Test fixes
QgsFeature f;
int nextId = context->featurePools[errs1[0]->layerId()]->getLayer()->featureCount();
int nextId = context->featurePools[errs1[0]->layerId()]->layer()->featureCount();
QVERIFY( fixCheckError( errs1[0],
QgsGeometrySelfIntersectionCheck::ToSingleObjects, QgsGeometryCheckError::StatusFixed,
{
@ -885,7 +886,7 @@ void TestQgsGeometryChecks::testSelfIntersectionCheck()
QCOMPARE( f.geometry().constGet()->vertexCount( 0 ), 4 );
QCOMPARE( f.geometry().constGet()->vertexCount( 1 ), 5 );
nextId = context->featurePools[errs3[0]->layerId()]->getLayer()->featureCount();
nextId = context->featurePools[errs3[0]->layerId()]->layer()->featureCount();
QVERIFY( fixCheckError( errs3[0],
QgsGeometrySelfIntersectionCheck::ToSingleObjects, QgsGeometryCheckError::StatusFixed,
{
@ -975,7 +976,7 @@ QgsFeaturePool *TestQgsGeometryChecks::createFeaturePool( QgsVectorLayer *layer,
{
double layerToMapUntis = layerToMapUnits( layer, mapCrs );
QgsCoordinateTransform layerToMapTransform = QgsCoordinateTransform( layer->crs(), mapCrs, QgsProject::instance() );
return new QgsFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly );
return new QgsVectorDataProviderFeaturePool( layer, layerToMapUntis, layerToMapTransform, selectedOnly );
}
QgsGeometryCheckerContext *TestQgsGeometryChecks::createTestContext( QTemporaryDir &tempDir, QMap<QString, QString> &layers, const QgsCoordinateReferenceSystem &mapCrs, double prec ) const
@ -1008,8 +1009,8 @@ void TestQgsGeometryChecks::cleanupTestContext( QgsGeometryCheckerContext *ctx )
{
for ( const QgsFeaturePool *pool : ctx->featurePools )
{
pool->getLayer()->dataProvider()->leaveUpdateMode();
delete pool->getLayer();
pool->layer()->dataProvider()->leaveUpdateMode();
delete pool->layer();
}
qDeleteAll( ctx->featurePools );
delete ctx;