Skip to content

Commit

Permalink
Send feature counting to background task
Browse files Browse the repository at this point in the history
  • Loading branch information
m-kuhn committed May 11, 2017
1 parent f264370 commit 964d9ac
Show file tree
Hide file tree
Showing 8 changed files with 195 additions and 97 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion python/core/qgsvectorlayer.sip
Expand Up @@ -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
Expand Down Expand Up @@ -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 );
Expand Down
45 changes: 45 additions & 0 deletions 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<QString, long> &symbolFeatureCountMap );

};

/************************************************************************
* This file has been generated automatically from *
* *
* src/core/qgsvectorlayerfeaturecounter.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
2 changes: 2 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -263,6 +263,7 @@ SET(QGIS_CORE_SRCS
qgsvectorfilewriter.cpp
qgsvectorfilewritertask.cpp
qgsvectorlayer.cpp
qgsvectorlayerfeaturecounter.cpp
qgsvectorlayercache.cpp
qgsvectorlayerdiagramprovider.cpp
qgsvectorlayereditbuffer.cpp
Expand Down Expand Up @@ -569,6 +570,7 @@ SET(QGIS_CORE_MOC_HDRS
qgsvectorlayereditbuffer.h
qgsvectorlayereditpassthrough.h
qgsvectorlayer.h
qgsvectorlayerfeaturecounter.h
qgsvectorlayerjoinbuffer.h
qgsvectorlayertools.h
qgsmapthemecollection.h
Expand Down
111 changes: 17 additions & 94 deletions src/core/qgsvectorlayer.cpp
Expand Up @@ -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"
Expand All @@ -83,6 +84,7 @@
#include "qgsfeedback.h"
#include "qgsxmlutils.h"
#include "qgsunittypes.h"
#include "qgstaskmanager.h"

#include "diagram/qgsdiagram.h"

Expand Down Expand Up @@ -126,6 +128,7 @@ typedef bool deleteStyleById_t(
QString &errCause
);


QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
const QString &baseName,
const QString &providerKey,
Expand Down Expand Up @@ -674,7 +677,7 @@ class QgsVectorLayerInterruptionCheckerDuringCountSymbolFeatures: public QgsInte
QProgressDialog *mDialog = nullptr;
};

bool QgsVectorLayer::countSymbolFeatures( bool showProgress )
bool QgsVectorLayer::countSymbolFeatures()
{
if ( mSymbolFeatureCounted )
return true;
Expand All @@ -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(); } );

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 );
QgsApplication::taskManager()->addTask( mFeatureCounter.get() );
}
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<QString> 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;
}

Expand Down Expand Up @@ -3983,6 +3899,13 @@ void QgsVectorLayer::onRelationsLoaded()
mEditFormConfig.onRelationsLoaded();
}

void QgsVectorLayer::onSymbolsCounted( const QHash<QString, long> &symbolFeatureCountMap )
{
mSymbolFeatureCountMap = symbolFeatureCountMap;
mSymbolFeatureCounted = true;
emit symbolFeatureCountMapChanged();
}

QList<QgsRelation> QgsVectorLayer::referencingRelations( int idx ) const
{
return QgsProject::instance()->relationManager()->referencingRelations( this, idx );
Expand Down
13 changes: 11 additions & 2 deletions src/core/qgsvectorlayer.h
Expand Up @@ -65,6 +65,7 @@ class QgsSymbol;
class QgsVectorLayerJoinInfo;
class QgsVectorLayerEditBuffer;
class QgsVectorLayerJoinBuffer;
class QgsVectorLayerFeatureCounter;
class QgsAbstractVectorLayerLabeling;
class QgsPointV2;
class QgsFeedback;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<QString, long> &symbolFeatureCountMap );

protected:
//! Set the extent
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2001,6 +2008,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte

mutable QMutex mFeatureSourceConstructorMutex;

std::unique_ptr<QgsVectorLayerFeatureCounter> mFeatureCounter;

friend class QgsVectorLayerFeatureSource;
};

Expand Down
69 changes: 69 additions & 0 deletions 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<QString> 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;
}

0 comments on commit 964d9ac

Please sign in to comment.