LCOV - code coverage report
Current view: top level - frmts/hdf5 - s102dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 223 269 82.9 %
Date: 2025-05-31 00:00:17 Functions: 14 14 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Hierarchical Data Format Release 5 (HDF5)
       4             :  * Purpose:  Read S102 bathymetric datasets.
       5             :  * Author:   Even Rouault <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_port.h"
      14             : #include "hdf5dataset.h"
      15             : #include "hdf5drivercore.h"
      16             : #include "gh5_convenience.h"
      17             : #include "rat.h"
      18             : #include "s100.h"
      19             : 
      20             : #include "gdal_priv.h"
      21             : #include "gdal_proxy.h"
      22             : #include "gdal_rat.h"
      23             : 
      24             : #include <cmath>
      25             : #include <limits>
      26             : 
      27             : /************************************************************************/
      28             : /*                             S102Dataset                              */
      29             : /************************************************************************/
      30             : 
      31             : class S102Dataset final : public S100BaseDataset
      32             : {
      33             :     bool OpenQuality(GDALOpenInfo *poOpenInfo,
      34             :                      const std::shared_ptr<GDALGroup> &poRootGroup);
      35             : 
      36             :   public:
      37          22 :     explicit S102Dataset(const std::string &osFilename)
      38          22 :         : S100BaseDataset(osFilename)
      39             :     {
      40          22 :     }
      41             : 
      42             :     static GDALDataset *Open(GDALOpenInfo *);
      43             : };
      44             : 
      45             : /************************************************************************/
      46             : /*                            S102RasterBand                            */
      47             : /************************************************************************/
      48             : 
      49             : class S102RasterBand : public GDALProxyRasterBand
      50             : {
      51             :     friend class S102Dataset;
      52             :     std::unique_ptr<GDALDataset> m_poDS{};
      53             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      54             :     double m_dfMinimum = std::numeric_limits<double>::quiet_NaN();
      55             :     double m_dfMaximum = std::numeric_limits<double>::quiet_NaN();
      56             : 
      57             :   public:
      58          31 :     explicit S102RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      59          62 :         : m_poDS(std::move(poDSIn)),
      60          31 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      61             :     {
      62          31 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      63          31 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      64          31 :     }
      65             : 
      66             :     GDALRasterBand *
      67          23 :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
      68             :     {
      69          23 :         return m_poUnderlyingBand;
      70             :     }
      71             : 
      72           8 :     double GetMinimum(int *pbSuccess = nullptr) override
      73             :     {
      74           8 :         if (pbSuccess)
      75           8 :             *pbSuccess = !std::isnan(m_dfMinimum);
      76           8 :         return m_dfMinimum;
      77             :     }
      78             : 
      79           8 :     double GetMaximum(int *pbSuccess = nullptr) override
      80             :     {
      81           8 :         if (pbSuccess)
      82           8 :             *pbSuccess = !std::isnan(m_dfMaximum);
      83           8 :         return m_dfMaximum;
      84             :     }
      85             : 
      86           4 :     const char *GetUnitType() override
      87             :     {
      88           4 :         return "metre";
      89             :     }
      90             : };
      91             : 
      92             : /************************************************************************/
      93             : /*                   S102GeoreferencedMetadataRasterBand                */
      94             : /************************************************************************/
      95             : 
      96             : class S102GeoreferencedMetadataRasterBand : public GDALProxyRasterBand
      97             : {
      98             :     friend class S102Dataset;
      99             : 
     100             :     std::unique_ptr<GDALDataset> m_poDS{};
     101             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
     102             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
     103             : 
     104             :   public:
     105           4 :     explicit S102GeoreferencedMetadataRasterBand(
     106             :         std::unique_ptr<GDALDataset> &&poDSIn,
     107             :         std::unique_ptr<GDALRasterAttributeTable> &&poRAT)
     108           8 :         : m_poDS(std::move(poDSIn)),
     109           4 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1)),
     110           8 :           m_poRAT(std::move(poRAT))
     111             :     {
     112           4 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
     113           4 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     114           4 :     }
     115             : 
     116             :     GDALRasterBand *
     117           6 :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
     118             :     {
     119           6 :         return m_poUnderlyingBand;
     120             :     }
     121             : 
     122           2 :     GDALRasterAttributeTable *GetDefaultRAT() override
     123             :     {
     124           2 :         return m_poRAT.get();
     125             :     }
     126             : };
     127             : 
     128             : /************************************************************************/
     129             : /*                                Open()                                */
     130             : /************************************************************************/
     131             : 
     132          26 : GDALDataset *S102Dataset::Open(GDALOpenInfo *poOpenInfo)
     133             : 
     134             : {
     135             :     // Confirm that this appears to be a S102 file.
     136          26 :     if (!S102DatasetIdentify(poOpenInfo))
     137           0 :         return nullptr;
     138             : 
     139             :     HDF5_GLOBAL_LOCK();
     140             : 
     141          26 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
     142             :     {
     143           2 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     144             :     }
     145             : 
     146             :     // Confirm the requested access is supported.
     147          24 :     if (poOpenInfo->eAccess == GA_Update)
     148             :     {
     149           0 :         ReportUpdateNotSupportedByDriver("S102");
     150           0 :         return nullptr;
     151             :     }
     152             : 
     153          48 :     std::string osFilename(poOpenInfo->pszFilename);
     154          24 :     bool bIsSubdataset = false;
     155          24 :     bool bIsQuality = false;
     156          24 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S102:"))
     157             :     {
     158             :         const CPLStringList aosTokens(
     159          11 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     160          11 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     161             : 
     162          11 :         if (aosTokens.size() == 2)
     163             :         {
     164           1 :             osFilename = aosTokens[1];
     165             :         }
     166          10 :         else if (aosTokens.size() == 3)
     167             :         {
     168          10 :             bIsSubdataset = true;
     169          10 :             osFilename = aosTokens[1];
     170          10 :             if (EQUAL(aosTokens[2], "BathymetryCoverage"))
     171             :             {
     172             :                 // Default dataset
     173             :             }
     174          12 :             else if (EQUAL(aosTokens[2], "QualityOfSurvey") ||  // < v3
     175           4 :                      EQUAL(aosTokens[2], "QualityOfBathymetryCoverage"))  // v3
     176             :             {
     177           6 :                 bIsQuality = true;
     178             :             }
     179             :             else
     180             :             {
     181           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
     182             :                          "Unsupported subdataset component: '%s'. Expected "
     183             :                          "'QualityOfSurvey'",
     184             :                          aosTokens[2]);
     185           2 :                 return nullptr;
     186             :             }
     187             :         }
     188             :         else
     189             :         {
     190           0 :             return nullptr;
     191             :         }
     192             :     }
     193             : 
     194          44 :     auto poDS = std::make_unique<S102Dataset>(osFilename);
     195          22 :     if (!poDS->Init())
     196           0 :         return nullptr;
     197             : 
     198          22 :     const auto &poRootGroup = poDS->m_poRootGroup;
     199             :     auto poBathymetryCoverage01 = poRootGroup->OpenGroupFromFullname(
     200          66 :         "/BathymetryCoverage/BathymetryCoverage.01");
     201          22 :     if (!poBathymetryCoverage01)
     202             :     {
     203           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     204             :                  "S102: Cannot find /BathymetryCoverage/BathymetryCoverage.01");
     205           0 :         return nullptr;
     206             :     }
     207             : 
     208          22 :     const bool bNorthUp = CPLTestBool(
     209          22 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     210             : 
     211          22 :     if (bIsQuality)
     212             :     {
     213           6 :         if (!poDS->OpenQuality(poOpenInfo, poRootGroup))
     214           2 :             return nullptr;
     215             : 
     216             :         // Setup/check for pam .aux.xml.
     217           4 :         poDS->SetDescription(osFilename.c_str());
     218           4 :         poDS->TryLoadXML();
     219             : 
     220             :         // Setup overviews.
     221           4 :         poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     222             : 
     223           4 :         return poDS.release();
     224             :     }
     225             : 
     226             :     // Compute geotransform
     227          16 :     poDS->m_bHasGT = S100GetGeoTransform(poBathymetryCoverage01.get(),
     228          16 :                                          poDS->m_adfGeoTransform, bNorthUp);
     229             : 
     230          48 :     auto poGroup001 = poBathymetryCoverage01->OpenGroup("Group_001");
     231          16 :     if (!poGroup001)
     232             :     {
     233           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     234             :                  "S102: Cannot find "
     235             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001");
     236           0 :         return nullptr;
     237             :     }
     238          48 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     239          16 :     if (!poValuesArray || poValuesArray->GetDimensionCount() != 2)
     240             :     {
     241           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     242             :                  "S102: Cannot find "
     243             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     244           0 :         return nullptr;
     245             :     }
     246          16 :     const auto &oType = poValuesArray->GetDataType();
     247          16 :     if (oType.GetClass() != GEDTC_COMPOUND)
     248             :     {
     249           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     250             :                  "S102: Wrong type for "
     251             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     252           0 :         return nullptr;
     253             :     }
     254          16 :     const auto &oComponents = oType.GetComponents();
     255          16 :     if (oComponents.size() == 0 || oComponents[0]->GetName() != "depth")
     256             :     {
     257           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     258             :                  "S102: Wrong type for "
     259             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     260           0 :         return nullptr;
     261             :     }
     262             : 
     263          16 :     if (bNorthUp)
     264          15 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     265             : 
     266          48 :     auto poDepth = poValuesArray->GetView("[\"depth\"]");
     267             : 
     268             :     // Mandatory in v2.2
     269          16 :     bool bCSIsElevation = false;
     270          48 :     auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
     271          16 :     if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
     272             :     {
     273           6 :         const auto nVal = poVerticalCS->ReadAsInt();
     274           6 :         if (nVal == 6498)  // Depth metre
     275             :         {
     276             :             // nothing to do
     277             :         }
     278           0 :         else if (nVal == 6499)  // Height metre
     279             :         {
     280           0 :             bCSIsElevation = true;
     281             :         }
     282             :         else
     283             :         {
     284           0 :             CPLError(CE_Warning, CPLE_NotSupported, "Unsupported verticalCS=%d",
     285             :                      nVal);
     286             :         }
     287             :     }
     288             : 
     289             :     const bool bUseElevation =
     290          16 :         EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     291             :                                    "DEPTH_OR_ELEVATION", "DEPTH"),
     292             :               "ELEVATION");
     293          31 :     const bool bInvertDepth = (bUseElevation && !bCSIsElevation) ||
     294          15 :                               (!bUseElevation && bCSIsElevation);
     295          16 :     const double dfDepthNoData = poDepth->GetNoDataValueAsDouble();
     296          33 :     auto poDepthDS = [&poDepth, bInvertDepth, dfDepthNoData]()
     297             :     {
     298          16 :         if (bInvertDepth)
     299             :         {
     300           1 :             auto poInverted = poDepth->GetUnscaled(-1, 0, dfDepthNoData);
     301             :             return std::unique_ptr<GDALDataset>(
     302           1 :                 poInverted->AsClassicDataset(1, 0));
     303             :         }
     304             :         else
     305             :         {
     306             :             return std::unique_ptr<GDALDataset>(
     307          15 :                 poDepth->AsClassicDataset(1, 0));
     308             :         }
     309          32 :     }();
     310             : 
     311          16 :     poDS->nRasterXSize = poDepthDS->GetRasterXSize();
     312          16 :     poDS->nRasterYSize = poDepthDS->GetRasterYSize();
     313             : 
     314             :     // Create depth (or elevation) band
     315          16 :     auto poDepthBand = new S102RasterBand(std::move(poDepthDS));
     316          16 :     poDepthBand->SetDescription(bUseElevation ? "elevation" : "depth");
     317             : 
     318          48 :     auto poMinimumDepth = poGroup001->GetAttribute("minimumDepth");
     319          32 :     if (poMinimumDepth &&
     320          32 :         poMinimumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     321             :     {
     322          16 :         const double dfVal = poMinimumDepth->ReadAsDouble();
     323          16 :         if (dfVal != dfDepthNoData)
     324             :         {
     325          13 :             if (bInvertDepth)
     326           1 :                 poDepthBand->m_dfMaximum = -dfVal;
     327             :             else
     328          12 :                 poDepthBand->m_dfMinimum = dfVal;
     329             :         }
     330             :     }
     331             : 
     332          48 :     auto poMaximumDepth = poGroup001->GetAttribute("maximumDepth");
     333          32 :     if (poMaximumDepth &&
     334          32 :         poMaximumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     335             :     {
     336          16 :         const double dfVal = poMaximumDepth->ReadAsDouble();
     337          16 :         if (dfVal != dfDepthNoData)
     338             :         {
     339          16 :             if (bInvertDepth)
     340           1 :                 poDepthBand->m_dfMinimum = -dfVal;
     341             :             else
     342          15 :                 poDepthBand->m_dfMaximum = dfVal;
     343             :         }
     344             :     }
     345             : 
     346          16 :     poDS->SetBand(1, poDepthBand);
     347             : 
     348             :     const bool bHasUncertainty =
     349          16 :         oComponents.size() >= 2 && oComponents[1]->GetName() == "uncertainty";
     350          16 :     if (bHasUncertainty)
     351             :     {
     352             :         // Create uncertainty band
     353          45 :         auto poUncertainty = poValuesArray->GetView("[\"uncertainty\"]");
     354             :         const double dfUncertaintyNoData =
     355          15 :             poUncertainty->GetNoDataValueAsDouble();
     356             :         auto poUncertaintyDS =
     357          30 :             std::unique_ptr<GDALDataset>(poUncertainty->AsClassicDataset(1, 0));
     358             : 
     359          15 :         auto poUncertaintyBand = new S102RasterBand(std::move(poUncertaintyDS));
     360          15 :         poUncertaintyBand->SetDescription("uncertainty");
     361             : 
     362             :         auto poMinimumUncertainty =
     363          45 :             poGroup001->GetAttribute("minimumUncertainty");
     364          30 :         if (poMinimumUncertainty &&
     365          30 :             poMinimumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     366             :         {
     367          15 :             const double dfVal = poMinimumUncertainty->ReadAsDouble();
     368          15 :             if (dfVal != dfUncertaintyNoData)
     369             :             {
     370          15 :                 poUncertaintyBand->m_dfMinimum = dfVal;
     371             :             }
     372             :         }
     373             : 
     374             :         auto poMaximumUncertainty =
     375          45 :             poGroup001->GetAttribute("maximumUncertainty");
     376          30 :         if (poMaximumUncertainty &&
     377          30 :             poMaximumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     378             :         {
     379          15 :             const double dfVal = poMaximumUncertainty->ReadAsDouble();
     380          15 :             if (dfVal != dfUncertaintyNoData)
     381             :             {
     382          15 :                 poUncertaintyBand->m_dfMaximum = dfVal;
     383             :             }
     384             :         }
     385             : 
     386          15 :         poDS->SetBand(2, poUncertaintyBand);
     387             :     }
     388             : 
     389          16 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     390             : 
     391          48 :     auto poGroupQuality = poRootGroup->OpenGroup("QualityOfSurvey");
     392          16 :     const bool bIsNamedQualityOfSurvey = poGroupQuality != nullptr;
     393          16 :     if (!bIsNamedQualityOfSurvey)
     394             :     {
     395             :         // S102 v3 now uses QualityOfBathymetryCoverage instead of QualityOfSurvey
     396          14 :         poGroupQuality = poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
     397             :     }
     398          16 :     if (!bIsSubdataset && poGroupQuality)
     399             :     {
     400           3 :         const char *pszNameOfQualityGroup = bIsNamedQualityOfSurvey
     401           3 :                                                 ? "QualityOfSurvey"
     402             :                                                 : "QualityOfBathymetryCoverage";
     403           3 :         auto poGroupQuality01 = poGroupQuality->OpenGroup(
     404           9 :             CPLSPrintf("%s.01", pszNameOfQualityGroup));
     405           3 :         if (poGroupQuality01)
     406             :         {
     407           3 :             poDS->GDALDataset::SetMetadataItem(
     408             :                 "SUBDATASET_1_NAME",
     409             :                 CPLSPrintf("S102:\"%s\":BathymetryCoverage",
     410             :                            osFilename.c_str()),
     411             :                 "SUBDATASETS");
     412           3 :             poDS->GDALDataset::SetMetadataItem(
     413             :                 "SUBDATASET_1_DESC", "Bathymetric gridded data", "SUBDATASETS");
     414             : 
     415           3 :             poDS->GDALDataset::SetMetadataItem(
     416             :                 "SUBDATASET_2_NAME",
     417             :                 CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
     418             :                            pszNameOfQualityGroup),
     419             :                 "SUBDATASETS");
     420           3 :             poDS->GDALDataset::SetMetadataItem(
     421             :                 "SUBDATASET_2_DESC",
     422             :                 CPLSPrintf("Georeferenced metadata %s", pszNameOfQualityGroup),
     423             :                 "SUBDATASETS");
     424             :         }
     425             :     }
     426             : 
     427             :     // Setup/check for pam .aux.xml.
     428          16 :     poDS->SetDescription(osFilename.c_str());
     429          16 :     poDS->TryLoadXML();
     430             : 
     431             :     // Setup overviews.
     432          16 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     433             : 
     434          16 :     return poDS.release();
     435             : }
     436             : 
     437             : /************************************************************************/
     438             : /*                       OpenQuality()                          */
     439             : /************************************************************************/
     440             : 
     441           6 : bool S102Dataset::OpenQuality(GDALOpenInfo *poOpenInfo,
     442             :                               const std::shared_ptr<GDALGroup> &poRootGroup)
     443             : {
     444           6 :     const bool bNorthUp = CPLTestBool(
     445           6 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     446             : 
     447           6 :     const char *pszNameOfQualityGroup = "QualityOfSurvey";
     448          18 :     auto poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     449           6 :     if (!poGroupQuality)
     450             :     {
     451           4 :         pszNameOfQualityGroup = "QualityOfBathymetryCoverage";
     452           4 :         poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     453           4 :         if (!poGroupQuality)
     454             :         {
     455           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     456             :                      "Cannot find group /QualityOfSurvey or "
     457             :                      "/QualityOfBathymetryCoverage");
     458           2 :             return false;
     459             :         }
     460             :     }
     461             : 
     462             :     const std::string osQuality01Name =
     463          12 :         std::string(pszNameOfQualityGroup).append(".01");
     464           8 :     const std::string osQuality01FullName = std::string("/")
     465           4 :                                                 .append(pszNameOfQualityGroup)
     466           4 :                                                 .append("/")
     467           8 :                                                 .append(osQuality01Name);
     468           8 :     auto poGroupQuality01 = poGroupQuality->OpenGroup(osQuality01Name);
     469           4 :     if (!poGroupQuality01)
     470             :     {
     471           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
     472             :                  osQuality01FullName.c_str());
     473           0 :         return false;
     474             :     }
     475             : 
     476           8 :     if (auto poStartSequence = poGroupQuality01->GetAttribute("startSequence"))
     477             :     {
     478           0 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     479           0 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     480             :         {
     481           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     482             :                      "startSequence (=%s) != 0,0 is not supported",
     483             :                      pszStartSequence);
     484           0 :             return false;
     485             :         }
     486             :     }
     487             : 
     488             :     // Compute geotransform
     489           4 :     m_bHasGT = S100GetGeoTransform(poGroupQuality01.get(), m_adfGeoTransform,
     490             :                                    bNorthUp);
     491             : 
     492          12 :     auto poGroup001 = poGroupQuality01->OpenGroup("Group_001");
     493           4 :     if (!poGroup001)
     494             :     {
     495           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s/Group_001",
     496             :                  osQuality01FullName.c_str());
     497           0 :         return false;
     498             :     }
     499             : 
     500          12 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     501           4 :     if (!poValuesArray)
     502             :     {
     503           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     504             :                  "Cannot find array "
     505             :                  "%s/Group_001/values",
     506             :                  osQuality01FullName.c_str());
     507           0 :         return false;
     508             :     }
     509             : 
     510             :     {
     511           4 :         const auto &oType = poValuesArray->GetDataType();
     512           6 :         if (oType.GetClass() == GEDTC_NUMERIC &&
     513           2 :             oType.GetNumericDataType() == GDT_UInt32)
     514             :         {
     515             :             // ok
     516             :         }
     517           4 :         else if (oType.GetClass() == GEDTC_COMPOUND &&
     518           4 :                  oType.GetComponents().size() == 1 &&
     519           2 :                  oType.GetComponents()[0]->GetType().GetClass() ==
     520           4 :                      GEDTC_NUMERIC &&
     521           2 :                  oType.GetComponents()[0]->GetType().GetNumericDataType() ==
     522             :                      GDT_UInt32)
     523             :         {
     524             :             // seen in a S102 v3 product (102DE00CA22_UNC_MD.H5), although
     525             :             // I believe this is non-conformant.
     526             : 
     527             :             // Escape potentials single-quote and double-quote with back-slash
     528           2 :             CPLString osEscapedCompName(oType.GetComponents()[0]->GetName());
     529           4 :             osEscapedCompName.replaceAll("\\", "\\\\")
     530           4 :                 .replaceAll("'", "\\'")
     531           2 :                 .replaceAll("\"", "\\\"");
     532             : 
     533             :             // Gets a view with that single component extracted.
     534           4 :             poValuesArray = poValuesArray->GetView(
     535           4 :                 std::string("['").append(osEscapedCompName).append("']"));
     536           2 :             if (!poValuesArray)
     537           0 :                 return false;
     538             :         }
     539             :         else
     540             :         {
     541           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     542             :                      "Unsupported data type for %s",
     543           0 :                      poValuesArray->GetFullName().c_str());
     544           0 :             return false;
     545             :         }
     546             :     }
     547             : 
     548           4 :     if (poValuesArray->GetDimensionCount() != 2)
     549             :     {
     550           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     551             :                  "Unsupported number of dimensions for %s",
     552           0 :                  poValuesArray->GetFullName().c_str());
     553           0 :         return false;
     554             :     }
     555             : 
     556             :     auto poFeatureAttributeTable =
     557          12 :         poGroupQuality->OpenMDArray("featureAttributeTable");
     558           4 :     if (!poFeatureAttributeTable)
     559             :     {
     560           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     561             :                  "Cannot find array /%s/featureAttributeTable",
     562             :                  pszNameOfQualityGroup);
     563           0 :         return false;
     564             :     }
     565             : 
     566             :     {
     567           4 :         const auto &oType = poFeatureAttributeTable->GetDataType();
     568           4 :         if (oType.GetClass() != GEDTC_COMPOUND)
     569             :         {
     570           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     571             :                      "Unsupported data type for %s",
     572           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     573           0 :             return false;
     574             :         }
     575             : 
     576           4 :         const auto &poComponents = oType.GetComponents();
     577           4 :         if (poComponents.size() >= 1 && poComponents[0]->GetName() != "id")
     578             :         {
     579           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     580             :                      "Missing 'id' component in %s",
     581           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     582           0 :             return false;
     583             :         }
     584             :     }
     585             : 
     586           4 :     if (bNorthUp)
     587           2 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     588             : 
     589             :     auto poDS =
     590           8 :         std::unique_ptr<GDALDataset>(poValuesArray->AsClassicDataset(1, 0));
     591           4 :     if (!poDS)
     592           0 :         return false;
     593             : 
     594           4 :     nRasterXSize = poDS->GetRasterXSize();
     595           4 :     nRasterYSize = poDS->GetRasterYSize();
     596             : 
     597             :     auto poRAT =
     598           8 :         HDF5CreateRAT(poFeatureAttributeTable, /* bFirstColIsMinMax = */ true);
     599             :     auto poBand = std::make_unique<S102GeoreferencedMetadataRasterBand>(
     600           4 :         std::move(poDS), std::move(poRAT));
     601           4 :     SetBand(1, poBand.release());
     602             : 
     603           4 :     return true;
     604             : }
     605             : 
     606             : /************************************************************************/
     607             : /*                      S102DatasetDriverUnload()                       */
     608             : /************************************************************************/
     609             : 
     610           6 : static void S102DatasetDriverUnload(GDALDriver *)
     611             : {
     612           6 :     HDF5UnloadFileDriver();
     613           6 : }
     614             : 
     615             : /************************************************************************/
     616             : /*                         GDALRegister_S102()                          */
     617             : /************************************************************************/
     618          11 : void GDALRegister_S102()
     619             : 
     620             : {
     621          11 :     if (!GDAL_CHECK_VERSION("S102"))
     622           0 :         return;
     623             : 
     624          11 :     if (GDALGetDriverByName(S102_DRIVER_NAME) != nullptr)
     625           0 :         return;
     626             : 
     627          11 :     GDALDriver *poDriver = new GDALDriver();
     628             : 
     629          11 :     S102DriverSetCommonMetadata(poDriver);
     630          11 :     poDriver->pfnOpen = S102Dataset::Open;
     631          11 :     poDriver->pfnUnloadDriver = S102DatasetDriverUnload;
     632             : 
     633          11 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     634             : }

Generated by: LCOV version 1.14