diff --git a/python/core/core.sip b/python/core/core.sip index 59f102f3470..5d5921bcc94 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -165,6 +165,7 @@ %Include qgsvectorlayercache.sip %Include qgsvectorlayereditbuffer.sip %Include qgsvectorlayereditpassthrough.sip +%Include qgsvectorlayerfeaturecounter.sip %Include qgsvectorlayerimport.sip %Include qgsvectorlayerjoinbuffer.sip %Include qgsvectorlayerjoininfo.sip diff --git a/python/core/qgsvectorlayer.sip b/python/core/qgsvectorlayer.sip index 968339ef7e8..702a6739fa3 100644 --- a/python/core/qgsvectorlayer.sip +++ b/python/core/qgsvectorlayer.sip @@ -841,7 +841,7 @@ Return the provider type for this layer .. versionadded:: 2.10 %End - bool countSymbolFeatures( bool showProgress = true ); + bool countSymbolFeatures(); %Docstring Count features for symbols. Feature counts may be get by featureCount(). \param showProgress show progress dialog @@ -2000,6 +2000,12 @@ Signal emitted when setLayerTransparency() is called .. versionadded:: 3.0 %End + void symbolFeatureCountMapChanged(); +%Docstring + Emitted when the feature count for symbols on this layer has been recalculated. + +.. versionadded:: 3.0 +%End protected: virtual void setExtent( const QgsRectangle &rect ); diff --git a/python/core/qgsvectorlayerfeaturecounter.sip b/python/core/qgsvectorlayerfeaturecounter.sip new file mode 100644 index 00000000000..1fa0b554842 --- /dev/null +++ b/python/core/qgsvectorlayerfeaturecounter.sip @@ -0,0 +1,45 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsvectorlayerfeaturecounter.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + +class QgsVectorLayerFeatureCounter : QgsTask +{ +%Docstring + + Counts the features in a QgsVectorLayer in task. + You should most likely not use this directly and instead call + QgsVectorLayer.countSymbolFeatures() and connect to the signal + QgsVectorLayer.symbolFeatureCountMapChanged(). + +.. versionadded:: 3.0 +%End + +%TypeHeaderCode +#include "qgsvectorlayerfeaturecounter.h" +%End + public: + + QgsVectorLayerFeatureCounter( QgsVectorLayer *layer ); +%Docstring + Create a new feature counter for ``layer``. +%End + + virtual bool run(); + + signals: + void symbolsCounted( const QHash &symbolFeatureCountMap ); + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/qgsvectorlayerfeaturecounter.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index c49fbf78406..268ffed9a60 100755 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -263,6 +263,7 @@ SET(QGIS_CORE_SRCS qgsvectorfilewriter.cpp qgsvectorfilewritertask.cpp qgsvectorlayer.cpp + qgsvectorlayerfeaturecounter.cpp qgsvectorlayercache.cpp qgsvectorlayerdiagramprovider.cpp qgsvectorlayereditbuffer.cpp @@ -569,6 +570,7 @@ SET(QGIS_CORE_MOC_HDRS qgsvectorlayereditbuffer.h qgsvectorlayereditpassthrough.h qgsvectorlayer.h + qgsvectorlayerfeaturecounter.h qgsvectorlayerjoinbuffer.h qgsvectorlayertools.h qgsmapthemecollection.h diff --git a/src/core/qgsvectorlayer.cpp b/src/core/qgsvectorlayer.cpp index 2185f40175a..ffa161240ce 100644 --- a/src/core/qgsvectorlayer.cpp +++ b/src/core/qgsvectorlayer.cpp @@ -71,6 +71,7 @@ #include "qgsvectorlayerlabeling.h" #include "qgsvectorlayerrenderer.h" #include "qgsvectorlayerundocommand.h" +#include "qgsvectorlayerfeaturecounter.h" #include "qgspointv2.h" #include "qgsrenderer.h" #include "qgssymbollayer.h" @@ -83,6 +84,7 @@ #include "qgsfeedback.h" #include "qgsxmlutils.h" #include "qgsunittypes.h" +#include "qgstaskmanager.h" #include "diagram/qgsdiagram.h" @@ -126,6 +128,7 @@ typedef bool deleteStyleById_t( QString &errCause ); + QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath, const QString &baseName, const QString &providerKey, @@ -674,7 +677,7 @@ class QgsVectorLayerInterruptionCheckerDuringCountSymbolFeatures: public QgsInte QProgressDialog *mDialog = nullptr; }; -bool QgsVectorLayer::countSymbolFeatures( bool showProgress ) +bool QgsVectorLayer::countSymbolFeatures() { if ( mSymbolFeatureCounted ) return true; @@ -697,103 +700,16 @@ bool QgsVectorLayer::countSymbolFeatures( bool showProgress ) return false; } - QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems(); - QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin(); - - for ( ; symbolIt != symbolList.constEnd(); ++symbolIt ) + if ( !mFeatureCounter ) { - mSymbolFeatureCountMap.insert( symbolIt->first, 0 ); + mFeatureCounter.reset( new QgsVectorLayerFeatureCounter( this ) ); + connect( mFeatureCounter.get(), &QgsVectorLayerFeatureCounter::symbolsCounted, this, &QgsVectorLayer::onSymbolsCounted ); + connect( mFeatureCounter.get(), &QgsTask::taskCompleted, [ = ]() { mFeatureCounter.reset(); } ); + connect( mFeatureCounter.get(), &QgsTask::taskTerminated, [ = ]() { mFeatureCounter.reset(); } ); + + QgsApplication::taskManager()->addTask( mFeatureCounter.get() ); } - long nFeatures = featureCount(); - - QWidget *mainWindow = nullptr; - Q_FOREACH ( QWidget *widget, qApp->topLevelWidgets() ) - { - if ( widget->objectName() == QLatin1String( "QgisApp" ) ) - { - mainWindow = widget; - break; - } - } - - QProgressDialog progressDialog( tr( "Updating feature count for layer %1" ).arg( name() ), tr( "Abort" ), 0, nFeatures, mainWindow ); - progressDialog.setWindowTitle( tr( "QGIS" ) ); - progressDialog.setWindowModality( Qt::WindowModal ); - if ( showProgress ) - { - // Properly initialize to 0 as recommended in doc so that the evaluation - // of the total time properly works - progressDialog.setValue( 0 ); - } - int featuresCounted = 0; - - // Renderer (rule based) may depend on context scale, with scale is ignored if 0 - QgsRenderContext renderContext; - renderContext.setRendererScale( 0 ); - renderContext.expressionContext().appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( this ) ); - - QgsFeatureRequest request; - if ( !mRenderer->filterNeedsGeometry() ) - request.setFlags( QgsFeatureRequest::NoGeometry ); - request.setSubsetOfAttributes( mRenderer->usedAttributes( renderContext ), mFields ); - QgsFeatureIterator fit = getFeatures( request ); - QgsVectorLayerInterruptionCheckerDuringCountSymbolFeatures interruptionCheck( &progressDialog ); - if ( showProgress ) - { - fit.setInterruptionChecker( &interruptionCheck ); - } - - mRenderer->startRender( renderContext, fields() ); - - QgsFeature f; - QTime time; - time.start(); - while ( fit.nextFeature( f ) ) - { - renderContext.expressionContext().setFeature( f ); - QSet featureKeyList = mRenderer->legendKeysForFeature( f, renderContext ); - Q_FOREACH ( const QString &key, featureKeyList ) - { - mSymbolFeatureCountMap[key] += 1; - } - ++featuresCounted; - - if ( showProgress ) - { - // Refresh progress every 50 features or second - if ( ( featuresCounted % 50 == 0 ) || time.elapsed() > 1000 ) - { - time.restart(); - if ( featuresCounted > nFeatures ) //sometimes the feature count is not correct - { - progressDialog.setMaximum( 0 ); - } - progressDialog.setValue( featuresCounted ); - } - // So that we get a chance of hitting the Abort button -#ifdef Q_OS_LINUX - // For some reason on Windows hasPendingEvents() always return true, - // but one iteration is actually enough on Windows to get good interactivity - // whereas on Linux we must allow for far more iterations. - // For safety limit the number of iterations - int nIters = 0; - while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 ) -#endif - { - QCoreApplication::processEvents(); - } - if ( progressDialog.wasCanceled() ) - { - mSymbolFeatureCountMap.clear(); - mRenderer->stopRender( renderContext ); - return false; - } - } - } - mRenderer->stopRender( renderContext ); - progressDialog.setValue( nFeatures ); - mSymbolFeatureCounted = true; return true; } @@ -3983,6 +3899,13 @@ void QgsVectorLayer::onRelationsLoaded() mEditFormConfig.onRelationsLoaded(); } +void QgsVectorLayer::onSymbolsCounted( const QHash &symbolFeatureCountMap ) +{ + mSymbolFeatureCountMap = symbolFeatureCountMap; + mSymbolFeatureCounted = true; + emit symbolFeatureCountMapChanged(); +} + QList QgsVectorLayer::referencingRelations( int idx ) const { return QgsProject::instance()->relationManager()->referencingRelations( this, idx ); diff --git a/src/core/qgsvectorlayer.h b/src/core/qgsvectorlayer.h index a81cb795d57..afaf4ca7022 100644 --- a/src/core/qgsvectorlayer.h +++ b/src/core/qgsvectorlayer.h @@ -65,6 +65,7 @@ class QgsSymbol; class QgsVectorLayerJoinInfo; class QgsVectorLayerEditBuffer; class QgsVectorLayerJoinBuffer; +class QgsVectorLayerFeatureCounter; class QgsAbstractVectorLayerLabeling; class QgsPointV2; class QgsFeedback; @@ -810,7 +811,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \param showProgress show progress dialog * \returns true if calculated, false if failed or was canceled by user */ - bool countSymbolFeatures( bool showProgress = true ); + bool countSymbolFeatures(); /** * Set the string (typically sql) used to define a subset of the layer @@ -1846,11 +1847,18 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte */ void readOnlyChanged(); + /** + * Emitted when the feature count for symbols on this layer has been recalculated. + * + * \since QGIS 3.0 + */ + void symbolFeatureCountMapChanged(); private slots: void onJoinedFieldsChanged(); void onFeatureDeleted( QgsFeatureId fid ); void onRelationsLoaded(); + void onSymbolsCounted( const QHash &symbolFeatureCountMap ); protected: //! Set the extent @@ -1883,7 +1891,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte #endif private: // Private attributes - QgsConditionalLayerStyles *mConditionalStyles = nullptr; //! Pointer to data provider derived from the abastract base class QgsDataProvider @@ -2001,6 +2008,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte mutable QMutex mFeatureSourceConstructorMutex; + std::unique_ptr mFeatureCounter; + friend class QgsVectorLayerFeatureSource; }; diff --git a/src/core/qgsvectorlayerfeaturecounter.cpp b/src/core/qgsvectorlayerfeaturecounter.cpp new file mode 100644 index 00000000000..9077f978719 --- /dev/null +++ b/src/core/qgsvectorlayerfeaturecounter.cpp @@ -0,0 +1,69 @@ +#include "qgsvectorlayerfeaturecounter.h" + +QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer ) + : mSource( new QgsVectorLayerFeatureSource( layer ) ) + , mRenderer( layer->renderer()->clone() ) + , mExpressionContextScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) ) + , mFeatureCount( layer->featureCount() ) +{ +} + +bool QgsVectorLayerFeatureCounter::run() +{ + QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems(); + QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin(); + + for ( ; symbolIt != symbolList.constEnd(); ++symbolIt ) + { + mSymbolFeatureCountMap.insert( symbolIt->first, 0 ); + } + + int featuresCounted = 0; + + // Renderer (rule based) may depend on context scale, with scale is ignored if 0 + QgsRenderContext renderContext; + renderContext.setRendererScale( 0 ); + renderContext.expressionContext().appendScopes( mExpressionContextScopes ); + + QgsFeatureRequest request; + if ( !mRenderer->filterNeedsGeometry() ) + request.setFlags( QgsFeatureRequest::NoGeometry ); + request.setSubsetOfAttributes( mRenderer->usedAttributes( renderContext ), mSource->fields() ); + QgsFeatureIterator fit = mSource->getFeatures( request ); + + // TODO: replace QgsInterruptionChecker with QgsFeedback + // fit.setInterruptionChecker( mFeedback ); + + mRenderer->startRender( renderContext, mSource->fields() ); + + double progress = 0; + QgsFeature f; + while ( fit.nextFeature( f ) ) + { + renderContext.expressionContext().setFeature( f ); + QSet featureKeyList = mRenderer->legendKeysForFeature( f, renderContext ); + Q_FOREACH ( const QString &key, featureKeyList ) + { + mSymbolFeatureCountMap[key] += 1; + } + ++featuresCounted; + + double p = ( featuresCounted / mFeatureCount ) * 100; + if ( p - progress > 1 ) + { + progress = p; + setProgress( progress ); + } + + if ( isCanceled() ) + { + mRenderer->stopRender( renderContext ); + return false; + } + } + mRenderer->stopRender( renderContext ); + setProgress( 100 ); + + emit symbolsCounted( mSymbolFeatureCountMap ); + return true; +} diff --git a/src/core/qgsvectorlayerfeaturecounter.h b/src/core/qgsvectorlayerfeaturecounter.h new file mode 100644 index 00000000000..2265c7365da --- /dev/null +++ b/src/core/qgsvectorlayerfeaturecounter.h @@ -0,0 +1,43 @@ +#ifndef QGSVECTORLAYERFEATURECOUNTER_H +#define QGSVECTORLAYERFEATURECOUNTER_H + +#include "qgsvectorlayer.h" +#include "qgsvectorlayerfeatureiterator.h" +#include "qgsrenderer.h" +#include "qgstaskmanager.h" + +/** \ingroup core + * + * Counts the features in a QgsVectorLayer in task. + * You should most likely not use this directly and instead call + * QgsVectorLayer::countSymbolFeatures() and connect to the signal + * QgsVectorLayer::symbolFeatureCountMapChanged(). + * + * \since QGIS 3.0 + */ +class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask +{ + Q_OBJECT + + public: + + /** + * Create a new feature counter for \a layer. + */ + QgsVectorLayerFeatureCounter( QgsVectorLayer *layer ); + + virtual bool run() override; + + signals: + void symbolsCounted( const QHash &symbolFeatureCountMap ); + + private: + std::unique_ptr mSource; + std::unique_ptr mRenderer; + QList mExpressionContextScopes; + QHash mSymbolFeatureCountMap; + int mFeatureCount; + +}; + +#endif // QGSVECTORLAYERFEATURECOUNTER_H