LCOV - code coverage report
Current view: top level - frmts/hdf5 - s102dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 277 329 84.2 %
Date: 2025-10-21 22:35:35 Functions: 16 16 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_frmts.h"
      21             : #include "gdal_priv.h"
      22             : #include "gdal_proxy.h"
      23             : #include "gdal_rat.h"
      24             : 
      25             : #include <cmath>
      26             : #include <limits>
      27             : 
      28             : /************************************************************************/
      29             : /*                             S102Dataset                              */
      30             : /************************************************************************/
      31             : 
      32          54 : class S102Dataset final : public S100BaseDataset
      33             : {
      34             :     bool OpenQuality(GDALOpenInfo *poOpenInfo,
      35             :                      const std::shared_ptr<GDALGroup> &poRootGroup);
      36             : 
      37             :   public:
      38          27 :     explicit S102Dataset(const std::string &osFilename)
      39          27 :         : S100BaseDataset(osFilename)
      40             :     {
      41          27 :     }
      42             : 
      43             :     ~S102Dataset() override;
      44             : 
      45             :     static GDALDataset *Open(GDALOpenInfo *);
      46             : };
      47             : 
      48             : S102Dataset::~S102Dataset() = default;
      49             : 
      50             : /************************************************************************/
      51             : /*                            S102RasterBand                            */
      52             : /************************************************************************/
      53             : 
      54             : class S102RasterBand final : public GDALProxyRasterBand
      55             : {
      56             :     friend class S102Dataset;
      57             :     std::unique_ptr<GDALDataset> m_poDS{};
      58             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      59             :     double m_dfMinimum = std::numeric_limits<double>::quiet_NaN();
      60             :     double m_dfMaximum = std::numeric_limits<double>::quiet_NaN();
      61             : 
      62             :     CPL_DISALLOW_COPY_ASSIGN(S102RasterBand)
      63             : 
      64             :   public:
      65          33 :     explicit S102RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      66          66 :         : m_poDS(std::move(poDSIn)),
      67          33 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      68             :     {
      69          33 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      70          33 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      71          33 :     }
      72             : 
      73             :     GDALRasterBand *
      74             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
      75             : 
      76           8 :     double GetMinimum(int *pbSuccess = nullptr) override
      77             :     {
      78           8 :         if (pbSuccess)
      79           8 :             *pbSuccess = !std::isnan(m_dfMinimum);
      80           8 :         return m_dfMinimum;
      81             :     }
      82             : 
      83           8 :     double GetMaximum(int *pbSuccess = nullptr) override
      84             :     {
      85           8 :         if (pbSuccess)
      86           8 :             *pbSuccess = !std::isnan(m_dfMaximum);
      87           8 :         return m_dfMaximum;
      88             :     }
      89             : 
      90           4 :     const char *GetUnitType() override
      91             :     {
      92           4 :         return "metre";
      93             :     }
      94             : };
      95             : 
      96             : GDALRasterBand *
      97          25 : S102RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
      98             : {
      99          25 :     return m_poUnderlyingBand;
     100             : }
     101             : 
     102             : /************************************************************************/
     103             : /*                   S102GeoreferencedMetadataRasterBand                */
     104             : /************************************************************************/
     105             : 
     106             : class S102GeoreferencedMetadataRasterBand final : public GDALProxyRasterBand
     107             : {
     108             :     friend class S102Dataset;
     109             : 
     110             :     std::unique_ptr<GDALDataset> m_poDS{};
     111             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
     112             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
     113             : 
     114             :     CPL_DISALLOW_COPY_ASSIGN(S102GeoreferencedMetadataRasterBand)
     115             : 
     116             :   public:
     117           5 :     explicit S102GeoreferencedMetadataRasterBand(
     118             :         std::unique_ptr<GDALDataset> &&poDSIn,
     119             :         std::unique_ptr<GDALRasterAttributeTable> &&poRAT)
     120          10 :         : m_poDS(std::move(poDSIn)),
     121           5 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1)),
     122          10 :           m_poRAT(std::move(poRAT))
     123             :     {
     124           5 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
     125           5 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     126           5 :     }
     127             : 
     128             :     GDALRasterBand *
     129             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
     130             : 
     131           2 :     GDALRasterAttributeTable *GetDefaultRAT() override
     132             :     {
     133           2 :         return m_poRAT.get();
     134             :     }
     135             : };
     136             : 
     137           7 : GDALRasterBand *S102GeoreferencedMetadataRasterBand::RefUnderlyingRasterBand(
     138             :     bool /*bForceOpen*/) const
     139             : {
     140           7 :     return m_poUnderlyingBand;
     141             : }
     142             : 
     143             : /************************************************************************/
     144             : /*                                Open()                                */
     145             : /************************************************************************/
     146             : 
     147          31 : GDALDataset *S102Dataset::Open(GDALOpenInfo *poOpenInfo)
     148             : 
     149             : {
     150             :     // Confirm that this appears to be a S102 file.
     151          31 :     if (!S102DatasetIdentify(poOpenInfo))
     152           0 :         return nullptr;
     153             : 
     154             :     HDF5_GLOBAL_LOCK();
     155             : 
     156          31 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
     157             :     {
     158           2 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     159             :     }
     160             : 
     161             :     // Confirm the requested access is supported.
     162          29 :     if (poOpenInfo->eAccess == GA_Update)
     163             :     {
     164           0 :         ReportUpdateNotSupportedByDriver("S102");
     165           0 :         return nullptr;
     166             :     }
     167             : 
     168          58 :     std::string osFilename(poOpenInfo->pszFilename);
     169          29 :     bool bIsSubdataset = false;
     170          29 :     bool bIsQuality = false;
     171          58 :     std::string osBathymetryCoverageName = "BathymetryCoverage.01";
     172          29 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S102:"))
     173             :     {
     174             :         const CPLStringList aosTokens(
     175          15 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     176          15 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     177             : 
     178          15 :         if (aosTokens.size() == 2)
     179             :         {
     180           1 :             osFilename = aosTokens[1];
     181             :         }
     182          14 :         else if (aosTokens.size() == 3)
     183             :         {
     184          14 :             bIsSubdataset = true;
     185          14 :             osFilename = aosTokens[1];
     186          14 :             if (EQUAL(aosTokens[2], "BathymetryCoverage"))
     187             :             {
     188             :                 // Default dataset
     189             :             }
     190          12 :             else if (STARTS_WITH(aosTokens[2], "BathymetryCoverage"))
     191             :             {
     192           3 :                 osBathymetryCoverageName = aosTokens[2];
     193             :             }
     194          14 :             else if (EQUAL(aosTokens[2], "QualityOfSurvey") ||  // < v3
     195           5 :                      EQUAL(aosTokens[2], "QualityOfBathymetryCoverage"))  // v3
     196             :             {
     197           7 :                 bIsQuality = true;
     198             :             }
     199             :             else
     200             :             {
     201           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
     202             :                          "Unsupported subdataset component: '%s'. Expected "
     203             :                          "'QualityOfSurvey'",
     204             :                          aosTokens[2]);
     205           2 :                 return nullptr;
     206             :             }
     207             :         }
     208             :         else
     209             :         {
     210           0 :             return nullptr;
     211             :         }
     212             :     }
     213             : 
     214          54 :     auto poDS = std::make_unique<S102Dataset>(osFilename);
     215          27 :     if (!poDS->Init())
     216           0 :         return nullptr;
     217             : 
     218          27 :     const auto &poRootGroup = poDS->m_poRootGroup;
     219             : 
     220          81 :     auto poBathymetryCoverage = poRootGroup->OpenGroup("BathymetryCoverage");
     221          27 :     if (!poBathymetryCoverage)
     222             :     {
     223           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     224             :                  "S102: Cannot find /BathymetryCoverage group");
     225           0 :         return nullptr;
     226             :     }
     227             : 
     228          27 :     if (!bIsSubdataset)
     229             :     {
     230             :         auto poNumInstances =
     231          30 :             poBathymetryCoverage->GetAttribute("numInstances");
     232          16 :         if (poNumInstances &&
     233          16 :             poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
     234             :         {
     235           1 :             const int nNumInstances = poNumInstances->ReadAsInt();
     236           1 :             if (nNumInstances != 1)
     237             :             {
     238           2 :                 CPLStringList aosSubDSList;
     239           1 :                 int iSubDS = 0;
     240           2 :                 for (const std::string &coverageName :
     241           5 :                      poBathymetryCoverage->GetGroupNames())
     242             :                 {
     243             :                     auto poCoverage =
     244           4 :                         poBathymetryCoverage->OpenGroup(coverageName);
     245           2 :                     if (poCoverage)
     246             :                     {
     247           4 :                         GDALMajorObject mo;
     248             :                         // Read first vertical datum from root group and let the
     249             :                         // coverage override it.
     250           2 :                         S100ReadVerticalDatum(&mo, poRootGroup.get());
     251           2 :                         S100ReadVerticalDatum(&mo, poCoverage.get());
     252           2 :                         ++iSubDS;
     253             :                         aosSubDSList.SetNameValue(
     254             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     255             :                             CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
     256           2 :                                        coverageName.c_str()));
     257           4 :                         std::string verticalDatum;
     258             :                         const char *pszValue =
     259           2 :                             mo.GetMetadataItem(S100_VERTICAL_DATUM_MEANING);
     260           2 :                         if (pszValue)
     261             :                         {
     262           2 :                             verticalDatum = ", vertical datum ";
     263           2 :                             verticalDatum += pszValue;
     264             :                             pszValue =
     265           2 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
     266           2 :                             if (pszValue)
     267             :                             {
     268           2 :                                 verticalDatum += " (";
     269           2 :                                 verticalDatum += pszValue;
     270           2 :                                 verticalDatum += ')';
     271             :                             }
     272             :                         }
     273             :                         else
     274             :                         {
     275             :                             pszValue =
     276           0 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
     277           0 :                             if (pszValue)
     278             :                             {
     279           0 :                                 verticalDatum = ", vertical datum ";
     280           0 :                                 verticalDatum += pszValue;
     281             :                             }
     282             :                         }
     283             :                         aosSubDSList.SetNameValue(
     284             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     285             :                             CPLSPrintf(
     286             :                                 "Bathymetric gridded data, instance %s%s",
     287           2 :                                 coverageName.c_str(), verticalDatum.c_str()));
     288             :                     }
     289             :                 }
     290             :                 auto poGroupQuality =
     291           3 :                     poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
     292           1 :                 if (poGroupQuality)
     293             :                 {
     294             :                     auto poQualityOfBathymetryCoverage01 =
     295           1 :                         poGroupQuality->OpenGroup(
     296           3 :                             "QualityOfBathymetryCoverage.01");
     297           1 :                     if (poQualityOfBathymetryCoverage01)
     298             :                     {
     299           1 :                         ++iSubDS;
     300             :                         aosSubDSList.SetNameValue(
     301             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     302             :                             CPLSPrintf(
     303             :                                 "S102:\"%s\":QualityOfBathymetryCoverage",
     304           1 :                                 osFilename.c_str()));
     305             :                         aosSubDSList.SetNameValue(
     306             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     307             :                             "Georeferenced metadata "
     308           1 :                             "QualityOfBathymetryCoverage");
     309             :                     }
     310             :                 }
     311             : 
     312           1 :                 poDS->GDALDataset::SetMetadata(aosSubDSList.List(),
     313             :                                                "SUBDATASETS");
     314             : 
     315             :                 // Setup/check for pam .aux.xml.
     316           1 :                 poDS->SetDescription(osFilename.c_str());
     317           1 :                 poDS->TryLoadXML();
     318             : 
     319             :                 // Setup overviews.
     320           1 :                 poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     321             : 
     322           1 :                 return poDS.release();
     323             :             }
     324             :         }
     325             :     }
     326             : 
     327          26 :     if (bIsQuality)
     328             :     {
     329           7 :         if (!poDS->OpenQuality(poOpenInfo, poRootGroup))
     330           2 :             return nullptr;
     331             : 
     332             :         // Setup/check for pam .aux.xml.
     333           5 :         poDS->SetDescription(osFilename.c_str());
     334           5 :         poDS->TryLoadXML();
     335             : 
     336             :         // Setup overviews.
     337           5 :         poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     338             : 
     339           5 :         return poDS.release();
     340             :     }
     341             : 
     342             :     auto poBathymetryCoverageInstance =
     343          38 :         poBathymetryCoverage->OpenGroup(osBathymetryCoverageName);
     344          19 :     if (!poBathymetryCoverageInstance)
     345             :     {
     346           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     347             :                  "S102: Cannot find %s group in BathymetryCoverage group",
     348             :                  osBathymetryCoverageName.c_str());
     349           1 :         return nullptr;
     350             :     }
     351             : 
     352          18 :     if (auto poStartSequence =
     353          36 :             poBathymetryCoverage->GetAttribute("startSequence"))
     354             :     {
     355           0 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     356           0 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     357             :         {
     358             :             // Shouldn't happen given this is imposed by the spec.
     359             :             // Cf 4.2.1.1.1.12 "startSequence" of Ed 3.0 spec, page 13
     360           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     361             :                      "startSequence (=%s) != 0,0 is not supported",
     362             :                      pszStartSequence);
     363           0 :             return nullptr;
     364             :         }
     365             :     }
     366             : 
     367             :     // Potentially override vertical datum
     368          18 :     S100ReadVerticalDatum(poDS.get(), poBathymetryCoverageInstance.get());
     369             : 
     370          18 :     const bool bNorthUp = CPLTestBool(
     371          18 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     372             : 
     373             :     // Compute geotransform
     374          18 :     poDS->m_bHasGT = S100GetGeoTransform(poBathymetryCoverageInstance.get(),
     375          18 :                                          poDS->m_gt, bNorthUp);
     376             : 
     377          54 :     auto poGroup001 = poBathymetryCoverageInstance->OpenGroup("Group_001");
     378          18 :     if (!poGroup001)
     379             :     {
     380           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     381             :                  "S102: Cannot find "
     382             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001");
     383           0 :         return nullptr;
     384             :     }
     385          54 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     386          18 :     if (!poValuesArray || poValuesArray->GetDimensionCount() != 2)
     387             :     {
     388           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     389             :                  "S102: Cannot find "
     390             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     391           0 :         return nullptr;
     392             :     }
     393          18 :     const auto &oType = poValuesArray->GetDataType();
     394          18 :     if (oType.GetClass() != GEDTC_COMPOUND)
     395             :     {
     396           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     397             :                  "S102: Wrong type for "
     398             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     399           0 :         return nullptr;
     400             :     }
     401          18 :     const auto &oComponents = oType.GetComponents();
     402          18 :     if (oComponents.size() == 0 || oComponents[0]->GetName() != "depth")
     403             :     {
     404           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     405             :                  "S102: Wrong type for "
     406             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     407           0 :         return nullptr;
     408             :     }
     409             : 
     410          18 :     if (bNorthUp)
     411          17 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     412             : 
     413          54 :     auto poDepth = poValuesArray->GetView("[\"depth\"]");
     414             : 
     415             :     // Mandatory in v2.2. Since v3.0, EPSG:6498 is the only allowed value
     416          18 :     bool bCSIsElevation = false;
     417          54 :     auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
     418          18 :     if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
     419             :     {
     420           8 :         const auto nVal = poVerticalCS->ReadAsInt();
     421           8 :         if (nVal == 6498)  // Depth metre
     422             :         {
     423             :             // nothing to do
     424             :         }
     425           0 :         else if (nVal == 6499)  // Height metre
     426             :         {
     427           0 :             bCSIsElevation = true;
     428             :         }
     429             :         else
     430             :         {
     431           0 :             CPLError(CE_Warning, CPLE_NotSupported, "Unsupported verticalCS=%d",
     432             :                      nVal);
     433             :         }
     434             :     }
     435             : 
     436             :     const bool bUseElevation =
     437          18 :         EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     438             :                                    "DEPTH_OR_ELEVATION", "DEPTH"),
     439             :               "ELEVATION");
     440          35 :     const bool bInvertDepth = (bUseElevation && !bCSIsElevation) ||
     441          17 :                               (!bUseElevation && bCSIsElevation);
     442          18 :     const double dfDepthNoData = poDepth->GetNoDataValueAsDouble();
     443          37 :     auto poDepthDS = [&poDepth, bInvertDepth, dfDepthNoData]()
     444             :     {
     445          18 :         if (bInvertDepth)
     446             :         {
     447           1 :             auto poInverted = poDepth->GetUnscaled(-1, 0, dfDepthNoData);
     448             :             return std::unique_ptr<GDALDataset>(
     449           1 :                 poInverted->AsClassicDataset(1, 0));
     450             :         }
     451             :         else
     452             :         {
     453             :             return std::unique_ptr<GDALDataset>(
     454          17 :                 poDepth->AsClassicDataset(1, 0));
     455             :         }
     456          36 :     }();
     457             : 
     458          18 :     poDS->nRasterXSize = poDepthDS->GetRasterXSize();
     459          18 :     poDS->nRasterYSize = poDepthDS->GetRasterYSize();
     460             : 
     461             :     // Create depth (or elevation) band
     462          18 :     auto poDepthBand = new S102RasterBand(std::move(poDepthDS));
     463          18 :     poDepthBand->SetDescription(bUseElevation ? "elevation" : "depth");
     464             : 
     465          54 :     auto poMinimumDepth = poGroup001->GetAttribute("minimumDepth");
     466          36 :     if (poMinimumDepth &&
     467          36 :         poMinimumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     468             :     {
     469          18 :         const double dfVal = poMinimumDepth->ReadAsDouble();
     470          18 :         if (dfVal != dfDepthNoData)
     471             :         {
     472          14 :             if (bInvertDepth)
     473           1 :                 poDepthBand->m_dfMaximum = -dfVal;
     474             :             else
     475          13 :                 poDepthBand->m_dfMinimum = dfVal;
     476             :         }
     477             :     }
     478             : 
     479          54 :     auto poMaximumDepth = poGroup001->GetAttribute("maximumDepth");
     480          36 :     if (poMaximumDepth &&
     481          36 :         poMaximumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     482             :     {
     483          18 :         const double dfVal = poMaximumDepth->ReadAsDouble();
     484          18 :         if (dfVal != dfDepthNoData)
     485             :         {
     486          18 :             if (bInvertDepth)
     487           1 :                 poDepthBand->m_dfMinimum = -dfVal;
     488             :             else
     489          17 :                 poDepthBand->m_dfMaximum = dfVal;
     490             :         }
     491             :     }
     492             : 
     493          18 :     poDS->SetBand(1, poDepthBand);
     494             : 
     495             :     const bool bHasUncertainty =
     496          18 :         oComponents.size() >= 2 && oComponents[1]->GetName() == "uncertainty";
     497          18 :     if (bHasUncertainty)
     498             :     {
     499             :         // Create uncertainty band
     500          45 :         auto poUncertainty = poValuesArray->GetView("[\"uncertainty\"]");
     501             :         const double dfUncertaintyNoData =
     502          15 :             poUncertainty->GetNoDataValueAsDouble();
     503             :         auto poUncertaintyDS =
     504          30 :             std::unique_ptr<GDALDataset>(poUncertainty->AsClassicDataset(1, 0));
     505             : 
     506          15 :         auto poUncertaintyBand = new S102RasterBand(std::move(poUncertaintyDS));
     507          15 :         poUncertaintyBand->SetDescription("uncertainty");
     508             : 
     509             :         auto poMinimumUncertainty =
     510          45 :             poGroup001->GetAttribute("minimumUncertainty");
     511          30 :         if (poMinimumUncertainty &&
     512          30 :             poMinimumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     513             :         {
     514          15 :             const double dfVal = poMinimumUncertainty->ReadAsDouble();
     515          15 :             if (dfVal != dfUncertaintyNoData)
     516             :             {
     517          15 :                 poUncertaintyBand->m_dfMinimum = dfVal;
     518             :             }
     519             :         }
     520             : 
     521             :         auto poMaximumUncertainty =
     522          45 :             poGroup001->GetAttribute("maximumUncertainty");
     523          30 :         if (poMaximumUncertainty &&
     524          30 :             poMaximumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     525             :         {
     526          15 :             const double dfVal = poMaximumUncertainty->ReadAsDouble();
     527          15 :             if (dfVal != dfUncertaintyNoData)
     528             :             {
     529          15 :                 poUncertaintyBand->m_dfMaximum = dfVal;
     530             :             }
     531             :         }
     532             : 
     533          15 :         poDS->SetBand(2, poUncertaintyBand);
     534             :     }
     535             : 
     536          18 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     537             : 
     538          54 :     auto poGroupQuality = poRootGroup->OpenGroup("QualityOfSurvey");
     539          18 :     const bool bIsNamedQualityOfSurvey = poGroupQuality != nullptr;
     540          18 :     if (!bIsNamedQualityOfSurvey)
     541             :     {
     542             :         // S102 v3 now uses QualityOfBathymetryCoverage instead of QualityOfSurvey
     543          16 :         poGroupQuality = poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
     544             :     }
     545          18 :     if (!bIsSubdataset && poGroupQuality)
     546             :     {
     547           3 :         const char *pszNameOfQualityGroup = bIsNamedQualityOfSurvey
     548           3 :                                                 ? "QualityOfSurvey"
     549             :                                                 : "QualityOfBathymetryCoverage";
     550           3 :         auto poGroupQuality01 = poGroupQuality->OpenGroup(
     551           9 :             CPLSPrintf("%s.01", pszNameOfQualityGroup));
     552           3 :         if (poGroupQuality01)
     553             :         {
     554           3 :             poDS->GDALDataset::SetMetadataItem(
     555             :                 "SUBDATASET_1_NAME",
     556             :                 CPLSPrintf("S102:\"%s\":BathymetryCoverage",
     557             :                            osFilename.c_str()),
     558             :                 "SUBDATASETS");
     559           3 :             poDS->GDALDataset::SetMetadataItem(
     560             :                 "SUBDATASET_1_DESC", "Bathymetric gridded data", "SUBDATASETS");
     561             : 
     562           3 :             poDS->GDALDataset::SetMetadataItem(
     563             :                 "SUBDATASET_2_NAME",
     564             :                 CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
     565             :                            pszNameOfQualityGroup),
     566             :                 "SUBDATASETS");
     567           3 :             poDS->GDALDataset::SetMetadataItem(
     568             :                 "SUBDATASET_2_DESC",
     569             :                 CPLSPrintf("Georeferenced metadata %s", pszNameOfQualityGroup),
     570             :                 "SUBDATASETS");
     571             :         }
     572             :     }
     573             : 
     574             :     // Setup/check for pam .aux.xml.
     575          18 :     poDS->SetDescription(osFilename.c_str());
     576          18 :     poDS->TryLoadXML();
     577             : 
     578             :     // Setup overviews.
     579          18 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     580             : 
     581          18 :     return poDS.release();
     582             : }
     583             : 
     584             : /************************************************************************/
     585             : /*                           OpenQuality()                              */
     586             : /************************************************************************/
     587             : 
     588           7 : bool S102Dataset::OpenQuality(GDALOpenInfo *poOpenInfo,
     589             :                               const std::shared_ptr<GDALGroup> &poRootGroup)
     590             : {
     591           7 :     const bool bNorthUp = CPLTestBool(
     592           7 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     593             : 
     594           7 :     const char *pszNameOfQualityGroup = "QualityOfSurvey";
     595          21 :     auto poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     596           7 :     if (!poGroupQuality)
     597             :     {
     598           5 :         pszNameOfQualityGroup = "QualityOfBathymetryCoverage";
     599           5 :         poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     600           5 :         if (!poGroupQuality)
     601             :         {
     602           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     603             :                      "Cannot find group /QualityOfSurvey or "
     604             :                      "/QualityOfBathymetryCoverage");
     605           2 :             return false;
     606             :         }
     607             :     }
     608             : 
     609             :     const std::string osQuality01Name =
     610          15 :         std::string(pszNameOfQualityGroup).append(".01");
     611          10 :     const std::string osQuality01FullName = std::string("/")
     612           5 :                                                 .append(pszNameOfQualityGroup)
     613           5 :                                                 .append("/")
     614          10 :                                                 .append(osQuality01Name);
     615          10 :     auto poGroupQuality01 = poGroupQuality->OpenGroup(osQuality01Name);
     616           5 :     if (!poGroupQuality01)
     617             :     {
     618           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
     619             :                  osQuality01FullName.c_str());
     620           0 :         return false;
     621             :     }
     622             : 
     623          10 :     if (auto poStartSequence = poGroupQuality01->GetAttribute("startSequence"))
     624             :     {
     625           1 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     626           1 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     627             :         {
     628           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     629             :                      "startSequence (=%s) != 0,0 is not supported",
     630             :                      pszStartSequence);
     631           0 :             return false;
     632             :         }
     633             :     }
     634             : 
     635             :     // Compute geotransform
     636           5 :     m_bHasGT = S100GetGeoTransform(poGroupQuality01.get(), m_gt, bNorthUp);
     637             : 
     638          15 :     auto poGroup001 = poGroupQuality01->OpenGroup("Group_001");
     639           5 :     if (!poGroup001)
     640             :     {
     641           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s/Group_001",
     642             :                  osQuality01FullName.c_str());
     643           0 :         return false;
     644             :     }
     645             : 
     646          15 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     647           5 :     if (!poValuesArray)
     648             :     {
     649           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     650             :                  "Cannot find array "
     651             :                  "%s/Group_001/values",
     652             :                  osQuality01FullName.c_str());
     653           0 :         return false;
     654             :     }
     655             : 
     656             :     {
     657           5 :         const auto &oType = poValuesArray->GetDataType();
     658           8 :         if (oType.GetClass() == GEDTC_NUMERIC &&
     659           3 :             oType.GetNumericDataType() == GDT_UInt32)
     660             :         {
     661             :             // ok
     662             :         }
     663           4 :         else if (oType.GetClass() == GEDTC_COMPOUND &&
     664           4 :                  oType.GetComponents().size() == 1 &&
     665           2 :                  oType.GetComponents()[0]->GetType().GetClass() ==
     666           4 :                      GEDTC_NUMERIC &&
     667           2 :                  oType.GetComponents()[0]->GetType().GetNumericDataType() ==
     668             :                      GDT_UInt32)
     669             :         {
     670             :             // seen in a S102 v3 product (102DE00CA22_UNC_MD.H5), although
     671             :             // I believe this is non-conformant.
     672             : 
     673             :             // Escape potentials single-quote and double-quote with back-slash
     674           2 :             CPLString osEscapedCompName(oType.GetComponents()[0]->GetName());
     675           4 :             osEscapedCompName.replaceAll("\\", "\\\\")
     676           4 :                 .replaceAll("'", "\\'")
     677           2 :                 .replaceAll("\"", "\\\"");
     678             : 
     679             :             // Gets a view with that single component extracted.
     680           4 :             poValuesArray = poValuesArray->GetView(
     681           4 :                 std::string("['").append(osEscapedCompName).append("']"));
     682           2 :             if (!poValuesArray)
     683           0 :                 return false;
     684             :         }
     685             :         else
     686             :         {
     687           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     688             :                      "Unsupported data type for %s",
     689           0 :                      poValuesArray->GetFullName().c_str());
     690           0 :             return false;
     691             :         }
     692             :     }
     693             : 
     694           5 :     if (poValuesArray->GetDimensionCount() != 2)
     695             :     {
     696           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     697             :                  "Unsupported number of dimensions for %s",
     698           0 :                  poValuesArray->GetFullName().c_str());
     699           0 :         return false;
     700             :     }
     701             : 
     702             :     auto poFeatureAttributeTable =
     703          15 :         poGroupQuality->OpenMDArray("featureAttributeTable");
     704           5 :     if (!poFeatureAttributeTable)
     705             :     {
     706           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     707             :                  "Cannot find array /%s/featureAttributeTable",
     708             :                  pszNameOfQualityGroup);
     709           0 :         return false;
     710             :     }
     711             : 
     712             :     {
     713           5 :         const auto &oType = poFeatureAttributeTable->GetDataType();
     714           5 :         if (oType.GetClass() != GEDTC_COMPOUND)
     715             :         {
     716           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     717             :                      "Unsupported data type for %s",
     718           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     719           0 :             return false;
     720             :         }
     721             : 
     722           5 :         const auto &poComponents = oType.GetComponents();
     723           5 :         if (poComponents.size() >= 1 && poComponents[0]->GetName() != "id")
     724             :         {
     725           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     726             :                      "Missing 'id' component in %s",
     727           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     728           0 :             return false;
     729             :         }
     730             :     }
     731             : 
     732           5 :     if (bNorthUp)
     733           3 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     734             : 
     735             :     auto poDS =
     736          10 :         std::unique_ptr<GDALDataset>(poValuesArray->AsClassicDataset(1, 0));
     737           5 :     if (!poDS)
     738           0 :         return false;
     739             : 
     740           5 :     nRasterXSize = poDS->GetRasterXSize();
     741           5 :     nRasterYSize = poDS->GetRasterYSize();
     742             : 
     743             :     auto poRAT =
     744          10 :         HDF5CreateRAT(poFeatureAttributeTable, /* bFirstColIsMinMax = */ true);
     745             :     auto poBand = std::make_unique<S102GeoreferencedMetadataRasterBand>(
     746           5 :         std::move(poDS), std::move(poRAT));
     747           5 :     SetBand(1, poBand.release());
     748             : 
     749           5 :     return true;
     750             : }
     751             : 
     752             : /************************************************************************/
     753             : /*                      S102DatasetDriverUnload()                       */
     754             : /************************************************************************/
     755             : 
     756         323 : static void S102DatasetDriverUnload(GDALDriver *)
     757             : {
     758         323 :     HDF5UnloadFileDriver();
     759         323 : }
     760             : 
     761             : /************************************************************************/
     762             : /*                         GDALRegister_S102()                          */
     763             : /************************************************************************/
     764         355 : void GDALRegister_S102()
     765             : 
     766             : {
     767         355 :     if (!GDAL_CHECK_VERSION("S102"))
     768           0 :         return;
     769             : 
     770         355 :     if (GDALGetDriverByName(S102_DRIVER_NAME) != nullptr)
     771           0 :         return;
     772             : 
     773         355 :     GDALDriver *poDriver = new GDALDriver();
     774             : 
     775         355 :     S102DriverSetCommonMetadata(poDriver);
     776         355 :     poDriver->pfnOpen = S102Dataset::Open;
     777         355 :     poDriver->pfnUnloadDriver = S102DatasetDriverUnload;
     778             : 
     779         355 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     780             : }

Generated by: LCOV version 1.14