From 93abde2c138282295ae3bd5b655fbee22e66a7d1 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Mar 2023 15:33:15 +0100 Subject: [PATCH 1/6] scripts/clang-format.sh: exclude frmts/jpeg/libjpeg/ --- scripts/clang-format.sh | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/clang-format.sh b/scripts/clang-format.sh index 13e4de20b77a..88d06972c1bf 100755 --- a/scripts/clang-format.sh +++ b/scripts/clang-format.sh @@ -38,6 +38,10 @@ for f in $FILES; do continue ;; + *frmts/jpeg/libjpeg/*) + continue + ;; + *swig/*) continue ;; From 751bde189a9688ef64c79f7c52b92f92bd20ad45 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Mar 2023 12:33:51 +0100 Subject: [PATCH 2/6] Internal libjpeg: decompressor: initialize Huffman tables to avoid issues with some FileGDB raster Ported from libjpeg-turbo commit a113506d175d03ae0e40965c3d3d21a5d561e119 --- frmts/jpeg/libjpeg/jdhuff.c | 11 +++ frmts/jpeg/libjpeg/jstdhuff.c | 133 ++++++++++++++++++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 frmts/jpeg/libjpeg/jstdhuff.c diff --git a/frmts/jpeg/libjpeg/jdhuff.c b/frmts/jpeg/libjpeg/jdhuff.c index f3621ed74904..731b99594ea7 100644 --- a/frmts/jpeg/libjpeg/jdhuff.c +++ b/frmts/jpeg/libjpeg/jdhuff.c @@ -19,6 +19,9 @@ #include "jpeglib.h" #include "jdhuff.h" /* Declarations shared with jdphuff.c */ +#if BITS_IN_JSAMPLE == 8 +#include "jstdhuff.c" +#endif /* * Expanded entropy decoder object for Huffman decoding. @@ -649,6 +652,14 @@ jinit_huff_decoder (j_decompress_ptr cinfo) huff_entropy_ptr entropy; int i; +#if BITS_IN_JSAMPLE == 8 + /* Motion JPEG frames typically do not include the Huffman tables if they + are the default tables. Thus, if the tables are not set by the time + the Huffman decoder is initialized (usually within the body of + jpeg_start_decompress()), we set them to default values. */ + std_huff_tables((j_common_ptr) cinfo); +#endif + entropy = (huff_entropy_ptr) (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_IMAGE, SIZEOF(huff_entropy_decoder)); diff --git a/frmts/jpeg/libjpeg/jstdhuff.c b/frmts/jpeg/libjpeg/jstdhuff.c new file mode 100644 index 000000000000..1efc04bf461b --- /dev/null +++ b/frmts/jpeg/libjpeg/jstdhuff.c @@ -0,0 +1,133 @@ +/* +* jstdhuff.c +* +* This file was part of the Independent JPEG Group's software: +* Copyright (C) 1991-1998, Thomas G. Lane. +* libjpeg-turbo Modifications: +* Copyright (C) 2013, D. R. Commander. +* For conditions of distribution and use, see the accompanying README file. +* +* This file contains routines to set the default Huffman tables, if they are +* not already set. +*/ + +/* + * Huffman table setup routines + */ + +LOCAL(void) +add_huff_table (j_common_ptr cinfo, + JHUFF_TBL **htblptr, const UINT8 *bits, const UINT8 *val) +/* Define a Huffman table */ +{ + int nsymbols, len; + + if (*htblptr == NULL) + *htblptr = jpeg_alloc_huff_table(cinfo); + else + return; + + /* Copy the number-of-symbols-of-each-code-length counts */ + MEMCOPY((*htblptr)->bits, bits, SIZEOF((*htblptr)->bits)); + + /* Validate the counts. We do this here mainly so we can copy the right + * number of symbols from the val[] array, without risking marching off + * the end of memory. jchuff.c will do a more thorough test later. + */ + nsymbols = 0; + for (len = 1; len <= 16; len++) + nsymbols += bits[len]; + if (nsymbols < 1 || nsymbols > 256) + ERREXIT(cinfo, JERR_BAD_HUFF_TABLE); + + MEMCOPY((*htblptr)->huffval, val, nsymbols * SIZEOF(UINT8)); + + /* Initialize sent_table FALSE so table will be written to JPEG file. */ + (*htblptr)->sent_table = FALSE; +} + + +LOCAL(void) +std_huff_tables (j_common_ptr cinfo) +/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */ +/* IMPORTANT: these are only valid for 8-bit data precision! */ +{ + JHUFF_TBL **dc_huff_tbl_ptrs, **ac_huff_tbl_ptrs; + + static const UINT8 bits_dc_luminance[17] = + { /* 0-base */ 0, 0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_luminance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_dc_chrominance[17] = + { /* 0-base */ 0, 0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0 }; + static const UINT8 val_dc_chrominance[] = + { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11 }; + + static const UINT8 bits_ac_luminance[17] = + { /* 0-base */ 0, 0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d }; + static const UINT8 val_ac_luminance[] = + { 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, + 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, + 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, + 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, + 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, + 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, + 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, + 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, + 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, + 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, + 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, + 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, + 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, + 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, + 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, + 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, + 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, + 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + static const UINT8 bits_ac_chrominance[17] = + { /* 0-base */ 0, 0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77 }; + static const UINT8 val_ac_chrominance[] = + { 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, + 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, + 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, + 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, + 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, + 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, + 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, + 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, + 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, + 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, + 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, + 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, + 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, + 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, + 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, + 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, + 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, + 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, + 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, + 0xf9, 0xfa }; + + if (cinfo->is_decompressor) { + dc_huff_tbl_ptrs = ((j_decompress_ptr)cinfo)->dc_huff_tbl_ptrs; + ac_huff_tbl_ptrs = ((j_decompress_ptr)cinfo)->ac_huff_tbl_ptrs; + } else { + dc_huff_tbl_ptrs = ((j_compress_ptr)cinfo)->dc_huff_tbl_ptrs; + ac_huff_tbl_ptrs = ((j_compress_ptr)cinfo)->ac_huff_tbl_ptrs; + } + + add_huff_table(cinfo, &dc_huff_tbl_ptrs[0], bits_dc_luminance, + val_dc_luminance); + add_huff_table(cinfo, &ac_huff_tbl_ptrs[0], bits_ac_luminance, + val_ac_luminance); + add_huff_table(cinfo, &dc_huff_tbl_ptrs[1], bits_dc_chrominance, + val_dc_chrominance); + add_huff_table(cinfo, &ac_huff_tbl_ptrs[1], bits_ac_chrominance, + val_ac_chrominance); +} From cec81c70bbf0cedae992ae21f01d00dc20c26087 Mon Sep 17 00:00:00 2001 From: Even Rouault Date: Thu, 9 Mar 2023 12:35:19 +0100 Subject: [PATCH 3/6] gdalinfo_lib.cpp: do not list files of a raster FileGDB unless -json is enabled --- apps/gdalinfo_lib.cpp | 7 +- autotest/ogr/ogr_openfilegdb.py | 4 + autotest/ogr/ogr_openfilegdb_write.py | 37 +- doc/source/drivers/raster/index.rst | 1 + doc/source/drivers/vector/openfilegdb.rst | 7 +- ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp | 16 + ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt | 2 + ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp | 16 - .../openfilegdb/filegdbtable_priv.h | 18 + ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h | 264 ++++++++++++- .../openfilegdb/ogropenfilegdbdatasource.cpp | 361 +++++++++++++++--- .../ogropenfilegdbdatasource_write.cpp | 19 +- .../openfilegdb/ogropenfilegdbdriver.cpp | 10 +- .../openfilegdb/ogropenfilegdblayer.cpp | 66 +--- .../openfilegdb/ogropenfilegdblayer_write.cpp | 2 +- 15 files changed, 662 insertions(+), 168 deletions(-) diff --git a/apps/gdalinfo_lib.cpp b/apps/gdalinfo_lib.cpp index b4fdbe19f2b2..e670875f43c1 100644 --- a/apps/gdalinfo_lib.cpp +++ b/apps/gdalinfo_lib.cpp @@ -287,7 +287,12 @@ char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions) GDALGetDriverShortName(hDriver), GDALGetDriverLongName(hDriver)); } - char **papszFileList = GDALGetFileList(hDataset); + // The list of files of a raster FileGDB is not super useful and potentially + // super long, so omit it, unless the -json mode is enabled + char **papszFileList = + (!bJson && EQUAL(GDALGetDriverShortName(hDriver), "OpenFileGDB")) + ? nullptr + : GDALGetFileList(hDataset); if (papszFileList == nullptr || *papszFileList == nullptr) { diff --git a/autotest/ogr/ogr_openfilegdb.py b/autotest/ogr/ogr_openfilegdb.py index d4faf778502e..5c95d6653ece 100755 --- a/autotest/ogr/ogr_openfilegdb.py +++ b/autotest/ogr/ogr_openfilegdb.py @@ -407,6 +407,10 @@ def test_ogr_openfilegdb_1(gdb_source): srs = osr.SpatialReference() srs.SetFromUserInput("WGS84") + assert gdal.OpenEx(filename, gdal.OF_RASTER) is None + + assert gdal.OpenEx(filename, gdal.OF_RASTER | gdal.OF_VECTOR) is not None + ds = ogr.Open(filename) for data in ogrtest.openfilegdb_datalist: diff --git a/autotest/ogr/ogr_openfilegdb_write.py b/autotest/ogr/ogr_openfilegdb_write.py index e1322de33046..59f1e7934f97 100755 --- a/autotest/ogr/ogr_openfilegdb_write.py +++ b/autotest/ogr/ogr_openfilegdb_write.py @@ -83,7 +83,7 @@ def test_ogr_openfilegdb_write_empty(): assert ds is not None ds = None - ds = ogr.Open(dirname) + ds = ogr.Open(dirname, update=1) assert ds is not None assert ds.GetLayerCount() == 0 ds = None @@ -3232,23 +3232,6 @@ def test_ogr_openfilegdb_write_emulated_transactions(): # Implicit rollback ds = None - gdal.Mkdir(dirname + "/.ogrtransaction_backup", 0o755) - with gdaltest.error_handler(): - # Cannot open in update mode with an existing backup directory - assert ogr.Open(dirname, update=1) is None - - # Emit warning in read-only mode when opening with an existing backup directory - gdal.ErrorReset() - assert ogr.Open(dirname) is not None - assert "A previous backup directory" in gdal.GetLastErrorMsg() - gdal.Rmdir(dirname + "/.ogrtransaction_backup") - - # Transaction not supported in read-only mode - ds = ogr.Open(dirname) - assert ds.TestCapability(ogr.ODsCEmulatedTransactions) == 0 - with gdaltest.error_handler(): - assert ds.StartTransaction(True) == ogr.OGRERR_FAILURE - ds = ogr.Open(dirname, update=1) assert ds.StartTransaction(True) == ogr.OGRERR_NONE gdal.Rmdir(dirname + "/.ogrtransaction_backup") @@ -3283,6 +3266,24 @@ def test_ogr_openfilegdb_write_emulated_transactions(): assert gdal.VSIStatL(dirname + "/a0000000a.gdbtable") is not None ds = None + gdal.Mkdir(dirname + "/.ogrtransaction_backup", 0o755) + with gdaltest.error_handler(): + # Cannot open in update mode with an existing backup directory + assert ogr.Open(dirname, update=1) is None + + # Emit warning in read-only mode when opening with an existing backup directory + gdal.ErrorReset() + assert ogr.Open(dirname) is not None + assert "A previous backup directory" in gdal.GetLastErrorMsg() + gdal.Rmdir(dirname + "/.ogrtransaction_backup") + + # Transaction not supported in read-only mode + ds = ogr.Open(dirname) + assert ds.TestCapability(ogr.ODsCEmulatedTransactions) == 0 + with gdaltest.error_handler(): + assert ds.StartTransaction(True) == ogr.OGRERR_FAILURE + ds = None + ds = ogr.Open(dirname, update=1) assert ds.GetLayerCount() == 1 lyr = ds.GetLayerByName("foo2") diff --git a/doc/source/drivers/raster/index.rst b/doc/source/drivers/raster/index.rst index 2f6584409305..5be2351eed5e 100644 --- a/doc/source/drivers/raster/index.rst +++ b/doc/source/drivers/raster/index.rst @@ -133,6 +133,7 @@ Raster drivers ntv2 nwtgrd ogcapi + openfilegdb ozi palsar paux diff --git a/doc/source/drivers/vector/openfilegdb.rst b/doc/source/drivers/vector/openfilegdb.rst index 77eb206c02c4..7c1c65b89f06 100644 --- a/doc/source/drivers/vector/openfilegdb.rst +++ b/doc/source/drivers/vector/openfilegdb.rst @@ -1,7 +1,7 @@ .. _vector.openfilegdb: -ESRI File Geodatabase (OpenFileGDB) -=================================== +ESRI File Geodatabase vector (OpenFileGDB) +========================================== .. shortname:: OpenFileGDB @@ -23,6 +23,8 @@ Curve in geometries are supported with GDAL >= 2.2. Write and update capabilities are supported since GDAL >= 3.6 +The driver also supports :ref:`raster layers` since GDAL 3.7 + Driver capabilities ------------------- @@ -243,6 +245,7 @@ Examples Links ----- +- :ref:`OpenFileGDB raster ` documentation page - :ref:`FileGDB driver `, relying on the FileGDB API SDK - Reverse-engineered specification of the `FileGDB format `__ diff --git a/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp b/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp index 7e8c40c6e777..ba330f66be29 100644 --- a/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp +++ b/ogr/ogrsf_frmts/filegdb/FGdbDriver.cpp @@ -123,6 +123,22 @@ static GDALDataset *OGRFileGDBDriverOpen(GDALOpenInfo *poOpenInfo) GDAL_IDENTIFY_FALSE) return nullptr; + // If this is a raster-only GDB, do not try to open it, to be consistent + // with OpenFileGDB behavior. + const char *const apszOpenFileGDBDriver[] = {"OpenFileGDB", nullptr}; + auto poOpenFileGDBDS = std::unique_ptr(GDALDataset::Open( + pszFilename, GDAL_OF_RASTER, apszOpenFileGDBDriver, nullptr, nullptr)); + if (poOpenFileGDBDS) + { + poOpenFileGDBDS.reset(); + poOpenFileGDBDS = std::unique_ptr( + GDALDataset::Open(pszFilename, GDAL_OF_VECTOR, + apszOpenFileGDBDriver, nullptr, nullptr)); + if (!poOpenFileGDBDS) + return nullptr; + } + poOpenFileGDBDS.reset(); + const bool bUpdate = poOpenInfo->eAccess == GA_Update; long hr; diff --git a/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt b/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt index 34149381e601..5418e3f31680 100644 --- a/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt +++ b/ogr/ogrsf_frmts/openfilegdb/CMakeLists.txt @@ -14,7 +14,9 @@ add_gdal_driver( ogropenfilegdbdriver.cpp ogropenfilegdblayer.cpp ogropenfilegdblayer_write.cpp + gdalopenfilegdbrasterband.cpp PLUGIN_CAPABLE NO_DEPS STRONG_CXX_WFLAGS) + gdal_standard_includes(ogr_OpenFileGDB) target_include_directories(ogr_OpenFileGDB PRIVATE $) diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp index 17c7b66f8e03..7b675b63b309 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable.cpp @@ -684,22 +684,6 @@ int FileGDBTable::ReadTableXHeader() return TRUE; } -/************************************************************************/ -/* ReadUTF16String() */ -/************************************************************************/ - -static std::string ReadUTF16String(const GByte *pabyIter, int nCarCount) -{ - std::wstring osWideStr; - for (int j = 0; j < nCarCount; j++) - osWideStr += pabyIter[2 * j] | (pabyIter[2 * j + 1] << 8); - char *pszStr = - CPLRecodeFromWChar(osWideStr.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8); - std::string osRet(pszStr); - CPLFree(pszStr); - return osRet; -} - /************************************************************************/ /* Open() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_priv.h b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_priv.h index fb718bf2a50e..c4f3c8286879 100644 --- a/ogr/ogrsf_frmts/openfilegdb/filegdbtable_priv.h +++ b/ogr/ogrsf_frmts/openfilegdb/filegdbtable_priv.h @@ -31,6 +31,8 @@ #define FILEGDBTABLE_PRIV_H_INCLUDED #include "filegdbtable.h" + +#include "cpl_conv.h" #include "cpl_error.h" #include "cpl_time.h" @@ -334,6 +336,22 @@ inline void WriteVarInt(std::vector &abyBuffer, int64_t nVal) WriteVarUInt(abyBuffer, nUVal); } +/************************************************************************/ +/* ReadUTF16String() */ +/************************************************************************/ + +inline std::string ReadUTF16String(const GByte *pabyIter, int nCarCount) +{ + std::wstring osWideStr; + for (int j = 0; j < nCarCount; j++) + osWideStr += pabyIter[2 * j] | (pabyIter[2 * j + 1] << 8); + char *pszStr = + CPLRecodeFromWChar(osWideStr.c_str(), CPL_ENC_UCS2, CPL_ENC_UTF8); + std::string osRet(pszStr); + CPLFree(pszStr); + return osRet; +} + /************************************************************************/ /* WriteUTF16String() */ /************************************************************************/ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h index d36ae8118f8d..5fb4a05a1e2d 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h +++ b/ogr/ogrsf_frmts/openfilegdb/ogr_openfilegdb.h @@ -36,6 +36,9 @@ #include "cpl_mem_cache.h" #include "cpl_quad_tree.h" +#include "gdal_rat.h" + +#include #include #include @@ -166,7 +169,6 @@ class OGROpenFileGDBLayer final : public OGRLayer void *pQTUserData); void TryToDetectMultiPatchKind(); - OGRSpatialReference *BuildSRS(const CPLXMLNode *psInfo) const; void BuildCombinedIterator(); bool RegisterTable(); void RefreshXMLDefinitionInMemory(); @@ -410,14 +412,31 @@ class OGROpenFileGDBFeatureDefn : public OGRFeatureDefn class OGROpenFileGDBDataSource final : public OGRDataSource { friend class OGROpenFileGDBLayer; + friend class GDALOpenFileGDBRasterBand; + friend class GDALOpenFileGDBRasterAttributeTable; - char *m_pszName = nullptr; CPLString m_osDirName{}; std::vector> m_apoLayers{}; std::vector> m_apoHiddenLayers{}; char **m_papszFiles = nullptr; std::map m_osMapNameToIdx{}; std::shared_ptr m_poRootGroup{}; + CPLStringList m_aosSubdatasets{}; + + std::string m_osRasterLayerName{}; + std::map m_oMapGDALBandToGDBBandId{}; + bool m_bHasGeoTransform = false; + std::array m_adfGeoTransform = {{0.0, 1.0, 0, 0.0, 0.0, 1.0}}; + OGRSpatialReference m_oRasterSRS{}; + std::unique_ptr m_poBlkLayer{}; + enum class Compression + { + NONE, + LZ77, + JPEG, + JPEG2000, + }; + Compression m_eRasterCompression = Compression::NONE; lru11::Cache> m_oCacheWKTToSRS{}; @@ -442,11 +461,22 @@ class OGROpenFileGDBDataSource final : public OGRDataSource /* For debugging/testing */ bool bLastSQLUsedOptimizedImplementation; - int OpenFileGDBv10(int iGDBItems, int nInterestTable); + bool OpenFileGDBv10(int iGDBItems, int nInterestTable, + const GDALOpenInfo *poOpenInfo, + const std::string &osRasterLayerName, + std::set &oSetIgnoredRasterLayerTableNum); int OpenFileGDBv9(int iGDBFeatureClasses, int iGDBObjectClasses, int nInterestTable); + bool OpenRaster(const GDALOpenInfo *poOpenInfo, + const std::string &osLayerName, + const std::string &osDefinition, + const std::string &osDocumentation); + void GuessJPEGQuality(int nOverviewCount); + void ReadAuxTable(const std::string &osLayerName); int FileExists(const char *pszFilename); + std::unique_ptr + BuildLayerFromName(const char *pszName); OGRLayer *AddLayer(const CPLString &osName, int nInterestTable, int &nCandidateLayers, int &nLayersSDCOrCDF, const CPLString &osDefinition, @@ -474,14 +504,14 @@ class OGROpenFileGDBDataSource final : public OGRDataSource OGROpenFileGDBDataSource(); virtual ~OGROpenFileGDBDataSource(); - int Open(const GDALOpenInfo *poOpenInfo); + bool Open(const GDALOpenInfo *poOpenInfo); bool Create(const char *pszName); virtual CPLErr FlushCache(bool bAtClosing = false) override; virtual const char *GetName() override { - return m_pszName; + return m_osDirName.c_str(); } virtual int GetLayerCount() override { @@ -516,6 +546,11 @@ class OGROpenFileGDBDataSource final : public OGRDataSource virtual OGRErr CommitTransaction() override; virtual OGRErr RollbackTransaction() override; + CPLErr GetGeoTransform(double *padfGeoTransform) override; + const OGRSpatialReference *GetSpatialRef() const override; + + char **GetMetadata(const char *pszDomain = "") override; + bool AddFieldDomain(std::unique_ptr &&domain, std::string &failureReason) override; @@ -603,6 +638,7 @@ class OGROpenFileGDBDataSource final : public OGRDataSource return m_osTransactionBackupDirname; } + OGRSpatialReference *BuildSRS(const CPLXMLNode *psInfo); OGRSpatialReference *BuildSRS(const char *pszWKT); }; @@ -639,4 +675,222 @@ class OGROpenFileGDBSingleFeatureLayer final : public OGRLayer } }; +/************************************************************************/ +/* GDALOpenFileGDBRasterAttributeTable */ +/************************************************************************/ + +class GDALOpenFileGDBRasterAttributeTable final + : public GDALRasterAttributeTable +{ + std::unique_ptr m_poDS{}; + const std::string m_osVATTableName; + std::unique_ptr m_poVATLayer{}; + mutable std::string m_osCachedValue{}; + + GDALOpenFileGDBRasterAttributeTable( + const GDALOpenFileGDBRasterAttributeTable &) = delete; + GDALOpenFileGDBRasterAttributeTable & + operator=(const GDALOpenFileGDBRasterAttributeTable &) = delete; + + public: + GDALOpenFileGDBRasterAttributeTable( + std::unique_ptr &&poDS, + const std::string &osVATTableName, + std::unique_ptr &&poVATLayer) + : m_poDS(std::move(poDS)), m_osVATTableName(osVATTableName), + m_poVATLayer(std::move(poVATLayer)) + { + } + + GDALRasterAttributeTable *Clone() const override + { + auto poDS = cpl::make_unique(); + GDALOpenInfo oOpenInfo(m_poDS->m_osDirName.c_str(), GA_ReadOnly); + if (!poDS->Open(&oOpenInfo)) + return nullptr; + auto poVatLayer = poDS->BuildLayerFromName(m_osVATTableName.c_str()); + if (!poVatLayer) + return nullptr; + return new GDALOpenFileGDBRasterAttributeTable( + std::move(poDS), m_osVATTableName, std::move(poVatLayer)); + } + + int GetColumnCount() const override + { + return m_poVATLayer->GetLayerDefn()->GetFieldCount(); + } + + int GetRowCount() const override + { + return static_cast(m_poVATLayer->GetFeatureCount()); + } + + const char *GetNameOfCol(int iCol) const override + { + if (iCol < 0 || iCol >= GetColumnCount()) + return nullptr; + return m_poVATLayer->GetLayerDefn()->GetFieldDefn(iCol)->GetNameRef(); + } + + GDALRATFieldUsage GetUsageOfCol(int iCol) const override + { + const char *pszColName = GetNameOfCol(iCol); + return pszColName && EQUAL(pszColName, "Value") ? GFU_MinMax + : pszColName && EQUAL(pszColName, "Count") ? GFU_PixelCount + : GFU_Generic; + } + + int GetColOfUsage(GDALRATFieldUsage eUsage) const override + { + if (eUsage == GFU_MinMax) + return m_poVATLayer->GetLayerDefn()->GetFieldIndex("Value"); + if (eUsage == GFU_PixelCount) + return m_poVATLayer->GetLayerDefn()->GetFieldIndex("Count"); + return -1; + } + + GDALRATFieldType GetTypeOfCol(int iCol) const override + { + if (iCol < 0 || iCol >= GetColumnCount()) + return GFT_Integer; + switch (m_poVATLayer->GetLayerDefn()->GetFieldDefn(iCol)->GetType()) + { + case OFTInteger: + return GFT_Integer; + case OFTReal: + return GFT_Real; + default: + break; + } + return GFT_String; + } + + const char *GetValueAsString(int iRow, int iField) const override + { + auto poFeat = + std::unique_ptr(m_poVATLayer->GetFeature(iRow + 1)); + if (!poFeat || iField >= poFeat->GetFieldCount()) + return ""; + m_osCachedValue = poFeat->GetFieldAsString(iField); + return m_osCachedValue.c_str(); + } + + int GetValueAsInt(int iRow, int iField) const override + { + auto poFeat = + std::unique_ptr(m_poVATLayer->GetFeature(iRow + 1)); + if (!poFeat || iField >= poFeat->GetFieldCount()) + return 0; + return poFeat->GetFieldAsInteger(iField); + } + + double GetValueAsDouble(int iRow, int iField) const override + { + auto poFeat = + std::unique_ptr(m_poVATLayer->GetFeature(iRow + 1)); + if (!poFeat || iField >= poFeat->GetFieldCount()) + return 0; + return poFeat->GetFieldAsDouble(iField); + } + + void SetValue(int, int, const char *) override + { + CPLError(CE_Failure, CPLE_NotSupported, "SetValue() not supported"); + } + + void SetValue(int, int, int) override + { + CPLError(CE_Failure, CPLE_NotSupported, "SetValue() not supported"); + } + + void SetValue(int, int, double) override + { + CPLError(CE_Failure, CPLE_NotSupported, "SetValue() not supported"); + } + + int ChangesAreWrittenToFile() override + { + return false; + } + + CPLErr SetTableType(const GDALRATTableType) override + { + CPLError(CE_Failure, CPLE_NotSupported, "SetTableType() not supported"); + return CE_Failure; + } + + GDALRATTableType GetTableType() const override + { + return GRTT_THEMATIC; + } + + void RemoveStatistics() override + { + CPLError(CE_Failure, CPLE_NotSupported, + "RemoveStatistics() not supported"); + } +}; + +/************************************************************************/ +/* GDALOpenFileGDBRasterBand */ +/************************************************************************/ + +class GDALOpenFileGDBRasterBand final : public GDALRasterBand +{ + friend class OGROpenFileGDBDataSource; + std::vector m_abyTmpBuffer{}; + int m_nBitWidth = 0; + int m_nOverviewLevel = 0; + std::vector> + m_apoOverviewBands{}; + bool m_bIsMask = false; + std::unique_ptr m_poMaskBandOwned{}; + GDALOpenFileGDBRasterBand *m_poMainBand = nullptr; + GDALOpenFileGDBRasterBand *m_poMaskBand = nullptr; + bool m_bHasNoData = false; + double m_dfNoData = 0.0; + std::unique_ptr m_poRAT{}; + + CPL_DISALLOW_COPY_ASSIGN(GDALOpenFileGDBRasterBand) + + public: + GDALOpenFileGDBRasterBand(OGROpenFileGDBDataSource *poDSIn, int nBandIn, + GDALDataType eDT, int nBitWidth, int nBlockWidth, + int nBlockHeight, int nOverviewLevel, + bool bIsMask); + + protected: + CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override; + + int GetOverviewCount() override + { + return static_cast(m_apoOverviewBands.size()); + } + + GDALRasterBand *GetOverview(int i) override + { + return (i >= 0 && i < GetOverviewCount()) ? m_apoOverviewBands[i].get() + : nullptr; + } + + GDALRasterBand *GetMaskBand() override + { + return m_poMaskBand ? m_poMaskBand : GDALRasterBand::GetMaskBand(); + } + + int GetMaskFlags() override + { + return m_poMaskBand ? GMF_PER_DATASET : GDALRasterBand::GetMaskFlags(); + } + + double GetNoDataValue(int *pbHasNoData) override + { + if (pbHasNoData) + *pbHasNoData = m_bHasNoData; + return m_dfNoData; + } + + GDALRasterAttributeTable *GetDefaultRAT() override; +}; + #endif /* ndef OGR_OPENFILEGDB_H_INCLUDED */ diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp index d74c52d87752..51350e85d7ca 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource.cpp @@ -97,8 +97,7 @@ class OGROpenFileGDBGroup final : public GDALGroup /* OGROpenFileGDBDataSource() */ /************************************************************************/ OGROpenFileGDBDataSource::OGROpenFileGDBDataSource() - : m_pszName(nullptr), m_papszFiles(nullptr), - bLastSQLUsedOptimizedImplementation(false) + : m_papszFiles(nullptr), bLastSQLUsedOptimizedImplementation(false) { } @@ -128,7 +127,6 @@ CPLErr OGROpenFileGDBDataSource::Close() m_apoLayers.clear(); m_apoHiddenLayers.clear(); - CPLFree(m_pszName); CSLDestroy(m_papszFiles); if (GDALDataset::Close() != CE_None) @@ -155,18 +153,46 @@ int OGROpenFileGDBDataSource::FileExists(const char *pszFilename) /* Open() */ /************************************************************************/ -int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) +bool OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) { - const char *pszFilename = poOpenInfo->pszFilename; + if (poOpenInfo->nOpenFlags == (GDAL_OF_RASTER | GDAL_OF_UPDATE)) + { + CPLError(CE_Failure, CPLE_NotSupported, + "Update mode of rasters is not supported"); + return false; + } - FileGDBTable oTable; + m_osDirName = poOpenInfo->pszFilename; + + std::string osRasterLayerName; + if (STARTS_WITH(poOpenInfo->pszFilename, "OpenFileGDB:")) + { + const CPLStringList aosTokens(CSLTokenizeString2( + poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS)); + if (aosTokens.size() == 4 && strlen(aosTokens[1]) == 1) + { + m_osDirName = aosTokens[1]; + m_osDirName += ':'; + m_osDirName += aosTokens[2]; + osRasterLayerName = aosTokens[3]; + } + else if (aosTokens.size() == 3) + { + m_osDirName = aosTokens[1]; + osRasterLayerName = aosTokens[2]; + } + else + { + CPLError(CE_Failure, CPLE_AppDefined, "Invalid connection string"); + return false; + } + } - m_pszName = CPLStrdup(pszFilename); + FileGDBTable oTable; - m_osDirName = pszFilename; int nInterestTable = 0; unsigned int unInterestTable = 0; - const char *pszFilenameWithoutPath = CPLGetFilename(pszFilename); + const char *pszFilenameWithoutPath = CPLGetFilename(m_osDirName.c_str()); if (strlen(pszFilenameWithoutPath) == strlen("a00000000.gdbtable") && pszFilenameWithoutPath[0] == 'a' && sscanf(pszFilenameWithoutPath, "a%08x.gdbtable", &unInterestTable) == 1) @@ -232,7 +258,7 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) "Either manually restore the previous state from that " "directory or remove it, before opening in update mode.", osTransactionBackupDirname.c_str()); - return FALSE; + return false; } else { @@ -255,23 +281,25 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) if (!FileExists(m_osGDBSystemCatalogFilename.c_str()) || !oTable.Open(m_osGDBSystemCatalogFilename.c_str(), false)) { - if (nInterestTable > 0 && FileExists(m_pszName)) + if (nInterestTable > 0 && FileExists(poOpenInfo->pszFilename)) { const char *pszLyrName = CPLSPrintf("a%08x", nInterestTable); auto poLayer = cpl::make_unique( - this, m_pszName, pszLyrName, "", "", eAccess == GA_Update); - const char *pszTablX = CPLResetExtension(m_pszName, "gdbtablx"); + this, poOpenInfo->pszFilename, pszLyrName, "", "", + eAccess == GA_Update); + const char *pszTablX = + CPLResetExtension(poOpenInfo->pszFilename, "gdbtablx"); if ((!FileExists(pszTablX) && poLayer->GetLayerDefn()->GetFieldCount() == 0 && poLayer->GetFeatureCount() == 0) || !poLayer->IsValidLayerDefn()) { - return FALSE; + return false; } m_apoLayers.push_back(std::move(poLayer)); - return TRUE; + return true; } - return FALSE; + return false; } const int idxName = oTable.GetFieldIdx("Name"); @@ -281,7 +309,7 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) (oTable.GetField(idxFileformat)->GetType() == FGFT_INT16 || oTable.GetField(idxFileformat)->GetType() == FGFT_INT32))) { - return FALSE; + return false; } int iGDBItems = -1; /* V10 */ @@ -349,18 +377,21 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) } catch (const std::exception &) { - return FALSE; + return false; } oTable.Close(); + std::set oSetIgnoredRasterLayerTableNum; if (iGDBItems >= 0) { eAccess = poOpenInfo->eAccess; - int bRet = OpenFileGDBv10(iGDBItems, nInterestTable); + const bool bRet = + OpenFileGDBv10(iGDBItems, nInterestTable, poOpenInfo, + osRasterLayerName, oSetIgnoredRasterLayerTableNum); if (!bRet) - return FALSE; + return false; } else if (iGDBFeatureClasses >= 0 && iGDBObjectClasses >= 0) { @@ -374,18 +405,18 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) int bRet = OpenFileGDBv9(iGDBFeatureClasses, iGDBObjectClasses, nInterestTable); if (!bRet) - return FALSE; + return false; } else { CPLError(CE_Failure, CPLE_AppDefined, "No GDB_Items nor GDB_FeatureClasses table"); - return FALSE; + return false; } if (m_apoLayers.empty() && nInterestTable > 0) { - if (FileExists(m_pszName)) + if (FileExists(poOpenInfo->pszFilename)) { const char *pszLyrName = nullptr; if (nInterestTable <= static_cast(aosTableNames.size()) && @@ -394,11 +425,12 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) else pszLyrName = CPLSPrintf("a%08x", nInterestTable); m_apoLayers.push_back(cpl::make_unique( - this, m_pszName, pszLyrName, "", "", eAccess == GA_Update)); + this, poOpenInfo->pszFilename, pszLyrName, "", "", + eAccess == GA_Update)); } else { - return FALSE; + return false; } } @@ -420,7 +452,9 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) const int idx = oIter.second; CPLString osFilename(CPLFormFilename( m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable")); - if (FileExists(osFilename)) + if (oSetIgnoredRasterLayerTableNum.find(idx) == + oSetIgnoredRasterLayerTableNum.end() && + FileExists(osFilename)) { m_apoLayers.emplace_back( cpl::make_unique( @@ -438,7 +472,45 @@ int OGROpenFileGDBDataSource::Open(const GDALOpenInfo *poOpenInfo) } } - return TRUE; + if (!osRasterLayerName.empty()) + { + m_aosSubdatasets.Clear(); + SetDescription(poOpenInfo->pszFilename); + return nBands != 0; + } + + // If opening in raster-only mode, return false if there are no raster + // layers + if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 && + (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0) + { + if (m_aosSubdatasets.empty()) + { + return false; + } + else if (m_aosSubdatasets.size() == 2) + { + // If there is a single raster dataset, open it right away. + GDALOpenInfo oOpenInfo( + m_aosSubdatasets.FetchNameValue("SUBDATASET_1_NAME"), + poOpenInfo->nOpenFlags); + bool bRet = Open(&oOpenInfo); + SetDescription(poOpenInfo->pszFilename); + return bRet; + } + } + // If opening in vector-only mode, return false if there are no vector + // layers and opened in read-only mode + else if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 && + (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0) + { + if (m_apoLayers.empty() && poOpenInfo->eAccess == GA_ReadOnly) + { + return false; + } + } + + return true; } /***********************************************************************/ @@ -545,7 +617,10 @@ OGRLayer *OGROpenFileGDBGroup::OpenVectorLayer(const std::string &osName, /* OpenFileGDBv10() */ /***********************************************************************/ -int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) +bool OGROpenFileGDBDataSource::OpenFileGDBv10( + int iGDBItems, int nInterestTable, const GDALOpenInfo *poOpenInfo, + const std::string &osRasterLayerName, + std::set &oSetIgnoredRasterLayerTableNum) { CPLDebug("OpenFileGDB", "FileGDB v10 or later"); @@ -555,7 +630,7 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) CPLString osFilename(CPLFormFilename( m_osDirName, CPLSPrintf("a%08x.gdbtable", iGDBItems + 1), nullptr)); if (!oTable.Open(osFilename, false)) - return FALSE; + return false; const int iUUID = oTable.GetFieldIdx("UUID"); const int iType = oTable.GetFieldIdx("Type"); @@ -574,7 +649,7 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) { CPLError(CE_Failure, CPLE_AppDefined, "Wrong structure for GDB_Items table"); - return FALSE; + return false; } auto poRootGroup = std::make_shared(std::string(), ""); @@ -662,6 +737,7 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) // Now collect layers int nCandidateLayers = 0; int nLayersSDCOrCDF = 0; + bool bRet = true; for (int i = 0; i < oTable.GetTotalRecordCount(); i++) { if (!oTable.SelectRow(i)) @@ -672,7 +748,7 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) } const OGRField *psField = oTable.GetFieldValue(iDefinition); - if (psField != nullptr && + if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 && (strstr(psField->String, "DEFeatureClassInfo") != nullptr || strstr(psField->String, "DETableInfo") != nullptr)) { @@ -734,7 +810,7 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) } } } - else if (psField != nullptr && + else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 && (strstr(psField->String, "GPCodedValueDomain2") != nullptr || strstr(psField->String, "GPRangeDomain2") != nullptr)) { @@ -745,13 +821,110 @@ int OGROpenFileGDBDataSource::OpenFileGDBv10(int iGDBItems, int nInterestTable) m_oMapFieldDomains[domainName] = std::move(poDomain); } } + else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 && + strstr(psField->String, "DERasterDataset")) + { + const std::string osDefinition(psField->String); + + psField = oTable.GetFieldValue(iName); + if (psField) + { + const std::string osLayerName(psField->String); + + psField = oTable.GetFieldValue(iDocumentation); + const std::string osDocumentation(psField ? psField->String + : ""); + + if (!osRasterLayerName.empty()) + { + if (osRasterLayerName == osLayerName) + { + bRet = OpenRaster(poOpenInfo, osLayerName, osDefinition, + osDocumentation); + } + } + else + { + const int iSubDSNum = 1 + m_aosSubdatasets.size() / 2; + m_aosSubdatasets.SetNameValue( + CPLSPrintf("SUBDATASET_%d_NAME", iSubDSNum), + CPLSPrintf("OpenFileGDB:\"%s\":%s", + poOpenInfo->pszFilename, + osLayerName.c_str())); + + std::string osDesc(osLayerName); + if (!osDocumentation.empty()) + { + CPLXMLTreeCloser psTree( + CPLParseXMLString(osDocumentation.c_str())); + if (psTree) + { + const auto psRastInfo = CPLGetXMLNode( + psTree.get(), "=metadata.spdoinfo.rastinfo"); + if (psRastInfo) + { + const char *pszRowCount = CPLGetXMLValue( + psRastInfo, "rowcount", nullptr); + const char *pszColCount = CPLGetXMLValue( + psRastInfo, "colcount", nullptr); + const char *pszRastBand = CPLGetXMLValue( + psRastInfo, "rastband", nullptr); + const char *pszRastBPP = CPLGetXMLValue( + psRastInfo, "rastbpp", nullptr); + if (pszRowCount && pszColCount && pszRastBand && + pszRastBPP) + { + osDesc += CPLSPrintf( + " [%sx%sx%s], %s bits", pszColCount, + pszRowCount, pszRastBand, pszRastBPP); + } + } + } + } + + m_aosSubdatasets.SetNameValue( + CPLSPrintf("SUBDATASET_%d_DESC", iSubDSNum), + ("Raster " + osDesc).c_str()); + } + } + } + else if (psField && (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 && + strstr(psField->String, "DERasterDataset")) + { + psField = oTable.GetFieldValue(iName); + if (psField) + { + const std::string osLayerName(psField->String); + auto oIter = m_osMapNameToIdx.find(osLayerName); + if (oIter != m_osMapNameToIdx.end()) + { + oSetIgnoredRasterLayerTableNum.insert(oIter->second); + + for (const char *pszPrefix : + {"fras_ras_", "fras_aux_", "fras_bnd_", "fras_blk_"}) + { + oIter = m_osMapNameToIdx.find( + std::string(pszPrefix).append(osLayerName).c_str()); + if (oIter != m_osMapNameToIdx.end()) + { + oSetIgnoredRasterLayerTableNum.insert( + oIter->second); + } + } + } + } + } } + // Return false if there are no vector layers and all candidate layers are + // compressed ones. if (m_apoLayers.empty() && nCandidateLayers > 0 && nCandidateLayers == nLayersSDCOrCDF) - return FALSE; + { + return false; + } - return TRUE; + return bRet; } /***********************************************************************/ @@ -934,6 +1107,30 @@ OGRLayer *OGROpenFileGDBDataSource::GetLayer(int iIndex) return m_apoLayers[iIndex].get(); } +/***********************************************************************/ +/* BuildLayerFromName() */ +/***********************************************************************/ + +std::unique_ptr +OGROpenFileGDBDataSource::BuildLayerFromName(const char *pszName) +{ + + std::map::const_iterator oIter = + m_osMapNameToIdx.find(pszName); + if (oIter != m_osMapNameToIdx.end()) + { + int idx = oIter->second; + CPLString osFilename( + CPLFormFilename(m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable")); + if (FileExists(osFilename)) + { + return cpl::make_unique( + this, osFilename, pszName, "", "", eAccess == GA_Update); + } + } + return nullptr; +} + /***********************************************************************/ /* GetLayerByName() */ /***********************************************************************/ @@ -953,20 +1150,11 @@ OGROpenFileGDBDataSource::GetLayerByName(const char *pszName) return poLayer.get(); } - std::map::const_iterator oIter = - m_osMapNameToIdx.find(pszName); - if (oIter != m_osMapNameToIdx.end()) + auto poLayer = BuildLayerFromName(pszName); + if (poLayer) { - int idx = oIter->second; - CPLString osFilename( - CPLFormFilename(m_osDirName, CPLSPrintf("a%08x", idx), "gdbtable")); - if (FileExists(osFilename)) - { - m_apoHiddenLayers.emplace_back( - cpl::make_unique( - this, osFilename, pszName, "", "", eAccess == GA_Update)); - return m_apoHiddenLayers.back().get(); - } + m_apoHiddenLayers.emplace_back(std::move(poLayer)); + return m_apoHiddenLayers.back().get(); } return nullptr; } @@ -980,7 +1168,9 @@ bool OGROpenFileGDBDataSource::IsPrivateLayerName(const CPLString &osName) const CPLString osLCTableName(CPLString(osName).tolower()); // tables beginning with "GDB_" are private tables - return osLCTableName.size() >= 4 && osLCTableName.substr(0, 4) == "gdb_"; + // Also consider "VAT_" tables as private ones. + return osLCTableName.size() >= 4 && (osLCTableName.substr(0, 4) == "gdb_" || + osLCTableName.substr(0, 4) == "vat_"); } /***********************************************************************/ @@ -996,6 +1186,17 @@ bool OGROpenFileGDBDataSource::IsLayerPrivate(int iLayer) const return IsPrivateLayerName(osName); } +/***********************************************************************/ +/* GetMetadata() */ +/***********************************************************************/ + +char **OGROpenFileGDBDataSource::GetMetadata(const char *pszDomain) +{ + if (pszDomain && EQUAL(pszDomain, "SUBDATASETS")) + return m_aosSubdatasets.List(); + return OGRDataSource::GetMetadata(pszDomain); +} + /************************************************************************/ /* OGROpenFileGDBSingleFeatureLayer() */ /************************************************************************/ @@ -1786,7 +1987,7 @@ void OGROpenFileGDBDataSource::ReleaseResultSet(OGRLayer *poResultsSet) char **OGROpenFileGDBDataSource::GetFileList() { int nInterestTable = -1; - const char *pszFilenameWithoutPath = CPLGetFilename(m_pszName); + const char *pszFilenameWithoutPath = CPLGetFilename(m_osDirName.c_str()); CPLString osFilenameRadix; unsigned int unInterestTable = 0; if (strlen(pszFilenameWithoutPath) == strlen("a00000000.gdbtable") && @@ -1819,6 +2020,68 @@ char **OGROpenFileGDBDataSource::GetFileList() /* BuildSRS() */ /************************************************************************/ +OGRSpatialReference * +OGROpenFileGDBDataSource::BuildSRS(const CPLXMLNode *psInfo) +{ + const char *pszWKT = + CPLGetXMLValue(psInfo, "SpatialReference.WKT", nullptr); + const int nWKID = + atoi(CPLGetXMLValue(psInfo, "SpatialReference.WKID", "0")); + // The concept of LatestWKID is explained in + // http://resources.arcgis.com/en/help/arcgis-rest-api/index.html#//02r3000000n1000000 + int nLatestWKID = + atoi(CPLGetXMLValue(psInfo, "SpatialReference.LatestWKID", "0")); + + OGRSpatialReference *poSRS = nullptr; + if (nWKID > 0 || nLatestWKID > 0) + { + int bSuccess = FALSE; + poSRS = new OGRSpatialReference(); + poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER); + CPLPushErrorHandler(CPLQuietErrorHandler); + // Try first with nLatestWKID as there is a higher chance it is a + // EPSG code and not an ESRI one. + if (nLatestWKID > 0) + { + if (poSRS->importFromEPSG(nLatestWKID) == OGRERR_NONE) + { + bSuccess = TRUE; + } + else + { + CPLDebug("OpenFileGDB", "Cannot import SRID %d", nLatestWKID); + } + } + if (!bSuccess && nWKID > 0) + { + if (poSRS->importFromEPSG(nWKID) == OGRERR_NONE) + { + bSuccess = TRUE; + } + else + { + CPLDebug("OpenFileGDB", "Cannot import SRID %d", nWKID); + } + } + if (!bSuccess) + { + delete poSRS; + poSRS = nullptr; + } + CPLPopErrorHandler(); + CPLErrorReset(); + } + if (poSRS == nullptr && pszWKT != nullptr && pszWKT[0] != '{') + { + poSRS = BuildSRS(pszWKT); + } + return poSRS; +} + +/************************************************************************/ +/* BuildSRS() */ +/************************************************************************/ + OGRSpatialReference *OGROpenFileGDBDataSource::BuildSRS(const char *pszWKT) { std::shared_ptr poSharedObj; diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp index 20fea084cb05..08b6cc804328 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdatasource_write.cpp @@ -664,7 +664,7 @@ bool OGROpenFileGDBDataSource::CreateGDBSystemCatalog() { // Write GDB_SystemCatalog file m_osGDBSystemCatalogFilename = - CPLFormFilename(m_pszName, "a00000001.gdbtable", nullptr); + CPLFormFilename(m_osDirName.c_str(), "a00000001.gdbtable", nullptr); FileGDBTable oTable; if (!oTable.Create(m_osGDBSystemCatalogFilename.c_str(), 4, FGTGT_NONE, false, false) || @@ -715,7 +715,7 @@ bool OGROpenFileGDBDataSource::CreateGDBDBTune() { // Write GDB_DBTune file const std::string osFilename( - CPLFormFilename(m_pszName, "a00000002.gdbtable", nullptr)); + CPLFormFilename(m_osDirName.c_str(), "a00000002.gdbtable", nullptr)); FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(cpl::make_unique( @@ -807,7 +807,7 @@ bool OGROpenFileGDBDataSource::CreateGDBSpatialRefs() { // Write GDB_SpatialRefs file m_osGDBSpatialRefsFilename = - CPLFormFilename(m_pszName, "a00000003.gdbtable", nullptr); + CPLFormFilename(m_osDirName.c_str(), "a00000003.gdbtable", nullptr); FileGDBTable oTable; if (!oTable.Create(m_osGDBSpatialRefsFilename.c_str(), 4, FGTGT_NONE, false, false) || @@ -887,7 +887,7 @@ bool OGROpenFileGDBDataSource::CreateGDBItems() } m_osGDBItemsFilename = - CPLFormFilename(m_pszName, "a00000004.gdbtable", nullptr); + CPLFormFilename(m_osDirName.c_str(), "a00000004.gdbtable", nullptr); FileGDBTable oTable; if (!oTable.Create(m_osGDBItemsFilename.c_str(), 4, FGTGT_POLYGON, false, false) || @@ -998,7 +998,7 @@ bool OGROpenFileGDBDataSource::CreateGDBItemTypes() { // Write GDB_ItemTypes file const std::string osFilename( - CPLFormFilename(m_pszName, "a00000005.gdbtable", nullptr)); + CPLFormFilename(m_osDirName.c_str(), "a00000005.gdbtable", nullptr)); FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(cpl::make_unique( @@ -1112,7 +1112,7 @@ bool OGROpenFileGDBDataSource::CreateGDBItemRelationships() { // Write GDB_ItemRelationships file m_osGDBItemRelationshipsFilename = - CPLFormFilename(m_pszName, "a00000006.gdbtable", nullptr); + CPLFormFilename(m_osDirName.c_str(), "a00000006.gdbtable", nullptr); FileGDBTable oTable; if (!oTable.Create(m_osGDBItemRelationshipsFilename.c_str(), 4, FGTGT_NONE, false, false) || @@ -1156,7 +1156,7 @@ bool OGROpenFileGDBDataSource::CreateGDBItemRelationshipTypes() { // Write GDB_ItemRelationshipTypes file const std::string osFilename( - CPLFormFilename(m_pszName, "a00000007.gdbtable", nullptr)); + CPLFormFilename(m_osDirName.c_str(), "a00000007.gdbtable", nullptr)); FileGDBTable oTable; if (!oTable.Create(osFilename.c_str(), 4, FGTGT_NONE, false, false) || !oTable.CreateField(cpl::make_unique( @@ -1308,8 +1308,7 @@ bool OGROpenFileGDBDataSource::Create(const char *pszName) return false; } - m_pszName = CPLStrdup(pszName); - m_osDirName = m_pszName; + m_osDirName = pszName; eAccess = GA_Update; { @@ -1370,7 +1369,7 @@ OGRLayer *OGROpenFileGDBDataSource::ICreateLayer(const char *pszLayerName, oTable.Close(); const std::string osFilename(CPLFormFilename( - m_pszName, CPLSPrintf("a%08x.gdbtable", nTableNum), nullptr)); + m_osDirName.c_str(), CPLSPrintf("a%08x.gdbtable", nTableNum), nullptr)); if (wkbFlatten(eType) == wkbLineString) eType = OGR_GT_SetModifier(wkbMultiLineString, OGR_GT_HasZ(eType), diff --git a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdriver.cpp b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdriver.cpp index 58994f9ebd98..1bbc1e2d23f3 100644 --- a/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdriver.cpp +++ b/ogr/ogrsf_frmts/openfilegdb/ogropenfilegdbdriver.cpp @@ -55,7 +55,10 @@ static GDALIdentifyEnum OGROpenFileGDBDriverIdentifyInternal(GDALOpenInfo *poOpenInfo, const char *&pszFilename) { - // FUSIL is a fuzzer + if (STARTS_WITH(pszFilename, "OpenFileGDB:")) + return GDAL_IDENTIFY_TRUE; + + // FUSIL is a fuzzer #ifdef FOR_FUSIL CPLString osOrigFilename(pszFilename); #endif @@ -229,7 +232,7 @@ static GDALDataset *OGROpenFileGDBDriverCreate(const char *pszName, int nXSize, if (!(nXSize == 0 && nYSize == 0 && nBands == 0 && eType == GDT_Unknown)) { CPLError(CE_Failure, CPLE_NotSupported, - "Only vector datasets supported"); + "OpenFileGDB::Create(): only vector datasets supported"); return nullptr; } @@ -292,6 +295,7 @@ void RegisterOGROpenFileGDB() poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gdb"); poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/vector/openfilegdb.html"); + poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_CREATE_LAYER, "YES"); poDriver->SetMetadataItem(GDAL_DCAP_DELETE_LAYER, "YES"); @@ -350,6 +354,8 @@ void RegisterOGROpenFileGDB() " YES" " NO" " " + "