Skip to content

Commit

Permalink
[processing] c++ framework for parameters and running algorithms (WIP)
Browse files Browse the repository at this point in the history
This commit adds the virtual method for running processing algs
to the base c++ class, and adds the initial framework
for c++ algorithm parameters.

When running an algorithm, a QVariantMap is passed
as the algorithm parameters. The high level API provided
by QgsProcessingParameters should be used to retrieve
strings/layers/doubles/etc from this QVariantMap.

This allows advanced use cases, such as passing QgsProperty
with the QVariantMap for "dynamic" parameters, where the
value should be evaluated for every feature processed.

E.g. if the buffer algorithm uses a dynamic property for distance,
then the distance could be bound to either a field value or
to a custom expression. This gets evaluated before buffering
each feature to allow for advanced variable buffering.

Support for dynamic parameters will be "opt in", and non default.
So algorithms will need to specifically add support for
dynamic properties as required.
  • Loading branch information
nyalldawson committed May 10, 2017
1 parent 8423aaf commit 3706d88
Show file tree
Hide file tree
Showing 12 changed files with 572 additions and 3 deletions.
1 change: 1 addition & 0 deletions python/core/core.sip
Expand Up @@ -282,6 +282,7 @@
%Include processing/qgsprocessingalgorithm.sip
%Include processing/qgsprocessingcontext.sip
%Include processing/qgsprocessingfeedback.sip
%Include processing/qgsprocessingparameters.sip
%Include processing/qgsprocessingprovider.sip
%Include processing/qgsprocessingregistry.sip
%Include processing/qgsprocessingutils.sip
Expand Down
17 changes: 17 additions & 0 deletions python/core/processing/qgsprocessingalgorithm.sip
Expand Up @@ -112,6 +112,23 @@ class QgsProcessingAlgorithm

void setProvider( QgsProcessingProvider *provider );

virtual bool run( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs /Out/ ) const = 0;
%Docstring
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 ``outputs`` map will be amended to include any outputs generated by this algorithm.

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.

:return: true if algorithm run was successful, or false if run was unsuccessful.
:rtype: bool
%End

private:
QgsProcessingAlgorithm( const QgsProcessingAlgorithm &other );
};
Expand Down
2 changes: 1 addition & 1 deletion python/core/processing/qgsprocessingcontext.sip
Expand Up @@ -64,7 +64,7 @@ class QgsProcessingContext
.. seealso:: project()
%End

QgsExpressionContext expressionContext() const;
QgsExpressionContext &expressionContext();
%Docstring
Returns the expression context.
:rtype: QgsExpressionContext
Expand Down
89 changes: 89 additions & 0 deletions python/core/processing/qgsprocessingparameters.sip
@@ -0,0 +1,89 @@
/************************************************************************
* This file has been generated automatically from *
* *
* src/core/processing/qgsprocessingparameters.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/






class QgsProcessingParameters
{
%Docstring

A collection of utilities for working with parameters when running a processing algorithm.

Parameters are stored in a QVariantMap and referenced by a unique string key.
The QVariants in parameters are not usually accessed
directly, and instead the high level API provided through QgsProcessingParameters
parameterAsString(), parameterAsDouble() are used instead.

Parameters are evaluated using a provided QgsProcessingContext, allowing
the evaluation to understand available map layers and expression contexts
(for expression based parameters).

.. versionadded:: 3.0
%End

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

static bool isDynamic( const QVariantMap &parameters, const QString &name );
%Docstring
Returns true if the parameter with matching ``name`` is a dynamic parameter, and must
be evaluated once for every input feature processed.
:rtype: bool
%End

static QString parameterAsString( const QVariantMap &parameters, const QString &name, const QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``name`` to a static string value.
:rtype: str
%End

static double parameterAsDouble( const QVariantMap &parameters, const QString &name, const QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``name`` to a static double value.
:rtype: float
%End

static int parameterAsInt( const QVariantMap &parameters, const QString &name, const QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``name`` to a static integer value.
:rtype: int
%End

static bool parameterAsBool( const QVariantMap &parameters, const QString &name, const QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``name`` to a static boolean value.
:rtype: bool
%End

static QgsMapLayer *parameterAsLayer( const QVariantMap &parameters, const QString &name, QgsProcessingContext &context );
%Docstring
Evaluates the parameter with matching ``name`` to a map layer.

Layers will either be taken from ``context``'s active project, or loaded from external
sources and stored temporarily in the ``context``. In either case, callers do not
need to handle deletion of the returned layer.
:rtype: QgsMapLayer
%End

};




/************************************************************************
* This file has been generated automatically from *
* *
* src/core/processing/qgsprocessingparameters.h *
* *
* Do not edit manually ! Edit header and run scripts/sipify.pl again *
************************************************************************/
4 changes: 4 additions & 0 deletions src/core/CMakeLists.txt
Expand Up @@ -87,7 +87,9 @@ SET(QGIS_CORE_SRCS
annotations/qgssvgannotation.cpp
annotations/qgstextannotation.cpp

processing/qgsnativealgorithms.cpp
processing/qgsprocessingalgorithm.cpp
processing/qgsprocessingparameters.cpp
processing/qgsprocessingprovider.cpp
processing/qgsprocessingregistry.cpp
processing/qgsprocessingutils.cpp
Expand Down Expand Up @@ -867,8 +869,10 @@ SET(QGIS_CORE_HDRS
metadata/qgslayermetadata.h
metadata/qgslayermetadatavalidator.h

processing/qgsnativealgorithms.h
processing/qgsprocessingalgorithm.h
processing/qgsprocessingcontext.h
processing/qgsprocessingparameters.h
processing/qgsprocessingutils.h

providers/memory/qgsmemoryfeatureiterator.h
Expand Down
123 changes: 123 additions & 0 deletions src/core/processing/qgsnativealgorithms.cpp
@@ -0,0 +1,123 @@
/***************************************************************************
qgsnativealgorithms.cpp
---------------------
begin : April 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#include "qgsnativealgorithms.h"
#include "qgsfeatureiterator.h"
#include "qgsprocessingcontext.h"
#include "qgsprocessingfeedback.h"
#include "qgsprocessingutils.h"
#include "qgsvectorlayer.h"
#include "qgsgeometry.h"
#include "qgswkbtypes.h"

bool QgsCentroidAlgorithm::run( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs ) const
{
Q_UNUSED( outputs );
QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingParameters::parameterAsLayer( parameters, QStringLiteral( "INPUT_LAYER" ), context ) );
if ( !layer )
return false;

QString dest;
QgsVectorLayer *outputLayer = nullptr;
std::unique_ptr< QgsFeatureSink > writer( QgsProcessingUtils::createFeatureSink( dest, QString(), layer->fields(), QgsWkbTypes::Point, layer->crs(), context, outputLayer ) );

QgsFeature f;
QgsFeatureIterator it = QgsProcessingUtils::getFeatures( layer, context );

double step = 100.0 / QgsProcessingUtils::featureCount( layer, context );
int current = 0;
while ( it.nextFeature( f ) )
{
if ( feedback->isCanceled() )
{
break;
}

QgsFeature out = f;
if ( out.hasGeometry() )
{
out.setGeometry( f.geometry().centroid() );
if ( !out.geometry() )
{
QgsMessageLog::logMessage( QObject::tr( "Error calculating centroid for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
}
}
writer->addFeature( out );

feedback->setProgress( current * step );
current++;
}

return true;
}

bool QgsBufferAlgorithm::run( const QVariantMap &parameters, QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs ) const
{
Q_UNUSED( outputs );

QgsVectorLayer *layer = qobject_cast< QgsVectorLayer *>( QgsProcessingParameters::parameterAsLayer( parameters, QStringLiteral( "INPUT" ), context ) );
if ( !layer )
return false;

QString dest;
QgsVectorLayer *outputLayer = nullptr;
std::unique_ptr< QgsFeatureSink > writer( QgsProcessingUtils::createFeatureSink( dest, QString(), layer->fields(), QgsWkbTypes::Point, layer->crs(), context, outputLayer ) );

// fixed parameters
bool dissolve = QgsProcessingParameters::parameterAsBool( parameters, QStringLiteral( "DISSOLVE" ), context );
int segments = QgsProcessingParameters::parameterAsInt( parameters, QStringLiteral( "DISSOLVE" ), context );
QgsGeometry::EndCapStyle endCapStyle = static_cast< QgsGeometry::EndCapStyle >( QgsProcessingParameters::parameterAsInt( parameters, QStringLiteral( "END_CAP_STYLE" ), context ) );
QgsGeometry::JoinStyle joinStyle = static_cast< QgsGeometry::JoinStyle>( QgsProcessingParameters::parameterAsInt( parameters, QStringLiteral( "JOIN_STYLE" ), context ) );
double miterLimit = QgsProcessingParameters::parameterAsDouble( parameters, QStringLiteral( "MITRE_LIMIT" ), context );
double bufferDistance = QgsProcessingParameters::parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
bool dynamicBuffer = QgsProcessingParameters::isDynamic( parameters, QStringLiteral( "DISTANCE" ) );

QgsFeature f;
QgsFeatureIterator it = QgsProcessingUtils::getFeatures( layer, context );

double step = 100.0 / QgsProcessingUtils::featureCount( layer, context );
int current = 0;
while ( it.nextFeature( f ) )
{
if ( feedback->isCanceled() )
{
break;
}

QgsFeature out = f;
if ( out.hasGeometry() )
{
if ( dynamicBuffer )
{
context.expressionContext().setFeature( f );
bufferDistance = QgsProcessingParameters::parameterAsDouble( parameters, QStringLiteral( "DISTANCE" ), context );
}

out.setGeometry( f.geometry().buffer( bufferDistance, segments, endCapStyle, joinStyle, miterLimit ) );
if ( !out.geometry() )
{
QgsMessageLog::logMessage( QObject::tr( "Error calculating buffer for feature %1" ).arg( f.id() ), QObject::tr( "Processing" ), QgsMessageLog::WARNING );
}
}
writer->addFeature( out );

feedback->setProgress( current * step );
current++;
}

return true;
}
71 changes: 71 additions & 0 deletions src/core/processing/qgsnativealgorithms.h
@@ -0,0 +1,71 @@
/***************************************************************************
qgsnativealgorithms.h
---------------------
begin : April 2017
copyright : (C) 2017 by Nyall Dawson
email : nyall dot dawson at gmail dot com
***************************************************************************/

/***************************************************************************
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
***************************************************************************/

#ifndef QGSNATIVEALGORITHMS_H
#define QGSNATIVEALGORITHMS_H

#include "qgis_core.h"
#include "qgis.h"
#include "qgsprocessingalgorithm.h"

///@cond PRIVATE

/**
* Native centroid algorithm.
*/
class QgsCentroidAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsCentroidAlgorithm() = default;

QString name() const override { return QStringLiteral( "centroids" ); }
QString displayName() const override { return QObject::tr( "Centroids" ); }
virtual QStringList tags() const override { return QObject::tr( "centroid,center,average,point,middle" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector geometry tools" ); }

virtual bool run( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs ) const override;

};

/**
* Native buffer algorithm.
*/
class QgsBufferAlgorithm : public QgsProcessingAlgorithm
{

public:

QgsBufferAlgorithm() = default;

QString name() const override { return QStringLiteral( "fixeddistancebuffer" ); }
QString displayName() const override { return QObject::tr( "Buffer" ); }
virtual QStringList tags() const override { return QObject::tr( "buffer,grow" ).split( ',' ); }
QString group() const override { return QObject::tr( "Vector geometry tools" ); }

virtual bool run( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs ) const override;

};

///@endcond PRIVATE

#endif // QGSNATIVEALGORITHMS_H


20 changes: 20 additions & 0 deletions src/core/processing/qgsprocessingalgorithm.h
Expand Up @@ -19,11 +19,15 @@
#define QGSPROCESSINGALGORITHM_H

#include "qgis_core.h"
#include "qgis.h"
#include "qgsprocessingparameters.h"
#include <QString>
#include <QVariant>
#include <QIcon>

class QgsProcessingProvider;
class QgsProcessingContext;
class QgsProcessingFeedback;

/**
* \class QgsProcessingAlgorithm
Expand Down Expand Up @@ -124,6 +128,22 @@ class CORE_EXPORT QgsProcessingAlgorithm
//TEMPORARY - remove when algorithms are no longer copied in python code
void setProvider( QgsProcessingProvider *provider );

/**
* 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 outputs map will be amended to include any outputs generated by this algorithm.
*
* 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.
*
* \returns true if algorithm run was successful, or false if run was unsuccessful.
*/
virtual bool run( const QVariantMap &parameters,
QgsProcessingContext &context, QgsProcessingFeedback *feedback, QVariantMap &outputs SIP_OUT ) const = 0;

private:

QgsProcessingProvider *mProvider = nullptr;
Expand Down

0 comments on commit 3706d88

Please sign in to comment.