mirror of
https://github.com/qgis/QGIS.git
synced 2025-10-17 00:09:36 -04:00
[ogr] Read field domains from datasets and auto translate to value map
editor config or range config Requires GDAL 3.3+
This commit is contained in:
parent
cad5707a62
commit
58c3665f23
@ -1215,6 +1215,17 @@ void QgsOgrProvider::loadFields()
|
||||
mPrimaryKeyAttrs << 0;
|
||||
}
|
||||
|
||||
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,3,0)
|
||||
// needed for field domain retrieval on GDAL 3.3+
|
||||
#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0)
|
||||
QMutex *datasetMutex = nullptr;
|
||||
#else
|
||||
QRecursiveMutex *datasetMutex = nullptr;
|
||||
#endif
|
||||
GDALDatasetH ds = mOgrLayer->getDatasetHandleAndMutex( datasetMutex );
|
||||
QMutexLocker locker( datasetMutex );
|
||||
#endif
|
||||
|
||||
for ( int i = 0; i < fdef.GetFieldCount(); ++i )
|
||||
{
|
||||
OGRFieldDefnH fldDef = fdef.GetFieldDefn( i );
|
||||
@ -1371,6 +1382,84 @@ void QgsOgrProvider::loadFields()
|
||||
mDefaultValues.insert( createdFields, defaultValue );
|
||||
}
|
||||
|
||||
#if GDAL_VERSION_NUM >= GDAL_COMPUTE_VERSION(3,3,0)
|
||||
if ( const char *domainName = OGR_Fld_GetDomainName( fldDef ) )
|
||||
{
|
||||
// dataset retains ownership of domain!
|
||||
if ( OGRFieldDomainH domain = GDALDatasetGetFieldDomain( ds, domainName ) )
|
||||
{
|
||||
switch ( OGR_FldDomain_GetDomainType( domain ) )
|
||||
{
|
||||
case OFDT_CODED:
|
||||
{
|
||||
QVariantList valueConfig;
|
||||
const OGRCodedValue *codedValue = OGR_CodedFldDomain_GetEnumeration( domain );
|
||||
while ( codedValue && codedValue->pszCode )
|
||||
{
|
||||
const QString code( codedValue->pszCode );
|
||||
const QString value( codedValue->pszValue );
|
||||
|
||||
QVariantMap config;
|
||||
config[ value ] = code;
|
||||
valueConfig.append( config );
|
||||
|
||||
codedValue++;
|
||||
}
|
||||
|
||||
QVariantMap editorConfig;
|
||||
editorConfig.insert( QStringLiteral( "map" ), valueConfig );
|
||||
newField.setEditorWidgetSetup( QgsEditorWidgetSetup( QStringLiteral( "ValueMap" ), editorConfig ) );
|
||||
break;
|
||||
}
|
||||
|
||||
case OFDT_RANGE:
|
||||
if ( newField.isNumeric() )
|
||||
{
|
||||
// QGIS doesn't support the inclusive option yet!
|
||||
bool isInclusive = false;
|
||||
|
||||
QVariantMap editorConfig;
|
||||
editorConfig.insert( QStringLiteral( "Step" ), 1 );
|
||||
editorConfig.insert( QStringLiteral( "Style" ), QStringLiteral( "SpinBox" ) );
|
||||
editorConfig.insert( QStringLiteral( "AllowNull" ), nullable );
|
||||
editorConfig.insert( QStringLiteral( "Precision" ), newField.precision() );
|
||||
|
||||
OGRFieldType domainFieldType = OGR_FldDomain_GetFieldType( domain );
|
||||
bool hasMinOrMax = false;
|
||||
if ( const OGRField *min = OGR_RangeFldDomain_GetMin( domain, &isInclusive ) )
|
||||
{
|
||||
const QVariant minValue = QgsOgrUtils::OGRFieldtoVariant( min, domainFieldType );
|
||||
if ( minValue.isValid() )
|
||||
{
|
||||
editorConfig.insert( QStringLiteral( "Min" ), minValue );
|
||||
hasMinOrMax = true;
|
||||
}
|
||||
}
|
||||
if ( const OGRField *max = OGR_RangeFldDomain_GetMax( domain, &isInclusive ) )
|
||||
{
|
||||
const QVariant maxValue = QgsOgrUtils::OGRFieldtoVariant( max, domainFieldType );
|
||||
if ( maxValue.isValid() )
|
||||
{
|
||||
editorConfig.insert( QStringLiteral( "Max" ), maxValue );
|
||||
hasMinOrMax = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( hasMinOrMax )
|
||||
newField.setEditorWidgetSetup( QgsEditorWidgetSetup( QStringLiteral( "Range" ), editorConfig ) );
|
||||
}
|
||||
// GDAL also supports range domains for fields types like date/datetimes, but the QGIS corresponding field
|
||||
// config doesn't support this yet!
|
||||
break;
|
||||
|
||||
case OFDT_GLOB:
|
||||
// not supported by QGIS yet
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
mAttributeFields.append( newField );
|
||||
createdFields++;
|
||||
}
|
||||
|
@ -98,6 +98,89 @@ void gdal::GDALWarpOptionsDeleter::operator()( GDALWarpOptions *options )
|
||||
GDALDestroyWarpOptions( options );
|
||||
}
|
||||
|
||||
QVariant QgsOgrUtils::OGRFieldtoVariant( const OGRField *value, OGRFieldType type )
|
||||
{
|
||||
if ( !value )
|
||||
return QVariant();
|
||||
|
||||
switch ( type )
|
||||
{
|
||||
case OFTInteger:
|
||||
return value->Integer;
|
||||
|
||||
case OFTInteger64:
|
||||
return value->Integer64;
|
||||
|
||||
case OFTReal:
|
||||
return value->Real;
|
||||
|
||||
case OFTString:
|
||||
case OFTWideString:
|
||||
return QString::fromUtf8( value->String );
|
||||
|
||||
case OFTDate:
|
||||
return QDate( value->Date.Year, value->Date.Month, value->Date.Day );
|
||||
|
||||
case OFTTime:
|
||||
{
|
||||
float secondsPart = 0;
|
||||
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
|
||||
return QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) );
|
||||
}
|
||||
|
||||
case OFTDateTime:
|
||||
{
|
||||
float secondsPart = 0;
|
||||
float millisecondPart = std::modf( value->Date.Second, &secondsPart );
|
||||
return QDateTime( QDate( value->Date.Year, value->Date.Month, value->Date.Day ),
|
||||
QTime( value->Date.Hour, value->Date.Minute, static_cast< int >( secondsPart ), static_cast< int >( 1000 * millisecondPart ) ) );
|
||||
}
|
||||
|
||||
case OFTBinary:
|
||||
// not supported!
|
||||
Q_ASSERT_X( false, "QgsOgrUtils::OGRFieldtoVariant", "OFTBinary type not supported" );
|
||||
return QVariant();
|
||||
|
||||
case OFTIntegerList:
|
||||
{
|
||||
QVariantList res;
|
||||
res.reserve( value->IntegerList.nCount );
|
||||
for ( int i = 0; i < value->IntegerList.nCount; ++i )
|
||||
res << value->IntegerList.paList[ i ];
|
||||
return res;
|
||||
}
|
||||
|
||||
case OFTInteger64List:
|
||||
{
|
||||
QVariantList res;
|
||||
res.reserve( value->Integer64List.nCount );
|
||||
for ( int i = 0; i < value->Integer64List.nCount; ++i )
|
||||
res << value->Integer64List.paList[ i ];
|
||||
return res;
|
||||
}
|
||||
|
||||
case OFTRealList:
|
||||
{
|
||||
QVariantList res;
|
||||
res.reserve( value->RealList.nCount );
|
||||
for ( int i = 0; i < value->RealList.nCount; ++i )
|
||||
res << value->RealList.paList[ i ];
|
||||
return res;
|
||||
}
|
||||
|
||||
case OFTStringList:
|
||||
case OFTWideStringList:
|
||||
{
|
||||
QVariantList res;
|
||||
res.reserve( value->StringList.nCount );
|
||||
for ( int i = 0; i < value->StringList.nCount; ++i )
|
||||
res << QString::fromUtf8( value->StringList.paList[ i ] );
|
||||
return res;
|
||||
}
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QgsFeature QgsOgrUtils::readOgrFeature( OGRFeatureH ogrFet, const QgsFields &fields, QTextCodec *encoding )
|
||||
{
|
||||
QgsFeature feature;
|
||||
|
@ -165,6 +165,12 @@ class CORE_EXPORT QgsOgrUtils
|
||||
{
|
||||
public:
|
||||
|
||||
/**
|
||||
* Converts an OGRField \a value of the specified \a type into a QVariant.
|
||||
* \since QGIS 3.20
|
||||
*/
|
||||
static QVariant OGRFieldtoVariant( const OGRField *value, OGRFieldType type );
|
||||
|
||||
/**
|
||||
* Reads an OGR feature and converts it to a QgsFeature.
|
||||
* \param ogrFet OGR feature handle
|
||||
|
@ -1231,6 +1231,52 @@ class PyQgsOGRProvider(unittest.TestCase):
|
||||
encodedUri = QgsProviderRegistry.instance().encodeUri('ogr', parts)
|
||||
self.assertEqual(encodedUri, uri)
|
||||
|
||||
@unittest.skipIf(int(gdal.VersionInfo('VERSION_NUM')) < GDAL_COMPUTE_VERSION(3, 3, 0), "GDAL 3.3 required")
|
||||
def testFieldDomains(self):
|
||||
"""
|
||||
Test that field domains are translated from OGR where available (requires GDAL 3.3 or later)
|
||||
"""
|
||||
datasource = os.path.join(unitTestDataPath(), 'domains.gpkg')
|
||||
vl = QgsVectorLayer(datasource, 'test', 'ogr')
|
||||
self.assertTrue(vl.isValid())
|
||||
|
||||
fields = vl.fields()
|
||||
|
||||
range_int_field = fields[fields.lookupField('with_range_domain_int')]
|
||||
range_int_setup = range_int_field.editorWidgetSetup()
|
||||
self.assertEqual(range_int_setup.type(), 'Range')
|
||||
self.assertTrue(range_int_setup.config()['AllowNull'])
|
||||
self.assertEqual(range_int_setup.config()['Max'], 2)
|
||||
self.assertEqual(range_int_setup.config()['Min'], 1)
|
||||
self.assertEqual(range_int_setup.config()['Precision'], 0)
|
||||
self.assertEqual(range_int_setup.config()['Step'], 1)
|
||||
self.assertEqual(range_int_setup.config()['Style'], 'SpinBox')
|
||||
|
||||
range_int64_field = fields[fields.lookupField('with_range_domain_int64')]
|
||||
range_int64_setup = range_int64_field.editorWidgetSetup()
|
||||
self.assertEqual(range_int64_setup.type(), 'Range')
|
||||
self.assertTrue(range_int64_setup.config()['AllowNull'])
|
||||
self.assertEqual(range_int64_setup.config()['Max'], 1234567890123)
|
||||
self.assertEqual(range_int64_setup.config()['Min'], -1234567890123)
|
||||
self.assertEqual(range_int64_setup.config()['Precision'], 0)
|
||||
self.assertEqual(range_int64_setup.config()['Step'], 1)
|
||||
self.assertEqual(range_int64_setup.config()['Style'], 'SpinBox')
|
||||
|
||||
range_real_field = fields[fields.lookupField('with_range_domain_real')]
|
||||
range_real_setup = range_real_field.editorWidgetSetup()
|
||||
self.assertEqual(range_real_setup.type(), 'Range')
|
||||
self.assertTrue(range_real_setup.config()['AllowNull'])
|
||||
self.assertEqual(range_real_setup.config()['Max'], 2.5)
|
||||
self.assertEqual(range_real_setup.config()['Min'], 1.5)
|
||||
self.assertEqual(range_real_setup.config()['Precision'], 0)
|
||||
self.assertEqual(range_real_setup.config()['Step'], 1)
|
||||
self.assertEqual(range_real_setup.config()['Style'], 'SpinBox')
|
||||
|
||||
enum_field = fields[fields.lookupField('with_enum_domain')]
|
||||
enum_setup = enum_field.editorWidgetSetup()
|
||||
self.assertEqual(enum_setup.type(), 'ValueMap')
|
||||
self.assertTrue(enum_setup.config()['map'], [{'one': '1'}, {'': '2'}])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
|
BIN
tests/testdata/domains.gpkg
vendored
Normal file
BIN
tests/testdata/domains.gpkg
vendored
Normal file
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user