Skip to content

Commit

Permalink
In-place moved check logic into QgsAlgorithm
Browse files Browse the repository at this point in the history
+ new tests
+ fixed fixer function
+ drop z/m
  • Loading branch information
elpaso authored and nyalldawson committed Sep 14, 2018
1 parent 11aaf90 commit 4549ee5
Show file tree
Hide file tree
Showing 18 changed files with 244 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,16 @@ should correspond to the invalid source parameter name.
.. seealso:: :py:func:`invalidRasterError`

.. versionadded:: 3.2
%End

virtual bool supportInPlaceEdit( const QgsVectorLayer *layer ) const;
%Docstring
Checks whether this algorithm supports in-place editing on the given ``layer``
Default implementation returns false.

:return: true if the algorithm supports in-place editing

.. versionadded:: 3.4
%End

private:
Expand Down Expand Up @@ -964,6 +974,17 @@ called from a subclasses' processFeature() implementation.
%Docstring
Returns the feature request used for fetching features to process from the
source layer. The default implementation requests all attributes and geometry.
%End

virtual bool supportInPlaceEdit( const QgsVectorLayer *layer ) const;
%Docstring
Checks whether this algorithm supports in-place editing on the given ``layer``
Default implementation for feature based algorithms run some basic compatibility
checks based on the geometry type of the layer.

:return: true if the algorithm supports in-place editing

.. versionadded:: 3.4
%End

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -418,7 +418,7 @@ Returns any filters that affect how toolbox content is filtered.
.. seealso:: :py:func:`setFilters`
%End

void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
void setInPlaceLayer( QgsVectorLayer *layer );

void setFilterString( const QString &filter );
%Docstring
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,11 @@ Sets ``filters`` controlling the view's contents.
%End


void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
void setInPlaceLayer( QgsVectorLayer *layer );
%Docstring
Sets geometry \type for the in-place algorithms
@param type

:param layer: the vector layer for in-place algorithm filter
%End

public slots:
Expand Down
8 changes: 5 additions & 3 deletions python/plugins/processing/gui/AlgorithmDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ def getParameterValues(self):
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
continue
if not param.isDestination():

if self.in_place and param.name() == 'INPUT':
parameters[param.name()] = iface.activeLayer()
continue

try:
wrapper = self.mainWidget().wrappers[param.name()]
except KeyError:
Expand All @@ -124,9 +129,6 @@ def getParameterValues(self):

value = wrapper.parameterValue()
parameters[param.name()] = value
if self.in_place and param.name() == 'INPUT':
parameters[param.name()] = iface.activeLayer()
continue

wrapper = self.mainWidget().wrappers[param.name()]
value = None
Expand Down
82 changes: 57 additions & 25 deletions python/plugins/processing/gui/AlgorithmExecutor.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
QgsFeatureRequest,
QgsFeature,
QgsExpression,
QgsWkbTypes)
QgsWkbTypes,
QgsGeometry)
from processing.gui.Postprocessing import handleAlgorithmResults
from processing.tools import dataobjects
from qgis.utils import iface
Expand Down Expand Up @@ -86,27 +87,53 @@ def make_feature_compatible(new_features, input_layer):

result_features = []
for new_f in new_features:
if (new_f.geometry().wkbType() != input_layer.wkbType() and
QgsWkbTypes.isMultiType(input_layer.wkbType()) and not
new_f.geometry().isMultipart()):
new_geom = new_f.geometry()
new_geom.convertToMultiType()
new_f.setGeometry(new_geom)
if len(new_f.fields()) > len(input_layer.fields()):
if new_f.geometry().wkbType() != input_layer.wkbType():
# Single -> Multi
if (QgsWkbTypes.isMultiType(input_layer.wkbType()) and not
new_f.geometry().isMultipart()):
new_geom = new_f.geometry()
new_geom.convertToMultiType()
new_f.setGeometry(new_geom)
# Drop Z/M
if ((QgsWkbTypes.hasZ(new_f.geometry().wkbType()) and not QgsWkbTypes.hasZ(input_layer.wkbType())) or
(QgsWkbTypes.hasM(new_f.geometry().wkbType()) and not QgsWkbTypes.hasM(input_layer.wkbType()))):
# FIXME: there must be a better way!!!
if new_f.geometry().type() == QgsWkbTypes.PointGeometry:
if new_f.geometry().isMultipart():
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPoint().asWkt())
else:
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPoint().asWkt())
elif new_f.geometry().type() == QgsWkbTypes.PolygonGeometry:
if new_f.geometry().isMultipart():
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPolygon().asWkt())
else:
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPolygon().asWkt())
elif new_f.geometry().type() == QgsWkbTypes.LineGeometry: # Linestring
if new_f.geometry().isMultipart():
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPolyline().asWkt())
else:
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPolyline().asWkt())
else:
new_geom = QgsGeometry()
new_f.setGeometry(new_geom)
if len(new_f.attributes()) > len(input_layer.fields()):
f = QgsFeature(input_layer.fields())
f.setGeometry(new_f.geometry())
f.setAttributes(new_f.attributes()[:len(input_layer.fields())])
new_f = f
result_features.append(new_f)
return result_features


def execute_in_place_run(alg, parameters, context=None, feedback=None):
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
"""Executes an algorithm modifying features in-place in the input layer.
The input layer must be editable or an exception is raised.
:param alg: algorithm to run
:type alg: QgsProcessingAlgorithm
:param active_layer: the editable layer
:type active_layer: QgsVectoLayer
:param parameters: parameters of the algorithm
:type parameters: dict
:param context: context, defaults to None
Expand All @@ -123,16 +150,11 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
if context is None:
context = dataobjects.createContext(feedback)

# It would be nicer to get the layer from INPUT with
# alg.parameterAsVectorLayer(parameters, 'INPUT', context)
# but it does not work.
active_layer_id, ok = parameters['INPUT'].source.value(context.expressionContext())
if ok:
active_layer = QgsProject.instance().mapLayer(active_layer_id)
if active_layer is None or not active_layer.isEditable():
raise QgsProcessingException(tr("Layer is not editable or layer with id '%s' could not be found in the current project.") % active_layer_id)
else:
return False, {}
if active_layer is None or not active_layer.isEditable():
raise QgsProcessingException(tr("Layer is not editable or layer is None."))

if not alg.supportInPlaceEdit(active_layer):
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))

parameters['OUTPUT'] = 'memory:'

Expand All @@ -147,7 +169,13 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):

# Checks whether the algorithm has a processFeature method
if hasattr(alg, 'processFeature'): # in-place feature editing
# Make a clone or it will crash the second time the dialog
# is opened and run
alg = alg.create()
alg.prepare(parameters, context, feedback)
# Check again for compatibility after prepare
if not alg.supportInPlaceEdit(active_layer):
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
field_idxs = range(len(active_layer.fields()))
feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
for f in feature_iterator:
Expand All @@ -166,7 +194,8 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
active_layer.deleteFeature(f.id())
# Get the new ids
old_ids = set([f.id() for f in active_layer.getFeatures(req)])
active_layer.addFeatures(new_features)
if not active_layer.addFeatures(new_features):
raise QgsProcessingException(tr("Error adding processed features back into the layer."))
new_ids = set([f.id() for f in active_layer.getFeatures(req)])
new_feature_ids += list(new_ids - old_ids)

Expand All @@ -188,21 +217,24 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
new_ids = set([f.id() for f in active_layer.getFeatures(req)])
new_feature_ids += list(new_ids - old_ids)

active_layer.endEditCommand()

if ok and new_feature_ids:
active_layer.selectByIds(new_feature_ids)
elif not ok:
active_layer.rollback()

active_layer.endEditCommand()

return ok, results

except QgsProcessingException as e:
active_layer.endEditCommand()
if raise_exceptions:
raise e
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
if feedback is not None:
feedback.reportError(e.msg)
feedback.reportError(getattr(e, 'msg', str(e)))

return False, {}
return False, {}


def execute_in_place(alg, parameters, context=None, feedback=None):
Expand All @@ -224,7 +256,7 @@ def execute_in_place(alg, parameters, context=None, feedback=None):
"""

parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
ok, results = execute_in_place_run(alg, parameters, context=context, feedback=feedback)
ok, results = execute_in_place_run(alg, iface.activeLayer(), parameters, context=context, feedback=feedback)
if ok:
iface.activeLayer().triggerRepaint()
return ok, results
Expand Down
2 changes: 1 addition & 1 deletion python/plugins/processing/gui/ProcessingToolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def set_in_place_edit_mode(self, enabled):
def layer_changed(self, layer):
if layer is None or layer.type() != QgsMapLayer.VectorLayer:
return
self.algorithmTree.setInPlaceLayerType(QgsWkbTypes.geometryType(layer.wkbType()))
self.algorithmTree.setInPlaceLayer(layer)

def disabledProviders(self):
showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)
Expand Down
6 changes: 6 additions & 0 deletions src/analysis/processing/qgsalgorithmdropmzvalues.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ QgsDropMZValuesAlgorithm *QgsDropMZValuesAlgorithm::createInstance() const
return new QgsDropMZValuesAlgorithm();
}

bool QgsDropMZValuesAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
{
Q_UNUSED( layer );
return false;
}

void QgsDropMZValuesAlgorithm::initParameters( const QVariantMap & )
{
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DROP_M_VALUES" ), QObject::tr( "Drop M Values" ), false ) );
Expand Down
2 changes: 2 additions & 0 deletions src/analysis/processing/qgsalgorithmdropmzvalues.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class QgsDropMZValuesAlgorithm : public QgsProcessingFeatureBasedAlgorithm
QString groupId() const override;
QString shortHelpString() const override;
QgsDropMZValuesAlgorithm *createInstance() const override SIP_FACTORY;
bool supportInPlaceEdit( const QgsVectorLayer *layer ) const override;

protected:

Expand All @@ -55,6 +56,7 @@ class QgsDropMZValuesAlgorithm : public QgsProcessingFeatureBasedAlgorithm

bool mDropM = false;
bool mDropZ = false;

};

///@endcond PRIVATE
Expand Down
23 changes: 19 additions & 4 deletions src/analysis/processing/qgsalgorithmtranslate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
***************************************************************************/

#include "qgsalgorithmtranslate.h"
#include "qgsvectorlayer.h"

///@cond PRIVATE

Expand Down Expand Up @@ -141,9 +142,9 @@ QgsFeatureList QgsTranslateAlgorithm::processFeature( const QgsFeature &feature,
if ( mDynamicDeltaM )
deltaM = mDeltaMProperty.valueAsDouble( context.expressionContext(), deltaM );

if ( deltaZ != 0 && !geometry.constGet()->is3D() )
if ( deltaZ != 0.0 && !geometry.constGet()->is3D() )
geometry.get()->addZValue( 0 );
if ( deltaM != 0 && !geometry.constGet()->isMeasure() )
if ( deltaM != 0.0 && !geometry.constGet()->isMeasure() )
geometry.get()->addMValue( 0 );

geometry.translate( deltaX, deltaY, deltaZ, deltaM );
Expand All @@ -155,13 +156,27 @@ QgsFeatureList QgsTranslateAlgorithm::processFeature( const QgsFeature &feature,
QgsWkbTypes::Type QgsTranslateAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
{
QgsWkbTypes::Type wkb = inputWkbType;
if ( mDeltaZ != 0 )
if ( mDeltaZ != 0.0 )
wkb = QgsWkbTypes::addZ( wkb );
if ( mDeltaM != 0 )
if ( mDeltaM != 0.0 )
wkb = QgsWkbTypes::addM( wkb );
return wkb;
}


bool QgsTranslateAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
{
if ( ! QgsProcessingFeatureBasedAlgorithm::supportInPlaceEdit( layer ) )
return false;

// Check if we can drop Z/M and still have some work done
if ( mDeltaX != 0.0 || mDeltaY != 0.0 )
return true;

// If the type differs there is no sense in executing the algorithm and drop the result
QgsWkbTypes::Type inPlaceWkbType = layer->wkbType();
return inPlaceWkbType == outputWkbType( inPlaceWkbType );
}
///@endcond


1 change: 1 addition & 0 deletions src/analysis/processing/qgsalgorithmtranslate.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ class QgsTranslateAlgorithm : public QgsProcessingFeatureBasedAlgorithm
QString shortHelpString() const override;
QgsTranslateAlgorithm *createInstance() const override SIP_FACTORY;
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
bool supportInPlaceEdit( const QgsVectorLayer *layer ) const override;

protected:
QString outputName() const override;
Expand Down
2 changes: 1 addition & 1 deletion src/core/locator/qgslocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
<< QStringLiteral( "allfeatures" )
<< QStringLiteral( "calculator" )
<< QStringLiteral( "bookmarks" )
<< QStringLiteral( "optionpages" );
<< QStringLiteral( "optionpages" )
<< QStringLiteral( "edit_features" );

QgsLocator::QgsLocator( QObject *parent )
Expand Down
31 changes: 31 additions & 0 deletions src/core/processing/qgsprocessingalgorithm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "qgsprocessingutils.h"
#include "qgsexception.h"
#include "qgsmessagelog.h"
#include "qgsvectorlayer.h"
#include "qgsprocessingfeedback.h"

QgsProcessingAlgorithm::~QgsProcessingAlgorithm()
Expand Down Expand Up @@ -761,6 +762,12 @@ QString QgsProcessingAlgorithm::invalidSinkError( const QVariantMap &parameters,
}
}

bool QgsProcessingAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
{
Q_UNUSED( layer );
return false;
}

bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParameterDefinition *parameter )
{
if ( !parameter->isDestination() )
Expand Down Expand Up @@ -902,3 +909,27 @@ QgsFeatureRequest QgsProcessingFeatureBasedAlgorithm::request() const
return QgsFeatureRequest();
}

bool QgsProcessingFeatureBasedAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
{
QgsWkbTypes::GeometryType inPlaceGeometryType = layer->geometryType();
if ( !inputLayerTypes().empty() &&
!inputLayerTypes().contains( QgsProcessing::TypeVector ) &&
!inputLayerTypes().contains( QgsProcessing::TypeVectorAnyGeometry ) &&
( ( inPlaceGeometryType == QgsWkbTypes::PolygonGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorPolygon ) ) ||
( inPlaceGeometryType == QgsWkbTypes::LineGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorLine ) ) ||
( inPlaceGeometryType == QgsWkbTypes::PointGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorPoint ) ) ) )
return false;

QgsWkbTypes::Type type = QgsWkbTypes::Unknown;
if ( inPlaceGeometryType == QgsWkbTypes::PointGeometry )
type = QgsWkbTypes::Point;
else if ( inPlaceGeometryType == QgsWkbTypes::LineGeometry )
type = QgsWkbTypes::LineString;
else if ( inPlaceGeometryType == QgsWkbTypes::PolygonGeometry )
type = QgsWkbTypes::Polygon;
if ( QgsWkbTypes::geometryType( outputWkbType( type ) ) != inPlaceGeometryType )
return false;

return true;
}

0 comments on commit 4549ee5

Please sign in to comment.