QGIS API Documentation 3.27.0-Master (f261cc1f8b)
qgsprocessingbatchalgorithmdialogbase.cpp
Go to the documentation of this file.
1/***************************************************************************
2 qgsprocessingbatchalgorithmdialogbase.cpp
3 ------------------------------------
4 Date : March 2022
5 Copyright : (C) 2022 Nyall Dawson
6 Email : nyall dot dawson at gmail dot com
7 ***************************************************************************
8 * *
9 * This program is free software; you can redistribute it and/or modify *
10 * it under the terms of the GNU General Public License as published by *
11 * the Free Software Foundation; either version 2 of the License, or *
12 * (at your option) any later version. *
13 * *
14 ***************************************************************************/
15
17#include "qgsprocessingbatch.h"
20#include "qgsjsonutils.h"
22#include "qgsapplication.h"
23#include <nlohmann/json.hpp>
24
26QgsProcessingBatchAlgorithmDialogBase::QgsProcessingBatchAlgorithmDialogBase( QWidget *parent, Qt::WindowFlags flags )
27 : QgsProcessingAlgorithmDialogBase( parent, flags, QgsProcessingAlgorithmDialogBase::DialogMode::Batch )
28{
29 mButtonRunSingle = new QPushButton( tr( "Run as Single Process…" ) );
30 connect( mButtonRunSingle, &QPushButton::clicked, this, &QgsProcessingBatchAlgorithmDialogBase::runAsSingle );
31 buttonBox()->addButton( mButtonRunSingle, QDialogButtonBox::ResetRole ); // reset role to ensure left alignment
32
33 connect( QgsApplication::taskManager(), &QgsTaskManager::taskTriggered, this, &QgsProcessingBatchAlgorithmDialogBase::taskTriggered );
34
35 updateRunButtonVisibility();
36}
37
38QgsProcessingBatchAlgorithmDialogBase::~QgsProcessingBatchAlgorithmDialogBase() = default;
39
40void QgsProcessingBatchAlgorithmDialogBase::resetAdditionalGui()
41{
42 mButtonRunSingle->setEnabled( true );
43}
44
45void QgsProcessingBatchAlgorithmDialogBase::blockAdditionalControlsWhileRunning()
46{
47 mButtonRunSingle->setEnabled( false );
48}
49
50void QgsProcessingBatchAlgorithmDialogBase::execute( const QList<QVariantMap> &parameters )
51{
52 mQueuedParameters = parameters;
53 mCurrentStep = 0;
54 mTotalSteps = mQueuedParameters.size();
55 mResults.clear();
56 mErrors.clear();
57
58 mFeedback.reset( createFeedback() );
59
60 mBatchFeedback = std::make_unique< QgsProcessingBatchFeedback >( mTotalSteps, mFeedback.get() );
61
62 mProxyTask = new QgsProxyProgressTask( tr( "Batch Processing - %1" ).arg( algorithm()->displayName() ), true );
63 connect( mProxyTask, &QgsProxyProgressTask::canceled, mBatchFeedback.get(), &QgsFeedback::cancel );
64 connect( mFeedback.get(), &QgsFeedback::progressChanged, mProxyTask, &QgsProxyProgressTask::setProxyProgress );
65 QgsApplication::taskManager()->addTask( mProxyTask );
66
67 blockControlsWhileRunning();
68 setExecutedAnyResult( true );
69 cancelButton()->setEnabled( true );
70
71 // Make sure the Log tab is visible before executing the algorithm
72 showLog();
73 repaint();
74
75 mTotalTimer.restart();
76 if ( mTotalSteps > 0 )
77 executeNext();
78}
79
80bool QgsProcessingBatchAlgorithmDialogBase::isFinalized()
81{
82 return mQueuedParameters.empty();
83}
84
85void QgsProcessingBatchAlgorithmDialogBase::executeNext()
86{
87 if ( mQueuedParameters.empty() || mFeedback->isCanceled() )
88 {
89 allTasksComplete( false );
90 return;
91 }
92
93 mBatchFeedback->setCurrentStep( mCurrentStep++ );
94 setProgressText( QStringLiteral( "\n" ) + tr( "Processing algorithm %1/%2…" ).arg( mCurrentStep ).arg( mTotalSteps ) );
95 setInfo( tr( "<b>Algorithm %1 starting&hellip;</b>" ).arg( algorithm()->displayName() ), false, false );
96
97 pushInfo( tr( "Input parameters:" ) );
98
99 // important - we create a new context for each iteration
100 // this avoids holding onto resources and layers from earlier iterations,
101 // and allows batch processing of many more items then is possible
102 // if we hold on to these layers
103 mTaskContext.reset( createContext( mBatchFeedback.get() ) );
104
105 const QVariantMap paramsJson = algorithm()->asMap( mQueuedParameters.constFirst(), *mTaskContext ).value( QStringLiteral( "inputs" ) ).toMap();
106 pushCommandInfo( QString::fromStdString( QgsJsonUtils::jsonFromVariant( paramsJson ).dump() ) );
107 pushInfo( QString() );
108
109 mCurrentParameters = algorithm()->preprocessParameters( mQueuedParameters.constFirst() );
110 mQueuedParameters.pop_front();
111
112 mCurrentStepTimer.restart();
114 {
115 QgsProcessingAlgRunnerTask *task = new QgsProcessingAlgRunnerTask( algorithm(), mCurrentParameters, *mTaskContext, mBatchFeedback.get(), QgsTask::CanCancel | QgsTask::Hidden );
116 if ( task->algorithmCanceled() )
117 onTaskComplete( false, {} );
118 else
119 {
120 setCurrentTask( task );
121 }
122 }
123 else
124 {
125 // have to execute in main thread, no tasks allowed
126 bool ok = false;
127 const QVariantMap results = algorithm()->run( mCurrentParameters, *mTaskContext, mBatchFeedback.get(), &ok );
128 onTaskComplete( ok, results );
129 }
130}
131
132void QgsProcessingBatchAlgorithmDialogBase::algExecuted( bool successful, const QVariantMap &results )
133{
134 // parent class cleanup first!
135 QgsProcessingAlgorithmDialogBase::algExecuted( successful, results );
136 onTaskComplete( successful, results );
137}
138
139void QgsProcessingBatchAlgorithmDialogBase::onTaskComplete( bool ok, const QVariantMap &results )
140{
141 if ( ok )
142 {
143 setInfo( tr( "Algorithm %1 correctly executed…" ).arg( algorithm()->displayName() ), false, false );
144 pushInfo( tr( "Execution completed in %1 seconds" ).arg( mCurrentStepTimer.elapsed() / 1000.0, 2 ) );
145 pushInfo( tr( "Results:" ) );
146
147 pushCommandInfo( QString::fromStdString( QgsJsonUtils::jsonFromVariant( results ).dump() ) );
148 pushInfo( QString() );
149
150 mResults.append( QVariantMap(
151 {
152 { QStringLiteral( "parameters" ), mCurrentParameters },
153 { QStringLiteral( "results" ), results }
154 } ) );
155
156 handleAlgorithmResults( algorithm(), *mTaskContext, mBatchFeedback.get(), mCurrentParameters );
157 executeNext();
158 }
159 else if ( mBatchFeedback->isCanceled() )
160 {
161 setInfo( tr( "Algorithm %1 canceled…" ).arg( algorithm()->displayName() ), false, false );
162 pushInfo( tr( "Execution canceled after %1 seconds" ).arg( mCurrentStepTimer.elapsed() / 1000.0, 2 ) );
163 allTasksComplete( true );
164 }
165 else
166 {
167 const QStringList taskErrors = mBatchFeedback->popErrors();
168 setInfo( tr( "Algorithm %1 failed…" ).arg( algorithm()->displayName() ), false, false );
169 reportError( tr( "Execution failed after %1 seconds" ).arg( mCurrentStepTimer.elapsed() / 1000.0, 2 ), false );
170
171 mErrors.append( QVariantMap(
172 {
173 { QStringLiteral( "parameters" ), mCurrentParameters },
174 { QStringLiteral( "errors" ), taskErrors }
175 } ) );
176 executeNext();
177 }
178}
179
180void QgsProcessingBatchAlgorithmDialogBase::taskTriggered( QgsTask *task )
181{
182 if ( task == mProxyTask )
183 {
184 show();
185 raise();
186 setWindowState( ( windowState() & ~Qt::WindowMinimized ) | Qt::WindowActive );
187 activateWindow();
188 showLog();
189 }
190}
191
192void QgsProcessingBatchAlgorithmDialogBase::allTasksComplete( bool canceled )
193{
194 mBatchFeedback.reset();
195 mFeedback.reset();
196 mTaskContext.reset();
197 mQueuedParameters.clear();
198 if ( mProxyTask )
199 {
200 mProxyTask->finalize( true );
201 mProxyTask = nullptr;
202 }
203
204 if ( !canceled )
205 {
206 pushInfo( tr( "Batch execution completed in %1 seconds" ).arg( mTotalTimer.elapsed() / 1000.0, 2 ) );
207 if ( !mErrors.empty() )
208 {
209 reportError( tr( "%1 executions failed. See log for further details." ).arg( mErrors.size() ), true );
210 }
211
212 for ( int i = 0; i < mResults.size(); ++i )
213 {
214 loadHtmlResults( mResults.at( i ).value( QStringLiteral( "results" ) ).toMap(), i );
215 }
216
217 createSummaryTable( mResults, mErrors );
218 }
219
220 resetGui();
221 cancelButton()->setEnabled( false );
222}
223
224
static QgsTaskManager * taskManager()
Returns the application's task manager, used for managing application wide background task handling.
void progressChanged(double progress)
Emitted when the feedback object reports a progress change.
void cancel()
Tells the internal routines that the current operation should be canceled. This should be run by the ...
Definition: qgsfeedback.h:108
static json jsonFromVariant(const QVariant &v)
Converts a QVariant v to a json object.
QgsTask task which runs a QgsProcessingAlgorithm in a background task.
bool algorithmCanceled()
Returns true if the algorithm was canceled.
virtual QVariantMap preprocessParameters(const QVariantMap &parameters)
Pre-processes a set of parameters, allowing the algorithm to clean their values.
@ FlagNoThreading
Algorithm is not thread safe and cannot be run in a background thread, e.g. for algorithms which mani...
virtual QVariantMap asMap(const QVariantMap &parameters, QgsProcessingContext &context) const
Returns a JSON serializable variant map containing the specified parameters and context settings.
QVariantMap run(const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, bool *ok=nullptr, const QVariantMap &configuration=QVariantMap(), bool catchExceptions=true) const SIP_THROW(QgsProcessingException)
Executes the algorithm using the specified parameters.
A QgsTask shell which proxies progress reports.
void setProxyProgress(double progress)
Sets the progress (from 0 to 100) for the proxied operation.
void canceled()
Emitted when the task is canceled.
long addTask(QgsTask *task, int priority=0)
Adds a task to the manager.
void taskTriggered(QgsTask *task)
Emitted when a task is triggered.
Abstract base class for long running background tasks.
@ Hidden
Hide task from GUI (since QGIS 3.26)
@ CanCancel
Task can be canceled.
As part of the API refactoring and improvements which landed in the Processing API was substantially reworked from the x version This was done in order to allow much of the underlying Processing framework to be ported into allowing algorithms to be written in pure substantial changes are required in order to port existing x Processing algorithms for QGIS x The most significant changes are outlined not GeoAlgorithm For algorithms which operate on features one by consider subclassing the QgsProcessingFeatureBasedAlgorithm class This class allows much of the boilerplate code for looping over features from a vector layer to be bypassed and instead requires implementation of a processFeature method Ensure that your algorithm(or algorithm 's parent class) implements the new pure virtual createInstance(self) call