Skip to content

Commit 964d9ac

Browse files
committed
Send feature counting to background task
1 parent f264370 commit 964d9ac

8 files changed

+195
-97
lines changed

python/core/core.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,7 @@
165165
%Include qgsvectorlayercache.sip
166166
%Include qgsvectorlayereditbuffer.sip
167167
%Include qgsvectorlayereditpassthrough.sip
168+
%Include qgsvectorlayerfeaturecounter.sip
168169
%Include qgsvectorlayerimport.sip
169170
%Include qgsvectorlayerjoinbuffer.sip
170171
%Include qgsvectorlayerjoininfo.sip

python/core/qgsvectorlayer.sip

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -841,7 +841,7 @@ Return the provider type for this layer
841841
.. versionadded:: 2.10
842842
%End
843843

844-
bool countSymbolFeatures( bool showProgress = true );
844+
bool countSymbolFeatures();
845845
%Docstring
846846
Count features for symbols. Feature counts may be get by featureCount().
847847
\param showProgress show progress dialog
@@ -2000,6 +2000,12 @@ Signal emitted when setLayerTransparency() is called
20002000
.. versionadded:: 3.0
20012001
%End
20022002

2003+
void symbolFeatureCountMapChanged();
2004+
%Docstring
2005+
Emitted when the feature count for symbols on this layer has been recalculated.
2006+
2007+
.. versionadded:: 3.0
2008+
%End
20032009

20042010
protected:
20052011
virtual void setExtent( const QgsRectangle &rect );
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/************************************************************************
2+
* This file has been generated automatically from *
3+
* *
4+
* src/core/qgsvectorlayerfeaturecounter.h *
5+
* *
6+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
7+
************************************************************************/
8+
9+
10+
class QgsVectorLayerFeatureCounter : QgsTask
11+
{
12+
%Docstring
13+
14+
Counts the features in a QgsVectorLayer in task.
15+
You should most likely not use this directly and instead call
16+
QgsVectorLayer.countSymbolFeatures() and connect to the signal
17+
QgsVectorLayer.symbolFeatureCountMapChanged().
18+
19+
.. versionadded:: 3.0
20+
%End
21+
22+
%TypeHeaderCode
23+
#include "qgsvectorlayerfeaturecounter.h"
24+
%End
25+
public:
26+
27+
QgsVectorLayerFeatureCounter( QgsVectorLayer *layer );
28+
%Docstring
29+
Create a new feature counter for ``layer``.
30+
%End
31+
32+
virtual bool run();
33+
34+
signals:
35+
void symbolsCounted( const QHash<QString, long> &symbolFeatureCountMap );
36+
37+
};
38+
39+
/************************************************************************
40+
* This file has been generated automatically from *
41+
* *
42+
* src/core/qgsvectorlayerfeaturecounter.h *
43+
* *
44+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
45+
************************************************************************/

src/core/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,6 +263,7 @@ SET(QGIS_CORE_SRCS
263263
qgsvectorfilewriter.cpp
264264
qgsvectorfilewritertask.cpp
265265
qgsvectorlayer.cpp
266+
qgsvectorlayerfeaturecounter.cpp
266267
qgsvectorlayercache.cpp
267268
qgsvectorlayerdiagramprovider.cpp
268269
qgsvectorlayereditbuffer.cpp
@@ -569,6 +570,7 @@ SET(QGIS_CORE_MOC_HDRS
569570
qgsvectorlayereditbuffer.h
570571
qgsvectorlayereditpassthrough.h
571572
qgsvectorlayer.h
573+
qgsvectorlayerfeaturecounter.h
572574
qgsvectorlayerjoinbuffer.h
573575
qgsvectorlayertools.h
574576
qgsmapthemecollection.h

src/core/qgsvectorlayer.cpp

Lines changed: 17 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@
7171
#include "qgsvectorlayerlabeling.h"
7272
#include "qgsvectorlayerrenderer.h"
7373
#include "qgsvectorlayerundocommand.h"
74+
#include "qgsvectorlayerfeaturecounter.h"
7475
#include "qgspointv2.h"
7576
#include "qgsrenderer.h"
7677
#include "qgssymbollayer.h"
@@ -83,6 +84,7 @@
8384
#include "qgsfeedback.h"
8485
#include "qgsxmlutils.h"
8586
#include "qgsunittypes.h"
87+
#include "qgstaskmanager.h"
8688

8789
#include "diagram/qgsdiagram.h"
8890

@@ -126,6 +128,7 @@ typedef bool deleteStyleById_t(
126128
QString &errCause
127129
);
128130

131+
129132
QgsVectorLayer::QgsVectorLayer( const QString &vectorLayerPath,
130133
const QString &baseName,
131134
const QString &providerKey,
@@ -674,7 +677,7 @@ class QgsVectorLayerInterruptionCheckerDuringCountSymbolFeatures: public QgsInte
674677
QProgressDialog *mDialog = nullptr;
675678
};
676679

677-
bool QgsVectorLayer::countSymbolFeatures( bool showProgress )
680+
bool QgsVectorLayer::countSymbolFeatures()
678681
{
679682
if ( mSymbolFeatureCounted )
680683
return true;
@@ -697,103 +700,16 @@ bool QgsVectorLayer::countSymbolFeatures( bool showProgress )
697700
return false;
698701
}
699702

700-
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
701-
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
702-
703-
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
703+
if ( !mFeatureCounter )
704704
{
705-
mSymbolFeatureCountMap.insert( symbolIt->first, 0 );
706-
}
705+
mFeatureCounter.reset( new QgsVectorLayerFeatureCounter( this ) );
706+
connect( mFeatureCounter.get(), &QgsVectorLayerFeatureCounter::symbolsCounted, this, &QgsVectorLayer::onSymbolsCounted );
707+
connect( mFeatureCounter.get(), &QgsTask::taskCompleted, [ = ]() { mFeatureCounter.reset(); } );
708+
connect( mFeatureCounter.get(), &QgsTask::taskTerminated, [ = ]() { mFeatureCounter.reset(); } );
707709

708-
long nFeatures = featureCount();
709-
710-
QWidget *mainWindow = nullptr;
711-
Q_FOREACH ( QWidget *widget, qApp->topLevelWidgets() )
712-
{
713-
if ( widget->objectName() == QLatin1String( "QgisApp" ) )
714-
{
715-
mainWindow = widget;
716-
break;
717-
}
718-
}
719-
720-
QProgressDialog progressDialog( tr( "Updating feature count for layer %1" ).arg( name() ), tr( "Abort" ), 0, nFeatures, mainWindow );
721-
progressDialog.setWindowTitle( tr( "QGIS" ) );
722-
progressDialog.setWindowModality( Qt::WindowModal );
723-
if ( showProgress )
724-
{
725-
// Properly initialize to 0 as recommended in doc so that the evaluation
726-
// of the total time properly works
727-
progressDialog.setValue( 0 );
710+
QgsApplication::taskManager()->addTask( mFeatureCounter.get() );
728711
}
729-
int featuresCounted = 0;
730-
731-
// Renderer (rule based) may depend on context scale, with scale is ignored if 0
732-
QgsRenderContext renderContext;
733-
renderContext.setRendererScale( 0 );
734-
renderContext.expressionContext().appendScopes( QgsExpressionContextUtils::globalProjectLayerScopes( this ) );
735-
736-
QgsFeatureRequest request;
737-
if ( !mRenderer->filterNeedsGeometry() )
738-
request.setFlags( QgsFeatureRequest::NoGeometry );
739-
request.setSubsetOfAttributes( mRenderer->usedAttributes( renderContext ), mFields );
740-
QgsFeatureIterator fit = getFeatures( request );
741-
QgsVectorLayerInterruptionCheckerDuringCountSymbolFeatures interruptionCheck( &progressDialog );
742-
if ( showProgress )
743-
{
744-
fit.setInterruptionChecker( &interruptionCheck );
745-
}
746-
747-
mRenderer->startRender( renderContext, fields() );
748-
749-
QgsFeature f;
750-
QTime time;
751-
time.start();
752-
while ( fit.nextFeature( f ) )
753-
{
754-
renderContext.expressionContext().setFeature( f );
755-
QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
756-
Q_FOREACH ( const QString &key, featureKeyList )
757-
{
758-
mSymbolFeatureCountMap[key] += 1;
759-
}
760-
++featuresCounted;
761712

762-
if ( showProgress )
763-
{
764-
// Refresh progress every 50 features or second
765-
if ( ( featuresCounted % 50 == 0 ) || time.elapsed() > 1000 )
766-
{
767-
time.restart();
768-
if ( featuresCounted > nFeatures ) //sometimes the feature count is not correct
769-
{
770-
progressDialog.setMaximum( 0 );
771-
}
772-
progressDialog.setValue( featuresCounted );
773-
}
774-
// So that we get a chance of hitting the Abort button
775-
#ifdef Q_OS_LINUX
776-
// For some reason on Windows hasPendingEvents() always return true,
777-
// but one iteration is actually enough on Windows to get good interactivity
778-
// whereas on Linux we must allow for far more iterations.
779-
// For safety limit the number of iterations
780-
int nIters = 0;
781-
while ( QCoreApplication::hasPendingEvents() && ++nIters < 100 )
782-
#endif
783-
{
784-
QCoreApplication::processEvents();
785-
}
786-
if ( progressDialog.wasCanceled() )
787-
{
788-
mSymbolFeatureCountMap.clear();
789-
mRenderer->stopRender( renderContext );
790-
return false;
791-
}
792-
}
793-
}
794-
mRenderer->stopRender( renderContext );
795-
progressDialog.setValue( nFeatures );
796-
mSymbolFeatureCounted = true;
797713
return true;
798714
}
799715

@@ -3983,6 +3899,13 @@ void QgsVectorLayer::onRelationsLoaded()
39833899
mEditFormConfig.onRelationsLoaded();
39843900
}
39853901

3902+
void QgsVectorLayer::onSymbolsCounted( const QHash<QString, long> &symbolFeatureCountMap )
3903+
{
3904+
mSymbolFeatureCountMap = symbolFeatureCountMap;
3905+
mSymbolFeatureCounted = true;
3906+
emit symbolFeatureCountMapChanged();
3907+
}
3908+
39863909
QList<QgsRelation> QgsVectorLayer::referencingRelations( int idx ) const
39873910
{
39883911
return QgsProject::instance()->relationManager()->referencingRelations( this, idx );

src/core/qgsvectorlayer.h

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class QgsSymbol;
6565
class QgsVectorLayerJoinInfo;
6666
class QgsVectorLayerEditBuffer;
6767
class QgsVectorLayerJoinBuffer;
68+
class QgsVectorLayerFeatureCounter;
6869
class QgsAbstractVectorLayerLabeling;
6970
class QgsPointV2;
7071
class QgsFeedback;
@@ -810,7 +811,7 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
810811
* \param showProgress show progress dialog
811812
* \returns true if calculated, false if failed or was canceled by user
812813
*/
813-
bool countSymbolFeatures( bool showProgress = true );
814+
bool countSymbolFeatures();
814815

815816
/**
816817
* 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
18461847
*/
18471848
void readOnlyChanged();
18481849

1850+
/**
1851+
* Emitted when the feature count for symbols on this layer has been recalculated.
1852+
*
1853+
* \since QGIS 3.0
1854+
*/
1855+
void symbolFeatureCountMapChanged();
18491856

18501857
private slots:
18511858
void onJoinedFieldsChanged();
18521859
void onFeatureDeleted( QgsFeatureId fid );
18531860
void onRelationsLoaded();
1861+
void onSymbolsCounted( const QHash<QString, long> &symbolFeatureCountMap );
18541862

18551863
protected:
18561864
//! Set the extent
@@ -1883,7 +1891,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
18831891
#endif
18841892

18851893
private: // Private attributes
1886-
18871894
QgsConditionalLayerStyles *mConditionalStyles = nullptr;
18881895

18891896
//! Pointer to data provider derived from the abastract base class QgsDataProvider
@@ -2001,6 +2008,8 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
20012008

20022009
mutable QMutex mFeatureSourceConstructorMutex;
20032010

2011+
std::unique_ptr<QgsVectorLayerFeatureCounter> mFeatureCounter;
2012+
20042013
friend class QgsVectorLayerFeatureSource;
20052014
};
20062015

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#include "qgsvectorlayerfeaturecounter.h"
2+
3+
QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer )
4+
: mSource( new QgsVectorLayerFeatureSource( layer ) )
5+
, mRenderer( layer->renderer()->clone() )
6+
, mExpressionContextScopes( QgsExpressionContextUtils::globalProjectLayerScopes( layer ) )
7+
, mFeatureCount( layer->featureCount() )
8+
{
9+
}
10+
11+
bool QgsVectorLayerFeatureCounter::run()
12+
{
13+
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
14+
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
15+
16+
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
17+
{
18+
mSymbolFeatureCountMap.insert( symbolIt->first, 0 );
19+
}
20+
21+
int featuresCounted = 0;
22+
23+
// Renderer (rule based) may depend on context scale, with scale is ignored if 0
24+
QgsRenderContext renderContext;
25+
renderContext.setRendererScale( 0 );
26+
renderContext.expressionContext().appendScopes( mExpressionContextScopes );
27+
28+
QgsFeatureRequest request;
29+
if ( !mRenderer->filterNeedsGeometry() )
30+
request.setFlags( QgsFeatureRequest::NoGeometry );
31+
request.setSubsetOfAttributes( mRenderer->usedAttributes( renderContext ), mSource->fields() );
32+
QgsFeatureIterator fit = mSource->getFeatures( request );
33+
34+
// TODO: replace QgsInterruptionChecker with QgsFeedback
35+
// fit.setInterruptionChecker( mFeedback );
36+
37+
mRenderer->startRender( renderContext, mSource->fields() );
38+
39+
double progress = 0;
40+
QgsFeature f;
41+
while ( fit.nextFeature( f ) )
42+
{
43+
renderContext.expressionContext().setFeature( f );
44+
QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
45+
Q_FOREACH ( const QString &key, featureKeyList )
46+
{
47+
mSymbolFeatureCountMap[key] += 1;
48+
}
49+
++featuresCounted;
50+
51+
double p = ( featuresCounted / mFeatureCount ) * 100;
52+
if ( p - progress > 1 )
53+
{
54+
progress = p;
55+
setProgress( progress );
56+
}
57+
58+
if ( isCanceled() )
59+
{
60+
mRenderer->stopRender( renderContext );
61+
return false;
62+
}
63+
}
64+
mRenderer->stopRender( renderContext );
65+
setProgress( 100 );
66+
67+
emit symbolsCounted( mSymbolFeatureCountMap );
68+
return true;
69+
}

0 commit comments

Comments
 (0)