Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
In-place moved check logic into QgsAlgorithm
+ 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.
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
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
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
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
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
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
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
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
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
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
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
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.