Skip to content

Commit 4549ee5

Browse files
elpasonyalldawson
authored andcommitted
In-place moved check logic into QgsAlgorithm
+ new tests + fixed fixer function + drop z/m
1 parent 11aaf90 commit 4549ee5

18 files changed

+244
-72
lines changed

python/core/auto_generated/processing/qgsprocessingalgorithm.sip.in

+21
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,16 @@ should correspond to the invalid source parameter name.
810810
.. seealso:: :py:func:`invalidRasterError`
811811

812812
.. versionadded:: 3.2
813+
%End
814+
815+
virtual bool supportInPlaceEdit( const QgsVectorLayer *layer ) const;
816+
%Docstring
817+
Checks whether this algorithm supports in-place editing on the given ``layer``
818+
Default implementation returns false.
819+
820+
:return: true if the algorithm supports in-place editing
821+
822+
.. versionadded:: 3.4
813823
%End
814824

815825
private:
@@ -964,6 +974,17 @@ called from a subclasses' processFeature() implementation.
964974
%Docstring
965975
Returns the feature request used for fetching features to process from the
966976
source layer. The default implementation requests all attributes and geometry.
977+
%End
978+
979+
virtual bool supportInPlaceEdit( const QgsVectorLayer *layer ) const;
980+
%Docstring
981+
Checks whether this algorithm supports in-place editing on the given ``layer``
982+
Default implementation for feature based algorithms run some basic compatibility
983+
checks based on the geometry type of the layer.
984+
985+
:return: true if the algorithm supports in-place editing
986+
987+
.. versionadded:: 3.4
967988
%End
968989

969990
};

python/gui/auto_generated/processing/qgsprocessingtoolboxmodel.sip.in

+1-1
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,7 @@ Returns any filters that affect how toolbox content is filtered.
418418
.. seealso:: :py:func:`setFilters`
419419
%End
420420

421-
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
421+
void setInPlaceLayer( QgsVectorLayer *layer );
422422

423423
void setFilterString( const QString &filter );
424424
%Docstring

python/gui/auto_generated/processing/qgsprocessingtoolboxtreeview.sip.in

+3-2
Original file line numberDiff line numberDiff line change
@@ -74,10 +74,11 @@ Sets ``filters`` controlling the view's contents.
7474
%End
7575

7676

77-
void setInPlaceLayerType( QgsWkbTypes::GeometryType type );
77+
void setInPlaceLayer( QgsVectorLayer *layer );
7878
%Docstring
7979
Sets geometry \type for the in-place algorithms
80-
@param type
80+
81+
:param layer: the vector layer for in-place algorithm filter
8182
%End
8283

8384
public slots:

python/plugins/processing/gui/AlgorithmDialog.py

+5-3
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ def getParameterValues(self):
105105
if param.flags() & QgsProcessingParameterDefinition.FlagHidden:
106106
continue
107107
if not param.isDestination():
108+
109+
if self.in_place and param.name() == 'INPUT':
110+
parameters[param.name()] = iface.activeLayer()
111+
continue
112+
108113
try:
109114
wrapper = self.mainWidget().wrappers[param.name()]
110115
except KeyError:
@@ -124,9 +129,6 @@ def getParameterValues(self):
124129

125130
value = wrapper.parameterValue()
126131
parameters[param.name()] = value
127-
if self.in_place and param.name() == 'INPUT':
128-
parameters[param.name()] = iface.activeLayer()
129-
continue
130132

131133
wrapper = self.mainWidget().wrappers[param.name()]
132134
value = None

python/plugins/processing/gui/AlgorithmExecutor.py

+57-25
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@
3939
QgsFeatureRequest,
4040
QgsFeature,
4141
QgsExpression,
42-
QgsWkbTypes)
42+
QgsWkbTypes,
43+
QgsGeometry)
4344
from processing.gui.Postprocessing import handleAlgorithmResults
4445
from processing.tools import dataobjects
4546
from qgis.utils import iface
@@ -86,27 +87,53 @@ def make_feature_compatible(new_features, input_layer):
8687

8788
result_features = []
8889
for new_f in new_features:
89-
if (new_f.geometry().wkbType() != input_layer.wkbType() and
90-
QgsWkbTypes.isMultiType(input_layer.wkbType()) and not
91-
new_f.geometry().isMultipart()):
92-
new_geom = new_f.geometry()
93-
new_geom.convertToMultiType()
94-
new_f.setGeometry(new_geom)
95-
if len(new_f.fields()) > len(input_layer.fields()):
90+
if new_f.geometry().wkbType() != input_layer.wkbType():
91+
# Single -> Multi
92+
if (QgsWkbTypes.isMultiType(input_layer.wkbType()) and not
93+
new_f.geometry().isMultipart()):
94+
new_geom = new_f.geometry()
95+
new_geom.convertToMultiType()
96+
new_f.setGeometry(new_geom)
97+
# Drop Z/M
98+
if ((QgsWkbTypes.hasZ(new_f.geometry().wkbType()) and not QgsWkbTypes.hasZ(input_layer.wkbType())) or
99+
(QgsWkbTypes.hasM(new_f.geometry().wkbType()) and not QgsWkbTypes.hasM(input_layer.wkbType()))):
100+
# FIXME: there must be a better way!!!
101+
if new_f.geometry().type() == QgsWkbTypes.PointGeometry:
102+
if new_f.geometry().isMultipart():
103+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPoint().asWkt())
104+
else:
105+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPoint().asWkt())
106+
elif new_f.geometry().type() == QgsWkbTypes.PolygonGeometry:
107+
if new_f.geometry().isMultipart():
108+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPolygon().asWkt())
109+
else:
110+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPolygon().asWkt())
111+
elif new_f.geometry().type() == QgsWkbTypes.LineGeometry: # Linestring
112+
if new_f.geometry().isMultipart():
113+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asPolyline().asWkt())
114+
else:
115+
new_geom = QgsGeometry.fromWkt(new_f.geometry().asMultiPolyline().asWkt())
116+
else:
117+
new_geom = QgsGeometry()
118+
new_f.setGeometry(new_geom)
119+
if len(new_f.attributes()) > len(input_layer.fields()):
96120
f = QgsFeature(input_layer.fields())
97121
f.setGeometry(new_f.geometry())
98122
f.setAttributes(new_f.attributes()[:len(input_layer.fields())])
123+
new_f = f
99124
result_features.append(new_f)
100125
return result_features
101126

102127

103-
def execute_in_place_run(alg, parameters, context=None, feedback=None):
128+
def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=None, raise_exceptions=False):
104129
"""Executes an algorithm modifying features in-place in the input layer.
105130
106131
The input layer must be editable or an exception is raised.
107132
108133
:param alg: algorithm to run
109134
:type alg: QgsProcessingAlgorithm
135+
:param active_layer: the editable layer
136+
:type active_layer: QgsVectoLayer
110137
:param parameters: parameters of the algorithm
111138
:type parameters: dict
112139
:param context: context, defaults to None
@@ -123,16 +150,11 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
123150
if context is None:
124151
context = dataobjects.createContext(feedback)
125152

126-
# It would be nicer to get the layer from INPUT with
127-
# alg.parameterAsVectorLayer(parameters, 'INPUT', context)
128-
# but it does not work.
129-
active_layer_id, ok = parameters['INPUT'].source.value(context.expressionContext())
130-
if ok:
131-
active_layer = QgsProject.instance().mapLayer(active_layer_id)
132-
if active_layer is None or not active_layer.isEditable():
133-
raise QgsProcessingException(tr("Layer is not editable or layer with id '%s' could not be found in the current project.") % active_layer_id)
134-
else:
135-
return False, {}
153+
if active_layer is None or not active_layer.isEditable():
154+
raise QgsProcessingException(tr("Layer is not editable or layer is None."))
155+
156+
if not alg.supportInPlaceEdit(active_layer):
157+
raise QgsProcessingException(tr("Selected algorithm and parameter configuration are not compatible with in-place modifications."))
136158

137159
parameters['OUTPUT'] = 'memory:'
138160

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

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

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

220+
active_layer.endEditCommand()
221+
191222
if ok and new_feature_ids:
192223
active_layer.selectByIds(new_feature_ids)
193224
elif not ok:
194225
active_layer.rollback()
195226

196-
active_layer.endEditCommand()
197-
198227
return ok, results
199228

200229
except QgsProcessingException as e:
230+
active_layer.endEditCommand()
231+
if raise_exceptions:
232+
raise e
201233
QgsMessageLog.logMessage(str(sys.exc_info()[0]), 'Processing', Qgis.Critical)
202234
if feedback is not None:
203-
feedback.reportError(e.msg)
235+
feedback.reportError(getattr(e, 'msg', str(e)))
204236

205-
return False, {}
237+
return False, {}
206238

207239

208240
def execute_in_place(alg, parameters, context=None, feedback=None):
@@ -224,7 +256,7 @@ def execute_in_place(alg, parameters, context=None, feedback=None):
224256
"""
225257

226258
parameters['INPUT'] = QgsProcessingFeatureSourceDefinition(iface.activeLayer().id(), True)
227-
ok, results = execute_in_place_run(alg, parameters, context=context, feedback=feedback)
259+
ok, results = execute_in_place_run(alg, iface.activeLayer(), parameters, context=context, feedback=feedback)
228260
if ok:
229261
iface.activeLayer().triggerRepaint()
230262
return ok, results

python/plugins/processing/gui/ProcessingToolbox.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ def set_in_place_edit_mode(self, enabled):
124124
def layer_changed(self, layer):
125125
if layer is None or layer.type() != QgsMapLayer.VectorLayer:
126126
return
127-
self.algorithmTree.setInPlaceLayerType(QgsWkbTypes.geometryType(layer.wkbType()))
127+
self.algorithmTree.setInPlaceLayer(layer)
128128

129129
def disabledProviders(self):
130130
showTip = ProcessingConfig.getSetting(ProcessingConfig.SHOW_PROVIDERS_TOOLTIP)

src/analysis/processing/qgsalgorithmdropmzvalues.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,12 @@ QgsDropMZValuesAlgorithm *QgsDropMZValuesAlgorithm::createInstance() const
5959
return new QgsDropMZValuesAlgorithm();
6060
}
6161

62+
bool QgsDropMZValuesAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
63+
{
64+
Q_UNUSED( layer );
65+
return false;
66+
}
67+
6268
void QgsDropMZValuesAlgorithm::initParameters( const QVariantMap & )
6369
{
6470
addParameter( new QgsProcessingParameterBoolean( QStringLiteral( "DROP_M_VALUES" ), QObject::tr( "Drop M Values" ), false ) );

src/analysis/processing/qgsalgorithmdropmzvalues.h

+2
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ class QgsDropMZValuesAlgorithm : public QgsProcessingFeatureBasedAlgorithm
4141
QString groupId() const override;
4242
QString shortHelpString() const override;
4343
QgsDropMZValuesAlgorithm *createInstance() const override SIP_FACTORY;
44+
bool supportInPlaceEdit( const QgsVectorLayer *layer ) const override;
4445

4546
protected:
4647

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

5657
bool mDropM = false;
5758
bool mDropZ = false;
59+
5860
};
5961

6062
///@endcond PRIVATE

src/analysis/processing/qgsalgorithmtranslate.cpp

+19-4
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
***************************************************************************/
1717

1818
#include "qgsalgorithmtranslate.h"
19+
#include "qgsvectorlayer.h"
1920

2021
///@cond PRIVATE
2122

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

144-
if ( deltaZ != 0 && !geometry.constGet()->is3D() )
145+
if ( deltaZ != 0.0 && !geometry.constGet()->is3D() )
145146
geometry.get()->addZValue( 0 );
146-
if ( deltaM != 0 && !geometry.constGet()->isMeasure() )
147+
if ( deltaM != 0.0 && !geometry.constGet()->isMeasure() )
147148
geometry.get()->addMValue( 0 );
148149

149150
geometry.translate( deltaX, deltaY, deltaZ, deltaM );
@@ -155,13 +156,27 @@ QgsFeatureList QgsTranslateAlgorithm::processFeature( const QgsFeature &feature,
155156
QgsWkbTypes::Type QgsTranslateAlgorithm::outputWkbType( QgsWkbTypes::Type inputWkbType ) const
156157
{
157158
QgsWkbTypes::Type wkb = inputWkbType;
158-
if ( mDeltaZ != 0 )
159+
if ( mDeltaZ != 0.0 )
159160
wkb = QgsWkbTypes::addZ( wkb );
160-
if ( mDeltaM != 0 )
161+
if ( mDeltaM != 0.0 )
161162
wkb = QgsWkbTypes::addM( wkb );
162163
return wkb;
163164
}
164165

166+
167+
bool QgsTranslateAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
168+
{
169+
if ( ! QgsProcessingFeatureBasedAlgorithm::supportInPlaceEdit( layer ) )
170+
return false;
171+
172+
// Check if we can drop Z/M and still have some work done
173+
if ( mDeltaX != 0.0 || mDeltaY != 0.0 )
174+
return true;
175+
176+
// If the type differs there is no sense in executing the algorithm and drop the result
177+
QgsWkbTypes::Type inPlaceWkbType = layer->wkbType();
178+
return inPlaceWkbType == outputWkbType( inPlaceWkbType );
179+
}
165180
///@endcond
166181

167182

src/analysis/processing/qgsalgorithmtranslate.h

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ class QgsTranslateAlgorithm : public QgsProcessingFeatureBasedAlgorithm
4242
QString shortHelpString() const override;
4343
QgsTranslateAlgorithm *createInstance() const override SIP_FACTORY;
4444
void initParameters( const QVariantMap &configuration = QVariantMap() ) override;
45+
bool supportInPlaceEdit( const QgsVectorLayer *layer ) const override;
4546

4647
protected:
4748
QString outputName() const override;

src/core/locator/qgslocator.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const QList<QString> QgsLocator::CORE_FILTERS = QList<QString>() << QStringLiter
2828
<< QStringLiteral( "allfeatures" )
2929
<< QStringLiteral( "calculator" )
3030
<< QStringLiteral( "bookmarks" )
31-
<< QStringLiteral( "optionpages" );
31+
<< QStringLiteral( "optionpages" )
3232
<< QStringLiteral( "edit_features" );
3333

3434
QgsLocator::QgsLocator( QObject *parent )

src/core/processing/qgsprocessingalgorithm.cpp

+31
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "qgsprocessingutils.h"
2626
#include "qgsexception.h"
2727
#include "qgsmessagelog.h"
28+
#include "qgsvectorlayer.h"
2829
#include "qgsprocessingfeedback.h"
2930

3031
QgsProcessingAlgorithm::~QgsProcessingAlgorithm()
@@ -761,6 +762,12 @@ QString QgsProcessingAlgorithm::invalidSinkError( const QVariantMap &parameters,
761762
}
762763
}
763764

765+
bool QgsProcessingAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
766+
{
767+
Q_UNUSED( layer );
768+
return false;
769+
}
770+
764771
bool QgsProcessingAlgorithm::createAutoOutputForParameter( QgsProcessingParameterDefinition *parameter )
765772
{
766773
if ( !parameter->isDestination() )
@@ -902,3 +909,27 @@ QgsFeatureRequest QgsProcessingFeatureBasedAlgorithm::request() const
902909
return QgsFeatureRequest();
903910
}
904911

912+
bool QgsProcessingFeatureBasedAlgorithm::supportInPlaceEdit( const QgsVectorLayer *layer ) const
913+
{
914+
QgsWkbTypes::GeometryType inPlaceGeometryType = layer->geometryType();
915+
if ( !inputLayerTypes().empty() &&
916+
!inputLayerTypes().contains( QgsProcessing::TypeVector ) &&
917+
!inputLayerTypes().contains( QgsProcessing::TypeVectorAnyGeometry ) &&
918+
( ( inPlaceGeometryType == QgsWkbTypes::PolygonGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorPolygon ) ) ||
919+
( inPlaceGeometryType == QgsWkbTypes::LineGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorLine ) ) ||
920+
( inPlaceGeometryType == QgsWkbTypes::PointGeometry && !inputLayerTypes().contains( QgsProcessing::TypeVectorPoint ) ) ) )
921+
return false;
922+
923+
QgsWkbTypes::Type type = QgsWkbTypes::Unknown;
924+
if ( inPlaceGeometryType == QgsWkbTypes::PointGeometry )
925+
type = QgsWkbTypes::Point;
926+
else if ( inPlaceGeometryType == QgsWkbTypes::LineGeometry )
927+
type = QgsWkbTypes::LineString;
928+
else if ( inPlaceGeometryType == QgsWkbTypes::PolygonGeometry )
929+
type = QgsWkbTypes::Polygon;
930+
if ( QgsWkbTypes::geometryType( outputWkbType( type ) ) != inPlaceGeometryType )
931+
return false;
932+
933+
return true;
934+
}
935+

0 commit comments

Comments
 (0)