mirror of
https://github.com/qgis/QGIS.git
synced 2025-04-15 00:04:00 -04:00
732 lines
31 KiB
Python
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()
|