Skip to content

Commit e01449f

Browse files
elpasonyalldawson
authored andcommittedSep 14, 2018
Handle bad/null geometries and geometryless
1 parent 8436813 commit e01449f

File tree

2 files changed

+110
-13
lines changed

2 files changed

+110
-13
lines changed
 

‎python/plugins/processing/gui/AlgorithmExecutor.py

Lines changed: 43 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def execute(alg, parameters, context=None, feedback=None):
7070
return False, {}
7171

7272

73-
def make_features_compatible(new_features, input_layer, context):
73+
def make_features_compatible(new_features, input_layer):
7474
"""Try to make the new features compatible with old features by:
7575
7676
- converting single to multi part
@@ -83,8 +83,6 @@ def make_features_compatible(new_features, input_layer, context):
8383
:type new_features: list of QgsFeatures
8484
:param input_layer: input layer
8585
:type input_layer: QgsVectorLayer
86-
:param context: processing context
87-
:type context: QgsProcessingContext
8886
:return: modified features
8987
:rtype: list of QgsFeatures
9088
"""
@@ -93,13 +91,46 @@ def make_features_compatible(new_features, input_layer, context):
9391
result_features = []
9492
for new_f in new_features:
9593
# Fix attributes
96-
if len(new_f.attributes()) > len(input_layer.fields()):
94+
if new_f.fields().count() > 0:
95+
attributes = []
96+
for field in input_layer.fields():
97+
if new_f.fields().indexFromName(field.name()) >= 0:
98+
attributes.append(new_f[field.name()])
99+
else:
100+
attributes.append(None)
97101
f = QgsFeature(input_layer.fields())
102+
f.setAttributes(attributes)
98103
f.setGeometry(new_f.geometry())
99-
f.setAttributes(new_f.attributes()[:len(input_layer.fields())])
100104
new_f = f
101-
# Fix geometry
102-
if new_f.geometry().wkbType() != input_wkb_type:
105+
else:
106+
lendiff = len(new_f.attributes()) - len(input_layer.fields())
107+
if lendiff > 0:
108+
f = QgsFeature(input_layer.fields())
109+
f.setGeometry(new_f.geometry())
110+
f.setAttributes(new_f.attributes()[:len(input_layer.fields())])
111+
new_f = f
112+
elif lendiff < 0:
113+
f = QgsFeature(input_layer.fields())
114+
f.setGeometry(new_f.geometry())
115+
attributes = new_f.attributes() + [None for i in range(-lendiff)]
116+
f.setAttributes(attributes)
117+
new_f = f
118+
119+
# Check if we need geometry manipulation
120+
new_f_geom_type = QgsWkbTypes.geometryType(new_f.geometry().wkbType())
121+
new_f_has_geom = new_f_geom_type not in (QgsWkbTypes.UnknownGeometry, QgsWkbTypes.NullGeometry)
122+
input_layer_has_geom = input_wkb_type not in (QgsWkbTypes.NoGeometry, QgsWkbTypes.Unknown)
123+
if input_layer_has_geom and not new_f_has_geom:
124+
continue # Skip this feature completely
125+
elif not input_layer_has_geom and new_f_has_geom:
126+
# Drop geometry
127+
f = QgsFeature(input_layer.fields())
128+
f.setAttributes(new_f.attributes())
129+
new_f = f
130+
result_features.append(new_f)
131+
continue # skip the rest
132+
133+
if input_layer_has_geom and new_f.geometry().wkbType() != input_wkb_type: # Fix geometry
103134
# Single -> Multi
104135
if (QgsWkbTypes.isMultiType(input_wkb_type) and not
105136
new_f.geometry().isMultipart()):
@@ -177,7 +208,7 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
177208
try:
178209
new_feature_ids = []
179210

180-
active_layer.beginEditCommand(tr('In-place editing by %s') % alg.name())
211+
active_layer.beginEditCommand(alg.name())
181212

182213
req = QgsFeatureRequest(QgsExpression(r"$id < 0"))
183214
req.setFlags(QgsFeatureRequest.NoGeometry)
@@ -196,7 +227,7 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
196227
feature_iterator = active_layer.getFeatures(QgsFeatureRequest(active_layer.selectedFeatureIds())) if parameters['INPUT'].selectedFeaturesOnly else active_layer.getFeatures()
197228
for f in feature_iterator:
198229
new_features = alg.processFeature(f, context, feedback)
199-
new_features = make_features_compatible(new_features, active_layer, context)
230+
new_features = make_features_compatible(new_features, active_layer)
200231
if len(new_features) == 0:
201232
active_layer.deleteFeature(f.id())
202233
elif len(new_features) == 1:
@@ -226,11 +257,12 @@ def execute_in_place_run(alg, active_layer, parameters, context=None, feedback=N
226257
active_layer.deleteFeatures(active_layer.selectedFeatureIds())
227258
new_features = []
228259
for f in result_layer.getFeatures():
229-
new_features.extend(make_features_compatible([f], active_layer, context))
260+
new_features.extend(make_features_compatible([f], active_layer))
230261

231262
# Get the new ids
232263
old_ids = set([f.id() for f in active_layer.getFeatures(req)])
233-
active_layer.addFeatures(new_features)
264+
if not active_layer.addFeatures(new_features):
265+
raise QgsProcessingException(tr("Error adding processed features back into the layer."))
234266
new_ids = set([f.id() for f in active_layer.getFeatures(req)])
235267
new_feature_ids += list(new_ids - old_ids)
236268

‎tests/src/python/test_qgsprocessinginplace.py

Lines changed: 67 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ def _support_inplace_edit_tester(self, alg_name, expected):
117117
alg = self.registry.createAlgorithmById(alg_name)
118118
for layer_wkb_name, supported in expected.items():
119119
layer = self._make_layer(layer_wkb_name)
120-
print("Checking %s ( %s ) : %s" % (alg_name, layer_wkb_name, supported))
120+
#print("Checking %s ( %s ) : %s" % (alg_name, layer_wkb_name, supported))
121121
self.assertEqual(alg.supportInPlaceEdit(layer), supported, "Expected: %s - %s = supported: %s" % (alg_name, layer_wkb_name, supported))
122122

123123
def test_support_in_place_edit(self):
@@ -175,7 +175,7 @@ def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
175175
context.setProject(QgsProject.instance())
176176

177177
# Fix it!
178-
new_features = make_features_compatible([f], layer, context)
178+
new_features = make_features_compatible([f], layer)
179179

180180
for new_f in new_features:
181181
self.assertEqual(new_f.geometry().wkbType(), layer.wkbType())
@@ -259,6 +259,71 @@ def test_make_features_compatible(self):
259259
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 2 2, 3 3, 1 1)')
260260
self.assertEqual(f[1].geometry().asWkt(), 'LineString (10 1, 20 2, 30 3, 10 1)')
261261

262+
def test_make_features_compatible_attributes(self):
263+
"""Test corner cases for attributes"""
264+
265+
# Test feature without attributes
266+
fields = QgsFields()
267+
fields.append(QgsField('int_f', QVariant.Int))
268+
fields.append(QgsField('str_f', QVariant.String))
269+
layer = QgsMemoryProviderUtils.createMemoryLayer(
270+
'mkfca_layer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
271+
self.assertTrue(layer.isValid())
272+
f1 = QgsFeature(layer.fields())
273+
f1['int_f'] = 1
274+
f1['str_f'] = 'str'
275+
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
276+
new_features = make_features_compatible([f1], layer)
277+
self.assertEqual(new_features[0].attributes(), f1.attributes())
278+
self.assertTrue(new_features[0].geometry().asWkt(), f1.geometry().asWkt())
279+
280+
# Test pad with 0 with fields
281+
f1.setAttributes([])
282+
new_features = make_features_compatible([f1], layer)
283+
self.assertEqual(len(new_features[0].attributes()), 2)
284+
self.assertEqual(new_features[0].attributes()[0], QVariant())
285+
self.assertEqual(new_features[0].attributes()[1], QVariant())
286+
287+
# Test pad with 0 without fields
288+
f1 = QgsFeature()
289+
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
290+
new_features = make_features_compatible([f1], layer)
291+
self.assertEqual(len(new_features[0].attributes()), 2)
292+
self.assertEqual(new_features[0].attributes()[0], QVariant())
293+
self.assertEqual(new_features[0].attributes()[1], QVariant())
294+
295+
# Test drop extra attrs
296+
f1 = QgsFeature(layer.fields())
297+
f1.setAttributes([1, 'foo', 'extra'])
298+
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
299+
new_features = make_features_compatible([f1], layer)
300+
self.assertEqual(len(new_features[0].attributes()), 2)
301+
self.assertEqual(new_features[0].attributes()[0], 1)
302+
self.assertEqual(new_features[0].attributes()[1], 'foo')
303+
304+
def test_make_features_compatible_geometry(self):
305+
"""Test corner cases for geometries"""
306+
layer = self._make_layer('Point')
307+
self.assertTrue(layer.isValid())
308+
self.assertTrue(layer.startEditing())
309+
f1 = QgsFeature(layer.fields())
310+
f1.setAttributes([1])
311+
new_features = make_features_compatible([f1], layer)
312+
self.assertEqual(len(new_features), 0)
313+
314+
nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
315+
'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
316+
new_features = make_features_compatible([f1], nogeom_layer)
317+
self.assertEqual(len(new_features), 1)
318+
self.assertEqual(new_features[0].geometry().asWkt(), '')
319+
320+
nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
321+
'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
322+
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
323+
new_features = make_features_compatible([f1], nogeom_layer)
324+
self.assertEqual(len(new_features), 1)
325+
self.assertEqual(new_features[0].geometry().asWkt(), '')
326+
262327
def _alg_tester(self, alg_name, input_layer, parameters):
263328

264329
alg = self.registry.createAlgorithmById(alg_name)

0 commit comments

Comments
 (0)
Please sign in to comment.