From 9156933c9e30c95413efb3afde7697ff10306608 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 10:34:55 +1000 Subject: [PATCH 01/49] Add a pure virtual clone method for algorithms This is required for safely executing the algorithm in a background thread. --- .../processing/qgsprocessingalgorithm.sip | 7 +++ .../qgsprocessingmodelalgorithm.sip | 2 + .../processing/modeler/ModelerDialog.py | 8 ++-- src/core/processing/qgsnativealgorithms.cpp | 45 +++++++++++++++++++ src/core/processing/qgsnativealgorithms.h | 9 ++++ src/core/processing/qgsprocessingalgorithm.h | 10 ++++- .../qgsprocessingmodelalgorithm.cpp | 7 +++ .../processing/qgsprocessingmodelalgorithm.h | 1 + tests/src/core/testqgsprocessing.cpp | 1 + 9 files changed, 84 insertions(+), 6 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index 532c047c4695..342266c0cda7 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -43,6 +43,13 @@ class QgsProcessingAlgorithm virtual ~QgsProcessingAlgorithm(); + + virtual QgsProcessingAlgorithm *clone() const = 0 /Factory/; +%Docstring + Clones the algorithm, returning a new copy for safe use in background threads. + :rtype: QgsProcessingAlgorithm +%End + virtual QString name() const = 0; %Docstring Returns the algorithm name, used for identifying the algorithm. This string diff --git a/python/core/processing/qgsprocessingmodelalgorithm.sip b/python/core/processing/qgsprocessingmodelalgorithm.sip index 8d4b741229cf..8522bb6308cf 100644 --- a/python/core/processing/qgsprocessingmodelalgorithm.sip +++ b/python/core/processing/qgsprocessingmodelalgorithm.sip @@ -588,6 +588,8 @@ Copies are protected to avoid slicing virtual QString asPythonCommand( const QVariantMap ¶meters, QgsProcessingContext &context ) const; + virtual QgsProcessingModelAlgorithm *clone() const /Factory/; + void setName( const QString &name ); %Docstring diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 917459439c5d..074cfb90334c 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -237,9 +237,9 @@ def _mimeDataAlgorithm(items): self.mActionRun.triggered.connect(self.runModel) if model is not None: - self.model = model - self.textGroup.setText(model.group()) - self.textName.setText(model.displayName()) + self.model = model.clone() + self.textGroup.setText(self.model.group()) + self.textName.setText(self.model.displayName()) self.repaintModel() else: @@ -440,7 +440,7 @@ def saveModel(self, saveAs): return self.model.setName(str(self.textName.text())) self.model.setGroup(str(self.textGroup.text())) - if self.model.sourceFilePath() is not None and not saveAs: + if self.model.sourceFilePath() and not saveAs: filename = self.model.sourceFilePath() else: filename, filter = QFileDialog.getSaveFileName(self, diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index bf45a523c684..c5af5ab1f92c 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -84,6 +84,11 @@ QString QgsCentroidAlgorithm::shortHelpString() const "The attributes associated to each point in the output layer are the same ones associated to the original features." ); } +QgsCentroidAlgorithm *QgsCentroidAlgorithm::clone() const +{ + return new QgsCentroidAlgorithm(); +} + QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -159,6 +164,11 @@ QString QgsBufferAlgorithm::shortHelpString() const "The mitre limit parameter is only applicable for mitre join styles, and controls the maximum distance from the offset curve to use when creating a mitred join." ); } +QgsBufferAlgorithm *QgsBufferAlgorithm::clone() const +{ + return new QgsBufferAlgorithm(); +} + QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -263,6 +273,11 @@ QString QgsDissolveAlgorithm::shortHelpString() const "In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased." ); } +QgsDissolveAlgorithm *QgsDissolveAlgorithm::clone() const +{ + return new QgsDissolveAlgorithm(); +} + QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -406,6 +421,11 @@ QString QgsClipAlgorithm::shortHelpString() const "be manually updated." ); } +QgsClipAlgorithm *QgsClipAlgorithm::clone() const +{ + return new QgsClipAlgorithm(); +} + QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -554,6 +574,11 @@ QString QgsTransformAlgorithm::shortHelpString() const "Attributes are not modified by this algorithm." ); } +QgsTransformAlgorithm *QgsTransformAlgorithm::clone() const +{ + return new QgsTransformAlgorithm(); +} + QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -618,6 +643,11 @@ QString QgsSubdivideAlgorithm::shortHelpString() const "Curved geometries will be segmentized before subdivision." ); } +QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::clone() const +{ + return new QgsSubdivideAlgorithm(); +} + QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -684,6 +714,11 @@ QString QgsMultipartToSinglepartAlgorithm::shortHelpString() const "contain, and the same attributes are used for each of them." ); } +QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::clone() const +{ + return new QgsMultipartToSinglepartAlgorithm(); +} + QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -766,6 +801,11 @@ QString QgsExtractByExpressionAlgorithm::shortHelpString() const "For more information about expressions see the user manual" ); } +QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::clone() const +{ + return new QgsExtractByExpressionAlgorithm(); +} + QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); @@ -892,6 +932,11 @@ QString QgsExtractByAttributeAlgorithm::shortHelpString() const "of an attribute from the input layer." ); } +QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::clone() const +{ + return new QgsExtractByAttributeAlgorithm(); +} + QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const { std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index 1221a425eab9..edd85af96cea 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -60,6 +60,7 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; + QgsCentroidAlgorithm *clone() const override SIP_FACTORY; protected: @@ -83,6 +84,7 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "transform,reproject,crs,srs,warp" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector general tools" ); } QString shortHelpString() const override; + QgsTransformAlgorithm *clone() const override SIP_FACTORY; protected: @@ -106,6 +108,7 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "buffer,grow" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; + QgsBufferAlgorithm *clone() const override SIP_FACTORY; protected: @@ -129,6 +132,7 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "dissolve,union,combine,collect" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; + QgsDissolveAlgorithm *clone() const override SIP_FACTORY; protected: @@ -167,6 +171,7 @@ class QgsExtractByAttributeAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "extract,filter,attribute,value,contains,null,field" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector selection tools" ); } QString shortHelpString() const override; + QgsExtractByAttributeAlgorithm *clone() const override SIP_FACTORY; protected: @@ -190,6 +195,7 @@ class QgsExtractByExpressionAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "extract,filter,expression,field" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector selection tools" ); } QString shortHelpString() const override; + QgsExtractByExpressionAlgorithm *clone() const override SIP_FACTORY; protected: @@ -213,6 +219,7 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "clip,intersect,intersection,mask" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector overlay tools" ); } QString shortHelpString() const override; + QgsClipAlgorithm *clone() const override SIP_FACTORY; protected: @@ -237,6 +244,7 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "subdivide,segmentize,split,tesselate" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; + QgsSubdivideAlgorithm *clone() const override SIP_FACTORY; protected: @@ -260,6 +268,7 @@ class QgsMultipartToSinglepartAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "multi,single,multiple,split,dump" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; + QgsMultipartToSinglepartAlgorithm *clone() const override SIP_FACTORY; protected: diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 2227bcfaa233..cec35e8117ad 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -61,11 +61,17 @@ class CORE_EXPORT QgsProcessingAlgorithm virtual ~QgsProcessingAlgorithm(); - //! Algorithms cannot be copied + + //! Algorithms cannot be copied - clone() should be used instead QgsProcessingAlgorithm( const QgsProcessingAlgorithm &other ) = delete; - //! Algorithms cannot be copied + //! Algorithms cannot be copied- clone() should be used instead QgsProcessingAlgorithm &operator=( const QgsProcessingAlgorithm &other ) = delete; + /** + * Clones the algorithm, returning a new copy for safe use in background threads. + */ + virtual QgsProcessingAlgorithm *clone() const = 0 SIP_FACTORY; + /** * Returns the algorithm name, used for identifying the algorithm. This string * should be fixed for the algorithm, and must not be localised. The name should diff --git a/src/core/processing/qgsprocessingmodelalgorithm.cpp b/src/core/processing/qgsprocessingmodelalgorithm.cpp index 4f100e3f45e0..2677a3b805bd 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/qgsprocessingmodelalgorithm.cpp @@ -1101,6 +1101,13 @@ QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap ¶met return QgsProcessingAlgorithm::asPythonCommand( parameters, context ); } +QgsProcessingModelAlgorithm *QgsProcessingModelAlgorithm::clone() const +{ + QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm(); + alg->loadVariant( toVariant() ); + alg->setProvider( provider() ); + return alg; +} bool QgsProcessingModelAlgorithm::ChildParameterSource::operator==( const QgsProcessingModelAlgorithm::ChildParameterSource &other ) const { diff --git a/src/core/processing/qgsprocessingmodelalgorithm.h b/src/core/processing/qgsprocessingmodelalgorithm.h index 0cb3c0a4bdab..580d6589b677 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.h +++ b/src/core/processing/qgsprocessingmodelalgorithm.h @@ -590,6 +590,7 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm bool canExecute( QString *errorMessage SIP_OUT = nullptr ) const override; QString asPythonCommand( const QVariantMap ¶meters, QgsProcessingContext &context ) const override; + QgsProcessingModelAlgorithm *clone() const override SIP_FACTORY; /** * Sets the model \a name. diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index f2d293d0ba6c..eeefcd23decd 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -46,6 +46,7 @@ class DummyAlgorithm : public QgsProcessingAlgorithm QgsProcessingContext &, QgsProcessingFeedback * ) const override { return QVariantMap(); } virtual Flags flags() const override { return mFlags; } + DummyAlgorithm *clone() const override { return new DummyAlgorithm( name() ); } QString mName; From 6d7b0a3bbc74140c1ca945bfd1d32d43ba7e3be4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 13:38:57 +1000 Subject: [PATCH 02/49] Fix re-saving already saved models --- python/plugins/processing/modeler/ModelerDialog.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 074cfb90334c..2073f8912d4e 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -238,6 +238,7 @@ def _mimeDataAlgorithm(items): if model is not None: self.model = model.clone() + self.model.setSourceFilePath(model.sourceFilePath()) self.textGroup.setText(self.model.group()) self.textName.setText(self.model.displayName()) self.repaintModel() From 0c6b19cd853f60f629140d9e529a304275423781 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 13:39:19 +1000 Subject: [PATCH 03/49] Improvements to QgsProcessingAlgRunnerTask - take a clone of algs before running them. This avoids issues if the algorithm is removed or edited while a background task is running - accept an optional existing feedback object --- .../processing/qgsprocessingalgrunnertask.sip | 3 ++- .../processing/qgsprocessingalgrunnertask.cpp | 15 ++++++++++----- src/core/processing/qgsprocessingalgrunnertask.h | 10 ++++++---- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/python/core/processing/qgsprocessingalgrunnertask.sip b/python/core/processing/qgsprocessingalgrunnertask.sip index bd05981d9b82..ab038e924aef 100644 --- a/python/core/processing/qgsprocessingalgrunnertask.sip +++ b/python/core/processing/qgsprocessingalgrunnertask.sip @@ -24,7 +24,8 @@ class QgsProcessingAlgRunnerTask : QgsTask QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, - QgsProcessingContext &context ); + QgsProcessingContext &context, + QgsProcessingFeedback *feedback = 0 ); %Docstring Constructor for QgsProcessingAlgRunnerTask. Takes an ``algorithm``, algorithm ``parameters`` and processing ``context``. diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index 308269c15511..9ca92cbe61ec 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -22,13 +22,18 @@ #include "qgsprocessingutils.h" #include "qgsvectorlayer.h" -QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, QgsProcessingContext &context ) +QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) : QgsTask( tr( "Running %1" ).arg( algorithm->name() ), QgsTask::CanCancel ) - , mAlgorithm( algorithm ) , mParameters( parameters ) , mContext( context ) + , mFeedback( feedback ) + , mAlgorithm( algorithm->clone() ) { - mFeedback.reset( new QgsProcessingFeedback() ); + if ( !mFeedback ) + { + mOwnedFeedback.reset( new QgsProcessingFeedback() ); + mFeedback = mOwnedFeedback.get(); + } } void QgsProcessingAlgRunnerTask::cancel() @@ -38,7 +43,7 @@ void QgsProcessingAlgRunnerTask::cancel() bool QgsProcessingAlgRunnerTask::run() { - connect( mFeedback.get(), &QgsFeedback::progressChanged, this, &QgsProcessingAlgRunnerTask::setProgress ); + connect( mFeedback, &QgsFeedback::progressChanged, this, &QgsProcessingAlgRunnerTask::setProgress ); bool ok = false; try { @@ -48,7 +53,7 @@ bool QgsProcessingAlgRunnerTask::run() { return false; } - return !mFeedback->isCanceled(); + return ok && !mFeedback->isCanceled(); } void QgsProcessingAlgRunnerTask::finished( bool result ) diff --git a/src/core/processing/qgsprocessingalgrunnertask.h b/src/core/processing/qgsprocessingalgrunnertask.h index 17fb88b8315b..4866b2abe0d2 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.h +++ b/src/core/processing/qgsprocessingalgrunnertask.h @@ -22,8 +22,8 @@ #include "qgis.h" #include "qgstaskmanager.h" #include "qgsprocessingfeedback.h" +#include "qgsprocessingalgorithm.h" -class QgsProcessingAlgorithm; class QgsProcessingContext; /** @@ -44,7 +44,8 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask */ QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, - QgsProcessingContext &context ); + QgsProcessingContext &context, + QgsProcessingFeedback *feedback = nullptr ); virtual void cancel() override; @@ -55,11 +56,12 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask private: - const QgsProcessingAlgorithm *mAlgorithm = nullptr; QVariantMap mParameters; QVariantMap mResults; QgsProcessingContext &mContext; - std::unique_ptr< QgsProcessingFeedback > mFeedback; + QgsProcessingFeedback *mFeedback = nullptr; + std::unique_ptr< QgsProcessingFeedback > mOwnedFeedback; + std::unique_ptr< const QgsProcessingAlgorithm > mAlgorithm; }; From 1b2afea23e0365ab9eddc7fdb07c1a4e336c7845 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 13:45:22 +1000 Subject: [PATCH 04/49] Add some more clone methods to algorithm subclasses --- python/plugins/processing/algs/qgis/QgisAlgorithm.py | 3 +++ python/plugins/processing/tests/QgisAlgorithmsTest.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithm.py b/python/plugins/processing/algs/qgis/QgisAlgorithm.py index 41605e87c890..c2121cb77d44 100755 --- a/python/plugins/processing/algs/qgis/QgisAlgorithm.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithm.py @@ -47,3 +47,6 @@ def trAlgorithm(self, string, context=''): if context == '': context = self.__class__.__name__ return string, QCoreApplication.translate(context, string) + + def clone(self): + return type(self)() diff --git a/python/plugins/processing/tests/QgisAlgorithmsTest.py b/python/plugins/processing/tests/QgisAlgorithmsTest.py index b81b452c3d3d..bd28a389dbe8 100644 --- a/python/plugins/processing/tests/QgisAlgorithmsTest.py +++ b/python/plugins/processing/tests/QgisAlgorithmsTest.py @@ -48,6 +48,9 @@ def name(self): def displayName(self): return 'testalg' + def clone(self): + return TestAlg() + def processAlgorithm(self, parameters, context, feedback): raise GeoAlgorithmExecutionException('Exception while processing') return {} From cd9328d259e64cab797a1f1c132ed6982fc80ca8 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 13:50:13 +1000 Subject: [PATCH 05/49] Fix some processing algorithm exception handling --- src/core/processing/qgsprocessingalgrunnertask.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index 9ca92cbe61ec..ccd68b26ef22 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -47,7 +47,7 @@ bool QgsProcessingAlgRunnerTask::run() bool ok = false; try { - mResults = mAlgorithm->run( mParameters, mContext, mFeedback.get(), &ok ); + mResults = mAlgorithm->run( mParameters, mContext, mFeedback, &ok ); } catch ( QgsProcessingException & ) { From 5c4f64270d456ae992dba1b8e7a8cd45283b3c48 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 14:18:01 +1000 Subject: [PATCH 06/49] Add clone method to ScriptAlgorithm --- python/plugins/processing/script/ScriptAlgorithm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py index 63120061d9d6..6f7fe7a0f46a 100644 --- a/python/plugins/processing/script/ScriptAlgorithm.py +++ b/python/plugins/processing/script/ScriptAlgorithm.py @@ -75,6 +75,9 @@ def __init__(self, descriptionFile, script=None): if descriptionFile is not None: self.defineCharacteristicsFromFile() + def clone(self): + return ScriptAlgorithm(self.descriptionFile) + def icon(self): return self._icon From c2621b127517d11c004e48e5790f37545cc02771 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Tue, 27 Jun 2017 21:16:15 +1000 Subject: [PATCH 07/49] Split algorithm execution into separate prepare/process/postProcess steps The prepare and postProcess steps are designed to be run in main thread only, while the process step can safely be run in a background thread. --- .../processing/qgsprocessingalgorithm.sip | 95 +++- .../qgsprocessingmodelalgorithm.sip | 8 +- src/core/processing/qgsnativealgorithms.cpp | 535 +++++++++++------- src/core/processing/qgsnativealgorithms.h | 133 ++++- .../processing/qgsprocessingalgorithm.cpp | 69 ++- src/core/processing/qgsprocessingalgorithm.h | 85 ++- .../processing/qgsprocessingalgrunnertask.h | 2 +- .../qgsprocessingmodelalgorithm.cpp | 22 +- .../processing/qgsprocessingmodelalgorithm.h | 10 +- tests/src/core/testqgsprocessing.cpp | 5 +- 10 files changed, 702 insertions(+), 262 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index 342266c0cda7..d7d83f6ba2d5 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -224,7 +224,9 @@ class QgsProcessingAlgorithm QVariantMap run( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok /Out/ = 0 ) const; %Docstring - Executes the algorithm using the specified ``parameters``. + Executes the algorithm using the specified ``parameters``. This method internally + creates a copy of the algorithm before running it, so it is safe to call + on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. The ``context`` argument specifies the context in which the algorithm is being run. @@ -234,6 +236,55 @@ class QgsProcessingAlgorithm :return: A map of algorithm outputs. These may be output layer references, or calculated values such as statistical calculations. + +.. note:: + + this method can only be called from the main thread. Use prepare(), runPrepared() and postProcess() + if you need to run algorithms from a background thread, or use the QgsProcessingAlgRunnerTask class. + :rtype: QVariantMap +%End + + bool prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); +%Docstring + Prepares the algorithm for execution. This must be run in the main thread, and allows the algorithm + to pre-evaluate input parameters in a thread-safe manner. This must be called before + calling runPrepared() (which is safe to do in any thread). +.. seealso:: runPrepared() +.. seealso:: postProcess() +.. note:: + + This method modifies the algorithm instance, so it is not safe to call + on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. + :rtype: bool +%End + + bool runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); +%Docstring + Runs the algorithm, which has been prepared by an earlier call to prepare(). + This method is safe to call from any thread. Returns true if the algorithm was successfully executed. + After runPrepared() has finished, the postProcess() method should be called from the main thread + to allow the algorithm to perform any required cleanup tasks and return its final result. +.. seealso:: prepare() +.. seealso:: postProcess() +.. note:: + + This method modifies the algorithm instance, so it is not safe to call + on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. + :rtype: bool +%End + + QVariantMap postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); +%Docstring + Should be called in the main thread following the completion of runPrepared(). This method + allows the algorithm to perform any required cleanup tasks. The returned variant map + includes the results evaluated by the algorithm. +.. note:: + + This method modifies the algorithm instance, so it is not safe to call + on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. :rtype: QVariantMap %End @@ -305,8 +356,25 @@ class QgsProcessingAlgorithm :rtype: bool %End - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const = 0 /VirtualErrorHandler=processing_exception_handler/; + virtual bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) /VirtualErrorHandler=processing_exception_handler/; +%Docstring + Prepares the algorithm to run using the specified ``parameters``. Algorithms should implement + their logic for evaluating parameter values here. The evaluated parameter results should + be stored in member variables ready for a call to processAlgorithm(). + + The ``context`` argument specifies the context in which the algorithm is being run. + + Algorithm preparation progress should be reported using the supplied ``feedback`` object. Additionally, + well-behaved algorithms should periodically check ``feedback`` to determine whether the + algorithm should be canceled and exited early. + + :return: true if preparation was successful. +.. seealso:: processAlgorithm() +.. seealso:: postProcessAlgorithm() + :rtype: bool +%End + + virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/; %Docstring Runs the algorithm using the specified ``parameters``. Algorithms should implement their custom processing logic here. @@ -319,6 +387,27 @@ class QgsProcessingAlgorithm :return: A map of algorithm outputs. These may be output layer references, or calculated values such as statistical calculations. +.. seealso:: prepareAlgorithm() +.. seealso:: postProcessAlgorithm() + :rtype: bool +%End + + virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/; +%Docstring + Allows the algorithm to perform any required cleanup tasks. The returned variant map + includes the results evaluated by the algorithm. These may be output layer references, or calculated + values such as statistical calculations. + + The ``context`` argument specifies the context in which the algorithm was run. + + Postprocess progress should be reported using the supplied ``feedback`` object. Additionally, + well-behaved algorithms should periodically check ``feedback`` to determine whether the + post processing should be canceled and exited early. + + :return: A map of algorithm outputs. These may be output layer references, or calculated + values such as statistical calculations. +.. seealso:: prepareAlgorithm() +.. seealso:: processAlgorithm() :rtype: QVariantMap %End diff --git a/python/core/processing/qgsprocessingmodelalgorithm.sip b/python/core/processing/qgsprocessingmodelalgorithm.sip index 8522bb6308cf..ad715862d22b 100644 --- a/python/core/processing/qgsprocessingmodelalgorithm.sip +++ b/python/core/processing/qgsprocessingmodelalgorithm.sip @@ -835,8 +835,12 @@ Copies are protected to avoid slicing protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const; + virtual bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + + virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + }; diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index c5af5ab1f92c..6abaaaeba8e0 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -74,8 +74,8 @@ void QgsNativeAlgorithms::loadAlgorithms() QgsCentroidAlgorithm::QgsCentroidAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); - addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Centroids" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); - addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Centroids" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Centroids" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Centroids" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); } QString QgsCentroidAlgorithm::shortHelpString() const @@ -89,23 +89,27 @@ QgsCentroidAlgorithm *QgsCentroidAlgorithm::clone() const return new QgsCentroidAlgorithm(); } -QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsCentroidAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LAYER" ), context, dest, source->fields(), QgsWkbTypes::Point, source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::Point, mSource->sourceCrs() ) ); + if ( !mSink ) + return false; - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); + return true; +} + +bool QgsCentroidAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return true; QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; @@ -125,14 +129,20 @@ QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meter QgsMessageLog::logMessage( QObject::tr( "Error calculating centroid for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } } - sink->addFeature( out, QgsFeatureSink::FastInsert ); + mSink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } + mSource.reset(); + mSink.reset(); + return true; +} +QVariantMap QgsCentroidAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), mSinkId ); return outputs; } @@ -151,8 +161,8 @@ QgsBufferAlgorithm::QgsBufferAlgorithm() addParameter( new QgsProcessingParameterNumber( QStringLiteral( "MITRE_LIMIT" ), QObject::tr( "Miter limit" ), QgsProcessingParameterNumber::Double, 2, false, 1 ) ); addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DISSOLVE" ), QObject::tr( "Dissolve result" ), false ) ); - addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Buffered" ), QgsProcessingParameterDefinition::TypeVectorPolygon ) ); - addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT_LAYER" ), QObject::tr( "Buffered" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); + addParameter( new QgsProcessingParameterFeatureSink( QStringLiteral( "OUTPUT" ), QObject::tr( "Buffered" ), QgsProcessingParameterDefinition::TypeVectorPolygon ) ); + addOutput( new QgsProcessingOutputVectorLayer( QStringLiteral( "OUTPUT" ), QObject::tr( "Buffered" ), QgsProcessingParameterDefinition::TypeVectorPoint ) ); } QString QgsBufferAlgorithm::shortHelpString() const @@ -169,37 +179,47 @@ QgsBufferAlgorithm *QgsBufferAlgorithm::clone() const return new QgsBufferAlgorithm(); } -QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsBufferAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT_LAYER" ), context, dest, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::Polygon, mSource->sourceCrs() ) ); + if ( !mSink ) + return false; // fixed parameters - bool dissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context ); - int segments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context ); - QgsGeometry::EndCapStyle endCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) ); - QgsGeometry::JoinStyle joinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) ); - double miterLimit = parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context ); - double bufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context ); - bool dynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) ); - const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) ); - - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); + mDissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context ); + mSegments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context ); + mEndCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) ); + mJoinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) ); + mMiterLimit = parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context ); + mBufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context ); + mDynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) ); + if ( mDynamicBuffer ) + { + mDynamicBufferProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >(); + mExpContext = context.expressionContext(); + mDefaultBuffer = parameterDefinition( QStringLiteral( "DISTANCE" ) )->defaultValue().toDouble(); + } + return true; +} + +bool QgsBufferAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return false; QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; + double bufferDistance = mBufferDistance; + QList< QgsGeometry > bufferedGeometriesForDissolve; QgsAttributes dissolveAttrs; @@ -215,45 +235,51 @@ QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsFeature out = f; if ( out.hasGeometry() ) { - if ( dynamicBuffer ) + if ( mDynamicBuffer ) { - context.expressionContext().setFeature( f ); - bufferDistance = QgsProcessingParameters::parameterAsDouble( distanceParamDef, parameters, context ); + mExpContext.setFeature( f ); + bufferDistance = mDynamicBufferProperty.valueAsDouble( mExpContext, mDefaultBuffer ); } - QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, segments, endCapStyle, joinStyle, miterLimit ); + QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, mSegments, mEndCapStyle, mJoinStyle, mMiterLimit ); if ( !outputGeometry ) { QgsMessageLog::logMessage( QObject::tr( "Error calculating buffer for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } - if ( dissolve ) + if ( mDissolve ) bufferedGeometriesForDissolve << outputGeometry; else out.setGeometry( outputGeometry ); } - if ( !dissolve ) - sink->addFeature( out, QgsFeatureSink::FastInsert ); + if ( !mDissolve ) + mSink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - if ( dissolve ) + if ( mDissolve ) { QgsGeometry finalGeometry = QgsGeometry::unaryUnion( bufferedGeometriesForDissolve ); QgsFeature f; f.setGeometry( finalGeometry ); f.setAttributes( dissolveAttrs ); - sink->addFeature( f, QgsFeatureSink::FastInsert ); + mSink->addFeature( f, QgsFeatureSink::FastInsert ); } + mSource.reset(); + mSink.reset(); + return true; +} + +QVariantMap QgsBufferAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } - QgsDissolveAlgorithm::QgsDissolveAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -278,31 +304,34 @@ QgsDissolveAlgorithm *QgsDissolveAlgorithm::clone() const return new QgsDissolveAlgorithm(); } -QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsDissolveAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) ); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + if ( !mSink ) + return false; - QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context ); + mFields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context ); + return true; +} - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); +bool QgsDissolveAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return true; QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; - if ( fields.isEmpty() ) + if ( mFields.isEmpty() ) { // dissolve all - not using fields bool firstFeature = true; @@ -340,14 +369,14 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meter } outputFeature.setGeometry( QgsGeometry::unaryUnion( geomQueue ) ); - sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); } else { QList< int > fieldIndexes; - Q_FOREACH ( const QString &field, fields ) + Q_FOREACH ( const QString &field, mFields ) { - int index = source->fields().lookupField( field ); + int index = mSource->fields().lookupField( field ); if ( index >= 0 ) fieldIndexes << index; } @@ -391,15 +420,23 @@ QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meter QgsFeature outputFeature; outputFeature.setGeometry( QgsGeometry::unaryUnion( geomIt.value() ) ); outputFeature.setAttributes( attributeHash.value( geomIt.key() ) ); - sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); feedback->setProgress( current * 100.0 / numberFeatures ); current++; } } + mSource.reset(); + mSink.reset(); + + return true; +} + +QVariantMap QgsDissolveAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } @@ -426,25 +463,29 @@ QgsClipAlgorithm *QgsClipAlgorithm::clone() const return new QgsClipAlgorithm(); } -QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsClipAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !featureSource ) - return QVariantMap(); + mFeatureSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mFeatureSource ) + return false; - std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); - if ( !maskSource ) - return QVariantMap(); + mMaskSource.reset( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); + if ( !mMaskSource ) + return false; - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs() ) ); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mFeatureSource->fields(), QgsWkbTypes::multiType( mFeatureSource->wkbType() ), mFeatureSource->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + if ( !mSink ) + return false; + return true; +} + +bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ // first build up a list of clip geometries QList< QgsGeometry > clipGeoms; - QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs() ) ); + QgsFeatureIterator it = mMaskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( mFeatureSource->sourceCrs() ) ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -453,7 +494,7 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q } if ( clipGeoms.isEmpty() ) - return QVariantMap(); + return true; // are we clipping against a single feature? if so, we can show finer progress reports bool singleClipFeature = false; @@ -484,7 +525,7 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q break; } - QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) ); + QgsFeatureIterator inputIt = mFeatureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) ); QgsFeatureList inputFeatures; QgsFeature f; while ( inputIt.nextFeature( f ) ) @@ -539,8 +580,7 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q QgsFeature outputFeature; outputFeature.setGeometry( newGeometry ); outputFeature.setAttributes( inputFeature.attributes() ); - sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); - + mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); if ( singleClipFeature ) feedback->setProgress( current * step ); @@ -552,13 +592,19 @@ QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, Q feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() ); } } + mFeatureSource.reset(); + mMaskSource.reset(); + mSink.reset(); + return true; +} +QVariantMap QgsClipAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } - QgsTransformAlgorithm::QgsTransformAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -579,29 +625,41 @@ QgsTransformAlgorithm *QgsTransformAlgorithm::clone() const return new QgsTransformAlgorithm(); } -QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; + + mCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); - QgsCoordinateReferenceSystem targetCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), mSource->wkbType(), mCrs ) ); + if ( !mSink ) + return false; + + return true; +} + +QVariantMap QgsTransformAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), source->wkbType(), targetCrs ) ); - if ( !sink ) - return QVariantMap(); + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + return outputs; +} - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); +bool QgsTransformAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return true; QgsFeature f; QgsFeatureRequest req; // perform reprojection in the iterators... - req.setDestinationCrs( targetCrs ); + req.setDestinationCrs( mCrs ); - QgsFeatureIterator it = source->getFeatures( req ); + QgsFeatureIterator it = mSource->getFeatures( req ); double step = 100.0 / count; int current = 0; @@ -612,14 +670,12 @@ QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap ¶mete break; } - sink->addFeature( f, QgsFeatureSink::FastInsert ); + mSink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); - return outputs; + return true; } @@ -648,25 +704,29 @@ QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::clone() const return new QgsSubdivideAlgorithm(); } -QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - int maxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), - QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + mMaxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), + QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) ); + if ( !mSink ) + return false; - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); + return true; +} + +bool QgsSubdivideAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return true; QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; @@ -680,25 +740,30 @@ QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶mete QgsFeature out = f; if ( out.hasGeometry() ) { - out.setGeometry( f.geometry().subdivide( maxNodes ) ); + out.setGeometry( f.geometry().subdivide( mMaxNodes ) ); if ( !out.geometry() ) { QgsMessageLog::logMessage( QObject::tr( "Error calculating subdivision for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } } - sink->addFeature( out, QgsFeatureSink::FastInsert ); + mSink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } + mSource.reset(); + mSink.reset(); + return true; +} +QVariantMap QgsSubdivideAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } - QgsMultipartToSinglepartAlgorithm::QgsMultipartToSinglepartAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -719,26 +784,30 @@ QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::clone() co return new QgsMultipartToSinglepartAlgorithm(); } -QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsMultipartToSinglepartAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; + + QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( mSource->wkbType() ); - QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( source->wkbType() ); + mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), + sinkType, mSource->sourceCrs() ) ); + if ( !mSink ) + return false; - QString dest; - std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), - sinkType, source->sourceCrs() ) ); - if ( !sink ) - return QVariantMap(); + return true; +} - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); +bool QgsMultipartToSinglepartAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + long count = mSource->featureCount(); + if ( count == 0 ) + return true; QgsFeature f; - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); double step = 100.0 / count; int current = 0; @@ -758,30 +827,36 @@ QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantM Q_FOREACH ( const QgsGeometry &g, inputGeometry.asGeometryCollection() ) { out.setGeometry( g ); - sink->addFeature( out, QgsFeatureSink::FastInsert ); + mSink->addFeature( out, QgsFeatureSink::FastInsert ); } } else { - sink->addFeature( out, QgsFeatureSink::FastInsert ); + mSink->addFeature( out, QgsFeatureSink::FastInsert ); } } else { // feature with null geometry - sink->addFeature( out, QgsFeatureSink::FastInsert ); + mSink->addFeature( out, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); current++; } + mSource.reset(); + mSink.reset(); + + return true; +} +QVariantMap QgsMultipartToSinglepartAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } - QgsExtractByExpressionAlgorithm::QgsExtractByExpressionAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -806,47 +881,50 @@ QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::clone() const return new QgsExtractByExpressionAlgorithm(); } -QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsExtractByExpressionAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + mExpressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); - QString matchingSinkId; - std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(), - source->wkbType(), source->sourceCrs() ) ); - if ( !matchingSink ) - return QVariantMap(); + mMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mMatchingSinkId, mSource->fields(), + mSource->wkbType(), mSource->sourceCrs() ) ); + if ( !mMatchingSink ) + return false; - QString nonMatchingSinkId; - std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(), - source->wkbType(), source->sourceCrs() ) ); + mNonMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, mNonMatchingSinkId, mSource->fields(), + mSource->wkbType(), mSource->sourceCrs() ) ); + + mExpressionContext = createExpressionContext( parameters, context ); + return true; +} - QgsExpression expression( expressionString ); +bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + QgsExpression expression( mExpressionString ); if ( expression.hasParserError() ) { throw QgsProcessingException( expression.parserErrorString() ); } - QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); + long count = mSource->featureCount(); + if ( count == 0 ) + return true; double step = 100.0 / count; int current = 0; - if ( !nonMatchingSink ) + if ( !mNonMatchingSink ) { // not saving failing features - so only fetch good features QgsFeatureRequest req; - req.setFilterExpression( expressionString ); - req.setExpressionContext( expressionContext ); + req.setFilterExpression( mExpressionString ); + req.setExpressionContext( mExpressionContext ); - QgsFeatureIterator it = source->getFeatures( req ); + QgsFeatureIterator it = mSource->getFeatures( req ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -855,7 +933,7 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap break; } - matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; @@ -864,10 +942,10 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap else { // saving non-matching features, so we need EVERYTHING - expressionContext.setFields( source->fields() ); - expression.prepare( &expressionContext ); + mExpressionContext.setFields( mSource->fields() ); + expression.prepare( &mExpressionContext ); - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -876,14 +954,14 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap break; } - expressionContext.setFeature( f ); - if ( expression.evaluate( &expressionContext ).toBool() ) + mExpressionContext.setFeature( f ); + if ( expression.evaluate( &mExpressionContext ).toBool() ) { - matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } else { - nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mNonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); @@ -891,15 +969,22 @@ QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap } } + mSource.reset(); + mMatchingSink.reset(); + mNonMatchingSink.reset(); + return true; +} + +QVariantMap QgsExtractByExpressionAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); - if ( nonMatchingSink ) - outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), mMatchingSinkId ); + if ( !mNonMatchingSinkId.isEmpty() ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), mNonMatchingSinkId ); return outputs; } - QgsExtractByAttributeAlgorithm::QgsExtractByAttributeAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -937,34 +1022,38 @@ QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::clone() const return new QgsExtractByAttributeAlgorithm(); } -QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsExtractByAttributeAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) { - std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !source ) - return QVariantMap(); + mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !mSource ) + return false; - QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); - Operation op = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) ); - QString value = parameterAsString( parameters, QStringLiteral( "VALUE" ), context ); + mFieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); + mOp = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) ); + mValue = parameterAsString( parameters, QStringLiteral( "VALUE" ), context ); - QString matchingSinkId; - std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(), - source->wkbType(), source->sourceCrs() ) ); - if ( !matchingSink ) - return QVariantMap(); + mMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mMatchingSinkId, mSource->fields(), + mSource->wkbType(), mSource->sourceCrs() ) ); + if ( !mMatchingSink ) + return false; - QString nonMatchingSinkId; - std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(), - source->wkbType(), source->sourceCrs() ) ); + mNonMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, mNonMatchingSinkId, mSource->fields(), + mSource->wkbType(), mSource->sourceCrs() ) ); - int idx = source->fields().lookupField( fieldName ); - QVariant::Type fieldType = source->fields().at( idx ).type(); + mExpressionContext = createExpressionContext( parameters, context ); + return true; +} + +bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) +{ + int idx = mSource->fields().lookupField( mFieldName ); + QVariant::Type fieldType = mSource->fields().at( idx ).type(); - if ( fieldType != QVariant::String && ( op == BeginsWith || op == Contains || op == DoesNotContain ) ) + if ( fieldType != QVariant::String && ( mOp == BeginsWith || mOp == Contains || mOp == DoesNotContain ) ) { QString method; - switch ( op ) + switch ( mOp ) { case BeginsWith: method = QObject::tr( "begins with" ); @@ -983,10 +1072,10 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap throw QgsProcessingException( QObject::tr( "Operator '%1' can be used only with string fields." ).arg( method ) ); } - QString fieldRef = QgsExpression::quotedColumnRef( fieldName ); - QString quotedVal = QgsExpression::quotedValue( value ); + QString fieldRef = QgsExpression::quotedColumnRef( mFieldName ); + QString quotedVal = QgsExpression::quotedValue( mValue ); QString expr; - switch ( op ) + switch ( mOp ) { case Equals: expr = QStringLiteral( "%1 = %3" ).arg( fieldRef, quotedVal ); @@ -1007,10 +1096,10 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap expr = QStringLiteral( "%1 <= %3" ).arg( fieldRef, quotedVal ); break; case BeginsWith: - expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, value ); + expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, mValue ); break; case Contains: - expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, value ); + expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, mValue ); break; case IsNull: expr = QStringLiteral( "%1 IS NULL" ).arg( fieldRef ); @@ -1019,7 +1108,7 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap expr = QStringLiteral( "%1 IS NOT NULL" ).arg( fieldRef ); break; case DoesNotContain: - expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, value ); + expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, mValue ); break; } @@ -1029,23 +1118,21 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap throw QgsProcessingException( expression.parserErrorString() ); } - QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); - - long count = source->featureCount(); - if ( count <= 0 ) - return QVariantMap(); + long count = mSource->featureCount(); + if ( count == 0 ) + return true; double step = 100.0 / count; int current = 0; - if ( !nonMatchingSink ) + if ( !mNonMatchingSink ) { // not saving failing features - so only fetch good features QgsFeatureRequest req; req.setFilterExpression( expr ); - req.setExpressionContext( expressionContext ); + req.setExpressionContext( mExpressionContext ); - QgsFeatureIterator it = source->getFeatures( req ); + QgsFeatureIterator it = mSource->getFeatures( req ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -1054,7 +1141,7 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap break; } - matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; @@ -1063,10 +1150,10 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap else { // saving non-matching features, so we need EVERYTHING - expressionContext.setFields( source->fields() ); - expression.prepare( &expressionContext ); + mExpressionContext.setFields( mSource->fields() ); + expression.prepare( &mExpressionContext ); - QgsFeatureIterator it = source->getFeatures(); + QgsFeatureIterator it = mSource->getFeatures(); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -1075,14 +1162,14 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap break; } - expressionContext.setFeature( f ); - if ( expression.evaluate( &expressionContext ).toBool() ) + mExpressionContext.setFeature( f ); + if ( expression.evaluate( &mExpressionContext ).toBool() ) { - matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } else { - nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + mNonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); @@ -1090,12 +1177,20 @@ QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap } } + mSource.reset(); + mMatchingSink.reset(); + mNonMatchingSink.reset(); + return true; +} +QVariantMap QgsExtractByAttributeAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); - if ( nonMatchingSink ) - outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), mMatchingSinkId ); + if ( !mNonMatchingSinkId.isEmpty() ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), mNonMatchingSinkId ); return outputs; } + ///@endcond diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index edd85af96cea..d788b6762078 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -64,9 +64,16 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + private: + + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; }; /** @@ -88,9 +95,17 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; + QgsCoordinateReferenceSystem mCrs; }; /** @@ -112,9 +127,28 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; - + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; + bool mDissolve = false; + int mSegments = 8; + QgsGeometry::EndCapStyle mEndCapStyle = QgsGeometry::CapRound; + QgsGeometry::JoinStyle mJoinStyle = QgsGeometry::JoinStyleRound; + double mMiterLimit = 1; + double mBufferDistance = 1; + bool mDynamicBuffer = false; + QgsProperty mDynamicBufferProperty; + QVariantMap mDynamicParams; + double mDefaultBuffer; + const QgsProcessingParameterDefinition *mDistanceParamDef = nullptr; + QgsExpressionContext mExpContext; }; /** @@ -136,8 +170,17 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; + QStringList mFields; }; @@ -175,9 +218,22 @@ class QgsExtractByAttributeAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; - + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mMatchingSink; + QString mMatchingSinkId; + std::unique_ptr< QgsFeatureSink > mNonMatchingSink; + QString mNonMatchingSinkId; + QString mFieldName; + Operation mOp; + QString mValue; + QgsExpressionContext mExpressionContext; }; /** @@ -199,9 +255,20 @@ class QgsExtractByExpressionAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mMatchingSink; + QString mMatchingSinkId; + std::unique_ptr< QgsFeatureSink > mNonMatchingSink; + QString mNonMatchingSinkId; + QString mExpressionString; + QgsExpressionContext mExpressionContext; }; /** @@ -223,8 +290,18 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + + private: + + std::unique_ptr< QgsFeatureSource > mFeatureSource; + std::unique_ptr< QgsFeatureSource > mMaskSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; }; @@ -248,8 +325,17 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; + int mMaxNodes = 64; }; @@ -272,9 +358,16 @@ class QgsMultipartToSinglepartAlgorithm : public QgsProcessingAlgorithm protected: - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + + private: + std::unique_ptr< QgsFeatureSource > mSource; + std::unique_ptr< QgsFeatureSink > mSink; + QString mSinkId; }; ///@endcond PRIVATE diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 3d4efd795c97..b63b82040552 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -260,6 +260,11 @@ bool QgsProcessingAlgorithm::addOutput( QgsProcessingOutputDefinition *definitio return true; } +bool QgsProcessingAlgorithm::prepareAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) +{ + return true; +} + const QgsProcessingParameterDefinition *QgsProcessingAlgorithm::parameterDefinition( const QString &name ) const { Q_FOREACH ( const QgsProcessingParameterDefinition *def, mParameters ) @@ -316,22 +321,76 @@ bool QgsProcessingAlgorithm::hasHtmlOutputs() const QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok ) const { + std::unique_ptr< QgsProcessingAlgorithm > alg( clone() ); if ( ok ) *ok = false; - QVariantMap results; + bool res = alg->prepare( parameters, context, feedback ); + if ( !res ) + return QVariantMap(); + + res = alg->runPrepared( context, feedback ); + if ( !res ) + return QVariantMap(); + + if ( ok ) + *ok = true; + + return alg->postProcess( context, feedback ); +} + +bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_ASSERT_X( QApplication::instance()->thread() == QThread::currentThread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the main thread" ); + Q_ASSERT_X( !mHasPrepared, "QgsProcessingAlgorithm::prepare", "prepare() has already been called for the algorithm instance" ); + try + { + mHasPrepared = prepareAlgorithm( parameters, context, feedback ); + return mHasPrepared; + } + catch ( QgsProcessingException &e ) + { + QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); + feedback->reportError( e.what() ); + return false; + } +} + +bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", "prepare() was not called for the algorithm instance" ); + Q_ASSERT_X( !mHasExecuted, "QgsProcessingAlgorithm::runPrepared", "runPrepared() was already called for this algorithm instance" ); + + try + { + mHasExecuted = processAlgorithm( context, feedback ); + return mHasExecuted; + } + catch ( QgsProcessingException &e ) + { + QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); + feedback->reportError( e.what() ); + return false; + } +} + +QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +{ + Q_ASSERT_X( QApplication::instance()->thread() == QThread::currentThread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the main thread" ); + Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", "runPrepared() was not called for the algorithm instance" ); + Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" ); + + mHasPostProcessed = true; try { - results = processAlgorithm( parameters, context, feedback ); - if ( ok ) - *ok = true; + return postProcessAlgorithm( context, feedback ); } catch ( QgsProcessingException &e ) { QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); feedback->reportError( e.what() ); + return QVariantMap(); } - return results; } QString QgsProcessingAlgorithm::parameterAsString( const QVariantMap ¶meters, const QString &name, const QgsProcessingContext &context ) const diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index cec35e8117ad..4326b8411497 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -223,7 +223,9 @@ class CORE_EXPORT QgsProcessingAlgorithm bool hasHtmlOutputs() const; /** - * Executes the algorithm using the specified \a parameters. + * Executes the algorithm using the specified \a parameters. This method internally + * creates a copy of the algorithm before running it, so it is safe to call + * on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. * * The \a context argument specifies the context in which the algorithm is being run. * @@ -233,10 +235,48 @@ class CORE_EXPORT QgsProcessingAlgorithm * * \returns A map of algorithm outputs. These may be output layer references, or calculated * values such as statistical calculations. + * + * \note this method can only be called from the main thread. Use prepare(), runPrepared() and postProcess() + * if you need to run algorithms from a background thread, or use the QgsProcessingAlgRunnerTask class. */ QVariantMap run( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok SIP_OUT = nullptr ) const; + /** + * Prepares the algorithm for execution. This must be run in the main thread, and allows the algorithm + * to pre-evaluate input parameters in a thread-safe manner. This must be called before + * calling runPrepared() (which is safe to do in any thread). + * \see runPrepared() + * \see postProcess() + * \note This method modifies the algorithm instance, so it is not safe to call + * on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + * of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. + */ + bool prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + + /** + * Runs the algorithm, which has been prepared by an earlier call to prepare(). + * This method is safe to call from any thread. Returns true if the algorithm was successfully executed. + * After runPrepared() has finished, the postProcess() method should be called from the main thread + * to allow the algorithm to perform any required cleanup tasks and return its final result. + * \see prepare() + * \see postProcess() + * \note This method modifies the algorithm instance, so it is not safe to call + * on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + * of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. + */ + bool runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + + /** + * Should be called in the main thread following the completion of runPrepared(). This method + * allows the algorithm to perform any required cleanup tasks. The returned variant map + * includes the results evaluated by the algorithm. + * \note This method modifies the algorithm instance, so it is not safe to call + * on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy + * of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. + */ + QVariantMap postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + /** * If an algorithm subclass implements a custom parameters widget, a copy of this widget * should be constructed and returned by this method. @@ -299,6 +339,23 @@ class CORE_EXPORT QgsProcessingAlgorithm */ bool addOutput( QgsProcessingOutputDefinition *outputDefinition SIP_TRANSFER ); + /** + * Prepares the algorithm to run using the specified \a parameters. Algorithms should implement + * their logic for evaluating parameter values here. The evaluated parameter results should + * be stored in member variables ready for a call to processAlgorithm(). + * + * The \a context argument specifies the context in which the algorithm is being run. + * + * Algorithm preparation progress should be reported using the supplied \a feedback object. Additionally, + * well-behaved algorithms should periodically check \a feedback to determine whether the + * algorithm should be canceled and exited early. + * + * \returns true if preparation was successful. + * \see processAlgorithm() + * \see postProcessAlgorithm() + */ + virtual bool prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) SIP_VIRTUALERRORHANDLER( processing_exception_handler ); + /** * Runs the algorithm using the specified \a parameters. Algorithms should implement * their custom processing logic here. @@ -311,9 +368,28 @@ class CORE_EXPORT QgsProcessingAlgorithm * * \returns A map of algorithm outputs. These may be output layer references, or calculated * values such as statistical calculations. + * \see prepareAlgorithm() + * \see postProcessAlgorithm() + */ + virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); + + /** + * Allows the algorithm to perform any required cleanup tasks. The returned variant map + * includes the results evaluated by the algorithm. These may be output layer references, or calculated + * values such as statistical calculations. + * + * The \a context argument specifies the context in which the algorithm was run. + * + * Postprocess progress should be reported using the supplied \a feedback object. Additionally, + * well-behaved algorithms should periodically check \a feedback to determine whether the + * post processing should be canceled and exited early. + * + * \returns A map of algorithm outputs. These may be output layer references, or calculated + * values such as statistical calculations. + * \see prepareAlgorithm() + * \see processAlgorithm() */ - virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); + virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); /** * Evaluates the parameter with matching \a name to a static string value. @@ -460,6 +536,9 @@ class CORE_EXPORT QgsProcessingAlgorithm QgsProcessingProvider *mProvider = nullptr; QgsProcessingParameterDefinitions mParameters; QgsProcessingOutputDefinitions mOutputs; + bool mHasPrepared = false; + bool mHasExecuted = false; + bool mHasPostProcessed = false; // friend class to access setProvider() - we do not want this public! friend class QgsProcessingProvider; diff --git a/src/core/processing/qgsprocessingalgrunnertask.h b/src/core/processing/qgsprocessingalgrunnertask.h index 4866b2abe0d2..460f2b433bad 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.h +++ b/src/core/processing/qgsprocessingalgrunnertask.h @@ -61,7 +61,7 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask QgsProcessingContext &mContext; QgsProcessingFeedback *mFeedback = nullptr; std::unique_ptr< QgsProcessingFeedback > mOwnedFeedback; - std::unique_ptr< const QgsProcessingAlgorithm > mAlgorithm; + std::unique_ptr< QgsProcessingAlgorithm > mAlgorithm; }; diff --git a/src/core/processing/qgsprocessingmodelalgorithm.cpp b/src/core/processing/qgsprocessingmodelalgorithm.cpp index 2677a3b805bd..30ae04f13e6d 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/qgsprocessingmodelalgorithm.cpp @@ -472,7 +472,7 @@ bool QgsProcessingModelAlgorithm::childOutputIsRequired( const QString &childId, return false; } -QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const +bool QgsProcessingModelAlgorithm::processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { QSet< QString > toExecute; QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin(); @@ -515,7 +515,7 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa const ChildAlgorithm &child = mChildAlgorithms[ childId ]; - QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults ); + QVariantMap childParams = parametersForChildAlgorithm( child, mInputParameters, childResults ); feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) ); //feedback->pushDebugInfo( "Parameters: " + ', '.join( [str( p ).strip() + // '=' + str( p.value ) for p in alg.algorithm.parameters] ) ) @@ -524,7 +524,9 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa childTime.start(); bool ok = false; - QVariantMap results = child.algorithm()->run( childParams, context, feedback, &ok ); + std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->clone() ); + QVariantMap results = childAlg->run( childParams, context, feedback, &ok ); + childAlg.reset( nullptr ); if ( !ok ) { QString error = QObject::tr( "Error encountered while running %1" ).arg( child.description() ); @@ -548,7 +550,13 @@ QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap &pa } feedback->pushDebugInfo( QObject::tr( "Model processed OK. Executed %1 algorithms total in %2 s." ).arg( executed.count() ).arg( totalTime.elapsed() / 1000.0 ) ); - return finalResults; + mResults = finalResults; + return true; +} + +QVariantMap QgsProcessingModelAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ + return mResults; } QString QgsProcessingModelAlgorithm::sourceFilePath() const @@ -647,6 +655,12 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const return lines.join( '\n' ); } +bool QgsProcessingModelAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &, QgsProcessingFeedback * ) +{ + mInputParameters = parameters; + return true; +} + QVariantMap QgsProcessingModelAlgorithm::helpContent() const { return mHelpContent; diff --git a/src/core/processing/qgsprocessingmodelalgorithm.h b/src/core/processing/qgsprocessingmodelalgorithm.h index 580d6589b677..9ba43a6cfbfb 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.h +++ b/src/core/processing/qgsprocessingmodelalgorithm.h @@ -826,8 +826,10 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm protected: - QVariantMap processAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) const override; + bool prepareAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; private: @@ -844,6 +846,10 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm //! Model source file QString mSourceFile; + QVariantMap mInputParameters; + + QVariantMap mResults; + void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet &depends ) const; void dependentChildAlgorithmsRecursive( const QString &childId, QSet &depends ) const; diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index eeefcd23decd..8a88add3cf69 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -42,8 +42,9 @@ class DummyAlgorithm : public QgsProcessingAlgorithm QString name() const override { return mName; } QString displayName() const override { return mName; } - virtual QVariantMap processAlgorithm( const QVariantMap &, - QgsProcessingContext &, QgsProcessingFeedback * ) const override { return QVariantMap(); } + bool processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) override { return true; } + bool prepareAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return true; } + QVariantMap postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); } virtual Flags flags() const override { return mFlags; } DummyAlgorithm *clone() const override { return new DummyAlgorithm( name() ); } From cd7776ca1c105354ecba146c2366607e5912a48d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 28 Jun 2017 19:55:08 +1000 Subject: [PATCH 08/49] Upgrade ported python algs to be thread ready --- .../processing/algs/qgis/AddTableField.py | 43 ++++--- python/plugins/processing/algs/qgis/Aspect.py | 34 ++++-- .../algs/qgis/AutoincrementalField.py | 26 ++-- .../processing/algs/qgis/BasicStatistics.py | 48 +++++--- .../plugins/processing/algs/qgis/Boundary.py | 27 +++-- .../processing/algs/qgis/BoundingBox.py | 24 ++-- .../processing/algs/qgis/CheckValidity.py | 102 +++++++++------- .../algs/qgis/CreateAttributeIndex.py | 26 ++-- .../processing/algs/qgis/DeleteColumn.py | 42 ++++--- .../processing/algs/qgis/DeleteHoles.py | 33 +++-- .../processing/algs/qgis/DensifyGeometries.py | 29 +++-- .../algs/qgis/DensifyGeometriesInterval.py | 29 +++-- .../processing/algs/qgis/DropGeometry.py | 35 ++++-- .../processing/algs/qgis/ExtentFromLayer.py | 27 +++-- .../processing/algs/qgis/FixGeometry.py | 26 ++-- .../processing/algs/qgis/GridPolygon.py | 67 ++++++---- .../processing/algs/qgis/ImportIntoPostGIS.py | 114 ++++++++++-------- .../algs/qgis/ImportIntoSpatialite.py | 106 +++++++++------- python/plugins/processing/algs/qgis/Merge.py | 64 ++++++---- .../processing/algs/qgis/PostGISExecuteSQL.py | 18 ++- .../processing/algs/qgis/RandomExtract.py | 51 ++++---- .../algs/qgis/RandomExtractWithinSubsets.py | 50 +++++--- .../processing/algs/qgis/RegularPoints.py | 62 ++++++---- .../algs/qgis/SaveSelectedFeatures.py | 26 ++-- .../processing/algs/qgis/SelectByAttribute.py | 55 +++++---- .../algs/qgis/SelectByExpression.py | 32 +++-- .../algs/qgis/SimplifyGeometries.py | 41 ++++--- python/plugins/processing/algs/qgis/Smooth.py | 35 ++++-- .../algs/qgis/SpatialiteExecuteSQL.py | 18 ++- .../algs/qgis/SymmetricalDifference.py | 47 +++++--- .../processing/algs/qgis/VectorSplit.py | 56 +++++---- .../processing/algs/qgis/ZonalStatistics.py | 40 +++--- .../processing/script/ScriptAlgorithm.py | 41 ++++--- .../tests/testdata/qgis_algorithm_tests.yaml | 22 ++-- src/core/processing/qgsnativealgorithms.cpp | 2 +- tests/src/core/testqgsprocessing.cpp | 14 +-- 36 files changed, 924 insertions(+), 588 deletions(-) diff --git a/python/plugins/processing/algs/qgis/AddTableField.py b/python/plugins/processing/algs/qgis/AddTableField.py index a92b37945931..c5048ca863ba 100644 --- a/python/plugins/processing/algs/qgis/AddTableField.py +++ b/python/plugins/processing/algs/qgis/AddTableField.py @@ -74,28 +74,38 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr('Added'))) + self.source = None + self.fieldType = None + self.fieldLength = None + self.fieldName = None + self.fieldPrecision = None + self.sink = None + self.dest_id = None + def name(self): return 'addfieldtoattributestable' def displayName(self): return self.tr('Add field to attributes table') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) - fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) - fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) - fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) + self.fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) + self.fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) + self.fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) + self.fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) - fields = source.fields() - fields.append(QgsField(fieldName, self.TYPES[fieldType], '', - fieldLength, fieldPrecision)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - fields, source.wkbType(), source.sourceCrs()) + fields = self.source.fields() + fields.append(QgsField(self.fieldName, self.TYPES[self.fieldType], '', + self.fieldLength, self.fieldPrecision)) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + fields, self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -106,7 +116,10 @@ def processAlgorithm(self, parameters, context, feedback): attributes.append(None) output_feature.setAttributes(attributes) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return {self.OUTPUT_LAYER: dest_id} + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT_LAYER: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/Aspect.py b/python/plugins/processing/algs/qgis/Aspect.py index ff2a83dad324..6167c6965308 100644 --- a/python/plugins/processing/algs/qgis/Aspect.py +++ b/python/plugins/processing/algs/qgis/Aspect.py @@ -47,7 +47,7 @@ class Aspect(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' + INPUT = 'INPUT' Z_FACTOR = 'Z_FACTOR' OUTPUT_LAYER = 'OUTPUT_LAYER' @@ -60,13 +60,18 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_LAYER, + self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT, self.tr('Elevation layer'))) self.addParameter(QgsProcessingParameterNumber(self.Z_FACTOR, self.tr('Z factor'), QgsProcessingParameterNumber.Double, 1, False, 1, 999999.99)) - self.addParameter(QgsProcessingParameterRasterOutput(self.OUTPUT_LAYER, self.tr('Aspect'))) - self.addOutput(QgsProcessingOutputRasterLayer(self.OUTPUT_LAYER, self.tr('Aspect'))) + self.addParameter(QgsProcessingParameterRasterOutput(self.OUTPUT, self.tr('Aspect'))) + self.addOutput(QgsProcessingOutputRasterLayer(self.OUTPUT, self.tr('Aspect'))) + + self.inputFile = None + self.outputFile = None + self.outputFormat = None + self.zFactor = None def name(self): return 'aspect' @@ -74,16 +79,19 @@ def name(self): def displayName(self): return self.tr('Aspect') - def processAlgorithm(self, parameters, context, feedback): - inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT_LAYER, context)) - zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT, context)) + self.zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context) - outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT_LAYER, context) + self.outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT, context) - outputFormat = raster.formatShortNameFromFileName(outputFile) + self.outputFormat = raster.formatShortNameFromFileName(self.outputFile) + return True - aspect = QgsAspectFilter(inputFile, outputFile, outputFormat) - aspect.setZFactor(zFactor) - aspect.processRaster(feedback) + def processAlgorithm(self, context, feedback): + aspect = QgsAspectFilter(self.inputFile, self.outputFile, self.outputFormat) + aspect.setZFactor(self.zFactor) + return aspect.processRaster(feedback) == 0 - return {self.OUTPUT_LAYER: outputFile} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.outputFile} diff --git a/python/plugins/processing/algs/qgis/AutoincrementalField.py b/python/plugins/processing/algs/qgis/AutoincrementalField.py index 92dc7f8736a1..1100d3ae2d19 100644 --- a/python/plugins/processing/algs/qgis/AutoincrementalField.py +++ b/python/plugins/processing/algs/qgis/AutoincrementalField.py @@ -51,6 +51,10 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Incremented'))) + self.source = None + self.sink = None + self.dest_id = None + def group(self): return self.tr('Vector table tools') @@ -60,16 +64,18 @@ def name(self): def displayName(self): return self.tr('Add autoincremental field') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fields = source.fields() + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + fields = self.source.fields() fields.append(QgsField('AUTO', QVariant.Int)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break @@ -79,7 +85,9 @@ def processAlgorithm(self, parameters, context, feedback): attributes.append(current) output_feature.setAttributes(attributes) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/BasicStatistics.py b/python/plugins/processing/algs/qgis/BasicStatistics.py index 0c1ce80992cb..7c4584d42255 100644 --- a/python/plugins/processing/algs/qgis/BasicStatistics.py +++ b/python/plugins/processing/algs/qgis/BasicStatistics.py @@ -119,42 +119,52 @@ def __init__(self): self.addOutput(QgsProcessingOutputNumber(self.THIRDQUARTILE, self.tr('Third quartile'))) self.addOutput(QgsProcessingOutputNumber(self.IQR, self.tr('Interquartile Range (IQR)'))) + self.source = None + self.field = None + self.field_name = None + self.output_file = None + self.results = {} + def name(self): return 'basicstatisticsforfields' def displayName(self): return self.tr('Basic statistics for fields') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) - field = source.fields().at(source.fields().lookupField(field_name)) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + self.field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) + self.field = self.source.fields().at(self.source.fields().lookupField(self.field_name)) - output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) + self.output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) + return True - request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([field_name], source.fields()) - features = source.getFeatures(request) - count = source.featureCount() + def processAlgorithm(self, context, feedback): + request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([self.field_name], self.source.fields()) + features = self.source.getFeatures(request) + count = self.source.featureCount() data = [] - data.append(self.tr('Analyzed field: {}').format(field_name)) - results = {} + data.append(self.tr('Analyzed field: {}').format(self.field_name)) - if field.isNumeric(): - d, results = self.calcNumericStats(features, feedback, field, count) + if self.field.isNumeric(): + d, self.results = self.calcNumericStats(features, feedback, self.field, count) data.extend(d) - elif field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): - d, results = self.calcDateTimeStats(features, feedback, field, count) + elif self.field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): + d, self.results = self.calcDateTimeStats(features, feedback, self.field, count) data.extend(d) else: - d, results = self.calcStringStats(features, feedback, field, count) + d, self.results = self.calcStringStats(features, feedback, self.field, count) data.extend(d) - if output_file: - self.createHTML(output_file, data) - results[self.OUTPUT_HTML_FILE] = output_file + if self.output_file: + self.createHTML(self.output_file, data) + self.results[self.OUTPUT_HTML_FILE] = self.output_file + + return True - return results + def postProcessAlgorithm(self, context, feedback): + return self.results def calcNumericStats(self, features, feedback, field, count): total = 100.0 / count if count else 0 diff --git a/python/plugins/processing/algs/qgis/Boundary.py b/python/plugins/processing/algs/qgis/Boundary.py index 8c883ed2bd37..ac60ef439284 100644 --- a/python/plugins/processing/algs/qgis/Boundary.py +++ b/python/plugins/processing/algs/qgis/Boundary.py @@ -58,6 +58,10 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Boundary'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Boundaries"))) + self.source = None + self.sink = None + self.dest_id = None + def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png')) @@ -70,10 +74,11 @@ def name(self): def displayName(self): return self.tr('Boundary') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - input_wkb = source.wkbType() + input_wkb = self.source.wkbType() + output_wkb = None if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: @@ -83,11 +88,13 @@ def processAlgorithm(self, parameters, context, feedback): if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - source.fields(), output_wkb, source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + self.source.fields(), output_wkb, self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -102,7 +109,9 @@ def processAlgorithm(self, parameters, context, feedback): output_feature.setGeometry(output_geometry) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT_LAYER: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT_LAYER: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/BoundingBox.py b/python/plugins/processing/algs/qgis/BoundingBox.py index 502a2108c353..5d2f02d8aeef 100644 --- a/python/plugins/processing/algs/qgis/BoundingBox.py +++ b/python/plugins/processing/algs/qgis/BoundingBox.py @@ -66,20 +66,26 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Bounds'), QgsProcessingParameterDefinition.TypeVectorPolygon)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Bounds"))) + self.source = None + self.sink = None + self.dest_id = None + def name(self): return 'boundingboxes' def displayName(self): return self.tr('Bounding boxes') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - source.fields(), QgsWkbTypes.Polygon, source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + self.source.fields(), QgsWkbTypes.Polygon, self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -94,7 +100,9 @@ def processAlgorithm(self, parameters, context, feedback): output_feature.setGeometry(output_geometry) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT_LAYER: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT_LAYER: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/CheckValidity.py b/python/plugins/processing/algs/qgis/CheckValidity.py index fe5a6a9a7aa4..05639eabe521 100644 --- a/python/plugins/processing/algs/qgis/CheckValidity.py +++ b/python/plugins/processing/algs/qgis/CheckValidity.py @@ -93,46 +93,60 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.ERROR_OUTPUT, self.tr('Error output'))) self.addOutput(QgsProcessingOutputNumber(self.ERROR_COUNT, self.tr('Count of errors'))) + self.method = None + self.source = None + self.valid_output_sink = None + self.valid_output_dest_id = None + self.valid_count = 0 + self.invalid_output_sink = None + self.invalid_output_dest_id = None + self.invalid_count = 0 + self.error_output_sink = None + self.error_output_dest_id = None + self.error_count = 0 + def name(self): return 'checkvalidity' def displayName(self): return self.tr('Check validity') - def processAlgorithm(self, parameters, context, feedback): + def prepareAlgorithm(self, parameters, context, feedback): method_param = self.parameterAsEnum(parameters, self.METHOD, context) if method_param == 0: settings = QgsSettings() - method = int(settings.value(settings_method_key, 0)) - 1 - if method < 0: - method = 0 + self.method = int(settings.value(settings_method_key, 0)) - 1 + if self.method < 0: + self.method = 0 else: - method = method_param - 1 - - results = self.doCheck(method, parameters, context, feedback) - return results + self.method = method_param - 1 - def doCheck(self, method, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - (valid_output_sink, valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) - valid_count = 0 + (self.valid_output_sink, self.valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, + context, + self.source.fields(), + self.source.wkbType(), + self.source.sourceCrs()) - invalid_fields = source.fields() + invalid_fields = self.source.fields() invalid_fields.append(QgsField('_errors', QVariant.String, 'string', 255)) - (invalid_output_sink, invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, context, - invalid_fields, source.wkbType(), source.sourceCrs()) - invalid_count = 0 + (self.invalid_output_sink, self.invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, + context, + invalid_fields, + self.source.wkbType(), + self.source.sourceCrs()) error_fields = QgsFields() error_fields.append(QgsField('message', QVariant.String, 'string', 255)) - (error_output_sink, error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, - error_fields, QgsWkbTypes.Point, source.sourceCrs()) - error_count = 0 - - features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) - total = 100.0 / source.featureCount() if source.featureCount() else 0 + (self.error_output_sink, self.error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, + error_fields, QgsWkbTypes.Point, + self.source.sourceCrs()) + return True + + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break @@ -141,10 +155,10 @@ def doCheck(self, method, parameters, context, feedback): valid = True if not geom.isNull() and not geom.isEmpty(): - errors = list(geom.validateGeometry(method)) + errors = list(geom.validateGeometry(self.method)) if errors: # QGIS method return a summary at the end - if method == 1: + if self.method == 1: errors.pop() valid = False reasons = [] @@ -153,9 +167,9 @@ def doCheck(self, method, parameters, context, feedback): error_geom = QgsGeometry.fromPoint(error.where()) errFeat.setGeometry(error_geom) errFeat.setAttributes([error.what()]) - if error_output_sink: - error_output_sink.addFeature(errFeat, QgsFeatureSink.FastInsert) - error_count += 1 + if self.error_output_sink: + self.error_output_sink.addFeature(errFeat, QgsFeatureSink.FastInsert) + self.error_count += 1 reasons.append(error.what()) @@ -169,26 +183,28 @@ def doCheck(self, method, parameters, context, feedback): outFeat.setAttributes(attrs) if valid: - if valid_output_sink: - valid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - valid_count += 1 + if self.valid_output_sink: + self.valid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + self.valid_count += 1 else: - if invalid_output_sink: - invalid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - invalid_count += 1 + if self.invalid_output_sink: + self.invalid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + self.invalid_count += 1 feedback.setProgress(int(current * total)) + return True + def postProcessAlgorithm(self, context, feedback): results = { - self.VALID_COUNT: valid_count, - self.INVALID_COUNT: invalid_count, - self.ERROR_COUNT: error_count + self.VALID_COUNT: self.valid_count, + self.INVALID_COUNT: self.invalid_count, + self.ERROR_COUNT: self.error_count } - if valid_output_sink: - results[self.VALID_OUTPUT] = valid_output_dest_id - if invalid_output_sink: - results[self.INVALID_OUTPUT] = invalid_output_dest_id - if error_output_sink: - results[self.ERROR_OUTPUT] = error_output_dest_id + if self.valid_output_sink: + results[self.VALID_OUTPUT] = self.valid_output_dest_id + if self.invalid_output_sink: + results[self.INVALID_OUTPUT] = self.invalid_output_dest_id + if self.error_output_sink: + results[self.ERROR_OUTPUT] = self.error_output_dest_id return results diff --git a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py index 758976b228d4..58e47d743cd9 100644 --- a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py +++ b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py @@ -53,27 +53,35 @@ def __init__(self): self.tr('Attribute to index'), None, self.INPUT)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Indexed layer'))) + self.layer = None + self.field = None + def name(self): return 'createattributeindex' def displayName(self): return self.tr('Create attribute index') - def processAlgorithm(self, parameters, context, feedback): - layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - field = self.parameterAsString(parameters, self.FIELD, context) - provider = layer.dataProvider() + def prepareAlgorithm(self, parameters, context, feedback): + self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + self.field = self.parameterAsString(parameters, self.FIELD, context) + return True + + def processAlgorithm(self, context, feedback): + provider = self.layer.dataProvider() - field_index = layer.fields().lookupField(field) - if field_index < 0 or layer.fields().fieldOrigin(field_index) != QgsFields.OriginProvider: - feedback.pushInfo(self.tr('Can not create attribute index on "{}"').format(field)) + field_index = self.layer.fields().lookupField(self.field) + if field_index < 0 or self.layer.fields().fieldOrigin(field_index) != QgsFields.OriginProvider: + feedback.pushInfo(self.tr('Can not create attribute index on "{}"').format(self.field)) else: - provider_index = layer.fields().fieldOriginIndex(field_index) + provider_index = self.layer.fields().fieldOriginIndex(field_index) if provider.capabilities() & QgsVectorDataProvider.CreateAttributeIndex: if not provider.createAttributeIndex(provider_index): feedback.pushInfo(self.tr('Could not create attribute index')) else: feedback.pushInfo(self.tr("Layer's data provider does not support " "creating attribute indexes")) + return True - return {self.OUTPUT: layer.id()} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.layer.id()} diff --git a/python/plugins/processing/algs/qgis/DeleteColumn.py b/python/plugins/processing/algs/qgis/DeleteColumn.py index 95f569871e32..d5a15cec9e14 100644 --- a/python/plugins/processing/algs/qgis/DeleteColumn.py +++ b/python/plugins/processing/algs/qgis/DeleteColumn.py @@ -58,46 +58,56 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Output layer'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Output layer"))) + self.source = None + self.fields_to_delete = None + self.sink = None + self.dest_id = None + self.field_indices = [] + def name(self): return 'deletecolumn' def displayName(self): return self.tr('Drop field(s)') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) - fields = source.fields() - field_indices = [] + fields = self.source.fields() # loop through twice - first we need to build up a list of original attribute indices - for f in fields_to_delete: + for f in self.fields_to_delete: index = fields.lookupField(f) - field_indices.append(index) + self.field_indices.append(index) # important - make sure we remove from the end so we aren't changing used indices as we go - field_indices.sort(reverse=True) + self.field_indices.sort(reverse=True) # this second time we make a cleaned version of the fields - for index in field_indices: + for index in self.field_indices: fields.remove(index) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break attributes = f.attributes() - for index in field_indices: + for index in self.field_indices: del attributes[index] f.setAttributes(attributes) - sink.addFeature(f, QgsFeatureSink.FastInsert) + self.sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return {self.OUTPUT: dest_id} + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/DeleteHoles.py b/python/plugins/processing/algs/qgis/DeleteHoles.py index b5baf2d254ba..e215477d6c5c 100644 --- a/python/plugins/processing/algs/qgis/DeleteHoles.py +++ b/python/plugins/processing/algs/qgis/DeleteHoles.py @@ -58,31 +58,40 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.source = None + self.min_area = None + self.sink = None + self.dest_id = None + def name(self): return 'deleteholes' def displayName(self): return self.tr('Delete holes') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) - if min_area == 0.0: - min_area = -1.0 + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) + if self.min_area == 0.0: + self.min_area = -1.0 - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if f.hasGeometry(): - f.setGeometry(f.geometry().removeInteriorRings(min_area)) - sink.addFeature(f, QgsFeatureSink.FastInsert) + f.setGeometry(f.geometry().removeInteriorRings(self.min_area)) + self.sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometries.py b/python/plugins/processing/algs/qgis/DensifyGeometries.py index 591b79b3163b..e5b2f41b5e39 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometries.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometries.py @@ -63,21 +63,28 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) + self.source = None + self.sink = None + self.vertices = None + self.dest_id = None + def name(self): return 'densifygeometries' def displayName(self): return self.tr('Densify geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - vertices = self.parameterAsInt(parameters, self.VERTICES, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.vertices = self.parameterAsInt(parameters, self.VERTICES, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): @@ -85,9 +92,11 @@ def processAlgorithm(self, parameters, context, feedback): feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByCount(vertices) + new_geometry = feature.geometry().densifyByCount(self.vertices) feature.setGeometry(new_geometry) - sink.addFeature(feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py index 1618fa0fe87b..610735a7116b 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py @@ -61,31 +61,40 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) + self.source = None + self.interval = None + self.sink = None + self.dest_id = None + def name(self): return 'densifygeometriesgivenaninterval' def displayName(self): return self.tr('Densify geometries given an interval') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - interval = self.parameterAsDouble(parameters, self.INTERVAL, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.interval = self.parameterAsDouble(parameters, self.INTERVAL, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByDistance(float(interval)) + new_geometry = feature.geometry().densifyByDistance(float(self.interval)) feature.setGeometry(new_geometry) - sink.addFeature(feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/DropGeometry.py b/python/plugins/processing/algs/qgis/DropGeometry.py index b3e79c0060ac..159c1994506f 100644 --- a/python/plugins/processing/algs/qgis/DropGeometry.py +++ b/python/plugins/processing/algs/qgis/DropGeometry.py @@ -39,8 +39,8 @@ class DropGeometry(QgisAlgorithm): - INPUT_LAYER = 'INPUT_LAYER' - OUTPUT_TABLE = 'OUTPUT_TABLE' + INPUT = 'INPUT' + OUTPUT = 'OUTPUT' def tags(self): return self.tr('remove,drop,delete,geometry,objects').split(',') @@ -51,9 +51,13 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT_LAYER, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint, QgsProcessingParameterDefinition.TypeVectorLine, QgsProcessingParameterDefinition.TypeVectorPolygon])) - self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_TABLE, self.tr('Dropped geometry'))) - self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_TABLE, self.tr("Dropped geometry"))) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint, QgsProcessingParameterDefinition.TypeVectorLine, QgsProcessingParameterDefinition.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Dropped geometry'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Dropped geometry"))) + + self.source = None + self.sink = None + self.dest_id = None def name(self): return 'dropgeometries' @@ -61,21 +65,26 @@ def name(self): def displayName(self): return self.tr('Drop geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_TABLE, context, - source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) + return True + def processAlgorithm(self, context, feedback): request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = source.getFeatures(request) - total = 100.0 / source.featureCount() if source.featureCount() else 0 + features = self.source.getFeatures(request) + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break input_feature.clearGeometry() - sink.addFeature(input_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(input_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return {self.OUTPUT_TABLE: dest_id} + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/ExtentFromLayer.py b/python/plugins/processing/algs/qgis/ExtentFromLayer.py index e3f2e00adfcb..73d0eed138cc 100644 --- a/python/plugins/processing/algs/qgis/ExtentFromLayer.py +++ b/python/plugins/processing/algs/qgis/ExtentFromLayer.py @@ -79,15 +79,20 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extent'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Extent"), QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.source = None + self.byFeature = None + self.sink = None + self.dest_id = None + def name(self): return 'polygonfromlayerextent' def displayName(self): return self.tr('Polygon from layer extent') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + self.byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) fields = QgsFields() fields.append(QgsField('MINX', QVariant.Double)) @@ -101,15 +106,19 @@ def processAlgorithm(self, parameters, context, feedback): fields.append(QgsField('HEIGHT', QVariant.Double)) fields.append(QgsField('WIDTH', QVariant.Double)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, self.source.sourceCrs()) + return True - if byFeature: - self.featureExtent(source, context, sink, feedback) + def processAlgorithm(self, context, feedback): + if self.byFeature: + self.featureExtent(self.source, context, self.sink, feedback) else: - self.layerExtent(source, sink, feedback) + self.layerExtent(self.source, self.sink, feedback) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} def layerExtent(self, source, sink, feedback): rect = source.sourceExtent() diff --git a/python/plugins/processing/algs/qgis/FixGeometry.py b/python/plugins/processing/algs/qgis/FixGeometry.py index e06fe5943c99..4dd86b7f0a0e 100644 --- a/python/plugins/processing/algs/qgis/FixGeometry.py +++ b/python/plugins/processing/algs/qgis/FixGeometry.py @@ -55,20 +55,26 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Fixed geometries'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Fixed geometries"))) + self.source = None + self.sink = None + self.dest_id = None + def name(self): return 'fixgeometries' def displayName(self): return self.tr('Fix geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), QgsWkbTypes.multiType(self.source.wkbType()), self.source.sourceCrs()) + return True - features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, inputFeature in enumerate(features): if feedback.isCanceled(): break @@ -86,7 +92,7 @@ def processAlgorithm(self, parameters, context, feedback): try: g.convertToMultiType() outputFeature.setGeometry(QgsGeometry(g)) - sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) + self.sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) except: pass feedback.setProgress(int(current * total)) @@ -95,7 +101,9 @@ def processAlgorithm(self, parameters, context, feedback): outputGeometry.convertToMultiType() outputFeature.setGeometry(outputGeometry) - sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) + self.sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/GridPolygon.py b/python/plugins/processing/algs/qgis/GridPolygon.py index e450a48be624..f2bf05a59eff 100644 --- a/python/plugins/processing/algs/qgis/GridPolygon.py +++ b/python/plugins/processing/algs/qgis/GridPolygon.py @@ -102,41 +102,52 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Grid'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Grid'), QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.idx = None + self.hSpacing = None + self.vSpacing = None + self.hOverlay = None + self.vOverlay = None + self.width = None + self.height = None + self.originX = None + self.originY = None + self.sink = None + self.dest_id = None + def name(self): return 'creategridpolygon' def displayName(self): return self.tr('Create grid (polygon)') - def processAlgorithm(self, parameters, context, feedback): - idx = self.parameterAsEnum(parameters, self.TYPE, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.idx = self.parameterAsEnum(parameters, self.TYPE, context) - hSpacing = self.parameterAsDouble(parameters, self.HSPACING, context) - vSpacing = self.parameterAsDouble(parameters, self.VSPACING, context) - hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context) - vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context) + self.hSpacing = self.parameterAsDouble(parameters, self.HSPACING, context) + self.vSpacing = self.parameterAsDouble(parameters, self.VSPACING, context) + self.hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context) + self.vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context) bbox = self.parameterAsExtent(parameters, self.EXTENT, context) crs = self.parameterAsCrs(parameters, self.CRS, context) + self.width = bbox.width() + self.height = bbox.height() + self.originX = bbox.xMinimum() + self.originY = bbox.yMaximum() - width = bbox.width() - height = bbox.height() - originX = bbox.xMinimum() - originY = bbox.yMaximum() - - if hSpacing <= 0 or vSpacing <= 0: + if self.hSpacing <= 0 or self.vSpacing <= 0: raise GeoAlgorithmExecutionException( - self.tr('Invalid grid spacing: {0}/{1}').format(hSpacing, vSpacing)) + self.tr('Invalid grid spacing: {0}/{1}').format(self.hSpacing, self.vSpacing)) - if width < hSpacing: + if self.width < self.hSpacing: raise GeoAlgorithmExecutionException( self.tr('Horizontal spacing is too small for the covered area')) - if hSpacing <= hOverlay or vSpacing <= vOverlay: + if self.hSpacing <= self.hOverlay or self.vSpacing <= self.vOverlay: raise GeoAlgorithmExecutionException( - self.tr('Invalid overlay: {0}/{1}').format(hOverlay, vOverlay)) + self.tr('Invalid overlay: {0}/{1}').format(self.hOverlay, self.vOverlay)) - if height < vSpacing: + if self.height < self.vSpacing: raise GeoAlgorithmExecutionException( self.tr('Vertical spacing is too small for the covered area')) @@ -147,20 +158,24 @@ def processAlgorithm(self, parameters, context, feedback): fields.append(QgsField('bottom', QVariant.Double, '', 24, 16)) fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, crs) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, crs) + return True - if idx == 0: + def processAlgorithm(self, context, feedback): + if self.idx == 0: self._rectangleGrid( - sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) - elif idx == 1: + self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) + elif self.idx == 1: self._diamondGrid( - sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) - elif idx == 2: + self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) + elif self.idx == 2: self._hexagonGrid( - sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) + self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} def _rectangleGrid(self, sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback): diff --git a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py index eac647e43f08..bff841c22a14 100644 --- a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py +++ b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py @@ -27,9 +27,7 @@ from qgis.core import (QgsVectorLayerExporter, QgsSettings, - QgsApplication, QgsFeatureSink, - QgsProcessingUtils, QgsProcessingParameterFeatureSource, QgsProcessingParameterString, QgsProcessingParameterField, @@ -38,10 +36,6 @@ from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterBoolean -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterString -from processing.core.parameters import ParameterTableField from processing.tools import postgis @@ -112,70 +106,86 @@ def __init__(self): self.addParameter(QgsProcessingParameterBoolean(self.FORCE_SINGLEPART, self.tr('Create single-part geometries instead of multi-part'), False)) + self.db = None + self.schema = None + self.overwrite = None + self.createIndex = None + self.convertLowerCase = None + self.dropStringLength = None + self.forceSinglePart = None + self.primaryKeyField = None + self.encoding = None + self.source = None + self.table = None + self.providerName = None + self.geomColumn = None + def name(self): return 'importintopostgis' def displayName(self): return self.tr('Import into PostGIS') - def processAlgorithm(self, parameters, context, feedback): + def prepareAlgorithm(self, parameters, context, feedback): connection = self.parameterAsString(parameters, self.DATABASE, context) - db = postgis.GeoDB.from_name(connection) - - schema = self.parameterAsString(parameters, self.SCHEMA, context) - overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) - createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) - convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) - dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) - forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) - primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' - encoding = self.parameterAsString(parameters, self.ENCODING, context) - - source = self.parameterAsSource(parameters, self.INPUT, context) - - table = self.parameterAsString(parameters, self.TABLENAME, context) - if table: - table.strip() - if not table or table == '': - table = source.sourceName() - table = table.replace('.', '_') - table = table.replace(' ', '').lower()[0:62] - providerName = 'postgres' - - geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) - if not geomColumn: - geomColumn = 'geom' - + self.db = postgis.GeoDB.from_name(connection) + + self.schema = self.parameterAsString(parameters, self.SCHEMA, context) + self.overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) + self.createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) + self.convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) + self.dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) + self.forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) + self.primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' + self.encoding = self.parameterAsString(parameters, self.ENCODING, context) + + self.source = self.parameterAsSource(parameters, self.INPUT, context) + + self.table = self.parameterAsString(parameters, self.TABLENAME, context) + if self.table: + self.table.strip() + if not self.table or self.table == '': + self.table = self.source.sourceName() + self.table = self.table.replace('.', '_') + self.table = self.table.replace(' ', '').lower()[0:62] + self.providerName = 'postgres' + + self.geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) + if not self.geomColumn: + self.geomColumn = 'geom' + return True + + def processAlgorithm(self, context, feedback): options = {} - if overwrite: + if self.overwrite: options['overwrite'] = True - if convertLowerCase: + if self.convertLowerCase: options['lowercaseFieldNames'] = True - geomColumn = geomColumn.lower() - if dropStringLength: + self.geomColumn = self.geomColumn.lower() + if self.dropStringLength: options['dropStringConstraints'] = True - if forceSinglePart: + if self.forceSinglePart: options['forceSinglePartGeometryType'] = True # Clear geometry column for non-geometry tables - if source.wkbType() == QgsWkbTypes.NoGeometry: - geomColumn = None + if self.source.wkbType() == QgsWkbTypes.NoGeometry: + self.geomColumn = None - uri = db.uri - uri.setDataSource(schema, table, geomColumn, '', primaryKeyField) + uri = self.db.uri + uri.setDataSource(self.schema, self.table, self.geomColumn, '', self.primaryKeyField) - if encoding: - options['fileEncoding'] = encoding + if self.encoding: + options['fileEncoding'] = self.encoding - exporter = QgsVectorLayerExporter(uri.uri(), providerName, source.fields(), - source.wkbType(), source.sourceCrs(), overwrite, options) + exporter = QgsVectorLayerExporter(uri.uri(), self.providerName, self.source.fields(), + self.source.wkbType(), self.source.sourceCrs(), self.overwrite, options) if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise GeoAlgorithmExecutionException( self.tr('Error importing to PostGIS\n{0}').format(exporter.errorMessage())) - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break @@ -190,11 +200,13 @@ def processAlgorithm(self, parameters, context, feedback): raise GeoAlgorithmExecutionException( self.tr('Error importing to PostGIS\n{0}').format(exporter.errorMessage())) - if geomColumn and createIndex: - db.create_spatial_index(table, schema, geomColumn) + if self.geomColumn and self.createIndex: + self.db.create_spatial_index(self.table, self.schema, self.geomColumn) - db.vacuum_analyze(table, schema) + self.db.vacuum_analyze(self.table, self.schema) + return True + def postProcessAlgorithm(self, context, feedback): return {} def dbConnectionNames(self): diff --git a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py index 4a575b6c6633..983d9b40b09e 100644 --- a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py +++ b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py @@ -28,8 +28,6 @@ from qgis.core import (QgsDataSourceUri, QgsFeatureSink, QgsVectorLayerExporter, - QgsApplication, - QgsProcessingUtils, QgsProcessingParameterFeatureSource, QgsProcessingParameterVectorLayer, QgsProcessingParameterField, @@ -73,13 +71,26 @@ def __init__(self): self.addParameter(QgsProcessingParameterBoolean(self.DROP_STRING_LENGTH, self.tr('Drop length constraints on character fields'), False)) self.addParameter(QgsProcessingParameterBoolean(self.FORCE_SINGLEPART, self.tr('Create single-part geometries instead of multi-part'), False)) + self.db = None + self.overwrite = None + self.createIndex = None + self.dropStringLength = None + self.convertLowerCase = None + self.forceSinglePart = None + self.primaryKeyField = None + self.encoding = None + self.source = None + self.table = None + self.providerName = None + self.geomColumn = None + def name(self): return 'importintospatialite' def displayName(self): return self.tr('Import into Spatialite') - def processAlgorithm(self, parameters, context, feedback): + def prepareAlgorithm(self, parameters, context, feedback): database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) databaseuri = database.dataProvider().dataSourceUri() uri = QgsDataSourceUri(databaseuri) @@ -87,61 +98,64 @@ def processAlgorithm(self, parameters, context, feedback): if '|layerid' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layerid')] uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri)) - db = spatialite.GeoDB(uri) - - overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) - createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) - convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) - dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) - forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) - primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' - encoding = self.parameterAsString(parameters, self.ENCODING, context) - - source = self.parameterAsSource(parameters, self.INPUT, context) - - table = self.parameterAsString(parameters, self.TABLENAME, context) - if table: - table.strip() - if not table or table == '': - table = source.sourceName() - table = table.replace('.', '_') - table = table.replace(' ', '').lower() - providerName = 'spatialite' - - geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) - if not geomColumn: - geomColumn = 'geom' - + self.db = spatialite.GeoDB(uri) + + self.overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) + self.createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) + self.convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) + self.dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) + self.forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) + self.primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' + self.encoding = self.parameterAsString(parameters, self.ENCODING, context) + + self.source = self.parameterAsSource(parameters, self.INPUT, context) + + self.table = self.parameterAsString(parameters, self.TABLENAME, context) + if self.table: + self.table.strip() + if not self.table or self.table == '': + self.table = self.source.sourceName() + self.table = self.table.replace('.', '_') + self.table = self.table.replace(' ', '').lower() + self.providerName = 'spatialite' + + self.geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) + if not self.geomColumn: + self.geomColumn = 'geom' + + return True + + def processAlgorithm(self, context, feedback): options = {} - if overwrite: + if self.overwrite: options['overwrite'] = True - if convertLowerCase: + if self.convertLowerCase: options['lowercaseFieldNames'] = True - geomColumn = geomColumn.lower() - if dropStringLength: + self.geomColumn = self.geomColumn.lower() + if self.dropStringLength: options['dropStringConstraints'] = True - if forceSinglePart: + if self.forceSinglePart: options['forceSinglePartGeometryType'] = True # Clear geometry column for non-geometry tables - if source.wkbType() == QgsWkbTypes.NoGeometry: - geomColumn = None + if self.source.wkbType() == QgsWkbTypes.NoGeometry: + self.geomColumn = None - uri = db.uri - uri.setDataSource('', table, geomColumn, '', primaryKeyField) + uri = self.db.uri + uri.setDataSource('', self.table, self.geomColumn, '', self.primaryKeyField) - if encoding: - options['fileEncoding'] = encoding + if self.encoding: + options['fileEncoding'] = self.encoding - exporter = QgsVectorLayerExporter(uri.uri(), providerName, source.fields(), - source.wkbType(), source.sourceCrs(), overwrite, options) + exporter = QgsVectorLayerExporter(uri.uri(), self.providerName, self.source.fields(), + self.source.wkbType(), self.source.sourceCrs(), self.overwrite, options) if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise GeoAlgorithmExecutionException( self.tr('Error importing to Spatialite\n{0}').format(exporter.errorMessage())) - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break @@ -156,7 +170,9 @@ def processAlgorithm(self, parameters, context, feedback): raise GeoAlgorithmExecutionException( self.tr('Error importing to Spatialite\n{0}').format(exporter.errorMessage())) - if geomColumn and createIndex: - db.create_spatial_index(table, geomColumn) + if self.geomColumn and self.createIndex: + self.db.create_spatial_index(self.table, self.geomColumn) + return True + def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/Merge.py b/python/plugins/processing/algs/qgis/Merge.py index cd9923c53e5e..c00b11b4713c 100644 --- a/python/plugins/processing/algs/qgis/Merge.py +++ b/python/plugins/processing/algs/qgis/Merge.py @@ -68,19 +68,26 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Merged'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Merged'))) + self.input_layers = [] + self.fields = None + self.add_layer_field = None + self.add_path_field = None + self.sink = None + self.dest_id = None + self.dest_crs = None + def name(self): return 'mergevectorlayers' def displayName(self): return self.tr('Merge vector layers') - def processAlgorithm(self, parameters, context, feedback): - input_layers = self.parameterAsLayerList(parameters, self.LAYERS, context) - + def prepareAlgorithm(self, parameters, context, feedback): + self.input_layers = self.parameterAsLayerList(parameters, self.LAYERS, context) layers = [] - fields = QgsFields() + self.fields = QgsFields() totalFeatureCount = 0 - for layer in input_layers: + for layer in self.input_layers: if layer.type() != QgsMapLayer.VectorLayer: raise GeoAlgorithmExecutionException( self.tr('All layers must be vector layers!')) @@ -95,7 +102,7 @@ def processAlgorithm(self, parameters, context, feedback): for sindex, sfield in enumerate(layer.fields()): found = None - for dfield in fields: + for dfield in self.fields: if (dfield.name().upper() == sfield.name().upper()): found = dfield if (dfield.type() != sfield.type()): @@ -104,35 +111,38 @@ def processAlgorithm(self, parameters, context, feedback): 'data type than in other layers.'.format(sfield.name(), layerSource))) if not found: - fields.append(sfield) + self.fields.append(sfield) - add_layer_field = False - if fields.lookupField('layer') < 0: - fields.append(QgsField('layer', QVariant.String, '', 100)) - add_layer_field = True - add_path_field = False - if fields.lookupField('path') < 0: - fields.append(QgsField('path', QVariant.String, '', 200)) - add_path_field = True + self.add_layer_field = False + if self.fields.lookupField('layer') < 0: + self.fields.append(QgsField('layer', QVariant.String, '', 100)) + self.add_layer_field = True + self.add_path_field = False + if self.fields.lookupField('path') < 0: + self.fields.append(QgsField('path', QVariant.String, '', 200)) + self.add_path_field = True total = 100.0 / totalFeatureCount if totalFeatureCount else 1 - dest_crs = layers[0].crs() - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, layers[0].wkbType(), dest_crs) + self.dest_crs = layers[0].crs() + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.fields, layers[0].wkbType(), self.dest_crs) + return True + + def processAlgorithm(self, context, feedback): featureCount = 0 - for layer in layers: - for feature in layer.getFeatures(QgsFeatureRequest().setDestinationCrs(dest_crs)): + for layer in self.layers: + for feature in layer.getFeatures(QgsFeatureRequest().setDestinationCrs(self.dest_crs)): if feedback.isCanceled(): break sattributes = feature.attributes() dattributes = [] - for dindex, dfield in enumerate(fields): - if add_layer_field and dfield.name() == 'layer': + for dindex, dfield in enumerate(self.fields): + if self.add_layer_field and dfield.name() == 'layer': dattributes.append(layer.name()) continue - if add_path_field and dfield.name() == 'path': + if self.add_path_field and dfield.name() == 'path': dattributes.append(layer.publicSource()) continue @@ -154,8 +164,10 @@ def processAlgorithm(self, parameters, context, feedback): dattributes.append(dattribute) feature.setAttributes(dattributes) - sink.addFeature(feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(feature, QgsFeatureSink.FastInsert) featureCount += 1 - feedback.setProgress(int(featureCount * total)) + feedback.setProgress(int(featureCount * self.total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py index ec5600aaae95..a4b104603a50 100644 --- a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py @@ -53,20 +53,28 @@ def __init__(self): self.addParameter(db_param) self.addParameter(QgsProcessingParameterString(self.SQL, self.tr('SQL query'))) + self.connection = None + self.sql = None + def name(self): return 'postgisexecutesql' def displayName(self): return self.tr('PostGIS execute SQL') - def processAlgorithm(self, parameters, context, feedback): - connection = self.parameterAsString(parameters, self.DATABASE, context) - db = postgis.GeoDB.from_name(connection) + def prepareAlgorithm(self, parameters, context, feedback): + self.connection = self.parameterAsString(parameters, self.DATABASE, context) + self.sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') + return True - sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') + def processAlgorithm(self, context, feedback): + db = postgis.GeoDB.from_name(self.connection) try: - db._exec_sql_and_commit(str(sql)) + db._exec_sql_and_commit(str(self.sql)) except postgis.DbError as e: raise GeoAlgorithmExecutionException( self.tr('Error executing SQL:\n{0}').format(str(e))) + return True + + def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/RandomExtract.py b/python/plugins/processing/algs/qgis/RandomExtract.py index e5089c699359..ea4019010100 100644 --- a/python/plugins/processing/algs/qgis/RandomExtract.py +++ b/python/plugins/processing/algs/qgis/RandomExtract.py @@ -28,9 +28,7 @@ import random -from qgis.core import (QgsApplication, - QgsFeatureSink, - QgsProcessingUtils, +from qgis.core import (QgsFeatureSink, QgsProcessingParameterFeatureSource, QgsProcessingParameterEnum, QgsProcessingParameterNumber, @@ -38,7 +36,6 @@ QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.outputs import OutputVector class RandomExtract(QgisAlgorithm): @@ -69,42 +66,54 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extracted (random)'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Extracted (random)'))) + self.source = None + self.method = None + self.value = None + self.featureCount = None + self.sink = None + self.dest_id = None + def name(self): return 'randomextract' def displayName(self): return self.tr('Random extract') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - method = self.parameterAsEnum(parameters, self.METHOD, context) - - features = source.getFeatures() - featureCount = source.featureCount() - value = self.parameterAsInt(parameters, self.NUMBER, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.method = self.parameterAsEnum(parameters, self.METHOD, context) + self.value = self.parameterAsInt(parameters, self.NUMBER, context) + self.featureCount = self.source.featureCount() - if method == 0: - if value > featureCount: + if self.method == 0: + if self.value > self.featureCount: raise GeoAlgorithmExecutionException( self.tr('Selected number is greater than feature count. ' 'Choose a lower value and try again.')) else: - if value > 100: + if self.value > 100: raise GeoAlgorithmExecutionException( self.tr("Percentage can't be greater than 100. Set a " "different value and try again.")) - value = int(round(value / 100.0000, 4) * featureCount) + self.value = int(round(self.value / 100.0000, 4) * self.featureCount) - selran = random.sample(list(range(featureCount)), value) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + return True - total = 100.0 / featureCount if featureCount else 1 + def processAlgorithm(self, context, feedback): + selran = random.sample(list(range(self.featureCount)), self.value) + features = self.source.getFeatures() + + total = 100.0 / self.featureCount if self.featureCount else 1 for i, feat in enumerate(features): if feedback.isCanceled(): break if i in selran: - sink.addFeature(feat, QgsFeatureSink.FastInsert) + self.sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(i * total)) - return {self.OUTPUT: dest_id} + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py b/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py index 688cfe465fd3..b28925386bbc 100644 --- a/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py +++ b/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py @@ -74,38 +74,46 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extracted (random stratified)'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Extracted (random stratified)'))) + self.source = None + self.method = None + self.field = None + self.value = None + self.sink = None + self.dest_id = None + def name(self): return 'randomextractwithinsubsets' def displayName(self): return self.tr('Random extract within subsets') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - method = self.parameterAsEnum(parameters, self.METHOD, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.method = self.parameterAsEnum(parameters, self.METHOD, context) - field = self.parameterAsString(parameters, self.FIELD, context) + self.field = self.parameterAsString(parameters, self.FIELD, context) + self.value = self.parameterAsInt(parameters, self.NUMBER, context) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True - index = source.fields().lookupField(field) + def processAlgorithm(self, context, feedback): + index = self.source.fields().lookupField(self.field) - features = source.getFeatures() - featureCount = source.featureCount() - unique = source.uniqueValues(index) - value = self.parameterAsInt(parameters, self.NUMBER, context) - if method == 0: - if value > featureCount: + features = self.source.getFeatures() + featureCount = self.source.featureCount() + unique = self.source.uniqueValues(index) + if self.method == 0: + if self.value > featureCount: raise GeoAlgorithmExecutionException( self.tr('Selected number is greater that feature count. ' 'Choose lesser value and try again.')) else: - if value > 100: + if self.value > 100: raise GeoAlgorithmExecutionException( self.tr("Percentage can't be greater than 100. Set " "correct value and try again.")) - value = value / 100.0 - - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + self.value = self.value / 100.0 selran = [] total = 100.0 / (featureCount * len(unique)) if featureCount else 1 @@ -119,13 +127,17 @@ def processAlgorithm(self, parameters, context, feedback): feedback.setProgress(int(i * total)) for subset in classes.values(): - selValue = value if method != 1 else int(round(value * len(subset), 0)) + selValue = self.value if self.method != 1 else int(round(self.value * len(subset), 0)) selran.extend(random.sample(subset, selValue)) total = 100.0 / featureCount if featureCount else 1 for (i, feat) in enumerate(selran): if feedback.isCanceled(): break - sink.addFeature(feat, QgsFeatureSink.FastInsert) + self.sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(i * total)) - return {self.OUTPUT: dest_id} + + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/RegularPoints.py b/python/plugins/processing/algs/qgis/RegularPoints.py index 6a0b25f8ec38..ce701395e718 100644 --- a/python/plugins/processing/algs/qgis/RegularPoints.py +++ b/python/plugins/processing/algs/qgis/RegularPoints.py @@ -81,55 +81,66 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Regular points'), QgsProcessingParameterDefinition.TypeVectorPoint)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Regular points'), QgsProcessingParameterDefinition.TypeVectorPoint)) + self.extent = None + self.spacing = None + self.inset = None + self.randomize = None + self.isSpacing = None + self.fields = None + self.sink = None + self.dest_id = None + def name(self): return 'regularpoints' def displayName(self): return self.tr('Regular points') - def processAlgorithm(self, parameters, context, feedback): - extent = self.parameterAsExtent(parameters, self.EXTENT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.extent = self.parameterAsExtent(parameters, self.EXTENT, context) - spacing = self.parameterAsDouble(parameters, self.SPACING, context) - inset = self.parameterAsDouble(parameters, self.INSET, context) - randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context) - isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context) + self.spacing = self.parameterAsDouble(parameters, self.SPACING, context) + self.inset = self.parameterAsDouble(parameters, self.INSET, context) + self.randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context) + self.isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context) crs = self.parameterAsCrs(parameters, self.CRS, context) - fields = QgsFields() - fields.append(QgsField('id', QVariant.Int, '', 10, 0)) + self.fields = QgsFields() + self.fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Point, crs) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.fields, QgsWkbTypes.Point, crs) + return True - if randomize: + def processAlgorithm(self, context, feedback): + if self.randomize: seed() - area = extent.width() * extent.height() - if isSpacing: - pSpacing = spacing + area = self.extent.width() * self.extent.height() + if self.isSpacing: + pSpacing = self.spacing else: - pSpacing = sqrt(area / spacing) + pSpacing = sqrt(area / self.spacing) f = QgsFeature() f.initAttributes(1) - f.setFields(fields) + f.setFields(self.fields) count = 0 total = 100.0 / (area / pSpacing) - y = extent.yMaximum() - inset + y = self.extent.yMaximum() - self.inset - extent_geom = QgsGeometry.fromRect(extent) + extent_geom = QgsGeometry.fromRect(self.extent) extent_engine = QgsGeometry.createGeometryEngine(extent_geom.geometry()) extent_engine.prepareGeometry() - while y >= extent.yMinimum(): - x = extent.xMinimum() + inset - while x <= extent.xMaximum(): + while y >= self.extent.yMinimum(): + x = self.extent.xMinimum() + self.inset + while x <= self.extent.xMaximum(): if feedback.isCanceled(): break - if randomize: + if self.randomize: geom = QgsGeometry().fromPoint(QgsPointXY( uniform(x - (pSpacing / 2.0), x + (pSpacing / 2.0)), uniform(y - (pSpacing / 2.0), y + (pSpacing / 2.0)))) @@ -139,10 +150,13 @@ def processAlgorithm(self, parameters, context, feedback): if extent_engine.intersects(geom.geometry()): f.setAttribute('id', count) f.setGeometry(geom) - sink.addFeature(f, QgsFeatureSink.FastInsert) + self.sink.addFeature(f, QgsFeatureSink.FastInsert) x += pSpacing count += 1 feedback.setProgress(int(count * total)) y = y - pSpacing - return {self.OUTPUT: dest_id} + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py b/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py index 3abc6a0d05cd..7c79b0126552 100644 --- a/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py +++ b/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py @@ -26,12 +26,10 @@ __revision__ = '$Format:%H$' from qgis.core import (QgsFeatureSink, - QgsProcessingUtils, QgsProcessingParameterVectorLayer, QgsProcessingParameterFeatureSink, QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException class SaveSelectedFeatures(QgisAlgorithm): @@ -49,27 +47,35 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Selection'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Selection"))) + self.vectorLayer = None + self.sink = None + self.dest_id = None + def name(self): return 'saveselectedfeatures' def displayName(self): return self.tr('Save selected features') - def processAlgorithm(self, parameters, context, feedback): - vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - vectorLayer.fields(), vectorLayer.wkbType(), vectorLayer.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.vectorLayer.fields(), self.vectorLayer.wkbType(), self.vectorLayer.sourceCrs()) + return True - features = vectorLayer.getSelectedFeatures() - count = int(vectorLayer.selectedFeatureCount()) + def processAlgorithm(self, context, feedback): + features = self.vectorLayer.getSelectedFeatures() + count = int(self.vectorLayer.selectedFeatureCount()) total = 100.0 / count if count else 1 for current, feat in enumerate(features): if feedback.isCanceled(): break - sink.addFeature(feat, QgsFeatureSink.FastInsert) + self.sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/SelectByAttribute.py b/python/plugins/processing/algs/qgis/SelectByAttribute.py index 3af9eb908c35..4f6087e3a9c2 100644 --- a/python/plugins/processing/algs/qgis/SelectByAttribute.py +++ b/python/plugins/processing/algs/qgis/SelectByAttribute.py @@ -92,47 +92,60 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (attribute)'))) + self.layer = None + self.fieldName = None + self.operator = None + self.value = None + self.input = None + def name(self): return 'selectbyattribute' def displayName(self): return self.tr('Select by attribute') - def processAlgorithm(self, parameters, context, feedback): - layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + + self.fieldName = self.parameterAsString(parameters, self.FIELD, context) + self.operator = self.OPERATORS[self.parameterAsEnum(parameters, self.OPERATOR, context)] + self.value = self.parameterAsString(parameters, self.VALUE, context) - fieldName = self.parameterAsString(parameters, self.FIELD, context) - operator = self.OPERATORS[self.parameterAsEnum(parameters, self.OPERATOR, context)] - value = self.parameterAsString(parameters, self.VALUE, context) + self.input = parameters[self.INPUT] + return True - fields = layer.fields() + def processAlgorithm(self, context, feedback): + fields = self.layer.fields() - idx = layer.fields().lookupField(fieldName) + idx = self.layer.fields().lookupField(self.fieldName) fieldType = fields[idx].type() - if fieldType != QVariant.String and operator in self.STRING_OPERATORS: + if fieldType != QVariant.String and self.operator in self.STRING_OPERATORS: op = ''.join(['"%s", ' % o for o in self.STRING_OPERATORS]) raise GeoAlgorithmExecutionException( self.tr('Operators {0} can be used only with string fields.').format(op)) - field_ref = QgsExpression.quotedColumnRef(fieldName) - quoted_val = QgsExpression.quotedValue(value) - if operator == 'is null': + field_ref = QgsExpression.quotedColumnRef(self.fieldName) + quoted_val = QgsExpression.quotedValue(self.value) + if self.operator == 'is null': expression_string = '{} IS NULL'.format(field_ref) - elif operator == 'is not null': + elif self.operator == 'is not null': expression_string = '{} IS NOT NULL'.format(field_ref) - elif operator == 'begins with': - expression_string = """%s LIKE '%s%%'""" % (field_ref, value) - elif operator == 'contains': - expression_string = """%s LIKE '%%%s%%'""" % (field_ref, value) - elif operator == 'does not contain': - expression_string = """%s NOT LIKE '%%%s%%'""" % (field_ref, value) + elif self.operator == 'begins with': + expression_string = """%s LIKE '%s%%'""" % (field_ref, self.value) + elif self.operator == 'contains': + expression_string = """%s LIKE '%%%s%%'""" % (field_ref, self.value) + elif self.operator == 'does not contain': + expression_string = """%s NOT LIKE '%%%s%%'""" % (field_ref, self.value) else: - expression_string = '{} {} {}'.format(field_ref, operator, quoted_val) + expression_string = '{} {} {}'.format(field_ref, self.operator, quoted_val) expression = QgsExpression(expression_string) if expression.hasParserError(): raise GeoAlgorithmExecutionException(expression.parserErrorString()) - layer.selectByExpression(expression_string) - return {self.OUTPUT: parameters[self.INPUT]} + self.layer.selectByExpression(expression_string) + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.input} diff --git a/python/plugins/processing/algs/qgis/SelectByExpression.py b/python/plugins/processing/algs/qgis/SelectByExpression.py index ac1f833ab2ab..2ba58c41bf9f 100644 --- a/python/plugins/processing/algs/qgis/SelectByExpression.py +++ b/python/plugins/processing/algs/qgis/SelectByExpression.py @@ -60,30 +60,42 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (attribute)'))) + self.layer = None + self.behavior = None + self.input = None + self.expression = None + def name(self): return 'selectbyexpression' def displayName(self): return self.tr('Select by expression') - def processAlgorithm(self, parameters, context, feedback): - layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) if method == 0: - behavior = QgsVectorLayer.SetSelection + self.behavior = QgsVectorLayer.SetSelection elif method == 1: - behavior = QgsVectorLayer.AddToSelection + self.behavior = QgsVectorLayer.AddToSelection elif method == 2: - behavior = QgsVectorLayer.RemoveFromSelection + self.behavior = QgsVectorLayer.RemoveFromSelection elif method == 3: - behavior = QgsVectorLayer.IntersectSelection + self.behavior = QgsVectorLayer.IntersectSelection - expression = self.parameterAsString(parameters, self.EXPRESSION, context) - qExp = QgsExpression(expression) + self.expression = self.parameterAsString(parameters, self.EXPRESSION, context) + qExp = QgsExpression(self.expression) if qExp.hasParserError(): raise GeoAlgorithmExecutionException(qExp.parserErrorString()) - layer.selectByExpression(expression, behavior) - return {self.OUTPUT: parameters[self.INPUT]} + self.input = parameters[self.INPUT] + return True + + def processAlgorithm(self, context, feedback): + self.layer.selectByExpression(self.expression, self.behavior) + return True + + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.input} diff --git a/python/plugins/processing/algs/qgis/SimplifyGeometries.py b/python/plugins/processing/algs/qgis/SimplifyGeometries.py index f84ebd8aff80..a58c450a233c 100644 --- a/python/plugins/processing/algs/qgis/SimplifyGeometries.py +++ b/python/plugins/processing/algs/qgis/SimplifyGeometries.py @@ -76,28 +76,37 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Simplified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Simplified'))) + self.source = None + self.tolerance = None + self.method = None + self.sink = None + self.dest_id = None + def name(self): return 'simplifygeometries' def displayName(self): return self.tr('Simplify geometries') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) - method = self.parameterAsEnum(parameters, self.METHOD, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) + self.method = self.parameterAsEnum(parameters, self.METHOD, context) + + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True + def processAlgorithm(self, context, feedback): pointsBefore = 0 pointsAfter = 0 - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) - - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 - if method != 0: - simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, tolerance, method) + simplifier = None + if self.method != 0: + simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, self.tolerance, self.method) for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -107,18 +116,20 @@ def processAlgorithm(self, parameters, context, feedback): input_geometry = input_feature.geometry() pointsBefore += input_geometry.geometry().nCoordinates() - if method == 0: # distance - output_geometry = input_geometry.simplify(tolerance) + if self.method == 0: # distance + output_geometry = input_geometry.simplify(self.tolerance) else: output_geometry = simplifier.simplify(input_geometry) pointsAfter += output_geometry.geometry().nCoordinates() out_feature.setGeometry(output_geometry) - sink.addFeature(out_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) QgsMessageLog.logMessage(self.tr('Simplify: Input geometries have been simplified from {0} to {1} points').format(pointsBefore, pointsAfter), self.tr('Processing'), QgsMessageLog.INFO) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/Smooth.py b/python/plugins/processing/algs/qgis/Smooth.py index bd27362879f5..0e85212019df 100644 --- a/python/plugins/processing/algs/qgis/Smooth.py +++ b/python/plugins/processing/algs/qgis/Smooth.py @@ -66,37 +66,48 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Smoothed'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Smoothed'))) + self.source = None + self.iterations = None + self.offset = None + self.max_angle = None + self.sink = None + self.dest_id = None + def name(self): return 'smoothgeometry' def displayName(self): return self.tr('Smooth geometry') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) - offset = self.parameterAsDouble(parameters, self.OFFSET, context) - max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) + self.offset = self.parameterAsDouble(parameters, self.OFFSET, context) + self.max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - source.fields(), source.wkbType(), source.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + return True - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + def processAlgorithm(self, context, feedback): + features = self.source.getFeatures() + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break output_feature = input_feature if input_feature.geometry(): - output_geometry = input_feature.geometry().smooth(iterations, offset, -1, max_angle) + output_geometry = input_feature.geometry().smooth(self.iterations, self.offset, -1, self.max_angle) if not output_geometry: raise GeoAlgorithmExecutionException( self.tr('Error smoothing geometry')) output_feature.setGeometry(output_geometry) - sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py index 955cc33e6e42..e58d2aec7a30 100644 --- a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py @@ -49,26 +49,34 @@ def __init__(self): self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File Database'), False, False)) self.addParameter(QgsProcessingParameterString(self.SQL, self.tr('SQL query'), '', True)) + self.database = None + self.sql = None + def name(self): return 'spatialiteexecutesql' def displayName(self): return self.tr('Spatialite execute SQL') - def processAlgorithm(self, parameters, context, feedback): - database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) - databaseuri = database.dataProvider().dataSourceUri() + def prepareAlgorithm(self, parameters, context, feedback): + self.database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) + self.sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') + return True + + def processAlgorithm(self, context, feedback): + databaseuri = self.database.dataProvider().dataSourceUri() uri = QgsDataSourceUri(databaseuri) if uri.database() is '': if '|layerid' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layerid')] uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri)) db = spatialite.GeoDB(uri) - sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') try: - db._exec_sql_and_commit(str(sql)) + db._exec_sql_and_commit(str(self.sql)) except spatialite.DbError as e: raise GeoAlgorithmExecutionException( self.tr('Error executing SQL:\n{0}').format(str(e))) + return True + def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/SymmetricalDifference.py b/python/plugins/processing/algs/qgis/SymmetricalDifference.py index fdd9a0127664..41f234069554 100644 --- a/python/plugins/processing/algs/qgis/SymmetricalDifference.py +++ b/python/plugins/processing/algs/qgis/SymmetricalDifference.py @@ -70,32 +70,39 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Symmetrical difference'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Symmetrical difference'))) + self.sourceA = None + self.sourceB = None + self.sink = None + self.dest_id = None + def name(self): return 'symmetricaldifference' def displayName(self): return self.tr('Symmetrical difference') - def processAlgorithm(self, parameters, context, feedback): - sourceA = self.parameterAsSource(parameters, self.INPUT, context) - sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.sourceA = self.parameterAsSource(parameters, self.INPUT, context) + self.sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) - geomType = QgsWkbTypes.multiType(sourceA.wkbType()) - fields = vector.combineFields(sourceA.fields(), sourceB.fields()) + geomType = QgsWkbTypes.multiType(self.sourceA.wkbType()) + fields = vector.combineFields(self.sourceA.fields(), self.sourceB.fields()) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, geomType, sourceA.sourceCrs()) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, geomType, self.sourceA.sourceCrs()) + return True + def processAlgorithm(self, context, feedback): featB = QgsFeature() outFeat = QgsFeature() - indexA = QgsSpatialIndex(sourceA) - indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs()))) + indexA = QgsSpatialIndex(self.sourceA) + indexB = QgsSpatialIndex(self.sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(self.sourceA.sourceCrs()))) - total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 + total = 100.0 / (self.sourceA.featureCount() * self.sourceB.featureCount()) if self.sourceA.featureCount() and self.sourceB.featureCount() else 1 count = 0 - for featA in sourceA.getFeatures(): + for featA in self.sourceA.getFeatures(): if feedback.isCanceled(): break @@ -104,8 +111,8 @@ def processAlgorithm(self, parameters, context, feedback): attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) - request.setDestinationCrs(sourceA.sourceCrs()) - for featB in sourceB.getFeatures(request): + request.setDestinationCrs(self.sourceA.sourceCrs()) + for featB in self.sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() @@ -115,7 +122,7 @@ def processAlgorithm(self, parameters, context, feedback): try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + self.sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) @@ -124,9 +131,9 @@ def processAlgorithm(self, parameters, context, feedback): count += 1 feedback.setProgress(int(count * total)) - length = len(sourceA.fields()) + length = len(self.sourceA.fields()) - for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): + for featA in self.sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(self.sourceA.sourceCrs())): if feedback.isCanceled(): break @@ -136,7 +143,7 @@ def processAlgorithm(self, parameters, context, feedback): attrs = [NULL] * length + attrs intersects = indexA.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) - for featB in sourceA.getFeatures(request): + for featB in self.sourceA.getFeatures(request): if feedback.isCanceled(): break @@ -147,7 +154,7 @@ def processAlgorithm(self, parameters, context, feedback): try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) - sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + self.sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) @@ -155,5 +162,7 @@ def processAlgorithm(self, parameters, context, feedback): count += 1 feedback.setProgress(int(count * total)) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} diff --git a/python/plugins/processing/algs/qgis/VectorSplit.py b/python/plugins/processing/algs/qgis/VectorSplit.py index b418d6d02d25..4f59813ee4fb 100644 --- a/python/plugins/processing/algs/qgis/VectorSplit.py +++ b/python/plugins/processing/algs/qgis/VectorSplit.py @@ -28,7 +28,6 @@ import os -from qgis.PyQt.QtGui import QIcon from qgis.core import (QgsProcessingUtils, QgsFeatureSink, QgsProcessingParameterFeatureSource, @@ -39,10 +38,6 @@ QgsFeatureRequest) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterTableField -from processing.core.outputs import OutputDirectory -from processing.tools import vector from processing.tools.system import mkdir pluginPath = os.path.split(os.path.split(os.path.dirname(__file__))[0])[0] @@ -71,42 +66,51 @@ def __init__(self): self.addOutput(QgsProcessingOutputFolder(self.OUTPUT, self.tr('Output directory'))) + self.source = None + self.fieldName = None + self.directory = None + self.uniqueValues = None + self.sinks = {} + def name(self): return 'splitvectorlayer' def displayName(self): return self.tr('Split vector layer') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) - fieldName = self.parameterAsString(parameters, self.FIELD, context) - directory = self.parameterAsString(parameters, self.OUTPUT, context) - - mkdir(directory) - - fieldIndex = source.fields().lookupField(fieldName) - uniqueValues = source.uniqueValues(fieldIndex) - baseName = os.path.join(directory, '{0}'.format(fieldName)) - - fields = source.fields() - crs = source.sourceCrs() - geomType = source.wkbType() + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) + self.fieldName = self.parameterAsString(parameters, self.FIELD, context) + self.directory = self.parameterAsString(parameters, self.OUTPUT, context) + mkdir(self.directory) - total = 100.0 / len(uniqueValues) if uniqueValues else 1 + fieldIndex = self.source.fields().lookupField(self.fieldName) + self.uniqueValues = self.source.uniqueValues(fieldIndex) - for current, i in enumerate(uniqueValues): + baseName = os.path.join(self.directory, '{0}'.format(self.fieldName)) + self.sinks = {} + for current, i in enumerate(self.uniqueValues): if feedback.isCanceled(): break fName = u'{0}_{1}.shp'.format(baseName, str(i).strip()) feedback.pushInfo(self.tr('Creating layer: {}').format(fName)) + sink, dest = QgsProcessingUtils.createFeatureSink(fName, context, self.source.fields, self.source.wkbType(), self.source.sourceCrs()) + self.sinks[i] = sink + return True + + def processAlgorithm(self, context, feedback): + total = 100.0 / len(self.uniqueValues) if self.uniqueValues else 1 + for current, i in enumerate(self.uniqueValues): + if feedback.isCanceled(): + break - sink, dest = QgsProcessingUtils.createFeatureSink(fName, context, fields, geomType, crs) + sink = self.sinks[i] - filter = '{} = {}'.format(QgsExpression.quotedColumnRef(fieldName), QgsExpression.quotedValue(i)) + filter = '{} = {}'.format(QgsExpression.quotedColumnRef(self.fieldName), QgsExpression.quotedValue(i)) req = QgsFeatureRequest().setFilterExpression(filter) count = 0 - for f in source.getFeatures(req): + for f in self.source.getFeatures(req): if feedback.isCanceled(): break sink.addFeature(f, QgsFeatureSink.FastInsert) @@ -115,5 +119,7 @@ def processAlgorithm(self, parameters, context, feedback): del sink feedback.setProgress(int(current * total)) + return True - return {self.OUTPUT: directory} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.directory} diff --git a/python/plugins/processing/algs/qgis/ZonalStatistics.py b/python/plugins/processing/algs/qgis/ZonalStatistics.py index 0140be28adb0..29bec06da2e2 100644 --- a/python/plugins/processing/algs/qgis/ZonalStatistics.py +++ b/python/plugins/processing/algs/qgis/ZonalStatistics.py @@ -94,30 +94,40 @@ def __init__(self): self.tr('Zonal statistics'), QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.bandNumber = None + self.columnPrefix = None + self.selectedStats = None + self.vectorLayer = None + self.rasterLayer = None + def name(self): return 'zonalstatistics' def displayName(self): return self.tr('Zonal Statistics') - def processAlgorithm(self, parameters, context, feedback): - bandNumber = self.parameterAsInt(parameters, self.RASTER_BAND, context) - columnPrefix = self.parameterAsString(parameters, self.COLUMN_PREFIX, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.bandNumber = self.parameterAsInt(parameters, self.RASTER_BAND, context) + self.columnPrefix = self.parameterAsString(parameters, self.COLUMN_PREFIX, context) st = self.parameterAsEnums(parameters, self.STATISTICS, context) - vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT_VECTOR, context) - rasterLayer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context) - keys = list(self.STATS.keys()) - selectedStats = 0 + self.selectedStats = 0 for i in st: - selectedStats |= self.STATS[keys[i]] - - zs = QgsZonalStatistics(vectorLayer, - rasterLayer, - columnPrefix, - bandNumber, - selectedStats) + self.selectedStats |= self.STATS[keys[i]] + + self.vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT_VECTOR, context) + self.rasterLayer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context) + return True + + def processAlgorithm(self, context, feedback): + zs = QgsZonalStatistics(self.vectorLayer, + self.rasterLayer, + self.columnPrefix, + self.bandNumber, + self.selectedStats) zs.calculateStatistics(feedback) + return True - return {self.INPUT_VECTOR: vectorLayer} + def postProcessAlgorithm(self, context, feedback): + return {self.INPUT_VECTOR: self.vectorLayer} diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py index 6f7fe7a0f46a..4e4300147b03 100644 --- a/python/plugins/processing/script/ScriptAlgorithm.py +++ b/python/plugins/processing/script/ScriptAlgorithm.py @@ -75,6 +75,10 @@ def __init__(self, descriptionFile, script=None): if descriptionFile is not None: self.defineCharacteristicsFromFile() + self.ns = {} + self.cleaned_script = None + self.results = {} + def clone(self): return ScriptAlgorithm(self.descriptionFile) @@ -178,11 +182,7 @@ def processParameterLine(self, line): self.tr('Could not load script: {0}.\n' 'Problem with line "{1}"', 'ScriptAlgorithm').format(self.descriptionFile or '', line)) - def processAlgorithm(self, parameters, context, feedback): - ns = {} - - ns['scriptDescriptionFile'] = self.descriptionFile - + def prepareAlgorithm(self, parameters, context, feedback): for param in self.parameterDefinitions(): method = None if param.type() == "boolean": @@ -224,20 +224,19 @@ def processAlgorithm(self, parameters, context, feedback): method = self.parameterAsSource if method: - ns[param.name()] = method(parameters, param.name(), context) + self.ns[param.name()] = method(parameters, param.name(), context) + self.ns['scriptDescriptionFile'] = self.descriptionFile for out in self.outputDefinitions(): - ns[out.name()] = None + self.ns[out.name()] = None - ns['self'] = self - ns['parameters'] = parameters - ns['feedback'] = feedback - ns['context'] = context + self.ns['self'] = self + self.ns['parameters'] = parameters + expression_context = self.createExpressionContext(parameters, context) variables = re.findall('@[a-zA-Z0-9_]*', self.script) script = 'import processing\n' script += self.script - context = self.createExpressionContext(parameters, context) for var in variables: varname = var[1:] if context.hasVariable(varname): @@ -245,12 +244,22 @@ def processAlgorithm(self, parameters, context, feedback): else: # messy - it's probably NOT a variable, and instead an email address or some other string containing '@' QgsMessageLog.logMessage(self.tr('Cannot find variable: {0}').format(varname), self.tr('Processing'), QgsMessageLog.WARNING) + self.cleaned_script = script - exec((script), ns) - results = {} + return True + + def processAlgorithm(self, context, feedback): + self.ns['feedback'] = feedback + self.ns['context'] = context + + exec((self.cleaned_script), self.ns) + self.results = {} for out in self.outputDefinitions(): - results[out.name()] = ns[out.name()] - return results + self.results[out.name()] = self.ns[out.name()] + return True + + def postProcessAlgorithm(self, context, feedback): + return self.results def helpUrl(self): if self.descriptionFile is None: diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index c3356a037b80..849151d4eda3 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -9,7 +9,7 @@ tests: type: vector # Param is a vector layer name: polys.gml # file name results: # A map of results (only one here) - OUTPUT_LAYER: + OUTPUT: type: vector # Expected result is a vector layer name: expected/polys_centroid.gml # The relative filepath from the processing testdata directory compare: @@ -735,7 +735,7 @@ tests: name: lines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_lines.gml type: vector compare: @@ -749,7 +749,7 @@ tests: name: multilines.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_multilines.gml type: vector compare: @@ -763,7 +763,7 @@ tests: name: multipoints.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_multipoint.gml type: vector compare: @@ -777,7 +777,7 @@ tests: name: multipolys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_multipolys.gml type: vector compare: @@ -791,7 +791,7 @@ tests: name: points.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_points.gml type: vector compare: @@ -805,7 +805,7 @@ tests: name: polys.gml type: vector results: - OUTPUT_LAYER: + OUTPUT: name: expected/centroid_polys.gml type: vector compare: @@ -1037,12 +1037,12 @@ tests: - algorithm: qgis:aspect name: Aspect from QGIS analysis library params: - INPUT_LAYER: + INPUT: name: dem.tif type: raster Z_FACTOR: 1.0 results: - OUTPUT_LAYER: + OUTPUT: hash: 762865ee485a6736d188402aa10e6fd38a812a9e45a7dd2d4885a63a type: rasterhash @@ -1670,11 +1670,11 @@ tests: - algorithm: qgis:dropgeometries name: Drop geometries params: - INPUT_LAYER: + INPUT: name: polys.gml type: vector results: - OUTPUT_TABLE: + OUTPUT: name: expected/dropped_geometry.csv type: vector diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 6abaaaeba8e0..a36f57c1fa09 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -142,7 +142,7 @@ bool QgsCentroidAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi QVariantMap QgsCentroidAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) { QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT_LAYER" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); return outputs; } diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 8a88add3cf69..12897ade0e87 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -4610,7 +4610,7 @@ void TestQgsProcessing::modelerAlgorithm() QMap alg7c1outputs; QgsProcessingModelAlgorithm::ModelOutput alg7c1out1( QStringLiteral( "my_output" ) ); alg7c1out1.setChildId( "cx1" ); - alg7c1out1.setChildOutputName( "OUTPUT_LAYER" ); + alg7c1out1.setChildOutputName( "OUTPUT" ); alg7c1out1.setDescription( QStringLiteral( "my output" ) ); alg7c1outputs.insert( QStringLiteral( "my_output" ), alg7c1out1 ); alg7c1.setModelOutputs( alg7c1outputs ); @@ -4630,7 +4630,7 @@ void TestQgsProcessing::modelerAlgorithm() QMap alg7c2outputs; QgsProcessingModelAlgorithm::ModelOutput alg7c2out1( QStringLiteral( "my_output2" ) ); alg7c2out1.setChildId( "cx2" ); - alg7c2out1.setChildOutputName( "OUTPUT_LAYER" ); + alg7c2out1.setChildOutputName( "OUTPUT" ); alg7c2out1.setDescription( QStringLiteral( "my output2" ) ); alg7c2outputs.insert( QStringLiteral( "my_output2" ), alg7c2out1 ); alg7c2.setModelOutputs( alg7c2outputs ); @@ -4700,7 +4700,7 @@ void TestQgsProcessing::modelExecution() alg2c1.addParameterSources( "DISSOLVE", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromStaticValue( false ) ); QMap outputs1; QgsProcessingModelAlgorithm::ModelOutput out1( "MODEL_OUT_LAYER" ); - out1.setChildOutputName( "OUTPUT_LAYER" ); + out1.setChildOutputName( "OUTPUT" ); outputs1.insert( QStringLiteral( "MODEL_OUT_LAYER" ), out1 ); alg2c1.setModelOutputs( outputs1 ); model2.addChildAlgorithm( alg2c1 ); @@ -4720,7 +4720,7 @@ void TestQgsProcessing::modelExecution() QCOMPARE( params.value( "END_CAP_STYLE" ).toInt(), 1 ); QCOMPARE( params.value( "JOIN_STYLE" ).toInt(), 2 ); QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "my_layer_id" ) ); - QCOMPARE( params.value( "OUTPUT_LAYER" ).toString(), QStringLiteral( "dest.shp" ) ); + QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "dest.shp" ) ); QCOMPARE( params.count(), 7 ); QVariantMap results; @@ -4735,7 +4735,7 @@ void TestQgsProcessing::modelExecution() model2.addChildAlgorithm( alg2c2 ); params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx2" ), modelInputs, childResults ); QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) ); - QCOMPARE( params.value( "OUTPUT_LAYER" ).toString(), QStringLiteral( "memory:" ) ); + QCOMPARE( params.value( "OUTPUT" ).toString(), QStringLiteral( "memory:" ) ); QCOMPARE( params.count(), 2 ); // a child with an optional output @@ -4770,8 +4770,8 @@ void TestQgsProcessing::modelExecution() "##my_out=output outputVector\n" "results={}\n" "outputs['cx1']=processing.run('native:buffer', {'DISSOLVE':false,'DISTANCE':parameters['DIST'],'END_CAP_STYLE':1,'INPUT':parameters['SOURCE_LAYER'],'JOIN_STYLE':2,'SEGMENTS':16}, context=context, feedback=feedback)\n" - "results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT_LAYER']\n" - "outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT_LAYER']}, context=context, feedback=feedback)\n" + "results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n" + "outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n" "outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT_LAYER'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n" "results['MY_OUT']=outputs['cx3']['OUTPUT']\n" "return results" ).split( '\n' ); From e0c7daa2d88c6e91d1b6674b2518b3180e711d2f Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Wed, 28 Jun 2017 23:05:12 +1000 Subject: [PATCH 09/49] Rename QgsProcessingAlgorithm::clone to ::create Since it better describes what the function does. It returns a new pristine copy of the algorithm, not a clone of its current state --- .../core/processing/qgsprocessingalgorithm.sip | 4 ++-- .../processing/qgsprocessingmodelalgorithm.sip | 2 +- .../processing/algs/qgis/QgisAlgorithm.py | 2 +- .../processing/modeler/ModelerDialog.py | 2 +- .../processing/script/ScriptAlgorithm.py | 2 +- .../processing/tests/QgisAlgorithmsTest.py | 2 +- src/core/processing/qgsnativealgorithms.cpp | 18 +++++++++--------- src/core/processing/qgsnativealgorithms.h | 18 +++++++++--------- src/core/processing/qgsprocessingalgorithm.cpp | 2 +- src/core/processing/qgsprocessingalgorithm.h | 4 ++-- .../processing/qgsprocessingalgrunnertask.cpp | 2 +- .../processing/qgsprocessingmodelalgorithm.cpp | 4 ++-- .../processing/qgsprocessingmodelalgorithm.h | 2 +- tests/src/core/testqgsprocessing.cpp | 2 +- 14 files changed, 33 insertions(+), 33 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index d7d83f6ba2d5..2d71e15e30bf 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -44,9 +44,9 @@ class QgsProcessingAlgorithm - virtual QgsProcessingAlgorithm *clone() const = 0 /Factory/; + virtual QgsProcessingAlgorithm *create() const = 0 /Factory/; %Docstring - Clones the algorithm, returning a new copy for safe use in background threads. + Creates a copy of the algorithm, ready for execution. :rtype: QgsProcessingAlgorithm %End diff --git a/python/core/processing/qgsprocessingmodelalgorithm.sip b/python/core/processing/qgsprocessingmodelalgorithm.sip index ad715862d22b..defa8cc3430b 100644 --- a/python/core/processing/qgsprocessingmodelalgorithm.sip +++ b/python/core/processing/qgsprocessingmodelalgorithm.sip @@ -588,7 +588,7 @@ Copies are protected to avoid slicing virtual QString asPythonCommand( const QVariantMap ¶meters, QgsProcessingContext &context ) const; - virtual QgsProcessingModelAlgorithm *clone() const /Factory/; + virtual QgsProcessingModelAlgorithm *create() const /Factory/; void setName( const QString &name ); diff --git a/python/plugins/processing/algs/qgis/QgisAlgorithm.py b/python/plugins/processing/algs/qgis/QgisAlgorithm.py index c2121cb77d44..8576a06618d8 100755 --- a/python/plugins/processing/algs/qgis/QgisAlgorithm.py +++ b/python/plugins/processing/algs/qgis/QgisAlgorithm.py @@ -48,5 +48,5 @@ def trAlgorithm(self, string, context=''): context = self.__class__.__name__ return string, QCoreApplication.translate(context, string) - def clone(self): + def create(self): return type(self)() diff --git a/python/plugins/processing/modeler/ModelerDialog.py b/python/plugins/processing/modeler/ModelerDialog.py index 2073f8912d4e..3f000e888107 100644 --- a/python/plugins/processing/modeler/ModelerDialog.py +++ b/python/plugins/processing/modeler/ModelerDialog.py @@ -237,7 +237,7 @@ def _mimeDataAlgorithm(items): self.mActionRun.triggered.connect(self.runModel) if model is not None: - self.model = model.clone() + self.model = model.create() self.model.setSourceFilePath(model.sourceFilePath()) self.textGroup.setText(self.model.group()) self.textName.setText(self.model.displayName()) diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py index 4e4300147b03..e1afb7e35f1e 100644 --- a/python/plugins/processing/script/ScriptAlgorithm.py +++ b/python/plugins/processing/script/ScriptAlgorithm.py @@ -79,7 +79,7 @@ def __init__(self, descriptionFile, script=None): self.cleaned_script = None self.results = {} - def clone(self): + def create(self): return ScriptAlgorithm(self.descriptionFile) def icon(self): diff --git a/python/plugins/processing/tests/QgisAlgorithmsTest.py b/python/plugins/processing/tests/QgisAlgorithmsTest.py index bd28a389dbe8..81be11e515e1 100644 --- a/python/plugins/processing/tests/QgisAlgorithmsTest.py +++ b/python/plugins/processing/tests/QgisAlgorithmsTest.py @@ -48,7 +48,7 @@ def name(self): def displayName(self): return 'testalg' - def clone(self): + def create(self): return TestAlg() def processAlgorithm(self, parameters, context, feedback): diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index a36f57c1fa09..82b56c0c6358 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -84,7 +84,7 @@ QString QgsCentroidAlgorithm::shortHelpString() const "The attributes associated to each point in the output layer are the same ones associated to the original features." ); } -QgsCentroidAlgorithm *QgsCentroidAlgorithm::clone() const +QgsCentroidAlgorithm *QgsCentroidAlgorithm::create() const { return new QgsCentroidAlgorithm(); } @@ -174,7 +174,7 @@ QString QgsBufferAlgorithm::shortHelpString() const "The mitre limit parameter is only applicable for mitre join styles, and controls the maximum distance from the offset curve to use when creating a mitred join." ); } -QgsBufferAlgorithm *QgsBufferAlgorithm::clone() const +QgsBufferAlgorithm *QgsBufferAlgorithm::create() const { return new QgsBufferAlgorithm(); } @@ -299,7 +299,7 @@ QString QgsDissolveAlgorithm::shortHelpString() const "In case the input is a polygon layer, common boundaries of adjacent polygons being dissolved will get erased." ); } -QgsDissolveAlgorithm *QgsDissolveAlgorithm::clone() const +QgsDissolveAlgorithm *QgsDissolveAlgorithm::create() const { return new QgsDissolveAlgorithm(); } @@ -458,7 +458,7 @@ QString QgsClipAlgorithm::shortHelpString() const "be manually updated." ); } -QgsClipAlgorithm *QgsClipAlgorithm::clone() const +QgsClipAlgorithm *QgsClipAlgorithm::create() const { return new QgsClipAlgorithm(); } @@ -620,7 +620,7 @@ QString QgsTransformAlgorithm::shortHelpString() const "Attributes are not modified by this algorithm." ); } -QgsTransformAlgorithm *QgsTransformAlgorithm::clone() const +QgsTransformAlgorithm *QgsTransformAlgorithm::create() const { return new QgsTransformAlgorithm(); } @@ -699,7 +699,7 @@ QString QgsSubdivideAlgorithm::shortHelpString() const "Curved geometries will be segmentized before subdivision." ); } -QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::clone() const +QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::create() const { return new QgsSubdivideAlgorithm(); } @@ -779,7 +779,7 @@ QString QgsMultipartToSinglepartAlgorithm::shortHelpString() const "contain, and the same attributes are used for each of them." ); } -QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::clone() const +QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::create() const { return new QgsMultipartToSinglepartAlgorithm(); } @@ -876,7 +876,7 @@ QString QgsExtractByExpressionAlgorithm::shortHelpString() const "For more information about expressions see the user manual" ); } -QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::clone() const +QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::create() const { return new QgsExtractByExpressionAlgorithm(); } @@ -1017,7 +1017,7 @@ QString QgsExtractByAttributeAlgorithm::shortHelpString() const "of an attribute from the input layer." ); } -QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::clone() const +QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::create() const { return new QgsExtractByAttributeAlgorithm(); } diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index d788b6762078..47d3c0a008d7 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -60,7 +60,7 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; - QgsCentroidAlgorithm *clone() const override SIP_FACTORY; + QgsCentroidAlgorithm *create() const override SIP_FACTORY; protected: @@ -91,7 +91,7 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "transform,reproject,crs,srs,warp" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector general tools" ); } QString shortHelpString() const override; - QgsTransformAlgorithm *clone() const override SIP_FACTORY; + QgsTransformAlgorithm *create() const override SIP_FACTORY; protected: @@ -123,7 +123,7 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "buffer,grow" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; - QgsBufferAlgorithm *clone() const override SIP_FACTORY; + QgsBufferAlgorithm *create() const override SIP_FACTORY; protected: @@ -166,7 +166,7 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "dissolve,union,combine,collect" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; - QgsDissolveAlgorithm *clone() const override SIP_FACTORY; + QgsDissolveAlgorithm *create() const override SIP_FACTORY; protected: @@ -214,7 +214,7 @@ class QgsExtractByAttributeAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "extract,filter,attribute,value,contains,null,field" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector selection tools" ); } QString shortHelpString() const override; - QgsExtractByAttributeAlgorithm *clone() const override SIP_FACTORY; + QgsExtractByAttributeAlgorithm *create() const override SIP_FACTORY; protected: @@ -251,7 +251,7 @@ class QgsExtractByExpressionAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "extract,filter,expression,field" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector selection tools" ); } QString shortHelpString() const override; - QgsExtractByExpressionAlgorithm *clone() const override SIP_FACTORY; + QgsExtractByExpressionAlgorithm *create() const override SIP_FACTORY; protected: @@ -286,7 +286,7 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "clip,intersect,intersection,mask" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector overlay tools" ); } QString shortHelpString() const override; - QgsClipAlgorithm *clone() const override SIP_FACTORY; + QgsClipAlgorithm *create() const override SIP_FACTORY; protected: @@ -321,7 +321,7 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "subdivide,segmentize,split,tesselate" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; - QgsSubdivideAlgorithm *clone() const override SIP_FACTORY; + QgsSubdivideAlgorithm *create() const override SIP_FACTORY; protected: @@ -354,7 +354,7 @@ class QgsMultipartToSinglepartAlgorithm : public QgsProcessingAlgorithm virtual QStringList tags() const override { return QObject::tr( "multi,single,multiple,split,dump" ).split( ',' ); } QString group() const override { return QObject::tr( "Vector geometry tools" ); } QString shortHelpString() const override; - QgsMultipartToSinglepartAlgorithm *clone() const override SIP_FACTORY; + QgsMultipartToSinglepartAlgorithm *create() const override SIP_FACTORY; protected: diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index b63b82040552..a60c7dd5a7d2 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -321,7 +321,7 @@ bool QgsProcessingAlgorithm::hasHtmlOutputs() const QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok ) const { - std::unique_ptr< QgsProcessingAlgorithm > alg( clone() ); + std::unique_ptr< QgsProcessingAlgorithm > alg( create() ); if ( ok ) *ok = false; diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 4326b8411497..81a9a2077e88 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -68,9 +68,9 @@ class CORE_EXPORT QgsProcessingAlgorithm QgsProcessingAlgorithm &operator=( const QgsProcessingAlgorithm &other ) = delete; /** - * Clones the algorithm, returning a new copy for safe use in background threads. + * Creates a copy of the algorithm, ready for execution. */ - virtual QgsProcessingAlgorithm *clone() const = 0 SIP_FACTORY; + virtual QgsProcessingAlgorithm *create() const = 0 SIP_FACTORY; /** * Returns the algorithm name, used for identifying the algorithm. This string diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index ccd68b26ef22..c5f5cb92380c 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -27,7 +27,7 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgor , mParameters( parameters ) , mContext( context ) , mFeedback( feedback ) - , mAlgorithm( algorithm->clone() ) + , mAlgorithm( algorithm->create() ) { if ( !mFeedback ) { diff --git a/src/core/processing/qgsprocessingmodelalgorithm.cpp b/src/core/processing/qgsprocessingmodelalgorithm.cpp index 30ae04f13e6d..367afeafae81 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/qgsprocessingmodelalgorithm.cpp @@ -524,7 +524,7 @@ bool QgsProcessingModelAlgorithm::processAlgorithm( QgsProcessingContext &contex childTime.start(); bool ok = false; - std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->clone() ); + std::unique_ptr< QgsProcessingAlgorithm > childAlg( child.algorithm()->create() ); QVariantMap results = childAlg->run( childParams, context, feedback, &ok ); childAlg.reset( nullptr ); if ( !ok ) @@ -1115,7 +1115,7 @@ QString QgsProcessingModelAlgorithm::asPythonCommand( const QVariantMap ¶met return QgsProcessingAlgorithm::asPythonCommand( parameters, context ); } -QgsProcessingModelAlgorithm *QgsProcessingModelAlgorithm::clone() const +QgsProcessingModelAlgorithm *QgsProcessingModelAlgorithm::create() const { QgsProcessingModelAlgorithm *alg = new QgsProcessingModelAlgorithm(); alg->loadVariant( toVariant() ); diff --git a/src/core/processing/qgsprocessingmodelalgorithm.h b/src/core/processing/qgsprocessingmodelalgorithm.h index 9ba43a6cfbfb..0c8ac246f855 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.h +++ b/src/core/processing/qgsprocessingmodelalgorithm.h @@ -590,7 +590,7 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm bool canExecute( QString *errorMessage SIP_OUT = nullptr ) const override; QString asPythonCommand( const QVariantMap ¶meters, QgsProcessingContext &context ) const override; - QgsProcessingModelAlgorithm *clone() const override SIP_FACTORY; + QgsProcessingModelAlgorithm *create() const override SIP_FACTORY; /** * Sets the model \a name. diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 12897ade0e87..3401a5e73dca 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -47,7 +47,7 @@ class DummyAlgorithm : public QgsProcessingAlgorithm QVariantMap postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); } virtual Flags flags() const override { return mFlags; } - DummyAlgorithm *clone() const override { return new DummyAlgorithm( name() ); } + DummyAlgorithm *create() const override { return new DummyAlgorithm( name() ); } QString mName; From 7a1fd93ace933d650ad7aa97b82b2ba2dbca7405 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 12:41:39 +1000 Subject: [PATCH 10/49] Flush sink buffers instead of deleting sink/sources in processAlgorithm Avoids potential issues if processAlgorithm is run in a different thread --- src/core/processing/qgsnativealgorithms.cpp | 36 ++++++++------------- 1 file changed, 14 insertions(+), 22 deletions(-) diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 82b56c0c6358..1cf40d97bf49 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -134,8 +134,7 @@ bool QgsCentroidAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi feedback->setProgress( current * step ); current++; } - mSource.reset(); - mSink.reset(); + mSink->flushBuffer(); return true; } @@ -268,8 +267,7 @@ bool QgsBufferAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessing mSink->addFeature( f, QgsFeatureSink::FastInsert ); } - mSource.reset(); - mSink.reset(); + mSink->flushBuffer(); return true; } @@ -427,9 +425,7 @@ bool QgsDissolveAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi } } - mSource.reset(); - mSink.reset(); - + mSink->flushBuffer(); return true; } @@ -592,9 +588,7 @@ bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFe feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() ); } } - mFeatureSource.reset(); - mMaskSource.reset(); - mSink.reset(); + mSink->flushBuffer(); return true; } @@ -751,8 +745,7 @@ bool QgsSubdivideAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcess feedback->setProgress( current * step ); current++; } - mSource.reset(); - mSink.reset(); + mSink->flushBuffer(); return true; } @@ -844,9 +837,7 @@ bool QgsMultipartToSinglepartAlgorithm::processAlgorithm( QgsProcessingContext & feedback->setProgress( current * step ); current++; } - mSource.reset(); - mSink.reset(); - + mSink->flushBuffer(); return true; } @@ -968,10 +959,10 @@ bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, current++; } } - - mSource.reset(); - mMatchingSink.reset(); - mNonMatchingSink.reset(); + if ( mMatchingSink ) + mMatchingSink->flushBuffer(); + if ( mNonMatchingSink ) + mNonMatchingSink->flushBuffer(); return true; } @@ -1177,9 +1168,10 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q } } - mSource.reset(); - mMatchingSink.reset(); - mNonMatchingSink.reset(); + if ( mMatchingSink ) + mMatchingSink->flushBuffer(); + if ( mNonMatchingSink ) + mNonMatchingSink->flushBuffer(); return true; } From 8cfcf5754113fff862c4ca13f17aa7df2b2bfc0b Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 12:42:18 +1000 Subject: [PATCH 11/49] Fix tests --- python/plugins/processing/algs/qgis/Aspect.py | 2 +- .../algs/qgis/PointsLayerFromTable.py | 65 ++++++++++--------- 2 files changed, 36 insertions(+), 31 deletions(-) diff --git a/python/plugins/processing/algs/qgis/Aspect.py b/python/plugins/processing/algs/qgis/Aspect.py index 6167c6965308..5de4b860d20e 100644 --- a/python/plugins/processing/algs/qgis/Aspect.py +++ b/python/plugins/processing/algs/qgis/Aspect.py @@ -49,7 +49,7 @@ class Aspect(QgisAlgorithm): INPUT = 'INPUT' Z_FACTOR = 'Z_FACTOR' - OUTPUT_LAYER = 'OUTPUT_LAYER' + OUTPUT = 'OUTPUT' def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'dem.png')) diff --git a/python/plugins/processing/algs/qgis/PointsLayerFromTable.py b/python/plugins/processing/algs/qgis/PointsLayerFromTable.py index d50d15135b1a..69a737293a9c 100644 --- a/python/plugins/processing/algs/qgis/PointsLayerFromTable.py +++ b/python/plugins/processing/algs/qgis/PointsLayerFromTable.py @@ -28,10 +28,8 @@ from qgis.core import (QgsApplication, QgsWkbTypes, QgsPoint, - QgsCoordinateReferenceSystem, QgsFeatureRequest, QgsGeometry, - QgsProcessingUtils, QgsProcessingParameterDefinition, QgsProcessingParameterFeatureSink, QgsProcessingParameterFeatureSource, @@ -39,11 +37,6 @@ QgsProcessingOutputVectorLayer, QgsProcessingParameterField) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterTable -from processing.core.parameters import ParameterTableField -from processing.core.parameters import ParameterCrs -from processing.core.outputs import OutputVector -from processing.tools import dataobjects class PointsLayerFromTable(QgisAlgorithm): @@ -87,39 +80,49 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Points from table'), type=QgsProcessingParameterDefinition.TypeVectorPoint)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Points from table'), type=QgsProcessingParameterDefinition.TypeVectorPoint)) + self.source = None + self.x_field_index = None + self.y_field_index = None + self.z_field_index = None + self.m_field_index = None + self.sink = None + self.dest_id = None + def name(self): return 'createpointslayerfromtable' def displayName(self): return self.tr('Create points layer from table') - def processAlgorithm(self, parameters, context, feedback): - source = self.parameterAsSource(parameters, self.INPUT, context) + def prepareAlgorithm(self, parameters, context, feedback): + self.source = self.parameterAsSource(parameters, self.INPUT, context) - fields = source.fields() - x_field_index = fields.lookupField(self.parameterAsString(parameters, self.XFIELD, context)) - y_field_index = fields.lookupField(self.parameterAsString(parameters, self.YFIELD, context)) - z_field_index = -1 + fields = self.source.fields() + self.x_field_index = fields.lookupField(self.parameterAsString(parameters, self.XFIELD, context)) + self.y_field_index = fields.lookupField(self.parameterAsString(parameters, self.YFIELD, context)) + self.z_field_index = -1 if self.parameterAsString(parameters, self.ZFIELD, context): - z_field_index = fields.lookupField(self.parameterAsString(parameters, self.ZFIELD, context)) - m_field_index = -1 + self.z_field_index = fields.lookupField(self.parameterAsString(parameters, self.ZFIELD, context)) + self.m_field_index = -1 if self.parameterAsString(parameters, self.MFIELD, context): - m_field_index = fields.lookupField(self.parameterAsString(parameters, self.MFIELD, context)) + self.m_field_index = fields.lookupField(self.parameterAsString(parameters, self.MFIELD, context)) wkb_type = QgsWkbTypes.Point - if z_field_index >= 0: + if self.z_field_index >= 0: wkb_type = QgsWkbTypes.addZ(wkb_type) - if m_field_index >= 0: + if self.m_field_index >= 0: wkb_type = QgsWkbTypes.addM(wkb_type) target_crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) - (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkb_type, target_crs) + (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkb_type, target_crs) + return True + def processAlgorithm(self, context, feedback): request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = source.getFeatures() - total = 100.0 / source.featureCount() if source.featureCount() else 0 + features = self.source.getFeatures(request) + total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 for current, feature in enumerate(features): if feedback.isCanceled(): @@ -129,20 +132,20 @@ def processAlgorithm(self, parameters, context, feedback): attrs = feature.attributes() try: - x = float(attrs[x_field_index]) - y = float(attrs[y_field_index]) + x = float(attrs[self.x_field_index]) + y = float(attrs[self.y_field_index]) point = QgsPoint(x, y) - if z_field_index >= 0: + if self.z_field_index >= 0: try: - point.addZValue(float(attrs[z_field_index])) + point.addZValue(float(attrs[self.z_field_index])) except: point.addZValue(0.0) - if m_field_index >= 0: + if self.m_field_index >= 0: try: - point.addMValue(float(attrs[m_field_index])) + point.addMValue(float(attrs[self.m_field_index])) except: point.addMValue(0.0) @@ -150,6 +153,8 @@ def processAlgorithm(self, parameters, context, feedback): except: pass # no geometry - sink.addFeature(feature) + self.sink.addFeature(feature) + return True - return {self.OUTPUT: dest_id} + def postProcessAlgorithm(self, context, feedback): + return {self.OUTPUT: self.dest_id} From 1b2086e8a6c789d7ec38a7f59c73c9888aaa87de Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 13:09:44 +1000 Subject: [PATCH 12/49] Make algorithm dialog use background tasks to execute algorithms --- .../plugins/processing/gui/AlgorithmDialog.py | 30 ++++++++++++------- .../processing/gui/AlgorithmDialogBase.py | 28 ++++++++++++----- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/python/plugins/processing/gui/AlgorithmDialog.py b/python/plugins/processing/gui/AlgorithmDialog.py index 4220ea3b8468..59799c7801a9 100644 --- a/python/plugins/processing/gui/AlgorithmDialog.py +++ b/python/plugins/processing/gui/AlgorithmDialog.py @@ -34,11 +34,13 @@ from qgis.PyQt.QtGui import QCursor, QColor, QPalette from qgis.core import (QgsProject, + QgsApplication, QgsProcessingUtils, QgsMessageLog, QgsProcessingParameterDefinition, QgsProcessingOutputRasterLayer, QgsProcessingOutputVectorLayer, + QgsProcessingAlgRunnerTask, QgsProcessingOutputHtml, QgsProcessingParameterVectorOutput, QgsProcessingOutputLayerDefinition, @@ -244,18 +246,24 @@ def accept(self): if command: ProcessingLog.addToLog(command) self.buttonCancel.setEnabled(self.alg.flags() & QgsProcessingAlgorithm.FlagCanCancel) - result, ok = executeAlgorithm(self.alg, parameters, context, feedback) - if ok: - feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds'.format(time.time() - start_time))) - feedback.pushInfo(self.tr('Results:')) - feedback.pushCommandInfo(pformat(result)) - else: - feedback.reportError( - self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - start_time))) - feedback.pushInfo('') - self.buttonCancel.setEnabled(False) - self.finish(result, context, feedback) + def on_complete(ok, results): + if ok: + feedback.pushInfo(self.tr('Execution completed in {0:0.2f} seconds'.format(time.time() - start_time))) + feedback.pushInfo(self.tr('Results:')) + feedback.pushCommandInfo(pformat(results)) + else: + feedback.reportError( + self.tr('Execution failed after {0:0.2f} seconds'.format(time.time() - start_time))) + feedback.pushInfo('') + + self.buttonCancel.setEnabled(False) + self.finish(results, context, feedback) + + task = QgsProcessingAlgRunnerTask(self.alg, parameters, context, feedback) + task.executed.connect(on_complete) + QgsApplication.taskManager().addTask(task) + except AlgorithmDialogBase.InvalidParameterValue as e: try: self.buttonBox.accepted.connect(lambda e=e: diff --git a/python/plugins/processing/gui/AlgorithmDialogBase.py b/python/plugins/processing/gui/AlgorithmDialogBase.py index 431a15309aa5..29a948c348ce 100644 --- a/python/plugins/processing/gui/AlgorithmDialogBase.py +++ b/python/plugins/processing/gui/AlgorithmDialogBase.py @@ -31,7 +31,7 @@ import html from qgis.PyQt import uic -from qgis.PyQt.QtCore import Qt, QCoreApplication, QByteArray, QUrl +from qgis.PyQt.QtCore import pyqtSignal, Qt, QCoreApplication, QByteArray, QUrl from qgis.PyQt.QtWidgets import QApplication, QDialogButtonBox, QVBoxLayout, QToolButton from qgis.utils import iface @@ -52,27 +52,34 @@ class AlgorithmDialogFeedback(QgsProcessingFeedback): Directs algorithm feedback to an algorithm dialog """ + error = pyqtSignal(str) + progress_text = pyqtSignal(str) + info = pyqtSignal(str) + command_info = pyqtSignal(str) + debug_info = pyqtSignal(str) + console_info = pyqtSignal(str) + def __init__(self, dialog): QgsProcessingFeedback.__init__(self) self.dialog = dialog def reportError(self, msg): - self.dialog.error(msg) + self.error.emit(msg) def setProgressText(self, text): - self.dialog.setText(text) + self.progress_text.emit(text) def pushInfo(self, msg): - self.dialog.setInfo(msg) + self.info.emit(msg) def pushCommandInfo(self, msg): - self.dialog.setCommand(msg) + self.command_info.emit(msg) def pushDebugInfo(self, msg): - self.dialog.setDebugInfo(msg) + self.debug_info.emit(msg) def pushConsoleInfo(self, msg): - self.dialog.setConsoleInfo(msg) + self.console_info.emit(msg) class AlgorithmDialogBase(BASE, WIDGET): @@ -151,6 +158,13 @@ def linkClicked(url): def createFeedback(self): feedback = AlgorithmDialogFeedback(self) feedback.progressChanged.connect(self.setPercentage) + feedback.error.connect(self.error) + feedback.progress_text.connect(self.setText) + feedback.info.connect(self.setInfo) + feedback.command_info.connect(self.setCommand) + feedback.debug_info.connect(self.setDebugInfo) + feedback.console_info.connect(self.setConsoleInfo) + self.buttonCancel.clicked.connect(feedback.cancel) return feedback From 2543e079a59be70a30a44a23d242bd89860c60e4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 13:14:45 +1000 Subject: [PATCH 13/49] Better assert messages Issues is actually when prepare/postProcess is called in a different thread to context - it doesn't have to be the main thread --- src/core/processing/qgsprocessingalgorithm.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index a60c7dd5a7d2..a89b8c2a68d7 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -341,7 +341,7 @@ QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProce bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - Q_ASSERT_X( QApplication::instance()->thread() == QThread::currentThread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the main thread" ); + Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" ); Q_ASSERT_X( !mHasPrepared, "QgsProcessingAlgorithm::prepare", "prepare() has already been called for the algorithm instance" ); try { @@ -376,7 +376,7 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - Q_ASSERT_X( QApplication::instance()->thread() == QThread::currentThread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the main thread" ); + Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" ); Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", "runPrepared() was not called for the algorithm instance" ); Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" ); From 1dce45961018a68a0703290af310d9fae55efb5c Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:18:26 +1000 Subject: [PATCH 14/49] Add method to transfer all layers from one map store to another With a note and assert that both stores must have the same thread affinity --- python/core/qgsmaplayerstore.sip | 7 ++++++ src/core/qgsmaplayerstore.cpp | 17 +++++++++++++ src/core/qgsmaplayerstore.h | 7 ++++++ tests/src/python/test_qgsmaplayerstore.py | 30 +++++++++++++++++++++++ 4 files changed, 61 insertions(+) diff --git a/python/core/qgsmaplayerstore.sip b/python/core/qgsmaplayerstore.sip index ea3b27e2623c..f2848b4711a4 100644 --- a/python/core/qgsmaplayerstore.sip +++ b/python/core/qgsmaplayerstore.sip @@ -208,6 +208,13 @@ class QgsMapLayerStore : QObject .. seealso:: removeMapLayers() %End + void transferLayersFromStore( QgsMapLayerStore *other ); +%Docstring + Transfers all the map layers contained within another map layer store and adds + them to this store. + Note that ``other`` and this store must have the same thread affinity. +%End + signals: void layersWillBeRemoved( const QStringList &layerIds ); diff --git a/src/core/qgsmaplayerstore.cpp b/src/core/qgsmaplayerstore.cpp index 01e489639cfb..9d6e409c3ab1 100644 --- a/src/core/qgsmaplayerstore.cpp +++ b/src/core/qgsmaplayerstore.cpp @@ -180,6 +180,23 @@ void QgsMapLayerStore::removeAllMapLayers() mMapLayers.clear(); } +void QgsMapLayerStore::transferLayersFromStore( QgsMapLayerStore *other ) +{ + if ( !other || other == this ) + return; + + Q_ASSERT_X( other->thread() == thread(), "QgsMapLayerStore::transferLayersFromStore", "Cannot transfer layers from store with different thread affinity" ); + + QMap otherLayers = other->mapLayers(); + QMap::const_iterator it = otherLayers.constBegin(); + for ( ; it != otherLayers.constEnd(); ++it ) + { + QgsMapLayer *layer = other->takeMapLayer( it.value() ); + if ( layer ) + addMapLayer( layer ); + } +} + void QgsMapLayerStore::onMapLayerDeleted( QObject *obj ) { QString id = mMapLayers.key( static_cast( obj ) ); diff --git a/src/core/qgsmaplayerstore.h b/src/core/qgsmaplayerstore.h index d8d72656028e..1b6269b107fd 100644 --- a/src/core/qgsmaplayerstore.h +++ b/src/core/qgsmaplayerstore.h @@ -238,6 +238,13 @@ class CORE_EXPORT QgsMapLayerStore : public QObject */ void removeAllMapLayers(); + /** + * Transfers all the map layers contained within another map layer store and adds + * them to this store. + * Note that \a other and this store must have the same thread affinity. + */ + void transferLayersFromStore( QgsMapLayerStore *other ); + signals: /** diff --git a/tests/src/python/test_qgsmaplayerstore.py b/tests/src/python/test_qgsmaplayerstore.py index 8924235ec8a7..d860a268643a 100644 --- a/tests/src/python/test_qgsmaplayerstore.py +++ b/tests/src/python/test_qgsmaplayerstore.py @@ -495,6 +495,36 @@ def testTakeLayer(self): store = None self.assertTrue(l1.isValid()) + def testTransferLayers(self): + # test transferring all layers from another store + store1 = QgsMapLayerStore() + store2 = QgsMapLayerStore() + + # empty stores + store1.transferLayersFromStore(store2) + + # silly behavior checks + store1.transferLayersFromStore(None) + store1.transferLayersFromStore(store1) + + l1 = createLayer('l1') + l2 = createLayer('l2') + store1.addMapLayer(l1) + store1.addMapLayer(l2) + + l3 = createLayer('l3') + store2.addMapLayer(l3) + + store2.transferLayersFromStore(store1) + self.assertFalse(store1.mapLayers()) # no layers left + self.assertEqual(len(store2.mapLayers()), 3) + self.assertEqual(store2.mapLayers(), {l1.id(): l1, l2.id(): l2, l3.id(): l3}) + + store1.transferLayersFromStore(store2) + self.assertFalse(store2.mapLayers()) # no layers left + self.assertEqual(len(store1.mapLayers()), 3) + self.assertEqual(store1.mapLayers(), {l1.id(): l1, l2.id(): l2, l3.id(): l3}) + if __name__ == '__main__': unittest.main() From 6c6f646291c6fb512911e8144427640a9bcef253 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:23:43 +1000 Subject: [PATCH 15/49] Add methods to retrieve current thread affinity and push contexts to another thread With suitable assert in place to ensure that pushes are only made when current thread == existing thread affinity --- .../core/processing/qgsprocessingcontext.sip | 15 +++++++++++++++ src/core/processing/qgsprocessingcontext.h | 18 ++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/python/core/processing/qgsprocessingcontext.sip b/python/core/processing/qgsprocessingcontext.sip index a701fa9d7c84..368acaeb3591 100644 --- a/python/core/processing/qgsprocessingcontext.sip +++ b/python/core/processing/qgsprocessingcontext.sip @@ -214,6 +214,21 @@ Destination project and no errors will occur if feedback is deleted before the context. Ownership of ``feedback`` is not transferred. .. seealso:: setFeedback() +%End + + QThread *thread(); +%Docstring + Returns the thread in which the context lives. +.. seealso:: pushToThread() + :rtype: QThread +%End + + void pushToThread( QThread *thread ); +%Docstring + Pushes the thread affinity for the context (including all layers contained in the temporaryLayerStore()) into + another ``thread``. This method is only safe to call when the current thread matches the existing thread + affinity for the context (see thread()). +.. seealso:: thread() %End private: diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 4ba4fb5ad759..432ea13f0ebb 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -310,6 +310,24 @@ class CORE_EXPORT QgsProcessingContext */ void setFeedback( QgsProcessingFeedback *feedback ) { mFeedback = feedback; } + /** + * Returns the thread in which the context lives. + * \see pushToThread() + */ + QThread *thread() { return tempLayerStore.thread(); } + + /** + * Pushes the thread affinity for the context (including all layers contained in the temporaryLayerStore()) into + * another \a thread. This method is only safe to call when the current thread matches the existing thread + * affinity for the context (see thread()). + * \see thread() + */ + void pushToThread( QThread *thread ) + { + Q_ASSERTX( QThread::currentThread() == thread(), "QgsProcessingContext::pushToThread", "Cannot push context to another thread unless the current thread matches the existing context thread affinity" ); + tempLayerStore.moveToThread( thread ); + } + private: QgsProcessingContext::Flags mFlags = 0; From d20c68d3f136d769d6933a09dd7400075cd96e86 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:36:37 +1000 Subject: [PATCH 16/49] Add method to copy thread safe settings between processing contexts --- python/core/processing/qgsprocessingcontext.sip | 6 ++++++ src/core/processing/qgsprocessingcontext.h | 16 ++++++++++++++++ tests/src/core/testqgsprocessing.cpp | 7 +++++++ 3 files changed, 29 insertions(+) diff --git a/python/core/processing/qgsprocessingcontext.sip b/python/core/processing/qgsprocessingcontext.sip index 368acaeb3591..5b7d9897832b 100644 --- a/python/core/processing/qgsprocessingcontext.sip +++ b/python/core/processing/qgsprocessingcontext.sip @@ -38,6 +38,12 @@ class QgsProcessingContext %End + void copyThreadSafeSettings( const QgsProcessingContext &other ); +%Docstring + Copies all settings which are safe for use across different threads from + ``other`` to this context. +%End + QgsProcessingContext::Flags flags() const; %Docstring Returns any flags set in the context. diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 432ea13f0ebb..8ec8ac612df3 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -66,6 +66,22 @@ class CORE_EXPORT QgsProcessingContext //! QgsProcessingContext cannot be copied QgsProcessingContext &operator=( const QgsProcessingContext &other ) = delete; + /** + * Copies all settings which are safe for use across different threads from + * \a other to this context. + */ + void copyThreadSafeSettings( const QgsProcessingContext &other ) + { + mFlags = other.mFlags; + mProject = other.mProject; + mExpressionContext = other.mExpressionContext; + mInvalidGeometryCallback = other.mInvalidGeometryCallback; + mInvalidGeometryCheck = other.mInvalidGeometryCheck; + mTransformErrorCallback = other.mTransformErrorCallback; + mDefaultEncoding = other.mDefaultEncoding; + mFeedback = other.mFeedback; + } + /** * Returns any flags set in the context. * \see setFlags() diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 3401a5e73dca..3a6a9681a630 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -583,6 +583,13 @@ void TestQgsProcessing::context() context.setInvalidGeometryCheck( QgsFeatureRequest::GeometrySkipInvalid ); QCOMPARE( context.invalidGeometryCheck(), QgsFeatureRequest::GeometrySkipInvalid ); + QgsProcessingContext context2; + context2.copyThreadSafeSettings( context ); + QCOMPARE( context2.defaultEncoding(), context.defaultEncoding() ); + QCOMPARE( context2.invalidGeometryCheck(), context.invalidGeometryCheck() ); + QCOMPARE( context2.flags(), context.flags() ); + QCOMPARE( context2.project(), context.project() ); + // layers to load on completion QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V1", "memory" ); QgsVectorLayer *v2 = new QgsVectorLayer( "Polygon", "V2", "memory" ); From 5350483c907008854553272d6f9bbacb47213873 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:45:38 +1000 Subject: [PATCH 17/49] Add method to take results from another processing context and add to the current context With appropriate note and tests to ensure that both the current context and that which the results being taken from share the same thread affinity --- .../core/processing/qgsprocessingcontext.sip | 9 +++++++ src/core/processing/qgsprocessingcontext.h | 21 ++++++++++++++- tests/src/core/testqgsprocessing.cpp | 27 +++++++++++++++++-- 3 files changed, 54 insertions(+), 3 deletions(-) diff --git a/python/core/processing/qgsprocessingcontext.sip b/python/core/processing/qgsprocessingcontext.sip index 5b7d9897832b..555d2c9ebf77 100644 --- a/python/core/processing/qgsprocessingcontext.sip +++ b/python/core/processing/qgsprocessingcontext.sip @@ -237,6 +237,15 @@ Destination project .. seealso:: thread() %End + void takeResultsFrom( QgsProcessingContext &context ); +%Docstring + Takes the results from another ``context`` and merges them with the results currently + stored in this context. This includes settings like any layers loaded in the temporaryLayerStore() + and layersToLoadOnCompletion(). + This is only safe to call when both this context and the other ``context`` share the same + thread() affinity, and that thread is the current thread. +%End + private: QgsProcessingContext( const QgsProcessingContext &other ); }; diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 8ec8ac612df3..1e9d83b6066e 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -340,10 +340,29 @@ class CORE_EXPORT QgsProcessingContext */ void pushToThread( QThread *thread ) { - Q_ASSERTX( QThread::currentThread() == thread(), "QgsProcessingContext::pushToThread", "Cannot push context to another thread unless the current thread matches the existing context thread affinity" ); + Q_ASSERT_X( QThread::currentThread() == QgsProcessingContext::thread(), "QgsProcessingContext::pushToThread", "Cannot push context to another thread unless the current thread matches the existing context thread affinity" ); tempLayerStore.moveToThread( thread ); } + /** + * Takes the results from another \a context and merges them with the results currently + * stored in this context. This includes settings like any layers loaded in the temporaryLayerStore() + * and layersToLoadOnCompletion(). + * This is only safe to call when both this context and the other \a context share the same + * thread() affinity, and that thread is the current thread. + */ + void takeResultsFrom( QgsProcessingContext &context ) + { + QMap< QString, LayerDetails > loadOnCompletion = context.layersToLoadOnCompletion(); + QMap< QString, LayerDetails >::const_iterator llIt = loadOnCompletion.constBegin(); + for ( ; llIt != loadOnCompletion.constEnd(); ++llIt ) + { + mLayersToLoadOnCompletion.insert( llIt.key(), llIt.value() ); + } + context.setLayersToLoadOnCompletion( QMap< QString, LayerDetails >() ); + tempLayerStore.transferLayersFromStore( context.temporaryLayerStore() ); + } + private: QgsProcessingContext::Flags mFlags = 0; diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 3a6a9681a630..55aa7be656f6 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -583,12 +583,18 @@ void TestQgsProcessing::context() context.setInvalidGeometryCheck( QgsFeatureRequest::GeometrySkipInvalid ); QCOMPARE( context.invalidGeometryCheck(), QgsFeatureRequest::GeometrySkipInvalid ); + QgsVectorLayer *vector = new QgsVectorLayer( "Polygon", "vector", "memory" ); + context.temporaryLayerStore()->addMapLayer( vector ); + QCOMPARE( context.temporaryLayerStore()->mapLayer( vector->id() ), vector ); + QgsProcessingContext context2; context2.copyThreadSafeSettings( context ); QCOMPARE( context2.defaultEncoding(), context.defaultEncoding() ); QCOMPARE( context2.invalidGeometryCheck(), context.invalidGeometryCheck() ); QCOMPARE( context2.flags(), context.flags() ); QCOMPARE( context2.project(), context.project() ); + // layers from temporaryLayerStore must not be copied by copyThreadSafeSettings + QVERIFY( context2.temporaryLayerStore()->mapLayers().isEmpty() ); // layers to load on completion QgsVectorLayer *v1 = new QgsVectorLayer( "Polygon", "V1", "memory" ); @@ -606,6 +612,11 @@ void TestQgsProcessing::context() QCOMPARE( context.layersToLoadOnCompletion().values().at( 0 ).name, QStringLiteral( "v1" ) ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); QCOMPARE( context.layersToLoadOnCompletion().values().at( 1 ).name, QStringLiteral( "v2" ) ); + + // ensure that copyThreadSafeSettings doesn't copy layersToLoadOnCompletion() + context2.copyThreadSafeSettings( context ); + QVERIFY( context2.layersToLoadOnCompletion().isEmpty() ); + layers.clear(); layers.insert( v2->id(), QgsProcessingContext::LayerDetails( QStringLiteral( "v2" ), &p ) ); context.setLayersToLoadOnCompletion( layers ); @@ -616,8 +627,20 @@ void TestQgsProcessing::context() QCOMPARE( context.layersToLoadOnCompletion().count(), 2 ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); - delete v1; - delete v2; + + context.temporaryLayerStore()->addMapLayer( v1 ); + context.temporaryLayerStore()->addMapLayer( v2 ); + + // test takeResultsFrom + context2.takeResultsFrom( context ); + QVERIFY( context.temporaryLayerStore()->mapLayers().isEmpty() ); + QVERIFY( context.layersToLoadOnCompletion().isEmpty() ); + // should now be in context2 + QCOMPARE( context2.temporaryLayerStore()->mapLayer( v1->id() ), v1 ); + QCOMPARE( context2.temporaryLayerStore()->mapLayer( v2->id() ), v2 ); + QCOMPARE( context2.layersToLoadOnCompletion().count(), 2 ); + QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); + QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); } void TestQgsProcessing::mapLayers() From 0231e773a1aa23e079d7a96c095a8627002326f0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:46:41 +1000 Subject: [PATCH 18/49] Remove unused member --- src/core/processing/qgsprocessingcontext.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 1e9d83b6066e..7f5a26f6fc7a 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -178,13 +178,6 @@ class CORE_EXPORT QgsProcessingContext mLayersToLoadOnCompletion.insert( layer, details ); } - /** - * Returns a map of output values stored in the context. These are grouped with the map keys - * matching the algorithm name for multi-algorithm models. - * \note not available in Python bindings - */ - SIP_SKIP QMap &outputMap() { return mOutputMap; } - /** * Returns the behavior used for checking invalid geometries in input layers. * \see setInvalidGeometryCheck() @@ -369,7 +362,6 @@ class CORE_EXPORT QgsProcessingContext QPointer< QgsProject > mProject; //! Temporary project owned by the context, used for storing temporarily loaded map layers QgsMapLayerStore tempLayerStore; - QMap< QString, QVariantMap > mOutputMap; QgsExpressionContext mExpressionContext; QgsFeatureRequest::InvalidGeometryCheck mInvalidGeometryCheck = QgsFeatureRequest::GeometryNoCheck; std::function< void( const QgsFeature & ) > mInvalidGeometryCallback; From e5b156b86ae6c81af36c760b453f56c4bc6e4da0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:56:16 +1000 Subject: [PATCH 19/49] Make processing algorithms safe to run in threads --- .../core/processing/qgsprocessingcontext.sip | 1 - .../processing/qgsprocessingalgorithm.cpp | 55 ++++++++++++++++++- src/core/processing/qgsprocessingalgorithm.h | 3 +- 3 files changed, 54 insertions(+), 5 deletions(-) diff --git a/python/core/processing/qgsprocessingcontext.sip b/python/core/processing/qgsprocessingcontext.sip index 555d2c9ebf77..dca081ecdd66 100644 --- a/python/core/processing/qgsprocessingcontext.sip +++ b/python/core/processing/qgsprocessingcontext.sip @@ -132,7 +132,6 @@ Destination project .. seealso:: layersToLoadOnCompletion() %End - QgsFeatureRequest::InvalidGeometryCheck invalidGeometryCheck() const; %Docstring Returns the behavior used for checking invalid geometries in input layers. diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index a89b8c2a68d7..2623ea9afb85 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -341,7 +341,7 @@ QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProce bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" ); + Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::prepare", "prepare() must be called from the same thread as context was created in" ); Q_ASSERT_X( !mHasPrepared, "QgsProcessingAlgorithm::prepare", "prepare() has already been called for the algorithm instance" ); try { @@ -361,25 +361,74 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", "prepare() was not called for the algorithm instance" ); Q_ASSERT_X( !mHasExecuted, "QgsProcessingAlgorithm::runPrepared", "runPrepared() was already called for this algorithm instance" ); + // Hey kids, let's all be thread safe! It's the fun thing to do! + // + // First, let's see if we're going to run into issues. + QgsProcessingContext *runContext = nullptr; + if ( context.thread() == QThread::currentThread() ) + { + // OH. No issues. Seems you're running everything in the same thread, so go about your business. Sorry about + // the intrusion, we're just making sure everything's nice and safe here. We like to keep a clean and tidy neighbourhood, + // you know, for the kids and dogs and all. + runContext = &context; + } + else + { + // HA! I knew things looked a bit suspicious - seems you're running this algorithm in a different thread + // from that which the passed context has an affinity for. That's fine and all, but we need to make sure + // we proceed safely... + + // So first we create a temporary local context with affinity for the current thread + mLocalContext.reset( new QgsProcessingContext() ); + // copy across everything we can safely do from the passed context + mLocalContext->copyThreadSafeSettings( context ); + // and we'll run the actual algorithm processing using the local thread safe context + runContext = mLocalContext.get(); + } + try { - mHasExecuted = processAlgorithm( context, feedback ); + mHasExecuted = processAlgorithm( runContext, feedback ); + + if ( mLocalContext ) + { + // ok, time to clean things up. We need to push the temporary context back into + // the thread that the passed context is associated with (we can only push from the + // current thread, so we HAVE to do this here) + mLocalContext->pushToThread( context.thread() ); + } return mHasExecuted; } catch ( QgsProcessingException &e ) { QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); feedback->reportError( e.what() ); + + if ( mLocalContext ) + { + // see above! + mLocalContext->pushToThread( context.thread() ); + } return false; } } QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - Q_ASSERT_X( QApplication::instance()->thread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" ); + Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" ); Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", "runPrepared() was not called for the algorithm instance" ); Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" ); + if ( mLocalContext ) + { + // algorithm was processed using a temporary thread safe context. So now we need + // to take the results from that temporary context, and smash them into the passed + // context + context.takeResultsFrom( *mLocalContext ); + // now get lost, we don't need you anymore + mLocalContext.reset(); + } + mHasPostProcessed = true; try { diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 81a9a2077e88..c72b74054f00 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -22,12 +22,12 @@ #include "qgis.h" #include "qgsprocessingparameters.h" #include "qgsprocessingoutputs.h" +#include "qgsprocessingcontext.h" #include #include #include class QgsProcessingProvider; -class QgsProcessingContext; class QgsProcessingFeedback; class QgsFeatureSink; @@ -539,6 +539,7 @@ class CORE_EXPORT QgsProcessingAlgorithm bool mHasPrepared = false; bool mHasExecuted = false; bool mHasPostProcessed = false; + std::unique_ptr< QgsProcessingContext > mLocalContext; // friend class to access setProvider() - we do not want this public! friend class QgsProcessingProvider; From e1184cd69a74535942d24d619a6fdfa100df38c3 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:58:42 +1000 Subject: [PATCH 20/49] Make QgsProcessingAlgRunnerTask work correctly It now safely can execute algorithms in background threads without issues --- .../core/processing/qgsprocessingalgrunnertask.sip | 9 +++++++++ src/core/processing/qgsprocessingalgrunnertask.cpp | 14 ++++++-------- src/core/processing/qgsprocessingalgrunnertask.h | 10 +++++++++- 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/python/core/processing/qgsprocessingalgrunnertask.sip b/python/core/processing/qgsprocessingalgrunnertask.sip index ab038e924aef..85a1c868331a 100644 --- a/python/core/processing/qgsprocessingalgrunnertask.sip +++ b/python/core/processing/qgsprocessingalgrunnertask.sip @@ -33,6 +33,15 @@ class QgsProcessingAlgRunnerTask : QgsTask virtual void cancel(); + signals: + + void executed( bool successful, const QVariantMap &results ); +%Docstring + Emitted when the algorithm has finished execution. If the algorithm completed + execution without errors then ``successful`` will be true. The ``results`` argument + contains the results reported by the algorithm. +%End + protected: virtual bool run(); diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index c5f5cb92380c..e57716a0bfa9 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -24,7 +24,6 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) : QgsTask( tr( "Running %1" ).arg( algorithm->name() ), QgsTask::CanCancel ) - , mParameters( parameters ) , mContext( context ) , mFeedback( feedback ) , mAlgorithm( algorithm->create() ) @@ -34,6 +33,8 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgor mOwnedFeedback.reset( new QgsProcessingFeedback() ); mFeedback = mOwnedFeedback.get(); } + if ( !mAlgorithm->prepare( parameters, context, mFeedback ) ) + cancel(); } void QgsProcessingAlgRunnerTask::cancel() @@ -47,7 +48,7 @@ bool QgsProcessingAlgRunnerTask::run() bool ok = false; try { - mResults = mAlgorithm->run( mParameters, mContext, mFeedback, &ok ); + ok = mAlgorithm->runPrepared( mContext, mFeedback ); } catch ( QgsProcessingException & ) { @@ -59,12 +60,9 @@ bool QgsProcessingAlgRunnerTask::run() void QgsProcessingAlgRunnerTask::finished( bool result ) { Q_UNUSED( result ); - if ( !mResults.isEmpty() ) + if ( result ) { - QgsMapLayer *layer = QgsProcessingUtils::mapLayerFromString( mResults.value( "OUTPUT_LAYER" ).toString(), mContext ); - if ( layer ) - { - mContext.project()->addMapLayer( mContext.temporaryLayerStore()->takeMapLayer( layer ) ); - } + mResults = mAlgorithm->postProcess( mContext, mFeedback ); } + emit executed( result, mResults ); } diff --git a/src/core/processing/qgsprocessingalgrunnertask.h b/src/core/processing/qgsprocessingalgrunnertask.h index 460f2b433bad..34791b96e123 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.h +++ b/src/core/processing/qgsprocessingalgrunnertask.h @@ -49,6 +49,15 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask virtual void cancel() override; + signals: + + /** + * Emitted when the algorithm has finished execution. If the algorithm completed + * execution without errors then \a successful will be true. The \a results argument + * contains the results reported by the algorithm. + */ + void executed( bool successful, const QVariantMap &results ); + protected: virtual bool run() override; @@ -56,7 +65,6 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask private: - QVariantMap mParameters; QVariantMap mResults; QgsProcessingContext &mContext; QgsProcessingFeedback *mFeedback = nullptr; From e9e335a7bdc39d6d4b37dfdaa4d0b41cbad48481 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:59:17 +1000 Subject: [PATCH 21/49] Don't set thinking cursors when running processing algorithms Since now they're run in a background thread, it's not appropriate anymore --- python/plugins/processing/gui/AlgorithmDialog.py | 4 ---- python/plugins/processing/gui/AlgorithmDialogBase.py | 8 -------- 2 files changed, 12 deletions(-) diff --git a/python/plugins/processing/gui/AlgorithmDialog.py b/python/plugins/processing/gui/AlgorithmDialog.py index 59799c7801a9..6593fb502fe3 100644 --- a/python/plugins/processing/gui/AlgorithmDialog.py +++ b/python/plugins/processing/gui/AlgorithmDialog.py @@ -220,8 +220,6 @@ def accept(self): except: pass - QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) - self.setInfo( self.tr('Algorithm \'{0}\' starting...').format(self.alg.displayName()), escape_html=False) @@ -239,7 +237,6 @@ def accept(self): self.finish(parameters, context, feedback) else: self.buttonCancel.setEnabled(False) - QApplication.restoreOverrideCursor() self.resetGUI() else: command = self.alg.asPythonCommand(parameters, context) @@ -294,7 +291,6 @@ def finish(self, result, context, feedback): self.executed = True self.setInfo(self.tr('Algorithm \'{0}\' finished').format(self.alg.displayName()), escape_html=False) - QApplication.restoreOverrideCursor() if not keepOpen: self.close() diff --git a/python/plugins/processing/gui/AlgorithmDialogBase.py b/python/plugins/processing/gui/AlgorithmDialogBase.py index 29a948c348ce..683caa6c5ee0 100644 --- a/python/plugins/processing/gui/AlgorithmDialogBase.py +++ b/python/plugins/processing/gui/AlgorithmDialogBase.py @@ -188,13 +188,11 @@ def setMainWidget(self, widget): QgsProject.instance().layersWillBeRemoved.connect(self.mainWidget.layerRegistryChanged) def error(self, msg): - QApplication.restoreOverrideCursor() self.setInfo(msg, True) self.resetGUI() self.tabWidget.setCurrentIndex(1) def resetGUI(self): - QApplication.restoreOverrideCursor() self.lblProgress.setText('') self.progressBar.setMaximum(100) self.progressBar.setValue(0) @@ -208,33 +206,27 @@ def setInfo(self, msg, error=False, escape_html=True): self.txtLog.append(html.escape(msg)) else: self.txtLog.append(msg) - QCoreApplication.processEvents() def setCommand(self, cmd): if self.showDebug: self.txtLog.append('{}'.format(html.escape(cmd, quote=False))) - QCoreApplication.processEvents() def setDebugInfo(self, msg): if self.showDebug: self.txtLog.append('{}'.format(html.escape(msg, quote=False))) - QCoreApplication.processEvents() def setConsoleInfo(self, msg): if self.showDebug: self.txtLog.append('{}'.format(html.escape(msg, quote=False))) - QCoreApplication.processEvents() def setPercentage(self, value): if self.progressBar.maximum() == 0: self.progressBar.setMaximum(100) self.progressBar.setValue(value) - QCoreApplication.processEvents() def setText(self, text): self.lblProgress.setText(text) self.setInfo(text, False) - QCoreApplication.processEvents() def getParamValues(self): return {} From 5f02e9c89d1b2a43353dcf961cccbb9289a8d989 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 15:59:45 +1000 Subject: [PATCH 22/49] Avoid QgsFeedback flooding progress report signals We only emit the progress changed signal when there's been at least a 0.1% change since the last progress report. Otherwise it's possible that things like processing algorithms which are reporting feedback every feature in a 500k feature dataset cause a gazillion signals to be emitted and everything to slow to a crawl --- src/core/qgsfeedback.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/core/qgsfeedback.h b/src/core/qgsfeedback.h index 5dabd9b70831..98b042111d81 100644 --- a/src/core/qgsfeedback.h +++ b/src/core/qgsfeedback.h @@ -69,7 +69,14 @@ class CORE_EXPORT QgsFeedback : public QObject * \see progressChanged() * \since QGIS 3.0 */ - void setProgress( double progress ) { mProgress = progress; emit progressChanged( mProgress ); } + void setProgress( double progress ) + { + // avoid flooding with too many events + if ( static_cast< int >( mProgress * 10 ) != static_cast< int >( progress * 10 ) ) + emit progressChanged( progress ); + + mProgress = progress; + } /** * Returns the current progress reported by the feedback object. Depending on how the From f82899540e5327f0dbc8ca2ef23f834b1f58b95e Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 16:10:41 +1000 Subject: [PATCH 23/49] Fix build --- src/core/processing/qgsprocessingalgorithm.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 2623ea9afb85..1f99b2be3cb3 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -388,7 +388,7 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc try { - mHasExecuted = processAlgorithm( runContext, feedback ); + mHasExecuted = processAlgorithm( *runContext, feedback ); if ( mLocalContext ) { From 6654aec6a59beb92d17e8fd2c6eb72764fe03aee Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 17:20:51 +1000 Subject: [PATCH 24/49] Fix execution of script algorithms was not releasing layers/sinks --- python/plugins/processing/script/ScriptAlgorithm.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py index e1afb7e35f1e..def4a112044e 100644 --- a/python/plugins/processing/script/ScriptAlgorithm.py +++ b/python/plugins/processing/script/ScriptAlgorithm.py @@ -256,6 +256,7 @@ def processAlgorithm(self, context, feedback): self.results = {} for out in self.outputDefinitions(): self.results[out.name()] = self.ns[out.name()] + del self.ns return True def postProcessAlgorithm(self, context, feedback): From b917a826624568df7984677430636260020cb277 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 17:21:29 +1000 Subject: [PATCH 25/49] Better debuging from processing alg tests --- python/plugins/processing/tests/AlgorithmsTestBase.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 0f0d4e3b82b3..62270e07ce7c 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -259,11 +259,11 @@ def check_results(self, results, context, params, expected): raise KeyError('Expected result {} does not exist in {}'.format(str(e), list(results.keys()))) if isinstance(results[id], QgsMapLayer): - assert False result_lyr = results[id] else: result_lyr = QgsProcessingUtils.mapLayerFromString(results[id], context) + self.assertTrue(result_lyr, results[id]) compare = expected_result.get('compare', {}) self.assertLayersEqual(expected_lyr, result_lyr, compare=compare) From 274d684f64a352105548f98f39be085dec628eed Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 19:00:21 +1000 Subject: [PATCH 26/49] Fix procesing test layer loading --- python/plugins/processing/tests/AlgorithmsTestBase.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 62270e07ce7c..ee4466213360 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -252,6 +252,7 @@ def check_results(self, results, context, params, expected): expected_lyr = self.load_layer(id, expected_result) if 'in_place_result' in expected_result: result_lyr = QgsProcessingUtils.mapLayerFromString(self.in_place_layers[id], context) + self.assertTrue(result_lyr, self.in_place_layers[id]) else: try: results[id] @@ -262,8 +263,8 @@ def check_results(self, results, context, params, expected): result_lyr = results[id] else: result_lyr = QgsProcessingUtils.mapLayerFromString(results[id], context) + self.assertTrue(result_lyr, results[id]) - self.assertTrue(result_lyr, results[id]) compare = expected_result.get('compare', {}) self.assertLayersEqual(expected_lyr, result_lyr, compare=compare) From 4eca20f28dccabd1238e10a8c556e0bd69a35788 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 19:28:39 +1000 Subject: [PATCH 27/49] Fix zonal stats algorithm execution --- python/plugins/processing/algs/qgis/ZonalStatistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/algs/qgis/ZonalStatistics.py b/python/plugins/processing/algs/qgis/ZonalStatistics.py index 29bec06da2e2..d75b4951863b 100644 --- a/python/plugins/processing/algs/qgis/ZonalStatistics.py +++ b/python/plugins/processing/algs/qgis/ZonalStatistics.py @@ -125,7 +125,7 @@ def processAlgorithm(self, context, feedback): self.rasterLayer, self.columnPrefix, self.bandNumber, - self.selectedStats) + QgsZonalStatistics.Statistics(self.selectedStats)) zs.calculateStatistics(feedback) return True From 4fa2bc025c6c03587eef38145530f4ead48cb02a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 19:29:05 +1000 Subject: [PATCH 28/49] Keep correct order for zonal stats options, set some stats by default --- .../processing/algs/qgis/ZonalStatistics.py | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ZonalStatistics.py b/python/plugins/processing/algs/qgis/ZonalStatistics.py index d75b4951863b..7e8f6df17f9e 100644 --- a/python/plugins/processing/algs/qgis/ZonalStatistics.py +++ b/python/plugins/processing/algs/qgis/ZonalStatistics.py @@ -26,6 +26,7 @@ __revision__ = '$Format:%H$' import os +from collections import OrderedDict from qgis.PyQt.QtGui import QIcon @@ -61,20 +62,19 @@ def group(self): def __init__(self): super().__init__() - self.STATS = {self.tr('Count'): QgsZonalStatistics.Count, - self.tr('Sum'): QgsZonalStatistics.Sum, - self.tr('Mean'): QgsZonalStatistics.Mean, - self.tr('Median'): QgsZonalStatistics.Median, - self.tr('Std. dev.'): QgsZonalStatistics.StDev, - self.tr('Min'): QgsZonalStatistics.Min, - self.tr('Max'): QgsZonalStatistics.Max, - self.tr('Range'): QgsZonalStatistics.Range, - self.tr('Minority'): QgsZonalStatistics.Minority, - self.tr('Majority (mode)'): QgsZonalStatistics.Majority, - self.tr('Variety'): QgsZonalStatistics.Variety, - self.tr('Variance'): QgsZonalStatistics.Variance, - self.tr('All'): QgsZonalStatistics.All - } + self.STATS = OrderedDict([(self.tr('Count'), QgsZonalStatistics.Count), + (self.tr('Sum'), QgsZonalStatistics.Sum), + (self.tr('Mean'), QgsZonalStatistics.Mean), + (self.tr('Median'), QgsZonalStatistics.Median), + (self.tr('Std. dev.'), QgsZonalStatistics.StDev), + (self.tr('Min'), QgsZonalStatistics.Min), + (self.tr('Max'), QgsZonalStatistics.Max), + (self.tr('Range'), QgsZonalStatistics.Range), + (self.tr('Minority'), QgsZonalStatistics.Minority), + (self.tr('Majority (mode)'), QgsZonalStatistics.Majority), + (self.tr('Variety'), QgsZonalStatistics.Variety), + (self.tr('Variance'), QgsZonalStatistics.Variance), + (self.tr('All'), QgsZonalStatistics.All)]) self.addParameter(QgsProcessingParameterRasterLayer(self.INPUT_RASTER, self.tr('Raster layer'))) @@ -86,10 +86,11 @@ def __init__(self): [QgsProcessingParameterDefinition.TypeVectorPolygon])) self.addParameter(QgsProcessingParameterString(self.COLUMN_PREFIX, self.tr('Output column prefix'), '_')) + keys = list(self.STATS.keys()) self.addParameter(QgsProcessingParameterEnum(self.STATISTICS, self.tr('Statistics to calculate'), - list(self.STATS.keys()), - allowMultiple=True)) + keys, + allowMultiple=True, defaultValue=[0,1,2])) self.addOutput(QgsProcessingOutputVectorLayer(self.INPUT_VECTOR, self.tr('Zonal statistics'), QgsProcessingParameterDefinition.TypeVectorPolygon)) From 3108d68f87bc2d2269a0c94301adaceadba03d23 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 19:52:22 +1000 Subject: [PATCH 29/49] Indentation --- python/plugins/processing/algs/qgis/ZonalStatistics.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/plugins/processing/algs/qgis/ZonalStatistics.py b/python/plugins/processing/algs/qgis/ZonalStatistics.py index 7e8f6df17f9e..92a26c3f36be 100644 --- a/python/plugins/processing/algs/qgis/ZonalStatistics.py +++ b/python/plugins/processing/algs/qgis/ZonalStatistics.py @@ -90,7 +90,7 @@ def __init__(self): self.addParameter(QgsProcessingParameterEnum(self.STATISTICS, self.tr('Statistics to calculate'), keys, - allowMultiple=True, defaultValue=[0,1,2])) + allowMultiple=True, defaultValue=[0, 1, 2])) self.addOutput(QgsProcessingOutputVectorLayer(self.INPUT_VECTOR, self.tr('Zonal statistics'), QgsProcessingParameterDefinition.TypeVectorPolygon)) From f39b7a0c4cdb5192fe4185215f85dce2693c7793 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 30 Jun 2017 20:52:14 +1000 Subject: [PATCH 30/49] Fix build warning --- src/core/processing/qgsnativealgorithms.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index 47d3c0a008d7..275f6aef3573 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -147,7 +147,6 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm QgsProperty mDynamicBufferProperty; QVariantMap mDynamicParams; double mDefaultBuffer; - const QgsProcessingParameterDefinition *mDistanceParamDef = nullptr; QgsExpressionContext mExpContext; }; From 8a84e134cc671db7cd969d2af2282ee137de5e85 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 12:21:11 +1000 Subject: [PATCH 31/49] Algorithms don't have to be split to prepare/process/postProcess Since it's safe to evaluate parameters in background threads now, it's usually going to be ok to evaluate everything in the processAlgorithm step. This keeps the algorithm code as simple as possible, and will make porting faster. Note that the prepare/postProcess virtual methods still exist and can be used when an algorithm MUST do setup/cleanup work in the main thread. --- .../processing/qgsprocessingalgorithm.sip | 10 +- .../qgsprocessingmodelalgorithm.sip | 6 +- .../processing/algs/qgis/AddTableField.py | 43 +- python/plugins/processing/algs/qgis/Aspect.py | 26 +- .../algs/qgis/AutoincrementalField.py | 26 +- .../processing/algs/qgis/BasicStatistics.py | 48 +- .../plugins/processing/algs/qgis/Boundary.py | 27 +- .../processing/algs/qgis/BoundingBox.py | 24 +- .../processing/algs/qgis/CheckValidity.py | 102 ++-- .../algs/qgis/CreateAttributeIndex.py | 26 +- .../processing/algs/qgis/DeleteColumn.py | 42 +- .../processing/algs/qgis/DeleteHoles.py | 33 +- .../processing/algs/qgis/DensifyGeometries.py | 29 +- .../algs/qgis/DensifyGeometriesInterval.py | 29 +- .../processing/algs/qgis/DropGeometry.py | 25 +- .../processing/algs/qgis/ExtentFromLayer.py | 27 +- .../processing/algs/qgis/FixGeometry.py | 26 +- .../processing/algs/qgis/GridPolygon.py | 67 +-- .../processing/algs/qgis/ImportIntoPostGIS.py | 110 ++-- .../algs/qgis/ImportIntoSpatialite.py | 104 ++-- python/plugins/processing/algs/qgis/Merge.py | 64 +-- .../algs/qgis/PointsLayerFromTable.py | 58 +- .../processing/algs/qgis/PostGISExecuteSQL.py | 18 +- .../processing/algs/qgis/RandomExtract.py | 46 +- .../algs/qgis/RandomExtractWithinSubsets.py | 50 +- .../processing/algs/qgis/RegularPoints.py | 62 +-- .../algs/qgis/SaveSelectedFeatures.py | 24 +- .../processing/algs/qgis/SelectByAttribute.py | 55 +- .../algs/qgis/SelectByExpression.py | 32 +- .../algs/qgis/SimplifyGeometries.py | 41 +- python/plugins/processing/algs/qgis/Smooth.py | 35 +- .../algs/qgis/SpatialiteExecuteSQL.py | 18 +- .../algs/qgis/SymmetricalDifference.py | 47 +- .../processing/algs/qgis/VectorSplit.py | 51 +- .../processing/algs/qgis/ZonalStatistics.py | 5 +- .../processing/script/ScriptAlgorithm.py | 5 +- .../processing/tests/AlgorithmsTestBase.py | 2 +- src/core/processing/qgsnativealgorithms.cpp | 522 ++++++++---------- src/core/processing/qgsnativealgorithms.h | 132 +---- .../processing/qgsprocessingalgorithm.cpp | 29 +- src/core/processing/qgsprocessingalgorithm.h | 6 +- .../processing/qgsprocessingalgrunnertask.cpp | 11 +- .../processing/qgsprocessingalgrunnertask.h | 1 + .../qgsprocessingmodelalgorithm.cpp | 15 +- .../processing/qgsprocessingmodelalgorithm.h | 7 +- tests/src/core/testqgsprocessing.cpp | 4 +- 46 files changed, 817 insertions(+), 1353 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index 2d71e15e30bf..a0fc43b574e5 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -259,7 +259,7 @@ class QgsProcessingAlgorithm :rtype: bool %End - bool runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + QVariantMap runPrepared( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); %Docstring Runs the algorithm, which has been prepared by an earlier call to prepare(). This method is safe to call from any thread. Returns true if the algorithm was successfully executed. @@ -272,7 +272,7 @@ class QgsProcessingAlgorithm This method modifies the algorithm instance, so it is not safe to call on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. - :rtype: bool + :rtype: QVariantMap %End QVariantMap postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); @@ -374,7 +374,7 @@ class QgsProcessingAlgorithm :rtype: bool %End - virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/; %Docstring Runs the algorithm using the specified ``parameters``. Algorithms should implement their custom processing logic here. @@ -389,10 +389,10 @@ class QgsProcessingAlgorithm values such as statistical calculations. .. seealso:: prepareAlgorithm() .. seealso:: postProcessAlgorithm() - :rtype: bool + :rtype: QVariantMap %End - virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 /VirtualErrorHandler=processing_exception_handler/; + virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) /VirtualErrorHandler=processing_exception_handler/; %Docstring Allows the algorithm to perform any required cleanup tasks. The returned variant map includes the results evaluated by the algorithm. These may be output layer references, or calculated diff --git a/python/core/processing/qgsprocessingmodelalgorithm.sip b/python/core/processing/qgsprocessingmodelalgorithm.sip index defa8cc3430b..d21fd9506714 100644 --- a/python/core/processing/qgsprocessingmodelalgorithm.sip +++ b/python/core/processing/qgsprocessingmodelalgorithm.sip @@ -835,11 +835,7 @@ Copies are protected to avoid slicing protected: - virtual bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ); - virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); - - virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); }; diff --git a/python/plugins/processing/algs/qgis/AddTableField.py b/python/plugins/processing/algs/qgis/AddTableField.py index c5048ca863ba..a92b37945931 100644 --- a/python/plugins/processing/algs/qgis/AddTableField.py +++ b/python/plugins/processing/algs/qgis/AddTableField.py @@ -74,38 +74,28 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Added'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr('Added'))) - self.source = None - self.fieldType = None - self.fieldLength = None - self.fieldName = None - self.fieldPrecision = None - self.sink = None - self.dest_id = None - def name(self): return 'addfieldtoattributestable' def displayName(self): return self.tr('Add field to attributes table') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - self.fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) - self.fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) - self.fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) - self.fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) + fieldType = self.parameterAsEnum(parameters, self.FIELD_TYPE, context) + fieldName = self.parameterAsString(parameters, self.FIELD_NAME, context) + fieldLength = self.parameterAsInt(parameters, self.FIELD_LENGTH, context) + fieldPrecision = self.parameterAsInt(parameters, self.FIELD_PRECISION, context) - fields = self.source.fields() - fields.append(QgsField(self.fieldName, self.TYPES[self.fieldType], '', - self.fieldLength, self.fieldPrecision)) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - fields, self.source.wkbType(), self.source.sourceCrs()) - return True + fields = source.fields() + fields.append(QgsField(fieldName, self.TYPES[fieldType], '', + fieldLength, fieldPrecision)) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + fields, source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -116,10 +106,7 @@ def processAlgorithm(self, context, feedback): attributes.append(None) output_feature.setAttributes(attributes) - self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT_LAYER: self.dest_id} + return {self.OUTPUT_LAYER: dest_id} diff --git a/python/plugins/processing/algs/qgis/Aspect.py b/python/plugins/processing/algs/qgis/Aspect.py index 5de4b860d20e..a371ef3937ee 100644 --- a/python/plugins/processing/algs/qgis/Aspect.py +++ b/python/plugins/processing/algs/qgis/Aspect.py @@ -68,30 +68,22 @@ def __init__(self): self.addParameter(QgsProcessingParameterRasterOutput(self.OUTPUT, self.tr('Aspect'))) self.addOutput(QgsProcessingOutputRasterLayer(self.OUTPUT, self.tr('Aspect'))) - self.inputFile = None - self.outputFile = None - self.outputFormat = None - self.zFactor = None - def name(self): return 'aspect' def displayName(self): return self.tr('Aspect') - def prepareAlgorithm(self, parameters, context, feedback): - self.inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT, context)) - self.zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context) + def processAlgorithm(self, parameters, context, feedback): + inputFile = exportRasterLayer(self.parameterAsRasterLayer(parameters, self.INPUT, context)) + zFactor = self.parameterAsDouble(parameters, self.Z_FACTOR, context) - self.outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT, context) + outputFile = self.parameterAsRasterOutputLayer(parameters, self.OUTPUT, context) - self.outputFormat = raster.formatShortNameFromFileName(self.outputFile) - return True + outputFormat = raster.formatShortNameFromFileName(outputFile) - def processAlgorithm(self, context, feedback): - aspect = QgsAspectFilter(self.inputFile, self.outputFile, self.outputFormat) - aspect.setZFactor(self.zFactor) - return aspect.processRaster(feedback) == 0 + aspect = QgsAspectFilter(inputFile, outputFile, outputFormat) + aspect.setZFactor(zFactor) + aspect.processRaster(feedback) - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.outputFile} + return {self.OUTPUT: outputFile} diff --git a/python/plugins/processing/algs/qgis/AutoincrementalField.py b/python/plugins/processing/algs/qgis/AutoincrementalField.py index 1100d3ae2d19..92dc7f8736a1 100644 --- a/python/plugins/processing/algs/qgis/AutoincrementalField.py +++ b/python/plugins/processing/algs/qgis/AutoincrementalField.py @@ -51,10 +51,6 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Incremented'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Incremented'))) - self.source = None - self.sink = None - self.dest_id = None - def group(self): return self.tr('Vector table tools') @@ -64,18 +60,16 @@ def name(self): def displayName(self): return self.tr('Add autoincremental field') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - fields = self.source.fields() + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + fields = source.fields() fields.append(QgsField('AUTO', QVariant.Int)) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break @@ -85,9 +79,7 @@ def processAlgorithm(self, context, feedback): attributes.append(current) output_feature.setAttributes(attributes) - self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/BasicStatistics.py b/python/plugins/processing/algs/qgis/BasicStatistics.py index 7c4584d42255..0c1ce80992cb 100644 --- a/python/plugins/processing/algs/qgis/BasicStatistics.py +++ b/python/plugins/processing/algs/qgis/BasicStatistics.py @@ -119,52 +119,42 @@ def __init__(self): self.addOutput(QgsProcessingOutputNumber(self.THIRDQUARTILE, self.tr('Third quartile'))) self.addOutput(QgsProcessingOutputNumber(self.IQR, self.tr('Interquartile Range (IQR)'))) - self.source = None - self.field = None - self.field_name = None - self.output_file = None - self.results = {} - def name(self): return 'basicstatisticsforfields' def displayName(self): return self.tr('Basic statistics for fields') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - self.field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) - self.field = self.source.fields().at(self.source.fields().lookupField(self.field_name)) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + field_name = self.parameterAsString(parameters, self.FIELD_NAME, context) + field = source.fields().at(source.fields().lookupField(field_name)) - self.output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) - return True + output_file = self.parameterAsFileOutput(parameters, self.OUTPUT_HTML_FILE, context) - def processAlgorithm(self, context, feedback): - request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([self.field_name], self.source.fields()) - features = self.source.getFeatures(request) - count = self.source.featureCount() + request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry).setSubsetOfAttributes([field_name], source.fields()) + features = source.getFeatures(request) + count = source.featureCount() data = [] - data.append(self.tr('Analyzed field: {}').format(self.field_name)) + data.append(self.tr('Analyzed field: {}').format(field_name)) + results = {} - if self.field.isNumeric(): - d, self.results = self.calcNumericStats(features, feedback, self.field, count) + if field.isNumeric(): + d, results = self.calcNumericStats(features, feedback, field, count) data.extend(d) - elif self.field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): - d, self.results = self.calcDateTimeStats(features, feedback, self.field, count) + elif field.type() in (QVariant.Date, QVariant.Time, QVariant.DateTime): + d, results = self.calcDateTimeStats(features, feedback, field, count) data.extend(d) else: - d, self.results = self.calcStringStats(features, feedback, self.field, count) + d, results = self.calcStringStats(features, feedback, field, count) data.extend(d) - if self.output_file: - self.createHTML(self.output_file, data) - self.results[self.OUTPUT_HTML_FILE] = self.output_file - - return True + if output_file: + self.createHTML(output_file, data) + results[self.OUTPUT_HTML_FILE] = output_file - def postProcessAlgorithm(self, context, feedback): - return self.results + return results def calcNumericStats(self, features, feedback, field, count): total = 100.0 / count if count else 0 diff --git a/python/plugins/processing/algs/qgis/Boundary.py b/python/plugins/processing/algs/qgis/Boundary.py index ac60ef439284..8c883ed2bd37 100644 --- a/python/plugins/processing/algs/qgis/Boundary.py +++ b/python/plugins/processing/algs/qgis/Boundary.py @@ -58,10 +58,6 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Boundary'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Boundaries"))) - self.source = None - self.sink = None - self.dest_id = None - def icon(self): return QIcon(os.path.join(pluginPath, 'images', 'ftools', 'convex_hull.png')) @@ -74,11 +70,10 @@ def name(self): def displayName(self): return self.tr('Boundary') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - input_wkb = self.source.wkbType() - output_wkb = None + input_wkb = source.wkbType() if QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.LineGeometry: output_wkb = QgsWkbTypes.MultiPoint elif QgsWkbTypes.geometryType(input_wkb) == QgsWkbTypes.PolygonGeometry: @@ -88,13 +83,11 @@ def prepareAlgorithm(self, parameters, context, feedback): if QgsWkbTypes.hasM(input_wkb): output_wkb = QgsWkbTypes.addM(output_wkb) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - self.source.fields(), output_wkb, self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + source.fields(), output_wkb, source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -109,9 +102,7 @@ def processAlgorithm(self, context, feedback): output_feature.setGeometry(output_geometry) - self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT_LAYER: self.dest_id} + return {self.OUTPUT_LAYER: dest_id} diff --git a/python/plugins/processing/algs/qgis/BoundingBox.py b/python/plugins/processing/algs/qgis/BoundingBox.py index 5d2f02d8aeef..502a2108c353 100644 --- a/python/plugins/processing/algs/qgis/BoundingBox.py +++ b/python/plugins/processing/algs/qgis/BoundingBox.py @@ -66,26 +66,20 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT_LAYER, self.tr('Bounds'), QgsProcessingParameterDefinition.TypeVectorPolygon)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT_LAYER, self.tr("Bounds"))) - self.source = None - self.sink = None - self.dest_id = None - def name(self): return 'boundingboxes' def displayName(self): return self.tr('Bounding boxes') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, - self.source.fields(), QgsWkbTypes.Polygon, self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT_LAYER, context, + source.fields(), QgsWkbTypes.Polygon, source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -100,9 +94,7 @@ def processAlgorithm(self, context, feedback): output_feature.setGeometry(output_geometry) - self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT_LAYER: self.dest_id} + return {self.OUTPUT_LAYER: dest_id} diff --git a/python/plugins/processing/algs/qgis/CheckValidity.py b/python/plugins/processing/algs/qgis/CheckValidity.py index 05639eabe521..fe5a6a9a7aa4 100644 --- a/python/plugins/processing/algs/qgis/CheckValidity.py +++ b/python/plugins/processing/algs/qgis/CheckValidity.py @@ -93,60 +93,46 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.ERROR_OUTPUT, self.tr('Error output'))) self.addOutput(QgsProcessingOutputNumber(self.ERROR_COUNT, self.tr('Count of errors'))) - self.method = None - self.source = None - self.valid_output_sink = None - self.valid_output_dest_id = None - self.valid_count = 0 - self.invalid_output_sink = None - self.invalid_output_dest_id = None - self.invalid_count = 0 - self.error_output_sink = None - self.error_output_dest_id = None - self.error_count = 0 - def name(self): return 'checkvalidity' def displayName(self): return self.tr('Check validity') - def prepareAlgorithm(self, parameters, context, feedback): + def processAlgorithm(self, parameters, context, feedback): method_param = self.parameterAsEnum(parameters, self.METHOD, context) if method_param == 0: settings = QgsSettings() - self.method = int(settings.value(settings_method_key, 0)) - 1 - if self.method < 0: - self.method = 0 + method = int(settings.value(settings_method_key, 0)) - 1 + if method < 0: + method = 0 else: - self.method = method_param - 1 + method = method_param - 1 + + results = self.doCheck(method, parameters, context, feedback) + return results - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + def doCheck(self, method, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - (self.valid_output_sink, self.valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, - context, - self.source.fields(), - self.source.wkbType(), - self.source.sourceCrs()) + (valid_output_sink, valid_output_dest_id) = self.parameterAsSink(parameters, self.VALID_OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) + valid_count = 0 - invalid_fields = self.source.fields() + invalid_fields = source.fields() invalid_fields.append(QgsField('_errors', QVariant.String, 'string', 255)) - (self.invalid_output_sink, self.invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, - context, - invalid_fields, - self.source.wkbType(), - self.source.sourceCrs()) + (invalid_output_sink, invalid_output_dest_id) = self.parameterAsSink(parameters, self.INVALID_OUTPUT, context, + invalid_fields, source.wkbType(), source.sourceCrs()) + invalid_count = 0 error_fields = QgsFields() error_fields.append(QgsField('message', QVariant.String, 'string', 255)) - (self.error_output_sink, self.error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, - error_fields, QgsWkbTypes.Point, - self.source.sourceCrs()) - return True - - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + (error_output_sink, error_output_dest_id) = self.parameterAsSink(parameters, self.ERROR_OUTPUT, context, + error_fields, QgsWkbTypes.Point, source.sourceCrs()) + error_count = 0 + + features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): if feedback.isCanceled(): break @@ -155,10 +141,10 @@ def processAlgorithm(self, context, feedback): valid = True if not geom.isNull() and not geom.isEmpty(): - errors = list(geom.validateGeometry(self.method)) + errors = list(geom.validateGeometry(method)) if errors: # QGIS method return a summary at the end - if self.method == 1: + if method == 1: errors.pop() valid = False reasons = [] @@ -167,9 +153,9 @@ def processAlgorithm(self, context, feedback): error_geom = QgsGeometry.fromPoint(error.where()) errFeat.setGeometry(error_geom) errFeat.setAttributes([error.what()]) - if self.error_output_sink: - self.error_output_sink.addFeature(errFeat, QgsFeatureSink.FastInsert) - self.error_count += 1 + if error_output_sink: + error_output_sink.addFeature(errFeat, QgsFeatureSink.FastInsert) + error_count += 1 reasons.append(error.what()) @@ -183,28 +169,26 @@ def processAlgorithm(self, context, feedback): outFeat.setAttributes(attrs) if valid: - if self.valid_output_sink: - self.valid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - self.valid_count += 1 + if valid_output_sink: + valid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + valid_count += 1 else: - if self.invalid_output_sink: - self.invalid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) - self.invalid_count += 1 + if invalid_output_sink: + invalid_output_sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + invalid_count += 1 feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): results = { - self.VALID_COUNT: self.valid_count, - self.INVALID_COUNT: self.invalid_count, - self.ERROR_COUNT: self.error_count + self.VALID_COUNT: valid_count, + self.INVALID_COUNT: invalid_count, + self.ERROR_COUNT: error_count } - if self.valid_output_sink: - results[self.VALID_OUTPUT] = self.valid_output_dest_id - if self.invalid_output_sink: - results[self.INVALID_OUTPUT] = self.invalid_output_dest_id - if self.error_output_sink: - results[self.ERROR_OUTPUT] = self.error_output_dest_id + if valid_output_sink: + results[self.VALID_OUTPUT] = valid_output_dest_id + if invalid_output_sink: + results[self.INVALID_OUTPUT] = invalid_output_dest_id + if error_output_sink: + results[self.ERROR_OUTPUT] = error_output_dest_id return results diff --git a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py index 58e47d743cd9..758976b228d4 100644 --- a/python/plugins/processing/algs/qgis/CreateAttributeIndex.py +++ b/python/plugins/processing/algs/qgis/CreateAttributeIndex.py @@ -53,35 +53,27 @@ def __init__(self): self.tr('Attribute to index'), None, self.INPUT)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Indexed layer'))) - self.layer = None - self.field = None - def name(self): return 'createattributeindex' def displayName(self): return self.tr('Create attribute index') - def prepareAlgorithm(self, parameters, context, feedback): - self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - self.field = self.parameterAsString(parameters, self.FIELD, context) - return True - - def processAlgorithm(self, context, feedback): - provider = self.layer.dataProvider() + def processAlgorithm(self, parameters, context, feedback): + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + field = self.parameterAsString(parameters, self.FIELD, context) + provider = layer.dataProvider() - field_index = self.layer.fields().lookupField(self.field) - if field_index < 0 or self.layer.fields().fieldOrigin(field_index) != QgsFields.OriginProvider: - feedback.pushInfo(self.tr('Can not create attribute index on "{}"').format(self.field)) + field_index = layer.fields().lookupField(field) + if field_index < 0 or layer.fields().fieldOrigin(field_index) != QgsFields.OriginProvider: + feedback.pushInfo(self.tr('Can not create attribute index on "{}"').format(field)) else: - provider_index = self.layer.fields().fieldOriginIndex(field_index) + provider_index = layer.fields().fieldOriginIndex(field_index) if provider.capabilities() & QgsVectorDataProvider.CreateAttributeIndex: if not provider.createAttributeIndex(provider_index): feedback.pushInfo(self.tr('Could not create attribute index')) else: feedback.pushInfo(self.tr("Layer's data provider does not support " "creating attribute indexes")) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.layer.id()} + return {self.OUTPUT: layer.id()} diff --git a/python/plugins/processing/algs/qgis/DeleteColumn.py b/python/plugins/processing/algs/qgis/DeleteColumn.py index d5a15cec9e14..95f569871e32 100644 --- a/python/plugins/processing/algs/qgis/DeleteColumn.py +++ b/python/plugins/processing/algs/qgis/DeleteColumn.py @@ -58,56 +58,46 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Output layer'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Output layer"))) - self.source = None - self.fields_to_delete = None - self.sink = None - self.dest_id = None - self.field_indices = [] - def name(self): return 'deletecolumn' def displayName(self): return self.tr('Drop field(s)') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + fields_to_delete = self.parameterAsFields(parameters, self.COLUMNS, context) - fields = self.source.fields() + fields = source.fields() + field_indices = [] # loop through twice - first we need to build up a list of original attribute indices - for f in self.fields_to_delete: + for f in fields_to_delete: index = fields.lookupField(f) - self.field_indices.append(index) + field_indices.append(index) # important - make sure we remove from the end so we aren't changing used indices as we go - self.field_indices.sort(reverse=True) + field_indices.sort(reverse=True) # this second time we make a cleaned version of the fields - for index in self.field_indices: + for index in field_indices: fields.remove(index) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break attributes = f.attributes() - for index in self.field_indices: + for index in field_indices: del attributes[index] f.setAttributes(attributes) - self.sink.addFeature(f, QgsFeatureSink.FastInsert) + sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DeleteHoles.py b/python/plugins/processing/algs/qgis/DeleteHoles.py index e215477d6c5c..b5baf2d254ba 100644 --- a/python/plugins/processing/algs/qgis/DeleteHoles.py +++ b/python/plugins/processing/algs/qgis/DeleteHoles.py @@ -58,40 +58,31 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Cleaned'), QgsProcessingParameterDefinition.TypeVectorPolygon)) - self.source = None - self.min_area = None - self.sink = None - self.dest_id = None - def name(self): return 'deleteholes' def displayName(self): return self.tr('Delete holes') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) - if self.min_area == 0.0: - self.min_area = -1.0 + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + min_area = self.parameterAsDouble(parameters, self.MIN_AREA, context) + if min_area == 0.0: + min_area = -1.0 - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break if f.hasGeometry(): - f.setGeometry(f.geometry().removeInteriorRings(self.min_area)) - self.sink.addFeature(f, QgsFeatureSink.FastInsert) + f.setGeometry(f.geometry().removeInteriorRings(min_area)) + sink.addFeature(f, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometries.py b/python/plugins/processing/algs/qgis/DensifyGeometries.py index e5b2f41b5e39..591b79b3163b 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometries.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometries.py @@ -63,28 +63,21 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) - self.source = None - self.sink = None - self.vertices = None - self.dest_id = None - def name(self): return 'densifygeometries' def displayName(self): return self.tr('Densify geometries') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.vertices = self.parameterAsInt(parameters, self.VERTICES, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + vertices = self.parameterAsInt(parameters, self.VERTICES, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): @@ -92,11 +85,9 @@ def processAlgorithm(self, context, feedback): feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByCount(self.vertices) + new_geometry = feature.geometry().densifyByCount(vertices) feature.setGeometry(new_geometry) - self.sink.addFeature(feature, QgsFeatureSink.FastInsert) + sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py index 610735a7116b..1618fa0fe87b 100644 --- a/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py +++ b/python/plugins/processing/algs/qgis/DensifyGeometriesInterval.py @@ -61,40 +61,31 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Densified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Densified'))) - self.source = None - self.interval = None - self.sink = None - self.dest_id = None - def name(self): return 'densifygeometriesgivenaninterval' def displayName(self): return self.tr('Densify geometries given an interval') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.interval = self.parameterAsDouble(parameters, self.INTERVAL, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + interval = self.parameterAsDouble(parameters, self.INTERVAL, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break feature = f if feature.hasGeometry(): - new_geometry = feature.geometry().densifyByDistance(float(self.interval)) + new_geometry = feature.geometry().densifyByDistance(float(interval)) feature.setGeometry(new_geometry) - self.sink.addFeature(feature, QgsFeatureSink.FastInsert) + sink.addFeature(feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/DropGeometry.py b/python/plugins/processing/algs/qgis/DropGeometry.py index 159c1994506f..5705cc6a103d 100644 --- a/python/plugins/processing/algs/qgis/DropGeometry.py +++ b/python/plugins/processing/algs/qgis/DropGeometry.py @@ -55,36 +55,27 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Dropped geometry'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Dropped geometry"))) - self.source = None - self.sink = None - self.dest_id = None - def name(self): return 'dropgeometries' def displayName(self): return self.tr('Drop geometries') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) - return True + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem()) - def processAlgorithm(self, context, feedback): request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = self.source.getFeatures(request) - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures(request) + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break input_feature.clearGeometry() - self.sink.addFeature(input_feature, QgsFeatureSink.FastInsert) + sink.addFeature(input_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/ExtentFromLayer.py b/python/plugins/processing/algs/qgis/ExtentFromLayer.py index 73d0eed138cc..e3f2e00adfcb 100644 --- a/python/plugins/processing/algs/qgis/ExtentFromLayer.py +++ b/python/plugins/processing/algs/qgis/ExtentFromLayer.py @@ -79,20 +79,15 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extent'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Extent"), QgsProcessingParameterDefinition.TypeVectorPolygon)) - self.source = None - self.byFeature = None - self.sink = None - self.dest_id = None - def name(self): return 'polygonfromlayerextent' def displayName(self): return self.tr('Polygon from layer extent') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) - self.byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT_LAYER, context) + byFeature = self.parameterAsBool(parameters, self.BY_FEATURE, context) fields = QgsFields() fields.append(QgsField('MINX', QVariant.Double)) @@ -106,19 +101,15 @@ def prepareAlgorithm(self, parameters, context, feedback): fields.append(QgsField('HEIGHT', QVariant.Double)) fields.append(QgsField('WIDTH', QVariant.Double)) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, source.sourceCrs()) - def processAlgorithm(self, context, feedback): - if self.byFeature: - self.featureExtent(self.source, context, self.sink, feedback) + if byFeature: + self.featureExtent(source, context, sink, feedback) else: - self.layerExtent(self.source, self.sink, feedback) - return True + self.layerExtent(source, sink, feedback) - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} def layerExtent(self, source, sink, feedback): rect = source.sourceExtent() diff --git a/python/plugins/processing/algs/qgis/FixGeometry.py b/python/plugins/processing/algs/qgis/FixGeometry.py index 4dd86b7f0a0e..e06fe5943c99 100644 --- a/python/plugins/processing/algs/qgis/FixGeometry.py +++ b/python/plugins/processing/algs/qgis/FixGeometry.py @@ -55,26 +55,20 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Fixed geometries'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Fixed geometries"))) - self.source = None - self.sink = None - self.dest_id = None - def name(self): return 'fixgeometries' def displayName(self): return self.tr('Fix geometries') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), QgsWkbTypes.multiType(self.source.wkbType()), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), QgsWkbTypes.multiType(source.wkbType()), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures(QgsFeatureRequest(), QgsProcessingFeatureSource.FlagSkipGeometryValidityChecks) + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inputFeature in enumerate(features): if feedback.isCanceled(): break @@ -92,7 +86,7 @@ def processAlgorithm(self, context, feedback): try: g.convertToMultiType() outputFeature.setGeometry(QgsGeometry(g)) - self.sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) + sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) except: pass feedback.setProgress(int(current * total)) @@ -101,9 +95,7 @@ def processAlgorithm(self, context, feedback): outputGeometry.convertToMultiType() outputFeature.setGeometry(outputGeometry) - self.sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) + sink.addFeature(outputFeature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/GridPolygon.py b/python/plugins/processing/algs/qgis/GridPolygon.py index f2bf05a59eff..e450a48be624 100644 --- a/python/plugins/processing/algs/qgis/GridPolygon.py +++ b/python/plugins/processing/algs/qgis/GridPolygon.py @@ -102,52 +102,41 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Grid'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Grid'), QgsProcessingParameterDefinition.TypeVectorPolygon)) - self.idx = None - self.hSpacing = None - self.vSpacing = None - self.hOverlay = None - self.vOverlay = None - self.width = None - self.height = None - self.originX = None - self.originY = None - self.sink = None - self.dest_id = None - def name(self): return 'creategridpolygon' def displayName(self): return self.tr('Create grid (polygon)') - def prepareAlgorithm(self, parameters, context, feedback): - self.idx = self.parameterAsEnum(parameters, self.TYPE, context) + def processAlgorithm(self, parameters, context, feedback): + idx = self.parameterAsEnum(parameters, self.TYPE, context) - self.hSpacing = self.parameterAsDouble(parameters, self.HSPACING, context) - self.vSpacing = self.parameterAsDouble(parameters, self.VSPACING, context) - self.hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context) - self.vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context) + hSpacing = self.parameterAsDouble(parameters, self.HSPACING, context) + vSpacing = self.parameterAsDouble(parameters, self.VSPACING, context) + hOverlay = self.parameterAsDouble(parameters, self.HOVERLAY, context) + vOverlay = self.parameterAsDouble(parameters, self.VOVERLAY, context) bbox = self.parameterAsExtent(parameters, self.EXTENT, context) crs = self.parameterAsCrs(parameters, self.CRS, context) - self.width = bbox.width() - self.height = bbox.height() - self.originX = bbox.xMinimum() - self.originY = bbox.yMaximum() - if self.hSpacing <= 0 or self.vSpacing <= 0: + width = bbox.width() + height = bbox.height() + originX = bbox.xMinimum() + originY = bbox.yMaximum() + + if hSpacing <= 0 or vSpacing <= 0: raise GeoAlgorithmExecutionException( - self.tr('Invalid grid spacing: {0}/{1}').format(self.hSpacing, self.vSpacing)) + self.tr('Invalid grid spacing: {0}/{1}').format(hSpacing, vSpacing)) - if self.width < self.hSpacing: + if width < hSpacing: raise GeoAlgorithmExecutionException( self.tr('Horizontal spacing is too small for the covered area')) - if self.hSpacing <= self.hOverlay or self.vSpacing <= self.vOverlay: + if hSpacing <= hOverlay or vSpacing <= vOverlay: raise GeoAlgorithmExecutionException( - self.tr('Invalid overlay: {0}/{1}').format(self.hOverlay, self.vOverlay)) + self.tr('Invalid overlay: {0}/{1}').format(hOverlay, vOverlay)) - if self.height < self.vSpacing: + if height < vSpacing: raise GeoAlgorithmExecutionException( self.tr('Vertical spacing is too small for the covered area')) @@ -158,24 +147,20 @@ def prepareAlgorithm(self, parameters, context, feedback): fields.append(QgsField('bottom', QVariant.Double, '', 24, 16)) fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, QgsWkbTypes.Polygon, crs) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, crs) - def processAlgorithm(self, context, feedback): - if self.idx == 0: + if idx == 0: self._rectangleGrid( - self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) - elif self.idx == 1: + sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) + elif idx == 1: self._diamondGrid( - self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) - elif self.idx == 2: + sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) + elif idx == 2: self._hexagonGrid( - self.sink, self.width, self.height, self.originX, self.originY, self.hSpacing, self.vSpacing, self.hOverlay, self.vOverlay, feedback) - return True + sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback) - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} def _rectangleGrid(self, sink, width, height, originX, originY, hSpacing, vSpacing, hOverlay, vOverlay, feedback): diff --git a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py index bff841c22a14..32a419986dbe 100644 --- a/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py +++ b/python/plugins/processing/algs/qgis/ImportIntoPostGIS.py @@ -27,7 +27,9 @@ from qgis.core import (QgsVectorLayerExporter, QgsSettings, + QgsApplication, QgsFeatureSink, + QgsProcessingUtils, QgsProcessingParameterFeatureSource, QgsProcessingParameterString, QgsProcessingParameterField, @@ -106,86 +108,70 @@ def __init__(self): self.addParameter(QgsProcessingParameterBoolean(self.FORCE_SINGLEPART, self.tr('Create single-part geometries instead of multi-part'), False)) - self.db = None - self.schema = None - self.overwrite = None - self.createIndex = None - self.convertLowerCase = None - self.dropStringLength = None - self.forceSinglePart = None - self.primaryKeyField = None - self.encoding = None - self.source = None - self.table = None - self.providerName = None - self.geomColumn = None - def name(self): return 'importintopostgis' def displayName(self): return self.tr('Import into PostGIS') - def prepareAlgorithm(self, parameters, context, feedback): + def processAlgorithm(self, parameters, context, feedback): connection = self.parameterAsString(parameters, self.DATABASE, context) - self.db = postgis.GeoDB.from_name(connection) - - self.schema = self.parameterAsString(parameters, self.SCHEMA, context) - self.overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) - self.createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) - self.convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) - self.dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) - self.forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) - self.primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' - self.encoding = self.parameterAsString(parameters, self.ENCODING, context) - - self.source = self.parameterAsSource(parameters, self.INPUT, context) - - self.table = self.parameterAsString(parameters, self.TABLENAME, context) - if self.table: - self.table.strip() - if not self.table or self.table == '': - self.table = self.source.sourceName() - self.table = self.table.replace('.', '_') - self.table = self.table.replace(' ', '').lower()[0:62] - self.providerName = 'postgres' - - self.geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) - if not self.geomColumn: - self.geomColumn = 'geom' - return True - - def processAlgorithm(self, context, feedback): + db = postgis.GeoDB.from_name(connection) + + schema = self.parameterAsString(parameters, self.SCHEMA, context) + overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) + createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) + convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) + dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) + forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) + primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' + encoding = self.parameterAsString(parameters, self.ENCODING, context) + + source = self.parameterAsSource(parameters, self.INPUT, context) + + table = self.parameterAsString(parameters, self.TABLENAME, context) + if table: + table.strip() + if not table or table == '': + table = source.sourceName() + table = table.replace('.', '_') + table = table.replace(' ', '').lower()[0:62] + providerName = 'postgres' + + geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) + if not geomColumn: + geomColumn = 'geom' + options = {} - if self.overwrite: + if overwrite: options['overwrite'] = True - if self.convertLowerCase: + if convertLowerCase: options['lowercaseFieldNames'] = True - self.geomColumn = self.geomColumn.lower() - if self.dropStringLength: + geomColumn = geomColumn.lower() + if dropStringLength: options['dropStringConstraints'] = True - if self.forceSinglePart: + if forceSinglePart: options['forceSinglePartGeometryType'] = True # Clear geometry column for non-geometry tables - if self.source.wkbType() == QgsWkbTypes.NoGeometry: - self.geomColumn = None + if source.wkbType() == QgsWkbTypes.NoGeometry: + geomColumn = None - uri = self.db.uri - uri.setDataSource(self.schema, self.table, self.geomColumn, '', self.primaryKeyField) + uri = db.uri + uri.setDataSource(schema, table, geomColumn, '', primaryKeyField) - if self.encoding: - options['fileEncoding'] = self.encoding + if encoding: + options['fileEncoding'] = encoding - exporter = QgsVectorLayerExporter(uri.uri(), self.providerName, self.source.fields(), - self.source.wkbType(), self.source.sourceCrs(), self.overwrite, options) + exporter = QgsVectorLayerExporter(uri.uri(), providerName, source.fields(), + source.wkbType(), source.sourceCrs(), overwrite, options) if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise GeoAlgorithmExecutionException( self.tr('Error importing to PostGIS\n{0}').format(exporter.errorMessage())) - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break @@ -200,13 +186,11 @@ def processAlgorithm(self, context, feedback): raise GeoAlgorithmExecutionException( self.tr('Error importing to PostGIS\n{0}').format(exporter.errorMessage())) - if self.geomColumn and self.createIndex: - self.db.create_spatial_index(self.table, self.schema, self.geomColumn) + if geomColumn and createIndex: + db.create_spatial_index(table, schema, geomColumn) - self.db.vacuum_analyze(self.table, self.schema) - return True + db.vacuum_analyze(table, schema) - def postProcessAlgorithm(self, context, feedback): return {} def dbConnectionNames(self): diff --git a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py index 983d9b40b09e..407a4671caeb 100644 --- a/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py +++ b/python/plugins/processing/algs/qgis/ImportIntoSpatialite.py @@ -71,26 +71,13 @@ def __init__(self): self.addParameter(QgsProcessingParameterBoolean(self.DROP_STRING_LENGTH, self.tr('Drop length constraints on character fields'), False)) self.addParameter(QgsProcessingParameterBoolean(self.FORCE_SINGLEPART, self.tr('Create single-part geometries instead of multi-part'), False)) - self.db = None - self.overwrite = None - self.createIndex = None - self.dropStringLength = None - self.convertLowerCase = None - self.forceSinglePart = None - self.primaryKeyField = None - self.encoding = None - self.source = None - self.table = None - self.providerName = None - self.geomColumn = None - def name(self): return 'importintospatialite' def displayName(self): return self.tr('Import into Spatialite') - def prepareAlgorithm(self, parameters, context, feedback): + def processAlgorithm(self, parameters, context, feedback): database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) databaseuri = database.dataProvider().dataSourceUri() uri = QgsDataSourceUri(databaseuri) @@ -98,64 +85,61 @@ def prepareAlgorithm(self, parameters, context, feedback): if '|layerid' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layerid')] uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri)) - self.db = spatialite.GeoDB(uri) - - self.overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) - self.createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) - self.convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) - self.dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) - self.forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) - self.primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' - self.encoding = self.parameterAsString(parameters, self.ENCODING, context) - - self.source = self.parameterAsSource(parameters, self.INPUT, context) - - self.table = self.parameterAsString(parameters, self.TABLENAME, context) - if self.table: - self.table.strip() - if not self.table or self.table == '': - self.table = self.source.sourceName() - self.table = self.table.replace('.', '_') - self.table = self.table.replace(' ', '').lower() - self.providerName = 'spatialite' - - self.geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) - if not self.geomColumn: - self.geomColumn = 'geom' - - return True - - def processAlgorithm(self, context, feedback): + db = spatialite.GeoDB(uri) + + overwrite = self.parameterAsBool(parameters, self.OVERWRITE, context) + createIndex = self.parameterAsBool(parameters, self.CREATEINDEX, context) + convertLowerCase = self.parameterAsBool(parameters, self.LOWERCASE_NAMES, context) + dropStringLength = self.parameterAsBool(parameters, self.DROP_STRING_LENGTH, context) + forceSinglePart = self.parameterAsBool(parameters, self.FORCE_SINGLEPART, context) + primaryKeyField = self.parameterAsString(parameters, self.PRIMARY_KEY, context) or 'id' + encoding = self.parameterAsString(parameters, self.ENCODING, context) + + source = self.parameterAsSource(parameters, self.INPUT, context) + + table = self.parameterAsString(parameters, self.TABLENAME, context) + if table: + table.strip() + if not table or table == '': + table = source.sourceName() + table = table.replace('.', '_') + table = table.replace(' ', '').lower() + providerName = 'spatialite' + + geomColumn = self.parameterAsString(parameters, self.GEOMETRY_COLUMN, context) + if not geomColumn: + geomColumn = 'geom' + options = {} - if self.overwrite: + if overwrite: options['overwrite'] = True - if self.convertLowerCase: + if convertLowerCase: options['lowercaseFieldNames'] = True - self.geomColumn = self.geomColumn.lower() - if self.dropStringLength: + geomColumn = geomColumn.lower() + if dropStringLength: options['dropStringConstraints'] = True - if self.forceSinglePart: + if forceSinglePart: options['forceSinglePartGeometryType'] = True # Clear geometry column for non-geometry tables - if self.source.wkbType() == QgsWkbTypes.NoGeometry: - self.geomColumn = None + if source.wkbType() == QgsWkbTypes.NoGeometry: + geomColumn = None - uri = self.db.uri - uri.setDataSource('', self.table, self.geomColumn, '', self.primaryKeyField) + uri = db.uri + uri.setDataSource('', table, geomColumn, '', primaryKeyField) - if self.encoding: - options['fileEncoding'] = self.encoding + if encoding: + options['fileEncoding'] = encoding - exporter = QgsVectorLayerExporter(uri.uri(), self.providerName, self.source.fields(), - self.source.wkbType(), self.source.sourceCrs(), self.overwrite, options) + exporter = QgsVectorLayerExporter(uri.uri(), providerName, source.fields(), + source.wkbType(), source.sourceCrs(), overwrite, options) if exporter.errorCode() != QgsVectorLayerExporter.NoError: raise GeoAlgorithmExecutionException( self.tr('Error importing to Spatialite\n{0}').format(exporter.errorMessage())) - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, f in enumerate(features): if feedback.isCanceled(): break @@ -170,9 +154,7 @@ def processAlgorithm(self, context, feedback): raise GeoAlgorithmExecutionException( self.tr('Error importing to Spatialite\n{0}').format(exporter.errorMessage())) - if self.geomColumn and self.createIndex: - self.db.create_spatial_index(self.table, self.geomColumn) - return True + if geomColumn and createIndex: + db.create_spatial_index(table, geomColumn) - def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/Merge.py b/python/plugins/processing/algs/qgis/Merge.py index c00b11b4713c..cd9923c53e5e 100644 --- a/python/plugins/processing/algs/qgis/Merge.py +++ b/python/plugins/processing/algs/qgis/Merge.py @@ -68,26 +68,19 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Merged'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Merged'))) - self.input_layers = [] - self.fields = None - self.add_layer_field = None - self.add_path_field = None - self.sink = None - self.dest_id = None - self.dest_crs = None - def name(self): return 'mergevectorlayers' def displayName(self): return self.tr('Merge vector layers') - def prepareAlgorithm(self, parameters, context, feedback): - self.input_layers = self.parameterAsLayerList(parameters, self.LAYERS, context) + def processAlgorithm(self, parameters, context, feedback): + input_layers = self.parameterAsLayerList(parameters, self.LAYERS, context) + layers = [] - self.fields = QgsFields() + fields = QgsFields() totalFeatureCount = 0 - for layer in self.input_layers: + for layer in input_layers: if layer.type() != QgsMapLayer.VectorLayer: raise GeoAlgorithmExecutionException( self.tr('All layers must be vector layers!')) @@ -102,7 +95,7 @@ def prepareAlgorithm(self, parameters, context, feedback): for sindex, sfield in enumerate(layer.fields()): found = None - for dfield in self.fields: + for dfield in fields: if (dfield.name().upper() == sfield.name().upper()): found = dfield if (dfield.type() != sfield.type()): @@ -111,38 +104,35 @@ def prepareAlgorithm(self, parameters, context, feedback): 'data type than in other layers.'.format(sfield.name(), layerSource))) if not found: - self.fields.append(sfield) + fields.append(sfield) - self.add_layer_field = False - if self.fields.lookupField('layer') < 0: - self.fields.append(QgsField('layer', QVariant.String, '', 100)) - self.add_layer_field = True - self.add_path_field = False - if self.fields.lookupField('path') < 0: - self.fields.append(QgsField('path', QVariant.String, '', 200)) - self.add_path_field = True + add_layer_field = False + if fields.lookupField('layer') < 0: + fields.append(QgsField('layer', QVariant.String, '', 100)) + add_layer_field = True + add_path_field = False + if fields.lookupField('path') < 0: + fields.append(QgsField('path', QVariant.String, '', 200)) + add_path_field = True total = 100.0 / totalFeatureCount if totalFeatureCount else 1 - self.dest_crs = layers[0].crs() - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.fields, layers[0].wkbType(), self.dest_crs) - return True - - def processAlgorithm(self, context, feedback): + dest_crs = layers[0].crs() + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, layers[0].wkbType(), dest_crs) featureCount = 0 - for layer in self.layers: - for feature in layer.getFeatures(QgsFeatureRequest().setDestinationCrs(self.dest_crs)): + for layer in layers: + for feature in layer.getFeatures(QgsFeatureRequest().setDestinationCrs(dest_crs)): if feedback.isCanceled(): break sattributes = feature.attributes() dattributes = [] - for dindex, dfield in enumerate(self.fields): - if self.add_layer_field and dfield.name() == 'layer': + for dindex, dfield in enumerate(fields): + if add_layer_field and dfield.name() == 'layer': dattributes.append(layer.name()) continue - if self.add_path_field and dfield.name() == 'path': + if add_path_field and dfield.name() == 'path': dattributes.append(layer.publicSource()) continue @@ -164,10 +154,8 @@ def processAlgorithm(self, context, feedback): dattributes.append(dattribute) feature.setAttributes(dattributes) - self.sink.addFeature(feature, QgsFeatureSink.FastInsert) + sink.addFeature(feature, QgsFeatureSink.FastInsert) featureCount += 1 - feedback.setProgress(int(featureCount * self.total)) - return True + feedback.setProgress(int(featureCount * total)) - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/PointsLayerFromTable.py b/python/plugins/processing/algs/qgis/PointsLayerFromTable.py index 69a737293a9c..065459c178e2 100644 --- a/python/plugins/processing/algs/qgis/PointsLayerFromTable.py +++ b/python/plugins/processing/algs/qgis/PointsLayerFromTable.py @@ -80,49 +80,39 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Points from table'), type=QgsProcessingParameterDefinition.TypeVectorPoint)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Points from table'), type=QgsProcessingParameterDefinition.TypeVectorPoint)) - self.source = None - self.x_field_index = None - self.y_field_index = None - self.z_field_index = None - self.m_field_index = None - self.sink = None - self.dest_id = None - def name(self): return 'createpointslayerfromtable' def displayName(self): return self.tr('Create points layer from table') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) - fields = self.source.fields() - self.x_field_index = fields.lookupField(self.parameterAsString(parameters, self.XFIELD, context)) - self.y_field_index = fields.lookupField(self.parameterAsString(parameters, self.YFIELD, context)) - self.z_field_index = -1 + fields = source.fields() + x_field_index = fields.lookupField(self.parameterAsString(parameters, self.XFIELD, context)) + y_field_index = fields.lookupField(self.parameterAsString(parameters, self.YFIELD, context)) + z_field_index = -1 if self.parameterAsString(parameters, self.ZFIELD, context): - self.z_field_index = fields.lookupField(self.parameterAsString(parameters, self.ZFIELD, context)) - self.m_field_index = -1 + z_field_index = fields.lookupField(self.parameterAsString(parameters, self.ZFIELD, context)) + m_field_index = -1 if self.parameterAsString(parameters, self.MFIELD, context): - self.m_field_index = fields.lookupField(self.parameterAsString(parameters, self.MFIELD, context)) + m_field_index = fields.lookupField(self.parameterAsString(parameters, self.MFIELD, context)) wkb_type = QgsWkbTypes.Point - if self.z_field_index >= 0: + if z_field_index >= 0: wkb_type = QgsWkbTypes.addZ(wkb_type) - if self.m_field_index >= 0: + if m_field_index >= 0: wkb_type = QgsWkbTypes.addM(wkb_type) target_crs = self.parameterAsCrs(parameters, self.TARGET_CRS, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, wkb_type, target_crs) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, wkb_type, target_crs) - def processAlgorithm(self, context, feedback): request = QgsFeatureRequest().setFlags(QgsFeatureRequest.NoGeometry) - features = self.source.getFeatures(request) - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, feature in enumerate(features): if feedback.isCanceled(): @@ -132,20 +122,20 @@ def processAlgorithm(self, context, feedback): attrs = feature.attributes() try: - x = float(attrs[self.x_field_index]) - y = float(attrs[self.y_field_index]) + x = float(attrs[x_field_index]) + y = float(attrs[y_field_index]) point = QgsPoint(x, y) - if self.z_field_index >= 0: + if z_field_index >= 0: try: - point.addZValue(float(attrs[self.z_field_index])) + point.addZValue(float(attrs[z_field_index])) except: point.addZValue(0.0) - if self.m_field_index >= 0: + if m_field_index >= 0: try: - point.addMValue(float(attrs[self.m_field_index])) + point.addMValue(float(attrs[m_field_index])) except: point.addMValue(0.0) @@ -153,8 +143,6 @@ def processAlgorithm(self, context, feedback): except: pass # no geometry - self.sink.addFeature(feature) - return True + sink.addFeature(feature) - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py index a4b104603a50..ec5600aaae95 100644 --- a/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/PostGISExecuteSQL.py @@ -53,28 +53,20 @@ def __init__(self): self.addParameter(db_param) self.addParameter(QgsProcessingParameterString(self.SQL, self.tr('SQL query'))) - self.connection = None - self.sql = None - def name(self): return 'postgisexecutesql' def displayName(self): return self.tr('PostGIS execute SQL') - def prepareAlgorithm(self, parameters, context, feedback): - self.connection = self.parameterAsString(parameters, self.DATABASE, context) - self.sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') - return True + def processAlgorithm(self, parameters, context, feedback): + connection = self.parameterAsString(parameters, self.DATABASE, context) + db = postgis.GeoDB.from_name(connection) - def processAlgorithm(self, context, feedback): - db = postgis.GeoDB.from_name(self.connection) + sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') try: - db._exec_sql_and_commit(str(self.sql)) + db._exec_sql_and_commit(str(sql)) except postgis.DbError as e: raise GeoAlgorithmExecutionException( self.tr('Error executing SQL:\n{0}').format(str(e))) - return True - - def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/RandomExtract.py b/python/plugins/processing/algs/qgis/RandomExtract.py index ea4019010100..73cc9a1c28ef 100644 --- a/python/plugins/processing/algs/qgis/RandomExtract.py +++ b/python/plugins/processing/algs/qgis/RandomExtract.py @@ -66,54 +66,42 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extracted (random)'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Extracted (random)'))) - self.source = None - self.method = None - self.value = None - self.featureCount = None - self.sink = None - self.dest_id = None - def name(self): return 'randomextract' def displayName(self): return self.tr('Random extract') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.method = self.parameterAsEnum(parameters, self.METHOD, context) - self.value = self.parameterAsInt(parameters, self.NUMBER, context) - self.featureCount = self.source.featureCount() + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) + + features = source.getFeatures() + featureCount = source.featureCount() + value = self.parameterAsInt(parameters, self.NUMBER, context) - if self.method == 0: - if self.value > self.featureCount: + if method == 0: + if value > featureCount: raise GeoAlgorithmExecutionException( self.tr('Selected number is greater than feature count. ' 'Choose a lower value and try again.')) else: - if self.value > 100: + if value > 100: raise GeoAlgorithmExecutionException( self.tr("Percentage can't be greater than 100. Set a " "different value and try again.")) - self.value = int(round(self.value / 100.0000, 4) * self.featureCount) + value = int(round(value / 100.0000, 4) * featureCount) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) + selran = random.sample(list(range(featureCount)), value) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - selran = random.sample(list(range(self.featureCount)), self.value) - features = self.source.getFeatures() - - total = 100.0 / self.featureCount if self.featureCount else 1 + total = 100.0 / featureCount if featureCount else 1 for i, feat in enumerate(features): if feedback.isCanceled(): break if i in selran: - self.sink.addFeature(feat, QgsFeatureSink.FastInsert) + sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(i * total)) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py b/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py index b28925386bbc..688cfe465fd3 100644 --- a/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py +++ b/python/plugins/processing/algs/qgis/RandomExtractWithinSubsets.py @@ -74,46 +74,38 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Extracted (random stratified)'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Extracted (random stratified)'))) - self.source = None - self.method = None - self.field = None - self.value = None - self.sink = None - self.dest_id = None - def name(self): return 'randomextractwithinsubsets' def displayName(self): return self.tr('Random extract within subsets') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.method = self.parameterAsEnum(parameters, self.METHOD, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) - self.field = self.parameterAsString(parameters, self.FIELD, context) - self.value = self.parameterAsInt(parameters, self.NUMBER, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + field = self.parameterAsString(parameters, self.FIELD, context) - def processAlgorithm(self, context, feedback): - index = self.source.fields().lookupField(self.field) + index = source.fields().lookupField(field) - features = self.source.getFeatures() - featureCount = self.source.featureCount() - unique = self.source.uniqueValues(index) - if self.method == 0: - if self.value > featureCount: + features = source.getFeatures() + featureCount = source.featureCount() + unique = source.uniqueValues(index) + value = self.parameterAsInt(parameters, self.NUMBER, context) + if method == 0: + if value > featureCount: raise GeoAlgorithmExecutionException( self.tr('Selected number is greater that feature count. ' 'Choose lesser value and try again.')) else: - if self.value > 100: + if value > 100: raise GeoAlgorithmExecutionException( self.tr("Percentage can't be greater than 100. Set " "correct value and try again.")) - self.value = self.value / 100.0 + value = value / 100.0 + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) selran = [] total = 100.0 / (featureCount * len(unique)) if featureCount else 1 @@ -127,17 +119,13 @@ def processAlgorithm(self, context, feedback): feedback.setProgress(int(i * total)) for subset in classes.values(): - selValue = self.value if self.method != 1 else int(round(self.value * len(subset), 0)) + selValue = value if method != 1 else int(round(value * len(subset), 0)) selran.extend(random.sample(subset, selValue)) total = 100.0 / featureCount if featureCount else 1 for (i, feat) in enumerate(selran): if feedback.isCanceled(): break - self.sink.addFeature(feat, QgsFeatureSink.FastInsert) + sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(i * total)) - - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/RegularPoints.py b/python/plugins/processing/algs/qgis/RegularPoints.py index ce701395e718..6a0b25f8ec38 100644 --- a/python/plugins/processing/algs/qgis/RegularPoints.py +++ b/python/plugins/processing/algs/qgis/RegularPoints.py @@ -81,66 +81,55 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Regular points'), QgsProcessingParameterDefinition.TypeVectorPoint)) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Regular points'), QgsProcessingParameterDefinition.TypeVectorPoint)) - self.extent = None - self.spacing = None - self.inset = None - self.randomize = None - self.isSpacing = None - self.fields = None - self.sink = None - self.dest_id = None - def name(self): return 'regularpoints' def displayName(self): return self.tr('Regular points') - def prepareAlgorithm(self, parameters, context, feedback): - self.extent = self.parameterAsExtent(parameters, self.EXTENT, context) + def processAlgorithm(self, parameters, context, feedback): + extent = self.parameterAsExtent(parameters, self.EXTENT, context) - self.spacing = self.parameterAsDouble(parameters, self.SPACING, context) - self.inset = self.parameterAsDouble(parameters, self.INSET, context) - self.randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context) - self.isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context) + spacing = self.parameterAsDouble(parameters, self.SPACING, context) + inset = self.parameterAsDouble(parameters, self.INSET, context) + randomize = self.parameterAsBool(parameters, self.RANDOMIZE, context) + isSpacing = self.parameterAsBool(parameters, self.IS_SPACING, context) crs = self.parameterAsCrs(parameters, self.CRS, context) - self.fields = QgsFields() - self.fields.append(QgsField('id', QVariant.Int, '', 10, 0)) + fields = QgsFields() + fields.append(QgsField('id', QVariant.Int, '', 10, 0)) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.fields, QgsWkbTypes.Point, crs) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Point, crs) - def processAlgorithm(self, context, feedback): - if self.randomize: + if randomize: seed() - area = self.extent.width() * self.extent.height() - if self.isSpacing: - pSpacing = self.spacing + area = extent.width() * extent.height() + if isSpacing: + pSpacing = spacing else: - pSpacing = sqrt(area / self.spacing) + pSpacing = sqrt(area / spacing) f = QgsFeature() f.initAttributes(1) - f.setFields(self.fields) + f.setFields(fields) count = 0 total = 100.0 / (area / pSpacing) - y = self.extent.yMaximum() - self.inset + y = extent.yMaximum() - inset - extent_geom = QgsGeometry.fromRect(self.extent) + extent_geom = QgsGeometry.fromRect(extent) extent_engine = QgsGeometry.createGeometryEngine(extent_geom.geometry()) extent_engine.prepareGeometry() - while y >= self.extent.yMinimum(): - x = self.extent.xMinimum() + self.inset - while x <= self.extent.xMaximum(): + while y >= extent.yMinimum(): + x = extent.xMinimum() + inset + while x <= extent.xMaximum(): if feedback.isCanceled(): break - if self.randomize: + if randomize: geom = QgsGeometry().fromPoint(QgsPointXY( uniform(x - (pSpacing / 2.0), x + (pSpacing / 2.0)), uniform(y - (pSpacing / 2.0), y + (pSpacing / 2.0)))) @@ -150,13 +139,10 @@ def processAlgorithm(self, context, feedback): if extent_engine.intersects(geom.geometry()): f.setAttribute('id', count) f.setGeometry(geom) - self.sink.addFeature(f, QgsFeatureSink.FastInsert) + sink.addFeature(f, QgsFeatureSink.FastInsert) x += pSpacing count += 1 feedback.setProgress(int(count * total)) y = y - pSpacing - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py b/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py index 7c79b0126552..985d916bc954 100644 --- a/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py +++ b/python/plugins/processing/algs/qgis/SaveSelectedFeatures.py @@ -47,35 +47,27 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Selection'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Selection"))) - self.vectorLayer = None - self.sink = None - self.dest_id = None - def name(self): return 'saveselectedfeatures' def displayName(self): return self.tr('Save selected features') - def prepareAlgorithm(self, parameters, context, feedback): - self.vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + def processAlgorithm(self, parameters, context, feedback): + vectorLayer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.vectorLayer.fields(), self.vectorLayer.wkbType(), self.vectorLayer.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + vectorLayer.fields(), vectorLayer.wkbType(), vectorLayer.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.vectorLayer.getSelectedFeatures() - count = int(self.vectorLayer.selectedFeatureCount()) + features = vectorLayer.getSelectedFeatures() + count = int(vectorLayer.selectedFeatureCount()) total = 100.0 / count if count else 1 for current, feat in enumerate(features): if feedback.isCanceled(): break - self.sink.addFeature(feat, QgsFeatureSink.FastInsert) + sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/SelectByAttribute.py b/python/plugins/processing/algs/qgis/SelectByAttribute.py index 4f6087e3a9c2..3af9eb908c35 100644 --- a/python/plugins/processing/algs/qgis/SelectByAttribute.py +++ b/python/plugins/processing/algs/qgis/SelectByAttribute.py @@ -92,60 +92,47 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (attribute)'))) - self.layer = None - self.fieldName = None - self.operator = None - self.value = None - self.input = None - def name(self): return 'selectbyattribute' def displayName(self): return self.tr('Select by attribute') - def prepareAlgorithm(self, parameters, context, feedback): - self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - - self.fieldName = self.parameterAsString(parameters, self.FIELD, context) - self.operator = self.OPERATORS[self.parameterAsEnum(parameters, self.OPERATOR, context)] - self.value = self.parameterAsString(parameters, self.VALUE, context) + def processAlgorithm(self, parameters, context, feedback): + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) - self.input = parameters[self.INPUT] - return True + fieldName = self.parameterAsString(parameters, self.FIELD, context) + operator = self.OPERATORS[self.parameterAsEnum(parameters, self.OPERATOR, context)] + value = self.parameterAsString(parameters, self.VALUE, context) - def processAlgorithm(self, context, feedback): - fields = self.layer.fields() + fields = layer.fields() - idx = self.layer.fields().lookupField(self.fieldName) + idx = layer.fields().lookupField(fieldName) fieldType = fields[idx].type() - if fieldType != QVariant.String and self.operator in self.STRING_OPERATORS: + if fieldType != QVariant.String and operator in self.STRING_OPERATORS: op = ''.join(['"%s", ' % o for o in self.STRING_OPERATORS]) raise GeoAlgorithmExecutionException( self.tr('Operators {0} can be used only with string fields.').format(op)) - field_ref = QgsExpression.quotedColumnRef(self.fieldName) - quoted_val = QgsExpression.quotedValue(self.value) - if self.operator == 'is null': + field_ref = QgsExpression.quotedColumnRef(fieldName) + quoted_val = QgsExpression.quotedValue(value) + if operator == 'is null': expression_string = '{} IS NULL'.format(field_ref) - elif self.operator == 'is not null': + elif operator == 'is not null': expression_string = '{} IS NOT NULL'.format(field_ref) - elif self.operator == 'begins with': - expression_string = """%s LIKE '%s%%'""" % (field_ref, self.value) - elif self.operator == 'contains': - expression_string = """%s LIKE '%%%s%%'""" % (field_ref, self.value) - elif self.operator == 'does not contain': - expression_string = """%s NOT LIKE '%%%s%%'""" % (field_ref, self.value) + elif operator == 'begins with': + expression_string = """%s LIKE '%s%%'""" % (field_ref, value) + elif operator == 'contains': + expression_string = """%s LIKE '%%%s%%'""" % (field_ref, value) + elif operator == 'does not contain': + expression_string = """%s NOT LIKE '%%%s%%'""" % (field_ref, value) else: - expression_string = '{} {} {}'.format(field_ref, self.operator, quoted_val) + expression_string = '{} {} {}'.format(field_ref, operator, quoted_val) expression = QgsExpression(expression_string) if expression.hasParserError(): raise GeoAlgorithmExecutionException(expression.parserErrorString()) - self.layer.selectByExpression(expression_string) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.input} + layer.selectByExpression(expression_string) + return {self.OUTPUT: parameters[self.INPUT]} diff --git a/python/plugins/processing/algs/qgis/SelectByExpression.py b/python/plugins/processing/algs/qgis/SelectByExpression.py index 2ba58c41bf9f..ac1f833ab2ab 100644 --- a/python/plugins/processing/algs/qgis/SelectByExpression.py +++ b/python/plugins/processing/algs/qgis/SelectByExpression.py @@ -60,42 +60,30 @@ def __init__(self): self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Selected (attribute)'))) - self.layer = None - self.behavior = None - self.input = None - self.expression = None - def name(self): return 'selectbyexpression' def displayName(self): return self.tr('Select by expression') - def prepareAlgorithm(self, parameters, context, feedback): - self.layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) + def processAlgorithm(self, parameters, context, feedback): + layer = self.parameterAsVectorLayer(parameters, self.INPUT, context) method = self.parameterAsEnum(parameters, self.METHOD, context) if method == 0: - self.behavior = QgsVectorLayer.SetSelection + behavior = QgsVectorLayer.SetSelection elif method == 1: - self.behavior = QgsVectorLayer.AddToSelection + behavior = QgsVectorLayer.AddToSelection elif method == 2: - self.behavior = QgsVectorLayer.RemoveFromSelection + behavior = QgsVectorLayer.RemoveFromSelection elif method == 3: - self.behavior = QgsVectorLayer.IntersectSelection + behavior = QgsVectorLayer.IntersectSelection - self.expression = self.parameterAsString(parameters, self.EXPRESSION, context) - qExp = QgsExpression(self.expression) + expression = self.parameterAsString(parameters, self.EXPRESSION, context) + qExp = QgsExpression(expression) if qExp.hasParserError(): raise GeoAlgorithmExecutionException(qExp.parserErrorString()) - self.input = parameters[self.INPUT] - return True - - def processAlgorithm(self, context, feedback): - self.layer.selectByExpression(self.expression, self.behavior) - return True - - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.input} + layer.selectByExpression(expression, behavior) + return {self.OUTPUT: parameters[self.INPUT]} diff --git a/python/plugins/processing/algs/qgis/SimplifyGeometries.py b/python/plugins/processing/algs/qgis/SimplifyGeometries.py index a58c450a233c..f84ebd8aff80 100644 --- a/python/plugins/processing/algs/qgis/SimplifyGeometries.py +++ b/python/plugins/processing/algs/qgis/SimplifyGeometries.py @@ -76,37 +76,28 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Simplified'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Simplified'))) - self.source = None - self.tolerance = None - self.method = None - self.sink = None - self.dest_id = None - def name(self): return 'simplifygeometries' def displayName(self): return self.tr('Simplify geometries') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) - self.method = self.parameterAsEnum(parameters, self.METHOD, context) - - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) + method = self.parameterAsEnum(parameters, self.METHOD, context) - def processAlgorithm(self, context, feedback): pointsBefore = 0 pointsAfter = 0 - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) + + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 - simplifier = None - if self.method != 0: - simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, self.tolerance, self.method) + if method != 0: + simplifier = QgsMapToPixelSimplifier(QgsMapToPixelSimplifier.SimplifyGeometry, tolerance, method) for current, input_feature in enumerate(features): if feedback.isCanceled(): @@ -116,20 +107,18 @@ def processAlgorithm(self, context, feedback): input_geometry = input_feature.geometry() pointsBefore += input_geometry.geometry().nCoordinates() - if self.method == 0: # distance - output_geometry = input_geometry.simplify(self.tolerance) + if method == 0: # distance + output_geometry = input_geometry.simplify(tolerance) else: output_geometry = simplifier.simplify(input_geometry) pointsAfter += output_geometry.geometry().nCoordinates() out_feature.setGeometry(output_geometry) - self.sink.addFeature(out_feature, QgsFeatureSink.FastInsert) + sink.addFeature(out_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) QgsMessageLog.logMessage(self.tr('Simplify: Input geometries have been simplified from {0} to {1} points').format(pointsBefore, pointsAfter), self.tr('Processing'), QgsMessageLog.INFO) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/Smooth.py b/python/plugins/processing/algs/qgis/Smooth.py index 0e85212019df..bd27362879f5 100644 --- a/python/plugins/processing/algs/qgis/Smooth.py +++ b/python/plugins/processing/algs/qgis/Smooth.py @@ -66,48 +66,37 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Smoothed'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Smoothed'))) - self.source = None - self.iterations = None - self.offset = None - self.max_angle = None - self.sink = None - self.dest_id = None - def name(self): return 'smoothgeometry' def displayName(self): return self.tr('Smooth geometry') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) - self.offset = self.parameterAsDouble(parameters, self.OFFSET, context) - self.max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + iterations = self.parameterAsInt(parameters, self.ITERATIONS, context) + offset = self.parameterAsDouble(parameters, self.OFFSET, context) + max_angle = self.parameterAsDouble(parameters, self.MAX_ANGLE, context) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - self.source.fields(), self.source.wkbType(), self.source.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) - def processAlgorithm(self, context, feedback): - features = self.source.getFeatures() - total = 100.0 / self.source.featureCount() if self.source.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, input_feature in enumerate(features): if feedback.isCanceled(): break output_feature = input_feature if input_feature.geometry(): - output_geometry = input_feature.geometry().smooth(self.iterations, self.offset, -1, self.max_angle) + output_geometry = input_feature.geometry().smooth(iterations, offset, -1, max_angle) if not output_geometry: raise GeoAlgorithmExecutionException( self.tr('Error smoothing geometry')) output_feature.setGeometry(output_geometry) - self.sink.addFeature(output_feature, QgsFeatureSink.FastInsert) + sink.addFeature(output_feature, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py index e58d2aec7a30..955cc33e6e42 100644 --- a/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py +++ b/python/plugins/processing/algs/qgis/SpatialiteExecuteSQL.py @@ -49,34 +49,26 @@ def __init__(self): self.addParameter(QgsProcessingParameterVectorLayer(self.DATABASE, self.tr('File Database'), False, False)) self.addParameter(QgsProcessingParameterString(self.SQL, self.tr('SQL query'), '', True)) - self.database = None - self.sql = None - def name(self): return 'spatialiteexecutesql' def displayName(self): return self.tr('Spatialite execute SQL') - def prepareAlgorithm(self, parameters, context, feedback): - self.database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) - self.sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') - return True - - def processAlgorithm(self, context, feedback): - databaseuri = self.database.dataProvider().dataSourceUri() + def processAlgorithm(self, parameters, context, feedback): + database = self.parameterAsVectorLayer(parameters, self.DATABASE, context) + databaseuri = database.dataProvider().dataSourceUri() uri = QgsDataSourceUri(databaseuri) if uri.database() is '': if '|layerid' in databaseuri: databaseuri = databaseuri[:databaseuri.find('|layerid')] uri = QgsDataSourceUri('dbname=\'%s\'' % (databaseuri)) db = spatialite.GeoDB(uri) + sql = self.parameterAsString(parameters, self.SQL, context).replace('\n', ' ') try: - db._exec_sql_and_commit(str(self.sql)) + db._exec_sql_and_commit(str(sql)) except spatialite.DbError as e: raise GeoAlgorithmExecutionException( self.tr('Error executing SQL:\n{0}').format(str(e))) - return True - def postProcessAlgorithm(self, context, feedback): return {} diff --git a/python/plugins/processing/algs/qgis/SymmetricalDifference.py b/python/plugins/processing/algs/qgis/SymmetricalDifference.py index 41f234069554..fdd9a0127664 100644 --- a/python/plugins/processing/algs/qgis/SymmetricalDifference.py +++ b/python/plugins/processing/algs/qgis/SymmetricalDifference.py @@ -70,39 +70,32 @@ def __init__(self): self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Symmetrical difference'))) self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr('Symmetrical difference'))) - self.sourceA = None - self.sourceB = None - self.sink = None - self.dest_id = None - def name(self): return 'symmetricaldifference' def displayName(self): return self.tr('Symmetrical difference') - def prepareAlgorithm(self, parameters, context, feedback): - self.sourceA = self.parameterAsSource(parameters, self.INPUT, context) - self.sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) + def processAlgorithm(self, parameters, context, feedback): + sourceA = self.parameterAsSource(parameters, self.INPUT, context) + sourceB = self.parameterAsSource(parameters, self.OVERLAY, context) - geomType = QgsWkbTypes.multiType(self.sourceA.wkbType()) - fields = vector.combineFields(self.sourceA.fields(), self.sourceB.fields()) + geomType = QgsWkbTypes.multiType(sourceA.wkbType()) + fields = vector.combineFields(sourceA.fields(), sourceB.fields()) - (self.sink, self.dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, - fields, geomType, self.sourceA.sourceCrs()) - return True + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, geomType, sourceA.sourceCrs()) - def processAlgorithm(self, context, feedback): featB = QgsFeature() outFeat = QgsFeature() - indexA = QgsSpatialIndex(self.sourceA) - indexB = QgsSpatialIndex(self.sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(self.sourceA.sourceCrs()))) + indexA = QgsSpatialIndex(sourceA) + indexB = QgsSpatialIndex(sourceB.getFeatures(QgsFeatureRequest().setSubsetOfAttributes([]).setDestinationCrs(sourceA.sourceCrs()))) - total = 100.0 / (self.sourceA.featureCount() * self.sourceB.featureCount()) if self.sourceA.featureCount() and self.sourceB.featureCount() else 1 + total = 100.0 / (sourceA.featureCount() * sourceB.featureCount()) if sourceA.featureCount() and sourceB.featureCount() else 1 count = 0 - for featA in self.sourceA.getFeatures(): + for featA in sourceA.getFeatures(): if feedback.isCanceled(): break @@ -111,8 +104,8 @@ def processAlgorithm(self, context, feedback): attrs = featA.attributes() intersects = indexB.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) - request.setDestinationCrs(self.sourceA.sourceCrs()) - for featB in self.sourceB.getFeatures(request): + request.setDestinationCrs(sourceA.sourceCrs()) + for featB in sourceB.getFeatures(request): if feedback.isCanceled(): break tmpGeom = featB.geometry() @@ -122,7 +115,7 @@ def processAlgorithm(self, context, feedback): try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) - self.sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) @@ -131,9 +124,9 @@ def processAlgorithm(self, context, feedback): count += 1 feedback.setProgress(int(count * total)) - length = len(self.sourceA.fields()) + length = len(sourceA.fields()) - for featA in self.sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(self.sourceA.sourceCrs())): + for featA in sourceB.getFeatures(QgsFeatureRequest().setDestinationCrs(sourceA.sourceCrs())): if feedback.isCanceled(): break @@ -143,7 +136,7 @@ def processAlgorithm(self, context, feedback): attrs = [NULL] * length + attrs intersects = indexA.intersects(geom.boundingBox()) request = QgsFeatureRequest().setFilterFids(intersects).setSubsetOfAttributes([]) - for featB in self.sourceA.getFeatures(request): + for featB in sourceA.getFeatures(request): if feedback.isCanceled(): break @@ -154,7 +147,7 @@ def processAlgorithm(self, context, feedback): try: outFeat.setGeometry(diffGeom) outFeat.setAttributes(attrs) - self.sink.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) except: QgsMessageLog.logMessage(self.tr('Feature geometry error: One or more output features ignored due to invalid geometry.'), self.tr('Processing'), QgsMessageLog.WARNING) @@ -162,7 +155,5 @@ def processAlgorithm(self, context, feedback): count += 1 feedback.setProgress(int(count * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.dest_id} + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/VectorSplit.py b/python/plugins/processing/algs/qgis/VectorSplit.py index 4f59813ee4fb..6019e8c3752d 100644 --- a/python/plugins/processing/algs/qgis/VectorSplit.py +++ b/python/plugins/processing/algs/qgis/VectorSplit.py @@ -66,51 +66,42 @@ def __init__(self): self.addOutput(QgsProcessingOutputFolder(self.OUTPUT, self.tr('Output directory'))) - self.source = None - self.fieldName = None - self.directory = None - self.uniqueValues = None - self.sinks = {} - def name(self): return 'splitvectorlayer' def displayName(self): return self.tr('Split vector layer') - def prepareAlgorithm(self, parameters, context, feedback): - self.source = self.parameterAsSource(parameters, self.INPUT, context) - self.fieldName = self.parameterAsString(parameters, self.FIELD, context) - self.directory = self.parameterAsString(parameters, self.OUTPUT, context) - mkdir(self.directory) + def processAlgorithm(self, parameters, context, feedback): + source = self.parameterAsSource(parameters, self.INPUT, context) + fieldName = self.parameterAsString(parameters, self.FIELD, context) + directory = self.parameterAsString(parameters, self.OUTPUT, context) + + mkdir(directory) + + fieldIndex = source.fields().lookupField(fieldName) + uniqueValues = source.uniqueValues(fieldIndex) + baseName = os.path.join(directory, '{0}'.format(fieldName)) + + fields = source.fields() + crs = source.sourceCrs() + geomType = source.wkbType() - fieldIndex = self.source.fields().lookupField(self.fieldName) - self.uniqueValues = self.source.uniqueValues(fieldIndex) + total = 100.0 / len(uniqueValues) if uniqueValues else 1 - baseName = os.path.join(self.directory, '{0}'.format(self.fieldName)) - self.sinks = {} - for current, i in enumerate(self.uniqueValues): + for current, i in enumerate(uniqueValues): if feedback.isCanceled(): break fName = u'{0}_{1}.shp'.format(baseName, str(i).strip()) feedback.pushInfo(self.tr('Creating layer: {}').format(fName)) - sink, dest = QgsProcessingUtils.createFeatureSink(fName, context, self.source.fields, self.source.wkbType(), self.source.sourceCrs()) - self.sinks[i] = sink - return True - - def processAlgorithm(self, context, feedback): - total = 100.0 / len(self.uniqueValues) if self.uniqueValues else 1 - for current, i in enumerate(self.uniqueValues): - if feedback.isCanceled(): - break - sink = self.sinks[i] + sink, dest = QgsProcessingUtils.createFeatureSink(fName, context, fields, geomType, crs) - filter = '{} = {}'.format(QgsExpression.quotedColumnRef(self.fieldName), QgsExpression.quotedValue(i)) + filter = '{} = {}'.format(QgsExpression.quotedColumnRef(fieldName), QgsExpression.quotedValue(i)) req = QgsFeatureRequest().setFilterExpression(filter) count = 0 - for f in self.source.getFeatures(req): + for f in source.getFeatures(req): if feedback.isCanceled(): break sink.addFeature(f, QgsFeatureSink.FastInsert) @@ -119,7 +110,5 @@ def processAlgorithm(self, context, feedback): del sink feedback.setProgress(int(current * total)) - return True - def postProcessAlgorithm(self, context, feedback): - return {self.OUTPUT: self.directory} + return {self.OUTPUT: directory} diff --git a/python/plugins/processing/algs/qgis/ZonalStatistics.py b/python/plugins/processing/algs/qgis/ZonalStatistics.py index 92a26c3f36be..2ae842b5edc0 100644 --- a/python/plugins/processing/algs/qgis/ZonalStatistics.py +++ b/python/plugins/processing/algs/qgis/ZonalStatistics.py @@ -121,14 +121,11 @@ def prepareAlgorithm(self, parameters, context, feedback): self.rasterLayer = self.parameterAsRasterLayer(parameters, self.INPUT_RASTER, context) return True - def processAlgorithm(self, context, feedback): + def processAlgorithm(self, parameters, context, feedback): zs = QgsZonalStatistics(self.vectorLayer, self.rasterLayer, self.columnPrefix, self.bandNumber, QgsZonalStatistics.Statistics(self.selectedStats)) zs.calculateStatistics(feedback) - return True - - def postProcessAlgorithm(self, context, feedback): return {self.INPUT_VECTOR: self.vectorLayer} diff --git a/python/plugins/processing/script/ScriptAlgorithm.py b/python/plugins/processing/script/ScriptAlgorithm.py index def4a112044e..631f8f062954 100644 --- a/python/plugins/processing/script/ScriptAlgorithm.py +++ b/python/plugins/processing/script/ScriptAlgorithm.py @@ -248,7 +248,7 @@ def prepareAlgorithm(self, parameters, context, feedback): return True - def processAlgorithm(self, context, feedback): + def processAlgorithm(self, parameters, context, feedback): self.ns['feedback'] = feedback self.ns['context'] = context @@ -257,9 +257,6 @@ def processAlgorithm(self, context, feedback): for out in self.outputDefinitions(): self.results[out.name()] = self.ns[out.name()] del self.ns - return True - - def postProcessAlgorithm(self, context, feedback): return self.results def helpUrl(self): diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index ee4466213360..1aad193fe147 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -138,7 +138,7 @@ def check_algorithm(self, name, defs): pass else: results, ok = alg.run(parameters, context, feedback) - self.assertTrue(ok, parameters) + self.assertTrue(ok, 'params: {}, results: {}'.format(parameters, results)) self.check_results(results, context, defs['params'], defs['results']) def load_params(self, params): diff --git a/src/core/processing/qgsnativealgorithms.cpp b/src/core/processing/qgsnativealgorithms.cpp index 1cf40d97bf49..953d2ee2f23e 100644 --- a/src/core/processing/qgsnativealgorithms.cpp +++ b/src/core/processing/qgsnativealgorithms.cpp @@ -89,27 +89,23 @@ QgsCentroidAlgorithm *QgsCentroidAlgorithm::create() const return new QgsCentroidAlgorithm(); } -bool QgsCentroidAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsCentroidAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::Point, mSource->sourceCrs() ) ); - if ( !mSink ) - return false; + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::Point, source->sourceCrs() ) ); + if ( !sink ) + return QVariantMap(); - return true; -} - -bool QgsCentroidAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; int current = 0; @@ -129,19 +125,14 @@ bool QgsCentroidAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi QgsMessageLog::logMessage( QObject::tr( "Error calculating centroid for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } } - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + sink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - mSink->flushBuffer(); - return true; -} -QVariantMap QgsCentroidAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } @@ -178,47 +169,37 @@ QgsBufferAlgorithm *QgsBufferAlgorithm::create() const return new QgsBufferAlgorithm(); } -bool QgsBufferAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsBufferAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::Polygon, mSource->sourceCrs() ) ); - if ( !mSink ) - return false; + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::Polygon, source->sourceCrs() ) ); + if ( !sink ) + return QVariantMap(); // fixed parameters - mDissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context ); - mSegments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context ); - mEndCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) ); - mJoinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) ); - mMiterLimit = parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context ); - mBufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context ); - mDynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) ); - if ( mDynamicBuffer ) - { - mDynamicBufferProperty = parameters.value( QStringLiteral( "DISTANCE" ) ).value< QgsProperty >(); - mExpContext = context.expressionContext(); - mDefaultBuffer = parameterDefinition( QStringLiteral( "DISTANCE" ) )->defaultValue().toDouble(); - } - return true; -} - -bool QgsBufferAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return false; + bool dissolve = parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context ); + int segments = parameterAsInt( parameters, QStringLiteral( "SEGMENTS" ), context ); + QgsGeometry::EndCapStyle endCapStyle = static_cast< QgsGeometry::EndCapStyle >( 1 + parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) ); + QgsGeometry::JoinStyle joinStyle = static_cast< QgsGeometry::JoinStyle>( 1 + parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) ); + double miterLimit = parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context ); + double bufferDistance = parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context ); + bool dynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) ); + const QgsProcessingParameterDefinition *distanceParamDef = parameterDefinition( QStringLiteral( "DISTANCE" ) ); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; int current = 0; - double bufferDistance = mBufferDistance; - QList< QgsGeometry > bufferedGeometriesForDissolve; QgsAttributes dissolveAttrs; @@ -234,50 +215,45 @@ bool QgsBufferAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessing QgsFeature out = f; if ( out.hasGeometry() ) { - if ( mDynamicBuffer ) + if ( dynamicBuffer ) { - mExpContext.setFeature( f ); - bufferDistance = mDynamicBufferProperty.valueAsDouble( mExpContext, mDefaultBuffer ); + context.expressionContext().setFeature( f ); + bufferDistance = QgsProcessingParameters::parameterAsDouble( distanceParamDef, parameters, context ); } - QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, mSegments, mEndCapStyle, mJoinStyle, mMiterLimit ); + QgsGeometry outputGeometry = f.geometry().buffer( bufferDistance, segments, endCapStyle, joinStyle, miterLimit ); if ( !outputGeometry ) { QgsMessageLog::logMessage( QObject::tr( "Error calculating buffer for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } - if ( mDissolve ) + if ( dissolve ) bufferedGeometriesForDissolve << outputGeometry; else out.setGeometry( outputGeometry ); } - if ( !mDissolve ) - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + if ( !dissolve ) + sink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - if ( mDissolve ) + if ( dissolve ) { QgsGeometry finalGeometry = QgsGeometry::unaryUnion( bufferedGeometriesForDissolve ); QgsFeature f; f.setGeometry( finalGeometry ); f.setAttributes( dissolveAttrs ); - mSink->addFeature( f, QgsFeatureSink::FastInsert ); + sink->addFeature( f, QgsFeatureSink::FastInsert ); } - mSink->flushBuffer(); - return true; -} - -QVariantMap QgsBufferAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } + QgsDissolveAlgorithm::QgsDissolveAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -302,34 +278,31 @@ QgsDissolveAlgorithm *QgsDissolveAlgorithm::create() const return new QgsDissolveAlgorithm(); } -bool QgsDissolveAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsDissolveAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) ); + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) ); - if ( !mSink ) - return false; + if ( !sink ) + return QVariantMap(); - mFields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context ); - return true; -} + QStringList fields = parameterAsFields( parameters, QStringLiteral( "FIELD" ), context ); -bool QgsDissolveAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; int current = 0; - if ( mFields.isEmpty() ) + if ( fields.isEmpty() ) { // dissolve all - not using fields bool firstFeature = true; @@ -367,14 +340,14 @@ bool QgsDissolveAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi } outputFeature.setGeometry( QgsGeometry::unaryUnion( geomQueue ) ); - mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); } else { QList< int > fieldIndexes; - Q_FOREACH ( const QString &field, mFields ) + Q_FOREACH ( const QString &field, fields ) { - int index = mSource->fields().lookupField( field ); + int index = source->fields().lookupField( field ); if ( index >= 0 ) fieldIndexes << index; } @@ -418,21 +391,15 @@ bool QgsDissolveAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessi QgsFeature outputFeature; outputFeature.setGeometry( QgsGeometry::unaryUnion( geomIt.value() ) ); outputFeature.setAttributes( attributeHash.value( geomIt.key() ) ); - mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); feedback->setProgress( current * 100.0 / numberFeatures ); current++; } } - mSink->flushBuffer(); - return true; -} - -QVariantMap QgsDissolveAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } @@ -459,29 +426,25 @@ QgsClipAlgorithm *QgsClipAlgorithm::create() const return new QgsClipAlgorithm(); } -bool QgsClipAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsClipAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mFeatureSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mFeatureSource ) - return false; + std::unique_ptr< QgsFeatureSource > featureSource( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !featureSource ) + return QVariantMap(); - mMaskSource.reset( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); - if ( !mMaskSource ) - return false; + std::unique_ptr< QgsFeatureSource > maskSource( parameterAsSource( parameters, QStringLiteral( "OVERLAY" ), context ) ); + if ( !maskSource ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mFeatureSource->fields(), QgsWkbTypes::multiType( mFeatureSource->wkbType() ), mFeatureSource->sourceCrs() ) ); + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, featureSource->fields(), QgsWkbTypes::multiType( featureSource->wkbType() ), featureSource->sourceCrs() ) ); - if ( !mSink ) - return false; + if ( !sink ) + return QVariantMap(); - return true; -} - -bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ // first build up a list of clip geometries QList< QgsGeometry > clipGeoms; - QgsFeatureIterator it = mMaskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( mFeatureSource->sourceCrs() ) ); + QgsFeatureIterator it = maskSource->getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QList< int >() ).setDestinationCrs( featureSource->sourceCrs() ) ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -490,7 +453,7 @@ bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFe } if ( clipGeoms.isEmpty() ) - return true; + return QVariantMap(); // are we clipping against a single feature? if so, we can show finer progress reports bool singleClipFeature = false; @@ -521,7 +484,7 @@ bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFe break; } - QgsFeatureIterator inputIt = mFeatureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) ); + QgsFeatureIterator inputIt = featureSource->getFeatures( QgsFeatureRequest().setFilterRect( clipGeom.boundingBox() ) ); QgsFeatureList inputFeatures; QgsFeature f; while ( inputIt.nextFeature( f ) ) @@ -576,7 +539,8 @@ bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFe QgsFeature outputFeature; outputFeature.setGeometry( newGeometry ); outputFeature.setAttributes( inputFeature.attributes() ); - mSink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + sink->addFeature( outputFeature, QgsFeatureSink::FastInsert ); + if ( singleClipFeature ) feedback->setProgress( current * step ); @@ -588,17 +552,13 @@ bool QgsClipAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFe feedback->setProgress( 100.0 * static_cast< double >( i ) / clipGeoms.length() ); } } - mSink->flushBuffer(); - return true; -} -QVariantMap QgsClipAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } + QgsTransformAlgorithm::QgsTransformAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -619,41 +579,29 @@ QgsTransformAlgorithm *QgsTransformAlgorithm::create() const return new QgsTransformAlgorithm(); } -bool QgsTransformAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsTransformAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; - - mCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), mSource->wkbType(), mCrs ) ); - if ( !mSink ) - return false; - - return true; -} - -QVariantMap QgsTransformAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ + QgsCoordinateReferenceSystem targetCrs = parameterAsCrs( parameters, QStringLiteral( "TARGET_CRS" ), context ); - QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); - return outputs; -} + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), source->wkbType(), targetCrs ) ); + if ( !sink ) + return QVariantMap(); -bool QgsTransformAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; QgsFeatureRequest req; // perform reprojection in the iterators... - req.setDestinationCrs( mCrs ); + req.setDestinationCrs( targetCrs ); - QgsFeatureIterator it = mSource->getFeatures( req ); + QgsFeatureIterator it = source->getFeatures( req ); double step = 100.0 / count; int current = 0; @@ -664,12 +612,14 @@ bool QgsTransformAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcess break; } - mSink->addFeature( f, QgsFeatureSink::FastInsert ); + sink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - return true; + QVariantMap outputs; + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); + return outputs; } @@ -698,29 +648,25 @@ QgsSubdivideAlgorithm *QgsSubdivideAlgorithm::create() const return new QgsSubdivideAlgorithm(); } -bool QgsSubdivideAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsSubdivideAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mMaxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), - QgsWkbTypes::multiType( mSource->wkbType() ), mSource->sourceCrs() ) ); - if ( !mSink ) - return false; + int maxNodes = parameterAsInt( parameters, QStringLiteral( "MAX_NODES" ), context ); + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), + QgsWkbTypes::multiType( source->wkbType() ), source->sourceCrs() ) ); + if ( !sink ) + return QVariantMap(); - return true; -} - -bool QgsSubdivideAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; int current = 0; @@ -734,29 +680,25 @@ bool QgsSubdivideAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcess QgsFeature out = f; if ( out.hasGeometry() ) { - out.setGeometry( f.geometry().subdivide( mMaxNodes ) ); + out.setGeometry( f.geometry().subdivide( maxNodes ) ); if ( !out.geometry() ) { QgsMessageLog::logMessage( QObject::tr( "Error calculating subdivision for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING ); } } - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + sink->addFeature( out, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; } - mSink->flushBuffer(); - return true; -} -QVariantMap QgsSubdivideAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } + QgsMultipartToSinglepartAlgorithm::QgsMultipartToSinglepartAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -777,30 +719,26 @@ QgsMultipartToSinglepartAlgorithm *QgsMultipartToSinglepartAlgorithm::create() c return new QgsMultipartToSinglepartAlgorithm(); } -bool QgsMultipartToSinglepartAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsMultipartToSinglepartAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; - - QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( mSource->wkbType() ); + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mSinkId, mSource->fields(), - sinkType, mSource->sourceCrs() ) ); - if ( !mSink ) - return false; + QgsWkbTypes::Type sinkType = QgsWkbTypes::singleType( source->wkbType() ); - return true; -} + QString dest; + std::unique_ptr< QgsFeatureSink > sink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, dest, source->fields(), + sinkType, source->sourceCrs() ) ); + if ( !sink ) + return QVariantMap(); -bool QgsMultipartToSinglepartAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); QgsFeature f; - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); double step = 100.0 / count; int current = 0; @@ -820,34 +758,30 @@ bool QgsMultipartToSinglepartAlgorithm::processAlgorithm( QgsProcessingContext & Q_FOREACH ( const QgsGeometry &g, inputGeometry.asGeometryCollection() ) { out.setGeometry( g ); - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + sink->addFeature( out, QgsFeatureSink::FastInsert ); } } else { - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + sink->addFeature( out, QgsFeatureSink::FastInsert ); } } else { // feature with null geometry - mSink->addFeature( out, QgsFeatureSink::FastInsert ); + sink->addFeature( out, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); current++; } - mSink->flushBuffer(); - return true; -} -QVariantMap QgsMultipartToSinglepartAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), dest ); return outputs; } + QgsExtractByExpressionAlgorithm::QgsExtractByExpressionAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -872,50 +806,47 @@ QgsExtractByExpressionAlgorithm *QgsExtractByExpressionAlgorithm::create() const return new QgsExtractByExpressionAlgorithm(); } -bool QgsExtractByExpressionAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsExtractByExpressionAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; - - mExpressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mMatchingSinkId, mSource->fields(), - mSource->wkbType(), mSource->sourceCrs() ) ); - if ( !mMatchingSink ) - return false; + QString expressionString = parameterAsExpression( parameters, QStringLiteral( "EXPRESSION" ), context ); - mNonMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, mNonMatchingSinkId, mSource->fields(), - mSource->wkbType(), mSource->sourceCrs() ) ); + QString matchingSinkId; + std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(), + source->wkbType(), source->sourceCrs() ) ); + if ( !matchingSink ) + return QVariantMap(); - mExpressionContext = createExpressionContext( parameters, context ); - return true; -} + QString nonMatchingSinkId; + std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(), + source->wkbType(), source->sourceCrs() ) ); -bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - QgsExpression expression( mExpressionString ); + QgsExpression expression( expressionString ); if ( expression.hasParserError() ) { throw QgsProcessingException( expression.parserErrorString() ); } + QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); double step = 100.0 / count; int current = 0; - if ( !mNonMatchingSink ) + if ( !nonMatchingSink ) { // not saving failing features - so only fetch good features QgsFeatureRequest req; - req.setFilterExpression( mExpressionString ); - req.setExpressionContext( mExpressionContext ); + req.setFilterExpression( expressionString ); + req.setExpressionContext( expressionContext ); - QgsFeatureIterator it = mSource->getFeatures( req ); + QgsFeatureIterator it = source->getFeatures( req ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -924,7 +855,7 @@ bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, break; } - mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; @@ -933,10 +864,10 @@ bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, else { // saving non-matching features, so we need EVERYTHING - mExpressionContext.setFields( mSource->fields() ); - expression.prepare( &mExpressionContext ); + expressionContext.setFields( source->fields() ); + expression.prepare( &expressionContext ); - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -945,37 +876,30 @@ bool QgsExtractByExpressionAlgorithm::processAlgorithm( QgsProcessingContext &, break; } - mExpressionContext.setFeature( f ); - if ( expression.evaluate( &mExpressionContext ).toBool() ) + expressionContext.setFeature( f ); + if ( expression.evaluate( &expressionContext ).toBool() ) { - mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } else { - mNonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); current++; } } - if ( mMatchingSink ) - mMatchingSink->flushBuffer(); - if ( mNonMatchingSink ) - mNonMatchingSink->flushBuffer(); - return true; -} -QVariantMap QgsExtractByExpressionAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mMatchingSinkId ); - if ( !mNonMatchingSinkId.isEmpty() ) - outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), mNonMatchingSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); + if ( nonMatchingSink ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); return outputs; } + QgsExtractByAttributeAlgorithm::QgsExtractByAttributeAlgorithm() { addParameter( new QgsProcessingParameterFeatureSource( QStringLiteral( "INPUT" ), QObject::tr( "Input layer" ) ) ); @@ -1013,38 +937,34 @@ QgsExtractByAttributeAlgorithm *QgsExtractByAttributeAlgorithm::create() const return new QgsExtractByAttributeAlgorithm(); } -bool QgsExtractByAttributeAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback * ) +QVariantMap QgsExtractByAttributeAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - mSource.reset( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); - if ( !mSource ) - return false; + std::unique_ptr< QgsFeatureSource > source( parameterAsSource( parameters, QStringLiteral( "INPUT" ), context ) ); + if ( !source ) + return QVariantMap(); - mFieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); - mOp = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) ); - mValue = parameterAsString( parameters, QStringLiteral( "VALUE" ), context ); + QString fieldName = parameterAsString( parameters, QStringLiteral( "FIELD" ), context ); + Operation op = static_cast< Operation >( parameterAsEnum( parameters, QStringLiteral( "OPERATOR" ), context ) ); + QString value = parameterAsString( parameters, QStringLiteral( "VALUE" ), context ); - mMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, mMatchingSinkId, mSource->fields(), - mSource->wkbType(), mSource->sourceCrs() ) ); - if ( !mMatchingSink ) - return false; + QString matchingSinkId; + std::unique_ptr< QgsFeatureSink > matchingSink( parameterAsSink( parameters, QStringLiteral( "OUTPUT" ), context, matchingSinkId, source->fields(), + source->wkbType(), source->sourceCrs() ) ); + if ( !matchingSink ) + return QVariantMap(); - mNonMatchingSink.reset( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, mNonMatchingSinkId, mSource->fields(), - mSource->wkbType(), mSource->sourceCrs() ) ); + QString nonMatchingSinkId; + std::unique_ptr< QgsFeatureSink > nonMatchingSink( parameterAsSink( parameters, QStringLiteral( "FAIL_OUTPUT" ), context, nonMatchingSinkId, source->fields(), + source->wkbType(), source->sourceCrs() ) ); - mExpressionContext = createExpressionContext( parameters, context ); - return true; -} + int idx = source->fields().lookupField( fieldName ); + QVariant::Type fieldType = source->fields().at( idx ).type(); -bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback *feedback ) -{ - int idx = mSource->fields().lookupField( mFieldName ); - QVariant::Type fieldType = mSource->fields().at( idx ).type(); - - if ( fieldType != QVariant::String && ( mOp == BeginsWith || mOp == Contains || mOp == DoesNotContain ) ) + if ( fieldType != QVariant::String && ( op == BeginsWith || op == Contains || op == DoesNotContain ) ) { QString method; - switch ( mOp ) + switch ( op ) { case BeginsWith: method = QObject::tr( "begins with" ); @@ -1063,10 +983,10 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q throw QgsProcessingException( QObject::tr( "Operator '%1' can be used only with string fields." ).arg( method ) ); } - QString fieldRef = QgsExpression::quotedColumnRef( mFieldName ); - QString quotedVal = QgsExpression::quotedValue( mValue ); + QString fieldRef = QgsExpression::quotedColumnRef( fieldName ); + QString quotedVal = QgsExpression::quotedValue( value ); QString expr; - switch ( mOp ) + switch ( op ) { case Equals: expr = QStringLiteral( "%1 = %3" ).arg( fieldRef, quotedVal ); @@ -1087,10 +1007,10 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q expr = QStringLiteral( "%1 <= %3" ).arg( fieldRef, quotedVal ); break; case BeginsWith: - expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, mValue ); + expr = QStringLiteral( "%1 LIKE '%2%'" ).arg( fieldRef, value ); break; case Contains: - expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, mValue ); + expr = QStringLiteral( "%1 LIKE '%%2%'" ).arg( fieldRef, value ); break; case IsNull: expr = QStringLiteral( "%1 IS NULL" ).arg( fieldRef ); @@ -1099,31 +1019,34 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q expr = QStringLiteral( "%1 IS NOT NULL" ).arg( fieldRef ); break; case DoesNotContain: - expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, mValue ); + expr = QStringLiteral( "%1 NOT LIKE '%%2%'" ).arg( fieldRef, value ); break; } QgsExpression expression( expr ); if ( expression.hasParserError() ) { - throw QgsProcessingException( expression.parserErrorString() ); + // raise GeoAlgorithmExecutionException(expression.parserErrorString()) + return QVariantMap(); } - long count = mSource->featureCount(); - if ( count == 0 ) - return true; + QgsExpressionContext expressionContext = createExpressionContext( parameters, context ); + + long count = source->featureCount(); + if ( count <= 0 ) + return QVariantMap(); double step = 100.0 / count; int current = 0; - if ( !mNonMatchingSink ) + if ( !nonMatchingSink ) { // not saving failing features - so only fetch good features QgsFeatureRequest req; req.setFilterExpression( expr ); - req.setExpressionContext( mExpressionContext ); + req.setExpressionContext( expressionContext ); - QgsFeatureIterator it = mSource->getFeatures( req ); + QgsFeatureIterator it = source->getFeatures( req ); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -1132,7 +1055,7 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q break; } - mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); feedback->setProgress( current * step ); current++; @@ -1141,10 +1064,10 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q else { // saving non-matching features, so we need EVERYTHING - mExpressionContext.setFields( mSource->fields() ); - expression.prepare( &mExpressionContext ); + expressionContext.setFields( source->fields() ); + expression.prepare( &expressionContext ); - QgsFeatureIterator it = mSource->getFeatures(); + QgsFeatureIterator it = source->getFeatures(); QgsFeature f; while ( it.nextFeature( f ) ) { @@ -1153,14 +1076,14 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q break; } - mExpressionContext.setFeature( f ); - if ( expression.evaluate( &mExpressionContext ).toBool() ) + expressionContext.setFeature( f ); + if ( expression.evaluate( &expressionContext ).toBool() ) { - mMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + matchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } else { - mNonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); + nonMatchingSink->addFeature( f, QgsFeatureSink::FastInsert ); } feedback->setProgress( current * step ); @@ -1168,21 +1091,12 @@ bool QgsExtractByAttributeAlgorithm::processAlgorithm( QgsProcessingContext &, Q } } - if ( mMatchingSink ) - mMatchingSink->flushBuffer(); - if ( mNonMatchingSink ) - mNonMatchingSink->flushBuffer(); - return true; -} -QVariantMap QgsExtractByAttributeAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ QVariantMap outputs; - outputs.insert( QStringLiteral( "OUTPUT" ), mMatchingSinkId ); - if ( !mNonMatchingSinkId.isEmpty() ) - outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), mNonMatchingSinkId ); + outputs.insert( QStringLiteral( "OUTPUT" ), matchingSinkId ); + if ( nonMatchingSink ) + outputs.insert( QStringLiteral( "FAIL_OUTPUT" ), nonMatchingSinkId ); return outputs; } - ///@endcond diff --git a/src/core/processing/qgsnativealgorithms.h b/src/core/processing/qgsnativealgorithms.h index 275f6aef3573..8327f77caf5d 100644 --- a/src/core/processing/qgsnativealgorithms.h +++ b/src/core/processing/qgsnativealgorithms.h @@ -64,16 +64,9 @@ class QgsCentroidAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - private: - - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; }; /** @@ -95,17 +88,9 @@ class QgsTransformAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; - QgsCoordinateReferenceSystem mCrs; }; /** @@ -127,27 +112,9 @@ class QgsBufferAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: - - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; - bool mDissolve = false; - int mSegments = 8; - QgsGeometry::EndCapStyle mEndCapStyle = QgsGeometry::CapRound; - QgsGeometry::JoinStyle mJoinStyle = QgsGeometry::JoinStyleRound; - double mMiterLimit = 1; - double mBufferDistance = 1; - bool mDynamicBuffer = false; - QgsProperty mDynamicBufferProperty; - QVariantMap mDynamicParams; - double mDefaultBuffer; - QgsExpressionContext mExpContext; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + }; /** @@ -169,17 +136,8 @@ class QgsDissolveAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: - - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; - QStringList mFields; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; }; @@ -217,22 +175,9 @@ class QgsExtractByAttributeAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: - - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mMatchingSink; - QString mMatchingSinkId; - std::unique_ptr< QgsFeatureSink > mNonMatchingSink; - QString mNonMatchingSinkId; - QString mFieldName; - Operation mOp; - QString mValue; - QgsExpressionContext mExpressionContext; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + }; /** @@ -254,20 +199,9 @@ class QgsExtractByExpressionAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mMatchingSink; - QString mMatchingSinkId; - std::unique_ptr< QgsFeatureSink > mNonMatchingSink; - QString mNonMatchingSinkId; - QString mExpressionString; - QgsExpressionContext mExpressionContext; }; /** @@ -289,18 +223,8 @@ class QgsClipAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - - private: - - std::unique_ptr< QgsFeatureSource > mFeatureSource; - std::unique_ptr< QgsFeatureSource > mMaskSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; }; @@ -324,17 +248,8 @@ class QgsSubdivideAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: - - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; - int mMaxNodes = 64; + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; }; @@ -357,16 +272,9 @@ class QgsMultipartToSinglepartAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - - private: + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, + QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - std::unique_ptr< QgsFeatureSource > mSource; - std::unique_ptr< QgsFeatureSink > mSink; - QString mSinkId; }; ///@endcond PRIVATE diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 1f99b2be3cb3..68428f2d92db 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -265,6 +265,11 @@ bool QgsProcessingAlgorithm::prepareAlgorithm( const QVariantMap &, QgsProcessin return true; } +QVariantMap QgsProcessingAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) +{ + return QVariantMap(); +} + const QgsProcessingParameterDefinition *QgsProcessingAlgorithm::parameterDefinition( const QString &name ) const { Q_FOREACH ( const QgsProcessingParameterDefinition *def, mParameters ) @@ -329,14 +334,19 @@ QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProce if ( !res ) return QVariantMap(); - res = alg->runPrepared( context, feedback ); - if ( !res ) + QVariantMap runRes = alg->runPrepared( parameters, context, feedback ); + + if ( !alg->mHasExecuted ) return QVariantMap(); if ( ok ) *ok = true; - return alg->postProcess( context, feedback ); + QVariantMap ppRes = alg->postProcess( context, feedback ); + if ( !ppRes.isEmpty() ) + return ppRes; + else + return runRes; } bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) @@ -356,9 +366,9 @@ bool QgsProcessingAlgorithm::prepare( const QVariantMap ¶meters, QgsProcessi } } -bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +QVariantMap QgsProcessingAlgorithm::runPrepared( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { - Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", "prepare() was not called for the algorithm instance" ); + Q_ASSERT_X( mHasPrepared, "QgsProcessingAlgorithm::runPrepared", QString( "prepare() was not called for the algorithm instance %1" ).arg( name() ).toLatin1() ); Q_ASSERT_X( !mHasExecuted, "QgsProcessingAlgorithm::runPrepared", "runPrepared() was already called for this algorithm instance" ); // Hey kids, let's all be thread safe! It's the fun thing to do! @@ -388,8 +398,9 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc try { - mHasExecuted = processAlgorithm( *runContext, feedback ); + QVariantMap runResults = processAlgorithm( parameters, *runContext, feedback ); + mHasExecuted = true; if ( mLocalContext ) { // ok, time to clean things up. We need to push the temporary context back into @@ -397,7 +408,7 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc // current thread, so we HAVE to do this here) mLocalContext->pushToThread( context.thread() ); } - return mHasExecuted; + return runResults; } catch ( QgsProcessingException &e ) { @@ -409,14 +420,14 @@ bool QgsProcessingAlgorithm::runPrepared( QgsProcessingContext &context, QgsProc // see above! mLocalContext->pushToThread( context.thread() ); } - return false; + return QVariantMap(); } } QVariantMap QgsProcessingAlgorithm::postProcess( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { Q_ASSERT_X( QThread::currentThread() == context.temporaryLayerStore()->thread(), "QgsProcessingAlgorithm::postProcess", "postProcess() must be called from the same thread the context was created in" ); - Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", "runPrepared() was not called for the algorithm instance" ); + Q_ASSERT_X( mHasExecuted, "QgsProcessingAlgorithm::postProcess", QString( "algorithm instance %1 was not executed" ).arg( name() ).toLatin1() ); Q_ASSERT_X( !mHasPostProcessed, "QgsProcessingAlgorithm::postProcess", "postProcess() was already called for this algorithm instance" ); if ( mLocalContext ) diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index c72b74054f00..6494941201ec 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -265,7 +265,7 @@ class CORE_EXPORT QgsProcessingAlgorithm * on algorithms directly retrieved from QgsProcessingRegistry and QgsProcessingProvider. Instead, a copy * of the algorithm should be created with clone() and prepare()/runPrepared() called on the copy. */ - bool runPrepared( QgsProcessingContext &context, QgsProcessingFeedback *feedback ); + QVariantMap runPrepared( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ); /** * Should be called in the main thread following the completion of runPrepared(). This method @@ -371,7 +371,7 @@ class CORE_EXPORT QgsProcessingAlgorithm * \see prepareAlgorithm() * \see postProcessAlgorithm() */ - virtual bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); + virtual QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); /** * Allows the algorithm to perform any required cleanup tasks. The returned variant map @@ -389,7 +389,7 @@ class CORE_EXPORT QgsProcessingAlgorithm * \see prepareAlgorithm() * \see processAlgorithm() */ - virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0 SIP_VIRTUALERRORHANDLER( processing_exception_handler ); + virtual QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) SIP_VIRTUALERRORHANDLER( processing_exception_handler ); /** * Evaluates the parameter with matching \a name to a static string value. diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index e57716a0bfa9..4ea72fa33bba 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -24,6 +24,7 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgorithm *algorithm, const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) : QgsTask( tr( "Running %1" ).arg( algorithm->name() ), QgsTask::CanCancel ) + , mParameters( parameters ) , mContext( context ) , mFeedback( feedback ) , mAlgorithm( algorithm->create() ) @@ -33,7 +34,7 @@ QgsProcessingAlgRunnerTask::QgsProcessingAlgRunnerTask( const QgsProcessingAlgor mOwnedFeedback.reset( new QgsProcessingFeedback() ); mFeedback = mOwnedFeedback.get(); } - if ( !mAlgorithm->prepare( parameters, context, mFeedback ) ) + if ( !mAlgorithm->prepare( mParameters, context, mFeedback ) ) cancel(); } @@ -48,7 +49,8 @@ bool QgsProcessingAlgRunnerTask::run() bool ok = false; try { - ok = mAlgorithm->runPrepared( mContext, mFeedback ); + mResults = mAlgorithm->runPrepared( mParameters, mContext, mFeedback ); + ok = true; } catch ( QgsProcessingException & ) { @@ -60,9 +62,10 @@ bool QgsProcessingAlgRunnerTask::run() void QgsProcessingAlgRunnerTask::finished( bool result ) { Q_UNUSED( result ); + QVariantMap ppResults; if ( result ) { - mResults = mAlgorithm->postProcess( mContext, mFeedback ); + ppResults = mAlgorithm->postProcess( mContext, mFeedback ); } - emit executed( result, mResults ); + emit executed( result, !ppResults.isEmpty() ? ppResults : mResults ); } diff --git a/src/core/processing/qgsprocessingalgrunnertask.h b/src/core/processing/qgsprocessingalgrunnertask.h index 34791b96e123..2e2ba34915e8 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.h +++ b/src/core/processing/qgsprocessingalgrunnertask.h @@ -65,6 +65,7 @@ class CORE_EXPORT QgsProcessingAlgRunnerTask : public QgsTask private: + QVariantMap mParameters; QVariantMap mResults; QgsProcessingContext &mContext; QgsProcessingFeedback *mFeedback = nullptr; diff --git a/src/core/processing/qgsprocessingmodelalgorithm.cpp b/src/core/processing/qgsprocessingmodelalgorithm.cpp index 367afeafae81..e593d30ac5d1 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.cpp +++ b/src/core/processing/qgsprocessingmodelalgorithm.cpp @@ -472,7 +472,7 @@ bool QgsProcessingModelAlgorithm::childOutputIsRequired( const QString &childId, return false; } -bool QgsProcessingModelAlgorithm::processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) +QVariantMap QgsProcessingModelAlgorithm::processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) { QSet< QString > toExecute; QMap< QString, ChildAlgorithm >::const_iterator childIt = mChildAlgorithms.constBegin(); @@ -515,7 +515,7 @@ bool QgsProcessingModelAlgorithm::processAlgorithm( QgsProcessingContext &contex const ChildAlgorithm &child = mChildAlgorithms[ childId ]; - QVariantMap childParams = parametersForChildAlgorithm( child, mInputParameters, childResults ); + QVariantMap childParams = parametersForChildAlgorithm( child, parameters, childResults ); feedback->setProgressText( QObject::tr( "Running %1 [%2/%3]" ).arg( child.description() ).arg( executed.count() + 1 ).arg( toExecute.count() ) ); //feedback->pushDebugInfo( "Parameters: " + ', '.join( [str( p ).strip() + // '=' + str( p.value ) for p in alg.algorithm.parameters] ) ) @@ -551,11 +551,6 @@ bool QgsProcessingModelAlgorithm::processAlgorithm( QgsProcessingContext &contex feedback->pushDebugInfo( QObject::tr( "Model processed OK. Executed %1 algorithms total in %2 s." ).arg( executed.count() ).arg( totalTime.elapsed() / 1000.0 ) ); mResults = finalResults; - return true; -} - -QVariantMap QgsProcessingModelAlgorithm::postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) -{ return mResults; } @@ -655,12 +650,6 @@ QString QgsProcessingModelAlgorithm::asPythonCode() const return lines.join( '\n' ); } -bool QgsProcessingModelAlgorithm::prepareAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &, QgsProcessingFeedback * ) -{ - mInputParameters = parameters; - return true; -} - QVariantMap QgsProcessingModelAlgorithm::helpContent() const { return mHelpContent; diff --git a/src/core/processing/qgsprocessingmodelalgorithm.h b/src/core/processing/qgsprocessingmodelalgorithm.h index 0c8ac246f855..ecd671b7d9dc 100644 --- a/src/core/processing/qgsprocessingmodelalgorithm.h +++ b/src/core/processing/qgsprocessingmodelalgorithm.h @@ -826,10 +826,7 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm protected: - bool prepareAlgorithm( const QVariantMap ¶meters, - QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - bool processAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; - QVariantMap postProcessAlgorithm( QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; + QVariantMap processAlgorithm( const QVariantMap ¶meters, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) override; private: @@ -846,8 +843,6 @@ class CORE_EXPORT QgsProcessingModelAlgorithm : public QgsProcessingAlgorithm //! Model source file QString mSourceFile; - QVariantMap mInputParameters; - QVariantMap mResults; void dependsOnChildAlgorithmsRecursive( const QString &childId, QSet &depends ) const; diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 55aa7be656f6..8bfb7934490d 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -42,9 +42,7 @@ class DummyAlgorithm : public QgsProcessingAlgorithm QString name() const override { return mName; } QString displayName() const override { return mName; } - bool processAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) override { return true; } - bool prepareAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return true; } - QVariantMap postProcessAlgorithm( QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); } + QVariantMap processAlgorithm( const QVariantMap &, QgsProcessingContext &, QgsProcessingFeedback * ) override { return QVariantMap(); } virtual Flags flags() const override { return mFlags; } DummyAlgorithm *create() const override { return new DummyAlgorithm( name() ); } From 3601138d2a1ba9b1a00b8f5c736cf28212442869 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 13:56:34 +1000 Subject: [PATCH 32/49] Use a feature source instead of vector layer for QgsGeometrySnapper --- python/analysis/vector/qgsgeometrysnapper.sip | 6 +++--- src/analysis/vector/qgsgeometrysnapper.cpp | 10 ++++------ src/analysis/vector/qgsgeometrysnapper.h | 8 ++++---- src/core/qgsspatialindex.cpp | 2 +- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/python/analysis/vector/qgsgeometrysnapper.sip b/python/analysis/vector/qgsgeometrysnapper.sip index c0d3b95cb8c6..06744566fd32 100644 --- a/python/analysis/vector/qgsgeometrysnapper.sip +++ b/python/analysis/vector/qgsgeometrysnapper.sip @@ -33,11 +33,11 @@ class QgsGeometrySnapper : QObject EndPointToEndPoint, }; - QgsGeometrySnapper( QgsVectorLayer *referenceLayer ); + QgsGeometrySnapper( QgsFeatureSource *referenceSource ); %Docstring - Constructor for QgsGeometrySnapper. A reference layer which contains geometries to snap to must be + Constructor for QgsGeometrySnapper. A reference feature source which contains geometries to snap to must be set. It is assumed that all geometries snapped using this object will have the - same CRS as the reference layer (ie, no reprojection is performed). + same CRS as the reference source (ie, no reprojection is performed). %End QgsGeometry snapGeometry( const QgsGeometry &geometry, double snapTolerance, SnapMode mode = PreferNodes ) const; diff --git a/src/analysis/vector/qgsgeometrysnapper.cpp b/src/analysis/vector/qgsgeometrysnapper.cpp index 4b8a47d83d77..19297e8e61d0 100644 --- a/src/analysis/vector/qgsgeometrysnapper.cpp +++ b/src/analysis/vector/qgsgeometrysnapper.cpp @@ -459,13 +459,11 @@ QgsSnapIndex::SnapItem *QgsSnapIndex::getSnapItem( const QgsPoint &pos, double t // QgsGeometrySnapper // -QgsGeometrySnapper::QgsGeometrySnapper( QgsVectorLayer *referenceLayer ) - : mReferenceLayer( referenceLayer ) +QgsGeometrySnapper::QgsGeometrySnapper( QgsFeatureSource *referenceSource ) + : mReferenceSource( referenceSource ) { // Build spatial index - QgsFeatureRequest req; - req.setSubsetOfAttributes( QgsAttributeList() ); - mIndex = QgsSpatialIndex( mReferenceLayer->getFeatures( req ) ); + mIndex = QgsSpatialIndex( *mReferenceSource ); } QgsFeatureList QgsGeometrySnapper::snapFeatures( const QgsFeatureList &features, double snapTolerance, SnapMode mode ) @@ -496,7 +494,7 @@ QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, doubl QgsFeatureRequest refFeatureRequest = QgsFeatureRequest().setFilterFids( refFeatureIds ).setSubsetOfAttributes( QgsAttributeList() ); mReferenceLayerMutex.lock(); QgsFeature refFeature; - QgsFeatureIterator refFeatureIt = mReferenceLayer->getFeatures( refFeatureRequest ); + QgsFeatureIterator refFeatureIt = mReferenceSource->getFeatures( refFeatureRequest ); while ( refFeatureIt.nextFeature( refFeature ) ) { diff --git a/src/analysis/vector/qgsgeometrysnapper.h b/src/analysis/vector/qgsgeometrysnapper.h index e382e56dcd7f..94c3426318fd 100644 --- a/src/analysis/vector/qgsgeometrysnapper.h +++ b/src/analysis/vector/qgsgeometrysnapper.h @@ -53,11 +53,11 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject }; /** - * Constructor for QgsGeometrySnapper. A reference layer which contains geometries to snap to must be + * Constructor for QgsGeometrySnapper. A reference feature source which contains geometries to snap to must be * set. It is assumed that all geometries snapped using this object will have the - * same CRS as the reference layer (ie, no reprojection is performed). + * same CRS as the reference source (ie, no reprojection is performed). */ - QgsGeometrySnapper( QgsVectorLayer *referenceLayer ); + QgsGeometrySnapper( QgsFeatureSource *referenceSource ); /** * Snaps a geometry to the reference layer and returns the result. The geometry must be in the same @@ -99,7 +99,7 @@ class ANALYSIS_EXPORT QgsGeometrySnapper : public QObject enum PointFlag { SnappedToRefNode, SnappedToRefSegment, Unsnapped }; - QgsVectorLayer *mReferenceLayer = nullptr; + QgsFeatureSource *mReferenceSource = nullptr; QgsFeatureList mInputFeatures; QgsSpatialIndex mIndex; diff --git a/src/core/qgsspatialindex.cpp b/src/core/qgsspatialindex.cpp index 3879933a2d82..c6c2887965d6 100644 --- a/src/core/qgsspatialindex.cpp +++ b/src/core/qgsspatialindex.cpp @@ -231,7 +231,7 @@ QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureIterator &fi ) QgsSpatialIndex::QgsSpatialIndex( const QgsFeatureSource &source ) { - d = new QgsSpatialIndexData( source.getFeatures() ); + d = new QgsSpatialIndexData( source.getFeatures( QgsFeatureRequest().setSubsetOfAttributes( QgsAttributeList() ) ) ); } QgsSpatialIndex::QgsSpatialIndex( const QgsSpatialIndex &other ) //NOLINT From 3cbcd75d2fabeac810af523aaa88cfa541187629 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 15:21:46 +1000 Subject: [PATCH 33/49] Add equality operator for QgsProcessingFeatureSourceDefinition --- python/core/processing/qgsprocessingparameters.sip | 6 ++++++ src/core/processing/qgsprocessingparameters.h | 9 +++++++++ tests/src/core/testqgsprocessing.cpp | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/python/core/processing/qgsprocessingparameters.sip b/python/core/processing/qgsprocessingparameters.sip index 271102469c1a..35eca5ce7ef2 100644 --- a/python/core/processing/qgsprocessingparameters.sip +++ b/python/core/processing/qgsprocessingparameters.sip @@ -45,6 +45,12 @@ class QgsProcessingFeatureSourceDefinition True if only selected features in the source should be used by algorithms. %End + bool operator==( const QgsProcessingFeatureSourceDefinition &other ); + + bool operator!=( const QgsProcessingFeatureSourceDefinition &other ); +%Docstring + :rtype: bool +%End operator QVariant() const; %Docstring diff --git a/src/core/processing/qgsprocessingparameters.h b/src/core/processing/qgsprocessingparameters.h index e2c2b1565f53..8911f0e77022 100644 --- a/src/core/processing/qgsprocessingparameters.h +++ b/src/core/processing/qgsprocessingparameters.h @@ -72,6 +72,15 @@ class CORE_EXPORT QgsProcessingFeatureSourceDefinition */ bool selectedFeaturesOnly; + bool operator==( const QgsProcessingFeatureSourceDefinition &other ) + { + return source == other.source && selectedFeaturesOnly == other.selectedFeaturesOnly; + } + + bool operator!=( const QgsProcessingFeatureSourceDefinition &other ) + { + return !( *this == other ); + } //! Allows direct construction of QVariants. operator QVariant() const diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 8bfb7934490d..db6b1a0a893a 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -932,6 +932,10 @@ void TestQgsProcessing::features() ids = getIds( source->getFeatures() ); QVERIFY( !encountered ); + // equality operator + QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) == QgsProcessingFeatureSourceDefinition( layer->id(), true ) ); + QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( "b", true ) ); + QVERIFY( QgsProcessingFeatureSourceDefinition( layer->id(), true ) != QgsProcessingFeatureSourceDefinition( layer->id(), false ) ); } void TestQgsProcessing::uniqueValues() From ab06973d6a071f6afcb678a9c369f87d76af500a Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 15:22:26 +1000 Subject: [PATCH 34/49] Don't try to load default styles in processing test layers --- python/plugins/processing/tests/AlgorithmsTestBase.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 1aad193fe147..255f6e559ee9 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -216,9 +216,9 @@ def load_layer(self, id, param): self.in_place_layers[id] = filepath if param['type'] in ('vector', 'table'): - lyr = QgsVectorLayer(filepath, param['name'], 'ogr') + lyr = QgsVectorLayer(filepath, param['name'], 'ogr', False) elif param['type'] == 'raster': - lyr = QgsRasterLayer(filepath, param['name'], 'gdal') + lyr = QgsRasterLayer(filepath, param['name'], 'gdal', False) self.assertTrue(lyr.isValid(), 'Could not load layer "{}" from param {}'.format(filepath, param)) QgsProject.instance().addMapLayer(lyr) From d9fca48217c219e710ac5d820d169caf9994ed36 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 15:27:26 +1000 Subject: [PATCH 35/49] Add missing signal --- src/analysis/vector/qgsgeometrysnapper.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/analysis/vector/qgsgeometrysnapper.cpp b/src/analysis/vector/qgsgeometrysnapper.cpp index 19297e8e61d0..c075155f3d1d 100644 --- a/src/analysis/vector/qgsgeometrysnapper.cpp +++ b/src/analysis/vector/qgsgeometrysnapper.cpp @@ -477,10 +477,9 @@ void QgsGeometrySnapper::processFeature( QgsFeature &feature, double snapToleran { if ( !feature.geometry().isNull() ) feature.setGeometry( snapGeometry( feature.geometry(), snapTolerance, mode ) ); + emit featureSnapped(); } - - QgsGeometry QgsGeometrySnapper::snapGeometry( const QgsGeometry &geometry, double snapTolerance, SnapMode mode ) const { // Get potential reference features and construct snap index From 05364aa5f0038a79631e80aff26dbbc5dc136f5d Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 15:43:47 +1000 Subject: [PATCH 36/49] When running algorithm tests, if two parameters share the same layer source, ensure that the actual parameter values point to the same layer --- python/plugins/processing/tests/AlgorithmsTestBase.py | 7 +++++-- python/plugins/processing/tests/QgisAlgorithmsTest.py | 2 ++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/python/plugins/processing/tests/AlgorithmsTestBase.py b/python/plugins/processing/tests/AlgorithmsTestBase.py index 255f6e559ee9..c2f3ca98b296 100644 --- a/python/plugins/processing/tests/AlgorithmsTestBase.py +++ b/python/plugins/processing/tests/AlgorithmsTestBase.py @@ -77,8 +77,6 @@ def processingTestDataPath(): class AlgorithmsTest(object): - in_place_layers = {} - def test_algorithms(self): """ This is the main test function. All others will be executed based on the definitions in testdata/algorithm_tests.yaml @@ -96,6 +94,7 @@ def check_algorithm(self, name, defs): :param name: The identifier name used in the test output heading :param defs: A python dict containing a test algorithm definition """ + self.vector_layer_params = {} QgsProject.instance().removeAllMapLayers() params = self.load_params(defs['params']) @@ -216,7 +215,11 @@ def load_layer(self, id, param): self.in_place_layers[id] = filepath if param['type'] in ('vector', 'table'): + if filepath in self.vector_layer_params: + return self.vector_layer_params[filepath] + lyr = QgsVectorLayer(filepath, param['name'], 'ogr', False) + self.vector_layer_params[filepath] = lyr elif param['type'] == 'raster': lyr = QgsRasterLayer(filepath, param['name'], 'gdal', False) diff --git a/python/plugins/processing/tests/QgisAlgorithmsTest.py b/python/plugins/processing/tests/QgisAlgorithmsTest.py index 81be11e515e1..ea8c028c47c5 100644 --- a/python/plugins/processing/tests/QgisAlgorithmsTest.py +++ b/python/plugins/processing/tests/QgisAlgorithmsTest.py @@ -64,6 +64,8 @@ def setUpClass(cls): from processing.core.Processing import Processing Processing.initialize() cls.cleanup_paths = [] + cls.in_place_layers = {} + cls.vector_layer_params = {} @classmethod def tearDownClass(cls): From 90f10ae8538e789b877e3172c45528ded96c09ef Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 15:44:34 +1000 Subject: [PATCH 37/49] Port snap geometries algorithm to new API --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../processing/algs/qgis/SnapGeometries.py | 92 +++++---- .../tests/testdata/qgis_algorithm_tests.yaml | 188 +++++++++--------- 3 files changed, 150 insertions(+), 135 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 19cd4b84f5c2..76246f55bee4 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -69,6 +69,7 @@ from .SelectByExpression import SelectByExpression from .SimplifyGeometries import SimplifyGeometries from .Smooth import Smooth +from .SnapGeometries import SnapGeometriesToLayer from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SymmetricalDifference import SymmetricalDifference from .VectorSplit import VectorSplit @@ -156,7 +157,6 @@ # from .ExtendLines import ExtendLines # from .ExtractSpecificNodes import ExtractSpecificNodes # from .GeometryByExpression import GeometryByExpression -# from .SnapGeometries import SnapGeometriesToLayer # from .PoleOfInaccessibility import PoleOfInaccessibility # from .RasterCalculator import RasterCalculator # from .Heatmap import Heatmap @@ -230,7 +230,7 @@ def getAlgs(self): # IdwInterpolation(), TinInterpolation(), # RemoveNullGeometry(), # ExtendLines(), ExtractSpecificNodes(), - # GeometryByExpression(), SnapGeometriesToLayer(), + # GeometryByExpression(), # PoleOfInaccessibility(), # # RasterCalculator(), Heatmap(), Orthogonalize(), @@ -269,6 +269,7 @@ def getAlgs(self): SelectByExpression(), SimplifyGeometries(), Smooth(), + SnapGeometriesToLayer(), SpatialiteExecuteSQL(), SymmetricalDifference(), VectorSplit(), diff --git a/python/plugins/processing/algs/qgis/SnapGeometries.py b/python/plugins/processing/algs/qgis/SnapGeometries.py index 6f7e3005c34c..25eef8cad7e5 100644 --- a/python/plugins/processing/algs/qgis/SnapGeometries.py +++ b/python/plugins/processing/algs/qgis/SnapGeometries.py @@ -27,14 +27,15 @@ from qgis.analysis import (QgsGeometrySnapper, QgsInternalGeometrySnapper) -from qgis.core import (QgsApplication, - QgsFeature, - QgsFeatureSink, - QgsProcessingUtils) +from qgis.core import (QgsFeatureSink, + QgsProcessingParameterDefinition, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterNumber, + QgsProcessingParameterEnum, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm -from processing.core.parameters import ParameterVector, ParameterNumber, ParameterSelection -from processing.core.outputs import OutputVector class SnapGeometriesToLayer(QgisAlgorithm): @@ -50,20 +51,28 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, self.tr('Input layer'))) - self.addParameter(ParameterVector(self.REFERENCE_LAYER, self.tr('Reference layer'))) - self.addParameter(ParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), 0.00000001, 9999999999, default=10.0)) + + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint, QgsProcessingParameterDefinition.TypeVectorLine, QgsProcessingParameterDefinition.TypeVectorPolygon])) + self.addParameter(QgsProcessingParameterFeatureSource(self.REFERENCE_LAYER, self.tr('Reference layer'), + [QgsProcessingParameterDefinition.TypeVectorPoint, + QgsProcessingParameterDefinition.TypeVectorLine, + QgsProcessingParameterDefinition.TypeVectorPolygon])) + + self.addParameter(QgsProcessingParameterNumber(self.TOLERANCE, self.tr('Tolerance (layer units)'), type=QgsProcessingParameterNumber.Double, + minValue=0.00000001, maxValue=9999999999, defaultValue=10.0)) self.modes = [self.tr('Prefer aligning nodes'), self.tr('Prefer closest point'), self.tr('Move end points only, prefer aligning nodes'), self.tr('Move end points only, prefer closest point'), self.tr('Snap end points to end points only')] - self.addParameter(ParameterSelection( + self.addParameter(QgsProcessingParameterEnum( self.BEHAVIOR, self.tr('Behavior'), - self.modes, default=0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Snapped geometries'))) + options=self.modes, defaultValue=0)) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Snapped geometry'))) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Snapped geometry"))) def name(self): return 'snapgeometries' @@ -72,39 +81,44 @@ def displayName(self): return self.tr('Snap geometries to layer') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - reference_layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.REFERENCE_LAYER), context) - tolerance = self.getParameterValue(self.TOLERANCE) - mode = self.getParameterValue(self.BEHAVIOR) - - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), layer.wkbType(), layer.crs(), - context) - - features = QgsProcessingUtils.getFeatures(layer, context) - - self.processed = 0 - self.feedback = feedback - self.total = 100.0 / layer.featureCount() if layer.featureCount() else 0 - - if self.getParameterValue(self.INPUT) != self.getParameterValue(self.REFERENCE_LAYER): - snapper = QgsGeometrySnapper(reference_layer) - snapper.featureSnapped.connect(self.featureSnapped) - snapped_features = snapper.snapFeatures(features, tolerance, mode) - for f in snapped_features: - writer.addFeature(f, QgsFeatureSink.FastInsert) + source = self.parameterAsSource(parameters, self.INPUT, context) + + reference_source = self.parameterAsSource(parameters, self.REFERENCE_LAYER, context) + tolerance = self.parameterAsDouble(parameters, self.TOLERANCE, context) + mode = self.parameterAsEnum(parameters, self.BEHAVIOR, context) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), source.wkbType(), source.sourceCrs()) + + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 + + if parameters[self.INPUT] != parameters[self.REFERENCE_LAYER]: + snapper = QgsGeometrySnapper(reference_source) + processed = 0 + for f in features: + if feedback.isCanceled(): + break + if f.hasGeometry(): + out_feature = f + out_feature.setGeometry(snapper.snapGeometry(f.geometry(), tolerance, mode)) + sink.addFeature(out_feature, QgsFeatureSink.FastInsert) + else: + sink.addFeature(f) + processed += 1 + feedback.setProgress(processed * total) else: # snapping internally snapper = QgsInternalGeometrySnapper(tolerance, mode) processed = 0 for f in features: + if feedback.isCanceled(): + break + out_feature = f out_feature.setGeometry(snapper.snapFeature(f)) - writer.addFeature(out_feature, QgsFeatureSink.FastInsert) + sink.addFeature(out_feature, QgsFeatureSink.FastInsert) processed += 1 - feedback.setProgress(processed * self.total) - - del writer + feedback.setProgress(processed * total) - def featureSnapped(self): - self.processed += 1 - self.feedback.setProgress(int(self.processed * self.total)) + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 849151d4eda3..8f758affe5cf 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1376,100 +1376,100 @@ tests: # OUTPUT_LAYER: # name: expected/geometry_by_expression_line.gml # type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap lines to lines -# params: -# INPUT: -# name: snap_lines.gml -# type: vector -# REFERENCE_LAYER: -# name: lines.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_lines_to_lines.gml -# type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap polygons to polygons -# params: -# INPUT: -# name: snap_polys.gml -# type: vector -# REFERENCE_LAYER: -# name: polys.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_polys_to_polys.gml -# type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap points to points -# params: -# INPUT: -# name: snap_points.gml -# type: vector -# REFERENCE_LAYER: -# name: points.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_points_to_points.gml -# type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap points to lines (prefer nodes) -# params: -# BEHAVIOR: '0' -# INPUT: -# name: snap_points.gml -# type: vector -# REFERENCE_LAYER: -# name: lines.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_point_to_lines_prefer_nodes.gml -# type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap points to lines (prefer closest) -# params: -# BEHAVIOR: '1' -# INPUT: -# name: snap_points.gml -# type: vector -# REFERENCE_LAYER: -# name: lines.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_point_to_lines_prefer_closest.gml -# type: vector -# -# - algorithm: qgis:snapgeometries -# name: Snap internal -# params: -# BEHAVIOR: '0' -# INPUT: -# name: custom/snap_internal.gml -# type: vector -# REFERENCE_LAYER: -# name: custom/snap_internal.gml -# type: vector -# TOLERANCE: 1.0 -# results: -# OUTPUT: -# name: expected/snap_internal.gml -# type: vector -# + + - algorithm: qgis:snapgeometries + name: Snap lines to lines + params: + INPUT: + name: snap_lines.gml + type: vector + REFERENCE_LAYER: + name: lines.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_lines_to_lines.gml + type: vector + + - algorithm: qgis:snapgeometries + name: Snap polygons to polygons + params: + INPUT: + name: snap_polys.gml + type: vector + REFERENCE_LAYER: + name: polys.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_polys_to_polys.gml + type: vector + + - algorithm: qgis:snapgeometries + name: Snap points to points + params: + INPUT: + name: snap_points.gml + type: vector + REFERENCE_LAYER: + name: points.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_points_to_points.gml + type: vector + + - algorithm: qgis:snapgeometries + name: Snap points to lines (prefer nodes) + params: + BEHAVIOR: '0' + INPUT: + name: snap_points.gml + type: vector + REFERENCE_LAYER: + name: lines.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_point_to_lines_prefer_nodes.gml + type: vector + + - algorithm: qgis:snapgeometries + name: Snap points to lines (prefer closest) + params: + BEHAVIOR: '1' + INPUT: + name: snap_points.gml + type: vector + REFERENCE_LAYER: + name: lines.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_point_to_lines_prefer_closest.gml + type: vector + + - algorithm: qgis:snapgeometries + name: Snap internal + params: + BEHAVIOR: '0' + INPUT: + name: custom/snap_internal.gml + type: vector + REFERENCE_LAYER: + name: custom/snap_internal.gml + type: vector + TOLERANCE: 1.0 + results: + OUTPUT: + name: expected/snap_internal.gml + type: vector + # - algorithm: qgis:poleofinaccessibility # name: Pole of inaccessibility (polygons) # params: From ebd346c407c526343ebfa19f6de2300a537388ab Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 16:30:11 +1000 Subject: [PATCH 38/49] runPrepared rethrows exceptions --- .../processing/qgsprocessingalgorithm.cpp | 21 ++++++++++++------- .../processing/qgsprocessingalgrunnertask.cpp | 4 +++- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/core/processing/qgsprocessingalgorithm.cpp b/src/core/processing/qgsprocessingalgorithm.cpp index 68428f2d92db..5f5370dc5109 100644 --- a/src/core/processing/qgsprocessingalgorithm.cpp +++ b/src/core/processing/qgsprocessingalgorithm.cpp @@ -334,10 +334,17 @@ QVariantMap QgsProcessingAlgorithm::run( const QVariantMap ¶meters, QgsProce if ( !res ) return QVariantMap(); - QVariantMap runRes = alg->runPrepared( parameters, context, feedback ); - - if ( !alg->mHasExecuted ) + QVariantMap runRes; + try + { + runRes = alg->runPrepared( parameters, context, feedback ); + } + catch ( QgsProcessingException &e ) + { + QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); + feedback->reportError( e.what() ); return QVariantMap(); + } if ( ok ) *ok = true; @@ -410,17 +417,15 @@ QVariantMap QgsProcessingAlgorithm::runPrepared( const QVariantMap ¶meters, } return runResults; } - catch ( QgsProcessingException &e ) + catch ( QgsProcessingException & ) { - QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); - feedback->reportError( e.what() ); - if ( mLocalContext ) { // see above! mLocalContext->pushToThread( context.thread() ); } - return QVariantMap(); + //rethrow + throw; } } diff --git a/src/core/processing/qgsprocessingalgrunnertask.cpp b/src/core/processing/qgsprocessingalgrunnertask.cpp index 4ea72fa33bba..8677f5d6234d 100644 --- a/src/core/processing/qgsprocessingalgrunnertask.cpp +++ b/src/core/processing/qgsprocessingalgrunnertask.cpp @@ -52,8 +52,10 @@ bool QgsProcessingAlgRunnerTask::run() mResults = mAlgorithm->runPrepared( mParameters, mContext, mFeedback ); ok = true; } - catch ( QgsProcessingException & ) + catch ( QgsProcessingException &e ) { + QgsMessageLog::logMessage( e.what(), QObject::tr( "Processing" ), QgsMessageLog::CRITICAL ); + mFeedback->reportError( e.what() ); return false; } return ok && !mFeedback->isCanceled(); From 75cd91b1a03676dd57ef9d2be6f764a9c6929734 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 16:30:25 +1000 Subject: [PATCH 39/49] Port voronoi polygons algorithm to new API --- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- .../processing/algs/qgis/VoronoiPolygons.py | 55 +++++++++++-------- .../tests/testdata/qgis_algorithm_tests.yaml | 50 ++++++++--------- 3 files changed, 61 insertions(+), 49 deletions(-) diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 76246f55bee4..8f66914e1f5f 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -73,6 +73,7 @@ from .SpatialiteExecuteSQL import SpatialiteExecuteSQL from .SymmetricalDifference import SymmetricalDifference from .VectorSplit import VectorSplit +from .VoronoiPolygons import VoronoiPolygons from .ZonalStatistics import ZonalStatistics # from .ExtractByLocation import ExtractByLocation @@ -87,7 +88,6 @@ # from .UniqueValues import UniqueValues # from .ExportGeometryInfo import ExportGeometryInfo # from .Delaunay import Delaunay -# from .VoronoiPolygons import VoronoiPolygons # from .LinesToPolygons import LinesToPolygons # from .PolygonsToLines import PolygonsToLines # from .SinglePartsToMultiparts import SinglePartsToMultiparts @@ -190,7 +190,7 @@ def getAlgs(self): # NearestNeighbourAnalysis(), MeanCoords(), # LinesIntersection(), UniqueValues(), PointDistance(), # ExportGeometryInfo(), - # Delaunay(), VoronoiPolygons(), + # Delaunay(), # , SinglePartsToMultiparts(), # PolygonsToLines(), LinesToPolygons(), ExtractNodes(), # ConvexHull(), FixedDistanceBuffer(), @@ -273,6 +273,7 @@ def getAlgs(self): SpatialiteExecuteSQL(), SymmetricalDifference(), VectorSplit(), + VoronoiPolygons(), ZonalStatistics() ] diff --git a/python/plugins/processing/algs/qgis/VoronoiPolygons.py b/python/plugins/processing/algs/qgis/VoronoiPolygons.py index 5df83a1b8bb8..73b9567df994 100644 --- a/python/plugins/processing/algs/qgis/VoronoiPolygons.py +++ b/python/plugins/processing/algs/qgis/VoronoiPolygons.py @@ -30,14 +30,21 @@ from qgis.PyQt.QtGui import QIcon -from qgis.core import QgsFeatureRequest, QgsFeatureSink, QgsFeature, QgsGeometry, QgsPointXY, QgsWkbTypes, QgsProcessingUtils +from qgis.core import (QgsFeatureRequest, + QgsFeatureSink, + QgsFeature, + QgsGeometry, + QgsPointXY, + QgsWkbTypes, + QgsProcessingUtils, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterDefinition, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer, + QgsProcessingParameterNumber) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.outputs import OutputVector -from processing.tools import dataobjects from . import voronoi @@ -58,12 +65,13 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POINT])) - self.addParameter(ParameterNumber(self.BUFFER, - self.tr('Buffer region'), 0.0, 100.0, 0.0)) - self.addOutput(OutputVector(self.OUTPUT, self.tr('Voronoi polygons'), datatype=[dataobjects.TYPE_VECTOR_POLYGON])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint])) + self.addParameter(QgsProcessingParameterNumber(self.BUFFER, self.tr('Buffer region'), type=QgsProcessingParameterNumber.Double, + minValue=0.0, maxValue=9999999999, defaultValue=0.0)) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Voronoi polygons'), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Voronoi polygons"), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) def name(self): return 'voronoipolygons' @@ -72,15 +80,13 @@ def displayName(self): return self.tr('Voronoi polygons') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) - - buf = self.getParameterValue(self.BUFFER) - - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), QgsWkbTypes.Polygon, - layer.crs(), context) + source = self.parameterAsSource(parameters, self.INPUT, context) + buf = self.parameterAsDouble(parameters, self.BUFFER, context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + source.fields(), QgsWkbTypes.Polygon, source.sourceCrs()) outFeat = QgsFeature() - extent = layer.extent() + extent = source.sourceExtent() extraX = extent.height() * (buf / 100.0) extraY = extent.width() * (buf / 100.0) height = extent.height() @@ -90,9 +96,11 @@ def processAlgorithm(self, parameters, context, feedback): ptDict = {} ptNdx = -1 - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): + if feedback.isCanceled(): + break geom = inFeat.geometry() point = geom.asPoint() x = point.x() - extent.xMinimum() @@ -122,20 +130,23 @@ def processAlgorithm(self, parameters, context, feedback): total = 100.0 / len(c.polygons) for (site, edges) in list(c.polygons.items()): + if feedback.isCanceled(): + break + request = QgsFeatureRequest().setFilterFid(ptDict[ids[site]]) - inFeat = next(layer.getFeatures(request)) + inFeat = next(source.getFeatures(request)) lines = self.clip_voronoi(edges, c, width, height, extent, extraX, extraY) geom = QgsGeometry.fromMultiPoint(lines) geom = QgsGeometry(geom.convexHull()) outFeat.setGeometry(geom) outFeat.setAttributes(inFeat.attributes()) - writer.addFeature(outFeat, QgsFeatureSink.FastInsert) + sink.addFeature(outFeat, QgsFeatureSink.FastInsert) current += 1 feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} def clip_voronoi(self, edges, c, width, height, extent, exX, exY): """Clip voronoi function based on code written for Inkscape. diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 8f758affe5cf..48db808930cc 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -2300,31 +2300,31 @@ tests: # OUTPUT: # name: expected/polygonize.gml # type: vector -# -# - algorithm: qgis:voronoipolygons -# name: Standard voronoi -# params: -# BUFFER: 0.0 -# INPUT: -# name: points.gml -# type: vector -# results: -# OUTPUT: -# name: expected/voronoi.gml -# type: vector -# -# - algorithm: qgis:voronoipolygons -# name: Vornoi with buffer region -# params: -# BUFFER: 10.0 -# INPUT: -# name: points.gml -# type: vector -# results: -# OUTPUT: -# name: expected/voronoi_buffer.gml -# type: vector -# + + - algorithm: qgis:voronoipolygons + name: Standard voronoi + params: + BUFFER: 0.0 + INPUT: + name: points.gml + type: vector + results: + OUTPUT: + name: expected/voronoi.gml + type: vector + + - algorithm: qgis:voronoipolygons + name: Vornoi with buffer region + params: + BUFFER: 10.0 + INPUT: + name: points.gml + type: vector + results: + OUTPUT: + name: expected/voronoi_buffer.gml + type: vector + # - algorithm: qgis:findprojection # name: Find projection # params: From a15d283cd6bd2d2b136639a9047e6c8314a578a0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 18:00:51 +1000 Subject: [PATCH 40/49] Port delaunay triangulation alg to new API --- .../plugins/processing/algs/qgis/Delaunay.py | 38 +++++++++++-------- .../algs/qgis/QGISAlgorithmProvider.py | 4 +- .../tests/testdata/qgis_algorithm_tests.yaml | 24 ++++++------ 3 files changed, 36 insertions(+), 30 deletions(-) diff --git a/python/plugins/processing/algs/qgis/Delaunay.py b/python/plugins/processing/algs/qgis/Delaunay.py index 75997916765c..05b3bb0676f7 100644 --- a/python/plugins/processing/algs/qgis/Delaunay.py +++ b/python/plugins/processing/algs/qgis/Delaunay.py @@ -39,13 +39,14 @@ QgsPointXY, QgsWkbTypes, QgsProcessingUtils, - QgsFields) + QgsFields, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterDefinition, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.outputs import OutputVector -from processing.tools import dataobjects from . import voronoi @@ -65,12 +66,10 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(ParameterVector(self.INPUT, - self.tr('Input layer'), [dataobjects.TYPE_VECTOR_POINT])) - self.addOutput(OutputVector(self.OUTPUT, - self.tr('Delaunay triangulation'), - datatype=[dataobjects.TYPE_VECTOR_POLYGON])) + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input layer'), [QgsProcessingParameterDefinition.TypeVectorPoint])) + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Delaunay triangulation'), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Delaunay triangulation"), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) def name(self): return 'delaunaytriangulation' @@ -79,22 +78,26 @@ def displayName(self): return self.tr('Delaunay triangulation') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(self.INPUT), context) + source = self.parameterAsSource(parameters, self.INPUT, context) fields = QgsFields() fields.append(QgsField('POINTA', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTB', QVariant.Double, '', 24, 15)) fields.append(QgsField('POINTC', QVariant.Double, '', 24, 15)) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(fields, QgsWkbTypes.Polygon, layer.crs(), context) + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + fields, QgsWkbTypes.Polygon, source.sourceCrs()) pts = [] ptDict = {} ptNdx = -1 c = voronoi.Context() - features = QgsProcessingUtils.getFeatures(layer, context) - total = 100.0 / layer.featureCount() if layer.featureCount() else 0 + features = source.getFeatures() + total = 100.0 / source.featureCount() if source.featureCount() else 0 for current, inFeat in enumerate(features): + if feedback.isCanceled(): + break + geom = QgsGeometry(inFeat.geometry()) if geom.isNull(): continue @@ -125,6 +128,9 @@ def processAlgorithm(self, parameters, context, feedback): total = 100.0 / len(triangles) if triangles else 1 for current, triangle in enumerate(triangles): + if feedback.isCanceled(): + break + indices = list(triangle) indices.append(indices[0]) polygon = [] @@ -133,7 +139,7 @@ def processAlgorithm(self, parameters, context, feedback): for index in indices: fid, n = ptDict[ids[index]] request = QgsFeatureRequest().setFilterFid(fid) - inFeat = next(layer.getFeatures(request)) + inFeat = next(source.getFeatures(request)) geom = QgsGeometry(inFeat.geometry()) if geom.isMultipart(): point = QgsPointXY(geom.asMultiPoint()[n]) @@ -146,7 +152,7 @@ def processAlgorithm(self, parameters, context, feedback): feat.setAttributes(attrs) geometry = QgsGeometry().fromPolygon([polygon]) feat.setGeometry(geometry) - writer.addFeature(feat, QgsFeatureSink.FastInsert) + sink.addFeature(feat, QgsFeatureSink.FastInsert) feedback.setProgress(int(current * total)) - del writer + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 8f66914e1f5f..95ac4978f44b 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -48,6 +48,7 @@ from .BoundingBox import BoundingBox from .CheckValidity import CheckValidity from .CreateAttributeIndex import CreateAttributeIndex +from .Delaunay import Delaunay from .DeleteColumn import DeleteColumn from .DeleteHoles import DeleteHoles from .DensifyGeometries import DensifyGeometries @@ -87,7 +88,6 @@ # from .PointDistance import PointDistance # from .UniqueValues import UniqueValues # from .ExportGeometryInfo import ExportGeometryInfo -# from .Delaunay import Delaunay # from .LinesToPolygons import LinesToPolygons # from .PolygonsToLines import PolygonsToLines # from .SinglePartsToMultiparts import SinglePartsToMultiparts @@ -190,7 +190,6 @@ def getAlgs(self): # NearestNeighbourAnalysis(), MeanCoords(), # LinesIntersection(), UniqueValues(), PointDistance(), # ExportGeometryInfo(), - # Delaunay(), # , SinglePartsToMultiparts(), # PolygonsToLines(), LinesToPolygons(), ExtractNodes(), # ConvexHull(), FixedDistanceBuffer(), @@ -248,6 +247,7 @@ def getAlgs(self): BoundingBox(), CheckValidity(), CreateAttributeIndex(), + Delaunay(), DeleteColumn(), DeleteHoles(), DensifyGeometries(), diff --git a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml index 48db808930cc..00d526ececb3 100644 --- a/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml +++ b/python/plugins/processing/tests/testdata/qgis_algorithm_tests.yaml @@ -1162,18 +1162,18 @@ tests: # OUTPUT: # name: expected/sum_line_length.gml # type: vector -# -# - algorithm: qgis:delaunaytriangulation -# name: Delaunay triangulation (multipoint data) -# params: -# INPUT: -# name: multipoints.gml -# type: vector -# results: -# OUTPUT: -# name: expected/multipoint_delaunay.gml -# type: vector -# + + - algorithm: qgis:delaunaytriangulation + name: Delaunay triangulation (multipoint data) + params: + INPUT: + name: multipoints.gml + type: vector + results: + OUTPUT: + name: expected/multipoint_delaunay.gml + type: vector + # - algorithm: qgis:idwinterpolation # name: IDW interpolation using attribute # params: From db816ec3fee53ff9d96072405ac8b15b29b1f0fb Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 18:00:57 +1000 Subject: [PATCH 41/49] Port a multi-step algorithm to new API (concave hull) --- .../processing/algs/qgis/ConcaveHull.py | 79 +++++++++++-------- .../algs/qgis/QGISAlgorithmProvider.py | 5 +- 2 files changed, 50 insertions(+), 34 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ConcaveHull.py b/python/plugins/processing/algs/qgis/ConcaveHull.py index 621e280f3385..129e1a0058c3 100644 --- a/python/plugins/processing/algs/qgis/ConcaveHull.py +++ b/python/plugins/processing/algs/qgis/ConcaveHull.py @@ -32,14 +32,16 @@ QgsFeatureSink, QgsWkbTypes, QgsApplication, - QgsProcessingUtils) + QgsProcessingUtils, + QgsProcessingParameterFeatureSource, + QgsProcessingParameterVectorLayer, + QgsProcessingParameterDefinition, + QgsProcessingParameterNumber, + QgsProcessingParameterBoolean, + QgsProcessingParameterFeatureSink, + QgsProcessingOutputVectorLayer) from processing.algs.qgis.QgisAlgorithm import QgisAlgorithm from processing.core.GeoAlgorithmExecutionException import GeoAlgorithmExecutionException -from processing.core.parameters import ParameterVector -from processing.core.parameters import ParameterNumber -from processing.core.parameters import ParameterBoolean -from processing.core.outputs import OutputVector -from processing.tools import dataobjects import processing from math import sqrt @@ -57,17 +59,19 @@ def group(self): def __init__(self): super().__init__() - self.addParameter(ParameterVector(ConcaveHull.INPUT, - self.tr('Input point layer'), [dataobjects.TYPE_VECTOR_POINT])) - self.addParameter(ParameterNumber(self.ALPHA, - self.tr('Threshold (0-1, where 1 is equivalent with Convex Hull)'), - 0, 1, 0.3)) - self.addParameter(ParameterBoolean(self.HOLES, - self.tr('Allow holes'), True)) - self.addParameter(ParameterBoolean(self.NO_MULTIGEOMETRY, - self.tr('Split multipart geometry into singleparts geometries'), False)) - self.addOutput( - OutputVector(ConcaveHull.OUTPUT, self.tr('Concave hull'), datatype=[dataobjects.TYPE_VECTOR_POLYGON])) + + self.addParameter(QgsProcessingParameterFeatureSource(self.INPUT, self.tr('Input point layer'), [QgsProcessingParameterDefinition.TypeVectorPoint])) + self.addParameter(QgsProcessingParameterNumber(self.ALPHA, + self.tr('Threshold (0-1, where 1 is equivalent with Convex Hull)'), + minValue=0, maxValue=1, defaultValue=0.3, type=QgsProcessingParameterNumber.Double)) + + self.addParameter(QgsProcessingParameterBoolean(self.HOLES, + self.tr('Allow holes'), defaultValue=True)) + self.addParameter(QgsProcessingParameterBoolean(self.NO_MULTIGEOMETRY, + self.tr('Split multipart geometry into singleparts geometries'), defaultValue=False)) + + self.addParameter(QgsProcessingParameterFeatureSink(self.OUTPUT, self.tr('Concave hull'), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) + self.addOutput(QgsProcessingOutputVectorLayer(self.OUTPUT, self.tr("Concave hull"), type=QgsProcessingParameterDefinition.TypeVectorPolygon)) def name(self): return 'concavehull' @@ -76,21 +80,21 @@ def displayName(self): return self.tr('Concave hull') def processAlgorithm(self, parameters, context, feedback): - layer = QgsProcessingUtils.mapLayerFromString(self.getParameterValue(ConcaveHull.INPUT), context) - alpha = self.getParameterValue(self.ALPHA) - holes = self.getParameterValue(self.HOLES) - no_multigeom = self.getParameterValue(self.NO_MULTIGEOMETRY) + layer = self.parameterAsSource(parameters, ConcaveHull.INPUT, context) + alpha = self.parameterAsDouble(parameters, self.ALPHA, context) + holes = self.parameterAsBool(parameters, self.HOLES, context) + no_multigeom = self.parameterAsBool(parameters, self.NO_MULTIGEOMETRY, context) # Delaunay triangulation from input point layer feedback.setProgressText(self.tr('Creating Delaunay triangles...')) - delone_triangles = processing.run("qgis:delaunaytriangulation", layer, None, context=context)['OUTPUT'] + delone_triangles = processing.run("qgis:delaunaytriangulation", {'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] delaunay_layer = QgsProcessingUtils.mapLayerFromString(delone_triangles, context) # Get max edge length from Delaunay triangles feedback.setProgressText(self.tr('Computing edges max length...')) - features = QgsProcessingUtils.getFeatures(delaunay_layer, context) - count = QgsProcessingUtils.featureCount(delaunay_layer, context) + features = delaunay_layer.getFeatures() + count = delaunay_layer.featureCount() if count == 0: raise GeoAlgorithmExecutionException(self.tr('No Delaunay triangles created.')) @@ -98,6 +102,9 @@ def processAlgorithm(self, parameters, context, feedback): lengths = [] edges = {} for feat in features: + if feedback.isCanceled(): + break + line = feat.geometry().asPolygon()[0] for i in range(len(line) - 1): lengths.append(sqrt(line[i].sqrDist(line[i + 1]))) @@ -111,6 +118,9 @@ def processAlgorithm(self, parameters, context, feedback): i = 0 ids = [] for id, max_len in list(edges.items()): + if feedback.isCanceled(): + break + if max_len > alpha * max_length: ids.append(id) feedback.setProgress(50 + i * counter) @@ -124,21 +134,25 @@ def processAlgorithm(self, parameters, context, feedback): # Dissolve all Delaunay triangles feedback.setProgressText(self.tr('Dissolving Delaunay triangles...')) - dissolved = processing.run("qgis:dissolve", delaunay_layer.id(), - True, None, None, context=context)['OUTPUT'] + dissolved = processing.run("native:dissolve", {'INPUT': delaunay_layer.id(), 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] dissolved_layer = QgsProcessingUtils.mapLayerFromString(dissolved, context) # Save result feedback.setProgressText(self.tr('Saving data...')) feat = QgsFeature() - QgsProcessingUtils.getFeatures(dissolved_layer, context).nextFeature(feat) - writer = self.getOutputFromName(self.OUTPUT).getVectorWriter(layer.fields(), QgsWkbTypes.Polygon, - layer.crs(), context) + dissolved_layer.getFeatures().nextFeature(feat) + + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, + layer.fields(), QgsWkbTypes.Polygon, layer.sourceCrs()) + geom = feat.geometry() if no_multigeom and geom.isMultipart(): # Only singlepart geometries are allowed geom_list = geom.asMultiPolygon() for single_geom_list in geom_list: + if feedback.isCanceled(): + break + single_feature = QgsFeature() single_geom = QgsGeometry.fromPolygon(single_geom_list) if not holes: @@ -147,7 +161,7 @@ def processAlgorithm(self, parameters, context, feedback): while deleted: deleted = single_geom.deleteRing(1) single_feature.setGeometry(single_geom) - writer.addFeature(single_feature, QgsFeatureSink.FastInsert) + sink.addFeature(single_feature, QgsFeatureSink.FastInsert) else: # Multipart geometries are allowed if not holes: @@ -155,5 +169,6 @@ def processAlgorithm(self, parameters, context, feedback): deleted = True while deleted: deleted = geom.deleteRing(1) - writer.addFeature(feat, QgsFeatureSink.FastInsert) - del writer + sink.addFeature(feat, QgsFeatureSink.FastInsert) + + return {self.OUTPUT: dest_id} diff --git a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py index 95ac4978f44b..ae21c05f7114 100755 --- a/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py +++ b/python/plugins/processing/algs/qgis/QGISAlgorithmProvider.py @@ -47,6 +47,7 @@ from .Boundary import Boundary from .BoundingBox import BoundingBox from .CheckValidity import CheckValidity +from .ConcaveHull import ConcaveHull from .CreateAttributeIndex import CreateAttributeIndex from .Delaunay import Delaunay from .DeleteColumn import DeleteColumn @@ -110,7 +111,6 @@ # from .HubDistanceLines import HubDistanceLines # from .HubLines import HubLines # from .GeometryConvert import GeometryConvert -# from .ConcaveHull import ConcaveHull # from .RasterLayerStatistics import RasterLayerStatistics # from .StatisticsByCategories import StatisticsByCategories # from .EquivalentNumField import EquivalentNumField @@ -206,7 +206,7 @@ def getAlgs(self): # JoinAttributes(), # Explode(), FieldsPyculator(), # EquivalentNumField(), - # StatisticsByCategories(), ConcaveHull(), + # StatisticsByCategories(), # RasterLayerStatistics(), PointsDisplacement(), # PointsFromPolygons(), # PointsFromLines(), RandomPointsExtent(), @@ -246,6 +246,7 @@ def getAlgs(self): Boundary(), BoundingBox(), CheckValidity(), + ConcaveHull(), CreateAttributeIndex(), Delaunay(), DeleteColumn(), From 70cc19687d4f681d218305fe3eb88dc6d718f5a7 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 19:38:40 +1000 Subject: [PATCH 42/49] Add a method to take result layers (and ownership) from processing context --- python/core/processing/qgsprocessingcontext.sip | 9 +++++++++ src/core/processing/qgsprocessingcontext.h | 11 +++++++++++ tests/src/core/testqgsprocessing.cpp | 13 +++++++++++++ 3 files changed, 33 insertions(+) diff --git a/python/core/processing/qgsprocessingcontext.sip b/python/core/processing/qgsprocessingcontext.sip index dca081ecdd66..53f97f35cc91 100644 --- a/python/core/processing/qgsprocessingcontext.sip +++ b/python/core/processing/qgsprocessingcontext.sip @@ -245,6 +245,15 @@ Destination project thread() affinity, and that thread is the current thread. %End + QgsMapLayer *takeResultLayer( const QString &id ) /TransferBack/; +%Docstring + Takes the result map layer with matching ``id`` from the context and + transfers ownership of it back to the caller. This method can be used + to remove temporary layers which are not required for further processing + from a context. + :rtype: QgsMapLayer +%End + private: QgsProcessingContext( const QgsProcessingContext &other ); }; diff --git a/src/core/processing/qgsprocessingcontext.h b/src/core/processing/qgsprocessingcontext.h index 7f5a26f6fc7a..3316300f2ec5 100644 --- a/src/core/processing/qgsprocessingcontext.h +++ b/src/core/processing/qgsprocessingcontext.h @@ -356,6 +356,17 @@ class CORE_EXPORT QgsProcessingContext tempLayerStore.transferLayersFromStore( context.temporaryLayerStore() ); } + /** + * Takes the result map layer with matching \a id from the context and + * transfers ownership of it back to the caller. This method can be used + * to remove temporary layers which are not required for further processing + * from a context. + */ + QgsMapLayer *takeResultLayer( const QString &id ) SIP_TRANSFERBACK + { + return tempLayerStore.takeMapLayer( tempLayerStore.mapLayer( id ) ); + } + private: QgsProcessingContext::Flags mFlags = 0; diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index db6b1a0a893a..863ae8cad421 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -639,6 +639,19 @@ void TestQgsProcessing::context() QCOMPARE( context2.layersToLoadOnCompletion().count(), 2 ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 0 ), v1->id() ); QCOMPARE( context2.layersToLoadOnCompletion().keys().at( 1 ), v2->id() ); + + // take result layer + QgsMapLayer *result = context2.takeResultLayer( v1->id() ); + QCOMPARE( result, v1 ); + QString id = v1->id(); + delete v1; + QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) ); + QVERIFY( !context2.takeResultLayer( v1->id() ) ); + result = context2.takeResultLayer( v2->id() ); + QCOMPARE( result, v2 ); + id = v2->id(); + delete v2; + QVERIFY( !context2.temporaryLayerStore()->mapLayer( id ) ); } void TestQgsProcessing::mapLayers() From a2af3a9345dcd872a897ef632c277394e9f95f95 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 19:46:48 +1000 Subject: [PATCH 43/49] Make concave hull alg more efficient - remove temporary layers from context, delete them as soon as they are finished with - directly remove features via data provider, instead of selecting and using edit buffer - use native geometry methods for splitting to single features and removing rings --- .../processing/algs/qgis/ConcaveHull.py | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ConcaveHull.py b/python/plugins/processing/algs/qgis/ConcaveHull.py index 129e1a0058c3..38800d640d16 100644 --- a/python/plugins/processing/algs/qgis/ConcaveHull.py +++ b/python/plugins/processing/algs/qgis/ConcaveHull.py @@ -88,7 +88,7 @@ def processAlgorithm(self, parameters, context, feedback): # Delaunay triangulation from input point layer feedback.setProgressText(self.tr('Creating Delaunay triangles...')) delone_triangles = processing.run("qgis:delaunaytriangulation", {'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] - delaunay_layer = QgsProcessingUtils.mapLayerFromString(delone_triangles, context) + delaunay_layer = context.takeResultLayer(delone_triangles) # Get max edge length from Delaunay triangles feedback.setProgressText(self.tr('Computing edges max length...')) @@ -127,48 +127,45 @@ def processAlgorithm(self, parameters, context, feedback): i += 1 # Remove features - delaunay_layer.selectByIds(ids) - delaunay_layer.startEditing() - delaunay_layer.deleteSelectedFeatures() - delaunay_layer.commitChanges() + delaunay_layer.dataProvider().deleteFeatures(ids) # Dissolve all Delaunay triangles feedback.setProgressText(self.tr('Dissolving Delaunay triangles...')) - dissolved = processing.run("native:dissolve", {'INPUT': delaunay_layer.id(), 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] - dissolved_layer = QgsProcessingUtils.mapLayerFromString(dissolved, context) + dissolved = processing.run("native:dissolve", {'INPUT': delaunay_layer, 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] + dissolved_layer = context.takeResultLayer(dissolved) # Save result feedback.setProgressText(self.tr('Saving data...')) feat = QgsFeature() dissolved_layer.getFeatures().nextFeature(feat) + # Not needed anymore, free up some resources + del delaunay_layer + del dissolved_layer + (sink, dest_id) = self.parameterAsSink(parameters, self.OUTPUT, context, layer.fields(), QgsWkbTypes.Polygon, layer.sourceCrs()) geom = feat.geometry() if no_multigeom and geom.isMultipart(): # Only singlepart geometries are allowed - geom_list = geom.asMultiPolygon() - for single_geom_list in geom_list: + geom_list = geom.asGeometryCollection() + for single_geom in geom_list: if feedback.isCanceled(): break single_feature = QgsFeature() - single_geom = QgsGeometry.fromPolygon(single_geom_list) if not holes: # Delete holes - deleted = True - while deleted: - deleted = single_geom.deleteRing(1) + single_geom = single_geom.removeInteriorRings() single_feature.setGeometry(single_geom) sink.addFeature(single_feature, QgsFeatureSink.FastInsert) else: # Multipart geometries are allowed if not holes: # Delete holes - deleted = True - while deleted: - deleted = geom.deleteRing(1) + geom = geom.removeInteriorRings() + feat.setGeometry(geom) sink.addFeature(feat, QgsFeatureSink.FastInsert) return {self.OUTPUT: dest_id} From 55ce31b37170a577438fe70f6158d95fce0d2fe4 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 20:04:36 +1000 Subject: [PATCH 44/49] When calling procesing.run(), map layer results are automatically converted to QgsMapLayer objects with the ownership transferred to the Python caller This should make it super-easy for PyQGIS scripts to run processing algorithms and immediately utilise the results, even if they are memory layers. They call processing.run(), and get a dict of results back which includes those layers ready for adding to the current project or doing some other processing or operations with, and if they don't transfer to ownership off then these layers will be correctly garbaged collected by Python. --- python/plugins/processing/algs/qgis/ConcaveHull.py | 6 ++---- python/plugins/processing/core/Processing.py | 14 +++++++++++++- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/python/plugins/processing/algs/qgis/ConcaveHull.py b/python/plugins/processing/algs/qgis/ConcaveHull.py index 38800d640d16..b91ed220250b 100644 --- a/python/plugins/processing/algs/qgis/ConcaveHull.py +++ b/python/plugins/processing/algs/qgis/ConcaveHull.py @@ -87,8 +87,7 @@ def processAlgorithm(self, parameters, context, feedback): # Delaunay triangulation from input point layer feedback.setProgressText(self.tr('Creating Delaunay triangles...')) - delone_triangles = processing.run("qgis:delaunaytriangulation", {'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] - delaunay_layer = context.takeResultLayer(delone_triangles) + delaunay_layer = processing.run("qgis:delaunaytriangulation", {'INPUT': parameters[ConcaveHull.INPUT], 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] # Get max edge length from Delaunay triangles feedback.setProgressText(self.tr('Computing edges max length...')) @@ -131,8 +130,7 @@ def processAlgorithm(self, parameters, context, feedback): # Dissolve all Delaunay triangles feedback.setProgressText(self.tr('Dissolving Delaunay triangles...')) - dissolved = processing.run("native:dissolve", {'INPUT': delaunay_layer, 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] - dissolved_layer = context.takeResultLayer(dissolved) + dissolved_layer = processing.run("native:dissolve", {'INPUT': delaunay_layer, 'OUTPUT': 'memory:'}, feedback=feedback, context=context)['OUTPUT'] # Save result feedback.setProgressText(self.tr('Saving data...')) diff --git a/python/plugins/processing/core/Processing.py b/python/plugins/processing/core/Processing.py index 155f975ccb05..a38dadf2d2a9 100755 --- a/python/plugins/processing/core/Processing.py +++ b/python/plugins/processing/core/Processing.py @@ -38,9 +38,12 @@ from qgis.utils import iface from qgis.core import (QgsMessageLog, QgsApplication, + QgsMapLayer, QgsProcessingProvider, QgsProcessingAlgorithm, - QgsProcessingParameterDefinition) + QgsProcessingParameterDefinition, + QgsProcessingOutputVectorLayer, + QgsProcessingOutputRasterLayer) import processing from processing.script.ScriptUtils import ScriptUtils @@ -173,6 +176,15 @@ def runAlgorithm(algOrName, parameters, onFinish=None, feedback=None, context=No if onFinish is not None: onFinish(alg, context, feedback) + else: + # auto convert layer references in results to map layers + for out in alg.outputDefinitions(): + if isinstance(out, (QgsProcessingOutputVectorLayer, QgsProcessingOutputRasterLayer)): + result = results[out.name()] + if not isinstance(result, QgsMapLayer): + layer = context.takeResultLayer(result) # transfer layer ownership out of context + if layer: + results[out.name()] = layer # replace layer string ref with actual layer (+ownership) else: msg = Processing.tr("There were errors executing the algorithm.") feedback.reportError(msg) From ac9a39f09afb865f5482a6642c12917c5360b174 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 20:22:58 +1000 Subject: [PATCH 45/49] Fix processing.runAndLoadResults --- .../plugins/processing/gui/HistoryDialog.py | 3 +- python/plugins/processing/tools/general.py | 30 +++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/python/plugins/processing/gui/HistoryDialog.py b/python/plugins/processing/gui/HistoryDialog.py index d196dabd7d92..a3c5c5b4cfef 100644 --- a/python/plugins/processing/gui/HistoryDialog.py +++ b/python/plugins/processing/gui/HistoryDialog.py @@ -112,7 +112,8 @@ def executeAlgorithm(self): if isinstance(item, TreeLogEntryItem): if item.isAlg: script = 'import processing\n' - script += item.entry.text.replace('run(', 'runAndLoadResults(') + script += 'from qgis.core import QgsProcessingOutputLayerDefinition, QgsProcessingFeatureSourceDefinition\n' + script += item.entry.text.replace('processing.run(', 'processing.runAndLoadResults(') exec(script) def changeText(self): diff --git a/python/plugins/processing/tools/general.py b/python/plugins/processing/tools/general.py index aa18f23ac565..1ab0e29fad77 100644 --- a/python/plugins/processing/tools/general.py +++ b/python/plugins/processing/tools/general.py @@ -35,7 +35,13 @@ except ImportError: import configparser as configparser -from qgis.core import (QgsApplication) +from qgis.core import (QgsApplication, + QgsProcessingAlgorithm, + QgsProcessingParameterFeatureSink, + QgsProcessingParameterVectorOutput, + QgsProcessingParameterRasterOutput, + QgsProcessingOutputLayerDefinition, + QgsProject) from processing.core.Processing import Processing from processing.core.parameters import ParameterSelection from processing.gui.Postprocessing import handleAlgorithmResults @@ -76,11 +82,29 @@ def run(algOrName, parameters, onFinish=None, feedback=None, context=None): return Processing.runAlgorithm(algOrName, parameters, onFinish, feedback, context) -def runAndLoadResults(name, *args, **kwargs): +def runAndLoadResults(algOrName, parameters, feedback=None, context=None): """Executes given algorithm and load its results into QGIS project when possible. """ - return Processing.runAlgorithm(name, handleAlgorithmResults, *args, **kwargs) + if isinstance(algOrName, QgsProcessingAlgorithm): + alg = algOrName + else: + alg = QgsApplication.processingRegistry().algorithmById(algOrName) + + # output destination parameters to point to current project + for param in alg.parameterDefinitions(): + if not param.name() in parameters: + continue + + if isinstance(param, (QgsProcessingParameterFeatureSink, QgsProcessingParameterVectorOutput, QgsProcessingParameterRasterOutput)): + p = parameters[param.name()] + if not isinstance(p, QgsProcessingOutputLayerDefinition): + parameters[param.name()] = QgsProcessingOutputLayerDefinition(p, QgsProject.instance()) + else: + p.destinationProject = QgsProject.instance() + parameters[param.name()] = p + + return Processing.runAlgorithm(alg, parameters=parameters, onFinish=handleAlgorithmResults, feedback=feedback, context=context) def version(): From 1ab9c992b505a397e4789f8efd29579f1468b3a5 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 21:11:11 +1000 Subject: [PATCH 46/49] Fix possible error when messagebar items are popped and events are queued --- src/gui/qgsmessagebar.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gui/qgsmessagebar.cpp b/src/gui/qgsmessagebar.cpp index 0b9cf7ad0f49..28da7f5b8f62 100644 --- a/src/gui/qgsmessagebar.cpp +++ b/src/gui/qgsmessagebar.cpp @@ -131,7 +131,7 @@ void QgsMessageBar::popItem( QgsMessageBarItem *item ) mLayout->removeWidget( widget ); mCurrentItem->hide(); disconnect( mCurrentItem, &QgsMessageBarItem::styleChanged, this, &QWidget::setStyleSheet ); - delete mCurrentItem; + mCurrentItem->deleteLater(); mCurrentItem = nullptr; } @@ -168,7 +168,7 @@ bool QgsMessageBar::popWidget( QgsMessageBarItem *item ) if ( existingItem == item ) { mItems.removeOne( existingItem ); - delete existingItem; + existingItem->deleteLater(); return true; } } From da02c9a7ba1073332326af4c462e2347621f17f1 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Sat, 1 Jul 2017 22:41:37 +1000 Subject: [PATCH 47/49] Fix deadlock when running algs in task manager and python exception occurs --- python/core/core.sip | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/python/core/core.sip b/python/core/core.sip index 3f661544a1ab..71e7733f1bda 100644 --- a/python/core/core.sip +++ b/python/core/core.sip @@ -104,5 +104,7 @@ done: %Include core_auto.sip %VirtualErrorHandler processing_exception_handler - throw QgsProcessingException( getTraceback() ); + QString trace = getTraceback(); + SIP_RELEASE_GIL( sipGILState ); + throw QgsProcessingException( trace ); %End From 2c91df4c127770b034a6810c9fe2ffeb176f2fe0 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 10:14:51 +1000 Subject: [PATCH 48/49] Expand on docs --- .../processing/qgsprocessingalgorithm.sip | 47 +++++++++++++++++-- src/core/processing/qgsprocessingalgorithm.h | 47 +++++++++++++++++-- 2 files changed, 88 insertions(+), 6 deletions(-) diff --git a/python/core/processing/qgsprocessingalgorithm.sip b/python/core/processing/qgsprocessingalgorithm.sip index a0fc43b574e5..72c4cd29674a 100644 --- a/python/core/processing/qgsprocessingalgorithm.sip +++ b/python/core/processing/qgsprocessingalgorithm.sip @@ -364,10 +364,25 @@ class QgsProcessingAlgorithm The ``context`` argument specifies the context in which the algorithm is being run. + prepareAlgorithm should be used to handle any thread-sensitive preparation which is required + by the algorithm. It will always be called from the same thread that ``context`` has thread + affinity with. While this will generally be the main thread, it is not guaranteed. For instance, + algorithms which are run as a step in a larger model or as a subcomponent of a script-based algorithm + will call prepareAlgorithm from the same thread as that model/script it being executed in. + + Note that the processAlgorithm step uses a temporary context with affinity for the thread in + which the algorithm is executed, making it safe for processAlgorithm implementations to load + sources and sinks without issue. Implementing prepareAlgorithm is only required if special + thread safe handling is required by the algorithm. + Algorithm preparation progress should be reported using the supplied ``feedback`` object. Additionally, well-behaved algorithms should periodically check ``feedback`` to determine whether the algorithm should be canceled and exited early. + If the preparation was successful algorithms must return true. If a false value is returned + this indicates that the preparation could not be completed, and the algorithm execution + will be canceled. + :return: true if preparation was successful. .. seealso:: processAlgorithm() .. seealso:: postProcessAlgorithm() @@ -379,14 +394,29 @@ class QgsProcessingAlgorithm Runs the algorithm using the specified ``parameters``. Algorithms should implement their custom processing logic here. - The ``context`` argument specifies the context in which the algorithm is being run. + The ``context`` argument gives a temporary context with thread affinity matching the thread + in which the algorithm is being run. This is a cut-back copy of the context passed to + the prepareAlgorithm() and postProcessAlgorithm() steps, but it is generally safe + for most algorithms to utilize this context for loading layers and creating sinks. + Any loaded layers or sinks created within this temporary context will be transferred + back to the main execution context upon successful completion of the processAlgorithm() + step. Algorithm progress should be reported using the supplied ``feedback`` object. Additionally, well-behaved algorithms should periodically check ``feedback`` to determine whether the algorithm should be canceled and exited early. + This method will not be called if the prepareAlgorithm() step failed (returned false). + + c++ implementations of processAlgorithm can throw the QgsProcessingException exception + to indicate that a fatal error occurred within the execution. Python based subclasses + should raise GeoAlgorithmExecutionException for the same purpose. + :return: A map of algorithm outputs. These may be output layer references, or calculated - values such as statistical calculations. + values such as statistical calculations. Unless the algorithm subclass overrides + the postProcessAlgorithm() step this returned map will be used as the output for the + algorithm. + .. seealso:: prepareAlgorithm() .. seealso:: postProcessAlgorithm() :rtype: QVariantMap @@ -404,8 +434,19 @@ class QgsProcessingAlgorithm well-behaved algorithms should periodically check ``feedback`` to determine whether the post processing should be canceled and exited early. + postProcessAlgorithm should be used to handle any thread-sensitive cleanup which is required + by the algorithm. It will always be called from the same thread that ``context`` has thread + affinity with. While this will generally be the main thread, it is not guaranteed. For instance, + algorithms which are run as a step in a larger model or as a subcomponent of a script-based algorithm + will call postProcessAlgorithm from the same thread as that model/script it being executed in. + + postProcessAlgorithm will not be called if the prepareAlgorithm() step failed (returned false), + or if an exception was raised by the processAlgorithm() step. + :return: A map of algorithm outputs. These may be output layer references, or calculated - values such as statistical calculations. + values such as statistical calculations. Implementations which return a non-empty + map will override any results returned by processAlgorithm(). + .. seealso:: prepareAlgorithm() .. seealso:: processAlgorithm() :rtype: QVariantMap diff --git a/src/core/processing/qgsprocessingalgorithm.h b/src/core/processing/qgsprocessingalgorithm.h index 6494941201ec..488af884b5d0 100644 --- a/src/core/processing/qgsprocessingalgorithm.h +++ b/src/core/processing/qgsprocessingalgorithm.h @@ -346,10 +346,25 @@ class CORE_EXPORT QgsProcessingAlgorithm * * The \a context argument specifies the context in which the algorithm is being run. * + * prepareAlgorithm should be used to handle any thread-sensitive preparation which is required + * by the algorithm. It will always be called from the same thread that \a context has thread + * affinity with. While this will generally be the main thread, it is not guaranteed. For instance, + * algorithms which are run as a step in a larger model or as a subcomponent of a script-based algorithm + * will call prepareAlgorithm from the same thread as that model/script it being executed in. + * + * Note that the processAlgorithm step uses a temporary context with affinity for the thread in + * which the algorithm is executed, making it safe for processAlgorithm implementations to load + * sources and sinks without issue. Implementing prepareAlgorithm is only required if special + * thread safe handling is required by the algorithm. + * * Algorithm preparation progress should be reported using the supplied \a feedback object. Additionally, * well-behaved algorithms should periodically check \a feedback to determine whether the * algorithm should be canceled and exited early. * + * If the preparation was successful algorithms must return true. If a false value is returned + * this indicates that the preparation could not be completed, and the algorithm execution + * will be canceled. + * * \returns true if preparation was successful. * \see processAlgorithm() * \see postProcessAlgorithm() @@ -360,14 +375,29 @@ class CORE_EXPORT QgsProcessingAlgorithm * Runs the algorithm using the specified \a parameters. Algorithms should implement * their custom processing logic here. * - * The \a context argument specifies the context in which the algorithm is being run. + * The \a context argument gives a temporary context with thread affinity matching the thread + * in which the algorithm is being run. This is a cut-back copy of the context passed to + * the prepareAlgorithm() and postProcessAlgorithm() steps, but it is generally safe + * for most algorithms to utilize this context for loading layers and creating sinks. + * Any loaded layers or sinks created within this temporary context will be transferred + * back to the main execution context upon successful completion of the processAlgorithm() + * step. * * Algorithm progress should be reported using the supplied \a feedback object. Additionally, * well-behaved algorithms should periodically check \a feedback to determine whether the * algorithm should be canceled and exited early. * + * This method will not be called if the prepareAlgorithm() step failed (returned false). + * + * c++ implementations of processAlgorithm can throw the QgsProcessingException exception + * to indicate that a fatal error occurred within the execution. Python based subclasses + * should raise GeoAlgorithmExecutionException for the same purpose. + * * \returns A map of algorithm outputs. These may be output layer references, or calculated - * values such as statistical calculations. + * values such as statistical calculations. Unless the algorithm subclass overrides + * the postProcessAlgorithm() step this returned map will be used as the output for the + * algorithm. + * * \see prepareAlgorithm() * \see postProcessAlgorithm() */ @@ -384,8 +414,19 @@ class CORE_EXPORT QgsProcessingAlgorithm * well-behaved algorithms should periodically check \a feedback to determine whether the * post processing should be canceled and exited early. * + * postProcessAlgorithm should be used to handle any thread-sensitive cleanup which is required + * by the algorithm. It will always be called from the same thread that \a context has thread + * affinity with. While this will generally be the main thread, it is not guaranteed. For instance, + * algorithms which are run as a step in a larger model or as a subcomponent of a script-based algorithm + * will call postProcessAlgorithm from the same thread as that model/script it being executed in. + * + * postProcessAlgorithm will not be called if the prepareAlgorithm() step failed (returned false), + * or if an exception was raised by the processAlgorithm() step. + * * \returns A map of algorithm outputs. These may be output layer references, or calculated - * values such as statistical calculations. + * values such as statistical calculations. Implementations which return a non-empty + * map will override any results returned by processAlgorithm(). + * * \see prepareAlgorithm() * \see processAlgorithm() */ From 240dd19f975e808be9b52d7b69b94249328d5c32 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Fri, 7 Jul 2017 11:15:05 +1000 Subject: [PATCH 49/49] Update tests --- tests/src/core/testqgsprocessing.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/src/core/testqgsprocessing.cpp b/tests/src/core/testqgsprocessing.cpp index 863ae8cad421..31bab309f714 100644 --- a/tests/src/core/testqgsprocessing.cpp +++ b/tests/src/core/testqgsprocessing.cpp @@ -4769,14 +4769,14 @@ void TestQgsProcessing::modelExecution() QCOMPARE( params.count(), 7 ); QVariantMap results; - results.insert( "OUTPUT_LAYER", QStringLiteral( "dest.shp" ) ); + results.insert( "OUTPUT", QStringLiteral( "dest.shp" ) ); childResults.insert( "cx1", results ); // a child who uses an output from another alg as a parameter value QgsProcessingModelAlgorithm::ChildAlgorithm alg2c2; alg2c2.setChildId( "cx2" ); alg2c2.setAlgorithmId( "native:centroids" ); - alg2c2.addParameterSources( "INPUT", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx1", "OUTPUT_LAYER" ) ); + alg2c2.addParameterSources( "INPUT", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) ); model2.addChildAlgorithm( alg2c2 ); params = model2.parametersForChildAlgorithm( model2.childAlgorithm( "cx2" ), modelInputs, childResults ); QCOMPARE( params.value( "INPUT" ).toString(), QStringLiteral( "dest.shp" ) ); @@ -4787,7 +4787,7 @@ void TestQgsProcessing::modelExecution() QgsProcessingModelAlgorithm::ChildAlgorithm alg2c3; alg2c3.setChildId( "cx3" ); alg2c3.setAlgorithmId( "native:extractbyexpression" ); - alg2c3.addParameterSources( "INPUT", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx1", "OUTPUT_LAYER" ) ); + alg2c3.addParameterSources( "INPUT", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromChildOutput( "cx1", "OUTPUT" ) ); alg2c3.addParameterSources( "EXPRESSION", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromStaticValue( "true" ) ); alg2c3.addParameterSources( "OUTPUT", QgsProcessingModelAlgorithm::ChildParameterSources() << QgsProcessingModelAlgorithm::ChildParameterSource::fromModelParameter( "MY_OUT" ) ); alg2c3.setDependencies( QStringList() << "cx2" ); @@ -4817,7 +4817,7 @@ void TestQgsProcessing::modelExecution() "outputs['cx1']=processing.run('native:buffer', {'DISSOLVE':false,'DISTANCE':parameters['DIST'],'END_CAP_STYLE':1,'INPUT':parameters['SOURCE_LAYER'],'JOIN_STYLE':2,'SEGMENTS':16}, context=context, feedback=feedback)\n" "results['MODEL_OUT_LAYER']=outputs['cx1']['OUTPUT']\n" "outputs['cx2']=processing.run('native:centroids', {'INPUT':outputs['cx1']['OUTPUT']}, context=context, feedback=feedback)\n" - "outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT_LAYER'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n" + "outputs['cx3']=processing.run('native:extractbyexpression', {'EXPRESSION':true,'INPUT':outputs['cx1']['OUTPUT'],'OUTPUT':parameters['MY_OUT']}, context=context, feedback=feedback)\n" "results['MY_OUT']=outputs['cx3']['OUTPUT']\n" "return results" ).split( '\n' ); QCOMPARE( actualParts, expectedParts );