LCOV - code coverage report
Current view: top level - frmts/hdf5 - hdf5imagedataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 671 861 77.9 %
Date: 2024-05-03 15:49:35 Functions: 32 33 97.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Hierarchical Data Format Release 5 (HDF5)
       4             :  * Purpose:  Read subdatasets of HDF5 file.
       5             :  * Author:   Denis Nadeau <denis.nadeau@gmail.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
       9             :  * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
      10             :  *
      11             :  * Permission is hereby granted, free of charge, to any person obtaining a
      12             :  * copy of this software and associated documentation files (the "Software"),
      13             :  * to deal in the Software without restriction, including without limitation
      14             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      15             :  * and/or sell copies of the Software, and to permit persons to whom the
      16             :  * Software is furnished to do so, subject to the following conditions:
      17             :  *
      18             :  * The above copyright notice and this permission notice shall be included
      19             :  * in all copies or substantial portions of the Software.
      20             :  *
      21             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      22             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      23             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      24             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      25             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      26             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      27             :  * DEALINGS IN THE SOFTWARE.
      28             :  ****************************************************************************/
      29             : 
      30             : #include "hdf5_api.h"
      31             : 
      32             : #include "cpl_string.h"
      33             : #include "gdal_frmts.h"
      34             : #include "gdal_pam.h"
      35             : #include "gdal_priv.h"
      36             : #include "gh5_convenience.h"
      37             : #include "hdf5dataset.h"
      38             : #include "hdf5drivercore.h"
      39             : #include "ogr_spatialref.h"
      40             : #include "../mem/memdataset.h"
      41             : 
      42             : #include <algorithm>
      43             : 
      44             : class HDF5ImageDataset final : public HDF5Dataset
      45             : {
      46             :     typedef enum
      47             :     {
      48             :         UNKNOWN_PRODUCT = 0,
      49             :         CSK_PRODUCT
      50             :     } Hdf5ProductType;
      51             : 
      52             :     typedef enum
      53             :     {
      54             :         PROD_UNKNOWN = 0,
      55             :         PROD_CSK_L0,
      56             :         PROD_CSK_L1A,
      57             :         PROD_CSK_L1B,
      58             :         PROD_CSK_L1C,
      59             :         PROD_CSK_L1D
      60             :     } HDF5CSKProductEnum;
      61             : 
      62             :     friend class HDF5ImageRasterBand;
      63             : 
      64             :     OGRSpatialReference m_oSRS{};
      65             :     OGRSpatialReference m_oGCPSRS{};
      66             :     std::vector<gdal::GCP> m_aoGCPs{};
      67             : 
      68             :     hsize_t *dims;
      69             :     hsize_t *maxdims;
      70             :     HDF5GroupObjects *poH5Objects;
      71             :     int ndims;
      72             :     int dimensions;
      73             :     hid_t dataset_id;
      74             :     hid_t dataspace_id;
      75             :     hsize_t size;
      76             :     hid_t datatype;
      77             :     hid_t native;
      78             :     Hdf5ProductType iSubdatasetType;
      79             :     HDF5CSKProductEnum iCSKProductType;
      80             :     double adfGeoTransform[6];
      81             :     bool bHasGeoTransform;
      82             :     int m_nXIndex = -1;
      83             :     int m_nYIndex = -1;
      84             :     int m_nOtherDimIndex = -1;
      85             : 
      86             :     int m_nBlockXSize = 0;
      87             :     int m_nBlockYSize = 0;
      88             :     int m_nBandChunkSize = 1;  //! Number of bands in a chunk
      89             : 
      90             :     enum WholeBandChunkOptim
      91             :     {
      92             :         WBC_DETECTION_IN_PROGRESS,
      93             :         WBC_DISABLED,
      94             :         WBC_ENABLED,
      95             :     };
      96             : 
      97             :     //! Flag to detect if the read pattern of HDF5ImageRasterBand::IRasterIO()
      98             :     // is whole band after whole band.
      99             :     WholeBandChunkOptim m_eWholeBandChunkOptim = WBC_DETECTION_IN_PROGRESS;
     100             :     //! Value of nBand during last HDF5ImageRasterBand::IRasterIO() call
     101             :     int m_nLastRasterIOBand = -1;
     102             :     //! Value of nXOff during last HDF5ImageRasterBand::IRasterIO() call
     103             :     int m_nLastRasterIOXOff = -1;
     104             :     //! Value of nYOff during last HDF5ImageRasterBand::IRasterIO() call
     105             :     int m_nLastRasterIOYOff = -1;
     106             :     //! Value of nXSize during last HDF5ImageRasterBand::IRasterIO() call
     107             :     int m_nLastRasterIOXSize = -1;
     108             :     //! Value of nYSize during last HDF5ImageRasterBand::IRasterIO() call
     109             :     int m_nLastRasterIOYSize = -1;
     110             :     //! Value such that m_abyBandChunk represent band data in the range
     111             :     // [m_iCurrentBandChunk * m_nBandChunkSize, (m_iCurrentBandChunk+1) * m_nBandChunkSize[
     112             :     int m_iCurrentBandChunk = -1;
     113             :     //! Cached values (in native data type) for bands in the range
     114             :     // [m_iCurrentBandChunk * m_nBandChunkSize, (m_iCurrentBandChunk+1) * m_nBandChunkSize[
     115             :     std::vector<GByte> m_abyBandChunk{};
     116             : 
     117             :     CPLErr CreateODIMH5Projection();
     118             : 
     119             :   public:
     120             :     HDF5ImageDataset();
     121             :     virtual ~HDF5ImageDataset();
     122             : 
     123             :     CPLErr CreateProjections();
     124             :     static GDALDataset *Open(GDALOpenInfo *);
     125             :     static int Identify(GDALOpenInfo *);
     126             : 
     127             :     const OGRSpatialReference *GetSpatialRef() const override;
     128             :     virtual int GetGCPCount() override;
     129             :     const OGRSpatialReference *GetGCPSpatialRef() const override;
     130             :     virtual const GDAL_GCP *GetGCPs() override;
     131             :     virtual CPLErr GetGeoTransform(double *padfTransform) override;
     132             : 
     133             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     134             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     135             :                      GDALDataType eBufType, int nBandCount, int *panBandMap,
     136             :                      GSpacing nPixelSpace, GSpacing nLineSpace,
     137             :                      GSpacing nBandSpace,
     138             :                      GDALRasterIOExtraArg *psExtraArg) override;
     139             : 
     140             :     const char *GetMetadataItem(const char *pszName,
     141             :                                 const char *pszDomain = "") override;
     142             : 
     143         180 :     Hdf5ProductType GetSubdatasetType() const
     144             :     {
     145         180 :         return iSubdatasetType;
     146             :     }
     147             : 
     148           6 :     HDF5CSKProductEnum GetCSKProductType() const
     149             :     {
     150           6 :         return iCSKProductType;
     151             :     }
     152             : 
     153         180 :     int IsComplexCSKL1A() const
     154             :     {
     155         186 :         return GetSubdatasetType() == CSK_PRODUCT &&
     156         186 :                GetCSKProductType() == PROD_CSK_L1A && ndims == 3;
     157             :     }
     158             : 
     159         665 :     int GetYIndex() const
     160             :     {
     161         665 :         return m_nYIndex;
     162             :     }
     163             : 
     164         789 :     int GetXIndex() const
     165             :     {
     166         789 :         return m_nXIndex;
     167             :     }
     168             : 
     169             :     /**
     170             :      * Identify if the subdataset has a known product format
     171             :      * It stores a product identifier in iSubdatasetType,
     172             :      * UNKNOWN_PRODUCT, if it isn't a recognizable format.
     173             :      */
     174             :     void IdentifyProductType();
     175             : 
     176             :     /**
     177             :      * Captures Geolocation information from a COSMO-SKYMED
     178             :      * file.
     179             :      * The geoid will always be WGS84
     180             :      * The projection type may be UTM or UPS, depending on the
     181             :      * latitude from the center of the image.
     182             :      * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
     183             :      */
     184             :     void CaptureCSKGeolocation(int iProductType);
     185             : 
     186             :     /**
     187             :      * Get Geotransform information for COSMO-SKYMED files
     188             :      * In case of success it stores the transformation
     189             :      * in adfGeoTransform. In case of failure it doesn't
     190             :      * modify adfGeoTransform
     191             :      * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
     192             :      */
     193             :     void CaptureCSKGeoTransform(int iProductType);
     194             : 
     195             :     /**
     196             :      * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
     197             :      */
     198             :     void CaptureCSKGCPs(int iProductType);
     199             : };
     200             : 
     201             : /************************************************************************/
     202             : /* ==================================================================== */
     203             : /*                              HDF5ImageDataset                        */
     204             : /* ==================================================================== */
     205             : /************************************************************************/
     206             : 
     207             : /************************************************************************/
     208             : /*                           HDF5ImageDataset()                         */
     209             : /************************************************************************/
     210          60 : HDF5ImageDataset::HDF5ImageDataset()
     211             :     : dims(nullptr), maxdims(nullptr), poH5Objects(nullptr), ndims(0),
     212             :       dimensions(0), dataset_id(-1), dataspace_id(-1), size(0), datatype(-1),
     213             :       native(-1), iSubdatasetType(UNKNOWN_PRODUCT),
     214          60 :       iCSKProductType(PROD_UNKNOWN), bHasGeoTransform(false)
     215             : {
     216          60 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     217          60 :     m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     218          60 :     adfGeoTransform[0] = 0.0;
     219          60 :     adfGeoTransform[1] = 1.0;
     220          60 :     adfGeoTransform[2] = 0.0;
     221          60 :     adfGeoTransform[3] = 0.0;
     222          60 :     adfGeoTransform[4] = 0.0;
     223          60 :     adfGeoTransform[5] = 1.0;
     224          60 : }
     225             : 
     226             : /************************************************************************/
     227             : /*                            ~HDF5ImageDataset()                       */
     228             : /************************************************************************/
     229         120 : HDF5ImageDataset::~HDF5ImageDataset()
     230             : {
     231             :     HDF5_GLOBAL_LOCK();
     232             : 
     233          60 :     FlushCache(true);
     234             : 
     235          60 :     if (dataset_id > 0)
     236          60 :         H5Dclose(dataset_id);
     237          60 :     if (dataspace_id > 0)
     238          60 :         H5Sclose(dataspace_id);
     239          60 :     if (datatype > 0)
     240          60 :         H5Tclose(datatype);
     241          60 :     if (native > 0)
     242          60 :         H5Tclose(native);
     243             : 
     244          60 :     CPLFree(dims);
     245          60 :     CPLFree(maxdims);
     246         120 : }
     247             : 
     248             : /************************************************************************/
     249             : /* ==================================================================== */
     250             : /*                            Hdf5imagerasterband                       */
     251             : /* ==================================================================== */
     252             : /************************************************************************/
     253             : class HDF5ImageRasterBand final : public GDALPamRasterBand
     254             : {
     255             :     friend class HDF5ImageDataset;
     256             : 
     257             :     bool bNoDataSet = false;
     258             :     double dfNoDataValue = -9999.0;
     259             :     bool m_bHasOffset = false;
     260             :     double m_dfOffset = 0.0;
     261             :     bool m_bHasScale = false;
     262             :     double m_dfScale = 1.0;
     263             :     int m_nIRasterIORecCounter = 0;
     264             : 
     265             :   public:
     266             :     HDF5ImageRasterBand(HDF5ImageDataset *, int, GDALDataType);
     267             :     virtual ~HDF5ImageRasterBand();
     268             : 
     269             :     virtual CPLErr IReadBlock(int, int, void *) override;
     270             :     virtual double GetNoDataValue(int *) override;
     271             :     virtual double GetOffset(int *) override;
     272             :     virtual double GetScale(int *) override;
     273             :     // virtual CPLErr IWriteBlock( int, int, void * );
     274             : 
     275             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     276             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     277             :                      GDALDataType eBufType, GSpacing nPixelSpace,
     278             :                      GSpacing nLineSpace,
     279             :                      GDALRasterIOExtraArg *psExtraArg) override;
     280             : };
     281             : 
     282             : /************************************************************************/
     283             : /*                        ~HDF5ImageRasterBand()                        */
     284             : /************************************************************************/
     285             : 
     286         244 : HDF5ImageRasterBand::~HDF5ImageRasterBand()
     287             : {
     288         244 : }
     289             : 
     290             : /************************************************************************/
     291             : /*                           HDF5ImageRasterBand()                      */
     292             : /************************************************************************/
     293         122 : HDF5ImageRasterBand::HDF5ImageRasterBand(HDF5ImageDataset *poDSIn, int nBandIn,
     294         122 :                                          GDALDataType eType)
     295             : {
     296         122 :     poDS = poDSIn;
     297         122 :     nBand = nBandIn;
     298         122 :     eDataType = eType;
     299         122 :     nBlockXSize = poDSIn->m_nBlockXSize;
     300         122 :     nBlockYSize = poDSIn->m_nBlockYSize;
     301             : 
     302             :     // netCDF convention for nodata
     303         122 :     bNoDataSet =
     304         122 :         GH5_FetchAttribute(poDSIn->dataset_id, "_FillValue", dfNoDataValue);
     305         122 :     if (!bNoDataSet)
     306         114 :         dfNoDataValue = -9999.0;
     307             : 
     308             :     // netCDF conventions for scale and offset
     309         122 :     m_bHasOffset =
     310         122 :         GH5_FetchAttribute(poDSIn->dataset_id, "add_offset", m_dfOffset);
     311         122 :     if (!m_bHasOffset)
     312         121 :         m_dfOffset = 0.0;
     313         122 :     m_bHasScale =
     314         122 :         GH5_FetchAttribute(poDSIn->dataset_id, "scale_factor", m_dfScale);
     315         122 :     if (!m_bHasScale)
     316         121 :         m_dfScale = 1.0;
     317         122 : }
     318             : 
     319             : /************************************************************************/
     320             : /*                           GetNoDataValue()                           */
     321             : /************************************************************************/
     322          45 : double HDF5ImageRasterBand::GetNoDataValue(int *pbSuccess)
     323             : 
     324             : {
     325          45 :     if (bNoDataSet)
     326             :     {
     327           1 :         if (pbSuccess)
     328           1 :             *pbSuccess = bNoDataSet;
     329             : 
     330           1 :         return dfNoDataValue;
     331             :     }
     332             : 
     333          44 :     return GDALPamRasterBand::GetNoDataValue(pbSuccess);
     334             : }
     335             : 
     336             : /************************************************************************/
     337             : /*                             GetOffset()                              */
     338             : /************************************************************************/
     339             : 
     340          22 : double HDF5ImageRasterBand::GetOffset(int *pbSuccess)
     341             : 
     342             : {
     343          22 :     if (m_bHasOffset)
     344             :     {
     345           1 :         if (pbSuccess)
     346           1 :             *pbSuccess = m_bHasOffset;
     347             : 
     348           1 :         return m_dfOffset;
     349             :     }
     350             : 
     351          21 :     return GDALPamRasterBand::GetOffset(pbSuccess);
     352             : }
     353             : 
     354             : /************************************************************************/
     355             : /*                             GetScale()                               */
     356             : /************************************************************************/
     357             : 
     358          22 : double HDF5ImageRasterBand::GetScale(int *pbSuccess)
     359             : 
     360             : {
     361          22 :     if (m_bHasScale)
     362             :     {
     363           1 :         if (pbSuccess)
     364           1 :             *pbSuccess = m_bHasScale;
     365             : 
     366           1 :         return m_dfScale;
     367             :     }
     368             : 
     369          21 :     return GDALPamRasterBand::GetScale(pbSuccess);
     370             : }
     371             : 
     372             : /************************************************************************/
     373             : /*                             IReadBlock()                             */
     374             : /************************************************************************/
     375         103 : CPLErr HDF5ImageRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
     376             :                                        void *pImage)
     377             : {
     378         103 :     HDF5ImageDataset *poGDS = static_cast<HDF5ImageDataset *>(poDS);
     379             : 
     380         103 :     memset(pImage, 0,
     381         103 :            static_cast<size_t>(nBlockXSize) * nBlockYSize *
     382         103 :                GDALGetDataTypeSizeBytes(eDataType));
     383             : 
     384         103 :     if (poGDS->eAccess == GA_Update)
     385             :     {
     386           0 :         return CE_None;
     387             :     }
     388             : 
     389         103 :     const int nXOff = nBlockXOff * nBlockXSize;
     390         103 :     const int nYOff = nBlockYOff * nBlockYSize;
     391         103 :     const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
     392         103 :     const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
     393         103 :     if (poGDS->m_eWholeBandChunkOptim == HDF5ImageDataset::WBC_ENABLED)
     394             :     {
     395             :         const bool bIsBandInterleavedData =
     396           2 :             poGDS->ndims == 3 && poGDS->m_nOtherDimIndex == 0 &&
     397           3 :             poGDS->GetYIndex() == 1 && poGDS->GetXIndex() == 2;
     398           1 :         if (poGDS->nBands == 1 || bIsBandInterleavedData)
     399             :         {
     400             :             GDALRasterIOExtraArg sExtraArg;
     401           1 :             INIT_RASTERIO_EXTRA_ARG(sExtraArg);
     402           1 :             const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
     403           2 :             return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage,
     404             :                              nXSize, nYSize, eDataType, nDTSize,
     405           1 :                              static_cast<GSpacing>(nDTSize) * nBlockXSize,
     406           1 :                              &sExtraArg);
     407             :         }
     408             :     }
     409             : 
     410             :     HDF5_GLOBAL_LOCK();
     411             : 
     412         102 :     hsize_t count[3] = {0, 0, 0};
     413         102 :     H5OFFSET_TYPE offset[3] = {0, 0, 0};
     414         102 :     hsize_t col_dims[3] = {0, 0, 0};
     415         102 :     hsize_t rank = std::min(poGDS->ndims, 2);
     416             : 
     417         102 :     if (poGDS->ndims == 3)
     418             :     {
     419           1 :         rank = 3;
     420           1 :         offset[poGDS->m_nOtherDimIndex] = nBand - 1;
     421           1 :         count[poGDS->m_nOtherDimIndex] = 1;
     422           1 :         col_dims[poGDS->m_nOtherDimIndex] = 1;
     423             :     }
     424             : 
     425         102 :     const int nYIndex = poGDS->GetYIndex();
     426             :     // Blocksize may not be a multiple of imagesize.
     427         102 :     if (nYIndex >= 0)
     428             :     {
     429         101 :         offset[nYIndex] = nYOff;
     430         101 :         count[nYIndex] = nYSize;
     431             :     }
     432         102 :     offset[poGDS->GetXIndex()] = nXOff;
     433         102 :     count[poGDS->GetXIndex()] = nXSize;
     434             : 
     435             :     // Select block from file space.
     436         102 :     herr_t status = H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
     437             :                                         offset, nullptr, count, nullptr);
     438         102 :     if (status < 0)
     439           0 :         return CE_Failure;
     440             : 
     441             :     // Create memory space to receive the data.
     442         102 :     if (nYIndex >= 0)
     443         101 :         col_dims[nYIndex] = nBlockYSize;
     444         102 :     col_dims[poGDS->GetXIndex()] = nBlockXSize;
     445             : 
     446             :     const hid_t memspace =
     447         102 :         H5Screate_simple(static_cast<int>(rank), col_dims, nullptr);
     448         102 :     H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
     449         102 :     status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset, nullptr,
     450             :                                  count, nullptr);
     451         102 :     if (status < 0)
     452             :     {
     453           0 :         H5Sclose(memspace);
     454           0 :         return CE_Failure;
     455             :     }
     456             : 
     457         102 :     status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
     458             :                      poGDS->dataspace_id, H5P_DEFAULT, pImage);
     459             : 
     460         102 :     H5Sclose(memspace);
     461             : 
     462         102 :     if (status < 0)
     463             :     {
     464           0 :         CPLError(CE_Failure, CPLE_AppDefined, "H5Dread() failed for block.");
     465           0 :         return CE_Failure;
     466             :     }
     467             : 
     468         102 :     return CE_None;
     469             : }
     470             : 
     471             : /************************************************************************/
     472             : /*                             IRasterIO()                              */
     473             : /************************************************************************/
     474             : 
     475         312 : CPLErr HDF5ImageRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     476             :                                       int nXSize, int nYSize, void *pData,
     477             :                                       int nBufXSize, int nBufYSize,
     478             :                                       GDALDataType eBufType,
     479             :                                       GSpacing nPixelSpace, GSpacing nLineSpace,
     480             :                                       GDALRasterIOExtraArg *psExtraArg)
     481             : 
     482             : {
     483         312 :     HDF5ImageDataset *poGDS = static_cast<HDF5ImageDataset *>(poDS);
     484             : 
     485             :     const bool bIsBandInterleavedData =
     486         450 :         poGDS->ndims == 3 && poGDS->m_nOtherDimIndex == 0 &&
     487         762 :         poGDS->GetYIndex() == 1 && poGDS->GetXIndex() == 2;
     488             : 
     489         312 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
     490             : 
     491             :     // Try to detect if we read whole bands by chunks of whole lines
     492             :     // If so, then read and cache whole band (or group of m_nBandChunkSize bands)
     493             :     // to save HDF5 decompression.
     494         312 :     if (m_nIRasterIORecCounter == 0)
     495             :     {
     496         263 :         bool bInvalidateWholeBandChunkOptim = false;
     497         263 :         if (!(nXSize == nBufXSize && nYSize == nBufYSize))
     498             :         {
     499           1 :             bInvalidateWholeBandChunkOptim = true;
     500             :         }
     501             :         // Is the first request on band 1, line 0 and one or several full lines?
     502         262 :         else if (poGDS->m_eWholeBandChunkOptim !=
     503          70 :                      HDF5ImageDataset::WBC_ENABLED &&
     504          70 :                  nBand == 1 && nXOff == 0 && nYOff == 0 &&
     505          20 :                  nXSize == nRasterXSize)
     506             :         {
     507          19 :             poGDS->m_eWholeBandChunkOptim =
     508             :                 HDF5ImageDataset::WBC_DETECTION_IN_PROGRESS;
     509          19 :             poGDS->m_nLastRasterIOBand = 1;
     510          19 :             poGDS->m_nLastRasterIOXOff = nXOff;
     511          19 :             poGDS->m_nLastRasterIOYOff = nYOff;
     512          19 :             poGDS->m_nLastRasterIOXSize = nXSize;
     513          19 :             poGDS->m_nLastRasterIOYSize = nYSize;
     514             :         }
     515         243 :         else if (poGDS->m_eWholeBandChunkOptim ==
     516             :                  HDF5ImageDataset::WBC_DETECTION_IN_PROGRESS)
     517             :         {
     518          50 :             if (poGDS->m_nLastRasterIOBand == 1 && nBand == 1)
     519             :             {
     520             :                 // Is this request a continuation of the previous one?
     521          44 :                 if (nXOff == 0 && poGDS->m_nLastRasterIOXOff == 0 &&
     522          44 :                     nYOff == poGDS->m_nLastRasterIOYOff +
     523          44 :                                  poGDS->m_nLastRasterIOYSize &&
     524          43 :                     poGDS->m_nLastRasterIOXSize == nRasterXSize &&
     525          43 :                     nXSize == nRasterXSize)
     526             :                 {
     527          43 :                     poGDS->m_nLastRasterIOXOff = nXOff;
     528          43 :                     poGDS->m_nLastRasterIOYOff = nYOff;
     529          43 :                     poGDS->m_nLastRasterIOXSize = nXSize;
     530          43 :                     poGDS->m_nLastRasterIOYSize = nYSize;
     531             :                 }
     532             :                 else
     533             :                 {
     534           1 :                     bInvalidateWholeBandChunkOptim = true;
     535             :                 }
     536             :             }
     537           6 :             else if (poGDS->m_nLastRasterIOBand == 1 && nBand == 2)
     538             :             {
     539             :                 // Are we switching to band 2 while having fully read band 1?
     540           5 :                 if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
     541           5 :                     poGDS->m_nLastRasterIOXOff == 0 &&
     542           5 :                     poGDS->m_nLastRasterIOXSize == nRasterXSize &&
     543           5 :                     poGDS->m_nLastRasterIOYOff + poGDS->m_nLastRasterIOYSize ==
     544           5 :                         nRasterYSize)
     545             :                 {
     546          11 :                     if ((poGDS->m_nBandChunkSize > 1 ||
     547           5 :                          nBufYSize < nRasterYSize) &&
     548           1 :                         static_cast<int64_t>(poGDS->m_nBandChunkSize) *
     549           1 :                                 nRasterXSize * nRasterYSize * nDTSize <
     550           1 :                             CPLGetUsablePhysicalRAM() / 10)
     551             :                     {
     552           1 :                         poGDS->m_eWholeBandChunkOptim =
     553             :                             HDF5ImageDataset::WBC_ENABLED;
     554             :                     }
     555             :                     else
     556             :                     {
     557           3 :                         bInvalidateWholeBandChunkOptim = true;
     558             :                     }
     559             :                 }
     560             :                 else
     561             :                 {
     562           1 :                     bInvalidateWholeBandChunkOptim = true;
     563             :                 }
     564             :             }
     565             :             else
     566             :             {
     567           1 :                 bInvalidateWholeBandChunkOptim = true;
     568             :             }
     569             :         }
     570         263 :         if (bInvalidateWholeBandChunkOptim)
     571             :         {
     572           7 :             poGDS->m_eWholeBandChunkOptim = HDF5ImageDataset::WBC_DISABLED;
     573           7 :             poGDS->m_nLastRasterIOBand = -1;
     574           7 :             poGDS->m_nLastRasterIOXOff = -1;
     575           7 :             poGDS->m_nLastRasterIOYOff = -1;
     576           7 :             poGDS->m_nLastRasterIOXSize = -1;
     577           7 :             poGDS->m_nLastRasterIOYSize = -1;
     578             :         }
     579             :     }
     580             : 
     581         312 :     if (poGDS->m_eWholeBandChunkOptim == HDF5ImageDataset::WBC_ENABLED &&
     582         193 :         nXSize == nBufXSize && nYSize == nBufYSize)
     583             :     {
     584         193 :         if (poGDS->nBands == 1 || bIsBandInterleavedData)
     585             :         {
     586         193 :             if (poGDS->m_iCurrentBandChunk < 0)
     587           1 :                 CPLDebug("HDF5", "Using whole band chunk caching");
     588         193 :             const int iBandChunk = (nBand - 1) / poGDS->m_nBandChunkSize;
     589         193 :             if (iBandChunk != poGDS->m_iCurrentBandChunk)
     590             :             {
     591          15 :                 poGDS->m_abyBandChunk.resize(
     592          15 :                     static_cast<size_t>(poGDS->m_nBandChunkSize) *
     593          15 :                     nRasterXSize * nRasterYSize * nDTSize);
     594             : 
     595             :                 HDF5_GLOBAL_LOCK();
     596             : 
     597             :                 hsize_t count[3] = {
     598          30 :                     std::min(static_cast<hsize_t>(poGDS->nBands),
     599          30 :                              static_cast<hsize_t>(iBandChunk + 1) *
     600          15 :                                  poGDS->m_nBandChunkSize) -
     601          15 :                         static_cast<hsize_t>(iBandChunk) *
     602          15 :                             poGDS->m_nBandChunkSize,
     603          15 :                     static_cast<hsize_t>(nRasterYSize),
     604          15 :                     static_cast<hsize_t>(nRasterXSize)};
     605          15 :                 H5OFFSET_TYPE offset[3] = {
     606          15 :                     static_cast<H5OFFSET_TYPE>(iBandChunk) *
     607          15 :                         poGDS->m_nBandChunkSize,
     608             :                     static_cast<H5OFFSET_TYPE>(0),
     609          15 :                     static_cast<H5OFFSET_TYPE>(0)};
     610             :                 herr_t status =
     611          15 :                     H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
     612             :                                         offset, nullptr, count, nullptr);
     613          15 :                 if (status < 0)
     614           0 :                     return CE_Failure;
     615             : 
     616             :                 const hid_t memspace =
     617          15 :                     H5Screate_simple(poGDS->ndims, count, nullptr);
     618          15 :                 H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
     619             :                 status =
     620          15 :                     H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
     621             :                                         nullptr, count, nullptr);
     622          15 :                 if (status < 0)
     623             :                 {
     624           0 :                     H5Sclose(memspace);
     625           0 :                     return CE_Failure;
     626             :                 }
     627             : 
     628          15 :                 status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
     629             :                                  poGDS->dataspace_id, H5P_DEFAULT,
     630          15 :                                  poGDS->m_abyBandChunk.data());
     631             : 
     632          15 :                 H5Sclose(memspace);
     633             : 
     634          15 :                 if (status < 0)
     635             :                 {
     636           0 :                     CPLError(
     637             :                         CE_Failure, CPLE_AppDefined,
     638             :                         "HDF5ImageRasterBand::IRasterIO(): H5Dread() failed");
     639           0 :                     return CE_Failure;
     640             :                 }
     641             : 
     642          15 :                 poGDS->m_iCurrentBandChunk = iBandChunk;
     643             :             }
     644             : 
     645        1447 :             for (int iY = 0; iY < nYSize; ++iY)
     646             :             {
     647        2508 :                 GDALCopyWords(poGDS->m_abyBandChunk.data() +
     648        1254 :                                   static_cast<size_t>((nBand - 1) %
     649        1254 :                                                       poGDS->m_nBandChunkSize) *
     650        1254 :                                       nRasterYSize * nRasterXSize * nDTSize +
     651        1254 :                                   static_cast<size_t>(nYOff + iY) *
     652        1254 :                                       nRasterXSize * nDTSize +
     653        1254 :                                   nXOff * nDTSize,
     654             :                               eDataType, nDTSize,
     655             :                               static_cast<GByte *>(pData) +
     656        1254 :                                   static_cast<size_t>(iY) * nLineSpace,
     657             :                               eBufType, static_cast<int>(nPixelSpace), nXSize);
     658             :             }
     659         193 :             return CE_None;
     660             :         }
     661             :     }
     662             : 
     663             :     const bool bIsExpectedLayout =
     664         206 :         (bIsBandInterleavedData ||
     665         173 :          (poGDS->ndims == 2 && poGDS->GetYIndex() == 0 &&
     666          86 :           poGDS->GetXIndex() == 1));
     667         119 :     if (eRWFlag == GF_Read && bIsExpectedLayout && nXSize == nBufXSize &&
     668         117 :         nYSize == nBufYSize && eBufType == eDataType &&
     669          68 :         nPixelSpace == nDTSize && nLineSpace == nXSize * nPixelSpace)
     670             :     {
     671             :         HDF5_GLOBAL_LOCK();
     672             : 
     673          68 :         hsize_t count[3] = {1, static_cast<hsize_t>(nYSize),
     674          68 :                             static_cast<hsize_t>(nXSize)};
     675          68 :         H5OFFSET_TYPE offset[3] = {static_cast<H5OFFSET_TYPE>(nBand - 1),
     676          68 :                                    static_cast<H5OFFSET_TYPE>(nYOff),
     677          68 :                                    static_cast<H5OFFSET_TYPE>(nXOff)};
     678          68 :         if (poGDS->ndims == 2)
     679             :         {
     680          47 :             count[0] = count[1];
     681          47 :             count[1] = count[2];
     682             : 
     683          47 :             offset[0] = offset[1];
     684          47 :             offset[1] = offset[2];
     685             :         }
     686          68 :         herr_t status = H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
     687             :                                             offset, nullptr, count, nullptr);
     688          68 :         if (status < 0)
     689           0 :             return CE_Failure;
     690             : 
     691          68 :         const hid_t memspace = H5Screate_simple(poGDS->ndims, count, nullptr);
     692          68 :         H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
     693          68 :         status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
     694             :                                      nullptr, count, nullptr);
     695          68 :         if (status < 0)
     696             :         {
     697           0 :             H5Sclose(memspace);
     698           0 :             return CE_Failure;
     699             :         }
     700             : 
     701          68 :         status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
     702             :                          poGDS->dataspace_id, H5P_DEFAULT, pData);
     703             : 
     704          68 :         H5Sclose(memspace);
     705             : 
     706          68 :         if (status < 0)
     707             :         {
     708           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     709             :                      "HDF5ImageRasterBand::IRasterIO(): H5Dread() failed");
     710           0 :             return CE_Failure;
     711             :         }
     712             : 
     713          68 :         return CE_None;
     714             :     }
     715             : 
     716             :     // If the request is still small enough, try to read from libhdf5 with
     717             :     // the natural interleaving into a temporary MEMDataset, and then read
     718             :     // from it with the requested interleaving and data type.
     719          51 :     if (eRWFlag == GF_Read && bIsExpectedLayout && nXSize == nBufXSize &&
     720         102 :         nYSize == nBufYSize &&
     721          49 :         static_cast<GIntBig>(nXSize) * nYSize < CPLGetUsablePhysicalRAM() / 10)
     722             :     {
     723             :         auto poMemDS = std::unique_ptr<GDALDataset>(
     724          49 :             MEMDataset::Create("", nXSize, nYSize, 1, eDataType, nullptr));
     725          49 :         if (poMemDS)
     726             :         {
     727          49 :             void *pMemData = poMemDS->GetInternalHandle("MEMORY1");
     728          49 :             CPLAssert(pMemData);
     729             :             // Read from HDF5 into the temporary MEMDataset using the
     730             :             // natural interleaving of the HDF5 dataset
     731          49 :             ++m_nIRasterIORecCounter;
     732             :             CPLErr eErr =
     733          98 :                 IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pMemData,
     734             :                           nXSize, nYSize, eDataType, nDTSize,
     735          49 :                           static_cast<GSpacing>(nXSize) * nDTSize, psExtraArg);
     736          49 :             --m_nIRasterIORecCounter;
     737          49 :             if (eErr != CE_None)
     738             :             {
     739           0 :                 return CE_Failure;
     740             :             }
     741             :             // Copy to the final buffer using requested data type and spacings.
     742          49 :             return poMemDS->GetRasterBand(1)->RasterIO(
     743             :                 GF_Read, 0, 0, nXSize, nYSize, pData, nXSize, nYSize, eBufType,
     744          49 :                 nPixelSpace, nLineSpace, nullptr);
     745             :         }
     746             :     }
     747             : 
     748           2 :     return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     749             :                                         pData, nBufXSize, nBufYSize, eBufType,
     750           2 :                                         nPixelSpace, nLineSpace, psExtraArg);
     751             : }
     752             : 
     753             : /************************************************************************/
     754             : /*                             IRasterIO()                              */
     755             : /************************************************************************/
     756             : 
     757          90 : CPLErr HDF5ImageDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     758             :                                    int nXSize, int nYSize, void *pData,
     759             :                                    int nBufXSize, int nBufYSize,
     760             :                                    GDALDataType eBufType, int nBandCount,
     761             :                                    int *panBandMap, GSpacing nPixelSpace,
     762             :                                    GSpacing nLineSpace, GSpacing nBandSpace,
     763             :                                    GDALRasterIOExtraArg *psExtraArg)
     764             : 
     765             : {
     766         134 :     const auto IsConsecutiveBands = [](const int *panVals, int nCount)
     767             :     {
     768         151 :         for (int i = 1; i < nCount; ++i)
     769             :         {
     770          19 :             if (panVals[i] != panVals[i - 1] + 1)
     771           2 :                 return false;
     772             :         }
     773         132 :         return true;
     774             :     };
     775             : 
     776          90 :     const auto eDT = GetRasterBand(1)->GetRasterDataType();
     777          90 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
     778             : 
     779             :     // Band-interleaved data and request
     780         176 :     const bool bIsBandInterleavedData = ndims == 3 && m_nOtherDimIndex == 0 &&
     781         266 :                                         GetYIndex() == 1 && GetXIndex() == 2;
     782          90 :     if (eRWFlag == GF_Read && bIsBandInterleavedData && nXSize == nBufXSize &&
     783          86 :         nYSize == nBufYSize && IsConsecutiveBands(panBandMap, nBandCount) &&
     784          84 :         eBufType == eDT && nPixelSpace == nDTSize &&
     785         180 :         nLineSpace == nXSize * nPixelSpace && nBandSpace == nYSize * nLineSpace)
     786             :     {
     787             :         HDF5_GLOBAL_LOCK();
     788             : 
     789          43 :         hsize_t count[3] = {static_cast<hsize_t>(nBandCount),
     790          43 :                             static_cast<hsize_t>(nYSize),
     791          43 :                             static_cast<hsize_t>(nXSize)};
     792             :         H5OFFSET_TYPE offset[3] = {
     793          43 :             static_cast<H5OFFSET_TYPE>(panBandMap[0] - 1),
     794          43 :             static_cast<H5OFFSET_TYPE>(nYOff),
     795          43 :             static_cast<H5OFFSET_TYPE>(nXOff)};
     796          43 :         herr_t status = H5Sselect_hyperslab(dataspace_id, H5S_SELECT_SET,
     797             :                                             offset, nullptr, count, nullptr);
     798          43 :         if (status < 0)
     799           0 :             return CE_Failure;
     800             : 
     801          43 :         const hid_t memspace = H5Screate_simple(ndims, count, nullptr);
     802          43 :         H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
     803          43 :         status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
     804             :                                      nullptr, count, nullptr);
     805          43 :         if (status < 0)
     806             :         {
     807           0 :             H5Sclose(memspace);
     808           0 :             return CE_Failure;
     809             :         }
     810             : 
     811          43 :         status = H5Dread(dataset_id, native, memspace, dataspace_id,
     812             :                          H5P_DEFAULT, pData);
     813             : 
     814          43 :         H5Sclose(memspace);
     815             : 
     816          43 :         if (status < 0)
     817             :         {
     818           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     819             :                      "HDF5ImageDataset::IRasterIO(): H5Dread() failed");
     820           0 :             return CE_Failure;
     821             :         }
     822             : 
     823          43 :         return CE_None;
     824             :     }
     825             : 
     826             :     // Pixel-interleaved data and request
     827             : 
     828          51 :     const bool bIsPixelInterleaveData = ndims == 3 && m_nOtherDimIndex == 2 &&
     829          98 :                                         GetYIndex() == 0 && GetXIndex() == 1;
     830          47 :     if (eRWFlag == GF_Read && bIsPixelInterleaveData && nXSize == nBufXSize &&
     831           4 :         nYSize == nBufYSize && IsConsecutiveBands(panBandMap, nBandCount) &&
     832           4 :         eBufType == eDT && nBandSpace == nDTSize &&
     833          97 :         nPixelSpace == nBandCount * nBandSpace &&
     834           3 :         nLineSpace == nXSize * nPixelSpace)
     835             :     {
     836             :         HDF5_GLOBAL_LOCK();
     837             : 
     838           3 :         hsize_t count[3] = {static_cast<hsize_t>(nYSize),
     839           3 :                             static_cast<hsize_t>(nXSize),
     840           3 :                             static_cast<hsize_t>(nBandCount)};
     841             :         H5OFFSET_TYPE offset[3] = {
     842           3 :             static_cast<H5OFFSET_TYPE>(nYOff),
     843           3 :             static_cast<H5OFFSET_TYPE>(nXOff),
     844           3 :             static_cast<H5OFFSET_TYPE>(panBandMap[0] - 1)};
     845           3 :         herr_t status = H5Sselect_hyperslab(dataspace_id, H5S_SELECT_SET,
     846             :                                             offset, nullptr, count, nullptr);
     847           3 :         if (status < 0)
     848           0 :             return CE_Failure;
     849             : 
     850           3 :         const hid_t memspace = H5Screate_simple(ndims, count, nullptr);
     851           3 :         H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
     852           3 :         status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
     853             :                                      nullptr, count, nullptr);
     854           3 :         if (status < 0)
     855             :         {
     856           0 :             H5Sclose(memspace);
     857           0 :             return CE_Failure;
     858             :         }
     859             : 
     860           3 :         status = H5Dread(dataset_id, native, memspace, dataspace_id,
     861             :                          H5P_DEFAULT, pData);
     862             : 
     863           3 :         H5Sclose(memspace);
     864             : 
     865           3 :         if (status < 0)
     866             :         {
     867           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     868             :                      "HDF5ImageDataset::IRasterIO(): H5Dread() failed");
     869           0 :             return CE_Failure;
     870             :         }
     871             : 
     872           3 :         return CE_None;
     873             :     }
     874             : 
     875             :     // If the request is still small enough, try to read from libhdf5 with
     876             :     // the natural interleaving into a temporary MEMDataset, and then read
     877             :     // from it with the requested interleaving and data type.
     878          44 :     if (eRWFlag == GF_Read &&
     879          44 :         (bIsBandInterleavedData || bIsPixelInterleaveData) &&
     880          88 :         nXSize == nBufXSize && nYSize == nBufYSize &&
     881         132 :         IsConsecutiveBands(panBandMap, nBandCount) &&
     882          43 :         static_cast<GIntBig>(nXSize) * nYSize <
     883          43 :             CPLGetUsablePhysicalRAM() / 10 / nBandCount)
     884             :     {
     885          43 :         const char *const apszOptions[] = {
     886          43 :             bIsPixelInterleaveData ? "INTERLEAVE=PIXEL" : nullptr, nullptr};
     887             :         auto poMemDS = std::unique_ptr<GDALDataset>(
     888          43 :             MEMDataset::Create("", nXSize, nYSize, nBandCount, eDT,
     889          43 :                                const_cast<char **>(apszOptions)));
     890          43 :         if (poMemDS)
     891             :         {
     892          43 :             void *pMemData = poMemDS->GetInternalHandle("MEMORY1");
     893          43 :             CPLAssert(pMemData);
     894             :             // Read from HDF5 into the temporary MEMDataset using the
     895             :             // natural interleaving of the HDF5 dataset
     896         129 :             if (IRasterIO(
     897             :                     eRWFlag, nXOff, nYOff, nXSize, nYSize, pMemData, nXSize,
     898             :                     nYSize, eDT, nBandCount, panBandMap,
     899           1 :                     bIsBandInterleavedData ? nDTSize : nDTSize * nBandCount,
     900             :                     bIsBandInterleavedData
     901          42 :                         ? static_cast<GSpacing>(nXSize) * nDTSize
     902           1 :                         : static_cast<GSpacing>(nXSize) * nDTSize * nBandCount,
     903             :                     bIsBandInterleavedData
     904          42 :                         ? static_cast<GSpacing>(nYSize) * nXSize * nDTSize
     905             :                         : nDTSize,
     906          43 :                     psExtraArg) != CE_None)
     907             :             {
     908           0 :                 return CE_Failure;
     909             :             }
     910             :             // Copy to the final buffer using requested data type and spacings.
     911          43 :             return poMemDS->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pData,
     912             :                                      nXSize, nYSize, eBufType, nBandCount,
     913             :                                      nullptr, nPixelSpace, nLineSpace,
     914          43 :                                      nBandSpace, nullptr);
     915             :         }
     916             :     }
     917             : 
     918           1 :     return HDF5Dataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
     919             :                                   nBufXSize, nBufYSize, eBufType, nBandCount,
     920             :                                   panBandMap, nPixelSpace, nLineSpace,
     921           1 :                                   nBandSpace, psExtraArg);
     922             : }
     923             : 
     924             : /************************************************************************/
     925             : /*                                Open()                                */
     926             : /************************************************************************/
     927          60 : GDALDataset *HDF5ImageDataset::Open(GDALOpenInfo *poOpenInfo)
     928             : {
     929          60 :     if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "HDF5:"))
     930           0 :         return nullptr;
     931             : 
     932             :     HDF5_GLOBAL_LOCK();
     933             : 
     934             :     // Confirm the requested access is supported.
     935          60 :     if (poOpenInfo->eAccess == GA_Update)
     936             :     {
     937           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     938             :                  "The HDF5ImageDataset driver does not support update access "
     939             :                  "to existing datasets.");
     940           0 :         return nullptr;
     941             :     }
     942             : 
     943          60 :     HDF5ImageDataset *poDS = new HDF5ImageDataset();
     944             : 
     945             :     // Create a corresponding GDALDataset.
     946             :     char **papszName =
     947          60 :         CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     948             :                            CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
     949             : 
     950          60 :     if (!(CSLCount(papszName) == 3 || CSLCount(papszName) == 4))
     951             :     {
     952           0 :         CSLDestroy(papszName);
     953           0 :         delete poDS;
     954           0 :         return nullptr;
     955             :     }
     956             : 
     957          60 :     poDS->SetDescription(poOpenInfo->pszFilename);
     958             : 
     959             :     // Check for drive name in windows HDF5:"D:\...
     960         120 :     CPLString osSubdatasetName;
     961             : 
     962         120 :     CPLString osFilename(papszName[1]);
     963             : 
     964          60 :     if ((strlen(papszName[1]) == 1 && papszName[3] != nullptr) ||
     965          60 :         (STARTS_WITH(papszName[1], "/vsicurl/http") && papszName[3] != nullptr))
     966             :     {
     967           0 :         osFilename += ":";
     968           0 :         osFilename += papszName[2];
     969           0 :         osSubdatasetName = papszName[3];
     970             :     }
     971             :     else
     972             :     {
     973          60 :         osSubdatasetName = papszName[2];
     974             :     }
     975             : 
     976          60 :     poDS->SetSubdatasetName(osSubdatasetName);
     977             : 
     978          60 :     CSLDestroy(papszName);
     979          60 :     papszName = nullptr;
     980             : 
     981          60 :     poDS->SetPhysicalFilename(osFilename);
     982             : 
     983             :     // Try opening the dataset.
     984          60 :     poDS->m_hHDF5 = GDAL_HDF5Open(osFilename);
     985          60 :     if (poDS->m_hHDF5 < 0)
     986             :     {
     987           0 :         delete poDS;
     988           0 :         return nullptr;
     989             :     }
     990             : 
     991          60 :     poDS->hGroupID = H5Gopen(poDS->m_hHDF5, "/");
     992          60 :     if (poDS->hGroupID < 0)
     993             :     {
     994           0 :         delete poDS;
     995           0 :         return nullptr;
     996             :     }
     997             : 
     998             :     // This is an HDF5 file.
     999          60 :     poDS->ReadGlobalAttributes(FALSE);
    1000             : 
    1001             :     // Create HDF5 Data Hierarchy in a link list.
    1002          60 :     poDS->poH5Objects = poDS->HDF5FindDatasetObjectsbyPath(poDS->poH5RootGroup,
    1003             :                                                            osSubdatasetName);
    1004             : 
    1005          60 :     if (poDS->poH5Objects == nullptr)
    1006             :     {
    1007           0 :         delete poDS;
    1008           0 :         return nullptr;
    1009             :     }
    1010             : 
    1011             :     // Retrieve HDF5 data information.
    1012          60 :     poDS->dataset_id = H5Dopen(poDS->m_hHDF5, poDS->poH5Objects->pszPath);
    1013          60 :     poDS->dataspace_id = H5Dget_space(poDS->dataset_id);
    1014          60 :     poDS->ndims = H5Sget_simple_extent_ndims(poDS->dataspace_id);
    1015          60 :     if (poDS->ndims <= 0)
    1016             :     {
    1017           0 :         delete poDS;
    1018           0 :         return nullptr;
    1019             :     }
    1020          60 :     poDS->dims =
    1021          60 :         static_cast<hsize_t *>(CPLCalloc(poDS->ndims, sizeof(hsize_t)));
    1022          60 :     poDS->maxdims =
    1023          60 :         static_cast<hsize_t *>(CPLCalloc(poDS->ndims, sizeof(hsize_t)));
    1024          60 :     poDS->dimensions = H5Sget_simple_extent_dims(poDS->dataspace_id, poDS->dims,
    1025             :                                                  poDS->maxdims);
    1026          60 :     poDS->datatype = H5Dget_type(poDS->dataset_id);
    1027          60 :     poDS->size = H5Tget_size(poDS->datatype);
    1028          60 :     poDS->native = H5Tget_native_type(poDS->datatype, H5T_DIR_ASCEND);
    1029             : 
    1030             :     // CSK code in IdentifyProductType() and CreateProjections()
    1031             :     // uses dataset metadata.
    1032          60 :     poDS->SetMetadata(poDS->m_aosMetadata.List());
    1033             : 
    1034             :     // Check if the hdf5 is a well known product type
    1035          60 :     poDS->IdentifyProductType();
    1036             : 
    1037          60 :     poDS->m_nYIndex = poDS->IsComplexCSKL1A() ? 0 : poDS->ndims - 2;
    1038          60 :     poDS->m_nXIndex = poDS->IsComplexCSKL1A() ? 1 : poDS->ndims - 1;
    1039             : 
    1040          60 :     if (poDS->IsComplexCSKL1A())
    1041             :     {
    1042           0 :         poDS->m_nOtherDimIndex = 2;
    1043             :     }
    1044          60 :     else if (poDS->ndims == 3)
    1045             :     {
    1046           9 :         poDS->m_nOtherDimIndex = 0;
    1047             :     }
    1048             : 
    1049          60 :     if (HDF5EOSParser::HasHDFEOS(poDS->hGroupID))
    1050             :     {
    1051          34 :         HDF5EOSParser oHDFEOSParser;
    1052          17 :         if (oHDFEOSParser.Parse(poDS->hGroupID))
    1053             :         {
    1054          17 :             CPLDebug("HDF5", "Successfully parsed HDFEOS metadata");
    1055          34 :             HDF5EOSParser::GridDataFieldMetadata oGridDataFieldMetadata;
    1056          34 :             HDF5EOSParser::SwathDataFieldMetadata oSwathDataFieldMetadata;
    1057          17 :             if (oHDFEOSParser.GetDataModel() ==
    1058           5 :                     HDF5EOSParser::DataModel::GRID &&
    1059           5 :                 oHDFEOSParser.GetGridDataFieldMetadata(
    1060          22 :                     osSubdatasetName.c_str(), oGridDataFieldMetadata) &&
    1061           5 :                 static_cast<int>(oGridDataFieldMetadata.aoDimensions.size()) ==
    1062           5 :                     poDS->ndims)
    1063             :             {
    1064           5 :                 int iDim = 0;
    1065          17 :                 for (const auto &oDim : oGridDataFieldMetadata.aoDimensions)
    1066             :                 {
    1067          12 :                     if (oDim.osName == "XDim")
    1068           5 :                         poDS->m_nXIndex = iDim;
    1069           7 :                     else if (oDim.osName == "YDim")
    1070           5 :                         poDS->m_nYIndex = iDim;
    1071             :                     else
    1072           2 :                         poDS->m_nOtherDimIndex = iDim;
    1073          12 :                     ++iDim;
    1074             :                 }
    1075             : 
    1076           5 :                 if (oGridDataFieldMetadata.poGridMetadata->GetGeoTransform(
    1077           5 :                         poDS->adfGeoTransform))
    1078           5 :                     poDS->bHasGeoTransform = true;
    1079             : 
    1080          10 :                 auto poSRS = oGridDataFieldMetadata.poGridMetadata->GetSRS();
    1081           5 :                 if (poSRS)
    1082           5 :                     poDS->m_oSRS = *(poSRS.get());
    1083             :             }
    1084          12 :             else if (oHDFEOSParser.GetDataModel() ==
    1085          12 :                          HDF5EOSParser::DataModel::SWATH &&
    1086          12 :                      oHDFEOSParser.GetSwathDataFieldMetadata(
    1087           6 :                          osSubdatasetName.c_str(), oSwathDataFieldMetadata) &&
    1088             :                      static_cast<int>(
    1089           6 :                          oSwathDataFieldMetadata.aoDimensions.size()) ==
    1090           6 :                          poDS->ndims &&
    1091          30 :                      oSwathDataFieldMetadata.iXDim >= 0 &&
    1092           6 :                      oSwathDataFieldMetadata.iYDim >= 0)
    1093             :             {
    1094           6 :                 poDS->m_nXIndex = oSwathDataFieldMetadata.iXDim;
    1095           6 :                 poDS->m_nYIndex = oSwathDataFieldMetadata.iYDim;
    1096           6 :                 poDS->m_nOtherDimIndex = oSwathDataFieldMetadata.iOtherDim;
    1097           6 :                 if (!oSwathDataFieldMetadata.osLongitudeSubdataset.empty())
    1098             :                 {
    1099             :                     // Arbitrary
    1100           6 :                     poDS->SetMetadataItem("SRS", SRS_WKT_WGS84_LAT_LONG,
    1101           6 :                                           "GEOLOCATION");
    1102           6 :                     poDS->SetMetadataItem(
    1103             :                         "X_DATASET",
    1104          12 :                         ("HDF5:\"" + osFilename +
    1105          12 :                          "\":" + oSwathDataFieldMetadata.osLongitudeSubdataset)
    1106             :                             .c_str(),
    1107           6 :                         "GEOLOCATION");
    1108           6 :                     poDS->SetMetadataItem("X_BAND", "1", "GEOLOCATION");
    1109           6 :                     poDS->SetMetadataItem(
    1110             :                         "Y_DATASET",
    1111          12 :                         ("HDF5:\"" + osFilename +
    1112          12 :                          "\":" + oSwathDataFieldMetadata.osLatitudeSubdataset)
    1113             :                             .c_str(),
    1114           6 :                         "GEOLOCATION");
    1115           6 :                     poDS->SetMetadataItem("Y_BAND", "1", "GEOLOCATION");
    1116           6 :                     poDS->SetMetadataItem(
    1117             :                         "PIXEL_OFFSET",
    1118             :                         CPLSPrintf("%d", oSwathDataFieldMetadata.nPixelOffset),
    1119           6 :                         "GEOLOCATION");
    1120           6 :                     poDS->SetMetadataItem(
    1121             :                         "PIXEL_STEP",
    1122             :                         CPLSPrintf("%d", oSwathDataFieldMetadata.nPixelStep),
    1123           6 :                         "GEOLOCATION");
    1124           6 :                     poDS->SetMetadataItem(
    1125             :                         "LINE_OFFSET",
    1126             :                         CPLSPrintf("%d", oSwathDataFieldMetadata.nLineOffset),
    1127           6 :                         "GEOLOCATION");
    1128           6 :                     poDS->SetMetadataItem(
    1129             :                         "LINE_STEP",
    1130             :                         CPLSPrintf("%d", oSwathDataFieldMetadata.nLineStep),
    1131           6 :                         "GEOLOCATION");
    1132             :                     // Not totally sure about that
    1133           6 :                     poDS->SetMetadataItem("GEOREFERENCING_CONVENTION",
    1134           6 :                                           "PIXEL_CENTER", "GEOLOCATION");
    1135             :                 }
    1136             :             }
    1137             :         }
    1138             :     }
    1139             : 
    1140          60 :     poDS->nRasterYSize =
    1141          60 :         poDS->GetYIndex() < 0
    1142          60 :             ? 1
    1143          59 :             : static_cast<int>(poDS->dims[poDS->GetYIndex()]);  // nRows
    1144          60 :     poDS->nRasterXSize =
    1145          60 :         static_cast<int>(poDS->dims[poDS->GetXIndex()]);  // nCols
    1146          60 :     int nBands = 1;
    1147          60 :     if (poDS->m_nOtherDimIndex >= 0)
    1148             :     {
    1149           9 :         nBands = static_cast<int>(poDS->dims[poDS->m_nOtherDimIndex]);
    1150             :     }
    1151             : 
    1152         120 :     CPLStringList aosMetadata;
    1153          60 :     std::map<std::string, CPLStringList> oMapBandSpecificMetadata;
    1154          60 :     if (poDS->poH5Objects->nType == H5G_DATASET)
    1155             :     {
    1156          60 :         HDF5Dataset::CreateMetadata(poDS->m_hHDF5, poDS->poH5Objects,
    1157             :                                     H5G_DATASET, false, aosMetadata);
    1158          60 :         if (nBands > 1)
    1159             :         {
    1160             :             // Logic specific of Planet data cubes with per-band metadata items
    1161             :             static const struct
    1162             :             {
    1163             :                 const char *pszSrcName;
    1164             :                 const char *pszDstName;
    1165             :             } asItems[] = {
    1166             :                 {"calibration_coefficients", "calibration_coefficient"},
    1167             :                 {"center_wavelengths", "center_wavelength"},
    1168             :                 {"fwhm", "fwhm"},
    1169             :                 {"bad_band_list", "bad_band"},
    1170             :             };
    1171             : 
    1172          45 :             for (const auto &sItem : asItems)
    1173             :             {
    1174             :                 const char *pszVal =
    1175          36 :                     aosMetadata.FetchNameValue(sItem.pszSrcName);
    1176          36 :                 if (pszVal)
    1177             :                 {
    1178           4 :                     CPLStringList aosTokens(CSLTokenizeString2(pszVal, " ", 0));
    1179           2 :                     if (aosTokens.size() == nBands)
    1180             :                     {
    1181           4 :                         oMapBandSpecificMetadata[sItem.pszDstName] =
    1182           4 :                             std::move(aosTokens);
    1183           2 :                         aosMetadata.SetNameValue(sItem.pszSrcName, nullptr);
    1184             :                     }
    1185             :                 }
    1186             :             }
    1187             :         }
    1188             :     }
    1189             : 
    1190          60 :     poDS->m_nBlockXSize = poDS->GetRasterXSize();
    1191          60 :     poDS->m_nBlockYSize = 1;
    1192          60 :     poDS->m_nBandChunkSize = 1;
    1193             : 
    1194             :     // Check for chunksize and set it as the blocksize (optimizes read).
    1195          60 :     const hid_t listid = H5Dget_create_plist(poDS->dataset_id);
    1196          60 :     if (listid > 0)
    1197             :     {
    1198          60 :         if (H5Pget_layout(listid) == H5D_CHUNKED)
    1199             :         {
    1200          21 :             hsize_t panChunkDims[3] = {0, 0, 0};
    1201          21 :             const int nDimSize = H5Pget_chunk(listid, 3, panChunkDims);
    1202          21 :             CPL_IGNORE_RET_VAL(nDimSize);
    1203          21 :             CPLAssert(nDimSize == poDS->ndims);
    1204          21 :             poDS->m_nBlockXSize =
    1205          21 :                 static_cast<int>(panChunkDims[poDS->GetXIndex()]);
    1206          21 :             if (poDS->GetYIndex() >= 0)
    1207          21 :                 poDS->m_nBlockYSize =
    1208          21 :                     static_cast<int>(panChunkDims[poDS->GetYIndex()]);
    1209          21 :             if (nBands > 1)
    1210             :             {
    1211           3 :                 poDS->m_nBandChunkSize =
    1212           3 :                     static_cast<int>(panChunkDims[poDS->m_nOtherDimIndex]);
    1213             : 
    1214           3 :                 poDS->SetMetadataItem("BAND_CHUNK_SIZE",
    1215             :                                       CPLSPrintf("%d", poDS->m_nBandChunkSize),
    1216           3 :                                       "IMAGE_STRUCTURE");
    1217             :             }
    1218             :         }
    1219             : 
    1220          60 :         const int nFilters = H5Pget_nfilters(listid);
    1221          86 :         for (int i = 0; i < nFilters; ++i)
    1222             :         {
    1223          26 :             unsigned int flags = 0;
    1224          26 :             size_t cd_nelmts = 0;
    1225          26 :             char szName[64 + 1] = {0};
    1226          26 :             const auto eFilter = H5Pget_filter(listid, i, &flags, &cd_nelmts,
    1227             :                                                nullptr, 64, szName);
    1228          26 :             if (eFilter == H5Z_FILTER_DEFLATE)
    1229             :             {
    1230          15 :                 poDS->SetMetadataItem("COMPRESSION", "DEFLATE",
    1231          15 :                                       "IMAGE_STRUCTURE");
    1232             :             }
    1233          11 :             else if (eFilter == H5Z_FILTER_SZIP)
    1234             :             {
    1235           0 :                 poDS->SetMetadataItem("COMPRESSION", "SZIP", "IMAGE_STRUCTURE");
    1236             :             }
    1237             :         }
    1238             : 
    1239          60 :         H5Pclose(listid);
    1240             :     }
    1241             : 
    1242         182 :     for (int i = 0; i < nBands; i++)
    1243             :     {
    1244             :         HDF5ImageRasterBand *const poBand = new HDF5ImageRasterBand(
    1245         122 :             poDS, i + 1, poDS->GetDataType(poDS->native));
    1246             : 
    1247         122 :         poDS->SetBand(i + 1, poBand);
    1248             : 
    1249         122 :         if (poDS->poH5Objects->nType == H5G_DATASET)
    1250             :         {
    1251         122 :             poBand->SetMetadata(aosMetadata.List());
    1252         126 :             for (const auto &oIter : oMapBandSpecificMetadata)
    1253             :             {
    1254           4 :                 poBand->SetMetadataItem(oIter.first.c_str(), oIter.second[i]);
    1255             :             }
    1256             :         }
    1257             :     }
    1258             : 
    1259          60 :     if (!poDS->GetMetadata("GEOLOCATION"))
    1260          54 :         poDS->CreateProjections();
    1261             : 
    1262             :     // Setup/check for pam .aux.xml.
    1263          60 :     poDS->TryLoadXML();
    1264             : 
    1265             :     // Setup overviews.
    1266          60 :     poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
    1267             : 
    1268          60 :     return poDS;
    1269             : }
    1270             : 
    1271             : /************************************************************************/
    1272             : /*                   HDF5ImageDatasetDriverUnload()                     */
    1273             : /************************************************************************/
    1274             : 
    1275           5 : static void HDF5ImageDatasetDriverUnload(GDALDriver *)
    1276             : {
    1277           5 :     HDF5UnloadFileDriver();
    1278           5 : }
    1279             : 
    1280             : /************************************************************************/
    1281             : /*                        GDALRegister_HDF5Image()                      */
    1282             : /************************************************************************/
    1283           8 : void GDALRegister_HDF5Image()
    1284             : 
    1285             : {
    1286           8 :     if (!GDAL_CHECK_VERSION("HDF5Image driver"))
    1287           0 :         return;
    1288             : 
    1289           8 :     if (GDALGetDriverByName(HDF5_IMAGE_DRIVER_NAME) != nullptr)
    1290           0 :         return;
    1291             : 
    1292           8 :     GDALDriver *poDriver = new GDALDriver();
    1293             : 
    1294           8 :     HDF5ImageDriverSetCommonMetadata(poDriver);
    1295             : 
    1296           8 :     poDriver->pfnOpen = HDF5ImageDataset::Open;
    1297           8 :     poDriver->pfnUnloadDriver = HDF5ImageDatasetDriverUnload;
    1298             : 
    1299           8 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1300             : }
    1301             : 
    1302             : /************************************************************************/
    1303             : /*                       CreateODIMH5Projection()                       */
    1304             : /************************************************************************/
    1305             : 
    1306             : // Reference:
    1307             : //   http://www.knmi.nl/opera/opera3/OPERA_2008_03_WP2.1b_ODIM_H5_v2.1.pdf
    1308             : //
    1309             : // 4.3.2 where for geographically referenced image Groups
    1310             : // We don't use the where_xscale and where_yscale parameters, but recompute them
    1311             : // from the lower-left and upper-right coordinates. There's some difference.
    1312             : // As all those parameters are linked together, I'm not sure which one should be
    1313             : // considered as the reference.
    1314             : 
    1315           0 : CPLErr HDF5ImageDataset::CreateODIMH5Projection()
    1316             : {
    1317           0 :     const char *const pszProj4String = GetMetadataItem("where_projdef");
    1318           0 :     const char *const pszLL_lon = GetMetadataItem("where_LL_lon");
    1319           0 :     const char *const pszLL_lat = GetMetadataItem("where_LL_lat");
    1320           0 :     const char *const pszUR_lon = GetMetadataItem("where_UR_lon");
    1321           0 :     const char *const pszUR_lat = GetMetadataItem("where_UR_lat");
    1322           0 :     if (pszProj4String == nullptr || pszLL_lon == nullptr ||
    1323           0 :         pszLL_lat == nullptr || pszUR_lon == nullptr || pszUR_lat == nullptr)
    1324           0 :         return CE_Failure;
    1325             : 
    1326           0 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1327           0 :     if (m_oSRS.importFromProj4(pszProj4String) != OGRERR_NONE)
    1328           0 :         return CE_Failure;
    1329             : 
    1330           0 :     OGRSpatialReference oSRSWGS84;
    1331           0 :     oSRSWGS84.SetWellKnownGeogCS("WGS84");
    1332           0 :     oSRSWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1333             : 
    1334             :     OGRCoordinateTransformation *poCT =
    1335           0 :         OGRCreateCoordinateTransformation(&oSRSWGS84, &m_oSRS);
    1336           0 :     if (poCT == nullptr)
    1337           0 :         return CE_Failure;
    1338             : 
    1339             :     // Reproject corners from long,lat WGS84 to the target SRS.
    1340           0 :     double dfLLX = CPLAtof(pszLL_lon);
    1341           0 :     double dfLLY = CPLAtof(pszLL_lat);
    1342           0 :     double dfURX = CPLAtof(pszUR_lon);
    1343           0 :     double dfURY = CPLAtof(pszUR_lat);
    1344           0 :     if (!poCT->Transform(1, &dfLLX, &dfLLY) ||
    1345           0 :         !poCT->Transform(1, &dfURX, &dfURY))
    1346             :     {
    1347           0 :         delete poCT;
    1348           0 :         return CE_Failure;
    1349             :     }
    1350           0 :     delete poCT;
    1351             : 
    1352             :     // Compute the geotransform now.
    1353           0 :     const double dfPixelX = (dfURX - dfLLX) / nRasterXSize;
    1354           0 :     const double dfPixelY = (dfURY - dfLLY) / nRasterYSize;
    1355             : 
    1356           0 :     bHasGeoTransform = true;
    1357           0 :     adfGeoTransform[0] = dfLLX;
    1358           0 :     adfGeoTransform[1] = dfPixelX;
    1359           0 :     adfGeoTransform[2] = 0;
    1360           0 :     adfGeoTransform[3] = dfURY;
    1361           0 :     adfGeoTransform[4] = 0;
    1362           0 :     adfGeoTransform[5] = -dfPixelY;
    1363             : 
    1364           0 :     return CE_None;
    1365             : }
    1366             : 
    1367             : /************************************************************************/
    1368             : /*                         CreateProjections()                          */
    1369             : /************************************************************************/
    1370          54 : CPLErr HDF5ImageDataset::CreateProjections()
    1371             : {
    1372          54 :     switch (iSubdatasetType)
    1373             :     {
    1374           2 :         case CSK_PRODUCT:
    1375             :         {
    1376           2 :             int productType = PROD_UNKNOWN;
    1377             : 
    1378           2 :             if (GetMetadataItem("Product_Type") != nullptr)
    1379             :             {
    1380             :                 // Get the format's level.
    1381             :                 const char *osMissionLevel =
    1382           2 :                     HDF5Dataset::GetMetadataItem("Product_Type");
    1383             : 
    1384           2 :                 if (STARTS_WITH_CI(osMissionLevel, "RAW"))
    1385           0 :                     productType = PROD_CSK_L0;
    1386             : 
    1387           2 :                 if (STARTS_WITH_CI(osMissionLevel, "SSC"))
    1388           0 :                     productType = PROD_CSK_L1A;
    1389             : 
    1390           2 :                 if (STARTS_WITH_CI(osMissionLevel, "DGM"))
    1391           1 :                     productType = PROD_CSK_L1B;
    1392             : 
    1393           2 :                 if (STARTS_WITH_CI(osMissionLevel, "GEC"))
    1394           1 :                     productType = PROD_CSK_L1C;
    1395             : 
    1396           2 :                 if (STARTS_WITH_CI(osMissionLevel, "GTC"))
    1397           0 :                     productType = PROD_CSK_L1D;
    1398             :             }
    1399             : 
    1400           2 :             CaptureCSKGeoTransform(productType);
    1401           2 :             CaptureCSKGeolocation(productType);
    1402           2 :             CaptureCSKGCPs(productType);
    1403             : 
    1404           2 :             break;
    1405             :         }
    1406          52 :         case UNKNOWN_PRODUCT:
    1407             :         {
    1408          52 :             constexpr int NBGCPLAT = 100;
    1409          52 :             constexpr int NBGCPLON = 30;
    1410             : 
    1411          52 :             const int nDeltaLat = nRasterYSize / NBGCPLAT;
    1412          52 :             const int nDeltaLon = nRasterXSize / NBGCPLON;
    1413             : 
    1414          52 :             if (nDeltaLat == 0 || nDeltaLon == 0)
    1415          34 :                 return CE_None;
    1416             : 
    1417             :             // Create HDF5 Data Hierarchy in a link list.
    1418          18 :             poH5Objects = HDF5FindDatasetObjects(poH5RootGroup, "Latitude");
    1419          18 :             if (!poH5Objects)
    1420             :             {
    1421          18 :                 if (GetMetadataItem("where_projdef") != nullptr)
    1422           0 :                     return CreateODIMH5Projection();
    1423          18 :                 return CE_None;
    1424             :             }
    1425             : 
    1426             :             // The Latitude and Longitude arrays must have a rank of 2 to
    1427             :             // retrieve GCPs.
    1428           0 :             if (poH5Objects->nRank != 2 ||
    1429           0 :                 poH5Objects->paDims[0] != static_cast<size_t>(nRasterYSize) ||
    1430           0 :                 poH5Objects->paDims[1] != static_cast<size_t>(nRasterXSize))
    1431             :             {
    1432           0 :                 return CE_None;
    1433             :             }
    1434             : 
    1435             :             // Retrieve HDF5 data information.
    1436             :             const hid_t LatitudeDatasetID =
    1437           0 :                 H5Dopen(m_hHDF5, poH5Objects->pszPath);
    1438             :             // LatitudeDataspaceID = H5Dget_space(dataset_id);
    1439             : 
    1440           0 :             poH5Objects = HDF5FindDatasetObjects(poH5RootGroup, "Longitude");
    1441             :             // GCPs.
    1442           0 :             if (poH5Objects == nullptr || poH5Objects->nRank != 2 ||
    1443           0 :                 poH5Objects->paDims[0] != static_cast<size_t>(nRasterYSize) ||
    1444           0 :                 poH5Objects->paDims[1] != static_cast<size_t>(nRasterXSize))
    1445             :             {
    1446           0 :                 if (LatitudeDatasetID > 0)
    1447           0 :                     H5Dclose(LatitudeDatasetID);
    1448           0 :                 return CE_None;
    1449             :             }
    1450             : 
    1451             :             const hid_t LongitudeDatasetID =
    1452           0 :                 H5Dopen(m_hHDF5, poH5Objects->pszPath);
    1453             :             // LongitudeDataspaceID = H5Dget_space(dataset_id);
    1454             : 
    1455           0 :             if (LatitudeDatasetID > 0 && LongitudeDatasetID > 0)
    1456             :             {
    1457             :                 float *const Latitude =
    1458           0 :                     static_cast<float *>(VSI_MALLOC3_VERBOSE(
    1459             :                         nRasterYSize, nRasterXSize, sizeof(float)));
    1460             :                 float *const Longitude =
    1461           0 :                     static_cast<float *>(VSI_MALLOC3_VERBOSE(
    1462             :                         nRasterYSize, nRasterXSize, sizeof(float)));
    1463           0 :                 if (!Latitude || !Longitude)
    1464             :                 {
    1465           0 :                     CPLFree(Latitude);
    1466           0 :                     CPLFree(Longitude);
    1467           0 :                     H5Dclose(LatitudeDatasetID);
    1468           0 :                     H5Dclose(LongitudeDatasetID);
    1469           0 :                     return CE_Failure;
    1470             :                 }
    1471           0 :                 memset(Latitude, 0,
    1472           0 :                        static_cast<size_t>(nRasterXSize) * nRasterYSize *
    1473             :                            sizeof(float));
    1474           0 :                 memset(Longitude, 0,
    1475           0 :                        static_cast<size_t>(nRasterXSize) * nRasterYSize *
    1476             :                            sizeof(float));
    1477             : 
    1478             :                 // netCDF convention for nodata
    1479           0 :                 double dfLatNoData = 0;
    1480           0 :                 bool bHasLatNoData = GH5_FetchAttribute(
    1481             :                     LatitudeDatasetID, "_FillValue", dfLatNoData);
    1482             : 
    1483           0 :                 double dfLongNoData = 0;
    1484           0 :                 bool bHasLongNoData = GH5_FetchAttribute(
    1485             :                     LongitudeDatasetID, "_FillValue", dfLongNoData);
    1486             : 
    1487           0 :                 H5Dread(LatitudeDatasetID, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL,
    1488             :                         H5P_DEFAULT, Latitude);
    1489             : 
    1490           0 :                 H5Dread(LongitudeDatasetID, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL,
    1491             :                         H5P_DEFAULT, Longitude);
    1492             : 
    1493           0 :                 m_oSRS.Clear();
    1494           0 :                 m_oGCPSRS.SetWellKnownGeogCS("WGS84");
    1495             : 
    1496           0 :                 const int nYLimit =
    1497           0 :                     (static_cast<int>(nRasterYSize) / nDeltaLat) * nDeltaLat;
    1498           0 :                 const int nXLimit =
    1499           0 :                     (static_cast<int>(nRasterXSize) / nDeltaLon) * nDeltaLon;
    1500             : 
    1501             :                 // The original code in
    1502             :                 // https://trac.osgeo.org/gdal/changeset/8066 always add +180 to
    1503             :                 // the longitudes, but without justification I suspect this
    1504             :                 // might be due to handling products crossing the antimeridian.
    1505             :                 // Trying to do it just when needed through a heuristics.
    1506           0 :                 bool bHasLonNearMinus180 = false;
    1507           0 :                 bool bHasLonNearPlus180 = false;
    1508           0 :                 bool bHasLonNearZero = false;
    1509           0 :                 for (int j = 0; j < nYLimit; j += nDeltaLat)
    1510             :                 {
    1511           0 :                     for (int i = 0; i < nXLimit; i += nDeltaLon)
    1512             :                     {
    1513           0 :                         const int iGCP = j * nRasterXSize + i;
    1514           0 :                         if ((bHasLatNoData && static_cast<float>(dfLatNoData) ==
    1515           0 :                                                   Latitude[iGCP]) ||
    1516           0 :                             (bHasLongNoData &&
    1517           0 :                              static_cast<float>(dfLongNoData) ==
    1518           0 :                                  Longitude[iGCP]))
    1519           0 :                             continue;
    1520           0 :                         if (Longitude[iGCP] > 170 && Longitude[iGCP] <= 180)
    1521           0 :                             bHasLonNearPlus180 = true;
    1522           0 :                         if (Longitude[iGCP] < -170 && Longitude[iGCP] >= -180)
    1523           0 :                             bHasLonNearMinus180 = true;
    1524           0 :                         if (fabs(Longitude[iGCP]) < 90)
    1525           0 :                             bHasLonNearZero = true;
    1526             :                     }
    1527             :                 }
    1528             : 
    1529             :                 // Fill the GCPs list.
    1530             :                 const char *pszShiftGCP =
    1531           0 :                     CPLGetConfigOption("HDF5_SHIFT_GCPX_BY_180", nullptr);
    1532             :                 const bool bAdd180 =
    1533           0 :                     (bHasLonNearPlus180 && bHasLonNearMinus180 &&
    1534           0 :                      !bHasLonNearZero && pszShiftGCP == nullptr) ||
    1535           0 :                     (pszShiftGCP != nullptr && CPLTestBool(pszShiftGCP));
    1536             : 
    1537           0 :                 for (int j = 0; j < nYLimit; j += nDeltaLat)
    1538             :                 {
    1539           0 :                     for (int i = 0; i < nXLimit; i += nDeltaLon)
    1540             :                     {
    1541           0 :                         const int iGCP = j * nRasterXSize + i;
    1542           0 :                         if ((bHasLatNoData && static_cast<float>(dfLatNoData) ==
    1543           0 :                                                   Latitude[iGCP]) ||
    1544           0 :                             (bHasLongNoData &&
    1545           0 :                              static_cast<float>(dfLongNoData) ==
    1546           0 :                                  Longitude[iGCP]))
    1547           0 :                             continue;
    1548           0 :                         double dfGCPX = static_cast<double>(Longitude[iGCP]);
    1549           0 :                         if (bAdd180)
    1550           0 :                             dfGCPX += 180.0;
    1551           0 :                         const double dfGCPY =
    1552           0 :                             static_cast<double>(Latitude[iGCP]);
    1553             : 
    1554           0 :                         m_aoGCPs.emplace_back("", "", i + 0.5, j + 0.5, dfGCPX,
    1555           0 :                                               dfGCPY);
    1556             :                     }
    1557             :                 }
    1558             : 
    1559           0 :                 CPLFree(Latitude);
    1560           0 :                 CPLFree(Longitude);
    1561             :             }
    1562             : 
    1563           0 :             if (LatitudeDatasetID > 0)
    1564           0 :                 H5Dclose(LatitudeDatasetID);
    1565           0 :             if (LongitudeDatasetID > 0)
    1566           0 :                 H5Dclose(LongitudeDatasetID);
    1567             : 
    1568           0 :             break;
    1569             :         }
    1570             :     }
    1571             : 
    1572           2 :     return CE_None;
    1573             : }
    1574             : 
    1575             : /************************************************************************/
    1576             : /*                         GetMetadataItem()                            */
    1577             : /************************************************************************/
    1578             : 
    1579         127 : const char *HDF5ImageDataset::GetMetadataItem(const char *pszName,
    1580             :                                               const char *pszDomain)
    1581             : {
    1582         127 :     if (pszDomain && EQUAL(pszDomain, "__DEBUG__") &&
    1583          66 :         EQUAL(pszName, "WholeBandChunkOptim"))
    1584             :     {
    1585          66 :         switch (m_eWholeBandChunkOptim)
    1586             :         {
    1587           4 :             case WBC_DETECTION_IN_PROGRESS:
    1588           4 :                 return "DETECTION_IN_PROGRESS";
    1589           3 :             case WBC_DISABLED:
    1590           3 :                 return "DISABLED";
    1591          59 :             case WBC_ENABLED:
    1592          59 :                 return "ENABLED";
    1593             :         }
    1594             :     }
    1595          61 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
    1596             : }
    1597             : 
    1598             : /************************************************************************/
    1599             : /*                         GetSpatialRef()                              */
    1600             : /************************************************************************/
    1601             : 
    1602          12 : const OGRSpatialReference *HDF5ImageDataset::GetSpatialRef() const
    1603             : {
    1604          12 :     if (!m_oSRS.IsEmpty())
    1605          11 :         return &m_oSRS;
    1606           1 :     return GDALPamDataset::GetSpatialRef();
    1607             : }
    1608             : 
    1609             : /************************************************************************/
    1610             : /*                            GetGCPCount()                             */
    1611             : /************************************************************************/
    1612             : 
    1613           3 : int HDF5ImageDataset::GetGCPCount()
    1614             : 
    1615             : {
    1616           3 :     if (!m_aoGCPs.empty())
    1617           1 :         return static_cast<int>(m_aoGCPs.size());
    1618             : 
    1619           2 :     return GDALPamDataset::GetGCPCount();
    1620             : }
    1621             : 
    1622             : /************************************************************************/
    1623             : /*                        GetGCPSpatialRef()                            */
    1624             : /************************************************************************/
    1625             : 
    1626           1 : const OGRSpatialReference *HDF5ImageDataset::GetGCPSpatialRef() const
    1627             : 
    1628             : {
    1629           1 :     if (!m_aoGCPs.empty() && !m_oGCPSRS.IsEmpty())
    1630           1 :         return &m_oGCPSRS;
    1631             : 
    1632           0 :     return GDALPamDataset::GetGCPSpatialRef();
    1633             : }
    1634             : 
    1635             : /************************************************************************/
    1636             : /*                               GetGCPs()                              */
    1637             : /************************************************************************/
    1638             : 
    1639           2 : const GDAL_GCP *HDF5ImageDataset::GetGCPs()
    1640             : {
    1641           2 :     if (!m_aoGCPs.empty())
    1642           1 :         return gdal::GCP::c_ptr(m_aoGCPs);
    1643             : 
    1644           1 :     return GDALPamDataset::GetGCPs();
    1645             : }
    1646             : 
    1647             : /************************************************************************/
    1648             : /*                         GetGeoTransform()                            */
    1649             : /************************************************************************/
    1650             : 
    1651           6 : CPLErr HDF5ImageDataset::GetGeoTransform(double *padfTransform)
    1652             : {
    1653           6 :     if (bHasGeoTransform)
    1654             :     {
    1655           5 :         memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
    1656           5 :         return CE_None;
    1657             :     }
    1658             : 
    1659           1 :     return GDALPamDataset::GetGeoTransform(padfTransform);
    1660             : }
    1661             : 
    1662             : /************************************************************************/
    1663             : /*                       IdentifyProductType()                          */
    1664             : /************************************************************************/
    1665             : 
    1666             : /**
    1667             :  * Identify if the subdataset has a known product format
    1668             :  * It stores a product identifier in iSubdatasetType,
    1669             :  * UNKNOWN_PRODUCT, if it isn't a recognizable format.
    1670             :  */
    1671          60 : void HDF5ImageDataset::IdentifyProductType()
    1672             : {
    1673          60 :     iSubdatasetType = UNKNOWN_PRODUCT;
    1674             : 
    1675             :     // COSMO-SKYMED
    1676             : 
    1677             :     // Get the Mission Id as a char *, because the field may not exist.
    1678          60 :     const char *const pszMissionId = HDF5Dataset::GetMetadataItem("Mission_ID");
    1679             : 
    1680             :     // If there is a Mission_ID field.
    1681          60 :     if (pszMissionId != nullptr && strstr(GetDescription(), "QLK") == nullptr)
    1682             :     {
    1683             :         // Check if the mission type is CSK, KMPS or CSG.
    1684             :         // KMPS: Komsat-5 is Korean mission with a SAR instrument.
    1685             :         // CSG: Cosmo Skymed 2nd Generation
    1686           2 :         if (EQUAL(pszMissionId, "CSK") || EQUAL(pszMissionId, "KMPS") ||
    1687           0 :             EQUAL(pszMissionId, "CSG"))
    1688             :         {
    1689           2 :             iSubdatasetType = CSK_PRODUCT;
    1690             : 
    1691           2 :             if (GetMetadataItem("Product_Type") != nullptr)
    1692             :             {
    1693             :                 // Get the format's level.
    1694             :                 const char *osMissionLevel =
    1695           2 :                     HDF5Dataset::GetMetadataItem("Product_Type");
    1696             : 
    1697           2 :                 if (STARTS_WITH_CI(osMissionLevel, "RAW"))
    1698           0 :                     iCSKProductType = PROD_CSK_L0;
    1699             : 
    1700           2 :                 if (STARTS_WITH_CI(osMissionLevel, "SCS"))
    1701           0 :                     iCSKProductType = PROD_CSK_L1A;
    1702             : 
    1703           2 :                 if (STARTS_WITH_CI(osMissionLevel, "DGM"))
    1704           1 :                     iCSKProductType = PROD_CSK_L1B;
    1705             : 
    1706           2 :                 if (STARTS_WITH_CI(osMissionLevel, "GEC"))
    1707           1 :                     iCSKProductType = PROD_CSK_L1C;
    1708             : 
    1709           2 :                 if (STARTS_WITH_CI(osMissionLevel, "GTC"))
    1710           0 :                     iCSKProductType = PROD_CSK_L1D;
    1711             :             }
    1712             :         }
    1713             :     }
    1714          60 : }
    1715             : 
    1716             : /************************************************************************/
    1717             : /*                       CaptureCSKGeolocation()                        */
    1718             : /************************************************************************/
    1719             : 
    1720             : /**
    1721             :  * Captures Geolocation information from a COSMO-SKYMED
    1722             :  * file.
    1723             :  * The geoid will always be WGS84
    1724             :  * The projection type may be UTM or UPS, depending on the
    1725             :  * latitude from the center of the image.
    1726             :  * @param iProductType type of CSK subproduct, see HDF5CSKProduct
    1727             :  */
    1728           2 : void HDF5ImageDataset::CaptureCSKGeolocation(int iProductType)
    1729             : {
    1730             :     // Set the ellipsoid to WGS84.
    1731           2 :     m_oSRS.SetWellKnownGeogCS("WGS84");
    1732             : 
    1733           2 :     if (iProductType == PROD_CSK_L1C || iProductType == PROD_CSK_L1D)
    1734             :     {
    1735           1 :         double *dfProjFalseEastNorth = nullptr;
    1736           1 :         double *dfProjScaleFactor = nullptr;
    1737           1 :         double *dfCenterCoord = nullptr;
    1738             : 
    1739             :         // Check if all the metadata attributes are present.
    1740           1 :         if (HDF5ReadDoubleAttr("Map Projection False East-North",
    1741           1 :                                &dfProjFalseEastNorth) == CE_Failure ||
    1742           1 :             HDF5ReadDoubleAttr("Map Projection Scale Factor",
    1743           1 :                                &dfProjScaleFactor) == CE_Failure ||
    1744           1 :             HDF5ReadDoubleAttr("Map Projection Centre", &dfCenterCoord) ==
    1745           2 :                 CE_Failure ||
    1746           1 :             GetMetadataItem("Projection_ID") == nullptr)
    1747             :         {
    1748           0 :             m_oSRS.Clear();
    1749           0 :             m_oGCPSRS.Clear();
    1750           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    1751             :                      "The CSK hdf5 file geolocation information is "
    1752             :                      "malformed");
    1753             :         }
    1754             :         else
    1755             :         {
    1756             :             // Fetch projection Type.
    1757           2 :             CPLString osProjectionID = GetMetadataItem("Projection_ID");
    1758             : 
    1759             :             // If the projection is UTM.
    1760           1 :             if (EQUAL(osProjectionID, "UTM"))
    1761             :             {
    1762             :                 // @TODO: use SetUTM
    1763           1 :                 m_oSRS.SetProjCS(SRS_PT_TRANSVERSE_MERCATOR);
    1764           1 :                 m_oSRS.SetTM(dfCenterCoord[0], dfCenterCoord[1],
    1765             :                              dfProjScaleFactor[0], dfProjFalseEastNorth[0],
    1766           1 :                              dfProjFalseEastNorth[1]);
    1767             :             }
    1768             :             else
    1769             :             {
    1770             :                 // TODO: No UPS projected files to test!
    1771             :                 // If the projection is UPS.
    1772           0 :                 if (EQUAL(osProjectionID, "UPS"))
    1773             :                 {
    1774           0 :                     m_oSRS.SetProjCS(SRS_PT_POLAR_STEREOGRAPHIC);
    1775           0 :                     m_oSRS.SetPS(dfCenterCoord[0], dfCenterCoord[1],
    1776             :                                  dfProjScaleFactor[0], dfProjFalseEastNorth[0],
    1777           0 :                                  dfProjFalseEastNorth[1]);
    1778             :                 }
    1779             :             }
    1780             : 
    1781           1 :             CPLFree(dfCenterCoord);
    1782           1 :             CPLFree(dfProjScaleFactor);
    1783           1 :             CPLFree(dfProjFalseEastNorth);
    1784           1 :         }
    1785             :     }
    1786             :     else
    1787             :     {
    1788           1 :         m_oGCPSRS = m_oSRS;
    1789             :     }
    1790           2 : }
    1791             : 
    1792             : /************************************************************************/
    1793             : /*                       CaptureCSKGeoTransform()                       */
    1794             : /************************************************************************/
    1795             : 
    1796             : /**
    1797             :  * Get Geotransform information for COSMO-SKYMED files
    1798             :  * In case of success it stores the transformation
    1799             :  * in adfGeoTransform. In case of failure it doesn't
    1800             :  * modify adfGeoTransform
    1801             :  * @param iProductType type of CSK subproduct, see HDF5CSKProduct
    1802             :  */
    1803           2 : void HDF5ImageDataset::CaptureCSKGeoTransform(int iProductType)
    1804             : {
    1805           2 :     const char *pszSubdatasetName = GetSubdatasetName();
    1806             : 
    1807           2 :     bHasGeoTransform = false;
    1808             :     // If the product level is not L1C or L1D then
    1809             :     // it doesn't have a valid projection.
    1810           2 :     if (iProductType == PROD_CSK_L1C || iProductType == PROD_CSK_L1D)
    1811             :     {
    1812             :         // If there is a subdataset.
    1813           1 :         if (pszSubdatasetName != nullptr)
    1814             :         {
    1815           2 :             CPLString osULPath = pszSubdatasetName;
    1816           1 :             osULPath += "/Top Left East-North";
    1817             : 
    1818           2 :             CPLString osLineSpacingPath = pszSubdatasetName;
    1819           1 :             osLineSpacingPath += "/Line Spacing";
    1820             : 
    1821           2 :             CPLString osColumnSpacingPath = pszSubdatasetName;
    1822           1 :             osColumnSpacingPath += "/Column Spacing";
    1823             : 
    1824           1 :             double *pdOutUL = nullptr;
    1825           1 :             double *pdLineSpacing = nullptr;
    1826           1 :             double *pdColumnSpacing = nullptr;
    1827             : 
    1828             :             // If it could find the attributes on the metadata.
    1829           1 :             if (HDF5ReadDoubleAttr(osULPath.c_str(), &pdOutUL) == CE_Failure ||
    1830           1 :                 HDF5ReadDoubleAttr(osLineSpacingPath.c_str(), &pdLineSpacing) ==
    1831           2 :                     CE_Failure ||
    1832           1 :                 HDF5ReadDoubleAttr(osColumnSpacingPath.c_str(),
    1833             :                                    &pdColumnSpacing) == CE_Failure)
    1834             :             {
    1835           0 :                 bHasGeoTransform = false;
    1836             :             }
    1837             :             else
    1838             :             {
    1839             :                 // geotransform[1] : width of pixel
    1840             :                 // geotransform[4] : rotational coefficient, zero for north up
    1841             :                 // images.
    1842             :                 // geotransform[2] : rotational coefficient, zero for north up
    1843             :                 // images.
    1844             :                 // geotransform[5] : height of pixel (but negative)
    1845             : 
    1846           1 :                 adfGeoTransform[0] = pdOutUL[0];
    1847           1 :                 adfGeoTransform[1] = pdLineSpacing[0];
    1848           1 :                 adfGeoTransform[2] = 0;
    1849           1 :                 adfGeoTransform[3] = pdOutUL[1];
    1850           1 :                 adfGeoTransform[4] = 0;
    1851           1 :                 adfGeoTransform[5] = -pdColumnSpacing[0];
    1852             : 
    1853           1 :                 CPLFree(pdOutUL);
    1854           1 :                 CPLFree(pdLineSpacing);
    1855           1 :                 CPLFree(pdColumnSpacing);
    1856             : 
    1857           1 :                 bHasGeoTransform = true;
    1858             :             }
    1859             :         }
    1860             :     }
    1861           2 : }
    1862             : 
    1863             : /************************************************************************/
    1864             : /*                          CaptureCSKGCPs()                            */
    1865             : /************************************************************************/
    1866             : 
    1867             : /**
    1868             :  * Retrieves and stores the GCPs from a COSMO-SKYMED dataset
    1869             :  * It only retrieves the GCPs for L0, L1A and L1B products
    1870             :  * for L1C and L1D products, geotransform is provided.
    1871             :  * The GCPs provided will be the Image's corners.
    1872             :  * @param iProductType type of CSK product @see HDF5CSKProductEnum
    1873             :  */
    1874           2 : void HDF5ImageDataset::CaptureCSKGCPs(int iProductType)
    1875             : {
    1876             :     // Only retrieve GCPs for L0,L1A and L1B products.
    1877           2 :     if (iProductType == PROD_CSK_L0 || iProductType == PROD_CSK_L1A ||
    1878             :         iProductType == PROD_CSK_L1B)
    1879             :     {
    1880          10 :         CPLString osCornerName[4];
    1881           1 :         double pdCornerPixel[4] = {0.0, 0.0, 0.0, 0.0};
    1882           1 :         double pdCornerLine[4] = {0.0, 0.0, 0.0, 0.0};
    1883             : 
    1884           1 :         const char *const pszSubdatasetName = GetSubdatasetName();
    1885             : 
    1886             :         // Load the subdataset name first.
    1887           5 :         for (int i = 0; i < 4; i++)
    1888           4 :             osCornerName[i] = pszSubdatasetName;
    1889             : 
    1890             :         // Load the attribute name, and raster coordinates for
    1891             :         // all the corners.
    1892           1 :         osCornerName[0] += "/Top Left Geodetic Coordinates";
    1893           1 :         pdCornerPixel[0] = 0;
    1894           1 :         pdCornerLine[0] = 0;
    1895             : 
    1896           1 :         osCornerName[1] += "/Top Right Geodetic Coordinates";
    1897           1 :         pdCornerPixel[1] = GetRasterXSize();
    1898           1 :         pdCornerLine[1] = 0;
    1899             : 
    1900           1 :         osCornerName[2] += "/Bottom Left Geodetic Coordinates";
    1901           1 :         pdCornerPixel[2] = 0;
    1902           1 :         pdCornerLine[2] = GetRasterYSize();
    1903             : 
    1904           1 :         osCornerName[3] += "/Bottom Right Geodetic Coordinates";
    1905           1 :         pdCornerPixel[3] = GetRasterXSize();
    1906           1 :         pdCornerLine[3] = GetRasterYSize();
    1907             : 
    1908             :         // For all the image's corners.
    1909           5 :         for (int i = 0; i < 4; i++)
    1910             :         {
    1911           4 :             double *pdCornerCoordinates = nullptr;
    1912             : 
    1913             :             // Retrieve the attributes.
    1914           4 :             if (HDF5ReadDoubleAttr(osCornerName[i].c_str(),
    1915           4 :                                    &pdCornerCoordinates) == CE_Failure)
    1916             :             {
    1917           0 :                 CPLError(CE_Failure, CPLE_OpenFailed,
    1918             :                          "Error retrieving CSK GCPs");
    1919           0 :                 m_aoGCPs.clear();
    1920           0 :                 break;
    1921             :             }
    1922             : 
    1923           0 :             m_aoGCPs.emplace_back(osCornerName[i].c_str(), "", pdCornerPixel[i],
    1924           4 :                                   pdCornerLine[i],
    1925           4 :                                   /* X = */ pdCornerCoordinates[1],
    1926             :                                   /* Y = */ pdCornerCoordinates[0],
    1927           4 :                                   /* Z = */ pdCornerCoordinates[2]);
    1928             : 
    1929             :             // Free the returned coordinates.
    1930           4 :             CPLFree(pdCornerCoordinates);
    1931             :         }
    1932             :     }
    1933           2 : }

Generated by: LCOV version 1.14