QGIS/tests/src/python/test_qgsprocessinginplace.py
Alessandro Pasotti 71e2a6be7e In-place passthrough for invalid geometries if processing option is to skip invalid
Fixes #20147 - difference deletes invalid geometries
2018-10-18 17:43:00 +02:00

732 lines
31 KiB
Python

# -*- coding: utf-8 -*-
"""QGIS Unit tests for Processing In-Place algorithms.
.. note:: This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
"""
__author__ = 'Alessandro Pasotti'
__date__ = '2018-09'
__copyright__ = 'Copyright 2018, The QGIS Project'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
from qgis.PyQt.QtCore import QCoreApplication, QVariant
from qgis.core import (
QgsFeature, QgsGeometry, QgsSettings, QgsApplication, QgsMemoryProviderUtils, QgsWkbTypes, QgsField, QgsFields, QgsProcessingFeatureSourceDefinition, QgsProcessingContext, QgsProcessingFeedback, QgsCoordinateReferenceSystem, QgsProject, QgsProcessingException
)
from processing.core.Processing import Processing
from processing.core.ProcessingConfig import ProcessingConfig
from processing.tools import dataobjects
from processing.gui.AlgorithmExecutor import execute_in_place_run
from qgis.testing import start_app, unittest
from qgis.PyQt.QtTest import QSignalSpy
from qgis.analysis import QgsNativeAlgorithms
from qgis.core import QgsVectorLayerUtils
start_app()
class ConsoleFeedBack(QgsProcessingFeedback):
def reportError(self, error, fatalError=False):
print(error)
base_types = ['Point', 'LineString', 'Polygon']
def _add_multi(base):
return base + ['Multi' + _b for _b in base]
def _add_z(base):
return base + [_b + 'Z' for _b in base]
def _add_m(base):
return base + [_b + 'M' for _b in base]
def _all_true():
types = base_types
types = _add_multi(types)
types = _add_z(types)
types = _add_m(types)
types.append('NoGeometry')
return {t: True for t in types}
def _all_false():
return {t: False for t in _all_true().keys()}
class TestQgsProcessingInPlace(unittest.TestCase):
@classmethod
def setUpClass(cls):
"""Run before all tests"""
QCoreApplication.setOrganizationName("QGIS_Test")
QCoreApplication.setOrganizationDomain(
"QGIS_TestPyQgsProcessingInPlace.com")
QCoreApplication.setApplicationName("QGIS_TestPyQgsProcessingInPlace")
QgsSettings().clear()
Processing.initialize()
QgsApplication.processingRegistry().addProvider(QgsNativeAlgorithms())
cls.registry = QgsApplication.instance().processingRegistry()
fields = QgsFields()
fields.append(QgsField('int_f', QVariant.Int))
cls.vl = QgsMemoryProviderUtils.createMemoryLayer(
'mylayer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
f1 = QgsFeature(cls.vl.fields())
f1['int_f'] = 1
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
f2 = QgsFeature(cls.vl.fields())
f2['int_f'] = 2
f2.setGeometry(QgsGeometry.fromWkt('Point(9.5 45.6)'))
cls.vl.dataProvider().addFeatures([f1, f2])
assert cls.vl.isValid()
assert cls.vl.featureCount() == 2
# Multipolygon layer
cls.multipoly_vl = QgsMemoryProviderUtils.createMemoryLayer(
'mymultiplayer', fields, QgsWkbTypes.MultiPolygon, QgsCoordinateReferenceSystem(4326))
f3 = QgsFeature(cls.multipoly_vl.fields())
f3.setGeometry(QgsGeometry.fromWkt('MultiPolygon (((2.81856297539240419 41.98170998812887689, 2.81874467773035464 41.98167537995160359, 2.81879535908157752 41.98154066615795443, 2.81866433873670452 41.98144056064155905, 2.81848263699778379 41.98147516865246587, 2.81843195500470811 41.98160988234612034, 2.81856297539240419 41.98170998812887689)),((2.81898589063455907 41.9815711567298635, 2.81892080450418803 41.9816030048432367, 2.81884192631866437 41.98143737613141724, 2.8190679469505846 41.98142270931093378, 2.81898589063455907 41.9815711567298635)))'))
f4 = QgsFeature(cls.multipoly_vl.fields())
f4.setGeometry(QgsGeometry.fromWkt('MultiPolygon (((2.81823679385631332 41.98133290154246566, 2.81830770255185703 41.98123540208609228, 2.81825871989355159 41.98112524362621656, 2.81813882853970243 41.98111258462271422, 2.81806791984415872 41.98121008407908761, 2.81811690250246416 41.98132024253896333, 2.81823679385631332 41.98133290154246566)),((2.81835835162010895 41.98123286963267731, 2.8183127674586852 41.98108725356146209, 2.8184520523963692 41.98115436357689134, 2.81835835162010895 41.98123286963267731)))'))
cls.multipoly_vl.dataProvider().addFeatures([f3, f4])
assert cls.multipoly_vl.isValid()
assert cls.multipoly_vl.featureCount() == 2
QgsProject.instance().addMapLayers([cls.vl, cls.multipoly_vl])
def _make_layer(self, layer_wkb_name):
fields = QgsFields()
wkb_type = getattr(QgsWkbTypes, layer_wkb_name)
fields.append(QgsField('int_f', QVariant.Int))
layer = QgsMemoryProviderUtils.createMemoryLayer(
'%s_layer' % layer_wkb_name, fields, wkb_type, QgsCoordinateReferenceSystem(4326))
self.assertTrue(layer.isValid())
self.assertEqual(layer.wkbType(), wkb_type)
return layer
def _support_inplace_edit_tester(self, alg_name, expected):
alg = self.registry.createAlgorithmById(alg_name)
for layer_wkb_name, supported in expected.items():
layer = self._make_layer(layer_wkb_name)
#print("Checking %s ( %s ) : %s" % (alg_name, layer_wkb_name, supported))
self.assertEqual(alg.supportInPlaceEdit(layer), supported, "Expected: %s - %s = supported: %s" % (alg_name, layer_wkb_name, supported))
def test_support_in_place_edit(self):
ALL = _all_true()
GEOMETRY_ONLY = {t: t != 'NoGeometry' for t in _all_true().keys()}
NONE = _all_false()
LINESTRING_ONLY = {t: t.find('LineString') >= 0 for t in _all_true().keys()}
Z_ONLY = {t: t.find('Z') > 0 for t in _all_true().keys()}
M_ONLY = {t: t.rfind('M') > 0 for t in _all_true().keys()}
NOT_M = {t: t.rfind('M') < 1 and t != 'NoGeometry' for t in _all_true().keys()}
POLYGON_ONLY = {t: t in ('Polygon', 'MultiPolygon') for t in _all_true().keys()}
MULTI_ONLY = {t: t.find('Multi') == 0 for t in _all_true().keys()}
SINGLE_ONLY = {t: t.find('Multi') == -1 for t in _all_true().keys()}
LINESTRING_AND_POLYGON_ONLY = {t: (t.find('LineString') >= 0 or t.find('Polygon') >= 0) for t in _all_true().keys()}
LINESTRING_AND_POLYGON_ONLY_NOT_M = {t: (t.rfind('M') < 1 and (t.find('LineString') >= 0 or t.find('Polygon') >= 0)) for t in _all_true().keys()}
LINESTRING_AND_POLYGON_ONLY_NOT_M_NOT_Z = {t: (t.rfind('M') < 1 and t.find('Z') == -1 and (t.find('LineString') >= 0 or t.find('Polygon') >= 0)) for t in _all_true().keys()}
self._support_inplace_edit_tester('native:smoothgeometry', LINESTRING_AND_POLYGON_ONLY)
self._support_inplace_edit_tester('native:arrayoffsetlines', LINESTRING_ONLY)
self._support_inplace_edit_tester('native:arraytranslatedfeatures', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:reprojectlayer', GEOMETRY_ONLY)
self._support_inplace_edit_tester('qgis:densifygeometries', LINESTRING_AND_POLYGON_ONLY)
self._support_inplace_edit_tester('qgis:densifygeometriesgivenaninterval', LINESTRING_AND_POLYGON_ONLY)
self._support_inplace_edit_tester('native:setzfromraster', Z_ONLY)
self._support_inplace_edit_tester('native:explodelines', LINESTRING_ONLY)
self._support_inplace_edit_tester('native:extendlines', LINESTRING_ONLY)
self._support_inplace_edit_tester('native:fixgeometries', NOT_M)
self._support_inplace_edit_tester('native:minimumenclosingcircle', POLYGON_ONLY)
self._support_inplace_edit_tester('native:multiringconstantbuffer', POLYGON_ONLY)
self._support_inplace_edit_tester('native:orientedminimumboundingbox', POLYGON_ONLY)
self._support_inplace_edit_tester('qgis:orthogonalize', LINESTRING_AND_POLYGON_ONLY)
self._support_inplace_edit_tester('native:removeduplicatevertices', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:rotatefeatures', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:segmentizebymaxangle', NONE)
self._support_inplace_edit_tester('native:segmentizebymaxdistance', NONE)
self._support_inplace_edit_tester('native:setmfromraster', M_ONLY)
self._support_inplace_edit_tester('native:simplifygeometries', LINESTRING_AND_POLYGON_ONLY)
self._support_inplace_edit_tester('native:snappointstogrid', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:multiparttosingleparts', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:promotetomulti', MULTI_ONLY)
self._support_inplace_edit_tester('native:subdivide', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:translategeometry', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:swapxy', GEOMETRY_ONLY)
self._support_inplace_edit_tester('qgis:linestopolygons', NONE)
self._support_inplace_edit_tester('qgis:polygonstolines', NONE)
self._support_inplace_edit_tester('native:boundary', NONE)
self._support_inplace_edit_tester('native:clip', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:difference', GEOMETRY_ONLY)
self._support_inplace_edit_tester('native:dropgeometries', ALL)
self._support_inplace_edit_tester('native:splitwithlines', LINESTRING_AND_POLYGON_ONLY)
def _make_compatible_tester(self, feature_wkt, layer_wkb_name, attrs=[1]):
layer = self._make_layer(layer_wkb_name)
layer.startEditing()
f = QgsFeature(layer.fields())
f.setAttributes(attrs)
f.setGeometry(QgsGeometry.fromWkt(feature_wkt))
self.assertTrue(f.isValid())
context = QgsProcessingContext()
context.setProject(QgsProject.instance())
# Fix it!
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f], layer)
for new_f in new_features:
self.assertEqual(new_f.geometry().wkbType(), layer.wkbType())
self.assertTrue(layer.addFeatures(new_features), "Fail: %s - %s - %s" % (feature_wkt, attrs, layer_wkb_name))
return layer, new_features
def test_QgsVectorLayerUtilsmakeFeaturesCompatible(self):
"""Test fixer function"""
# Test failure
with self.assertRaises(AssertionError):
self._make_compatible_tester('LineString (1 1, 2 2, 3 3)', 'Point')
self._make_compatible_tester('Point(1 1)', 'Point')
self._make_compatible_tester('Point(1 1)', 'Point', [1, 'nope'])
self._make_compatible_tester('Point z (1 1 3)', 'Point')
self._make_compatible_tester('Point z (1 1 3)', 'PointZ')
# Adding Z back
l, f = self._make_compatible_tester('Point (1 1)', 'PointZ')
self.assertEqual(f[0].geometry().get().z(), 0)
# Adding M back
l, f = self._make_compatible_tester('Point (1 1)', 'PointM')
self.assertEqual(f[0].geometry().get().m(), 0)
self._make_compatible_tester('Point m (1 1 3)', 'Point')
self._make_compatible_tester('Point(1 3)', 'MultiPoint')
self._make_compatible_tester('MultiPoint((1 3), (2 2))', 'MultiPoint')
self._make_compatible_tester('Polygon((1 1, 2 2, 3 3, 1 1))', 'Polygon')
self._make_compatible_tester('Polygon((1 1, 2 2, 3 3, 1 1)', 'Polygon', [1, 'nope'])
self._make_compatible_tester('Polygon z ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'Polygon')
self._make_compatible_tester('Polygon z ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'PolygonZ')
# Adding Z back
l, f = self._make_compatible_tester('Polygon ((1 1, 2 2, 3 3, 1 1))', 'PolygonZ')
g = f[0].geometry()
g2 = g.get()
for v in g2.vertices():
self.assertEqual(v.z(), 0)
# Adding M back
l, f = self._make_compatible_tester('Polygon ((1 1, 2 2, 3 3, 1 1))', 'PolygonM')
g = f[0].geometry()
g2 = g.get()
for v in g2.vertices():
self.assertEqual(v.m(), 0)
self._make_compatible_tester('Polygon m ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'Polygon')
self._make_compatible_tester('Polygon m ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'PolygonM')
self._make_compatible_tester('Polygon((1 1, 2 2, 3 3, 1 1))', 'MultiPolygon')
self._make_compatible_tester('MultiPolygon(((1 1, 2 2, 3 3, 1 1)), ((1 1, 2 2, 3 3, 1 1)))', 'MultiPolygon')
self._make_compatible_tester('LineString((1 1, 2 2, 3 3, 1 1))', 'LineString')
self._make_compatible_tester('LineString((1 1, 2 2, 3 3, 1 1)', 'LineString', [1, 'nope'])
self._make_compatible_tester('LineString z ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'LineString')
self._make_compatible_tester('LineString z ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'LineStringZ')
self._make_compatible_tester('LineString m ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'LineString')
self._make_compatible_tester('LineString m ((1 1 1, 2 2 2, 3 3 3, 1 1 1))', 'LineStringM')
# Adding Z back
l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3, 1 1))', 'LineStringZ')
g = f[0].geometry()
g2 = g.get()
for v in g2.vertices():
self.assertEqual(v.z(), 0)
# Adding M back
l, f = self._make_compatible_tester('LineString (1 1, 2 2, 3 3, 1 1))', 'LineStringM')
g = f[0].geometry()
g2 = g.get()
for v in g2.vertices():
self.assertEqual(v.m(), 0)
self._make_compatible_tester('LineString(1 1, 2 2, 3 3, 1 1)', 'MultiLineString')
self._make_compatible_tester('MultiLineString((1 1, 2 2, 3 3, 1 1), (1 1, 2 2, 3 3, 1 1))', 'MultiLineString')
# Test Multi -> Single
l, f = self._make_compatible_tester('MultiLineString((1 1, 2 2, 3 3, 1 1), (10 1, 20 2, 30 3, 10 1))', 'LineString')
self.assertEqual(len(f), 2)
self.assertEqual(f[0].geometry().asWkt(), 'LineString (1 1, 2 2, 3 3, 1 1)')
self.assertEqual(f[1].geometry().asWkt(), 'LineString (10 1, 20 2, 30 3, 10 1)')
def test_make_features_compatible_attributes(self):
"""Test corner cases for attributes"""
# Test feature without attributes
fields = QgsFields()
fields.append(QgsField('int_f', QVariant.Int))
fields.append(QgsField('str_f', QVariant.String))
layer = QgsMemoryProviderUtils.createMemoryLayer(
'mkfca_layer', fields, QgsWkbTypes.Point, QgsCoordinateReferenceSystem(4326))
self.assertTrue(layer.isValid())
f1 = QgsFeature(layer.fields())
f1['int_f'] = 1
f1['str_f'] = 'str'
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
self.assertEqual(new_features[0].attributes(), f1.attributes())
self.assertTrue(new_features[0].geometry().asWkt(), f1.geometry().asWkt())
# Test pad with 0 with fields
f1.setAttributes([])
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
self.assertEqual(len(new_features[0].attributes()), 2)
self.assertEqual(new_features[0].attributes()[0], QVariant())
self.assertEqual(new_features[0].attributes()[1], QVariant())
# Test pad with 0 without fields
f1 = QgsFeature()
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
self.assertEqual(len(new_features[0].attributes()), 2)
self.assertEqual(new_features[0].attributes()[0], QVariant())
self.assertEqual(new_features[0].attributes()[1], QVariant())
# Test drop extra attrs
f1 = QgsFeature(layer.fields())
f1.setAttributes([1, 'foo', 'extra'])
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
self.assertEqual(len(new_features[0].attributes()), 2)
self.assertEqual(new_features[0].attributes()[0], 1)
self.assertEqual(new_features[0].attributes()[1], 'foo')
def test_make_features_compatible_geometry(self):
"""Test corner cases for geometries"""
# Make a feature with no geometry
layer = self._make_layer('Point')
self.assertTrue(layer.isValid())
self.assertTrue(layer.startEditing())
f1 = QgsFeature(layer.fields())
f1.setAttributes([1])
# Check that it is accepted on a Point layer
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], layer)
self.assertEqual(len(new_features), 1)
self.assertEqual(new_features[0].geometry().asWkt(), '')
# Make a geometry-less layer
nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
# Check that a geometry-less feature is accepted
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], nogeom_layer)
self.assertEqual(len(new_features), 1)
self.assertEqual(new_features[0].geometry().asWkt(), '')
# Make a geometry-less layer
nogeom_layer = QgsMemoryProviderUtils.createMemoryLayer(
'nogeom_layer', layer.fields(), QgsWkbTypes.NoGeometry, QgsCoordinateReferenceSystem(4326))
# Check that a Point feature is accepted but geometry was dropped
f1.setGeometry(QgsGeometry.fromWkt('Point(9 45)'))
new_features = QgsVectorLayerUtils.makeFeaturesCompatible([f1], nogeom_layer)
self.assertEqual(len(new_features), 1)
self.assertEqual(new_features[0].geometry().asWkt(), '')
def _alg_tester(self, alg_name, input_layer, parameters):
alg = self.registry.createAlgorithmById(alg_name)
self.assertIsNotNone(alg)
parameters['INPUT'] = input_layer
parameters['OUTPUT'] = 'memory:'
old_features = [f for f in input_layer.getFeatures()]
input_layer.selectByIds([old_features[0].id()])
# Check selected
self.assertEqual(input_layer.selectedFeatureIds(), [old_features[0].id()], alg_name)
context = QgsProcessingContext()
context.setProject(QgsProject.instance())
feedback = ConsoleFeedBack()
input_layer.rollBack()
ok = False
ok, _ = execute_in_place_run(
alg, parameters, context=context, feedback=feedback, raise_exceptions=True)
new_features = [f for f in input_layer.getFeatures()]
# Check ret values
self.assertTrue(ok, alg_name)
# Check geometry types (drop Z or M)
self.assertEqual(new_features[0].geometry().wkbType(), old_features[0].geometry().wkbType())
return old_features, new_features
def test_execute_in_place_run(self):
"""Test the execution in place"""
self.vl.rollBack()
old_features, new_features = self._alg_tester(
'native:translategeometry',
self.vl,
{
'DELTA_X': 1.1,
'DELTA_Y': 1.1,
}
)
# First feature was selected and modified
self.assertEqual(new_features[0].id(), old_features[0].id())
self.assertAlmostEqual(new_features[0].geometry().asPoint().x(), old_features[0].geometry().asPoint().x() + 1.1, delta=0.01)
self.assertAlmostEqual(new_features[0].geometry().asPoint().y(), old_features[0].geometry().asPoint().y() + 1.1, delta=0.01)
# Second feature was not selected and not modified
self.assertEqual(new_features[1].id(), old_features[1].id())
self.assertEqual(new_features[1].geometry().asPoint().x(), old_features[1].geometry().asPoint().x())
self.assertEqual(new_features[1].geometry().asPoint().y(), old_features[1].geometry().asPoint().y())
# Check selected
self.assertEqual(self.vl.selectedFeatureIds(), [old_features[0].id()])
# Check that if the only change is Z or M then we should fail
with self.assertRaises(QgsProcessingException) as cm:
self._alg_tester(
'native:translategeometry',
self.vl,
{
'DELTA_Z': 1.1,
}
)
self.vl.rollBack()
# Check that if the only change is Z or M then we should fail
with self.assertRaises(QgsProcessingException) as cm:
self._alg_tester(
'native:translategeometry',
self.vl,
{
'DELTA_M': 1.1,
}
)
self.vl.rollBack()
old_features, new_features = self._alg_tester(
'native:translategeometry',
self.vl,
{
'DELTA_X': 1.1,
'DELTA_Z': 1.1,
}
)
def test_select_all_features(self):
"""Check that if there is no selection, the alg will run on all features"""
self.vl.rollBack()
self.vl.removeSelection()
old_count = self.vl.featureCount()
context = QgsProcessingContext()
context.setProject(QgsProject.instance())
feedback = ConsoleFeedBack()
alg = self.registry.createAlgorithmById('native:translategeometry')
self.assertIsNotNone(alg)
parameters = {
'DELTA_X': 1.1,
'DELTA_Y': 1.1,
}
parameters['INPUT'] = self.vl
parameters['OUTPUT'] = 'memory:'
old_features = [f for f in self.vl.getFeatures()]
ok, _ = execute_in_place_run(
alg, parameters, context=context, feedback=feedback, raise_exceptions=True)
new_features = [f for f in self.vl.getFeatures()]
self.assertEqual(len(new_features), old_count)
# Check all are selected
self.assertEqual(len(self.vl.selectedFeatureIds()), old_count)
def test_multi_to_single(self):
"""Check that the geometry type is still multi after the alg is run"""
old_features, new_features = self._alg_tester(
'native:multiparttosingleparts',
self.multipoly_vl,
{
}
)
self.assertEqual(len(new_features), 3)
# Check selected
self.assertEqual(len(self.multipoly_vl.selectedFeatureIds()), 2)
def test_arraytranslatedfeatures(self):
"""Check that this runs correctly and additional attributes are dropped"""
old_count = self.vl.featureCount()
old_features, new_features = self._alg_tester(
'native:arraytranslatedfeatures',
self.vl,
{
'COUNT': 2,
'DELTA_X': 1.1,
'DELTA_Z': 1.1,
}
)
self.assertEqual(len(new_features), old_count + 2)
# Check selected
self.assertEqual(len(self.vl.selectedFeatureIds()), 3)
def test_reprojectlayer(self):
"""Check that this runs correctly"""
old_count = self.vl.featureCount()
old_features, new_features = self._alg_tester(
'native:reprojectlayer',
self.vl,
{
'TARGET_CRS': 'EPSG:3857',
}
)
g = [f.geometry() for f in new_features][0]
self.assertAlmostEqual(g.get().x(), 1001875.4, 1)
self.assertAlmostEqual(g.get().y(), 5621521.5, 1)
# Check selected
self.assertEqual(self.vl.selectedFeatureIds(), [1])
def test_snappointstogrid(self):
"""Check that this runs correctly"""
polygon_layer = self._make_layer('Polygon')
f1 = QgsFeature(polygon_layer.fields())
f1.setAttributes([1])
f1.setGeometry(QgsGeometry.fromWkt('POLYGON((1.2 1.2, 1.2 2.2, 2.2 2.2, 2.2 1.2, 1.2 1.2))'))
f2 = QgsFeature(polygon_layer.fields())
f2.setAttributes([2])
f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))'))
self.assertTrue(f2.isValid())
self.assertTrue(polygon_layer.startEditing())
self.assertTrue(polygon_layer.addFeatures([f1, f2]))
self.assertEqual(polygon_layer.featureCount(), 2)
polygon_layer.commitChanges()
self.assertEqual(polygon_layer.featureCount(), 2)
QgsProject.instance().addMapLayers([polygon_layer])
polygon_layer.selectByIds([next(polygon_layer.getFeatures()).id()])
self.assertEqual(polygon_layer.selectedFeatureCount(), 1)
old_features, new_features = self._alg_tester(
'native:snappointstogrid',
polygon_layer,
{
'HSPACING': 0.5,
'VSPACING': 0.5,
}
)
g = [f.geometry() for f in new_features][0]
self.assertEqual(g.asWkt(), 'Polygon ((1 1, 1 2, 2 2, 2 1, 1 1))')
# Check selected
self.assertEqual(polygon_layer.selectedFeatureIds(), [1])
def test_clip(self):
mask_layer = QgsMemoryProviderUtils.createMemoryLayer(
'mask_layer', self.vl.fields(), QgsWkbTypes.Polygon, QgsCoordinateReferenceSystem(4326))
self.assertTrue(mask_layer.isValid())
self.assertTrue(mask_layer.startEditing())
f = QgsFeature(mask_layer.fields())
f.setAttributes([1])
f.setGeometry(QgsGeometry.fromWkt('POLYGON((0 0, 0 1, 1 1, 1 0, 0 0))'))
self.assertTrue(f.isValid())
f2 = QgsFeature(mask_layer.fields())
f2.setAttributes([1])
f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))'))
self.assertTrue(f2.isValid())
self.assertTrue(mask_layer.addFeatures([f, f2]))
mask_layer.commitChanges()
mask_layer.rollBack()
clip_layer = QgsMemoryProviderUtils.createMemoryLayer(
'clip_layer', self.vl.fields(), QgsWkbTypes.LineString, QgsCoordinateReferenceSystem(4326))
self.assertTrue(clip_layer.isValid())
self.assertTrue(clip_layer.startEditing())
f = QgsFeature(clip_layer.fields())
f.setAttributes([1])
f.setGeometry(QgsGeometry.fromWkt('LINESTRING(-1 -1, 3 3)'))
self.assertTrue(f.isValid())
self.assertTrue(clip_layer.addFeatures([f]))
self.assertEqual(clip_layer.featureCount(), 1)
clip_layer.commitChanges()
clip_layer.selectAll()
clip_layer.rollBack()
QgsProject.instance().addMapLayers([clip_layer, mask_layer])
old_features, new_features = self._alg_tester(
'native:clip',
clip_layer,
{
'OVERLAY': mask_layer.id(),
}
)
self.assertEqual(len(new_features), 2)
self.assertEqual(new_features[0].geometry().asWkt(), 'LineString (0 0, 1 1)')
self.assertEqual(new_features[0].attributes(), [1])
def test_fix_geometries(self):
polygon_layer = self._make_layer('Polygon')
self.assertTrue(polygon_layer.startEditing())
f = QgsFeature(polygon_layer.fields())
f.setAttributes([1])
# Flake!
f.setGeometry(QgsGeometry.fromWkt('POLYGON ((0 0, 2 2, 0 2, 2 0, 0 0))'))
self.assertTrue(f.isValid())
f2 = QgsFeature(polygon_layer.fields())
f2.setAttributes([1])
f2.setGeometry(QgsGeometry.fromWkt('POLYGON((1.1 1.1, 1.1 2.1, 2.1 2.1, 2.1 1.1, 1.1 1.1))'))
self.assertTrue(f2.isValid())
self.assertTrue(polygon_layer.addFeatures([f, f2]))
polygon_layer.commitChanges()
polygon_layer.rollBack()
self.assertEqual(polygon_layer.featureCount(), 2)
QgsProject.instance().addMapLayers([polygon_layer])
old_features, new_features = self._alg_tester(
'native:fixgeometries',
polygon_layer,
{
}
)
self.assertEqual(polygon_layer.featureCount(), 3)
wkt1, wkt2, _ = [f.geometry().asWkt() for f in new_features]
self.assertEqual(wkt1, 'Polygon ((0 0, 1 1, 2 0, 0 0))')
self.assertEqual(wkt2, 'Polygon ((1 1, 0 2, 2 2, 1 1))')
# Test with Z (interpolated)
polygonz_layer = self._make_layer('PolygonZ')
self.assertTrue(polygonz_layer.startEditing())
f3 = QgsFeature(polygonz_layer.fields())
f3.setAttributes([1])
f3.setGeometry(QgsGeometry.fromWkt('POLYGON Z((0 0 1, 2 2 1, 0 2 3, 2 0 4, 0 0 1))'))
self.assertTrue(f3.isValid())
self.assertTrue(polygonz_layer.addFeatures([f3]))
polygonz_layer.commitChanges()
polygonz_layer.rollBack()
self.assertEqual(polygonz_layer.featureCount(), 1)
QgsProject.instance().addMapLayers([polygonz_layer])
old_features, new_features = self._alg_tester(
'native:fixgeometries',
polygonz_layer,
{
}
)
self.assertEqual(polygonz_layer.featureCount(), 2)
wkt1, wkt2 = [f.geometry().asWkt() for f in new_features]
self.assertEqual(wkt1, 'PolygonZ ((0 0 1, 1 1 2.25, 2 0 4, 0 0 1))')
self.assertEqual(wkt2, 'PolygonZ ((1 1 2.25, 0 2 3, 2 2 1, 1 1 2.25))')
def _test_difference_on_invalid_geometries(self, geom_option):
polygon_layer = self._make_layer('Polygon')
self.assertTrue(polygon_layer.startEditing())
f = QgsFeature(polygon_layer.fields())
f.setAttributes([1])
# Flake!
f.setGeometry(QgsGeometry.fromWkt('Polygon ((0 0, 2 2, 0 2, 2 0, 0 0))'))
self.assertTrue(f.isValid())
self.assertTrue(polygon_layer.addFeatures([f]))
polygon_layer.commitChanges()
polygon_layer.rollBack()
self.assertEqual(polygon_layer.featureCount(), 1)
overlay_layer = self._make_layer('Polygon')
self.assertTrue(overlay_layer.startEditing())
f = QgsFeature(overlay_layer.fields())
f.setAttributes([1])
f.setGeometry(QgsGeometry.fromWkt('Polygon ((0 0, 2 0, 2 2, 0 2, 0 0))'))
self.assertTrue(f.isValid())
self.assertTrue(overlay_layer.addFeatures([f]))
overlay_layer.commitChanges()
overlay_layer.rollBack()
self.assertEqual(overlay_layer.featureCount(), 1)
QgsProject.instance().addMapLayers([polygon_layer, overlay_layer])
old_features = [f for f in polygon_layer.getFeatures()]
# 'Ignore features with invalid geometries' = 1
ProcessingConfig.setSettingValue(ProcessingConfig.FILTER_INVALID_GEOMETRIES, geom_option)
feedback = ConsoleFeedBack()
context = dataobjects.createContext(feedback)
context.setProject(QgsProject.instance())
alg = self.registry.createAlgorithmById('native:difference')
self.assertIsNotNone(alg)
parameters = {
'OVERLAY': overlay_layer,
'INPUT': polygon_layer,
'OUTPUT': ':memory',
}
old_features = [f for f in polygon_layer.getFeatures()]
self.assertTrue(polygon_layer.startEditing())
polygon_layer.selectAll()
ok, _ = execute_in_place_run(
alg, parameters, context=context, feedback=feedback, raise_exceptions=True)
new_features = [f for f in polygon_layer.getFeatures()]
return old_features, new_features
def test_difference_on_invalid_geometries(self):
"""Test #20147 difference deletes invalid geometries"""
old_features, new_features = self._test_difference_on_invalid_geometries(1)
self.assertEqual(len(new_features), 1)
old_features, new_features = self._test_difference_on_invalid_geometries(0)
self.assertEqual(len(new_features), 1)
old_features, new_features = self._test_difference_on_invalid_geometries(2)
self.assertEqual(len(new_features), 1)
if __name__ == '__main__':
unittest.main()