Merge pull request #60710 from GispoCoding/59494_merge_policies

Add merge policies
This commit is contained in:
Alexander Bruy 2025-04-02 09:22:52 +01:00 committed by GitHub
commit 34aedd067d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 673 additions and 33 deletions

View File

@ -7030,6 +7030,11 @@ Qgis.FieldDomainSplitPolicy.baseClass = Qgis
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
@ -7040,6 +7045,26 @@ attributes following the domain are computed.
* ``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
"""
# --

View File

@ -2140,6 +2140,11 @@ The development version
DefaultValue,
Sum,
GeometryWeighted,
UnsetField,
LargestGeometry,
MinimumValue,
MaximumValue,
SetToNull,
};
enum class FieldDuplicatePolicy /BaseType=IntEnum/

View File

@ -549,6 +549,26 @@ should 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__();

View File

@ -2023,6 +2023,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<QString> excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/;
%Docstring
A set of attributes that are not advertised in WMS requests with QGIS

View File

@ -6964,6 +6964,11 @@ Qgis.FieldDomainSplitPolicy.baseClass = Qgis
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
@ -6974,6 +6979,26 @@ attributes following the domain are computed.
* ``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
"""
# --

View File

@ -2140,6 +2140,11 @@ The development version
DefaultValue,
Sum,
GeometryWeighted,
UnsetField,
LargestGeometry,
MinimumValue,
MaximumValue,
SetToNull,
};
enum class FieldDuplicatePolicy

View File

@ -549,6 +549,26 @@ should 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__();

View File

@ -2023,6 +2023,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<QString> excludeAttributesWms() const /Deprecated="Since 3.16. Use fields().configurationFlags() instead."/;
%Docstring
A set of attributes that are not advertised in WMS requests with QGIS

View File

@ -2317,6 +2317,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( "Set attribute to NULL" );
break;
}
metadata += QLatin1String( "</table>\n<br><br>" );

View File

@ -9464,9 +9464,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;

View File

@ -50,7 +50,7 @@ const QList<Qgis::Statistic> QgsMergeAttributesDialog::DISPLAY_STATS = QList<Qgi
<< Qgis::Statistic::ThirdQuartile
<< Qgis::Statistic::InterQuartileRange;
QgsMergeAttributesDialog::QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, QWidget *parent, Qt::WindowFlags f )
QgsMergeAttributesDialog::QgsMergeAttributesDialog( const QgsFeatureList &features, QgsVectorLayer *vl, QgsMapCanvas *canvas, bool skipAll, QWidget *parent, Qt::WindowFlags f )
: QDialog( parent, f )
, mFeatureList( features )
, mVectorLayer( vl )
@ -63,7 +63,11 @@ QgsMergeAttributesDialog::QgsMergeAttributesDialog( const QgsFeatureList &featur
connect( mFromSelectedPushButton, &QPushButton::clicked, this, &QgsMergeAttributesDialog::mFromSelectedPushButton_clicked );
connect( mFromLargestPushButton, &QPushButton::clicked, this, &QgsMergeAttributesDialog::mFromLargestPushButton_clicked );
connect( mRemoveFeatureFromSelectionButton, &QPushButton::clicked, this, &QgsMergeAttributesDialog::mRemoveFeatureFromSelectionButton_clicked );
createTableWidgetContents();
createTableWidgetContents( skipAll );
if ( skipAll )
setAllToSkip();
QHeaderView *verticalHeader = mTableWidget->verticalHeader();
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() )
const QgsField field = mVectorLayer->fields().at( idx );
QComboBox *currentComboBox = qobject_cast<QComboBox *>( mTableWidget->cellWidget( 0, j ) );
switch ( field.mergePolicy() )
{
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() )
case Qgis::FieldDomainMergePolicy::Sum:
{
if ( !field.isNumeric() )
break;
if ( currentComboBox )
currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast<int>( 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<QgsGeometry> 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<QgsGeometry> 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<QgsFeature>::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<double( const QgsGeometry & )> getSize = mVectorLayer->geometryType() == Qgis::GeometryType::Polygon ? &QgsGeometry::area : &QgsGeometry::length;
QList<QgsFeature>::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<int>( Qgis::Statistic::Min ) ) );
break;
}
case Qgis::FieldDomainMergePolicy::MaximumValue:
{
if ( currentComboBox )
currentComboBox->setCurrentIndex( currentComboBox->findData( static_cast<int>( Qgis::Statistic::Max ) ) );
break;
}
case Qgis::FieldDomainMergePolicy::SetToNull:
{
if ( currentComboBox )
currentComboBox->setCurrentIndex( currentComboBox->findData( QStringLiteral( "null" ) ) );
break;
}
break;
}
if ( setToManual )
{
QComboBox *currentComboBox = qobject_cast<QComboBox *>( mTableWidget->cellWidget( 0, j ) );
if ( currentComboBox )
{
currentComboBox->blockSignals( true );
@ -315,6 +453,7 @@ QComboBox *QgsMergeAttributesDialog::createMergeComboBox( QMetaType::Type column
newComboBox->addItem( tr( "Skip Attribute" ), QStringLiteral( "skip" ) );
newComboBox->addItem( tr( "Manual Value" ), QStringLiteral( "manual" ) );
newComboBox->addItem( tr( "Set to NULL" ), QStringLiteral( "null" ) );
connect( newComboBox, static_cast<void ( QComboBox::* )( int )>( &QComboBox::currentIndexChanged ), this, [=]() {
bool isManual = newComboBox->currentData() == QLatin1String( "manual" );
@ -404,6 +543,10 @@ void QgsMergeAttributesDialog::refreshMergedValue( int col )
{
mergeResult = tr( "Skipped" );
}
else if ( mergeBehaviorString == QLatin1String( "null" ) )
{
mergeResult = tr( "NULL" );
}
else if ( mergeBehaviorString.startsWith( 'f' ) )
{
//an existing feature value
@ -422,7 +565,7 @@ void QgsMergeAttributesDialog::refreshMergedValue( int col )
// Result formatting
QString stringVal;
if ( mergeBehaviorString != QLatin1String( "skip" ) && mergeBehaviorString != QLatin1String( "manual" ) )
if ( mergeBehaviorString != QLatin1String( "skip" ) && mergeBehaviorString != QLatin1String( "manual" ) && mergeBehaviorString != QLatin1String( "null" ) )
{
const QgsEditorWidgetSetup setup = mFields.at( fieldIdx ).editorWidgetSetup();
const QgsFieldFormatter *formatter = QgsApplication::fieldFormatterRegistry()->fieldFormatter( setup.type() );
@ -783,6 +926,14 @@ QgsAttributes QgsMergeAttributesDialog::mergedAttributes() const
continue;
QVariant value;
if ( comboBox->currentData().toString() == QLatin1String( "null" ) )
{
results[fieldIdx] = value;
widgetIndex++;
continue;
}
QWidget *w = mTableWidget->cellWidget( mFeatureList.size() + 1, widgetIndex );
QgsEditorWidgetWrapper *eww = QgsEditorWidgetWrapper::fromWidget( w );
if ( eww )

View File

@ -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

View File

@ -3775,6 +3775,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 )

View File

@ -778,6 +778,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.

View File

@ -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

View File

@ -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;

View File

@ -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() )

View File

@ -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 )
@ -2585,6 +2589,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" ) );
@ -3136,6 +3153,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 ) )
@ -3655,6 +3685,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<QString> QgsVectorLayer::excludeAttributesWms() const
{
@ -4537,6 +4581,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 )

View File

@ -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<int> mDefaultValueOnUpdateFields;

View File

@ -126,6 +126,34 @@ QgsAttributeTypeDialog::QgsAttributeTypeDialog( QgsVectorLayer *vl, int fieldIdx
mDuplicatePolicyComboBox->addItem( tr( "Remove Value" ), QVariant::fromValue( Qgis::FieldDuplicatePolicy::UnsetField ) );
connect( mDuplicatePolicyComboBox, qOverload<int>( &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( "Set to NULL" ), QVariant::fromValue( Qgis::FieldDomainMergePolicy::SetToNull ) );
}
else
{
mMergePolicyComboBox->setEnabled( false );
mMergePolicyLabel->setEnabled( false );
mMergePolicyDescriptionLabel->hide();
}
connect( mMergePolicyComboBox, qOverload<int>( &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<Qgis::FieldDomainMergePolicy>();
}
QString QgsAttributeTypeDialog::constraintExpression() const
{
return constraintExpressionWidget->asExpression();
@ -537,6 +576,46 @@ void QgsAttributeTypeDialog::updateDuplicatePolicyLabel()
mDuplicatePolicyDescriptionLabel->setText( QStringLiteral( "<i>%1</i>" ).arg( helperText ) );
}
void QgsAttributeTypeDialog::updateMergePolicyLabel()
{
QString helperText;
switch ( mMergePolicyComboBox->currentData().value<Qgis::FieldDomainMergePolicy>() )
{
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( "Set attribute to NULL." );
break;
}
mMergePolicyDescriptionLabel->setText( QStringLiteral( "<i>%1</i>" ).arg( helperText ) );
}
QStandardItem *QgsAttributeTypeDialog::currentItem() const
{
QStandardItemModel *widgetTypeModel = qobject_cast<QStandardItemModel *>( mWidgetTypeComboBox->model() );

View File

@ -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;

View File

@ -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() );
@ -2262,6 +2271,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() )

View File

@ -376,6 +376,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();
};

View File

@ -284,19 +284,6 @@
<string>Policies</string>
</property>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="mSplitPolicyComboBox"/>
</item>
<item row="4" column="0">
<widget class="QLabel" name="mDuplicatePolicyLabel">
<property name="text">
<string>When duplicating features</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QComboBox" name="mDuplicatePolicyComboBox"/>
</item>
<item row="0" column="0">
<widget class="QLabel" name="mSplitPolicyLabel">
<property name="text">
@ -304,6 +291,9 @@
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QComboBox" name="mDuplicatePolicyComboBox"/>
</item>
<item row="5" column="0" colspan="4">
<widget class="QLabel" name="mDuplicatePolicyDescriptionLabel">
<property name="text">
@ -314,6 +304,26 @@
</property>
</widget>
</item>
<item row="6" column="1" colspan="3">
<widget class="QComboBox" name="mMergePolicyComboBox"/>
</item>
<item row="6" column="0">
<widget class="QLabel" name="mMergePolicyLabel">
<property name="text">
<string>When merging features</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="mDuplicatePolicyLabel">
<property name="text">
<string>When duplicating features</string>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="mSplitPolicyComboBox"/>
</item>
<item row="1" column="0" colspan="4">
<widget class="QLabel" name="mSplitPolicyDescriptionLabel">
<property name="text">
@ -324,6 +334,13 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="4">
<widget class="QLabel" name="mMergePolicyDescriptionLabel">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>

View File

@ -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<QVariant>() << 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<QVariant>() << 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 )

View File

@ -209,6 +209,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<int>( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } } );
QMap<int, QVariant> expected { { static_cast<int>( Qgis::FieldMetadataProperty::GeometryCrs ), QStringLiteral( "abc" ) }, { 2, 5 } };
QCOMPARE( field.metadata(), expected );