Skip to content

Commit 354b667

Browse files
authoredMay 11, 2017
Merge pull request #4526 from m-kuhn/featureCounter
Send feature counting to background task
2 parents 5888d22 + eaabce2 commit 354b667

19 files changed

+707
-339
lines changed
 

‎.ci/travis/linux/script.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export PATH=${HOME}/osgeo4travis/bin:${HOME}/osgeo4travis/sbin:${HOME}/OTB-5.6.0
1818
export LD_LIBRARY_PATH=${HOME}/osgeo4travis/lib
1919
export CTEST_PARALLEL_LEVEL=1
2020
export CCACHE_TEMPDIR=/tmp
21-
ccache -M 2G
21+
ccache -M 500M
22+
ccache -z
2223

2324
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
2425

‎.ci/travis/macos/script.sh

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ echo $PATH
1818

1919
export PATH=/usr/bin:${PATH}
2020

21-
ccache -s
22-
ccache -M 1G
21+
ccache -M 500M
22+
ccache -z
2323

2424
# Calculate the timeout for the tests.
2525
# The tests should be aborted before travis times out, in order to allow uploading

‎python/auto_sip.blacklist

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ core/qgssnappingutils.sip
7272
core/qgsspatialindex.sip
7373
core/qgssqlstatement.sip
7474
core/qgsstringutils.sip
75-
core/qgstaskmanager.sip
7675
core/qgstolerance.sip
7776
core/qgstracer.sip
7877
core/qgstrackedvectorlayertools.sip

‎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/qgstaskmanager.sip

Lines changed: 337 additions & 228 deletions
Large diffs are not rendered by default.

‎python/core/qgsvectorlayer.sip

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -841,11 +841,19 @@ 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
846-
Count features for symbols. Feature counts may be get by featureCount().
847-
\param showProgress show progress dialog
848-
:return: true if calculated, false if failed or was canceled by user
846+
Count features for symbols.
847+
The method will return immediately. You will need to connect to the
848+
symbolFeatureCountMapChanged() signal to be notified when the freshly updated
849+
feature counts are ready.
850+
851+
.. note::
852+
853+
If you need to wait for the results, create and start your own QgsVectorLayerFeatureCounter
854+
task and call waitForFinished().
855+
856+
.. versionadded:: 3.0
849857
:rtype: bool
850858
%End
851859

@@ -2000,6 +2008,12 @@ Signal emitted when setLayerTransparency() is called
20002008
.. versionadded:: 3.0
20012009
%End
20022010

2011+
void symbolFeatureCountMapChanged();
2012+
%Docstring
2013+
Emitted when the feature count for symbols on this layer has been recalculated.
2014+
2015+
.. versionadded:: 3.0
2016+
%End
20032017

20042018
protected:
20052019
virtual void setExtent( const QgsRectangle &rect );
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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, const QgsExpressionContext &context = QgsExpressionContext() );
28+
%Docstring
29+
Create a new feature counter for ``layer``.
30+
%End
31+
32+
virtual bool run();
33+
34+
35+
long featureCount( const QString &legendKey ) const;
36+
%Docstring
37+
Get the feature count for a particular ``legendKey``.
38+
If the key has not been found, -1 will be returned.
39+
:rtype: long
40+
%End
41+
42+
signals:
43+
44+
void symbolsCounted();
45+
%Docstring
46+
Emitted when the symbols have been counted.
47+
%End
48+
49+
};
50+
51+
/************************************************************************
52+
* This file has been generated automatically from *
53+
* *
54+
* src/core/qgsvectorlayerfeaturecounter.h *
55+
* *
56+
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
57+
************************************************************************/

‎python/core/qgsvectorlayerfeatureiterator.sip

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ class QgsVectorLayerFeatureSource : QgsAbstractFeatureSource
1717
~QgsVectorLayerFeatureSource();
1818

1919
virtual QgsFeatureIterator getFeatures( const QgsFeatureRequest &request = QgsFeatureRequest() );
20+
QgsFields fields();
2021
};
2122

2223
class QgsVectorLayerFeatureIterator : QgsAbstractFeatureIterator

‎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/layertree/qgslayertreemodellegendnode.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,7 @@ QgsSymbolLegendNode::QgsSymbolLegendNode( QgsLayerTreeLayer *nodeLayer, const Qg
135135
, mIconSize( 16, 16 )
136136
{
137137
updateLabel();
138+
connect( qobject_cast<QgsVectorLayer *>( nodeLayer->layer() ), &QgsVectorLayer::symbolFeatureCountMapChanged, this, &QgsSymbolLegendNode::updateLabel );
138139

139140
if ( mItem.symbol() )
140141
mSymbolUsesMapUnits = ( mItem.symbol()->outputUnit() != QgsUnitTypes::RenderMillimeters );
@@ -458,8 +459,13 @@ void QgsSymbolLegendNode::updateLabel()
458459
{
459460
mLabel = mUserLabel.isEmpty() ? mItem.label() : mUserLabel;
460461
if ( showFeatureCount && vl )
461-
mLabel += QStringLiteral( " [%1]" ).arg( vl->featureCount( mItem.ruleKey() ) );
462+
{
463+
qlonglong count = vl->featureCount( mItem.ruleKey() );
464+
mLabel += QStringLiteral( " [%1]" ).arg( count != -1 ? QLocale().toString( count ) : tr( "N/A" ) );
465+
}
462466
}
467+
468+
emit dataChanged();
463469
}
464470

465471

‎src/core/qgstaskmanager.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,27 @@ QList<QgsMapLayer *> QgsTask::dependentLayers() const
142142
return _qgis_listQPointerToRaw( mDependentLayers );
143143
}
144144

145+
bool QgsTask::waitForFinished( int timeout )
146+
{
147+
QEventLoop loop;
148+
bool rv = true;
149+
150+
connect( this, &QgsTask::taskCompleted, &loop, &QEventLoop::quit );
151+
connect( this, &QgsTask::taskTerminated, &loop, &QEventLoop::quit );
152+
QTimer timer;
153+
154+
if ( timeout != -1 )
155+
{
156+
timer.start( timeout );
157+
connect( &timer, &QTimer::timeout, [&rv]() { rv = false; } );
158+
connect( &timer, &QTimer::timeout, &loop, &QEventLoop::quit );
159+
}
160+
161+
loop.exec();
162+
163+
return rv;
164+
}
165+
145166
void QgsTask::setDependentLayers( const QList< QgsMapLayer * > &dependentLayers )
146167
{
147168
mDependentLayers = _qgis_listRawToQPointer( dependentLayers );

‎src/core/qgstaskmanager.h

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ class CORE_EXPORT QgsTask : public QObject
169169
* Subtasks can be nested, ie a subtask can legally be a parent task itself with
170170
* its own set of subtasks.
171171
*/
172-
void addSubTask( QgsTask *subTask, const QgsTaskList &dependencies = QgsTaskList(),
172+
void addSubTask( QgsTask *subTask SIP_TRANSFER, const QgsTaskList &dependencies = QgsTaskList(),
173173
SubTaskDependency subTaskDependency = SubTaskIndependent );
174174

175175
/**
@@ -186,6 +186,15 @@ class CORE_EXPORT QgsTask : public QObject
186186
*/
187187
QList< QgsMapLayer * > dependentLayers() const;
188188

189+
/**
190+
* Blocks the current thread until the task finishes or a maximum of \a timeout milliseconds.
191+
* If the \a timeout is ``-1`` the thread will be blocked forever.
192+
* In case of a timeout, the task will still be running.
193+
*
194+
* The result will be false if the wait timed out and true in any other case.
195+
*/
196+
bool waitForFinished( int timeout = 30000 );
197+
189198
signals:
190199

191200
/**

‎src/core/qgsvectorlayer.cpp

Lines changed: 19 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,15 @@ 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 = new QgsVectorLayerFeatureCounter( this );
706+
connect( mFeatureCounter, &QgsTask::taskCompleted, [ = ]() { onSymbolsCounted(); mFeatureCounter = nullptr; } );
707+
connect( mFeatureCounter, &QgsTask::taskTerminated, [ = ]() { mFeatureCounter = nullptr; } );
707708

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 );
709+
QgsApplication::taskManager()->addTask( mFeatureCounter );
728710
}
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;
761711

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;
797712
return true;
798713
}
799714

@@ -3983,6 +3898,16 @@ void QgsVectorLayer::onRelationsLoaded()
39833898
mEditFormConfig.onRelationsLoaded();
39843899
}
39853900

3901+
void QgsVectorLayer::onSymbolsCounted()
3902+
{
3903+
if ( mFeatureCounter )
3904+
{
3905+
mSymbolFeatureCountMap = mFeatureCounter->symbolFeatureCountMap();
3906+
mSymbolFeatureCounted = true;
3907+
emit symbolFeatureCountMapChanged();
3908+
}
3909+
}
3910+
39863911
QList<QgsRelation> QgsVectorLayer::referencingRelations( int idx ) const
39873912
{
39883913
return QgsProject::instance()->relationManager()->referencingRelations( this, idx );

‎src/core/qgsvectorlayer.h

Lines changed: 20 additions & 5 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;
@@ -806,11 +807,17 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
806807
void setDataSource( const QString &dataSource, const QString &baseName, const QString &provider, bool loadDefaultStyleFlag = false );
807808

808809
/**
809-
* Count features for symbols. Feature counts may be get by featureCount().
810-
* \param showProgress show progress dialog
811-
* \returns true if calculated, false if failed or was canceled by user
810+
* Count features for symbols.
811+
* The method will return immediately. You will need to connect to the
812+
* symbolFeatureCountMapChanged() signal to be notified when the freshly updated
813+
* feature counts are ready.
814+
*
815+
* \note If you need to wait for the results, create and start your own QgsVectorLayerFeatureCounter
816+
* task and call waitForFinished().
817+
*
818+
* \since This is asynchroneous since QGIS 3.0
812819
*/
813-
bool countSymbolFeatures( bool showProgress = true );
820+
bool countSymbolFeatures();
814821

815822
/**
816823
* Set the string (typically sql) used to define a subset of the layer
@@ -1846,11 +1853,18 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
18461853
*/
18471854
void readOnlyChanged();
18481855

1856+
/**
1857+
* Emitted when the feature count for symbols on this layer has been recalculated.
1858+
*
1859+
* \since QGIS 3.0
1860+
*/
1861+
void symbolFeatureCountMapChanged();
18491862

18501863
private slots:
18511864
void onJoinedFieldsChanged();
18521865
void onFeatureDeleted( QgsFeatureId fid );
18531866
void onRelationsLoaded();
1867+
void onSymbolsCounted();
18541868

18551869
protected:
18561870
//! Set the extent
@@ -1883,7 +1897,6 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
18831897
#endif
18841898

18851899
private: // Private attributes
1886-
18871900
QgsConditionalLayerStyles *mConditionalStyles = nullptr;
18881901

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

20022015
mutable QMutex mFeatureSourceConstructorMutex;
20032016

2017+
QgsVectorLayerFeatureCounter *mFeatureCounter = nullptr;
2018+
20042019
friend class QgsVectorLayerFeatureSource;
20052020
};
20062021

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#include "qgsvectorlayerfeaturecounter.h"
2+
3+
QgsVectorLayerFeatureCounter::QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context )
4+
: mSource( new QgsVectorLayerFeatureSource( layer ) )
5+
, mRenderer( layer->renderer()->clone() )
6+
, mExpressionContext( context )
7+
, mFeatureCount( layer->featureCount() )
8+
{
9+
if ( !mExpressionContext.scopeCount() )
10+
{
11+
mExpressionContext = layer->createExpressionContext();
12+
}
13+
}
14+
15+
bool QgsVectorLayerFeatureCounter::run()
16+
{
17+
QgsLegendSymbolList symbolList = mRenderer->legendSymbolItems();
18+
QgsLegendSymbolList::const_iterator symbolIt = symbolList.constBegin();
19+
20+
for ( ; symbolIt != symbolList.constEnd(); ++symbolIt )
21+
{
22+
mSymbolFeatureCountMap.insert( symbolIt->first, 0 );
23+
}
24+
25+
// If there are no features to be counted, we can spare us the trouble
26+
if ( mFeatureCount > 0 )
27+
{
28+
int featuresCounted = 0;
29+
30+
// Renderer (rule based) may depend on context scale, with scale is ignored if 0
31+
QgsRenderContext renderContext;
32+
renderContext.setRendererScale( 0 );
33+
renderContext.setExpressionContext( mExpressionContext );
34+
35+
QgsFeatureRequest request;
36+
if ( !mRenderer->filterNeedsGeometry() )
37+
request.setFlags( QgsFeatureRequest::NoGeometry );
38+
request.setSubsetOfAttributes( mRenderer->usedAttributes( renderContext ), mSource->fields() );
39+
QgsFeatureIterator fit = mSource->getFeatures( request );
40+
41+
// TODO: replace QgsInterruptionChecker with QgsFeedback
42+
// fit.setInterruptionChecker( mFeedback );
43+
44+
mRenderer->startRender( renderContext, mSource->fields() );
45+
46+
double progress = 0;
47+
QgsFeature f;
48+
while ( fit.nextFeature( f ) )
49+
{
50+
renderContext.expressionContext().setFeature( f );
51+
QSet<QString> featureKeyList = mRenderer->legendKeysForFeature( f, renderContext );
52+
Q_FOREACH ( const QString &key, featureKeyList )
53+
{
54+
mSymbolFeatureCountMap[key] += 1;
55+
}
56+
++featuresCounted;
57+
58+
double p = ( featuresCounted / mFeatureCount ) * 100;
59+
if ( p - progress > 1 )
60+
{
61+
progress = p;
62+
setProgress( progress );
63+
}
64+
65+
if ( isCanceled() )
66+
{
67+
mRenderer->stopRender( renderContext );
68+
return false;
69+
}
70+
}
71+
mRenderer->stopRender( renderContext );
72+
}
73+
74+
setProgress( 100 );
75+
76+
emit symbolsCounted();
77+
return true;
78+
}
79+
80+
QHash<QString, long> QgsVectorLayerFeatureCounter::symbolFeatureCountMap() const
81+
{
82+
return mSymbolFeatureCountMap;
83+
}
84+
85+
long QgsVectorLayerFeatureCounter::featureCount( const QString &legendKey ) const
86+
{
87+
return mSymbolFeatureCountMap.value( legendKey, -1 );
88+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#ifndef QGSVECTORLAYERFEATURECOUNTER_H
2+
#define QGSVECTORLAYERFEATURECOUNTER_H
3+
4+
#include "qgsvectorlayer.h"
5+
#include "qgsvectorlayerfeatureiterator.h"
6+
#include "qgsrenderer.h"
7+
#include "qgstaskmanager.h"
8+
9+
/** \ingroup core
10+
*
11+
* Counts the features in a QgsVectorLayer in task.
12+
* You should most likely not use this directly and instead call
13+
* QgsVectorLayer::countSymbolFeatures() and connect to the signal
14+
* QgsVectorLayer::symbolFeatureCountMapChanged().
15+
*
16+
* \since QGIS 3.0
17+
*/
18+
class CORE_EXPORT QgsVectorLayerFeatureCounter : public QgsTask
19+
{
20+
Q_OBJECT
21+
22+
public:
23+
24+
/**
25+
* Create a new feature counter for \a layer.
26+
*/
27+
QgsVectorLayerFeatureCounter( QgsVectorLayer *layer, const QgsExpressionContext &context = QgsExpressionContext() );
28+
29+
virtual bool run() override;
30+
31+
/**
32+
* Get the count for each symbol. Only valid after the symbolsCounted()
33+
* signal has been emitted.
34+
*
35+
* \note Not available in Python bindings.
36+
*/
37+
QHash<QString, long> symbolFeatureCountMap() const SIP_SKIP;
38+
39+
/**
40+
* Get the feature count for a particular \a legendKey.
41+
* If the key has not been found, -1 will be returned.
42+
*/
43+
long featureCount( const QString &legendKey ) const;
44+
45+
signals:
46+
47+
/**
48+
* Emitted when the symbols have been counted.
49+
*/
50+
void symbolsCounted();
51+
52+
private:
53+
std::unique_ptr<QgsVectorLayerFeatureSource> mSource;
54+
std::unique_ptr<QgsFeatureRenderer> mRenderer;
55+
QgsExpressionContext mExpressionContext;
56+
QHash<QString, long> mSymbolFeatureCountMap;
57+
int mFeatureCount;
58+
59+
};
60+
61+
#endif // QGSVECTORLAYERFEATURECOUNTER_H

‎src/core/qgsvectorlayerfeatureiterator.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ QgsFeatureIterator QgsVectorLayerFeatureSource::getFeatures( const QgsFeatureReq
9393
return QgsFeatureIterator( new QgsVectorLayerFeatureIterator( this, false, request ) );
9494
}
9595

96+
QgsFields QgsVectorLayerFeatureSource::fields() const
97+
{
98+
return mFields;
99+
}
100+
96101

97102
QgsVectorLayerFeatureIterator::QgsVectorLayerFeatureIterator( QgsVectorLayerFeatureSource *source, bool ownSource, const QgsFeatureRequest &request )
98103
: QgsAbstractFeatureIteratorFromSource<QgsVectorLayerFeatureSource>( source, ownSource, request )

‎src/core/qgsvectorlayerfeatureiterator.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,14 @@ class CORE_EXPORT QgsVectorLayerFeatureSource : public QgsAbstractFeatureSource
5353

5454
friend class QgsVectorLayerFeatureIterator;
5555

56+
/**
57+
* Returns the fields that will be available for features that are retrieved from
58+
* this source.
59+
*
60+
* \since QGIS 3.0
61+
*/
62+
QgsFields fields() const;
63+
5664
protected:
5765

5866
QgsAbstractFeatureSource *mProviderFeatureSource = nullptr;

‎tests/src/core/testqgstaskmanager.cpp

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@ class ProgressReportingTask : public QgsTask
5656
ProgressReportingTask( const QString &desc = QString() ) : QgsTask( desc ), finished( false ), terminated( false ) {}
5757

5858
void emitProgressChanged( double progress ) { setProgress( progress ); }
59-
void finish() { finished = true; }
60-
void terminate() { terminated = true; }
6159

6260
bool finished;
6361
bool terminated;
6462

63+
public slots:
64+
void finish() { finished = true; }
65+
void terminate() { terminated = true; }
66+
6567
protected:
6668

6769
bool run() override
@@ -198,6 +200,7 @@ class TestQgsTaskManager : public QObject
198200
void addTask();
199201
void taskTerminationBeforeDelete();
200202
void taskId();
203+
void waitForFinished();
201204
void progressChanged();
202205
void statusChanged();
203206
void allTasksFinished();
@@ -654,6 +657,49 @@ void TestQgsTaskManager::taskId()
654657
delete task3;
655658
}
656659

660+
void TestQgsTaskManager::waitForFinished()
661+
{
662+
QgsTaskManager manager;
663+
QEventLoop loop;
664+
665+
ProgressReportingTask *finishedTask = new ProgressReportingTask();
666+
connect( finishedTask, &ProgressReportingTask::begun, &loop, &QEventLoop::quit );
667+
manager.addTask( finishedTask );
668+
if ( finishedTask->status() != QgsTask::Running )
669+
loop.exec();
670+
671+
QTimer timer;
672+
connect( &timer, &QTimer::timeout, finishedTask, &ProgressReportingTask::finish );
673+
timer.start( 100 );
674+
QCOMPARE( finishedTask->status(), QgsTask::Running );
675+
QCOMPARE( finishedTask->waitForFinished(), true );
676+
QCOMPARE( finishedTask->status(), QgsTask::Complete );
677+
678+
ProgressReportingTask *failedTask = new ProgressReportingTask();
679+
connect( failedTask, &ProgressReportingTask::begun, &loop, &QEventLoop::quit );
680+
manager.addTask( failedTask );
681+
if ( failedTask->status() != QgsTask::Running )
682+
loop.exec();
683+
684+
connect( &timer, &QTimer::timeout, failedTask, &ProgressReportingTask::terminate );
685+
timer.start( 100 );
686+
QCOMPARE( failedTask->status(), QgsTask::Running );
687+
QCOMPARE( failedTask->waitForFinished(), true );
688+
QCOMPARE( failedTask->status(), QgsTask::Terminated );
689+
690+
ProgressReportingTask *timeoutTooShortTask = new ProgressReportingTask();
691+
connect( timeoutTooShortTask, &ProgressReportingTask::begun, &loop, &QEventLoop::quit );
692+
manager.addTask( timeoutTooShortTask );
693+
if ( timeoutTooShortTask->status() != QgsTask::Running )
694+
loop.exec();
695+
696+
connect( &timer, &QTimer::timeout, timeoutTooShortTask, &ProgressReportingTask::finish );
697+
timer.start( 1000 );
698+
QCOMPARE( timeoutTooShortTask->status(), QgsTask::Running );
699+
QCOMPARE( timeoutTooShortTask->waitForFinished( 20 ), false );
700+
QCOMPARE( timeoutTooShortTask->status(), QgsTask::Running );
701+
}
702+
657703
void TestQgsTaskManager::progressChanged()
658704
{
659705
// check that progressChanged signals emitted by tasks result in progressChanged signal from manager

0 commit comments

Comments
 (0)
Please sign in to comment.