Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
[processing] Allow algorithms to set layer post-processors for
execution on generated layers after the algorithm (or parent
model) completes

This commit adds an interface for layer post-processing handlers
for execution following a processing algorithm operation.

Post-processing of a layer will ONLY occur if that layer is set
to be loaded into a QGIS project on algorithm completion.

Algorithms that wish to set post-processing steps for generated
layers should implement this interface in a separate class
(NOT the algorithm class itself!) and implement a method
to handle the layer post-processing.

This method always runs in the main thread and can be used to
setup renderers, editor widgets, metadata, etc for the given layer.

Fixes #17961
  • Loading branch information
nyalldawson committed Mar 15, 2018
1 parent 09e5a4f commit bd1d87e
Show file tree
Hide file tree
Showing 5 changed files with 389 additions and 43 deletions.
138 changes: 132 additions & 6 deletions python/core/processing/qgsprocessingcontext.sip.in
Expand Up @@ -10,6 +10,7 @@




class QgsProcessingContext
{
%Docstring
Expand Down Expand Up @@ -39,6 +40,8 @@ Constructor for QgsProcessingContext.
%End


~QgsProcessingContext();

void copyThreadSafeSettings( const QgsProcessingContext &other );
%Docstring
Copies all settings which are safe for use across different threads from
Expand Down Expand Up @@ -110,19 +113,52 @@ Returns a reference to the layer store used for storing temporary layers during
algorithm execution.
%End

struct LayerDetails
{
class LayerDetails
{
%Docstring
Details for layers to load into projects.
%End

%TypeHeaderCode
#include "qgsprocessingcontext.h"
%End
public:

LayerDetails( const QString &name, QgsProject *project, const QString &outputName = QString() );
LayerDetails( const QString &name, QgsProject *project, const QString &outputName = QString() );
%Docstring
Constructor for LayerDetails.
%End

QString name;
LayerDetails();
%Docstring
Default constructor
%End

QString name;

QString outputName;

QgsProcessingLayerPostProcessorInterface *postProcessor() const;
%Docstring
Layer post-processor. May be None if no post-processing is required.

.. versionadded:: 3.2

QString outputName;
.. seealso:: :py:func:`setPostProcessor`
%End

void setPostProcessor( QgsProcessingLayerPostProcessorInterface *processor /Transfer/ );
%Docstring
Sets the layer post-processor. May be None if no post-processing is required.

QgsProject *project;
Ownership of ``processor`` is transferred.

.. versionadded:: 3.2

.. seealso:: :py:func:`postProcessor`
%End

QgsProject *project;

};

Expand All @@ -133,6 +169,26 @@ Returns a map of layers (by ID or datasource) to LayerDetails, to load into the
.. seealso:: :py:func:`setLayersToLoadOnCompletion`

.. seealso:: :py:func:`addLayerToLoadOnCompletion`

.. seealso:: :py:func:`willLoadLayerOnCompletion`

.. seealso:: :py:func:`layerToLoadOnCompletionDetails`
%End

bool willLoadLayerOnCompletion( const QString &layer ) const;
%Docstring
Returns true if the given ``layer`` (by ID or datasource) will be loaded into the current project
upon completion of the algorithm or model.

.. versionadded:: 3.2

.. seealso:: :py:func:`layersToLoadOnCompletion`

.. seealso:: :py:func:`setLayersToLoadOnCompletion`

.. seealso:: :py:func:`addLayerToLoadOnCompletion`

.. seealso:: :py:func:`layerToLoadOnCompletionDetails`
%End

void setLayersToLoadOnCompletion( const QMap< QString, QgsProcessingContext::LayerDetails > &layers );
Expand All @@ -142,6 +198,10 @@ Sets the map of ``layers`` (by ID or datasource) to LayerDetails, to load into t
.. seealso:: :py:func:`addLayerToLoadOnCompletion`

.. seealso:: :py:func:`layersToLoadOnCompletion`

.. seealso:: :py:func:`willLoadLayerOnCompletion`

.. seealso:: :py:func:`layerToLoadOnCompletionDetails`
%End

void addLayerToLoadOnCompletion( const QString &layer, const QgsProcessingContext::LayerDetails &details );
Expand All @@ -151,6 +211,31 @@ The ``details`` parameter dictates the LayerDetails.

.. seealso:: :py:func:`setLayersToLoadOnCompletion`

.. seealso:: :py:func:`layersToLoadOnCompletion`

.. seealso:: :py:func:`willLoadLayerOnCompletion`

.. seealso:: :py:func:`layerToLoadOnCompletionDetails`
%End

QgsProcessingContext::LayerDetails &layerToLoadOnCompletionDetails( const QString &layer );
%Docstring
Returns a reference to the details for a given ``layer`` which is loaded on completion of the
algorithm or model.

.. warning::

First check willLoadLayerOnCompletion(), or calling this method will incorrectly
add ``layer`` as a layer to load on completion.

.. versionadded:: 3.2

.. seealso:: :py:func:`willLoadLayerOnCompletion`

.. seealso:: :py:func:`addLayerToLoadOnCompletion`

.. seealso:: :py:func:`setLayersToLoadOnCompletion`

.. seealso:: :py:func:`layersToLoadOnCompletion`
%End

Expand Down Expand Up @@ -305,6 +390,47 @@ QFlags<QgsProcessingContext::Flag> operator|(QgsProcessingContext::Flag f1, QFla



class QgsProcessingLayerPostProcessorInterface
{
%Docstring
An interface for layer post-processing handlers for execution following a processing algorithm operation.

Note that post-processing of a layer will ONLY occur if that layer is set to be loaded into a QGIS project
on algorithm completion. See :py:func:`QgsProcessingContext.layersToLoadOnCompletion()`

Algorithms that wish to set post-processing steps for generated layers should implement this interface
in a separate class (NOT the algorithm class itself!).

.. versionadded:: 3.2
%End

%TypeHeaderCode
#include "qgsprocessingcontext.h"
%End
public:

virtual ~QgsProcessingLayerPostProcessorInterface();

virtual void postProcessLayer( QgsMapLayer *layer, QgsProcessingContext &context, QgsProcessingFeedback *feedback ) = 0;
%Docstring
Post-processes the specified ``layer``, following successful execution of a processing algorithm. This method
always runs in the main thread and can be used to setup renderers, editor widgets, metadata, etc for
the given layer.

Post-processing classes can utilize settings from the algorithm's ``context`` and report logging messages
or errors via the given ``feedback`` object.

In the case of an algorithm run as part of a larger model, the post-processing occurs following the completed
execution of the entire model.

Note that post-processing of a layer will ONLY occur if that layer is set to be loaded into a QGIS project
on algorithm completion. See :py:func:`QgsProcessingContext.layersToLoadOnCompletion()`
%End

};






Expand Down
5 changes: 5 additions & 0 deletions python/plugins/processing/gui/Postprocessing.py
Expand Up @@ -77,7 +77,12 @@ def handleAlgorithmResults(alg, context, feedback=None, showResults=True):
style = ProcessingConfig.getSetting(ProcessingConfig.VECTOR_POLYGON_STYLE)
if style:
layer.loadNamedStyle(style)

details.project.addMapLayer(context.temporaryLayerStore().takeMapLayer(layer))

if details.postProcessor():
details.postProcessor().postProcessLayer(layer, context, feedback)

else:
wrongLayers.append(str(l))
except Exception:
Expand Down
50 changes: 43 additions & 7 deletions src/core/processing/qgsprocessingcontext.cpp
Expand Up @@ -18,6 +18,32 @@
#include "qgsprocessingcontext.h"
#include "qgsprocessingutils.h"

QgsProcessingContext::~QgsProcessingContext()
{
for ( auto it = mLayersToLoadOnCompletion.constBegin(); it != mLayersToLoadOnCompletion.constEnd(); ++it )
{
delete it.value().postProcessor();
}
}

void QgsProcessingContext::setLayersToLoadOnCompletion( const QMap<QString, QgsProcessingContext::LayerDetails> &layers )
{
for ( auto it = mLayersToLoadOnCompletion.constBegin(); it != mLayersToLoadOnCompletion.constEnd(); ++it )
{
if ( !layers.contains( it.key() ) || layers.value( it.key() ).postProcessor() != it.value().postProcessor() )
delete it.value().postProcessor();
}
mLayersToLoadOnCompletion = layers;
}

void QgsProcessingContext::addLayerToLoadOnCompletion( const QString &layer, const QgsProcessingContext::LayerDetails &details )
{
if ( mLayersToLoadOnCompletion.contains( layer ) && mLayersToLoadOnCompletion.value( layer ).postProcessor() != details.postProcessor() )
delete mLayersToLoadOnCompletion.value( layer ).postProcessor();

mLayersToLoadOnCompletion.insert( layer, details );
}

void QgsProcessingContext::setInvalidGeometryCheck( QgsFeatureRequest::InvalidGeometryCheck check )
{
mInvalidGeometryCheck = check;
Expand Down Expand Up @@ -52,13 +78,8 @@ void QgsProcessingContext::setInvalidGeometryCheck( QgsFeatureRequest::InvalidGe

void QgsProcessingContext::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 >() );
setLayersToLoadOnCompletion( context.mLayersToLoadOnCompletion );
context.mLayersToLoadOnCompletion.clear();
tempLayerStore.transferLayersFromStore( context.temporaryLayerStore() );
}

Expand All @@ -71,3 +92,18 @@ QgsMapLayer *QgsProcessingContext::takeResultLayer( const QString &id )
{
return tempLayerStore.takeMapLayer( tempLayerStore.mapLayer( id ) );
}



QgsProcessingLayerPostProcessorInterface *QgsProcessingContext::LayerDetails::postProcessor() const
{
return mPostProcessor;
}

void QgsProcessingContext::LayerDetails::setPostProcessor( QgsProcessingLayerPostProcessorInterface *processor )
{
if ( mPostProcessor && mPostProcessor != processor )
delete mPostProcessor;

mPostProcessor = processor;
}

0 comments on commit bd1d87e

Please sign in to comment.