39
39
QgsFeatureRequest ,
40
40
QgsFeature ,
41
41
QgsExpression ,
42
- QgsWkbTypes )
42
+ QgsWkbTypes ,
43
+ QgsGeometry )
43
44
from processing .gui .Postprocessing import handleAlgorithmResults
44
45
from processing .tools import dataobjects
45
46
from qgis .utils import iface
@@ -86,27 +87,53 @@ def make_feature_compatible(new_features, input_layer):
86
87
87
88
result_features = []
88
89
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 ()):
96
120
f = QgsFeature (input_layer .fields ())
97
121
f .setGeometry (new_f .geometry ())
98
122
f .setAttributes (new_f .attributes ()[:len (input_layer .fields ())])
123
+ new_f = f
99
124
result_features .append (new_f )
100
125
return result_features
101
126
102
127
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 ):
104
129
"""Executes an algorithm modifying features in-place in the input layer.
105
130
106
131
The input layer must be editable or an exception is raised.
107
132
108
133
:param alg: algorithm to run
109
134
:type alg: QgsProcessingAlgorithm
135
+ :param active_layer: the editable layer
136
+ :type active_layer: QgsVectoLayer
110
137
:param parameters: parameters of the algorithm
111
138
:type parameters: dict
112
139
:param context: context, defaults to None
@@ -123,16 +150,11 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
123
150
if context is None :
124
151
context = dataobjects .createContext (feedback )
125
152
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." ))
136
158
137
159
parameters ['OUTPUT' ] = 'memory:'
138
160
@@ -147,7 +169,13 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
147
169
148
170
# Checks whether the algorithm has a processFeature method
149
171
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 ()
150
175
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." ))
151
179
field_idxs = range (len (active_layer .fields ()))
152
180
feature_iterator = active_layer .getFeatures (QgsFeatureRequest (active_layer .selectedFeatureIds ())) if parameters ['INPUT' ].selectedFeaturesOnly else active_layer .getFeatures ()
153
181
for f in feature_iterator :
@@ -166,7 +194,8 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
166
194
active_layer .deleteFeature (f .id ())
167
195
# Get the new ids
168
196
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." ))
170
199
new_ids = set ([f .id () for f in active_layer .getFeatures (req )])
171
200
new_feature_ids += list (new_ids - old_ids )
172
201
@@ -188,21 +217,24 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
188
217
new_ids = set ([f .id () for f in active_layer .getFeatures (req )])
189
218
new_feature_ids += list (new_ids - old_ids )
190
219
220
+ active_layer .endEditCommand ()
221
+
191
222
if ok and new_feature_ids :
192
223
active_layer .selectByIds (new_feature_ids )
193
224
elif not ok :
194
225
active_layer .rollback ()
195
226
196
- active_layer .endEditCommand ()
197
-
198
227
return ok , results
199
228
200
229
except QgsProcessingException as e :
230
+ active_layer .endEditCommand ()
231
+ if raise_exceptions :
232
+ raise e
201
233
QgsMessageLog .logMessage (str (sys .exc_info ()[0 ]), 'Processing' , Qgis .Critical )
202
234
if feedback is not None :
203
- feedback .reportError (e . msg )
235
+ feedback .reportError (getattr ( e , ' msg' , str ( e )) )
204
236
205
- return False , {}
237
+ return False , {}
206
238
207
239
208
240
def execute_in_place (alg , parameters , context = None , feedback = None ):
@@ -224,7 +256,7 @@ def execute_in_place(alg, parameters, context=None, feedback=None):
224
256
"""
225
257
226
258
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 )
228
260
if ok :
229
261
iface .activeLayer ().triggerRepaint ()
230
262
return ok , results
0 commit comments