diff --git a/src/core/vector/qgsvectorlayereditutils.cpp b/src/core/vector/qgsvectorlayereditutils.cpp index dfea4838aa9..17803c7c62b 100644 --- a/src/core/vector/qgsvectorlayereditutils.cpp +++ b/src/core/vector/qgsvectorlayereditutils.cpp @@ -14,6 +14,7 @@ ***************************************************************************/ #include "qgsvectorlayereditutils.h" +#include "qgsunsetattributevalue.h" #include "qgsvectordataprovider.h" #include "qgsfeatureiterator.h" #include "qgsvectorlayereditbuffer.h" @@ -413,6 +414,8 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsC QgsVectorLayerUtils::QgsFeaturesDataList featuresDataToAdd; + const int fieldCount = mLayer->fields().count(); + QgsFeature feat; while ( features.nextFeature( feat ) ) { @@ -422,7 +425,8 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsC } QVector newGeometries; QgsPointSequence featureTopologyTestPoints; - QgsGeometry featureGeom = feat.geometry(); + const QgsGeometry originalGeom = feat.geometry(); + QgsGeometry featureGeom = originalGeom; splitFunctionReturn = featureGeom.splitGeometry( curve, newGeometries, preserveCircular, topologicalEditing, featureTopologyTestPoints ); topologyTestPoints.append( featureTopologyTestPoints ); if ( splitFunctionReturn == Qgis::GeometryOperationResult::Success ) @@ -430,10 +434,142 @@ Qgis::GeometryOperationResult QgsVectorLayerEditUtils::splitFeatures( const QgsC //change this geometry mLayer->changeGeometry( feat.id(), featureGeom ); + //update any attributes for original feature which are set to GeometryRatio split policy + QgsAttributeMap attributeMap; + for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx ) + { + const QgsField field = mLayer->fields().at( fieldIdx ); + switch ( field.splitPolicy() ) + { + case Qgis::FieldDomainSplitPolicy::DefaultValue: + case Qgis::FieldDomainSplitPolicy::Duplicate: + case Qgis::FieldDomainSplitPolicy::UnsetField: + break; + + case Qgis::FieldDomainSplitPolicy::GeometryRatio: + { + if ( field.isNumeric() ) + { + const double originalValue = feat.attribute( fieldIdx ).toDouble(); + + double originalSize = 0; + + switch ( originalGeom.type() ) + { + case Qgis::GeometryType::Point: + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + originalSize = 0; + break; + case Qgis::GeometryType::Line: + originalSize = originalGeom.length(); + break; + case Qgis::GeometryType::Polygon: + originalSize = originalGeom.area(); + break; + } + + double newSize = 0; + switch ( featureGeom.type() ) + { + case Qgis::GeometryType::Point: + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + newSize = 0; + break; + case Qgis::GeometryType::Line: + newSize = featureGeom.length(); + break; + case Qgis::GeometryType::Polygon: + newSize = featureGeom.area(); + break; + } + + attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue ); + } + break; + } + } + } + + if ( !attributeMap.isEmpty() ) + { + mLayer->changeAttributeValues( feat.id(), attributeMap ); + } + //insert new features - QgsAttributeMap attributeMap = feat.attributes().toMap(); for ( const QgsGeometry &geom : std::as_const( newGeometries ) ) { + QgsAttributeMap attributeMap; + for ( int fieldIdx = 0; fieldIdx < fieldCount; ++fieldIdx ) + { + const QgsField field = mLayer->fields().at( fieldIdx ); + // respect field split policy + switch ( field.splitPolicy() ) + { + case Qgis::FieldDomainSplitPolicy::DefaultValue: + // TODO!!! + + break; + + case Qgis::FieldDomainSplitPolicy::Duplicate: + attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) ); + break; + + case Qgis::FieldDomainSplitPolicy::GeometryRatio: + { + if ( !field.isNumeric() ) + { + attributeMap.insert( fieldIdx, feat.attribute( fieldIdx ) ); + } + else + { + const double originalValue = feat.attribute( fieldIdx ).toDouble(); + + double originalSize = 0; + + switch ( originalGeom.type() ) + { + case Qgis::GeometryType::Point: + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + originalSize = 0; + break; + case Qgis::GeometryType::Line: + originalSize = originalGeom.length(); + break; + case Qgis::GeometryType::Polygon: + originalSize = originalGeom.area(); + break; + } + + double newSize = 0; + switch ( geom.type() ) + { + case Qgis::GeometryType::Point: + case Qgis::GeometryType::Unknown: + case Qgis::GeometryType::Null: + newSize = 0; + break; + case Qgis::GeometryType::Line: + newSize = geom.length(); + break; + case Qgis::GeometryType::Polygon: + newSize = geom.area(); + break; + } + + attributeMap.insert( fieldIdx, originalSize > 0 ? ( originalValue * newSize / originalSize ) : originalValue ); + } + break; + } + + case Qgis::FieldDomainSplitPolicy::UnsetField: + attributeMap.insert( fieldIdx, QgsUnsetAttributeValue() ); + break; + } + } + featuresDataToAdd << QgsVectorLayerUtils::QgsFeatureData( geom, attributeMap ); } diff --git a/tests/src/python/test_qgsvectorlayereditutils.py b/tests/src/python/test_qgsvectorlayereditutils.py index bf0b3f998e8..59300deaa35 100644 --- a/tests/src/python/test_qgsvectorlayereditutils.py +++ b/tests/src/python/test_qgsvectorlayereditutils.py @@ -34,6 +34,9 @@ from qgis.core import ( QgsVectorLayer, QgsVectorLayerEditUtils, QgsWkbTypes, + QgsDefaultValue, + QgsVectorLayerUtils, + QgsUnsetAttributeValue ) from qgis.testing import start_app, unittest @@ -571,6 +574,114 @@ class TestQgsVectorLayerEditUtils(unittest.TestCase): ) self.assertEqual(mergedFeature.attribute('name'), 'tre') + def test_split_policy_lines(self): + temp_layer = QgsVectorLayer("LineString?crs=epsg:3111&field=pk:int&field=field_default:real&field=field_dupe:real&field=field_unset:real&field=field_ratio:real&field=apply_default:real", + "vl", "memory") + self.assertTrue(temp_layer.isValid()) + + temp_layer.setDefaultValueDefinition(1, + QgsDefaultValue('301')) + temp_layer.setDefaultValueDefinition(2, + QgsDefaultValue('302')) + temp_layer.setDefaultValueDefinition(3, + QgsDefaultValue('303')) + temp_layer.setDefaultValueDefinition(4, + QgsDefaultValue('304')) + temp_layer.setDefaultValueDefinition(5, + QgsDefaultValue('305', True)) + + temp_layer.setFieldSplitPolicy(1, + Qgis.FieldDomainSplitPolicy.DefaultValue) + temp_layer.setFieldSplitPolicy(2, + Qgis.FieldDomainSplitPolicy.Duplicate) + temp_layer.setFieldSplitPolicy(3, + Qgis.FieldDomainSplitPolicy.UnsetField) + temp_layer.setFieldSplitPolicy(4, + Qgis.FieldDomainSplitPolicy.GeometryRatio) + # this will be ignored -- the apply on update default will override it + temp_layer.setFieldSplitPolicy(5, + Qgis.FieldDomainSplitPolicy.GeometryRatio) + + temp_layer.startEditing() + + feature = QgsVectorLayerUtils.createFeature(temp_layer, + QgsGeometry.fromWkt('LineString( 0 0, 10 0)')) + feature[1] = 3301 + feature[5] = 3305 + self.assertTrue(temp_layer.addFeature(feature)) + + temp_layer.commitChanges() + + original_feature = next(temp_layer.getFeatures()) + self.assertEqual(original_feature.attributes(), + [None, 3301.0, 302.0, 303.0, 304.0, 3305.0]) + + temp_layer.startEditing() + + split_curve = QgsGeometry.fromWkt('LineString (0.3 0.2, 0.27 -0.2, 0.86 -0.24, 0.88 0.22)') + temp_layer.splitFeatures(split_curve.constGet(), preserveCircular=False) + + features = list(temp_layer.getFeatures()) + attributes = [f.attributes() for f in features] + self.assertCountEqual(attributes, + [[None, 3301.0, 302.0, 303.0, 8.664000000000001, 305.0], + [None, 301, 302.0, QgsUnsetAttributeValue(), 17.797217391304347, 305.0], + [None, 301, 302.0, QgsUnsetAttributeValue(), 277.53878260869567, 305.0] + ]) + + temp_layer.rollBack() + + def test_split_policy_polygon(self): + temp_layer = QgsVectorLayer("Polygon?crs=epsg:3111&field=pk:int&field=field_default:real&field=field_dupe:real&field=field_unset:real&field=field_ratio:real", + "vl", "memory") + self.assertTrue(temp_layer.isValid()) + + temp_layer.setDefaultValueDefinition(1, + QgsDefaultValue('301')) + temp_layer.setDefaultValueDefinition(2, + QgsDefaultValue('302')) + temp_layer.setDefaultValueDefinition(3, + QgsDefaultValue('303')) + temp_layer.setDefaultValueDefinition(4, + QgsDefaultValue('304')) + + temp_layer.setFieldSplitPolicy(1, + Qgis.FieldDomainSplitPolicy.DefaultValue) + temp_layer.setFieldSplitPolicy(2, + Qgis.FieldDomainSplitPolicy.Duplicate) + temp_layer.setFieldSplitPolicy(3, + Qgis.FieldDomainSplitPolicy.UnsetField) + temp_layer.setFieldSplitPolicy(4, + Qgis.FieldDomainSplitPolicy.GeometryRatio) + + temp_layer.startEditing() + + feature = QgsVectorLayerUtils.createFeature(temp_layer, + QgsGeometry.fromWkt('Polygon(( 0 0, 10 0, 10 10, 0 10, 0 0))')) + feature[1] = 3301 + self.assertTrue(temp_layer.addFeature(feature)) + + temp_layer.commitChanges() + + original_feature = next(temp_layer.getFeatures()) + self.assertEqual(original_feature.attributes(), + [None, 3301.0, 302.0, 303.0, 304.0]) + + temp_layer.startEditing() + + split_curve = QgsGeometry.fromWkt('LineString (-2.7 4.3, 5.5 11.8, 5.28 -5.57)') + temp_layer.splitFeatures(split_curve.constGet(), preserveCircular=False) + + features = list(temp_layer.getFeatures()) + attributes = [f.attributes() for f in features] + self.assertCountEqual(attributes, + [[None, 3301.0, 302.0, 303.0, 147.23845863746018], + [None, 301, 302.0, QgsUnsetAttributeValue(), 17.343326048780483], + [None, 301, 302.0, QgsUnsetAttributeValue(), 139.41821531375936] + ]) + + temp_layer.rollBack() + if __name__ == '__main__': unittest.main()