Skip to content

Commit

Permalink
Add API to set field split policy for vector layers
Browse files Browse the repository at this point in the history
  • Loading branch information
nyalldawson committed Mar 1, 2023
1 parent 3528d88 commit edfb976
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 2 deletions.
22 changes: 22 additions & 0 deletions python/core/auto_generated/vector/qgsvectorlayer.sip.in
Expand Up @@ -2038,6 +2038,28 @@ Convenience function that returns the attribute alias if defined or the field na
Returns a map of field name to attribute alias
%End


void setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy );
%Docstring
Sets a split ``policy`` for the field with the specified index.

:raises KeyError: if no field with the specified index exists

.. versionadded:: 3.30
%End

%MethodCode
if ( a0 < 0 || a0 >= sipCpp->fields().count() )
{
PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
sipCpp->setFieldSplitPolicy( a0, a1 );
}
%End

QSet<QString> excludeAttributesWms() const /Deprecated/;
%Docstring
A set of attributes that are not advertised in WMS requests with QGIS server.
Expand Down
60 changes: 58 additions & 2 deletions src/core/vector/qgsvectorlayer.cpp
Expand Up @@ -2073,6 +2073,10 @@ bool QgsVectorLayer::setDataProvider( QString const &provider, const QgsDataProv
{
mAttributeAliasMap[ field.name() ] = field.alias();
}
if ( !mAttributeSplitPolicy.contains( field.name() ) )
{
mAttributeSplitPolicy[ field.name() ] = field.splitPolicy();
}
}

if ( profile )
Expand Down Expand Up @@ -2383,6 +2387,22 @@ bool QgsVectorLayer::readSymbology( const QDomNode &layerNode, QString &errorMes
}
}

// IMPORTANT - we don't clear mAttributeSplitPolicy here, as it may contain policies which are coming direct
// from the data provider. Instead we leave any existing policies and only overwrite them if the style
// has a specific value for that field's policy
const QDomNode splitPoliciesNode = layerNode.namedItem( QStringLiteral( "splitPolicies" ) );
if ( !splitPoliciesNode.isNull() )
{
const QDomNodeList splitPolicyNodeList = splitPoliciesNode.toElement().elementsByTagName( QStringLiteral( "policy" ) );
for ( int i = 0; i < splitPolicyNodeList.size(); ++i )
{
const QDomElement splitPolicyElem = splitPolicyNodeList.at( i ).toElement();
const QString field = splitPolicyElem.attribute( QStringLiteral( "field" ) );
const Qgis::FieldDomainSplitPolicy policy = qgsEnumKeyToValue( splitPolicyElem.attribute( QStringLiteral( "policy" ) ), Qgis::FieldDomainSplitPolicy::Duplicate );
mAttributeSplitPolicy.insert( field, policy );
}
}

// default expressions
mDefaultExpressionMap.clear();
QDomNode defaultsNode = layerNode.namedItem( QStringLiteral( "defaults" ) );
Expand Down Expand Up @@ -2879,6 +2899,19 @@ bool QgsVectorLayer::writeSymbology( QDomNode &node, QDomDocument &doc, QString
}
node.appendChild( aliasElem );

//split policies
{
QDomElement splitPoliciesElement = doc.createElement( QStringLiteral( "splitPolicies" ) );
for ( const QgsField &field : std::as_const( mFields ) )
{
QDomElement splitPolicyElem = doc.createElement( QStringLiteral( "policy" ) );
splitPolicyElem.setAttribute( QStringLiteral( "field" ), field.name() );
splitPolicyElem.setAttribute( QStringLiteral( "policy" ), qgsEnumValueToKey( field.splitPolicy() ) );
splitPoliciesElement.appendChild( splitPolicyElem );
}
node.appendChild( splitPoliciesElement );
}

//default expressions
QDomElement defaultsElem = doc.createElement( QStringLiteral( "defaults" ) );
for ( const QgsField &field : std::as_const( mFields ) )
Expand Down Expand Up @@ -3362,6 +3395,21 @@ QgsStringMap QgsVectorLayer::attributeAliases() const
return mAttributeAliasMap;
}

void QgsVectorLayer::setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy )
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS

if ( index < 0 || index >= fields().count() )
return;

const QString name = fields().at( index ).name();

mAttributeSplitPolicy.insert( name, policy );
mFields[ index ].setSplitPolicy( policy );
mEditFormConfig.setFields( mFields );
emit layerModified(); // TODO[MD]: should have a different signal?
}

QSet<QString> QgsVectorLayer::excludeAttributesWms() const
{
QGIS_PROTECT_QOBJECT_THREAD_ACCESS
Expand Down Expand Up @@ -4216,8 +4264,7 @@ void QgsVectorLayer::updateFields()
mExpressionFieldBuffer->updateFields( mFields );

// set aliases and default values
QMap< QString, QString >::const_iterator aliasIt = mAttributeAliasMap.constBegin();
for ( ; aliasIt != mAttributeAliasMap.constEnd(); ++aliasIt )
for ( auto aliasIt = mAttributeAliasMap.constBegin(); aliasIt != mAttributeAliasMap.constEnd(); ++aliasIt )
{
int index = mFields.lookupField( aliasIt.key() );
if ( index < 0 )
Expand All @@ -4226,6 +4273,15 @@ void QgsVectorLayer::updateFields()
mFields[ index ].setAlias( aliasIt.value() );
}

for ( auto splitPolicyIt = mAttributeSplitPolicy.constBegin(); splitPolicyIt != mAttributeSplitPolicy.constEnd(); ++splitPolicyIt )
{
int index = mFields.lookupField( splitPolicyIt.key() );
if ( index < 0 )
continue;

mFields[ index ].setSplitPolicy( splitPolicyIt.value() );
}

// Update configuration flags
QMap< QString, QgsField::ConfigurationFlags >::const_iterator flagsIt = mFieldConfigurationFlags.constBegin();
for ( ; flagsIt != mFieldConfigurationFlags.constEnd(); ++flagsIt )
Expand Down
34 changes: 34 additions & 0 deletions src/core/vector/qgsvectorlayer.h
Expand Up @@ -1921,6 +1921,37 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! Returns a map of field name to attribute alias
QgsStringMap attributeAliases() const;

#ifndef SIP_RUN

/**
* Sets a split \a policy for the field with the specified index.
*
* \since QGIS 3.30
*/
void setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy );
#else

/**
* Sets a split \a policy for the field with the specified index.
*
* \throws KeyError if no field with the specified index exists
* \since QGIS 3.30
*/
void setFieldSplitPolicy( int index, Qgis::FieldDomainSplitPolicy policy );

% MethodCode
if ( a0 < 0 || a0 >= sipCpp->fields().count() )
{
PyErr_SetString( PyExc_KeyError, QByteArray::number( a0 ) );
sipIsErr = 1;
}
else
{
sipCpp->setFieldSplitPolicy( a0, a1 );
}
% End
#endif

/**
* A set of attributes that are not advertised in WMS requests with QGIS server.
* \deprecated since QGIS 3.16, use fields().configurationFlags() instead
Expand Down Expand Up @@ -2949,6 +2980,9 @@ class CORE_EXPORT QgsVectorLayer : public QgsMapLayer, public QgsExpressionConte
//! Map which stores default value expressions for fields
QMap<QString, QgsDefaultValue> mDefaultExpressionMap;

//! Map that stores the split policy for attributes
QMap< QString, Qgis::FieldDomainSplitPolicy > mAttributeSplitPolicy;

//! An internal structure to keep track of fields that have a defaultValueOnUpdate
QSet<int> mDefaultValueOnUpdateFields;

Expand Down
45 changes: 45 additions & 0 deletions tests/src/python/test_qgsvectorlayer.py
Expand Up @@ -4438,6 +4438,51 @@ def testMapTips(self):
self.assertFalse(vl.mapTipTemplate())
self.assertFalse(vl.hasMapTips())

def test_split_policies(self):
vl = QgsVectorLayer('Point?crs=epsg:3111&field=field_default:integer&field=field_dupe:integer&field=field_unset:integer&field=field_ratio:integer', 'test', 'memory')
self.assertTrue(vl.isValid())

with self.assertRaises(KeyError):
vl.setFieldSplitPolicy(-1, Qgis.FieldDomainSplitPolicy.DefaultValue)
with self.assertRaises(KeyError):
vl.setFieldSplitPolicy(4, Qgis.FieldDomainSplitPolicy.DefaultValue)

vl.setFieldSplitPolicy(0, Qgis.FieldDomainSplitPolicy.DefaultValue)
vl.setFieldSplitPolicy(1, Qgis.FieldDomainSplitPolicy.Duplicate)
vl.setFieldSplitPolicy(2, Qgis.FieldDomainSplitPolicy.UnsetField)
vl.setFieldSplitPolicy(3, Qgis.FieldDomainSplitPolicy.GeometryRatio)

self.assertEqual(vl.fields()[0].splitPolicy(),
Qgis.FieldDomainSplitPolicy.DefaultValue)
self.assertEqual(vl.fields()[1].splitPolicy(),
Qgis.FieldDomainSplitPolicy.Duplicate)
self.assertEqual(vl.fields()[2].splitPolicy(),
Qgis.FieldDomainSplitPolicy.UnsetField)
self.assertEqual(vl.fields()[3].splitPolicy(),
Qgis.FieldDomainSplitPolicy.GeometryRatio)

p = QgsProject()
p.addMapLayer(vl)

# test saving and restoring split policies
with tempfile.TemporaryDirectory() as temp:
self.assertTrue(p.write(temp + '/test.qgs'))

p2 = QgsProject()
self.assertTrue(p2.read(temp + '/test.qgs'))

vl2 = list(p2.mapLayers().values())[0]
self.assertEqual(vl2.name(), vl.name())

self.assertEqual(vl2.fields()[0].splitPolicy(),
Qgis.FieldDomainSplitPolicy.DefaultValue)
self.assertEqual(vl2.fields()[1].splitPolicy(),
Qgis.FieldDomainSplitPolicy.Duplicate)
self.assertEqual(vl2.fields()[2].splitPolicy(),
Qgis.FieldDomainSplitPolicy.UnsetField)
self.assertEqual(vl2.fields()[3].splitPolicy(),
Qgis.FieldDomainSplitPolicy.GeometryRatio)


# TODO:
# - fetch rect: feat with changed geometry: 1. in rect, 2. out of rect
Expand Down

0 comments on commit edfb976

Please sign in to comment.