From 1a9a533fca584689805f94702c443cf1aa0c0e41 Mon Sep 17 00:00:00 2001 From: Nyall Dawson Date: Thu, 29 Apr 2021 14:45:51 +1000 Subject: [PATCH] [feature] Automatically translate layer data from ESRI Filegeodatabases to QGIS layer metadata Translate as much as possible of the original ESRI metadata across to the QGIS metadata, so that it's immediately available for use by users after loading a .gdb file. --- .../metadata/qgsmetadatautils.sip.in | 39 + python/core/core_auto.sip | 1 + src/core/CMakeLists.txt | 2 + src/core/metadata/qgsmetadatautils.cpp | 264 +++++ src/core/metadata/qgsmetadatautils.h | 45 + src/core/providers/ogr/qgsogrprovider.cpp | 40 +- tests/src/python/CMakeLists.txt | 1 + tests/src/python/test_provider_ogr.py | 21 + tests/src/python/test_qgsmetadatautils.py | 83 ++ tests/testdata/esri_metadata.xml | 1027 +++++++++++++++++ .../Test.DESKTOP-D021O0E.880.4500.sr.lock | 0 .../_gdb.DESKTOP-D021O0E.880.4500.sr.lock | 0 .../a00000001.TablesByName.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000001.gdbindexes | Bin 0 -> 110 bytes .../gdb_metadata.gdb/a00000001.gdbtable | Bin 0 -> 315 bytes .../gdb_metadata.gdb/a00000001.gdbtablx | Bin 0 -> 5152 bytes .../gdb_metadata.gdb/a00000002.gdbtable | Bin 0 -> 2055 bytes .../gdb_metadata.gdb/a00000002.gdbtablx | Bin 0 -> 5152 bytes .../gdb_metadata.gdb/a00000003.gdbindexes | Bin 0 -> 42 bytes .../gdb_metadata.gdb/a00000003.gdbtable | Bin 0 -> 602 bytes .../gdb_metadata.gdb/a00000003.gdbtablx | Bin 0 -> 5152 bytes .../a00000004.CatItemsByPhysicalName.atx | Bin 0 -> 4118 bytes .../a00000004.CatItemsByType.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000004.FDO_UUID.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000004.gdbindexes | Bin 0 -> 310 bytes .../gdb_metadata.gdb/a00000004.gdbtable | Bin 0 -> 9523 bytes .../gdb_metadata.gdb/a00000004.gdbtablx | Bin 0 -> 5152 bytes tests/testdata/gdb_metadata.gdb/a00000004.spx | Bin 0 -> 4118 bytes .../a00000005.CatItemTypesByName.atx | Bin 0 -> 12310 bytes .../a00000005.CatItemTypesByParentTypeID.atx | Bin 0 -> 4118 bytes .../a00000005.CatItemTypesByUUID.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000005.gdbindexes | Bin 0 -> 296 bytes .../gdb_metadata.gdb/a00000005.gdbtable | Bin 0 -> 1803 bytes .../gdb_metadata.gdb/a00000005.gdbtablx | Bin 0 -> 5152 bytes .../a00000006.CatRelsByDestinationID.atx | Bin 0 -> 4118 bytes .../a00000006.CatRelsByOriginID.atx | Bin 0 -> 4118 bytes .../a00000006.CatRelsByType.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000006.FDO_UUID.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000006.gdbindexes | Bin 0 -> 318 bytes .../gdb_metadata.gdb/a00000006.gdbtable | Bin 0 -> 263 bytes .../gdb_metadata.gdb/a00000006.gdbtablx | Bin 0 -> 5152 bytes .../a00000007.CatRelTypesByBackwardLabel.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByDestItemTypeID.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByForwardLabel.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByName.atx | Bin 0 -> 4118 bytes ...00000007.CatRelTypesByOriginItemTypeID.atx | Bin 0 -> 4118 bytes .../a00000007.CatRelTypesByUUID.atx | Bin 0 -> 4118 bytes .../gdb_metadata.gdb/a00000007.gdbindexes | Bin 0 -> 602 bytes .../gdb_metadata.gdb/a00000007.gdbtable | Bin 0 -> 2504 bytes .../gdb_metadata.gdb/a00000007.gdbtablx | Bin 0 -> 5152 bytes .../gdb_metadata.gdb/a00000009.gdbindexes | Bin 0 -> 116 bytes .../gdb_metadata.gdb/a00000009.gdbtable | Bin 0 -> 302 bytes .../gdb_metadata.gdb/a00000009.gdbtablx | Bin 0 -> 32 bytes tests/testdata/gdb_metadata.gdb/a00000009.spx | Bin 0 -> 4118 bytes tests/testdata/gdb_metadata.gdb/gdb | Bin 0 -> 4 bytes tests/testdata/gdb_metadata.gdb/timestamps | Bin 0 -> 400 bytes 56 files changed, 1516 insertions(+), 7 deletions(-) create mode 100644 python/core/auto_generated/metadata/qgsmetadatautils.sip.in create mode 100644 src/core/metadata/qgsmetadatautils.cpp create mode 100644 src/core/metadata/qgsmetadatautils.h create mode 100644 tests/src/python/test_qgsmetadatautils.py create mode 100644 tests/testdata/esri_metadata.xml create mode 100644 tests/testdata/gdb_metadata.gdb/Test.DESKTOP-D021O0E.880.4500.sr.lock create mode 100644 tests/testdata/gdb_metadata.gdb/_gdb.DESKTOP-D021O0E.880.4500.sr.lock create mode 100644 tests/testdata/gdb_metadata.gdb/a00000001.TablesByName.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000001.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000001.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000001.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000002.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000002.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000003.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000003.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000003.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.CatItemsByPhysicalName.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.CatItemsByType.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.FDO_UUID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000004.spx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByName.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByParentTypeID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByUUID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000005.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByDestinationID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByOriginID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByType.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.FDO_UUID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000006.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByBackwardLabel.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByDestItemTypeID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByForwardLabel.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByName.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByUUID.atx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000007.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000009.gdbindexes create mode 100644 tests/testdata/gdb_metadata.gdb/a00000009.gdbtable create mode 100644 tests/testdata/gdb_metadata.gdb/a00000009.gdbtablx create mode 100644 tests/testdata/gdb_metadata.gdb/a00000009.spx create mode 100644 tests/testdata/gdb_metadata.gdb/gdb create mode 100644 tests/testdata/gdb_metadata.gdb/timestamps diff --git a/python/core/auto_generated/metadata/qgsmetadatautils.sip.in b/python/core/auto_generated/metadata/qgsmetadatautils.sip.in new file mode 100644 index 000000000000..882a7c3713a0 --- /dev/null +++ b/python/core/auto_generated/metadata/qgsmetadatautils.sip.in @@ -0,0 +1,39 @@ +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/metadata/qgsmetadatautils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ + + + + + +class QgsMetadataUtils +{ +%Docstring(signature="appended") +Contains utility functions for working with metadata. + +.. versionadded:: 3.20 +%End + +%TypeHeaderCode +#include "qgsmetadatautils.h" +%End + public: + + static QgsLayerMetadata convertFromEsri( const QDomDocument &document ); +%Docstring +Converts ESRI layer metadata to :py:class:`QgsLayerMetadata`. +%End + +}; + +/************************************************************************ + * This file has been generated automatically from * + * * + * src/core/metadata/qgsmetadatautils.h * + * * + * Do not edit manually ! Edit header and run scripts/sipify.pl again * + ************************************************************************/ diff --git a/python/core/core_auto.sip b/python/core/core_auto.sip index 3c52aca54bec..9454b5924dff 100644 --- a/python/core/core_auto.sip +++ b/python/core/core_auto.sip @@ -438,6 +438,7 @@ %Include auto_generated/metadata/qgslayermetadata.sip %Include auto_generated/metadata/qgslayermetadataformatter.sip %Include auto_generated/metadata/qgslayermetadatavalidator.sip +%Include auto_generated/metadata/qgsmetadatautils.sip %Include auto_generated/metadata/qgsprojectmetadata.sip %Include auto_generated/network/qgsblockingnetworkrequest.sip %Include auto_generated/network/qgsfiledownloader.sip diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 23c246d05274..234f15262a9c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -120,6 +120,7 @@ set(QGIS_CORE_SRCS metadata/qgslayermetadata.cpp metadata/qgslayermetadatavalidator.cpp metadata/qgslayermetadataformatter.cpp + metadata/qgsmetadatautils.cpp metadata/qgsprojectmetadata.cpp numericformats/qgsbasicnumericformat.cpp @@ -1363,6 +1364,7 @@ set(QGIS_CORE_HDRS metadata/qgslayermetadata.h metadata/qgslayermetadataformatter.h metadata/qgslayermetadatavalidator.h + metadata/qgsmetadatautils.h metadata/qgsprojectmetadata.h network/qgsblockingnetworkrequest.h diff --git a/src/core/metadata/qgsmetadatautils.cpp b/src/core/metadata/qgsmetadatautils.cpp new file mode 100644 index 000000000000..414dab754cc9 --- /dev/null +++ b/src/core/metadata/qgsmetadatautils.cpp @@ -0,0 +1,264 @@ +/*************************************************************************** + qgsmetadatautils.cpp + ------------------- + begin : April 2021 + copyright : (C) 2021 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "qgsmetadatautils.h" +#include "qgslayermetadata.h" + +#include +#include + +QgsLayerMetadata QgsMetadataUtils::convertFromEsri( const QDomDocument &document ) +{ + QgsLayerMetadata metadata; + const QDomElement metadataElem = document.firstChildElement( QStringLiteral( "metadata" ) ); + + const QDomElement esri = metadataElem.firstChildElement( QStringLiteral( "Esri" ) ); + const QDomElement dataProperties = esri.firstChildElement( QStringLiteral( "DataProperties" ) ); + const QDomElement itemProps = dataProperties.firstChildElement( QStringLiteral( "itemProps" ) ); + metadata.setIdentifier( itemProps.firstChildElement( QStringLiteral( "itemName" ) ).text() ); + + const QDomElement dataIdInfo = metadataElem.firstChildElement( QStringLiteral( "dataIdInfo" ) ); + + // title + const QDomElement idCitation = dataIdInfo.firstChildElement( QStringLiteral( "idCitation" ) ); + const QString title = idCitation.firstChildElement( QStringLiteral( "resTitle" ) ).text(); + metadata.setTitle( title ); + + // abstract + const QDomElement idAbs = dataIdInfo.firstChildElement( QStringLiteral( "idAbs" ) ); + const QString abstractPlainText = QTextDocumentFragment::fromHtml( idAbs.text() ).toPlainText(); + metadata.setAbstract( abstractPlainText ); + + // purpose + const QDomElement idPurp = dataIdInfo.firstChildElement( QStringLiteral( "idPurp" ) ); + const QString purposePlainText = QTextDocumentFragment::fromHtml( idPurp.text() ).toPlainText(); + if ( !metadata.abstract().isEmpty() ) + metadata.setAbstract( metadata.abstract() + QStringLiteral( "\n\n" ) + purposePlainText ); + else + metadata.setAbstract( purposePlainText ); + + // supplementary info + const QDomElement suppInfo = dataIdInfo.firstChildElement( QStringLiteral( "suppInfo" ) ); + const QString suppInfoPlainText = QTextDocumentFragment::fromHtml( suppInfo.text() ).toPlainText(); + if ( !metadata.abstract().isEmpty() ) + metadata.setAbstract( metadata.abstract() + QStringLiteral( "\n\n" ) + suppInfoPlainText ); + else + metadata.setAbstract( suppInfoPlainText ); + + // language + const QDomElement dataLang = dataIdInfo.firstChildElement( QStringLiteral( "dataLang" ) ); + const QDomElement languageCode = dataLang.firstChildElement( QStringLiteral( "languageCode" ) ); + const QString language = languageCode.attribute( QStringLiteral( "value" ) ).toUpper(); + metadata.setLanguage( language ); + + // keywords + QDomElement searchKeys = dataIdInfo.firstChildElement( QStringLiteral( "searchKeys" ) ); + QStringList keywords; + while ( !searchKeys.isNull() ) + { + QDomElement keyword = searchKeys.firstChildElement( QStringLiteral( "keyword" ) ); + while ( !keyword.isNull() ) + { + keywords << keyword.text(); + keyword = keyword.nextSiblingElement( QStringLiteral( "keyword" ) ); + } + + searchKeys = searchKeys.nextSiblingElement( QStringLiteral( "searchKeys" ) ); + } + if ( !keywords.empty() ) + metadata.addKeywords( QObject::tr( "Search keys" ), keywords ); + + // categories + QDomElement themeKeys = dataIdInfo.firstChildElement( QStringLiteral( "themeKeys" ) ); + QStringList categories; + while ( !themeKeys.isNull() ) + { + QDomElement themeKeyword = themeKeys.firstChildElement( QStringLiteral( "keyword" ) ); + while ( !themeKeyword.isNull() ) + { + categories << themeKeyword.text(); + themeKeyword = themeKeyword.nextSiblingElement( QStringLiteral( "keyword" ) ); + } + themeKeys = themeKeys.nextSiblingElement( QStringLiteral( "themeKeys" ) ); + } + if ( !categories.isEmpty() ) + metadata.setCategories( categories ); + + QgsLayerMetadata::Extent extent; + + // pubDate + const QDomElement date = idCitation.firstChildElement( QStringLiteral( "date" ) ); + const QString pubDate = date.firstChildElement( QStringLiteral( "pubDate" ) ).text(); + const QDateTime publicationDate = QDateTime::fromString( pubDate, Qt::ISODate ); + if ( publicationDate.isValid() ) + { + extent.setTemporalExtents( { publicationDate, QDateTime() } ); + } + + //crs + QgsCoordinateReferenceSystem crs; + const QDomElement refSysInfo = metadataElem.firstChildElement( QStringLiteral( "refSysInfo" ) ); + if ( !refSysInfo.isNull() ) + { + const QDomElement refSystem = refSysInfo.firstChildElement( QStringLiteral( "RefSystem" ) ); + const QDomElement refSysID = refSystem.firstChildElement( QStringLiteral( "refSysID" ) ); + const QString code = refSysID.firstChildElement( QStringLiteral( "identCode" ) ).attribute( QStringLiteral( "code" ) ); + const QString auth = refSysID.firstChildElement( QStringLiteral( "idCodeSpace" ) ).text(); + + crs = QgsCoordinateReferenceSystem( QStringLiteral( "%1:%2" ).arg( auth, code ) ); + if ( crs.isValid() ) + metadata.setCrs( crs ); + } + + // extent + QDomElement dataExt = dataIdInfo.firstChildElement( QStringLiteral( "dataExt" ) ); + while ( !dataExt.isNull() ) + { + const QDomElement geoEle = dataExt.firstChildElement( QStringLiteral( "geoEle" ) ); + if ( !geoEle.isNull() ) + { + const QDomElement geoBndBox = geoEle.firstChildElement( QStringLiteral( "GeoBndBox" ) ); + const double west = geoBndBox.firstChildElement( QStringLiteral( "westBL" ) ).text().toDouble(); + const double east = geoBndBox.firstChildElement( QStringLiteral( "eastBL" ) ).text().toDouble(); + const double south = geoBndBox.firstChildElement( QStringLiteral( "northBL" ) ).text().toDouble(); + const double north = geoBndBox.firstChildElement( QStringLiteral( "southBL" ) ).text().toDouble(); + + QgsLayerMetadata::SpatialExtent spatialExtent; + spatialExtent.extentCrs = crs.isValid() ? crs : QgsCoordinateReferenceSystem( QStringLiteral( "EPSG:4326" ) ); + spatialExtent.bounds = QgsBox3d( west, south, 0, east, north, 0 ); + + extent.setSpatialExtents( { spatialExtent } ); + break; + } + dataExt = dataExt.nextSiblingElement( QStringLiteral( "dataExt" ) ); + } + + metadata.setExtent( extent ); + + // licenses, constraints + QStringList licenses; + QStringList rights; + QgsLayerMetadata::ConstraintList constraints; + QDomElement resConst = dataIdInfo.firstChildElement( QStringLiteral( "resConst" ) ); + while ( !resConst.isNull() ) + { + QDomElement legConsts = resConst.firstChildElement( QStringLiteral( "LegConsts" ) ); + while ( !legConsts.isNull() ) + { + const QString restrictCd = legConsts.firstChildElement( QStringLiteral( "useConsts" ) ).firstChildElement( QStringLiteral( "RestrictCd" ) ).attribute( QStringLiteral( "value" ) ); + + if ( restrictCd.compare( QLatin1String( "005" ) ) == 0 ) + { + licenses << QTextDocumentFragment::fromHtml( legConsts.firstChildElement( QStringLiteral( "useLimit" ) ).text() ).toPlainText(); + } + else if ( restrictCd.compare( QLatin1String( "006" ) ) == 0 ) + { + rights << QTextDocumentFragment::fromHtml( legConsts.firstChildElement( QStringLiteral( "useLimit" ) ).text() ).toPlainText(); + } + legConsts = legConsts.nextSiblingElement( QStringLiteral( "LegConsts" ) ); + } + + QDomElement secConsts = resConst.firstChildElement( QStringLiteral( "SecConsts" ) ); + while ( !secConsts.isNull() ) + { + QgsLayerMetadata::Constraint constraint; + constraint.type = QObject::tr( "Security constraints" ); + constraint.constraint = QTextDocumentFragment::fromHtml( secConsts.firstChildElement( QStringLiteral( "userNote" ) ).text() ).toPlainText(); + constraints << constraint; + secConsts = secConsts.nextSiblingElement( QStringLiteral( "SecConsts" ) ); + } + + QDomElement consts = resConst.firstChildElement( QStringLiteral( "Consts" ) ); + while ( !consts.isNull() ) + { + QgsLayerMetadata::Constraint constraint; + constraint.type = QObject::tr( "Limitations of use" ); + constraint.constraint = QTextDocumentFragment::fromHtml( consts.firstChildElement( QStringLiteral( "useLimit" ) ).text() ).toPlainText(); + constraints << constraint; + consts = consts.nextSiblingElement( QStringLiteral( "Consts" ) ); + } + + resConst = resConst.nextSiblingElement( QStringLiteral( "resConst" ) ); + } + + const QDomElement idCredit = dataIdInfo.firstChildElement( QStringLiteral( "idCredit" ) ); + const QString credit = idCredit.text(); + if ( !credit.isEmpty() ) + rights << credit; + + metadata.setLicenses( licenses ); + metadata.setRights( rights ); + metadata.setConstraints( constraints ); + + // links + const QDomElement distInfo = metadataElem.firstChildElement( QStringLiteral( "distInfo" ) ); + const QDomElement distributor = distInfo.firstChildElement( QStringLiteral( "distributor" ) ); + + QDomElement distorTran = distributor.firstChildElement( QStringLiteral( "distorTran" ) ); + while ( !distorTran.isNull() ) + { + const QDomElement onLineSrc = distorTran.firstChildElement( QStringLiteral( "onLineSrc" ) ); + if ( !onLineSrc.isNull() ) + { + QgsAbstractMetadataBase::Link link; + link.url = onLineSrc.firstChildElement( QStringLiteral( "linkage" ) ).text(); + + const QDomElement distorFormat = distributor.firstChildElement( QStringLiteral( "distorFormat" ) ); + link.name = distorFormat.firstChildElement( QStringLiteral( "formatName" ) ).text(); + link.type = distorFormat.firstChildElement( QStringLiteral( "formatSpec" ) ).text(); + metadata.addLink( link ); + } + + distorTran = distorTran.nextSiblingElement( QStringLiteral( "distorTran" ) ); + } + + // lineage + const QDomElement dqInfo = metadataElem.firstChildElement( QStringLiteral( "dqInfo" ) ); + const QDomElement dataLineage = dqInfo.firstChildElement( QStringLiteral( "dataLineage" ) ); + const QString statement = QTextDocumentFragment::fromHtml( dataLineage.firstChildElement( QStringLiteral( "statement" ) ).text() ).toPlainText(); + if ( !statement.isEmpty() ) + metadata.addHistoryItem( statement ); + + QDomElement dataSource = dataLineage.firstChildElement( QStringLiteral( "dataSource" ) ); + while ( !dataSource.isNull() ) + { + metadata.addHistoryItem( QObject::tr( "Data source: %1" ).arg( QTextDocumentFragment::fromHtml( dataSource.firstChildElement( QStringLiteral( "srcDesc" ) ).text() ).toPlainText() ) ); + dataSource = dataSource.nextSiblingElement( QStringLiteral( "dataSource" ) ); + } + + // contacts + const QDomElement mdContact = metadataElem.firstChildElement( QStringLiteral( "mdContact" ) ); + if ( !mdContact.isNull() ) + { + QgsAbstractMetadataBase::Contact contact; + contact.name = mdContact.firstChildElement( QStringLiteral( "rpIndName" ) ).text(); + contact.organization = mdContact.firstChildElement( QStringLiteral( "rpOrgName" ) ).text(); + contact.position = mdContact.firstChildElement( QStringLiteral( "rpPosName" ) ).text(); + + const QString role = mdContact.firstChildElement( QStringLiteral( "role" ) ).firstChildElement( QStringLiteral( "RoleCd" ) ).attribute( QStringLiteral( "value" ) ); + if ( role == QLatin1String( "007" ) ) + contact.role = QObject::tr( "Point of contact" ); + + const QDomElement rpCntInfo = mdContact.firstChildElement( QStringLiteral( "rpCntInfo" ) ); + contact.email = rpCntInfo.firstChildElement( QStringLiteral( "cntAddress" ) ).firstChildElement( QStringLiteral( "eMailAdd" ) ).text(); + contact.voice = rpCntInfo.firstChildElement( QStringLiteral( "cntPhone" ) ).firstChildElement( QStringLiteral( "voiceNum" ) ).text(); + + metadata.addContact( contact ); + } + + return metadata; +} diff --git a/src/core/metadata/qgsmetadatautils.h b/src/core/metadata/qgsmetadatautils.h new file mode 100644 index 000000000000..8d5dc17ead5f --- /dev/null +++ b/src/core/metadata/qgsmetadatautils.h @@ -0,0 +1,45 @@ +/*************************************************************************** + qgsmetadatautils.h + ------------------- + begin : April 2021 + copyright : (C) 2021 by Nyall Dawson + email : nyall dot dawson at gmail dot com + ***************************************************************************/ + +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef QGSMETADATAUTILS_H +#define QGSMETADATAUTILS_H + +#include "qgis_sip.h" +#include "qgis_core.h" + +class QgsLayerMetadata; +class QDomDocument; + +/** + * \ingroup core + * \class QgsMetadataUtils + * \brief Contains utility functions for working with metadata. + * + * \since QGIS 3.20 + */ +class CORE_EXPORT QgsMetadataUtils +{ + public: + + /** + * Converts ESRI layer metadata to QgsLayerMetadata. + */ + static QgsLayerMetadata convertFromEsri( const QDomDocument &document ); + +}; + +#endif // QGSMETADATAUTILS_H diff --git a/src/core/providers/ogr/qgsogrprovider.cpp b/src/core/providers/ogr/qgsogrprovider.cpp index 6661e467deab..ef7fae127967 100644 --- a/src/core/providers/ogr/qgsogrprovider.cpp +++ b/src/core/providers/ogr/qgsogrprovider.cpp @@ -49,6 +49,7 @@ email : sherman at mrcc.com #include "qgsgeopackageproviderconnection.h" #include "qgis.h" #include "qgsembeddedsymbolrenderer.h" +#include "qgsmetadatautils.h" #define CPL_SUPRESS_CPLUSPLUS //#spellok #include // to collect version information @@ -623,7 +624,6 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio bool supportsBoolean = false; // layer metadata - mLayerMetadata.setType( QStringLiteral( "dataset" ) ); if ( mOgrOrigLayer ) { #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) @@ -633,13 +633,39 @@ QgsOgrProvider::QgsOgrProvider( QString const &uri, const ProviderOptions &optio #endif OGRLayerH layer = mOgrOrigLayer->getHandleAndMutex( mutex ); QMutexLocker locker( mutex ); - const QString identifier = GDALGetMetadataItem( layer, "IDENTIFIER", nullptr ); - if ( !identifier.isEmpty() ) - mLayerMetadata.setTitle( identifier ); // see geopackage specs -- "'identifier' is analogous to 'title'" - const QString abstract = GDALGetMetadataItem( layer, "DESCRIPTION", nullptr ); - if ( !abstract.isEmpty() ) - mLayerMetadata.setAbstract( abstract ); + if ( ( mGDALDriverName == QLatin1String( "FileGDB" ) || mGDALDriverName == QLatin1String( "OpenFileGDB" ) ) ) + { + // read layer metadata + + // important -- this ONLY works if the layer name is NOT quoted!! + QByteArray sql = "GetLayerMetadata " + mOgrOrigLayer->name(); + if ( QgsOgrLayerUniquePtr l = mOgrOrigLayer->ExecuteSQL( sql ) ) + { + gdal::ogr_feature_unique_ptr f( l->GetNextFeature() ); + if ( f ) + { + bool ok = false; + QVariant res = QgsOgrUtils::getOgrFeatureAttribute( f.get(), QgsField( QString(), QVariant::String ), 0, textEncoding(), &ok ); + if ( ok ) + { + QDomDocument metadataDoc; + metadataDoc.setContent( res.toString() ); + mLayerMetadata = QgsMetadataUtils::convertFromEsri( metadataDoc ); + } + } + } + } + else + { + const QString identifier = GDALGetMetadataItem( layer, "IDENTIFIER", nullptr ); + if ( !identifier.isEmpty() ) + mLayerMetadata.setTitle( identifier ); // see geopackage specs -- "'identifier' is analogous to 'title'" + const QString abstract = GDALGetMetadataItem( layer, "DESCRIPTION", nullptr ); + if ( !abstract.isEmpty() ) + mLayerMetadata.setAbstract( abstract ); + } } + mLayerMetadata.setType( QStringLiteral( "dataset" ) ); if ( mOgrOrigLayer ) { diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 5f2098d84a66..e5744ad131ab 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -188,6 +188,7 @@ ADD_PYTHON_TEST(PyQgsMarkerLineSymbolLayer test_qgsmarkerlinesymbollayer.py) ADD_PYTHON_TEST(PyQgsMergedFeatureRenderer test_qgsmergedfeaturerenderer.py) ADD_PYTHON_TEST(PyQgsMessageLog test_qgsmessagelog.py) ADD_PYTHON_TEST(PyQgsMetadataBase test_qgsmetadatabase.py) +ADD_PYTHON_TEST(PyQgsMetadataUtils test_qgsmetadatautils.py) ADD_PYTHON_TEST(PyQgsMetadataWidget test_qgsmetadatawidget.py) ADD_PYTHON_TEST(PyQgsMemoryProvider test_provider_memory.py) ADD_PYTHON_TEST(PyQgsMultiEditToolButton test_qgsmultiedittoolbutton.py) diff --git a/tests/src/python/test_provider_ogr.py b/tests/src/python/test_provider_ogr.py index f808d5dabf3b..d684b48cee3c 100644 --- a/tests/src/python/test_provider_ogr.py +++ b/tests/src/python/test_provider_ogr.py @@ -1025,6 +1025,27 @@ def testFieldAliases(self): self.assertEqual([f.name() for f in fields], expected_fieldnames) self.assertEqual([f.alias() for f in fields], expected_alias) + def testGdbLayerMetadata(self): + """ + Test that we translate GDB metadata to QGIS layer metadata on loading a GDB source + """ + datasource = os.path.join(unitTestDataPath(), 'gdb_metadata.gdb') + vl = QgsVectorLayer(datasource, 'test', 'ogr') + self.assertTrue(vl.isValid()) + self.assertEqual(vl.metadata().identifier(), 'Test') + self.assertEqual(vl.metadata().title(), 'Title') + self.assertEqual(vl.metadata().type(), 'dataset') + self.assertEqual(vl.metadata().language(), 'ENG') + self.assertIn('This is the abstract', vl.metadata().abstract()) + self.assertEqual(vl.metadata().keywords(), {'Search keys': ['Tags']}) + self.assertEqual(vl.metadata().rights(), ['This is the credits']) + self.assertEqual(vl.metadata().constraints()[0].type, 'Limitations of use') + self.assertEqual(vl.metadata().constraints()[0].constraint, 'This is the use limitation') + self.assertEqual(vl.metadata().extent().spatialExtents()[0].bounds.xMinimum(), 1) + self.assertEqual(vl.metadata().extent().spatialExtents()[0].bounds.xMaximum(), 2) + self.assertEqual(vl.metadata().extent().spatialExtents()[0].bounds.yMinimum(), 3) + self.assertEqual(vl.metadata().extent().spatialExtents()[0].bounds.yMaximum(), 4) + def testOpenOptions(self): filename = os.path.join(tempfile.gettempdir(), "testOpenOptions.gpkg") diff --git a/tests/src/python/test_qgsmetadatautils.py b/tests/src/python/test_qgsmetadatautils.py new file mode 100644 index 000000000000..e09af3bccd74 --- /dev/null +++ b/tests/src/python/test_qgsmetadatautils.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsMetadataUtils. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Nyall Dawson' +__date__ = '2021-04-29' +__copyright__ = 'Copyright 2021, The QGIS Project' + +from qgis.PyQt.QtCore import QDateTime +from qgis.PyQt.QtXml import QDomDocument +from qgis.core import ( + QgsMetadataUtils +) +from qgis.testing import (start_app, + unittest, + ) + +from utilities import unitTestDataPath + +start_app() +TEST_DATA_DIR = unitTestDataPath() + + +class TestPyQgsMetadataUtils(unittest.TestCase): + + def testConvertEsri(self): + """ + Test ESRI metadata conversion + """ + src = TEST_DATA_DIR + '/esri_metadata.xml' + doc = QDomDocument() + with open(src, 'rt') as f: + doc.setContent('\n'.join(f.readlines())) + + metadata = QgsMetadataUtils.convertFromEsri(doc) + self.assertEqual(metadata.title(), 'Baseline roads and tracks Queensland') + self.assertEqual(metadata.identifier(), 'Baseline_roads_and_tracks') + self.assertEqual(metadata.abstract(), + 'This dataset represents street centrelines of Queensland. \n\nTo provide the digital road network of Queensland. \n\nThis is supplementary info') + self.assertEqual(metadata.language(), 'ENG') + self.assertEqual(metadata.keywords(), {'gmd:topicCategory': ['TRANSPORTATION Land', 'road']}) + self.assertEqual(metadata.categories(), ['TRANSPORTATION Land', 'road']) + self.assertEqual(metadata.extent().temporalExtents()[0].begin(), QDateTime(2016, 6, 28, 0, 0)) + self.assertEqual(metadata.crs().authid(), 'EPSG:4283') + self.assertEqual(metadata.extent().spatialExtents()[0].bounds.xMinimum(), 137.921721) + self.assertEqual(metadata.extent().spatialExtents()[0].bounds.xMaximum(), 153.551682) + self.assertEqual(metadata.extent().spatialExtents()[0].bounds.yMinimum(), -29.177948) + self.assertEqual(metadata.extent().spatialExtents()[0].bounds.yMaximum(), -9.373145) + self.assertEqual(metadata.extent().spatialExtents()[0].extentCrs.authid(), 'EPSG:4283') + + self.assertEqual(metadata.licenses(), ['This material is licensed under a CC4']) + self.assertEqual(metadata.rights(), ['The State of Queensland (Department of Natural Resources and Mines)', + '© State of Queensland (Department of Natural Resources and Mines) 2016']) + self.assertIn('Unrestricted to all levels of government and community.', metadata.constraints()[0].constraint) + self.assertEqual(metadata.constraints()[0].type, 'Security constraints') + self.assertIn('Dataset is wholly created and owned by Natural Resources and Mines for the State of Queensland', + metadata.constraints()[1].constraint) + self.assertEqual(metadata.constraints()[1].type, 'Limitations of use') + + self.assertEqual(metadata.links()[0].type, 'Download Service') + self.assertEqual(metadata.links()[0].name, 'Queensland Spatial Catalog') + self.assertEqual(metadata.links()[0].url, 'http://qldspatial.information.qld.gov.au/catalog/custom/') + + self.assertEqual(metadata.history(), [ + 'The street records of the State Digital Road Network (SDRN) baseline dataset have been generated from road casement boundaries of the QLD Digital Cadastre Database (DCDB)and updated where more accurate source data has been available. Other sources used in the maintenance of the streets dataset include aerial imagery, QLD Department of Transport and Main Roads State Controlled Roads dataset, supplied datasets from other State Government Departments, Local Government data, field work using GPS, and user feedback. ', + 'Data source: Land parcel boundaries Queensland', + 'Data source: QLD Department of Transport and Main Roads State Controlled Roads', + 'Data source: Local Government data', 'Data source: field work', + 'Data source: other State Government Departments']) + + self.assertEqual(metadata.contacts()[0].name, 'Name') + self.assertEqual(metadata.contacts()[0].email, 'someone@gov.au') + self.assertEqual(metadata.contacts()[0].voice, '77777777') + self.assertEqual(metadata.contacts()[0].role, 'Point of contact') + self.assertEqual(metadata.contacts()[0].organization, 'Department of Natural Resources and Mines') + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testdata/esri_metadata.xml b/tests/testdata/esri_metadata.xml new file mode 100644 index 000000000000..5d60ff7c7b7c --- /dev/null +++ b/tests/testdata/esri_metadata.xml @@ -0,0 +1,1027 @@ + + + 20140723 + 11115900 + 1.0 + North American Profile of ISO19115 2003 + FALSE + + + Baseline_roads_and_tracks + + 137.921721 + 153.551682 + -29.177948 + -9.373145 + 1 + + 92.910 + + + file://\\SIRGIS11\D$\sirgis-fs\customApps\QSC\working\QSC_Extracted_Data_20161102_145414886000-4824\data.gdb + + Local Area Network + + 002 + + + Geographic + GCS_GDA_1994 + Angular Unit: Degree (0.017453) + <GeographicCoordinateSystem xsi:type='typens:GeographicCoordinateSystem' + xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' xmlns:xs='http://www.w3.org/2001/XMLSchema' + xmlns:typens='http://www.esri.com/schemas/ArcGIS/10.3'><WKT>GEOGCS[&quot;GCS_GDA_1994&quot;,DATUM[&quot;D_GDA_1994&quot;,SPHEROID[&quot;GRS_1980&quot;,6378137.0,298.257222101]],PRIMEM[&quot;Greenwich&quot;,0.0],UNIT[&quot;Degree&quot;,0.0174532925199433],AUTHORITY[&quot;EPSG&quot;,4283]]</WKT><XOrigin>-400</XOrigin><YOrigin>-400</YOrigin><XYScale>1111948722.2222199</XYScale><ZOrigin>-100000</ZOrigin><ZScale>10000</ZScale><MOrigin>-100000</MOrigin><MScale>10000</MScale><XYTolerance>8.98315284119521e-009</XYTolerance><ZTolerance>0.001</ZTolerance><MTolerance>0.001</MTolerance><HighPrecision>true</HighPrecision><LeftLongitude>-180</LeftLongitude><WKID>4283</WKID><LatestWKID>4283</LatestWKID></GeographicCoordinateSystem> + + + + 20161102 + 14544700 + 20161102 + 14544700 + ISO19139 + + + + + 150000000 + 5000 + + + + + + + external + Name + Department of Natural Resources and Mines + Senior Spatial Information Officer + + + someone@gov.au + + + 77777777 + + + True + Disp name + + + + + + + 10.3 + SDE Feature Class + + + Shapefile (SHP) + 1 + + + MapInfo (TAB) + 1 + + + File Geodatabase (FGDB) + 1 + + + Keyhole Markup Language (KML) + 1 + + + + DNRM, Product Delivery + Department of Natural Resources and Mines + Senior Spatial Information Officer (Product Delivery) + + + someone@gov.au + + + 7777 + + + DNRM, Product Delivery + + + + + + Open the Queensland Spatial Catalog (QSpatial). + Select the large SEARCH button and all available records are displayed. + Select one of the four filter options provided and then the further options within the filter. + The resultant search is then displayed. + Select your record and complete the order. + + + + Whole of dataset, although clipped areas are available through QSpatial. + + http://qldspatial.information.qld.gov.au/catalog/custom/ + + 95 + + + Queensland Spatial Catalog + 2.0 + Download Service + + + + Queensland Spatial Catalog + 2.0 + Download Service + + + + + Baseline roads and tracks + Baseline roads and tracks Queensland + + 2016-06-28T00:00:00 + + + DNRM, NR, LSI, ED + Department of Natural Resources and Mines + Executive Director, Land and Spatial Information + DNRM, NR, LSI, ED + + + + + + external + 9228b01d13fdc07144bddd09eec6c8b6 + DNRM, NR, LSI, SDM, MAN Administrative Data + Department of Natural Resources and Mines + Manager Administrative Data, Spatial Data and Mapping, Land and Spatial Information + True + DNRM, NR, LSI, SDM, MAN Administrative Data + + + + + + external + 9228b01d13fdc07144bddd09eec6c8b6 + DNRM, NR, LSI, SDM, MAN Administrative Data + Department of Natural Resources and Mines + Manager Administrative Data, Spatial Data and Mapping, Land and Spatial Information + True + DNRM, NR, LSI, SDM, MAN Administrative Data + + + + + + external + 9228b01d13fdc07144bddd09eec6c8b6 + DNRM, NR, LSI, SDM, MAN Administrative Data + Department of Natural Resources and Mines + Manager Administrative Data, Spatial Data and Mapping, Land and Spatial Information + True + DNRM, NR, LSI, SDM, MAN Administrative Data + + + + + + external + 5aea8649c4dea572310f4eb9652ff2 + Name + Department of Natural Resources and Mines + Senior Spatial Information Officer + + + someone@gov.au + + + 7777 + + + True + Name + + + + + + + + + To provide the digital road network of Queensland. + + <DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN STYLE="font-size:10pt">This + dataset represents street centrelines of Queensland.</SPAN></P></DIV></DIV></DIV> + + + + ANZLIC Search Words + + 2007-08-01T00:00:00 + + + TRANSPORTATION Land + + + + + + + + + + + + + external + 5aea8649c4dea572310f4eb9652ff2 + Name + Department of Natural Resources and Mines + Senior Spatial Information Officer + + + someone@gov.au + + + 77777 + + + True + Name + + + + + + + + ANZLIC Search Words + + 2007-08-01T00:00:00 + + + Queensland + + This is supplementary info + + Bounding Box + + © State of Queensland (Department of Natural Resources and Mines) 2016 + + + Common Search Terms + + 2016-02-05T00:00:00 + + + road + + + + + + + BUILT.QLD_BASELINE_ROADS_AND_TRACKS + + Spatial Information Resource (SIR) Feature Class + + 2016-03-15T00:00:00 + + + + + + + + + + The State of Queensland (Department of Natural Resources and Mines) + + + + + + + + This material is licensed under a CC4 + + + + + + + + Unrestricted to all levels of government and community. + + ISO 19115 (mapped from Qld Govt Information Security Standard IS18) + + + + + <DIV STYLE="text-align:Left;"><DIV><DIV><P><SPAN STYLE="font-size:10pt">Dataset + is wholly created and owned by Natural Resources and Mines for the State of Queensland.</SPAN></P><P><SPAN + /></P></DIV></DIV></DIV> + + + + + external + 9228b01d13fdc07144bddd09eec6c8b6 + DNRM, NR, LSI, SDM, MAN Administrative Data + Department of Natural Resources and Mines + Manager Administrative Data, Spatial Data and Mapping, Land and Spatial Information + True + DNRM, NR, LSI, SDM, MAN Administrative Data + + + + + Version 6.2 (Build 9200) ; Esri ArcGIS 10.3.1.5004 + + + + + + + + + + + 1 + 137.921721 + 153.551682 + -9.373145 + -29.177948 + + + + + + + + + + + + + + + + + + Metadata Access Level + Public + + + + + The street records of the State Digital Road Network (SDRN) baseline dataset have been generated from + road casement boundaries of the QLD Digital Cadastre Database (DCDB)and updated where more accurate source data + has been available. Other sources used in the maintenance of the streets dataset include aerial imagery, QLD + Department of Transport and Main Roads State Controlled Roads dataset, supplied datasets from other State + Government Departments, Local Government data, field work using GPS, and user feedback. + + + + Land parcel boundaries Queensland + + + QLD Department of Transport and Main Roads State Controlled Roads + + + Local Government data + + + field work + + + other State Government Departments + + + + Completeness is a reflection of the completeness and validity of the DCDB casement layer. Each + maintenance release uses the DCDB data current to within 1-2 months of the release date. Private streets (or + non-gazetted streets) are included only where they form an important part of the overall street network. + + + + + Department of Natural Resources and Mines data capture process + + 2015-08-19T00:00:00 + + + The data is to a standard determined as fit for the purpose for which it was collected. + 1 + + + + + There are two forms of Logical consistency checks performed on the streets dataset. In terms of the + street segment geometry, procedures are carried out to identify and rectify: Overshoots and undershoots, + Pseudo-nodes (unnecessary breaks in continuous roads) except at location of overpassesunderpasses, Segments less + than 3 metres, Duplicate objects, Null objects, and Objects are polylines only. In regards to the attribution of + the street segment, checks are carried out to ensure: Street names contain valid characters (i.e. Alpha-numeric + characters, no apostrophes, or other punctuation characters), all components of the street name, including + prefixes, suffixes, and road types, are described in full, all characters are in Upper case, and no null values. + + + + + Department of Natural Resources and Mines data capture process + + 2015-08-19T00:00:00 + + + The data is to a standard determined as fit for the purpose for which it was collected. + 1 + + + + + Horizontal positional accuracy of the street centrelines varies from +/- 1.5 metres in urban areas up to + +/- 250 metres in rural/remote areas. + + + + + Department of Natural Resources and Mines data capture process + + 2015-08-19T00:00:00 + + + The data is to a standard determined as fit for the purpose for which it was collected. + 1 + + + + + Street names are sourced originally from the QLD DCDB and subsequently validated with various Local and + State Government datasets, including the QLD Property Location Index. Road classifications were originally + derived from Queensland Government road maps and are revised during maintenance periods. + + + + + Department of Natural Resources and Mines data capture process + + 2015-08-19T00:00:00 + + + The data is to a standard determined as fit for the purpose for which it was collected. + 1 + + + + + + + + + + + + + Baseline roads and tracks Queensland + Feature Class + 0 + + + OBJECTID + OBJECTID + OID + 4 + 0 + 0 + Internal feature number. + ESRI + + Sequential unique whole numbers that are automatically generated. + + + + Shape + SHAPE + Geometry + 0 + 0 + 0 + Feature geometry. + ESRI + + Coordinates defining the features. + + + + Connector + 1 + + FEATURETYPE + Connector + + + TYPE + 2 + + + PERENNIALITY + + dm_PerennialityMultScle + Perennialty which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + HIERARCHY + + dm_HierarchyMultScle + Hierarchy which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + UPPERSCALE + + dm_UpperScaleMultScle + Upper Scale of Data Utilisation + Coded Value + Default value + Duplicate + Integer + BUILT + + + + USCERTAINTY + + dm_USCertaintyMultScle + Upper Scale Certainity + Coded Value + Default value + Duplicate + String + BUILT + + + + SYMBOL100K + + SYMBOL100K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL250K + + SYMBOL250K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOLWAC1MIL + + SYMBOLWAC1Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL2_5MIL + + SYMBOL2_5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL5MIL + + SYMBOL5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL10MIL + + SYMBOL10Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + + Watercourse + 2 + + FEATURETYPE + Watercourse + + + TYPE + 2 + + + PERENNIALITY + + dm_PerennialityMultScle + Perennialty which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + HIERARCHY + + dm_HierarchyMultScle + Hierarchy which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + UPPERSCALE + + dm_UpperScaleMultScle + Upper Scale of Data Utilisation + Coded Value + Default value + Duplicate + Integer + BUILT + + + + USCERTAINTY + + dm_USCertaintyMultScle + Upper Scale Certainity + Coded Value + Default value + Duplicate + String + BUILT + + + + SYMBOL100K + + SYMBOL100K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL250K + + SYMBOL250K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOLWAC1MIL + + SYMBOLWAC1Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL2_5MIL + + SYMBOL2_5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL5MIL + + SYMBOL5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL10MIL + + SYMBOL10Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + + DEMConnector + 3 + + FEATURETYPE + DEM Connector + + + TYPE + 2 + + + PERENNIALITY + + dm_PerennialityMultScle + Perennialty which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + HIERARCHY + + dm_HierarchyMultScle + Hierarchy which may be populated + Coded Value + Default value + Duplicate + String + NAT + + + + UPPERSCALE + + dm_UpperScaleMultScle + Upper Scale of Data Utilisation + Coded Value + Default value + Duplicate + Integer + BUILT + + + + USCERTAINTY + + dm_USCertaintyMultScle + Upper Scale Certainity + Coded Value + Default value + Duplicate + String + BUILT + + + + SYMBOL100K + + SYMBOL100K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL250K + + SYMBOL250K_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOLWAC1MIL + + SYMBOLWAC1Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL2_5MIL + + SYMBOL2_5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL5MIL + + SYMBOL5Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + SYMBOL10MIL + + SYMBOL10Mil_WatercourseLines_Rules + Representation rules + Coded Value + Default value + Default value + NAT + Integer + + + + + STREET + STREET + String + 60 + 0 + 0 + + + ROADTYPE + ROADTYPE + Integer + 4 + 0 + 0 + + + STE_ROUTE_ + STE_ROUTE_ + String + 10 + 0 + 0 + + + NAT_ROUTE_ + NAT_ROUTE_ + String + 10 + 0 + 0 + + + BASELINE_I + BASELINE_I + Double + 8 + 0 + 0 + + + Culvert + 4 + + FEATURETYPE + Culvert + + + TYPE + 2 + + + + SHAPE_Length + SHAPE_Length + Double + 8 + 0 + 0 + Length of feature in internal units. + Esri + + Positive real numbers that are automatically generated. + + + + + + + + + + + + + + + + EPSG + 8.6.2 + + + + + + + + + + 0 + + + + + + + + + + Simple + + FALSE + 0 + TRUE + TRUE + + + + 20161102 + 8F5BC21A-252F-417F-9C9E-0D50D0229C83 + dataset + + + diff --git a/tests/testdata/gdb_metadata.gdb/Test.DESKTOP-D021O0E.880.4500.sr.lock b/tests/testdata/gdb_metadata.gdb/Test.DESKTOP-D021O0E.880.4500.sr.lock new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testdata/gdb_metadata.gdb/_gdb.DESKTOP-D021O0E.880.4500.sr.lock b/tests/testdata/gdb_metadata.gdb/_gdb.DESKTOP-D021O0E.880.4500.sr.lock new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tests/testdata/gdb_metadata.gdb/a00000001.TablesByName.atx b/tests/testdata/gdb_metadata.gdb/a00000001.TablesByName.atx new file mode 100644 index 0000000000000000000000000000000000000000..d3711c17e52ec80b9238b48212f4ff285266cc0d GIT binary patch literal 4118 zcmeH~TMC0P5Jj(jcNgxg^r1FjTdC=XyK$+#(-1^yY4O9PFe5i6qHs7)1OZ^nNX?iP zvt|~|lG!kGCSm-2oQ|pjCmgVQjTR4Fks*!f@O%cAWEb2yme_M;a3gJvGiMdgVW@xQ zvuH?}P0l&w3+3t4+ZpnO^7M^Q`mSQ<864StC3~+3)Ny@M@2@7E+Z945wE5({)+ckT zj;|q)+rwheMq*KjgzFu94A(||)b$}Xpa#@{8c+jjKn9U6hy}wg$h=Df-YS94}yzY6hU0n?j=a9h4g`~i~fzD>LglM1_r`8b1x)q z04}p(I!trctuJV}24w3mO5vh_-0^TEJIKKb@ literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000001.gdbtablx b/tests/testdata/gdb_metadata.gdb/a00000001.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..3d6f920bbf82725a7a359819071aa1ea475639f6 GIT binary patch literal 5152 zcmeI$F%5uF5JbTvgaQS)Ko~mkfDAOC0wwg^7Kk93pX{rCHRX*}HT34*)Z2QsCr5H7 pS8^v$vgH4ocmxO#AV7cs0RjXF5FkK+009C72>ebUr@imI_W~9`1swnY literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000002.gdbtable b/tests/testdata/gdb_metadata.gdb/a00000002.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..a0af90eaae1fb1738ddead12a36441ef8292c88d GIT binary patch literal 2055 zcma)7-*3|}5H{*c$JBM>=L-l0#>7CQsAyD!Hz!THh%}iJM;g3DrcFkqCP7ImHvV{o z?KWwh#Dz$f9Q*G3zB`}KEdam~89o_pGK#OWyPTb@7U=#Co!TX$U4{6>AOIn_1`aVw$t)&wf=(#gU`UYu?2c3ox(0GO zuI?fA3LT*zzBKa#yqM?G4Cgoq=e{&e;vl?Uq&N?gxKA~#Jr9)&XhQ|5wx?P41fm@m zY3IkB&B0i;CMvSOu1xlnB>I{9S?14Pa)yVcp@S?Nj@7$*Y=p7@oU`i6h9!Hv#WT)g ze*&*OO*d4}(Z8v!0g@$dzh^^-D&{dhQJX!Q29Lxr7#2x+MU%o-q};#Br65TooL90! z4|mhKmX|g|xS(eDIeFgMST3keDHZvbB774@i)eiYS@^qp5G=`ctV-|%&7hXcT4c>w62nN@a;TA-8V{K>>}pstJ{f{uzx zwLPMCxx@M@yVvWi+c+c2B|**yRJ90~?_BEwV8tnL66iN6&m&&HZqj>EQP1vYlw>%Z zvWArTxfI6is_^+f6(ZK!A$ulRq>T!y*LmZ~=!;3p`sW+_TcHP+nbl)MpW^1U@a>I0 KE8>8gpP0Wjd{iF* literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000002.gdbtablx b/tests/testdata/gdb_metadata.gdb/a00000002.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..7c12c568195050346285f43f1164067d9ce86e7c GIT binary patch literal 5152 zcmeI$uMNUL97pkkmOm2^Jhm2##9$eqEP*Rw1rkOeL9hS=;0Ue>8dF=-gapO!jZptC z`F_laOP;(er8I~wVx!n8mY&eUoVyj2aD)qdU^)!8P{9j+u+D-5Jm3qX*gyq0c!S3R7O;Q?EMNf(Sik}nuz&?DU;ztQzycQNS%9xS#`yy^ CN)wa- literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000003.gdbindexes b/tests/testdata/gdb_metadata.gdb/a00000003.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..58df68d525b47a895d5876a8bf5f437a90afc3f0 GIT binary patch literal 42 mcmZQ%U|?VaVmAgC27iWl22UW(z#ss`|Nk=rNhTnMhyegseFSR& literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000003.gdbtable b/tests/testdata/gdb_metadata.gdb/a00000003.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..5916201be540bd041f9d5a2b4b4f05cdafe25fdc GIT binary patch literal 602 zcmZQ(U|?VZ;vYcF3dArF1rh`T4G0C6X92Mp85npNm^>L=7#P@CnAn0Df*3*=Tp1#O zBm)Zr2Z&&EV@PDkVJK!u1qw4TbFdd~wM<1^N5WL`HX4e|P8LXeA&KAMPF; zZ)j;@qNL;E7!v9mt>l6v6dd5;8szWk60L+#W@c<|VQ6fwXP{$bX`yFiYHnm?Y-(m4 z8>X=!2% z)CzJJLqpmD5XI0C2BsTY^c=zbtRG-LL&sqUbbjyKtqZ;%J7D)`<;P3DNdfjS+7qa- z;=fw8laYm)vw@kDu7QQQrLLi&i;=FQqqC!~fq}DuiJPObv9pmut+unw>qll$hhQ|= J5};);!vJbqejoq< literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000003.gdbtablx b/tests/testdata/gdb_metadata.gdb/a00000003.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..2f80ed4fe5fed90691cce6424c11c3787cc56315 GIT binary patch literal 5152 zcmeI$u?+wq30qvba*L&bO}EnT?#NoXARLgk9lcOg}zT^ y<)U_*@@D5`i;M>|OQh|~v%!;br2+~lpnw7jD4>7>3Min!PXbTLU;o#+H0Kv*<{=#b literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000004.CatItemsByType.atx b/tests/testdata/gdb_metadata.gdb/a00000004.CatItemsByType.atx new file mode 100644 index 0000000000000000000000000000000000000000..4519bd34bec597edc800673356bb6dfe2ffa4ba0 GIT binary patch literal 4118 zcmeI#O$q}c3rnmoN4hP&=SoH)4+8~(OVoEK6eWDtb^RSWo63ES7toeSo8PJF)92>_q`Xu-zKjB literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000004.gdbtable b/tests/testdata/gdb_metadata.gdb/a00000004.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..71f16ba7af026bf6153d072d1ff82800b58a6e90 GIT binary patch literal 9523 zcmdT}O^h7H6)x}?i~~YQiE>L?D`6YOyFIf%w%4w0dpt9{9@zCxc4mzMBdd41W~S|) z?xDM9cQz5hQi8-K2b4>GB!qH{a!Dd37mz@TgrX?n5H2C7ToWaSBnS{fNxoOrzcXW- zfI(z!Z&$td>b-iu^{RY;F?Im|-uwz<2l0)679LikV-a8rLdW_uW0Vi^QkkJb(qJ%q3|EIwdn*{ z9uXYaW?O7MnI5iULmepM;$Mh=9!BhbcJP5JhHdPV6nB9lg;MmPI;H~1cnh@VaD(|+ zMRxNgwSc}-w?j&d34RVRfF5E84>n;TQRdc$egNjujzcUP_z8 zcL}(8{8_+=D4)ikk@F)NH!^TeIZ2U!Z;^+P6ZuXVg^})BUy&8V+g|k2W0sXvvR<2>k&V3Do(R+>G z|L)JvzWieOHMY`t^)H7X@{G~Hek;CM`}UXKTz>vfZ~Yt*aD@^7pWO}ech7lGy>jW@ z!>cd-`0VaipF8>W2mc+sal_v{_x!vzeEN?c-gDpMo_{?Um~C<2%t=`m2#ko^Q4PG%r)A70u zGwc~xxSpNzi)LRKTvwk;&vq7jV9VB#Ph>GXBq8-|v+YEi%?Sga?ltSum$CHk7Z3lkx zhvKMi@T%80Ve?53mVMvc-|Q$nOr=MZ2H!BRdHyBg2as$ma6LEY8@8Kbi@;{*{QR)n zu}AS@pz{E}B}}IeVevN;M+SvC9W3YGd8Pb|;jg~&cK!RmdBZ;Q+FL)pZxjrUKY!rS zmu8Ot?XOS$=z~H_1Yx07Yqkzvp&>;(9XAJ?7s`zqd*{mt-c@0SLtj)JGYAsXPy}US zc$_8YHH`X&YVV`6{L^DPzOQ@~r|nW60qYfX4olxP*l7H${|K)xf`ZcD~5n87CY zPuaosXu<(mwS{9zyKSC-v{q@=t6a~4`C3ELP$<<#H_xXW8PX~@pUlD>8suYJP@XJ1 zwi%3JQW%%}N}QL>uzjv4GlSQIi$ljTcN`HHw+tkz6RY+Ju2j}h^*}+8x5@z#E}8P0 zWux$=Jd-)sbGm&zOUQhVYjmm$Ro!HUnCOep-@lo%N}D^O>mAeR>bh{dVedAIRlOk$ z`_a`(oT4;Nrk{n6u9kgae*W~ydn`ahuukRcn@jTxOADo?`Poy;rSfd4R4vY)uT@UW z7K^8fORFasd?>kD;nwPa7#F{bOk+oAP*Tz&?$96g6VvnzQEv_Ox82w z4bKve%6~mGM$KAtydBel8c|ivf_RLS=@BiT0^2f!Q21G*2}o@eqSHfhMx!zierQkA zF2e0MMF<_^nd~hli(S-m#8g-l56M39SW;m=MLzuJxuNelj_@}`e+NAZtUtx*k)&zV zBx)KQ!9lgl^IN{zzK(XQ=2BftI>G5kUP0VrQPz@^APEsxIh-IH11cd|stE`I##ZT5 zVD+G7_eI^^ZdIb1orUn|fKN}2R@3eeWVKO!lf>|5&m4$^D6X^(&)M&KF4t4EqD(g) z6>dq8Lh1P3$b{{NL69dikS8UiIY4S5#LBitDzHu?o~!145uGEs2>mB`FS9hz4~(Dv;C zIV2977>zSd2Bw7|satfjFt~7cZJ*ZUEUZQM9PPy1ymsvT5RVnwGG105(avbZ1IDKa z)#%w{t~6I#TsVQ?NO&2XM65$k!~}j15fl~l7uv4r3e+#YzhO^kv`mi?G@)@U zsDlD9yC+bs!_zySV<}1IP7tC~2`857i6kAnjipW-lY*oe^h2#fUtwhT)Rv%th$1Wx zgz2|?xU6H%_2|mpGP|IOXUK*O4ZMo#6@;>H3qq_K24darLsDd@SjRhQOvv~+26bp( zGJrpkNfQlan<kRbBubY%u-6l(5y*FsI!=yt2R5Q^!Q8D&wB z?BUht{JMd&JsNR=HXablbv5F6oC|vZEpj~`^1vHP7+!7SAr~WJPU&r+AZBK`x5qm{ z?N-~wAlbMa#nq~BeHuk2=GwQ=YxuGa@o`zmX7u7jM#sdK9fYcs(kME!c}Jez6UXiN zX{SYfu_kYHTu+yg2{QcTM2YEJ7yR{IoLz04W7$1U7O%Aiui?7rqFY?oP~h7~u=o(B{n z2|`6hB-!AhH(1|w4Bqkv-n!V$3YsjtF|nRgM9=9M3@p!-;+?;kW$*T>qxE{hDccYO)rf(Rnv#n7%d5Lf||k22_;%_ zghFvgp_*m+!b!{sBWd<*E9~W%p$>vV!ifSc1nxjN3g{8TB@fxoO;Ussu6AOHPL80C z4&8T6N27qme>!OHkRB)Iq--D5$+EoY8p1lE!Rbi4SY98+WQb2rL=z9NFfwk%+R(Kz z*7kb1@kNrTi!ejYglKaZ;=g=C8%?RRuXTlsTOl3{uovP9SEWMaP)Sw4CwvxN<7o-u zB-NzDQmR{dYb|-FC3d%PnRLjf?Xo!1sWi~*@tyQl`T`JWaK7#0Vl3OdC_I8s;Ed$y z_@nqFOMENW#4pXaam*miYlUJoyH2E7xhkGecA{^#9WT1sn@jbN~PV literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000004.gdbtablx b/tests/testdata/gdb_metadata.gdb/a00000004.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..48521ed0dab9d72565d6f82a2d5d75c3ecf5380c GIT binary patch literal 5152 zcmeI$!3_W*3sk5#sx(8MC^8tlT8R)KK!5-N0t5&UAV7cs0Rq1Z+&b^#bThjF D7A^ox literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByName.atx b/tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByName.atx new file mode 100644 index 0000000000000000000000000000000000000000..845ea3996ea8a7191a738c35457d76db79965f32 GIT binary patch literal 12310 zcmeI2S#Q%o6oqftx0bSRr8WI0C?&u{5o#iMZqf)M(xkFeDF23^%6G<&tXl3%q-N&f z+B%*kiG6(To$XG{%sSv2xDKxJ=-bGC+SpF)$QCxCl-YjBA6Y^Df^y9BD&O7j?FzU8 zu7E4x3b+EUfGgk%xB{+#E8q&mR6uL~mlc8Aq;J4oum$$O3-Aow0C&J^a1%TPufS9A z80a4FKnLia8{jR-z%FCmRE{9~^A>t}bXSj5!k_5)5S#U{kE z3$J6;Iwt=Ou2VY;+U--Xn99UvQI$+huiCO@E)|`cyq4t;c227pW5ohn&Vw~m(zFLR zMefdM!D_QzJW!FlBC6Ld)yu5OFD327vWHElp#|rOC{}By+()dcoO z=k_Zy>rZgf_#rNe9;*k;?0{6<##}GH&$>&WL+d?>%;NI5K0vtwwbF~cmzceZ6+`kfPPp|nkd$RgFF>|TKbDCNV@VuNeex#(ytSk>0Ipaof+IcN&2lV|~!4A2e zC)fLD#1rStKg3Y+JM}SXo!{Z5m1+S?ML<& zbp)%h2J5f^o3I7Dum=Zl2q$m}S8xmW@Boi6k9P`Za1J+c2hXq#Pp|{mumbyV0WWY2 z%Uq-SESmRUHj8u9Xt<Won2+B96{j%E literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByUUID.atx b/tests/testdata/gdb_metadata.gdb/a00000005.CatItemTypesByUUID.atx new file mode 100644 index 0000000000000000000000000000000000000000..dd4ca1d59b190333d0a7bcfb8d2b436d3e3b5c7e GIT binary patch literal 4118 zcmeHL*{)kR47^>FE@_i=U(&RF$QuDkq)6(K5~ZIq{O_GpAfJ$@_yA)Z*gRZjhBEA@ zr>CcXf$Mq+`~$oIJ_SAjegb|6J_mjP{sevjegysiz6Y-7zXJXSUIU*2zXD$a-vZBo zpMh7v8{jYC3*bxO8{j$c8}J?Q7WkO2Tl0*ueb6~xNK%u0G$q-)&8k(+@_yq!wbWxI zeQlCBha#tuS!v16W7c-R$xeyxo3zzKQiN&rjfB0N8Pd<1>wfEVsCG3v(~&T9(_o)8 zWwJ@*&I1+koT>#ftQaH`jl8r$Ir=<}t;ihrn-yd8GEkx|^i>eQTcfu2+JQ>*1ftlg zhbqOH$Ti(5datz3i4wauZ3R(s#R*ER*-2VAFj0i&uqK;ZbiSgLyLC2VRw3~K6EZiM z^Rm@^_BF34s~ybHH3bQ`PHu+yR#GsR>|%(r2CA=~xqvV0NwMY7^kL;lJ3QoyvU--` zvx_A;$m$IV6+BhjBPv%!xy>Dn#=}u@Txq5_^-{ohl|H-QZ!V=rCc4Sv=%!)D6N~t) zNc6<)iYR3}^%8Dje({SrC_r^hZ|;+qGNKl%LwAbW;E83>QQ2vYrLuMG;pct}F1S3Y zKoE4VK_d_DG>*3soCnUqXa$50f=3#` z33*3iMwli7-G``++8~tn`z;0cRU5HXhK>p*GK5;}Gv}&07+%dDQa~TU#^}qysxPmU zn~&Do2Hb}z*IwEgG^|6Rz7)9{lV;W^wb&FmCx=U`ds21>UxuOyfbf2?M z9TvoM!V~S{-mypu7DATBPOzGgYw8yG{&FGD#44Unr#1GT>y&=YxW+y>45$W{QhI>Z zSV>$sdc!GD61%;XOT%`PRE0tUs^(_I@7f36idrz^!Ors5L)sUd(5sMh?X)%zRE*?# zMLF2SE>eTG@C3X)&mM$GEQUeMihVp2-pIswft`?oLkSxZwAbRS6{~ykbxsy|#OZ8< z+8d#x*usI}+oSJ=l=qMMd*CtqgMkMF4+b6#JQ#Q|@L=FWG4SsHU-{QB{rCR@T=)M0 D58*J{ literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000005.gdbindexes b/tests/testdata/gdb_metadata.gdb/a00000005.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..bc887093f340449a4e4f40224ff3a487814c09a8 GIT binary patch literal 296 zcmZQ!U|`?@VmAgC27iWlAWmY)Vn}63W+-9s1d1^*2mtZ_|BOJA1Bfx@g@B^Y42fX% zB|x3I3?U4a3dhbTnfm;XF>qb^dg{nc|bd0W}}-3 X@&j&lgxu%{v>_L00|Nu98xi6F*S08% literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000005.gdbtable b/tests/testdata/gdb_metadata.gdb/a00000005.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..98456f8cc8b154a855a6a36ec92cd4b129652a32 GIT binary patch literal 1803 zcmZQ(U|>)J;wT_y1!5TBW{0pepaLLy77&{eM05BvBr#+$q%tHklrVTQxG*rVu`scO zGK4}nTxu*l0St)@ML-pKKoubjl?(+yHdrN4lEn`wnF|zPU|9e*S06|&PQMnna?T=` z2F;t%pUhcyCMiz5B5ac9K6i=M``rgKE%|~Hi%U|A6kHNZ5{pwybb(sZQ>R6@|Gwut zxqHjz66N5zo%3#KH2lx??Nsx7v@tQgmM6F}FIgcdwIC-mInfxX;o)+&ZPCBxIJeBn zo1@YgT#c?lC@3{2u_QA;uQ(&KK*2dDvAEa(sLivmxMb^{aHmd|oke~|OAXMq@w=ra zmXsEy!rW#A)bnIp${qeYd!4s@{iNJ_=R+vE9zl=H;*$I#pu=($d=rbZQ;ST1y2`)J zyR%qoxog*11FImW3wO|U35VwwWfvDDCZ{U6R+OaX6$9O>2h@iiSm^qA9g~VniV~Ac zLK2g5Qq_Sv^6e@tTYmJaJN2)zKXs4YxQ)#%KPM%%2o!2U z-DW*{3+sHQS)4rPm-({)?ML%AmYy_^C0;T+G`ek)g`D$KQd1Pd5_3v{L6D!Dn3<;q z)W-j>_*g@czeB5$LTT)cKWyl3;)MGJ6#c1c2G6n`f4S^4_7k0$y^s-IHD7RPQCVsw zEQr;BTCDf5ot;||=(FnAlJtq%ovG-7zzX&(C-)Omj(%oGO3^==Qg1{|Kju1DIrriMfocrPAt@TF0Tl6>#tn-P#V332dLaD% z59i%tW;M-n{>icK>-=|2=Xd%%g*PCvC^E_eok=>ioQ9V_P^vg`To+8rbDRPOn{E$C^PCnzy59hQng>GHI#;|~udQ_uY` z4dX5A0tL{m<@3!iPDFKCT32E5&h;JMO9RjU;(Dm2fZ;Ot)coAk5@38NK*J9d)~2tj zE84vLJ$HKqa8DD7jzG7F-x*l&<>aTse4`1}b2+H6EbXnTb9;#suUonwJGxCAA^8RQ zKs}Wp3tlU>+-$lk>^`N%*}#`W;2XMPerP6uSpZ5bRhiPaw^?U7%~+K8e95tOm(lgG zhvetyB;{9t9D|;&(K84~P-=00DKIsf0u}cH3;b6+S^jf>xvg)SW%~);w<18t6r~oY y=9PfUV@O#HijVJ%+f2{7{`XqDy`1TC^K2pXd@mD-4kq(G7A8=`?jwD literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000005.gdbtablx b/tests/testdata/gdb_metadata.gdb/a00000005.gdbtablx new file mode 100644 index 0000000000000000000000000000000000000000..0feb604c7ee4d7ff8123e9941ce44fb5f67e1c9d GIT binary patch literal 5152 zcmeI$u?fOJ97gfeL`w%y0_qU1U?(e7`0| zj#plmQX0gTu~Dpwr5n6p!M7dM(7*@g!(b05xIzb$ELgz-YIwjKrg^Y|5-!j~!Oyq9 w8^9f&FdGMJIKmlP@L0eC7O;Q?EMNf(Sik}nuz&?DU;ztQzykji;C+vAzJ{<8YXATM literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByDestinationID.atx b/tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByDestinationID.atx new file mode 100644 index 0000000000000000000000000000000000000000..f01d979ce5346dfdf34d1561d783a2a94661b9ed GIT binary patch literal 4118 zcmeI#F%Ezr3BeY4O(QM(!bS6m$1& zF-^8^I)@lE?`AsHx9lZa3gPVWGiD-y00IagfB*srAbe-Kytn@E=HL1Y@lFX8 literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByType.atx b/tests/testdata/gdb_metadata.gdb/a00000006.CatRelsByType.atx new file mode 100644 index 0000000000000000000000000000000000000000..5143e308957bc5f55b65a6a85e368f019ade3d0b GIT binary patch literal 4118 zcmeI#Q3`-C2nJxgOm9MCg1xGqbxrC&7TT(-h5u0Hq1Rwwb2tWV=5P$##AOHaf{8?bWum11l-|G|cPzhTA literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000006.FDO_UUID.atx b/tests/testdata/gdb_metadata.gdb/a00000006.FDO_UUID.atx new file mode 100644 index 0000000000000000000000000000000000000000..be43e74d5d30cd2b689e1d28db7253d14c2cae71 GIT binary patch literal 4118 zcmeI#%?W@o41m$PjJH64r5J7AbTIYbHordSM0&Zv3dAtL4iW?c4G0C6X92N+;_M6@{tQVBSq!NR$qXe7o(wJw3~Ve+ zETIgc5DvE*6HrAFLncEy5a&Szxzt$LT!31NVOoF!EFlb)3bd^TFzhfWny&2}TxH23Mei{@!1_HR#pECg;X08x|$pVmuRY_RNFY z1rr_jGuQ^~dC4WX+i_FrwyK++Gfo`KwBCPt_3Domcb=H6ao*)F-MA9039Z2oNAZfB*pk1PBlyK!5-N L0{;rUe_$^>!R-Lc literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByBackwardLabel.atx b/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByBackwardLabel.atx new file mode 100644 index 0000000000000000000000000000000000000000..55707ef3b2b0fad1e1d9cd108c1f0b33f7ac9d6a GIT binary patch literal 4118 zcmeI0Yfghe6opTHzh5h0FKuf4qek68i>Sm}8j3OQ#-;k*8Brlt5|aWZFgY+opnN@d z?r?)7ogmu|(K^~jTWAOEqZPD??7AL0K&xmEt)U}yj5g3NTD0}`pjZ>#D$r1g>qSZ# zUFeQ|)2$EFRGCIlP8B)MGkQiFaW>O4w2=m`Khm6jRwMOqeBa${%EzDh%*miJ$BIt0 zov%Evd{$iR6%WG}xLD>(T-IKZ1v7FvhS74w=S}(AVkDL`8u08n1UBc4BzF1SmenQD zB~+>73SpalxHTuTew8_eKhYEWjO)zLK<94s!ueE>jF;J}_{uDG$N0bVdH#@yPKe`> z_>Vv1yv(@fzg^{D+xdzsP5!UyJ68GkwNf`jTE%z6%u8v$zNyc*{7Kb(39F)2GKuba z>*e>=b@Wk7amgw>s|5YKtXTa8>O*MNpRL?=wncSycfNC=bD(qJFAlr~J#I7QUjeND E4z#NyO8@`> literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByDestItemTypeID.atx b/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByDestItemTypeID.atx new file mode 100644 index 0000000000000000000000000000000000000000..384a22546579cb12fb79b3d3a7c00fb9ed9defd5 GIT binary patch literal 4118 zcmeI0!EPNP3`JkkG;NZ$N&6AqSpwS_vosifYBc{|ze?Fh*(3`r9f`#qfsbbcm&@hy zC%D#a@HO}rJO)35o8U`uAN&rk*@xhJ@D%(Aeg!YVUGN<2dcT1K)vZl!u$CrZ7YA)9 zz;j~HlBHHZKW(a}N>x(>EjWNJ5wwX2IOdpEdh5&1PvwUE9kNTw1)YN z)=1?%RH&{zV=f~xu!?{&8|2XyRXYu#^Ds(L>X4#w;3WplSFFUAVl(oE=P^74df? z=Rfm@AW1#&u?vULg#$Q(V>pFFBFep@kllah@yalDV(v zns5#+kN>Ez)XS%zZMzp#_3xUhPEgpsHY>P7i}UuX88hq6m)Ua??^u~G*PX>xN*CnR ziTx|}H>fG#83opK&wPYNYmH&Vu5Up7GTd`_2^#1r3fI<^yQQ8^p3~(tmeh94^(+## zt38n8-y=%BbH^x)r$;A!>nt^y4e4;MYsP2Jc%!;M_w;W)vdKH^+>`XxajW{>e`E`*HGUYK<0Mce#gx|{9% z{by!pgNVo#&}$bQfh}+f_Q3(r`3~3ymtYT^gF~u11F&D>PlaxU?Lg6w=$L< zmX3Xi!?N><#J;Wnz^{;8Uf|}UvswLm%kN%Z9|FdWJN9qs(;`npG~f93ZiGon1$ zIJ5BOmfysym;3z7h;b_KM7bar->~Y|YTf(0cpt%Mh-R97mhZY1amQ@6UXyoP%M;J_ zhD=oUTG`I$X89mX1ovP_>8sJXbMhgsb?aFmv1?J7cn1 z`l{wvuoLQTT~NQXDEYQz7ObB4AG$^Jx<^8tPKn}Z-g%qU*@Avm-Awhg^MBQ^|2g<~ zXs9QcF>31BWk}S=qTQ}6KkH`}Jgo1ws@!5`vkInuU(3->IW=KNwvn;?ny@2Ver4zC YcDz-fRiIViKMMRL9hNB;{{pD}3!1qdng9R* literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx b/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByOriginItemTypeID.atx new file mode 100644 index 0000000000000000000000000000000000000000..e9098712c6d5fefe1ef069cb587a5a7da0a7b6d3 GIT binary patch literal 4118 zcmeI0$*SEj42DnVd3=cO1h!@`ZOeYC!+G~Ah3+qxLN_*i5Xf)I`jSm@yWMU-lWV<8 z-Y2h<@5!g+y7wmek-SY_Bwv$P$>-!t@-q35d`!M2XFcD*9X)zm-e7Mpz@Y)&P=VLN zQMIaBJf4{172L%aDAo;Jn4rnZf>QSAEPIamxXWm&q6T}oWye_1$OjA_WQ-)#vZECv z^U`uNPT*d$)bMIdIs;F2p#aE6S#2f;QVmV`B z7|oCuVHdhmN@79=M9RS4H*_8g%xT(0A2P?4vMLw^E3z#U3b`9cuVR@pt9x2N zzb50IdB==u&Q@);Ua3Acl_g3ZsX_Wf;HCtluL70U-m|m_qsRSesc@BoyFoK?V3`Un zkU%49FSXduMfw`!;L=PG{ t<3CJ2|1Cc|kAY+07&r!wfn(qpI0lY^W8i-n`1R+D{`#u_`xoH4{td6Kpg{lt literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByUUID.atx b/tests/testdata/gdb_metadata.gdb/a00000007.CatRelTypesByUUID.atx new file mode 100644 index 0000000000000000000000000000000000000000..30cdc5038d2de78230c0f3e1b12209ca0634d83d GIT binary patch literal 4118 zcmeI0Nsc5T3`MKwc|1h#EMaWGENu*zO6A@AXGwj5+Vmzu`jR0W*#E(XczSwz`VBpv zAJ9kW7xV`D1bu}*K;NLZ(0Aw^^a}b3y@uXHk2O9+U!d!GIe}YAE+Iasp01>AcZ%T) z!&OI|$|CE2nOC2QO&_57I4_+!mtICT~QX8(Aja z!kwfwti`^`+>My64W38LCbBZiVn0|*lJsmRTgb;S+;WVSfvhu&wOweS2U2NDzGac< zLY1+7nBH%rqF#Dnx>eH2K|W@q){fNdqt*$JV=i>K4E}SXFJ+pAm7>d#U8Q@Vq9q9U zVOSrFv_CvL7m~G9qpUs|715Pz9|fiu9&L59+9$Cnu*un-$NlEq1uRgkhm)!r*(jaT zJa&y5rGi&T4QpMf#Oz>?inA`IVqL5Fp0n5cZLMlzkCv1{A`!V2xMfr+!sG06zh$X4 zw+J>mK(*>3vpCd(qsMF`68o41lMGWlOCYOG3fLu^Xg*n!w-eEl9l)@8wnelmHhfSvslCKT4%fW-MP=X_q|P^Ejie@ ztR;^lJA7iMszFamlA7Kv>GywM)d?H<7g`sv6;HpM-C2SERT9zjtKJvR}JS&t08k_L_S#SGPiCL9r)!ePGhfzngFmn6@ByF@Pra_$|vaxUXz5{NhU}op`{l4ej=Y5~| z{oaoW05A%LzxMgPGfSo za;gzHpuvh31cb|l3@Cs+^d_N2fEF3N*;PV!hOjvVp{-WRdvJMP|=qOUy%uyN}$@2tbpoDyu z+oUB0lBa~w^gL3aNt>-ettT1MO7Ut8S4;TNS4mEwO*BHGErL;FcQy70C=_Fg?wQ|l zHgRo2`PW&qH|^cp`Rlymn?=_GllByAmgv3w|EM^4D`NZ8Tzy5j;chc&-KN6sMoSu% zXQNFdwx?lJpv*3YIe1IWTGY?(0#u3o4Oz?Ar7k@YUs)eeV*hwq1s%P;?%W0w(b^mM z`xEbOUarY3cTG@|Cd#HGvpL!{o+5MPia4FsPH%uMVmt%0T6PuQiPAOr$F-&S&_VAU z`N0sHt$Af{e~Djf;*&FSeN^r;5Dd1*&_gBX$4~`0MCSF&O|`AMWNq2R8;y5A{p3Z$ z&I>2IUQ4J@p5_cuN0$6oB?tyfg5~ztu#7;`4DYJLY`lOfgWA9ytbiRNw|{ea$CR1R zC!GDW;-BuIt9_km_AQqF1)4G;!B=NBOp;e2*u4rFY#wW4tp(#%V1`@)mm{F#g~Dh& zp2elb%qEWR&3yENb^b^4l;w6XPMJ@mY^cr6895xg*_z{Rp>`^t-$7q#Z0D)W|>EKM-50tg<8@UK8>~#LhUApO(!!+Awn%1NcLu1E- z3vw2DhJH9m7tu34GiFK(C=DfIoTGROb){Gbqh)mSxMrMlXo+)T6m;gWiFy}%Ie~!2EefT#qxOpl* zzB=gh*i~U0zx>OCy0Pwq^NBTKxmOSQZ%vZR9fajJTG9&`6E?=05O@o|T9eCBHx9*-ppVx?ddb`Hp{0NGJ1Xi@&dV@%_Y8#}=R4`YW`ZirRS%~p>3$9QWGOv;hesO%OWm^Y76!C9`UtrM84H)rS@f& z{oSK~f9Gc{LRfl@T<962^duP3z%$inw0t4*u_J9_qypX+we5QReWt2C%20nHY5m+P zdr$1NxzoNSjj`Sj6EOfVS~$Dt81-T-{&3c!8z^+5I_I{1Q0*~0R#|0009IL OKmdV12>AEU^Z5W~ga|nR literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000009.gdbindexes b/tests/testdata/gdb_metadata.gdb/a00000009.gdbindexes new file mode 100644 index 0000000000000000000000000000000000000000..cc24e2a06b9b9cd5f6fe51035fb99fb73263853b GIT binary patch literal 116 zcmZY0I|_h600Yrc6cjAHl3&(sR?y!2pJE*gL9lo!5>f)4R%Q~HMj{InCz~Dg%DFHb V{rA@PeIEn{cK=Wbo?or+as&A53=;qV literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/a00000009.gdbtable b/tests/testdata/gdb_metadata.gdb/a00000009.gdbtable new file mode 100644 index 0000000000000000000000000000000000000000..00f5fc27466799537042191566f9935745ef73a6 GIT binary patch literal 302 zcmZQ(fB+^&1_o9r8${^=`5>SHp%_5&EI>93Gl=H!XK-ThVsK?}W(Z;MWN=|%U}Iro z4QB9QaAXJo3Nx@Xu=_An0~HxDSTL9Yl^Fm@Ck9<0ZNXs9Usk5#sx(8MC^8tlT8R)KK!5-N0t5&UAV7cs0Rq1Z+&b^#bThjF D7A^ox literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/gdb b/tests/testdata/gdb_metadata.gdb/gdb new file mode 100644 index 0000000000000000000000000000000000000000..a786e127004dd9e94e88fda7742d248237ad8885 GIT binary patch literal 4 LcmZQ&U|;|M02lxU literal 0 HcmV?d00001 diff --git a/tests/testdata/gdb_metadata.gdb/timestamps b/tests/testdata/gdb_metadata.gdb/timestamps new file mode 100644 index 0000000000000000000000000000000000000000..feee3e508cb6c286b4b4eac185db25473f22d2e8 GIT binary patch literal 400 jcmezW|Nnm$1_lNo^&dzv@BlG05Hmq(koYK|UkCsItRQx< literal 0 HcmV?d00001