Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit a78e7af

Browse files
authoredMar 31, 2023
Revert "[Backport ltr] Manual backport of 51456 merge feature relocation"
1 parent bc12bf4 commit a78e7af

File tree

9 files changed

+62
-335
lines changed

9 files changed

+62
-335
lines changed
 

‎python/core/auto_generated/vector/qgsvectorlayereditutils.sip.in

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -310,21 +310,6 @@ editing.
310310
:return: 2 in case vertex already exists or point does not intersect segment
311311

312312
.. versionadded:: 3.16
313-
%End
314-
315-
bool mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage /Out/ );
316-
%Docstring
317-
Merge features into a single one.
318-
319-
:param targetFeatureId: id of the target feature (will be updated)
320-
:param mergeFeatureIds: id list of features to merge (will be deleted)
321-
:param mergeAttributes: are the resulting attributes in the merged feature
322-
:param unionGeometry: is the resulting geometry of the merged feature
323-
324-
:return: - ``True`` if the merge was successful, or ``False`` if the operation failed.
325-
- errorMessage: will be set to a descriptive error message if any occurs
326-
327-
.. versionadded:: 3.30
328313
%End
329314

330315
};

‎src/app/qgisapp.cpp

Lines changed: 61 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@
102102
#include "qgsdockablewidgethelper.h"
103103
#include "vertextool/qgsvertexeditor.h"
104104
#include "qgsvectorlayerutils.h"
105-
#include "qgsvectorlayereditutils.h"
106105
#include "qgsadvanceddigitizingdockwidget.h"
107106
#include "qgsabstractdatasourcewidget.h"
108107
#include "qgsmeshlayer.h"
@@ -9518,8 +9517,9 @@ void QgisApp::mergeSelectedFeatures()
95189517
return;
95199518
}
95209519

9521-
// Check at least two features are selected
9522-
if ( vl->selectedFeatureIds().size() < 2 )
9520+
//get selected feature ids (as a QSet<int> )
9521+
const QgsFeatureIds &featureIdSet = vl->selectedFeatureIds();
9522+
if ( featureIdSet.size() < 2 )
95239523
{
95249524
visibleMessageBar()->pushMessage(
95259525
tr( "Not enough features selected" ),
@@ -9593,38 +9593,73 @@ void QgisApp::mergeSelectedFeatures()
95939593
}
95949594
return;
95959595
}
9596-
else if ( !QgsWkbTypes::isMultiType( vl->wkbType() ) )
9596+
}
9597+
9598+
QgsAttributes attrs = d.mergedAttributes();
9599+
QgsAttributeMap newAttributes;
9600+
QString errorMessage;
9601+
QgsFeatureId mergeFeatureId = FID_NULL;
9602+
for ( int i = 0; i < attrs.count(); ++i )
9603+
{
9604+
QVariant val = attrs.at( i );
9605+
bool isDefaultValue = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
9606+
vl->dataProvider() &&
9607+
vl->dataProvider()->defaultValueClause( vl->fields().fieldOriginIndex( i ) ) == val;
9608+
bool isPrimaryKey = vl->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
9609+
vl->dataProvider() &&
9610+
vl->dataProvider()->pkAttributeIndexes().contains( vl->fields().fieldOriginIndex( i ) );
9611+
9612+
if ( isPrimaryKey && !isDefaultValue )
9613+
mergeFeatureId = val.toLongLong();
9614+
9615+
// convert to destination data type
9616+
if ( !isDefaultValue && !vl->fields().at( i ).convertCompatible( val, &errorMessage ) )
95979617
{
9598-
const QgsGeometryCollection *c = qgsgeometry_cast<const QgsGeometryCollection *>( unionGeom.constGet() );
9599-
if ( ( c && c->partCount() > 1 ) || !unionGeom.convertToSingleType() )
9600-
{
9601-
visibleMessageBar()->pushMessage(
9602-
tr( "Merge failed" ),
9603-
tr( "Resulting geometry type (multipart) is incompatible with layer type (singlepart)." ),
9604-
Qgis::MessageLevel::Critical );
9605-
return;
9606-
}
9618+
visibleMessageBar()->pushMessage(
9619+
tr( "Invalid result" ),
9620+
tr( "Could not store value '%1' in field of type %2: %3" ).arg( attrs.at( i ).toString(), vl->fields().at( i ).typeName(), errorMessage ),
9621+
Qgis::MessageLevel::Warning );
96079622
}
9623+
newAttributes[ i ] = val;
96089624
}
96099625

9610-
QString errorMessage;
9611-
QgsVectorLayerEditUtils vectorLayerEditUtils( vl );
9612-
bool success = vectorLayerEditUtils.mergeFeatures( d.targetFeatureId(), vl->selectedFeatureIds(), d.mergedAttributes(), unionGeom, errorMessage );
9626+
vl->beginEditCommand( tr( "Merged features" ) );
96139627

9614-
if ( !success )
9628+
QgsFeature mergeFeature;
9629+
if ( mergeFeatureId == FID_NULL )
96159630
{
9616-
visibleMessageBar()->pushMessage(
9617-
tr( "Merge failed" ),
9618-
errorMessage,
9619-
Qgis::MessageLevel::Critical );
9631+
// Create new feature
9632+
mergeFeature = QgsVectorLayerUtils::createFeature( vl, unionGeom, newAttributes );
96209633
}
9621-
else if ( success && !errorMessage.isEmpty() )
9634+
else
96229635
{
9623-
visibleMessageBar()->pushMessage(
9624-
tr( "Invalid result" ),
9625-
errorMessage,
9626-
Qgis::MessageLevel::Warning );
9636+
// Merge into existing feature
9637+
featureIdsAfter.remove( mergeFeatureId );
96279638
}
9639+
9640+
// Delete other features
9641+
QgsFeatureIds::const_iterator feature_it = featureIdsAfter.constBegin();
9642+
for ( ; feature_it != featureIdsAfter.constEnd(); ++feature_it )
9643+
{
9644+
vl->deleteFeature( *feature_it );
9645+
}
9646+
9647+
9648+
if ( mergeFeatureId == FID_NULL )
9649+
{
9650+
// Add the new feature
9651+
vl->addFeature( mergeFeature );
9652+
}
9653+
else
9654+
{
9655+
// Modify merge feature
9656+
vl->changeGeometry( mergeFeatureId, unionGeom );
9657+
vl->changeAttributeValues( mergeFeatureId, newAttributes );
9658+
}
9659+
9660+
vl->endEditCommand();
9661+
9662+
vl->triggerRepaint();
96289663
}
96299664

96309665
void QgisApp::vertexTool()

‎src/app/qgsmergeattributesdialog.cpp

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,6 @@ QgsMergeAttributesDialog::QgsMergeAttributesDialog( const QgsFeatureList &featur
108108
break;
109109
}
110110

111-
if ( ! mFeatureList.isEmpty() )
112-
mTargetFeatureId = mFeatureList.first().id();
113-
114111
connect( mSkipAllButton, &QAbstractButton::clicked, this, &QgsMergeAttributesDialog::setAllToSkip );
115112
connect( mTableWidget, &QTableWidget::cellChanged, this, &QgsMergeAttributesDialog::tableWidgetCellChanged );
116113

@@ -468,8 +465,6 @@ void QgsMergeAttributesDialog::setAllAttributesFromFeature( QgsFeatureId feature
468465
currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "f%1" ).arg( FID_TO_STRING( featureId ) ) ) );
469466
}
470467
}
471-
472-
mTargetFeatureId = featureId;
473468
}
474469

475470
QVariant QgsMergeAttributesDialog::calcStatistic( int col, QgsStatisticalSummary::Statistic stat )
@@ -671,18 +666,12 @@ void QgsMergeAttributesDialog::mRemoveFeatureFromSelectionButton_clicked()
671666
f_it != mFeatureList.end();
672667
++f_it )
673668
{
674-
if ( f_it->id() == mTargetFeatureId )
675-
mTargetFeatureId = FID_NULL;
676-
677669
if ( f_it->id() == featureId )
678670
{
679671
mFeatureList.erase( f_it );
680672
break;
681673
}
682674
}
683-
684-
if ( mTargetFeatureId == FID_NULL && !mFeatureList.isEmpty() )
685-
mTargetFeatureId = mFeatureList.first().id();
686675
}
687676

688677
void QgsMergeAttributesDialog::tableWidgetCellChanged( int row, int column )
@@ -756,11 +745,6 @@ QgsAttributes QgsMergeAttributesDialog::mergedAttributes() const
756745
return results;
757746
}
758747

759-
QgsFeatureId QgsMergeAttributesDialog::targetFeatureId() const
760-
{
761-
return mTargetFeatureId;
762-
}
763-
764748
QSet<int> QgsMergeAttributesDialog::skippedAttributeIndexes() const
765749
{
766750
QSet<int> skipped;

‎src/app/qgsmergeattributesdialog.h

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -49,18 +49,6 @@ class APP_EXPORT QgsMergeAttributesDialog: public QDialog, private Ui::QgsMergeA
4949

5050
QgsAttributes mergedAttributes() const;
5151

52-
/**
53-
* Returns the id of the target feature.
54-
* By default it is the first feature of the list. Otherwise the feature explicitly selected
55-
* with buttons "Take attributes from selected feature" or "Take attributes from feature with
56-
* the largest area".
57-
*
58-
* \returns The id of the target feature.
59-
*
60-
* \since QGIS 3.30
61-
*/
62-
QgsFeatureId targetFeatureId() const;
63-
6452
/**
6553
* Returns a list of attribute indexes which should be skipped when merging (e.g., attributes
6654
* which have been set to "skip"
@@ -114,7 +102,6 @@ class APP_EXPORT QgsMergeAttributesDialog: public QDialog, private Ui::QgsMergeA
114102
void createRubberBandForFeature( QgsFeatureId featureId );
115103

116104
QgsFeatureList mFeatureList;
117-
QgsFeatureId mTargetFeatureId = FID_NULL;
118105
QgsVectorLayer *mVectorLayer = nullptr;
119106
QgsMapCanvas *mMapCanvas = nullptr;
120107
//! Item that highlights the selected feature in the merge table

‎src/core/vector/qgsvectorlayereditutils.cpp

Lines changed: 0 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -721,56 +721,6 @@ int QgsVectorLayerEditUtils::addTopologicalPoints( const QgsPointXY &p )
721721
return addTopologicalPoints( QgsPoint( p ) );
722722
}
723723

724-
bool QgsVectorLayerEditUtils::mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage )
725-
{
726-
errorMessage.clear();
727-
728-
if ( mergeFeatureIds.isEmpty() )
729-
{
730-
errorMessage = QObject::tr( "List of features to merge is empty" );
731-
return false;
732-
}
733-
734-
QgsAttributeMap newAttributes;
735-
for ( int i = 0; i < mergeAttributes.count(); ++i )
736-
{
737-
QVariant val = mergeAttributes.at( i );
738-
739-
bool isDefaultValue = mLayer->fields().fieldOrigin( i ) == QgsFields::OriginProvider &&
740-
mLayer->dataProvider() &&
741-
mLayer->dataProvider()->defaultValueClause( mLayer->fields().fieldOriginIndex( i ) ) == val;
742-
743-
// convert to destination data type
744-
QString errorMessageConvertCompatible;
745-
if ( !isDefaultValue && !mLayer->fields().at( i ).convertCompatible( val, &errorMessageConvertCompatible ) )
746-
{
747-
if ( errorMessage.isEmpty() )
748-
errorMessage = QObject::tr( "Could not store value '%1' in field of type %2: %3" ).arg( mergeAttributes.at( i ).toString(), mLayer->fields().at( i ).typeName(), errorMessageConvertCompatible );
749-
}
750-
newAttributes[ i ] = val;
751-
}
752-
753-
mLayer->beginEditCommand( QObject::tr( "Merged features" ) );
754-
755-
// Delete other features but the target feature
756-
QgsFeatureIds::const_iterator feature_it = mergeFeatureIds.constBegin();
757-
for ( ; feature_it != mergeFeatureIds.constEnd(); ++feature_it )
758-
{
759-
if ( *feature_it != targetFeatureId )
760-
mLayer->deleteFeature( *feature_it );
761-
}
762-
763-
// Modify merge feature
764-
QgsGeometry mergeGeometry = unionGeometry;
765-
mLayer->changeGeometry( targetFeatureId, mergeGeometry );
766-
mLayer->changeAttributeValues( targetFeatureId, newAttributes );
767-
768-
mLayer->endEditCommand();
769-
770-
mLayer->triggerRepaint();
771-
772-
return true;
773-
}
774724

775725
bool QgsVectorLayerEditUtils::boundingBoxFromPointList( const QgsPointSequence &list, double &xmin, double &ymin, double &xmax, double &ymax ) const
776726
{

‎src/core/vector/qgsvectorlayereditutils.h

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -268,20 +268,6 @@ class CORE_EXPORT QgsVectorLayerEditUtils
268268
*/
269269
int addTopologicalPoints( const QgsPointSequence &ps );
270270

271-
/**
272-
* Merge features into a single one.
273-
* \param targetFeatureId id of the target feature (will be updated)
274-
* \param mergeFeatureIds id list of features to merge (will be deleted)
275-
* \param mergeAttributes are the resulting attributes in the merged feature
276-
* \param unionGeometry is the resulting geometry of the merged feature
277-
* \param errorMessage will be set to a descriptive error message if any occurs
278-
*
279-
* \returns TRUE if the merge was successful, or FALSE if the operation failed.
280-
*
281-
* \since QGIS 3.30
282-
*/
283-
bool mergeFeatures( const QgsFeatureId &targetFeatureId, const QgsFeatureIds &mergeFeatureIds, const QgsAttributes &mergeAttributes, const QgsGeometry &unionGeometry, QString &errorMessage SIP_OUT );
284-
285271
private:
286272

287273
/**

‎tests/src/app/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,6 @@ set(TESTS
4646
testqgsmeshcalculatordialog.cpp
4747
testqgsgpsinformationwidget.cpp
4848
testqgslabelpropertydialog.cpp
49-
testqgsmergeattributesdialog.cpp
5049
)
5150

5251
if (HAVE_GEOREFERENCER)

‎tests/src/app/testqgsmergeattributesdialog.cpp

Lines changed: 0 additions & 97 deletions
This file was deleted.

‎tests/src/python/test_qgsvectorlayereditutils.py

Lines changed: 1 addition & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -14,25 +14,15 @@
1414

1515
import qgis # NOQA
1616

17-
import tempfile
18-
import os
19-
20-
from qgis.PyQt.QtCore import QVariant
2117

2218
from qgis.core import (Qgis,
2319
QgsFeature,
24-
QgsField,
25-
QgsFields,
2620
QgsGeometry,
2721
QgsLineString,
2822
QgsPoint,
2923
QgsPointXY,
30-
QgsCoordinateReferenceSystem,
31-
QgsCoordinateTransformContext,
3224
QgsVectorLayer,
33-
QgsVectorLayerEditUtils,
34-
QgsVectorFileWriter,
35-
QgsWkbTypes)
25+
QgsVectorLayerEditUtils)
3626

3727

3828
from qgis.testing import start_app, unittest
@@ -327,98 +317,6 @@ def testSplitParts(self):
327317
'MultiPolygon (((2 12, 2 16, 4 16, 4 12, 2 12)),((2 16, 2 12, 0 12, 0 16, 2 16)),((6 12, 10 12, 10 16, 6 16, 6 12)))'
328318
)
329319

330-
def testMergeFeatures(self):
331-
layer = createEmptyPolygonLayer()
332-
self.assertTrue(layer.startEditing())
333-
layer.addAttribute(QgsField('name', QVariant.String))
334-
pr = layer.dataProvider()
335-
336-
f1 = QgsFeature(layer.fields(), 1)
337-
f1.setGeometry(QgsGeometry.fromWkt('POLYGON((0 0, 5 0, 5 5, 0 5, 0 0))'))
338-
f1.setAttribute('name', 'uno')
339-
assert pr.addFeatures([f1])
340-
self.assertEqual(layer.featureCount(), 1)
341-
342-
f2 = QgsFeature(layer.fields(), 2)
343-
f2.setGeometry(QgsGeometry.fromWkt('POLYGON((3 3, 8 3, 8 8, 3 8, 3 3))'))
344-
f2.setAttribute('name', 'due')
345-
assert pr.addFeatures([f2])
346-
self.assertEqual(layer.featureCount(), 2)
347-
348-
featureIds = [f1.id(), f2.id()]
349-
350-
unionGeom = f1.geometry()
351-
unionGeom = unionGeom.combine(f2.geometry())
352-
self.assertFalse(unionGeom.isNull())
353-
354-
vle = QgsVectorLayerEditUtils(layer)
355-
success, errorMessage = vle.mergeFeatures(f1.id(), featureIds, ['tre'], unionGeom)
356-
self.assertFalse(errorMessage)
357-
self.assertTrue(success)
358-
359-
layer.commitChanges()
360-
361-
self.assertEqual(layer.featureCount(), 1)
362-
mergedFeature = next(layer.getFeatures())
363-
self.assertEqual(
364-
mergedFeature.geometry().asWkt(),
365-
"Polygon ((5 0, 0 0, 0 5, 3 5, 3 8, 8 8, 8 3, 5 3, 5 0))"
366-
)
367-
self.assertEqual(mergedFeature.attribute('name'), 'tre')
368-
369-
def testMergeFeaturesIntoExisting(self):
370-
tempgpkg = os.path.join(tempfile.mkdtemp(), 'testMergeFeaturesIntoExisting.gpkg')
371-
fields = QgsFields()
372-
fields.append(QgsField('name', QVariant.String))
373-
374-
crs = QgsCoordinateReferenceSystem('epsg:4326')
375-
options = QgsVectorFileWriter.SaveVectorOptions()
376-
options.driverName = "GPKG"
377-
QgsVectorFileWriter.create(
378-
fileName=tempgpkg,
379-
fields=fields,
380-
geometryType=QgsWkbTypes.Polygon,
381-
srs=crs,
382-
transformContext=QgsCoordinateTransformContext(),
383-
options=options)
384-
385-
layer = QgsVectorLayer(tempgpkg, 'my_layer', 'ogr')
386-
self.assertTrue(layer.startEditing())
387-
pr = layer.dataProvider()
388-
389-
f1 = QgsFeature(layer.fields(), 1)
390-
f1.setGeometry(QgsGeometry.fromWkt('POLYGON((0 0, 5 0, 5 5, 0 5, 0 0))'))
391-
f1.setAttribute('name', 'uno')
392-
assert pr.addFeatures([f1])
393-
self.assertEqual(layer.featureCount(), 1)
394-
395-
f2 = QgsFeature(layer.fields(), 2)
396-
f2.setGeometry(QgsGeometry.fromWkt('POLYGON((3 3, 8 3, 8 8, 3 8, 3 3))'))
397-
f2.setAttribute('name', 'due')
398-
assert pr.addFeatures([f2])
399-
self.assertEqual(layer.featureCount(), 2)
400-
401-
featureIds = [f1.id(), f2.id()]
402-
403-
unionGeom = f1.geometry()
404-
unionGeom = unionGeom.combine(f2.geometry())
405-
self.assertFalse(unionGeom.isNull())
406-
407-
vle = QgsVectorLayerEditUtils(layer)
408-
success, errorMessage = vle.mergeFeatures(f2.id(), featureIds, [2, 'tre'], unionGeom)
409-
self.assertFalse(errorMessage)
410-
self.assertTrue(success)
411-
412-
layer.commitChanges()
413-
414-
self.assertEqual(layer.featureCount(), 1)
415-
mergedFeature = layer.getFeature(2)
416-
self.assertEqual(
417-
mergedFeature.geometry().asWkt(),
418-
"Polygon ((5 0, 0 0, 0 5, 3 5, 3 8, 8 8, 8 3, 5 3, 5 0))"
419-
)
420-
self.assertEqual(mergedFeature.attribute('name'), 'tre')
421-
422320

423321
if __name__ == '__main__':
424322
unittest.main()

0 commit comments

Comments
 (0)
Please sign in to comment.