diff --git a/python/PyQt6/core/auto_additions/qgis.py b/python/PyQt6/core/auto_additions/qgis.py index 3f0acb5a87d7..791f3727a046 100644 --- a/python/PyQt6/core/auto_additions/qgis.py +++ b/python/PyQt6/core/auto_additions/qgis.py @@ -6928,6 +6928,11 @@ Qgis.FieldDomainMergePolicy.DefaultValue.__doc__ = "Use default field value" Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" +Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SetToNull.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6938,6 +6943,26 @@ * ``DefaultValue``: Use default field value * ``Sum``: Sum of values * ``GeometryWeighted``: New values are computed as the weighted average of the source values +* ``UnsetField``: Clears the field value so that the data provider backend will populate using any backend triggers or similar logic + + .. versionadded:: 3.44 + +* ``LargestGeometry``: Use value from the feature with the largest geometry + + .. versionadded:: 3.44 + +* ``MinimumValue``: Use the minimum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``MaximumValue``: Use the maximum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``SetToNull``: Use a null value + + .. versionadded:: 3.44 + """ # -- diff --git a/python/PyQt6/core/auto_generated/qgis.sip.in b/python/PyQt6/core/auto_generated/qgis.sip.in index 2c8ae9e630eb..3097b5eea976 100644 --- a/python/PyQt6/core/auto_generated/qgis.sip.in +++ b/python/PyQt6/core/auto_generated/qgis.sip.in @@ -2112,6 +2112,11 @@ The development version DefaultValue, Sum, GeometryWeighted, + UnsetField, + LargestGeometry, + MinimumValue, + MaximumValue, + SetToNull, }; enum class FieldDuplicatePolicy /BaseType=IntEnum/ diff --git a/python/PyQt6/core/auto_generated/qgsfield.sip.in b/python/PyQt6/core/auto_generated/qgsfield.sip.in index ea73cfbb6cb7..4ac9a664e153 100644 --- a/python/PyQt6/core/auto_generated/qgsfield.sip.in +++ b/python/PyQt6/core/auto_generated/qgsfield.sip.in @@ -531,6 +531,26 @@ be handled during a duplicate operation. .. seealso:: :py:func:`duplicatePolicy` .. versionadded:: 3.38 +%End + + Qgis::FieldDomainMergePolicy mergePolicy() const /HoldGIL/; +%Docstring +Returns the field's merge policy, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`setMergePolicy` + +.. versionadded:: 3.44 +%End + + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) /HoldGIL/; +%Docstring +Sets the field's merge ``policy``, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`mergePolicy` + +.. versionadded:: 3.44 %End SIP_PYOBJECT __repr__(); diff --git a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in index c9924db14036..9288f7e74cfe 100644 --- a/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/PyQt6/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1953,6 +1953,27 @@ Sets a duplicate ``policy`` for the field with the specified index. } %End + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); +%Docstring +Sets a merge ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.44 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/python/core/auto_additions/qgis.py b/python/core/auto_additions/qgis.py index a45fb29ec11d..1f7746ded7f7 100644 --- a/python/core/auto_additions/qgis.py +++ b/python/core/auto_additions/qgis.py @@ -6862,6 +6862,11 @@ Qgis.FieldDomainMergePolicy.DefaultValue.__doc__ = "Use default field value" Qgis.FieldDomainMergePolicy.Sum.__doc__ = "Sum of values" Qgis.FieldDomainMergePolicy.GeometryWeighted.__doc__ = "New values are computed as the weighted average of the source values" +Qgis.FieldDomainMergePolicy.UnsetField.__doc__ = "Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.LargestGeometry.__doc__ = "Use value from the feature with the largest geometry \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MinimumValue.__doc__ = "Use the minimum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.MaximumValue.__doc__ = "Use the maximum value from the features-to-be-merged \n.. versionadded:: 3.44" +Qgis.FieldDomainMergePolicy.SetToNull.__doc__ = "Use a null value \n.. versionadded:: 3.44" Qgis.FieldDomainMergePolicy.__doc__ = """Merge policy for field domains. When a feature is built by merging multiple features, defines how the value of @@ -6872,6 +6877,26 @@ * ``DefaultValue``: Use default field value * ``Sum``: Sum of values * ``GeometryWeighted``: New values are computed as the weighted average of the source values +* ``UnsetField``: Clears the field value so that the data provider backend will populate using any backend triggers or similar logic + + .. versionadded:: 3.44 + +* ``LargestGeometry``: Use value from the feature with the largest geometry + + .. versionadded:: 3.44 + +* ``MinimumValue``: Use the minimum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``MaximumValue``: Use the maximum value from the features-to-be-merged + + .. versionadded:: 3.44 + +* ``SetToNull``: Use a null value + + .. versionadded:: 3.44 + """ # -- diff --git a/python/core/auto_generated/qgis.sip.in b/python/core/auto_generated/qgis.sip.in index f6e2c40a25db..ab61ce224161 100644 --- a/python/core/auto_generated/qgis.sip.in +++ b/python/core/auto_generated/qgis.sip.in @@ -2112,6 +2112,11 @@ The development version DefaultValue, Sum, GeometryWeighted, + UnsetField, + LargestGeometry, + MinimumValue, + MaximumValue, + SetToNull, }; enum class FieldDuplicatePolicy diff --git a/python/core/auto_generated/qgsfield.sip.in b/python/core/auto_generated/qgsfield.sip.in index ea73cfbb6cb7..4ac9a664e153 100644 --- a/python/core/auto_generated/qgsfield.sip.in +++ b/python/core/auto_generated/qgsfield.sip.in @@ -531,6 +531,26 @@ be handled during a duplicate operation. .. seealso:: :py:func:`duplicatePolicy` .. versionadded:: 3.38 +%End + + Qgis::FieldDomainMergePolicy mergePolicy() const /HoldGIL/; +%Docstring +Returns the field's merge policy, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`setMergePolicy` + +.. versionadded:: 3.44 +%End + + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) /HoldGIL/; +%Docstring +Sets the field's merge ``policy``, which indicates how field values should +be handled during a merge operation. + +.. seealso:: :py:func:`mergePolicy` + +.. versionadded:: 3.44 %End SIP_PYOBJECT __repr__(); diff --git a/python/core/auto_generated/vector/qgsvectorlayer.sip.in b/python/core/auto_generated/vector/qgsvectorlayer.sip.in index c9924db14036..9288f7e74cfe 100644 --- a/python/core/auto_generated/vector/qgsvectorlayer.sip.in +++ b/python/core/auto_generated/vector/qgsvectorlayer.sip.in @@ -1953,6 +1953,27 @@ Sets a duplicate ``policy`` for the field with the specified index. } %End + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); +%Docstring +Sets a merge ``policy`` for the field with the specified index. + +:raises KeyError: if no field with the specified index exists + +.. versionadded:: 3.44 +%End + +%MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } +%End + QSet excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/; %Docstring A set of attributes that are not advertised in WMS requests with QGIS server. diff --git a/src/app/browser/qgsinbuiltdataitemproviders.cpp b/src/app/browser/qgsinbuiltdataitemproviders.cpp index 121d0bce532d..d2b54c1b44d4 100644 --- a/src/app/browser/qgsinbuiltdataitemproviders.cpp +++ b/src/app/browser/qgsinbuiltdataitemproviders.cpp @@ -2244,6 +2244,21 @@ QString QgsFieldDomainDetailsWidget::htmlMetadata( QgsFieldDomain *domain, const case Qgis::FieldDomainMergePolicy::GeometryWeighted: metadata += tr( "Use geometry weighted value" ); break; + case Qgis::FieldDomainMergePolicy::UnsetField: + metadata += tr( "Unset field" ); + break; + case Qgis::FieldDomainMergePolicy::LargestGeometry: + metadata += tr( "Largest geometry" ); + break; + case Qgis::FieldDomainMergePolicy::MaximumValue: + metadata += tr( "Maximum value" ); + break; + case Qgis::FieldDomainMergePolicy::MinimumValue: + metadata += tr( "Minimum value" ); + break; + case Qgis::FieldDomainMergePolicy::SetToNull: + metadata += tr( "Skip attribute" ); + break; } metadata += QLatin1String( "\n

" ); diff --git a/src/app/qgisapp.cpp b/src/app/qgisapp.cpp index 03cb1fe9107d..efd0bb335405 100644 --- a/src/app/qgisapp.cpp +++ b/src/app/qgisapp.cpp @@ -9451,9 +9451,8 @@ void QgisApp::mergeAttributesOfSelectedFeatures() QgsFeatureList featureList = vl->selectedFeatures(); //merge the attributes together - QgsMergeAttributesDialog d( featureList, vl, mapCanvas() ); - //initialize dialog with all columns set to skip - d.setAllToSkip(); + QgsMergeAttributesDialog d( featureList, vl, mapCanvas(), true ); + if ( d.exec() == QDialog::Rejected ) { return; diff --git a/src/app/qgsmergeattributesdialog.cpp b/src/app/qgsmergeattributesdialog.cpp index c6faed8be5b6..2bde12ee5f44 100644 --- a/src/app/qgsmergeattributesdialog.cpp +++ b/src/app/qgsmergeattributesdialog.cpp @@ -50,7 +50,7 @@ const QList QgsMergeAttributesDialog::DISPLAY_STATS = QListverticalHeader(); if ( verticalHeader ) @@ -149,7 +153,7 @@ void QgsMergeAttributesDialog::setAttributeTableConfig( const QgsAttributeTableC } } -void QgsMergeAttributesDialog::createTableWidgetContents() +void QgsMergeAttributesDialog::createTableWidgetContents( bool skipAll ) { //get information about attributes from vector layer if ( !mVectorLayer ) @@ -238,29 +242,163 @@ void QgsMergeAttributesDialog::createTableWidgetContents() //initially set any fields with default values/default value clauses to that value for ( int j = 0; j < mTableWidget->columnCount(); j++ ) { + if ( skipAll ) + break; + int idx = mTableWidget->horizontalHeaderItem( j )->data( FieldIndex ).toInt(); bool setToManual = false; - if ( !mVectorLayer->dataProvider()->defaultValueClause( idx ).isEmpty() ) - { - QVariant v = mVectorLayer->dataProvider()->defaultValueClause( idx ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); - mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); - setToManual = true; - } - else + const QgsField field = mVectorLayer->fields().at( idx ); + QComboBox *currentComboBox = qobject_cast( mTableWidget->cellWidget( 0, j ) ); + + switch ( field.mergePolicy() ) { - QVariant v = mVectorLayer->dataProvider()->defaultValue( idx ); - if ( v.isValid() ) + case Qgis::FieldDomainMergePolicy::Sum: { + if ( !field.isNumeric() ) + break; + + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Sum ) ) ); + break; + } + + case Qgis::FieldDomainMergePolicy::DefaultValue: + { + // create a dummy feature with the combined geometry in case the default value expression uses the geometry. + // however populating the feature's fields is problematic because the values haven't been set yet and + // generating them at this point isn't possible since the expression might refer to another field + // with a default value expression leading to conflicts + QgsFeature f; + + QVector geoms; + for ( const QgsFeature &f : mFeatureList ) + geoms << f.geometry(); + + const QgsGeometry mergedGeom = QgsGeometry::unaryUnion( geoms ); + f.setGeometry( mergedGeom ); + + const QVariant v = mVectorLayer->defaultValue( idx, f ); + + if ( !v.isValid() ) + break; + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); setToManual = true; + break; + } + + case Qgis::FieldDomainMergePolicy::GeometryWeighted: + { + if ( !field.isNumeric() || mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + break; + + QVector geoms; + for ( const QgsFeature &f : mFeatureList ) + geoms << f.geometry(); + + const QgsGeometry mergedGeom = QgsGeometry::unaryUnion( geoms ); + const double mergedSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? mergedGeom.area() : mergedGeom.length(); + + const double value = std::accumulate( mFeatureList.constBegin(), mFeatureList.constEnd(), 0.0, [&, idx]( double sum, const QgsFeature &f ) { + const double geomSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? f.geometry().area() : f.geometry().length(); + const double weightMultiplier = geomSize / mergedSize; + return sum + ( f.attribute( idx ).toDouble() * weightMultiplier ); + } ); + + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, value ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, value ); + setToManual = true; + + break; + } + + case Qgis::FieldDomainMergePolicy::UnsetField: + { + if ( !mVectorLayer->dataProvider()->defaultValueClause( idx ).isEmpty() ) + { + QVariant v = mVectorLayer->dataProvider()->defaultValueClause( idx ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); + setToManual = true; + } + else + { + QVariant v = mVectorLayer->dataProvider()->defaultValue( idx ); + if ( v.isValid() ) + { + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::DisplayRole, v ); + mTableWidget->item( mTableWidget->rowCount() - 1, j )->setData( Qt::UserRole, v ); + setToManual = true; + } + } + break; } + + case Qgis::FieldDomainMergePolicy::LargestGeometry: + { + if ( mVectorLayer->geometryType() == Qgis::GeometryType::Unknown || mVectorLayer->geometryType() == Qgis::GeometryType::Null ) + break; + + QgsFeatureId largestFeatureId = FID_NULL; + + if ( mVectorLayer->geometryType() == Qgis::GeometryType::Point ) + { + QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), []( const QgsFeature &a, const QgsFeature &b ) -> bool { + return a.geometry().constGet()->partCount() < b.geometry().constGet()->partCount(); + } ); + + largestFeatureId = largestSelectedFeature->id(); + } + else + { + std::function getSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length; + + QList::iterator largestSelectedFeature = std::max_element( mFeatureList.begin(), mFeatureList.end(), [&getSize]( const QgsFeature &a, const QgsFeature &b ) -> bool { + return getSize( a.geometry() ) < getSize( b.geometry() ); + } ); + + largestFeatureId = largestSelectedFeature->id(); + } + + if ( largestFeatureId == FID_NULL ) + break; + + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "f%1" ).arg( FID_TO_STRING( largestFeatureId ) ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::MinimumValue: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Min ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::MaximumValue: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast( Qgis::Statistic::Max ) ) ); + + break; + } + + case Qgis::FieldDomainMergePolicy::SetToNull: + { + if ( currentComboBox ) + currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "skip" ) ) ); + + break; + } + break; } + if ( setToManual ) { - QComboBox *currentComboBox = qobject_cast( mTableWidget->cellWidget( 0, j ) ); if ( currentComboBox ) { currentComboBox->blockSignals( true ); diff --git a/src/app/qgsmergeattributesdialog.h b/src/app/qgsmergeattributesdialog.h index 6b3eccaceb9a..bebcf915c4cc 100644 --- a/src/app/qgsmergeattributesdialog.h +++ b/src/app/qgsmergeattributesdialog.h @@ -42,7 +42,7 @@ class APP_EXPORT QgsMergeAttributesDialog : public QDialog, private Ui::QgsMerge }; - QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); + QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, bool skipAll = false, QWidget *parent = nullptr, Qt::WindowFlags f = Qt::WindowFlags() ); ~QgsMergeAttributesDialog() override; QgsAttributes mergedAttributes() const; @@ -82,7 +82,7 @@ class APP_EXPORT QgsMergeAttributesDialog : public QDialog, private Ui::QgsMerge private: QgsMergeAttributesDialog(); //default constructor forbidden - void createTableWidgetContents(); + void createTableWidgetContents( bool skipAll ); void setAttributeTableConfig( const QgsAttributeTableConfig &config ); //! Create new combo box with the options for featureXX / mean / min / max diff --git a/src/core/qgis.h b/src/core/qgis.h index 701dfaa6d32f..9adfc6b0cdcf 100644 --- a/src/core/qgis.h +++ b/src/core/qgis.h @@ -3729,6 +3729,11 @@ class CORE_EXPORT Qgis DefaultValue, //!< Use default field value Sum, //!< Sum of values GeometryWeighted, //!< New values are computed as the weighted average of the source values + UnsetField, //!< Clears the field value so that the data provider backend will populate using any backend triggers or similar logic \since QGIS 3.44 + LargestGeometry, //!< Use value from the feature with the largest geometry \since QGIS 3.44 + MinimumValue, //!< Use the minimum value from the features-to-be-merged \since QGIS 3.44 + MaximumValue, //!< Use the maximum value from the features-to-be-merged \since QGIS 3.44 + SetToNull, //!< Use a null value \since QGIS 3.44 }; Q_ENUM( FieldDomainMergePolicy ) diff --git a/src/core/qgsfield.cpp b/src/core/qgsfield.cpp index 056bd9b4afc3..fddade799a93 100644 --- a/src/core/qgsfield.cpp +++ b/src/core/qgsfield.cpp @@ -772,6 +772,16 @@ void QgsField::setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) d->duplicatePolicy = policy; } +Qgis::FieldDomainMergePolicy QgsField::mergePolicy() const +{ + return d->mergePolicy; +} + +void QgsField::setMergePolicy( Qgis::FieldDomainMergePolicy policy ) +{ + d->mergePolicy = policy; +} + /*************************************************************************** * This class is considered CRITICAL and any change MUST be accompanied with * full unit tests in testqgsfield.cpp. diff --git a/src/core/qgsfield.h b/src/core/qgsfield.h index 19bac8e57963..ba73a978fb1b 100644 --- a/src/core/qgsfield.h +++ b/src/core/qgsfield.h @@ -545,6 +545,26 @@ class CORE_EXPORT QgsField */ void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ) SIP_HOLDGIL; + /** + * Returns the field's merge policy, which indicates how field values should + * be handled during a merge operation. + * + * \see setMergePolicy() + * + * \since QGIS 3.44 + */ + Qgis::FieldDomainMergePolicy mergePolicy() const SIP_HOLDGIL; + + /** + * Sets the field's merge \a policy, which indicates how field values should + * be handled during a merge operation. + * + * \see mergePolicy() + * + * \since QGIS 3.44 + */ + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ) SIP_HOLDGIL; + #ifdef SIP_RUN SIP_PYOBJECT __repr__(); % MethodCode diff --git a/src/core/qgsfield_p.h b/src/core/qgsfield_p.h index 54d25c2bca8b..6ee0e9a8fda1 100644 --- a/src/core/qgsfield_p.h +++ b/src/core/qgsfield_p.h @@ -85,6 +85,7 @@ class QgsFieldPrivate : public QSharedData , editorWidgetSetup( other.editorWidgetSetup ) , splitPolicy( other.splitPolicy ) , duplicatePolicy( other.duplicatePolicy ) + , mergePolicy( other.mergePolicy ) , isReadOnly( other.isReadOnly ) { } @@ -101,6 +102,7 @@ class QgsFieldPrivate : public QSharedData && ( constraints == other.constraints ) && ( flags == other.flags ) && ( splitPolicy == other.splitPolicy ) && ( duplicatePolicy == other.duplicatePolicy ) + && ( mergePolicy == other.mergePolicy ) && ( isReadOnly == other.isReadOnly ) && ( editorWidgetSetup == other.editorWidgetSetup ) ); } @@ -149,6 +151,9 @@ class QgsFieldPrivate : public QSharedData //! Duplicate policy Qgis::FieldDuplicatePolicy duplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; + //! Merge policy + Qgis::FieldDomainMergePolicy mergePolicy = Qgis::FieldDomainMergePolicy::UnsetField; + //! Read-only bool isReadOnly = false; diff --git a/src/core/qgsogrutils.cpp b/src/core/qgsogrutils.cpp index e376b8a2432d..dcadd00cd563 100644 --- a/src/core/qgsogrutils.cpp +++ b/src/core/qgsogrutils.cpp @@ -2298,6 +2298,14 @@ OGRFieldDomainH QgsOgrUtils::convertFieldDomain( const QgsFieldDomain *domain ) case Qgis::FieldDomainMergePolicy::Sum: OGR_FldDomain_SetMergePolicy( res, OFDMP_SUM ); break; + + case Qgis::FieldDomainMergePolicy::UnsetField: + case Qgis::FieldDomainMergePolicy::LargestGeometry: + case Qgis::FieldDomainMergePolicy::MinimumValue: + case Qgis::FieldDomainMergePolicy::MaximumValue: + case Qgis::FieldDomainMergePolicy::SetToNull: + // not supported + break; } switch ( domain->splitPolicy() ) diff --git a/src/core/vector/qgsvectorlayer.cpp b/src/core/vector/qgsvectorlayer.cpp index a08758adeaa2..25fe70e60117 100644 --- a/src/core/vector/qgsvectorlayer.cpp +++ b/src/core/vector/qgsvectorlayer.cpp @@ -2245,6 +2245,10 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv { mAttributeDuplicatePolicy[ field.name() ] = field.duplicatePolicy(); } + if ( !mAttributeMergePolicy.contains( field.name() ) ) + { + mAttributeMergePolicy[ field.name() ] = field.mergePolicy(); + } } if ( profile ) @@ -2588,6 +2592,19 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes } } + const QDomNode mergePoliciesNode = layerNode.namedItem( QStringLiteral( "mergePolicies" ) ); + if ( !mergePoliciesNode.isNull() ) + { + const QDomNodeList mergePolicyNodeList = mergePoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) ); + for ( int i = 0; i < mergePolicyNodeList.size(); ++i ) + { + const QDomElement mergePolicyElem = mergePolicyNodeList.at( i ).toElement(); + const QString field = mergePolicyElem.attribute( QStringLiteral( "field" ) ); + const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::DefaultValue ); + mAttributeMergePolicy.insert( field, policy ); + } + } + // default expressions mDefaultExpressionMap.clear(); QDomNode defaultsNode = layerNode.namedItem( QStringLiteral( "defaults" ) ); @@ -3122,6 +3139,19 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString node.appendChild( duplicatePoliciesElement ); } + //merge policies + { + QDomElement mergePoliciesElement = doc.createElement( QStringLiteral( "mergePolicies" ) ); + for ( const QgsField &field : std::as_const( mFields ) ) + { + QDomElement mergePolicyElem = doc.createElement( QStringLiteral( "policy" ) ); + mergePolicyElem.setAttribute( QStringLiteral( "field" ), field.name() ); + mergePolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) ); + mergePoliciesElement.appendChild( mergePolicyElem ); + } + node.appendChild( mergePoliciesElement ); + } + //default expressions QDomElement defaultsElem = doc.createElement( QStringLiteral( "defaults" ) ); for ( const QgsField &field : std::as_const( mFields ) ) @@ -3641,6 +3671,20 @@ void QgsVectorLayer::setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePol emit layerModified(); // TODO[MD]: should have a different signal? } +void QgsVectorLayer::setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ) +{ + QGIS_PROTECT_QOBJECT_THREAD_ACCESS + + if ( index < 0 || index >= fields().count() ) + return; + + const QString name = fields().at( index ).name(); + + mAttributeMergePolicy.insert( name, policy ); + mFields[ index ].setMergePolicy( policy ); + mEditFormConfig.setFields( mFields ); + emit layerModified(); // TODO[MD]: should have a different signal? +} QSet QgsVectorLayer::excludeAttributesWms() const { @@ -4523,6 +4567,15 @@ void QgsVectorLayer::updateFields() mFields[ index ].setDuplicatePolicy( duplicatePolicyIt.value() ); } + for ( auto mergePolicyIt = mAttributeMergePolicy.constBegin(); mergePolicyIt != mAttributeMergePolicy.constEnd(); ++mergePolicyIt ) + { + int index = mFields.lookupField( mergePolicyIt.key() ); + if ( index < 0 ) + continue; + + mFields[ index ].setMergePolicy( mergePolicyIt.value() ); + } + // Update configuration flags QMap< QString, Qgis::FieldConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin(); for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt ) diff --git a/src/core/vector/qgsvectorlayer.h b/src/core/vector/qgsvectorlayer.h index 9d92063ceaa5..c819ff48e199 100644 --- a/src/core/vector/qgsvectorlayer.h +++ b/src/core/vector/qgsvectorlayer.h @@ -1851,6 +1851,13 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte * \since QGIS 3.38 */ void setFieldDuplicatePolicy( int index, Qgis::FieldDuplicatePolicy policy ); + + /** + * Sets a merge \a policy for the field with the specified index. + * + * \since QGIS 3.44 + */ + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); #else /** @@ -1892,6 +1899,26 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte sipCpp->setFieldDuplicatePolicy( a0, a1 ); } % End + + /** + * Sets a merge \a policy for the field with the specified index. + * + * \throws KeyError if no field with the specified index exists + * \since QGIS 3.44 + */ + void setFieldMergePolicy( int index, Qgis::FieldDomainMergePolicy policy ); + + % MethodCode + if ( a0 < 0 || a0 >= sipCpp->fields().count() ) + { + PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) ); + sipIsErr = 1; + } + else + { + sipCpp->setFieldMergePolicy( a0, a1 ); + } + % End #endif /** @@ -2909,6 +2936,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte //! Map that stores the duplicate policy for attributes QMap< QString, Qgis::FieldDuplicatePolicy > mAttributeDuplicatePolicy; + //! Map that stores the merge policy for attributes + QMap< QString, Qgis::FieldDomainMergePolicy > mAttributeMergePolicy; + //! An internal structure to keep track of fields that have a defaultValueOnUpdate QSet mDefaultValueOnUpdateFields; diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.cpp b/src/gui/attributeformconfig/qgsattributetypedialog.cpp index 2183a2c7b085..040682fc0d1e 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.cpp +++ b/src/gui/attributeformconfig/qgsattributetypedialog.cpp @@ -126,6 +126,34 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx mDuplicatePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::UnsetField ) ); connect( mDuplicatePolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateDuplicatePolicyLabel ); updateDuplicatePolicyLabel(); + + if ( mLayer->isSpatial() ) + { + mMergePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::UnsetField ) ); + mMergePolicyComboBox->addItem( tr( "Use Default Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::DefaultValue ) ); + + if ( mLayer->fields().at( mFieldIdx ).isNumeric() ) + { + mMergePolicyComboBox->addItem( tr( "Use Sum" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::Sum ) ); + mMergePolicyComboBox->addItem( tr( "Use Maximum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MaximumValue ) ); + mMergePolicyComboBox->addItem( tr( "Use Minimum Value" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::MinimumValue ) ); + + if ( mLayer->geometryType() != Qgis::GeometryType::Point ) + mMergePolicyComboBox->addItem( tr( "Use Average Weighted by Geometry" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::GeometryWeighted ) ); + } + + mMergePolicyComboBox->addItem( tr( "Use Largest Feature" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::LargestGeometry ) ); + mMergePolicyComboBox->addItem( tr( "Skip Attribute" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SetToNull ) ); + } + else + { + mMergePolicyComboBox->setEnabled( false ); + mMergePolicyLabel->setEnabled( false ); + mMergePolicyDescriptionLabel->hide(); + } + + connect( mMergePolicyComboBox, qOverload( &QComboBox::currentIndexChanged ), this, &QgsAttributeTypeDialog::updateMergePolicyLabel ); + updateMergePolicyLabel(); } QgsAttributeTypeDialog::~QgsAttributeTypeDialog() @@ -400,6 +428,17 @@ void QgsAttributeTypeDialog::setDuplicatePolicy( Qgis::FieldDuplicatePolicy poli updateSplitPolicyLabel(); } +void QgsAttributeTypeDialog::setMergePolicy( Qgis::FieldDomainMergePolicy policy ) +{ + mMergePolicyComboBox->setCurrentIndex( mMergePolicyComboBox->findData( QVariant::fromValue( policy ) ) ); + updateMergePolicyLabel(); +} + +Qgis::FieldDomainMergePolicy QgsAttributeTypeDialog::mergePolicy() const +{ + return mMergePolicyComboBox->currentData().value(); +} + QString QgsAttributeTypeDialog::constraintExpression() const { return constraintExpressionWidget->asExpression(); @@ -537,6 +576,46 @@ void QgsAttributeTypeDialog::updateDuplicatePolicyLabel() mDuplicatePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); } +void QgsAttributeTypeDialog::updateMergePolicyLabel() +{ + QString helperText; + switch ( mMergePolicyComboBox->currentData().value() ) + { + case Qgis::FieldDomainMergePolicy::DefaultValue: + helperText = tr( "Use default field value." ); + break; + + case Qgis::FieldDomainMergePolicy::Sum: + helperText = tr( "Sum of values." ); + break; + + case Qgis::FieldDomainMergePolicy::GeometryWeighted: + helperText = tr( "New values are computed as the weighted average of the source values." ); + break; + + case Qgis::FieldDomainMergePolicy::UnsetField: + helperText = tr( "Clears the field to an unset state." ); + break; + + case Qgis::FieldDomainMergePolicy::LargestGeometry: + helperText = tr( "Use value from feature with the largest geometry." ); + break; + + case Qgis::FieldDomainMergePolicy::MinimumValue: + helperText = tr( "Use the lowest value from the selected features." ); + break; + + case Qgis::FieldDomainMergePolicy::MaximumValue: + helperText = tr( "Use the highest value from the selected features." ); + break; + + case Qgis::FieldDomainMergePolicy::SetToNull: + helperText = tr( "Skip attribute." ); + break; + } + mMergePolicyDescriptionLabel->setText( QStringLiteral( "%1" ).arg( helperText ) ); +} + QStandardItem *QgsAttributeTypeDialog::currentItem() const { QStandardItemModel *widgetTypeModel = qobject_cast( mWidgetTypeComboBox->model() ); diff --git a/src/gui/attributeformconfig/qgsattributetypedialog.h b/src/gui/attributeformconfig/qgsattributetypedialog.h index 8b18c88911a7..88e8b9ec80f5 100644 --- a/src/gui/attributeformconfig/qgsattributetypedialog.h +++ b/src/gui/attributeformconfig/qgsattributetypedialog.h @@ -271,6 +271,24 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu */ void setDuplicatePolicy( Qgis::FieldDuplicatePolicy policy ); + /** + * Returns the field's merge policy. + * + * \see setMergePolicy() + * + * \since QGIS 3.44 + */ + Qgis::FieldDomainMergePolicy mergePolicy() const; + + /** + * Sets the field's merge policy. + * + * \see mergePolicy() + * + * \since QGIS 3.44 + */ + void setMergePolicy( Qgis::FieldDomainMergePolicy policy ); + private slots: /** @@ -285,6 +303,8 @@ class GUI_EXPORT QgsAttributeTypeDialog : public QWidget, private Ui::QgsAttribu void updateDuplicatePolicyLabel(); + void updateMergePolicyLabel(); + private: QgsVectorLayer *mLayer = nullptr; int mFieldIdx; diff --git a/src/gui/vector/qgsattributesformproperties.cpp b/src/gui/vector/qgsattributesformproperties.cpp index 99c9af2449e6..001c466f7876 100644 --- a/src/gui/vector/qgsattributesformproperties.cpp +++ b/src/gui/vector/qgsattributesformproperties.cpp @@ -343,6 +343,7 @@ void QgsAttributesFormProperties::loadAttributeTypeDialogFromConfiguration( cons mAttributeTypeDialog->setUniqueEnforced( constraints.constraintStrength( QgsFieldConstraints::ConstraintUnique ) == QgsFieldConstraints::ConstraintStrengthHard ); mAttributeTypeDialog->setSplitPolicy( config.mSplitPolicy ); mAttributeTypeDialog->setDuplicatePolicy( config.mDuplicatePolicy ); + mAttributeTypeDialog->setMergePolicy( config.mMergePolicy ); QgsFieldConstraints::Constraints providerConstraints = QgsFieldConstraints::Constraints(); if ( constraints.constraintOrigin( QgsFieldConstraints::ConstraintNotNull ) == QgsFieldConstraints::ConstraintOriginProvider ) @@ -417,6 +418,7 @@ void QgsAttributesFormProperties::storeAttributeTypeDialog() cfg.mEditorWidgetConfig = mAttributeTypeDialog->editorWidgetConfig(); cfg.mSplitPolicy = mAttributeTypeDialog->splitPolicy(); cfg.mDuplicatePolicy = mAttributeTypeDialog->duplicatePolicy(); + cfg.mMergePolicy = mAttributeTypeDialog->mergePolicy(); const int fieldIndex = mAttributeTypeDialog->fieldIdx(); mLayer->setDefaultValueDefinition( fieldIndex, QgsDefaultValue( mAttributeTypeDialog->defaultValueExpression(), mAttributeTypeDialog->applyDefaultValueOnUpdate() ) ); @@ -1047,6 +1049,7 @@ void QgsAttributesFormProperties::apply() mLayer->setFieldAlias( idx, cfg.mAlias ); mLayer->setFieldSplitPolicy( idx, cfg.mSplitPolicy ); mLayer->setFieldDuplicatePolicy( idx, cfg.mDuplicatePolicy ); + mLayer->setFieldMergePolicy( idx, cfg.mMergePolicy ); } // tabs and groups @@ -1118,6 +1121,7 @@ QgsAttributesFormProperties::FieldConfig::FieldConfig( QgsVectorLayer *layer, in mEditorWidgetConfig = setup.config(); mSplitPolicy = layer->fields().at( idx ).splitPolicy(); mDuplicatePolicy = layer->fields().at( idx ).duplicatePolicy(); + mMergePolicy = layer->fields().at( idx ).mergePolicy(); } QgsAttributesFormProperties::FieldConfig::operator QVariant() @@ -2117,6 +2121,11 @@ void QgsAttributesFormProperties::copyWidgetConfiguration() duplicatePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.duplicatePolicy() ) ); documentElement.appendChild( duplicatePolicyElement ); + // Merge policy + QDomElement mergePolicyElement = doc.createElement( QStringLiteral( "mergePolicy" ) ); + mergePolicyElement.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.mergePolicy() ) ); + documentElement.appendChild( mergePolicyElement ); + // Default expressions QDomElement defaultElem = doc.createElement( QStringLiteral( "default" ) ); defaultElem.setAttribute( QStringLiteral( "expression" ), field.defaultValueDefinition().expression() ); @@ -2246,6 +2255,14 @@ void QgsAttributesFormProperties::pasteWidgetConfiguration() config.mDuplicatePolicy = policy; } + // Merge policy + const QDomElement mergePolicyElement = docElem.firstChildElement( QStringLiteral( "mergePolicy" ) ); + if ( !mergePolicyElement.isNull() ) + { + const Qgis::FieldDomainMergePolicy policy = qgsEnumKeyToValue( mergePolicyElement.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainMergePolicy::DefaultValue ); + config.mMergePolicy = policy; + } + // Default expressions const QDomElement defaultElement = docElem.firstChildElement( QStringLiteral( "default" ) ); if ( !defaultElement.isNull() ) diff --git a/src/gui/vector/qgsattributesformproperties.h b/src/gui/vector/qgsattributesformproperties.h index 5500fb619bf3..bc48cb6b98ba 100644 --- a/src/gui/vector/qgsattributesformproperties.h +++ b/src/gui/vector/qgsattributesformproperties.h @@ -374,6 +374,7 @@ class GUI_EXPORT QgsAttributesFormProperties : public QWidget, public QgsExpress QString mComment; Qgis::FieldDomainSplitPolicy mSplitPolicy = Qgis::FieldDomainSplitPolicy::Duplicate; Qgis::FieldDuplicatePolicy mDuplicatePolicy = Qgis::FieldDuplicatePolicy::Duplicate; + Qgis::FieldDomainMergePolicy mMergePolicy = Qgis::FieldDomainMergePolicy::DefaultValue; operator QVariant(); }; diff --git a/src/ui/attributeformconfig/qgsattributetypeedit.ui b/src/ui/attributeformconfig/qgsattributetypeedit.ui index 7c46ca9b4f3a..1c4ed70bea57 100644 --- a/src/ui/attributeformconfig/qgsattributetypeedit.ui +++ b/src/ui/attributeformconfig/qgsattributetypeedit.ui @@ -284,19 +284,6 @@ Policies - - - - - - - When duplicating features - - - - - - @@ -304,6 +291,9 @@ + + + @@ -314,6 +304,26 @@ + + + + + + + When merging features + + + + + + + When duplicating features + + + + + + @@ -324,6 +334,13 @@ + + + + TextLabel + + + diff --git a/tests/src/app/testqgsmergeattributesdialog.cpp b/tests/src/app/testqgsmergeattributesdialog.cpp index 27e10c582ee3..353efdc2daa9 100644 --- a/tests/src/app/testqgsmergeattributesdialog.cpp +++ b/tests/src/app/testqgsmergeattributesdialog.cpp @@ -220,6 +220,71 @@ class TestQgsMergeattributesDialog : public QgsTest // QVariant gets turned into default value while saving the layer QCOMPARE( dialog.mergedAttributes(), QgsAttributes() << 1 << QVariant() ); } + + void testMergePolicies() + { + // Create test layer + QgsVectorFileWriter::SaveVectorOptions options; + QgsVectorLayer ml( "LineString", "test", "memory" ); + QVERIFY( ml.isValid() ); + + QgsField defaultValueField( QStringLiteral( "defaultValue" ), QMetaType::Type::Int ); + QgsField sumField( QStringLiteral( "sum" ), QMetaType::Type::Int ); + QgsField geometryWeightedField( QStringLiteral( "geometryWeighted" ), QMetaType::Type::Double ); + QgsField largestGeometryField( QStringLiteral( "largestGeometry" ), QMetaType::Type::QString ); + QgsField minimumValueField( QStringLiteral( "minimumValue" ), QMetaType::Type::Int ); + QgsField maximumValueField( QStringLiteral( "maximumValue" ), QMetaType::Type::Int ); + QgsField skipAttributeField( QStringLiteral( "skipAttribute" ), QMetaType::Type::Int ); + QgsField unsetField( QStringLiteral( "unsetField" ), QMetaType::Type::Int ); + + QVERIFY( ml.dataProvider()->addAttributes( { defaultValueField, sumField, geometryWeightedField, largestGeometryField, minimumValueField, maximumValueField, skipAttributeField, unsetField } ) ); + ml.updateFields(); + + // set policies + ml.setFieldMergePolicy( 0, Qgis::FieldDomainMergePolicy::DefaultValue ); + ml.setFieldMergePolicy( 1, Qgis::FieldDomainMergePolicy::Sum ); + ml.setFieldMergePolicy( 2, Qgis::FieldDomainMergePolicy::GeometryWeighted ); + ml.setFieldMergePolicy( 3, Qgis::FieldDomainMergePolicy::LargestGeometry ); + ml.setFieldMergePolicy( 4, Qgis::FieldDomainMergePolicy::MinimumValue ); + ml.setFieldMergePolicy( 5, Qgis::FieldDomainMergePolicy::MaximumValue ); + ml.setFieldMergePolicy( 6, Qgis::FieldDomainMergePolicy::SetToNull ); + ml.setFieldMergePolicy( 7, Qgis::FieldDomainMergePolicy::UnsetField ); + + // verify that policies have been correctly set + + QCOMPARE( ml.fields().field( 0 ).mergePolicy(), Qgis::FieldDomainMergePolicy::DefaultValue ); + QCOMPARE( ml.fields().field( 1 ).mergePolicy(), Qgis::FieldDomainMergePolicy::Sum ); + QCOMPARE( ml.fields().field( 2 ).mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); + QCOMPARE( ml.fields().field( 3 ).mergePolicy(), Qgis::FieldDomainMergePolicy::LargestGeometry ); + QCOMPARE( ml.fields().field( 4 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MinimumValue ); + QCOMPARE( ml.fields().field( 5 ).mergePolicy(), Qgis::FieldDomainMergePolicy::MaximumValue ); + QCOMPARE( ml.fields().field( 6 ).mergePolicy(), Qgis::FieldDomainMergePolicy::SetToNull ); + QCOMPARE( ml.fields().field( 7 ).mergePolicy(), Qgis::FieldDomainMergePolicy::UnsetField ); + + // Create features + QgsFeature f1( ml.fields(), 1 ); + f1.setAttributes( QVector() << 10 << 200 << 7.5 << QStringLiteral( "smaller" ) << 10 << -10 << 0 << 20 ); + f1.setGeometry( QgsGeometry::fromWkt( "LINESTRING(10 0, 15 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f1 ) ); + QCOMPARE( ml.featureCount(), 1 ); + + QgsFeature f2( ml.fields(), 2 ); + f2.setAttributes( QVector() << 15 << 100 << 5 << QStringLiteral( "bigger" ) << -10 << 10 << 5 << 12 ); + f2.setGeometry( QgsGeometry::fromWkt( "LINESTRING(0 0, 10 0)" ) ); + QVERIFY( ml.dataProvider()->addFeature( f2 ) ); + QCOMPARE( ml.featureCount(), 2 ); + + QgsMergeAttributesDialog dialog1( QgsFeatureList() << f1 << f2, &ml, mQgisApp->mapCanvas() ); + + QCOMPARE( dialog1.mergedAttributes().at( 0 ).toInt(), 10 ); + QCOMPARE( dialog1.mergedAttributes().at( 1 ).toInt(), 300 ); + QVERIFY( qgsDoubleNear( dialog1.mergedAttributes().at( 2 ).toDouble(), 5.83333, 0.00001 ) ); + QCOMPARE( dialog1.mergedAttributes().at( 3 ).toString(), QStringLiteral( "bigger" ) ); + QCOMPARE( dialog1.mergedAttributes().at( 4 ).toInt(), -10 ); + QCOMPARE( dialog1.mergedAttributes().at( 5 ).toInt(), 10 ); + QVERIFY( !dialog1.mergedAttributes().at( 6 ).isValid() ); + QCOMPARE( dialog1.mergedAttributes().at( 7 ).toInt(), 20 ); + } }; QGSTEST_MAIN( TestQgsMergeattributesDialog ) diff --git a/tests/src/core/testqgsfield.cpp b/tests/src/core/testqgsfield.cpp index b88ac69bb074..0785c7d41a33 100644 --- a/tests/src/core/testqgsfield.cpp +++ b/tests/src/core/testqgsfield.cpp @@ -208,6 +208,9 @@ void TestQgsField::gettersSetters() field.setDuplicatePolicy( Qgis::FieldDuplicatePolicy::UnsetField ); QCOMPARE( field.duplicatePolicy(), Qgis::FieldDuplicatePolicy::UnsetField ); + field.setMergePolicy( Qgis::FieldDomainMergePolicy::GeometryWeighted ); + QCOMPARE( field.mergePolicy(), Qgis::FieldDomainMergePolicy::GeometryWeighted ); + field.setMetadata( { { static_cast( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } } ); QMap expected { { static_cast( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } }; QCOMPARE( field.metadata(), expected );