LCOV - code coverage report
Current view: top level - frmts/hdf5 - s102dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 891 962 92.6 %
Date: 2026-06-19 21:24:00 Functions: 26 27 96.3 %

          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-2025, Even Rouault <even dot rouault at spatialys dot com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifdef _POSIX_C_SOURCE
      14             : #undef _POSIX_C_SOURCE
      15             : #endif
      16             : 
      17             : #include "cpl_port.h"
      18             : #include "cpl_vsi.h"
      19             : 
      20             : #include "hdf5dataset.h"
      21             : #include "hdf5drivercore.h"
      22             : #include "gh5_convenience.h"
      23             : #include "rat.h"
      24             : #include "s100.h"
      25             : 
      26             : #include "gdal_frmts.h"
      27             : #include "gdal_priv.h"
      28             : #include "gdal_proxy.h"
      29             : #include "gdal_rat.h"
      30             : 
      31             : #include <algorithm>
      32             : #include <array>
      33             : #include <cmath>
      34             : #include <limits>
      35             : #include <map>
      36             : #include <set>
      37             : #include <string_view>
      38             : 
      39             : /************************************************************************/
      40             : /*                             S102Dataset                              */
      41             : /************************************************************************/
      42             : 
      43          98 : class S102Dataset final : public S100BaseDataset
      44             : {
      45             :     bool OpenQuality(GDALOpenInfo *poOpenInfo,
      46             :                      const std::shared_ptr<GDALGroup> &poRootGroup);
      47             : 
      48             :   public:
      49          49 :     explicit S102Dataset(const std::string &osFilename)
      50          49 :         : S100BaseDataset(osFilename)
      51             :     {
      52          49 :     }
      53             : 
      54             :     ~S102Dataset() override;
      55             : 
      56             :     static GDALDataset *Open(GDALOpenInfo *);
      57             :     static GDALDataset *CreateCopy(const char *pszFilename,
      58             :                                    GDALDataset *poSrcDS, int bStrict,
      59             :                                    CSLConstList papszOptions,
      60             :                                    GDALProgressFunc pfnProgress,
      61             :                                    void *pProgressData);
      62             : };
      63             : 
      64             : S102Dataset::~S102Dataset() = default;
      65             : 
      66             : /************************************************************************/
      67             : /*                            S102RasterBand                            */
      68             : /************************************************************************/
      69             : 
      70             : class S102RasterBand final : public GDALProxyRasterBand
      71             : {
      72             :     friend class S102Dataset;
      73             :     std::unique_ptr<GDALDataset> m_poDS{};
      74             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      75             :     double m_dfMinimum = std::numeric_limits<double>::quiet_NaN();
      76             :     double m_dfMaximum = std::numeric_limits<double>::quiet_NaN();
      77             : 
      78             :     CPL_DISALLOW_COPY_ASSIGN(S102RasterBand)
      79             : 
      80             :   public:
      81          55 :     explicit S102RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      82         110 :         : m_poDS(std::move(poDSIn)),
      83          55 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      84             :     {
      85          55 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      86          55 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      87          55 :     }
      88             : 
      89             :     GDALRasterBand *
      90             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
      91             : 
      92          10 :     double GetMinimum(int *pbSuccess = nullptr) override
      93             :     {
      94          10 :         if (pbSuccess)
      95          10 :             *pbSuccess = !std::isnan(m_dfMinimum);
      96          10 :         return m_dfMinimum;
      97             :     }
      98             : 
      99          10 :     double GetMaximum(int *pbSuccess = nullptr) override
     100             :     {
     101          10 :         if (pbSuccess)
     102          10 :             *pbSuccess = !std::isnan(m_dfMaximum);
     103          10 :         return m_dfMaximum;
     104             :     }
     105             : 
     106           4 :     const char *GetUnitType() override
     107             :     {
     108           4 :         return "metre";
     109             :     }
     110             : };
     111             : 
     112             : GDALRasterBand *
     113          49 : S102RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
     114             : {
     115          49 :     return m_poUnderlyingBand;
     116             : }
     117             : 
     118             : /************************************************************************/
     119             : /*                 S102GeoreferencedMetadataRasterBand                  */
     120             : /************************************************************************/
     121             : 
     122             : class S102GeoreferencedMetadataRasterBand final : public GDALProxyRasterBand
     123             : {
     124             :     friend class S102Dataset;
     125             : 
     126             :     std::unique_ptr<GDALDataset> m_poDS{};
     127             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
     128             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
     129             : 
     130             :     CPL_DISALLOW_COPY_ASSIGN(S102GeoreferencedMetadataRasterBand)
     131             : 
     132             :   public:
     133           7 :     explicit S102GeoreferencedMetadataRasterBand(
     134             :         std::unique_ptr<GDALDataset> &&poDSIn,
     135             :         std::unique_ptr<GDALRasterAttributeTable> &&poRAT)
     136          14 :         : m_poDS(std::move(poDSIn)),
     137           7 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1)),
     138          14 :           m_poRAT(std::move(poRAT))
     139             :     {
     140           7 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
     141           7 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     142           7 :     }
     143             : 
     144             :     GDALRasterBand *
     145             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
     146             : 
     147           6 :     GDALRasterAttributeTable *GetDefaultRAT() override
     148             :     {
     149           6 :         return m_poRAT.get();
     150             :     }
     151             : };
     152             : 
     153          43 : GDALRasterBand *S102GeoreferencedMetadataRasterBand::RefUnderlyingRasterBand(
     154             :     bool /*bForceOpen*/) const
     155             : {
     156          43 :     return m_poUnderlyingBand;
     157             : }
     158             : 
     159             : /************************************************************************/
     160             : /*                                Open()                                */
     161             : /************************************************************************/
     162             : 
     163          54 : GDALDataset *S102Dataset::Open(GDALOpenInfo *poOpenInfo)
     164             : 
     165             : {
     166             :     // Confirm that this appears to be a S102 file.
     167          54 :     if (!S102DatasetIdentify(poOpenInfo))
     168           1 :         return nullptr;
     169             : 
     170             :     HDF5_GLOBAL_LOCK();
     171             : 
     172          53 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
     173             :     {
     174           2 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     175             :     }
     176             : 
     177             :     // Confirm the requested access is supported.
     178          51 :     if (poOpenInfo->eAccess == GA_Update)
     179             :     {
     180           0 :         ReportUpdateNotSupportedByDriver("S102");
     181           0 :         return nullptr;
     182             :     }
     183             : 
     184         102 :     std::string osFilename(poOpenInfo->pszFilename);
     185          51 :     bool bIsSubdataset = false;
     186          51 :     bool bIsQuality = false;
     187         102 :     std::string osBathymetryCoverageName = "BathymetryCoverage.01";
     188          51 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S102:"))
     189             :     {
     190             :         const CPLStringList aosTokens(
     191          19 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     192          19 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     193             : 
     194          19 :         if (aosTokens.size() == 2)
     195             :         {
     196           1 :             osFilename = aosTokens[1];
     197             :         }
     198          18 :         else if (aosTokens.size() == 3)
     199             :         {
     200          18 :             bIsSubdataset = true;
     201          18 :             osFilename = aosTokens[1];
     202          18 :             if (EQUAL(aosTokens[2], "BathymetryCoverage"))
     203             :             {
     204             :                 // Default dataset
     205             :             }
     206          16 :             else if (STARTS_WITH(aosTokens[2], "BathymetryCoverage"))
     207             :             {
     208           5 :                 osBathymetryCoverageName = aosTokens[2];
     209             :             }
     210          18 :             else if (EQUAL(aosTokens[2], "QualityOfSurvey") ||  // < v3
     211           7 :                      EQUAL(aosTokens[2], "QualityOfBathymetryCoverage"))  // v3
     212             :             {
     213           9 :                 bIsQuality = true;
     214             :             }
     215             :             else
     216             :             {
     217           2 :                 CPLError(CE_Failure, CPLE_NotSupported,
     218             :                          "Unsupported subdataset component: '%s'. Expected "
     219             :                          "'QualityOfSurvey'",
     220             :                          aosTokens[2]);
     221           2 :                 return nullptr;
     222             :             }
     223             :         }
     224             :         else
     225             :         {
     226           0 :             return nullptr;
     227             :         }
     228             :     }
     229             : 
     230          98 :     auto poDS = std::make_unique<S102Dataset>(osFilename);
     231          49 :     if (!poDS->Init())
     232           0 :         return nullptr;
     233             : 
     234          49 :     const auto &poRootGroup = poDS->m_poRootGroup;
     235             : 
     236         147 :     auto poBathymetryCoverage = poRootGroup->OpenGroup("BathymetryCoverage");
     237          49 :     if (!poBathymetryCoverage)
     238             :     {
     239           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     240             :                  "S102: Cannot find /BathymetryCoverage group");
     241           0 :         return nullptr;
     242             :     }
     243             : 
     244          49 :     if (!bIsSubdataset)
     245             :     {
     246             :         auto poNumInstances =
     247          66 :             poBathymetryCoverage->GetAttribute("numInstances");
     248          52 :         if (poNumInstances &&
     249          52 :             poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
     250             :         {
     251          19 :             const int nNumInstances = poNumInstances->ReadAsInt();
     252          19 :             if (nNumInstances != 1)
     253             :             {
     254           4 :                 CPLStringList aosSubDSList;
     255           2 :                 int iSubDS = 0;
     256           4 :                 for (const std::string &coverageName :
     257          10 :                      poBathymetryCoverage->GetGroupNames())
     258             :                 {
     259             :                     auto poCoverage =
     260           8 :                         poBathymetryCoverage->OpenGroup(coverageName);
     261           4 :                     if (poCoverage)
     262             :                     {
     263           8 :                         GDALMajorObject mo;
     264             :                         // Read first vertical datum from root group and let the
     265             :                         // coverage override it.
     266           4 :                         S100ReadVerticalDatum(&mo, poRootGroup.get());
     267           4 :                         S100ReadVerticalDatum(&mo, poCoverage.get());
     268           4 :                         ++iSubDS;
     269             :                         aosSubDSList.SetNameValue(
     270             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     271             :                             CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
     272           4 :                                        coverageName.c_str()));
     273           8 :                         std::string verticalDatum;
     274             :                         const char *pszValue =
     275           4 :                             mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
     276           4 :                         if (pszValue)
     277             :                         {
     278           4 :                             verticalDatum = ", vertical datum ";
     279           4 :                             verticalDatum += pszValue;
     280             :                             pszValue =
     281           4 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
     282           4 :                             if (pszValue)
     283             :                             {
     284           4 :                                 verticalDatum += " (";
     285           4 :                                 verticalDatum += pszValue;
     286           4 :                                 verticalDatum += ')';
     287             :                             }
     288             :                         }
     289             :                         aosSubDSList.SetNameValue(
     290             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     291             :                             CPLSPrintf(
     292             :                                 "Bathymetric gridded data, instance %s%s",
     293           4 :                                 coverageName.c_str(), verticalDatum.c_str()));
     294             :                     }
     295             :                 }
     296             :                 auto poGroupQuality =
     297           6 :                     poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
     298           2 :                 if (poGroupQuality)
     299             :                 {
     300             :                     auto poQualityOfBathymetryCoverage01 =
     301           1 :                         poGroupQuality->OpenGroup(
     302           3 :                             "QualityOfBathymetryCoverage.01");
     303           1 :                     if (poQualityOfBathymetryCoverage01)
     304             :                     {
     305           1 :                         ++iSubDS;
     306             :                         aosSubDSList.SetNameValue(
     307             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     308             :                             CPLSPrintf(
     309             :                                 "S102:\"%s\":QualityOfBathymetryCoverage",
     310           1 :                                 osFilename.c_str()));
     311             :                         aosSubDSList.SetNameValue(
     312             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     313             :                             "Georeferenced metadata "
     314           1 :                             "QualityOfBathymetryCoverage");
     315             :                     }
     316             :                 }
     317             : 
     318           2 :                 poDS->GDALDataset::SetMetadata(aosSubDSList.List(),
     319             :                                                GDAL_MDD_SUBDATASETS);
     320             : 
     321             :                 // Setup/check for pam .aux.xml.
     322           2 :                 poDS->SetDescription(osFilename.c_str());
     323           2 :                 poDS->TryLoadXML();
     324             : 
     325             :                 // Setup overviews.
     326           2 :                 poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     327             : 
     328           2 :                 return poDS.release();
     329             :             }
     330             :         }
     331             :     }
     332             : 
     333          47 :     if (bIsQuality)
     334             :     {
     335           9 :         if (!poDS->OpenQuality(poOpenInfo, poRootGroup))
     336           2 :             return nullptr;
     337             : 
     338             :         // Setup/check for pam .aux.xml.
     339           7 :         poDS->SetDescription(osFilename.c_str());
     340           7 :         poDS->TryLoadXML();
     341             : 
     342             :         // Setup overviews.
     343           7 :         poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     344             : 
     345           7 :         return poDS.release();
     346             :     }
     347             : 
     348             :     auto poBathymetryCoverageInstance =
     349          76 :         poBathymetryCoverage->OpenGroup(osBathymetryCoverageName);
     350          38 :     if (!poBathymetryCoverageInstance)
     351             :     {
     352           1 :         CPLError(CE_Failure, CPLE_AppDefined,
     353             :                  "S102: Cannot find %s group in BathymetryCoverage group",
     354             :                  osBathymetryCoverageName.c_str());
     355           1 :         return nullptr;
     356             :     }
     357             : 
     358          37 :     if (auto poStartSequence =
     359          74 :             poBathymetryCoverage->GetAttribute("startSequence"))
     360             :     {
     361           0 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     362           0 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     363             :         {
     364             :             // Shouldn't happen given this is imposed by the spec.
     365             :             // Cf 4.2.1.1.1.12 "startSequence" of Ed 3.0 spec, page 13
     366           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     367             :                      "startSequence (=%s) != 0,0 is not supported",
     368             :                      pszStartSequence);
     369           0 :             return nullptr;
     370             :         }
     371             :     }
     372             : 
     373             :     // Potentially override vertical datum
     374          37 :     S100ReadVerticalDatum(poDS.get(), poBathymetryCoverageInstance.get());
     375             : 
     376          37 :     const bool bNorthUp = CPLTestBool(
     377          37 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     378             : 
     379             :     // Compute geotransform
     380          37 :     poDS->m_bHasGT = S100GetGeoTransform(poBathymetryCoverageInstance.get(),
     381          37 :                                          poDS->m_gt, bNorthUp);
     382             : 
     383         111 :     auto poGroup001 = poBathymetryCoverageInstance->OpenGroup("Group_001");
     384          37 :     if (!poGroup001)
     385             :     {
     386           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     387             :                  "S102: Cannot find "
     388             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001");
     389           0 :         return nullptr;
     390             :     }
     391         111 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     392          37 :     if (!poValuesArray || poValuesArray->GetDimensionCount() != 2)
     393             :     {
     394           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     395             :                  "S102: Cannot find "
     396             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     397           0 :         return nullptr;
     398             :     }
     399          37 :     const auto &oType = poValuesArray->GetDataType();
     400          37 :     if (oType.GetClass() != GEDTC_COMPOUND)
     401             :     {
     402           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     403             :                  "S102: Wrong type for "
     404             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     405           0 :         return nullptr;
     406             :     }
     407          37 :     const auto &oComponents = oType.GetComponents();
     408          37 :     if (oComponents.size() == 0 || oComponents[0]->GetName() != "depth")
     409             :     {
     410           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     411             :                  "S102: Wrong type for "
     412             :                  "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
     413           0 :         return nullptr;
     414             :     }
     415             : 
     416          37 :     if (bNorthUp)
     417          36 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     418             : 
     419         111 :     auto poDepth = poValuesArray->GetView("[\"depth\"]");
     420             : 
     421             :     // Mandatory in v2.2. Since v3.0, EPSG:6498 is the only allowed value
     422          37 :     bool bCSIsElevation = false;
     423         111 :     auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
     424          37 :     if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
     425             :     {
     426          27 :         const auto nVal = poVerticalCS->ReadAsInt();
     427          27 :         if (nVal == 6498)  // Depth metre
     428             :         {
     429             :             // nothing to do
     430             :         }
     431           0 :         else if (nVal == 6499)  // Height metre
     432             :         {
     433           0 :             bCSIsElevation = true;
     434             :         }
     435             :         else
     436             :         {
     437           0 :             CPLError(CE_Warning, CPLE_NotSupported, "Unsupported verticalCS=%d",
     438             :                      nVal);
     439             :         }
     440             :     }
     441             : 
     442             :     const bool bUseElevation =
     443          37 :         EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
     444             :                                    "DEPTH_OR_ELEVATION", "DEPTH"),
     445             :               "ELEVATION");
     446          73 :     const bool bInvertDepth = (bUseElevation && !bCSIsElevation) ||
     447          36 :                               (!bUseElevation && bCSIsElevation);
     448          37 :     const double dfDepthNoData = poDepth->GetNoDataValueAsDouble();
     449          75 :     auto poDepthDS = [&poDepth, bInvertDepth, dfDepthNoData]()
     450             :     {
     451          37 :         if (bInvertDepth)
     452             :         {
     453           1 :             auto poInverted = poDepth->GetUnscaled(-1, 0, dfDepthNoData);
     454             :             return std::unique_ptr<GDALDataset>(
     455           1 :                 poInverted->AsClassicDataset(1, 0));
     456             :         }
     457             :         else
     458             :         {
     459             :             return std::unique_ptr<GDALDataset>(
     460          36 :                 poDepth->AsClassicDataset(1, 0));
     461             :         }
     462          74 :     }();
     463             : 
     464          37 :     poDS->nRasterXSize = poDepthDS->GetRasterXSize();
     465          37 :     poDS->nRasterYSize = poDepthDS->GetRasterYSize();
     466             : 
     467             :     // Create depth (or elevation) band
     468          37 :     auto poDepthBand = new S102RasterBand(std::move(poDepthDS));
     469          37 :     poDepthBand->SetDescription(bUseElevation ? "elevation" : "depth");
     470             : 
     471         111 :     auto poMinimumDepth = poGroup001->GetAttribute("minimumDepth");
     472          74 :     if (poMinimumDepth &&
     473          74 :         poMinimumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     474             :     {
     475          37 :         const double dfVal = poMinimumDepth->ReadAsDouble();
     476          37 :         if (dfVal != dfDepthNoData)
     477             :         {
     478          33 :             if (bInvertDepth)
     479           1 :                 poDepthBand->m_dfMaximum = -dfVal;
     480             :             else
     481          32 :                 poDepthBand->m_dfMinimum = dfVal;
     482             :         }
     483             :     }
     484             : 
     485         111 :     auto poMaximumDepth = poGroup001->GetAttribute("maximumDepth");
     486          74 :     if (poMaximumDepth &&
     487          74 :         poMaximumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
     488             :     {
     489          37 :         const double dfVal = poMaximumDepth->ReadAsDouble();
     490          37 :         if (dfVal != dfDepthNoData)
     491             :         {
     492          37 :             if (bInvertDepth)
     493           1 :                 poDepthBand->m_dfMinimum = -dfVal;
     494             :             else
     495          36 :                 poDepthBand->m_dfMaximum = dfVal;
     496             :         }
     497             :     }
     498             : 
     499          37 :     poDS->SetBand(1, poDepthBand);
     500             : 
     501             :     const bool bHasUncertainty =
     502          37 :         oComponents.size() >= 2 && oComponents[1]->GetName() == "uncertainty";
     503          37 :     if (bHasUncertainty)
     504             :     {
     505             :         // Create uncertainty band
     506          54 :         auto poUncertainty = poValuesArray->GetView("[\"uncertainty\"]");
     507             :         const double dfUncertaintyNoData =
     508          18 :             poUncertainty->GetNoDataValueAsDouble();
     509             :         auto poUncertaintyDS =
     510          36 :             std::unique_ptr<GDALDataset>(poUncertainty->AsClassicDataset(1, 0));
     511             : 
     512          18 :         auto poUncertaintyBand = new S102RasterBand(std::move(poUncertaintyDS));
     513          18 :         poUncertaintyBand->SetDescription("uncertainty");
     514             : 
     515             :         auto poMinimumUncertainty =
     516          54 :             poGroup001->GetAttribute("minimumUncertainty");
     517          36 :         if (poMinimumUncertainty &&
     518          36 :             poMinimumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     519             :         {
     520          18 :             const double dfVal = poMinimumUncertainty->ReadAsDouble();
     521          18 :             if (dfVal != dfUncertaintyNoData)
     522             :             {
     523          18 :                 poUncertaintyBand->m_dfMinimum = dfVal;
     524             :             }
     525             :         }
     526             : 
     527             :         auto poMaximumUncertainty =
     528          54 :             poGroup001->GetAttribute("maximumUncertainty");
     529          36 :         if (poMaximumUncertainty &&
     530          36 :             poMaximumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
     531             :         {
     532          18 :             const double dfVal = poMaximumUncertainty->ReadAsDouble();
     533          18 :             if (dfVal != dfUncertaintyNoData)
     534             :             {
     535          18 :                 poUncertaintyBand->m_dfMaximum = dfVal;
     536             :             }
     537             :         }
     538             : 
     539          18 :         poDS->SetBand(2, poUncertaintyBand);
     540             :     }
     541             : 
     542          37 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     543             : 
     544         111 :     auto poGroupQuality = poRootGroup->OpenGroup("QualityOfSurvey");
     545          37 :     const bool bIsNamedQualityOfSurvey = poGroupQuality != nullptr;
     546          37 :     if (!bIsNamedQualityOfSurvey)
     547             :     {
     548             :         // S102 v3 now uses QualityOfBathymetryCoverage instead of QualityOfSurvey
     549          35 :         poGroupQuality = poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
     550             :     }
     551          37 :     if (!bIsSubdataset && poGroupQuality)
     552             :     {
     553           6 :         const char *pszNameOfQualityGroup = bIsNamedQualityOfSurvey
     554           6 :                                                 ? "QualityOfSurvey"
     555             :                                                 : "QualityOfBathymetryCoverage";
     556           6 :         auto poGroupQuality01 = poGroupQuality->OpenGroup(
     557          18 :             CPLSPrintf("%s.01", pszNameOfQualityGroup));
     558           6 :         if (poGroupQuality01)
     559             :         {
     560           6 :             poDS->GDALDataset::SetMetadataItem(
     561             :                 "SUBDATASET_1_NAME",
     562             :                 CPLSPrintf("S102:\"%s\":BathymetryCoverage",
     563             :                            osFilename.c_str()),
     564             :                 GDAL_MDD_SUBDATASETS);
     565           6 :             poDS->GDALDataset::SetMetadataItem("SUBDATASET_1_DESC",
     566             :                                                "Bathymetric gridded data",
     567             :                                                GDAL_MDD_SUBDATASETS);
     568             : 
     569           6 :             poDS->GDALDataset::SetMetadataItem(
     570             :                 "SUBDATASET_2_NAME",
     571             :                 CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
     572             :                            pszNameOfQualityGroup),
     573             :                 GDAL_MDD_SUBDATASETS);
     574           6 :             poDS->GDALDataset::SetMetadataItem(
     575             :                 "SUBDATASET_2_DESC",
     576             :                 CPLSPrintf("Georeferenced metadata %s", pszNameOfQualityGroup),
     577             :                 GDAL_MDD_SUBDATASETS);
     578             :         }
     579             :     }
     580             : 
     581             :     // Setup/check for pam .aux.xml.
     582          37 :     poDS->SetDescription(osFilename.c_str());
     583          37 :     poDS->TryLoadXML();
     584             : 
     585             :     // Setup overviews.
     586          37 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     587             : 
     588          37 :     return poDS.release();
     589             : }
     590             : 
     591             : /************************************************************************/
     592             : /*                            OpenQuality()                             */
     593             : /************************************************************************/
     594             : 
     595           9 : bool S102Dataset::OpenQuality(GDALOpenInfo *poOpenInfo,
     596             :                               const std::shared_ptr<GDALGroup> &poRootGroup)
     597             : {
     598           9 :     const bool bNorthUp = CPLTestBool(
     599           9 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     600             : 
     601           9 :     const char *pszNameOfQualityGroup = "QualityOfSurvey";
     602          27 :     auto poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     603           9 :     if (!poGroupQuality)
     604             :     {
     605           7 :         pszNameOfQualityGroup = "QualityOfBathymetryCoverage";
     606           7 :         poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
     607           7 :         if (!poGroupQuality)
     608             :         {
     609           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     610             :                      "Cannot find group /QualityOfSurvey or "
     611             :                      "/QualityOfBathymetryCoverage");
     612           2 :             return false;
     613             :         }
     614             :     }
     615             : 
     616             :     const std::string osQuality01Name =
     617          21 :         std::string(pszNameOfQualityGroup).append(".01");
     618          14 :     const std::string osQuality01FullName = std::string("/")
     619           7 :                                                 .append(pszNameOfQualityGroup)
     620           7 :                                                 .append("/")
     621          14 :                                                 .append(osQuality01Name);
     622          14 :     auto poGroupQuality01 = poGroupQuality->OpenGroup(osQuality01Name);
     623           7 :     if (!poGroupQuality01)
     624             :     {
     625           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
     626             :                  osQuality01FullName.c_str());
     627           0 :         return false;
     628             :     }
     629             : 
     630          14 :     if (auto poStartSequence = poGroupQuality01->GetAttribute("startSequence"))
     631             :     {
     632           3 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     633           3 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     634             :         {
     635           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     636             :                      "startSequence (=%s) != 0,0 is not supported",
     637             :                      pszStartSequence);
     638           0 :             return false;
     639             :         }
     640             :     }
     641             : 
     642             :     // Compute geotransform
     643           7 :     m_bHasGT = S100GetGeoTransform(poGroupQuality01.get(), m_gt, bNorthUp);
     644             : 
     645          21 :     auto poGroup001 = poGroupQuality01->OpenGroup("Group_001");
     646           7 :     if (!poGroup001)
     647             :     {
     648           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s/Group_001",
     649             :                  osQuality01FullName.c_str());
     650           0 :         return false;
     651             :     }
     652             : 
     653          21 :     auto poValuesArray = poGroup001->OpenMDArray("values");
     654           7 :     if (!poValuesArray)
     655             :     {
     656           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     657             :                  "Cannot find array "
     658             :                  "%s/Group_001/values",
     659             :                  osQuality01FullName.c_str());
     660           0 :         return false;
     661             :     }
     662             : 
     663             :     {
     664           7 :         const auto &oType = poValuesArray->GetDataType();
     665          12 :         if (oType.GetClass() == GEDTC_NUMERIC &&
     666           5 :             (oType.GetNumericDataType() == GDT_UInt32 ||
     667             :              // Also accept Int32 as in /vsizip//vsicurl/https://services.data.shom.fr/static/jeux_test/S-102_FR.zip/S100_ROOT/S-102/DATASET_FILES/102FR0014581G013000/102FR0014581G013000.H5
     668           0 :              oType.GetNumericDataType() == GDT_Int32))
     669             :         {
     670             :             // ok
     671             :         }
     672           4 :         else if (oType.GetClass() == GEDTC_COMPOUND &&
     673           4 :                  oType.GetComponents().size() == 1 &&
     674           2 :                  oType.GetComponents()[0]->GetType().GetClass() ==
     675           4 :                      GEDTC_NUMERIC &&
     676           2 :                  oType.GetComponents()[0]->GetType().GetNumericDataType() ==
     677             :                      GDT_UInt32)
     678             :         {
     679             :             // seen in a S102 v3 product (102DE00CA22_UNC_MD.H5), although
     680             :             // I believe this is non-conformant.
     681             : 
     682             :             // Escape potentials single-quote and double-quote with back-slash
     683           2 :             CPLString osEscapedCompName(oType.GetComponents()[0]->GetName());
     684           4 :             osEscapedCompName.replaceAll("\\", "\\\\")
     685           4 :                 .replaceAll("'", "\\'")
     686           2 :                 .replaceAll("\"", "\\\"");
     687             : 
     688             :             // Gets a view with that single component extracted.
     689           4 :             poValuesArray = poValuesArray->GetView(
     690           4 :                 std::string("['").append(osEscapedCompName).append("']"));
     691           2 :             if (!poValuesArray)
     692           0 :                 return false;
     693             :         }
     694             :         else
     695             :         {
     696           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     697             :                      "Unsupported data type for %s",
     698           0 :                      poValuesArray->GetFullName().c_str());
     699           0 :             return false;
     700             :         }
     701             :     }
     702             : 
     703           7 :     if (poValuesArray->GetDimensionCount() != 2)
     704             :     {
     705           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     706             :                  "Unsupported number of dimensions for %s",
     707           0 :                  poValuesArray->GetFullName().c_str());
     708           0 :         return false;
     709             :     }
     710             : 
     711             :     auto poFeatureAttributeTable =
     712          21 :         poGroupQuality->OpenMDArray("featureAttributeTable");
     713           7 :     if (!poFeatureAttributeTable)
     714             :     {
     715           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     716             :                  "Cannot find array /%s/featureAttributeTable",
     717             :                  pszNameOfQualityGroup);
     718           0 :         return false;
     719             :     }
     720             : 
     721             :     {
     722           7 :         const auto &oType = poFeatureAttributeTable->GetDataType();
     723           7 :         if (oType.GetClass() != GEDTC_COMPOUND)
     724             :         {
     725           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     726             :                      "Unsupported data type for %s",
     727           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     728           0 :             return false;
     729             :         }
     730             : 
     731           7 :         const auto &poComponents = oType.GetComponents();
     732           7 :         if (poComponents.size() >= 1 && poComponents[0]->GetName() != "id")
     733             :         {
     734           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     735             :                      "Missing 'id' component in %s",
     736           0 :                      poFeatureAttributeTable->GetFullName().c_str());
     737           0 :             return false;
     738             :         }
     739             :     }
     740             : 
     741           7 :     if (bNorthUp)
     742           5 :         poValuesArray = poValuesArray->GetView("[::-1,...]");
     743             : 
     744             :     auto poDS =
     745          14 :         std::unique_ptr<GDALDataset>(poValuesArray->AsClassicDataset(1, 0));
     746           7 :     if (!poDS)
     747           0 :         return false;
     748             : 
     749           7 :     nRasterXSize = poDS->GetRasterXSize();
     750           7 :     nRasterYSize = poDS->GetRasterYSize();
     751             : 
     752             :     auto poRAT =
     753          14 :         HDF5CreateRAT(poFeatureAttributeTable, /* bFirstColIsMinMax = */ true);
     754             :     auto poBand = std::make_unique<S102GeoreferencedMetadataRasterBand>(
     755           7 :         std::move(poDS), std::move(poRAT));
     756           7 :     SetBand(1, poBand.release());
     757             : 
     758           7 :     return true;
     759             : }
     760             : 
     761             : /************************************************************************/
     762             : /*                             S102Creator                              */
     763             : /************************************************************************/
     764             : 
     765             : class S102Creator final : public S100BaseWriter
     766             : {
     767             :   public:
     768          56 :     S102Creator(const char *pszDestFilename, GDALDataset *poSrcDS,
     769             :                 CSLConstList papszOptions)
     770          56 :         : S100BaseWriter(pszDestFilename, poSrcDS, papszOptions)
     771             :     {
     772          56 :     }
     773             : 
     774             :     ~S102Creator() override;
     775             : 
     776             :     bool Create(GDALProgressFunc pfnProgress, void *pProgressData);
     777             : 
     778             :     // From the S102 spec
     779             :     static constexpr float NODATA = 1000000.0f;
     780             :     static constexpr const char *FEATURE_TYPE = "BathymetryCoverage";
     781             :     static constexpr const char *QUALITY_FEATURE_TYPE =
     782             :         "QualityOfBathymetryCoverage";
     783             : 
     784             :   protected:
     785          73 :     bool Close() override
     786             :     {
     787          73 :         return BaseClose();
     788             :     }
     789             : 
     790             :   private:
     791             :     bool WriteFeatureGroupAttributes(bool isQuality);
     792             :     bool CopyValues(GDALProgressFunc pfnProgress, void *pProgressData);
     793             :     bool CopyQualityValues(GDALDataset *poQualityDS,
     794             :                            const std::set<int> &oSetRATId,
     795             :                            GDALProgressFunc pfnProgress, void *pProgressData);
     796             :     bool WriteFeatureAttributeTable(const GDALRasterAttributeTable *poRAT);
     797             :     bool CreateGroupF(bool hasQualityOfBathymetryCoverage);
     798             : };
     799             : 
     800             : /************************************************************************/
     801             : /*                     S102Creator::~S102Creator()                      */
     802             : /************************************************************************/
     803             : 
     804          56 : S102Creator::~S102Creator()
     805             : {
     806          56 :     S102Creator::Close();
     807          56 : }
     808             : 
     809             : /************************************************************************/
     810             : /*                        S102Creator::Create()                         */
     811             : /************************************************************************/
     812             : 
     813             : // S102 v3.0 Table 10-8 - Elements of featureAttributeTable compound datatype
     814             : static const struct
     815             : {
     816             :     const char *pszName;
     817             :     const char *pszType;
     818             : } gasFeatureAttributeTableMembers[] = {
     819             :     {"id", "uint32"},
     820             :     {"dataAssessment", "uint8"},
     821             :     {"featuresDetected.leastDepthOfDetectedFeaturesMeasured", "boolean"},
     822             :     {"featuresDetected.significantFeaturesDetected", "boolean"},
     823             :     {"featuresDetected.sizeOfFeaturesDetected", "float32"},
     824             :     {"featureSizeVar", "float32"},
     825             :     {"fullSeafloorCoverageAchieved", "boolean"},
     826             :     {"bathyCoverage", "boolean"},
     827             :     {"zoneOfConfidence.horizontalPositionUncertainty.uncertaintyFixed",
     828             :      "float32"},
     829             :     {"zoneOfConfidence.horizontalPositionUncertainty.uncertaintyVariableFactor",
     830             :      "float32"},
     831             :     {"surveyDateRange.dateStart", "date"},
     832             :     {"surveyDateRange.dateEnd", "date"},
     833             :     {"sourceSurveyID", "string"},
     834             :     {"surveyAuthority", "string"},
     835             :     {"typeOfBathymetricEstimationUncertainty", "enumeration"},
     836             : };
     837             : 
     838          56 : bool S102Creator::Create(GDALProgressFunc pfnProgress, void *pProgressData)
     839             : {
     840          56 :     if (m_poSrcDS->GetRasterCount() != 1 && m_poSrcDS->GetRasterCount() != 2)
     841             :     {
     842           4 :         CPLError(CE_Failure, CPLE_NotSupported,
     843             :                  "Source dataset must have one or two bands");
     844           4 :         return false;
     845             :     }
     846             : 
     847          52 :     if (!BaseChecks("S102", /* crsMustBeEPSG = */ true,
     848             :                     /* verticalDatumRequired = */ true))
     849          21 :         return false;
     850             : 
     851             :     const bool bAppendSubdataset =
     852          31 :         CPLTestBool(m_aosOptions.FetchNameValueDef("APPEND_SUBDATASET", "NO"));
     853             : 
     854          31 :     std::unique_ptr<GDALDataset> poQualityDS;
     855             :     const char *pszQualityDataset =
     856          31 :         m_aosOptions.FetchNameValue("QUALITY_DATASET");
     857          31 :     const GDALRasterAttributeTable *poRAT = nullptr;
     858          31 :     if (!pszQualityDataset && !bAppendSubdataset)
     859             :     {
     860          26 :         const char *pszSubDSName = m_poSrcDS->GetMetadataItem(
     861          13 :             "SUBDATASET_2_NAME", GDAL_MDD_SUBDATASETS);
     862          13 :         if (pszSubDSName &&
     863          13 :             cpl::starts_with(std::string_view(pszSubDSName), "S102:") &&
     864          13 :             cpl::ends_with(std::string_view(pszSubDSName),
     865             :                            ":QualityOfBathymetryCoverage"))
     866             :         {
     867           0 :             pszQualityDataset = pszSubDSName;
     868             :         }
     869             :     }
     870             : 
     871          62 :     std::set<int> oSetRATId;
     872          31 :     if (pszQualityDataset)
     873             :     {
     874          15 :         if (bAppendSubdataset)
     875             :         {
     876           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     877             :                      "Quality dataset can only be set on initial creation");
     878          12 :             return false;
     879             :         }
     880          15 :         poQualityDS.reset(GDALDataset::Open(
     881             :             pszQualityDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR, nullptr,
     882             :             nullptr, nullptr));
     883          15 :         if (!poQualityDS)
     884           1 :             return false;
     885             : 
     886          14 :         if (poQualityDS->GetRasterCount() != 1)
     887             :         {
     888           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     889             :                      "%s does not have a single band.", pszQualityDataset);
     890           1 :             return false;
     891             :         }
     892          13 :         if (!GDALDataTypeIsInteger(
     893             :                 poQualityDS->GetRasterBand(1)->GetRasterDataType()))
     894             :         {
     895           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     896             :                      "%s band is not of an integer data type.",
     897             :                      pszQualityDataset);
     898           1 :             return false;
     899             :         }
     900          23 :         if (poQualityDS->GetRasterXSize() != m_poSrcDS->GetRasterXSize() ||
     901          11 :             poQualityDS->GetRasterYSize() != m_poSrcDS->GetRasterYSize())
     902             :         {
     903           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     904             :                      "%s does not have the same dimensions as %s.",
     905           2 :                      pszQualityDataset, m_poSrcDS->GetDescription());
     906           2 :             return false;
     907             :         }
     908             : 
     909          10 :         const auto poQualityDS_SRS = poQualityDS->GetSpatialRef();
     910          10 :         if (!poQualityDS_SRS || !poQualityDS_SRS->IsSame(m_poSRS))
     911             :         {
     912           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     913             :                      "%s does not have the same CRS as %s.", pszQualityDataset,
     914           1 :                      m_poSrcDS->GetDescription());
     915           1 :             return false;
     916             :         }
     917             : 
     918           9 :         GDALGeoTransform gt;
     919           9 :         if (poQualityDS->GetGeoTransform(gt) != CE_None || gt != m_gt)
     920             :         {
     921           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     922             :                      "%s does not have the same geotransform as %s.",
     923           1 :                      pszQualityDataset, m_poSrcDS->GetDescription());
     924           1 :             return false;
     925             :         }
     926             : 
     927           8 :         poRAT = poQualityDS->GetRasterBand(1)->GetDefaultRAT();
     928           8 :         if (!poRAT)
     929             :         {
     930           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     931             :                      "%s does not have a raster attribute table.",
     932           1 :                      poQualityDS->GetDescription());
     933           1 :             return false;
     934             :         }
     935             : 
     936           7 :         const int nRATColumnCount = poRAT->GetColumnCount();
     937           7 :         std::set<std::string_view> setKnownColumnNames;
     938         112 :         for (const auto &entry : gasFeatureAttributeTableMembers)
     939         105 :             setKnownColumnNames.insert(entry.pszName);
     940           7 :         int iRATIdField = -1;
     941          24 :         for (int i = 0; i < nRATColumnCount; ++i)
     942             :         {
     943          17 :             const char *pszColName = poRAT->GetNameOfCol(i);
     944          17 :             if (strcmp(pszColName, "id") == 0)
     945             :             {
     946           6 :                 iRATIdField = i;
     947             :             }
     948          11 :             else if (!cpl::contains(setKnownColumnNames, pszColName))
     949             :             {
     950           5 :                 CPLError(CE_Warning, CPLE_AppDefined,
     951             :                          "'%s' is not a valid S102 feature attribute table "
     952             :                          "column name.",
     953             :                          pszColName);
     954             :             }
     955             :         }
     956           7 :         if (iRATIdField < 0)
     957             :         {
     958           1 :             CPLError(
     959             :                 CE_Failure, CPLE_AppDefined,
     960             :                 "Input raster attribute table lacks an integer 'id' field");
     961           1 :             return false;
     962             :         }
     963           6 :         const int nRATRowCount = poRAT->GetRowCount();
     964          11 :         for (int i = 0; i < nRATRowCount; ++i)
     965             :         {
     966           8 :             const int nID = poRAT->GetValueAsInt(i, iRATIdField);
     967           8 :             if (nID == 0)
     968             :             {
     969           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     970             :                          "id=0 is not allowed in input raster attribute table");
     971           3 :                 return false;
     972             :             }
     973           7 :             else if (nID < 0)
     974             :             {
     975           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     976             :                          "Negative id is not allowed in input raster attribute "
     977             :                          "table");
     978           1 :                 return false;
     979             :             }
     980           6 :             else if (cpl::contains(oSetRATId, nID))
     981             :             {
     982           1 :                 CPLError(
     983             :                     CE_Failure, CPLE_AppDefined,
     984             :                     "Several rows of input raster attribute table have id=%d",
     985             :                     nID);
     986           1 :                 return false;
     987             :             }
     988           5 :             oSetRATId.insert(nID);
     989             :         }
     990             :     }
     991             : 
     992          19 :     if (!((m_nVerticalDatum >= 1 && m_nVerticalDatum <= 30) ||
     993           0 :           m_nVerticalDatum == 44))
     994             :     {
     995           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     996             :                  "VERTICAL_DATUM=%d value is a valid S100 value but not "
     997             :                  "allowed in S102. Valid values are [1, 30] or 44",
     998             :                  m_nVerticalDatum);
     999             :     }
    1000             : 
    1001          19 :     if (!(m_nEPSGCode == 4326 || m_nEPSGCode == 5041 || m_nEPSGCode == 5042 ||
    1002          19 :           (m_nEPSGCode >= 32601 && m_nEPSGCode <= 32660) ||
    1003           1 :           (m_nEPSGCode >= 32701 && m_nEPSGCode <= 32760)))
    1004             :     {
    1005           1 :         CPLError(CE_Warning, CPLE_NotSupported,
    1006             :                  "The EPSG code of the CRS is %d. "
    1007             :                  "Only EPSG codes 4326, 5041, 5042, [32601, 32660], "
    1008             :                  "[32701, 32760] are officially supported. "
    1009             :                  "The dataset may not be recognized by other software",
    1010             :                  m_nEPSGCode);
    1011             :     }
    1012             : 
    1013          19 :     if (bAppendSubdataset)
    1014             :     {
    1015           6 :         GDALOpenInfo oOpenInfo(m_osDestFilename.c_str(), GA_ReadOnly);
    1016             :         auto poOriDS =
    1017           6 :             std::unique_ptr<GDALDataset>(S102Dataset::Open(&oOpenInfo));
    1018           3 :         if (!poOriDS)
    1019             :         {
    1020           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1021             :                      "%s is not a valid existing S102 dataset",
    1022             :                      m_osDestFilename.c_str());
    1023           1 :             return false;
    1024             :         }
    1025           2 :         const auto poOriSRS = poOriDS->GetSpatialRef();
    1026           2 :         if (!poOriSRS)
    1027             :         {
    1028             :             // shouldn't happen
    1029           0 :             return false;
    1030             :         }
    1031           2 :         if (!poOriSRS->IsSame(m_poSRS))
    1032             :         {
    1033           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1034             :                      "CRS of %s is not the same as the one of %s",
    1035           1 :                      m_osDestFilename.c_str(), m_poSrcDS->GetDescription());
    1036           1 :             return false;
    1037             :         }
    1038           1 :         poOriDS.reset();
    1039             : 
    1040           1 :         OGREnvelope sExtent;
    1041           1 :         if (m_poSrcDS->GetExtentWGS84LongLat(&sExtent) != OGRERR_NONE)
    1042             :         {
    1043           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1044             :                      "Cannot get dataset extent in WGS84 longitude/latitude");
    1045           0 :             return false;
    1046             :         }
    1047             : 
    1048           1 :         bool ret = OpenFileUpdateMode();
    1049           1 :         if (ret)
    1050             :         {
    1051           1 :             m_featureGroup.reset(
    1052             :                 H5_CHECK(H5Gopen(m_hdf5, "BathymetryCoverage")));
    1053             :         }
    1054             : 
    1055           1 :         ret = ret && m_featureGroup;
    1056           1 :         double dfNumInstances = 0;
    1057           1 :         ret = ret && GH5_FetchAttribute(m_featureGroup, "numInstances",
    1058             :                                         dfNumInstances, true);
    1059           1 :         if (ret && !(dfNumInstances >= 1 && dfNumInstances <= 99 &&
    1060           1 :                      std::round(dfNumInstances) == dfNumInstances))
    1061             :         {
    1062           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1063             :                      "Invalid value for numInstances");
    1064           0 :             ret = false;
    1065             :         }
    1066           1 :         else if (ret && dfNumInstances == 99)
    1067             :         {
    1068           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1069             :                      "Too many existing feature instances");
    1070           0 :             ret = false;
    1071             :         }
    1072             :         else
    1073             :         {
    1074           1 :             double dfMainVerticalDatum = 0;
    1075           1 :             ret = ret && GH5_FetchAttribute(m_hdf5, "verticalDatum",
    1076             :                                             dfMainVerticalDatum, true);
    1077             : 
    1078           1 :             const int newNumInstances = static_cast<int>(dfNumInstances) + 1;
    1079           1 :             ret = ret && GH5_WriteAttribute(m_featureGroup, "numInstances",
    1080             :                                             newNumInstances);
    1081           1 :             ret = ret && CreateFeatureInstanceGroup(CPLSPrintf(
    1082             :                              "BathymetryCoverage.%02d", newNumInstances));
    1083           1 :             ret = ret && WriteFIGGridRelatedParameters(m_featureInstanceGroup);
    1084           1 :             if (dfMainVerticalDatum != m_nVerticalDatum)
    1085             :             {
    1086           2 :                 ret = ret &&
    1087           1 :                       GH5_CreateAttribute(m_featureInstanceGroup,
    1088             :                                           "verticalDatumReference",
    1089           2 :                                           H5T_STD_U8LE) &&
    1090             :                       // s100VerticalDatum
    1091           1 :                       GH5_WriteAttribute(m_featureInstanceGroup,
    1092             :                                          "verticalDatumReference", 1);
    1093           1 :                 ret =
    1094           2 :                     ret && WriteVerticalDatum(m_featureInstanceGroup,
    1095           1 :                                               H5T_STD_U16LE, m_nVerticalDatum);
    1096             :             }
    1097             : 
    1098           1 :             ret = ret && WriteNumGRP(m_featureInstanceGroup, H5T_STD_U8LE, 1);
    1099             : 
    1100           1 :             ret = ret && CreateValuesGroup("Group_001");
    1101           1 :             ret = ret && WriteVarLengthStringValue(m_valuesGroup, "timePoint",
    1102             :                                                    "00010101T000000Z");
    1103           1 :             ret = ret && CopyValues(pfnProgress, pProgressData);
    1104             :         }
    1105             : 
    1106             :         // Update global bounding box
    1107           1 :         OGREnvelope sExistingExtent;
    1108           1 :         ret = ret && GH5_FetchAttribute(m_hdf5, "westBoundLongitude",
    1109             :                                         sExistingExtent.MinX, true);
    1110           1 :         ret = ret && GH5_FetchAttribute(m_hdf5, "southBoundLatitude",
    1111             :                                         sExistingExtent.MinY, true);
    1112           1 :         ret = ret && GH5_FetchAttribute(m_hdf5, "eastBoundLongitude",
    1113             :                                         sExistingExtent.MaxX, true);
    1114           1 :         ret = ret && GH5_FetchAttribute(m_hdf5, "northBoundLatitude",
    1115             :                                         sExistingExtent.MaxY, true);
    1116             : 
    1117           1 :         sExtent.Merge(sExistingExtent);
    1118           2 :         ret = ret &&
    1119           1 :               GH5_WriteAttribute(m_hdf5, "westBoundLongitude", sExtent.MinX);
    1120           2 :         ret = ret &&
    1121           1 :               GH5_WriteAttribute(m_hdf5, "southBoundLatitude", sExtent.MinY);
    1122           2 :         ret = ret &&
    1123           1 :               GH5_WriteAttribute(m_hdf5, "eastBoundLongitude", sExtent.MaxX);
    1124           2 :         ret = ret &&
    1125           1 :               GH5_WriteAttribute(m_hdf5, "northBoundLatitude", sExtent.MaxY);
    1126             : 
    1127           1 :         return Close() && ret;
    1128             :     }
    1129             :     else
    1130             :     {
    1131          16 :         bool ret = CreateFile();
    1132          16 :         ret = ret && WriteProductSpecification("INT.IHO.S-102.3.0.0");
    1133          16 :         ret = ret && WriteIssueDate();
    1134          16 :         ret = ret && WriteIssueTime(/* bAutogenerateFromCurrent = */ false);
    1135          16 :         ret = ret && WriteHorizontalCRS();
    1136          16 :         ret = ret && WriteTopLevelBoundingBox();
    1137          16 :         ret = ret && WriteVerticalCS(6498);           // Depth, metre, down
    1138          16 :         ret = ret && WriteVerticalCoordinateBase(2);  // verticalDatum
    1139             :         // s100VerticalDatum
    1140          16 :         ret = ret && WriteVerticalDatumReference(m_hdf5, 1);
    1141          16 :         ret =
    1142          16 :             ret && WriteVerticalDatum(m_hdf5, H5T_STD_U16LE, m_nVerticalDatum);
    1143             : 
    1144             :         // BathymetryCoverage
    1145          16 :         ret = ret && CreateFeatureGroup(FEATURE_TYPE);
    1146          16 :         ret = ret && WriteFeatureGroupAttributes(/* isQuality = */ false);
    1147          16 :         ret = ret && WriteAxisNames(m_featureGroup);
    1148             : 
    1149          16 :         ret = ret && CreateFeatureInstanceGroup("BathymetryCoverage.01");
    1150          16 :         ret = ret && WriteFIGGridRelatedParameters(m_featureInstanceGroup);
    1151          16 :         ret = ret && WriteNumGRP(m_featureInstanceGroup, H5T_STD_U8LE, 1);
    1152             : 
    1153          16 :         ret = ret && CreateValuesGroup("Group_001");
    1154             : 
    1155          16 :         ret = ret && WriteVarLengthStringValue(m_valuesGroup, "timePoint",
    1156             :                                                "00010101T000000Z");
    1157             : 
    1158             :         const double dfIntermediatePct =
    1159          16 :             m_poSrcDS->GetRasterCount() /
    1160          16 :             (m_poSrcDS->GetRasterCount() + (poQualityDS ? 1.0 : 0.0));
    1161             :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
    1162             :             pScaledProgressData(GDALCreateScaledProgress(0.0, dfIntermediatePct,
    1163             :                                                          pfnProgress,
    1164             :                                                          pProgressData),
    1165          16 :                                 GDALDestroyScaledProgress);
    1166          16 :         ret = ret && CopyValues(GDALScaledProgress, pScaledProgressData.get());
    1167             : 
    1168          16 :         if (poQualityDS)
    1169             :         {
    1170             :             // QualityOfBathymetryCoverage group
    1171           3 :             ret = ret && CreateFeatureGroup(QUALITY_FEATURE_TYPE);
    1172           3 :             ret = ret && WriteFeatureGroupAttributes(/* isQuality = */ true);
    1173           3 :             ret = ret && WriteAxisNames(m_featureGroup);
    1174           3 :             ret = ret && WriteFeatureAttributeTable(poRAT);
    1175             : 
    1176           6 :             ret = ret &&
    1177           3 :                   CreateFeatureInstanceGroup("QualityOfBathymetryCoverage.01");
    1178           3 :             ret = ret && WriteFIGGridRelatedParameters(m_featureInstanceGroup);
    1179           3 :             ret = ret && WriteNumGRP(m_featureInstanceGroup, H5T_STD_U8LE, 1);
    1180             : 
    1181           3 :             ret = ret && CreateValuesGroup("Group_001");
    1182           3 :             pScaledProgressData.reset(GDALCreateScaledProgress(
    1183             :                 dfIntermediatePct, 1.0, pfnProgress, pProgressData));
    1184           3 :             ret = ret && CopyQualityValues(poQualityDS.get(), oSetRATId,
    1185             :                                            GDALScaledProgress,
    1186             :                                            pScaledProgressData.get());
    1187             :         }
    1188             : 
    1189          16 :         ret = ret && CreateGroupF(poQualityDS != nullptr);
    1190             : 
    1191          16 :         return Close() && ret;
    1192             :     }
    1193             : }
    1194             : 
    1195             : /************************************************************************/
    1196             : /*              S102Creator::WriteFeatureGroupAttributes()              */
    1197             : /************************************************************************/
    1198             : 
    1199          18 : bool S102Creator::WriteFeatureGroupAttributes(bool isQuality)
    1200             : {
    1201          18 :     CPLAssert(m_featureGroup);
    1202             : 
    1203          18 :     bool ret = WriteCommonPointRule(m_featureGroup, 2);  // low
    1204          18 :     if (isQuality)
    1205             :     {
    1206             :         // Feature oriented Regular Grid
    1207           3 :         ret = ret && WriteDataCodingFormat(m_featureGroup, 9);
    1208             :     }
    1209             :     else
    1210             :     {
    1211          15 :         ret = ret && WriteDataCodingFormat(m_featureGroup, 2);  // Regular grid
    1212             :     }
    1213          18 :     ret = ret && WriteDataOffsetCode(m_featureGroup, 5);  // Center of cell
    1214          18 :     ret = ret && WriteDimension(m_featureGroup, 2);
    1215             :     const char *pszHorizontalPositionUncertainty =
    1216          18 :         m_aosOptions.FetchNameValue("HORIZONTAL_POSITION_UNCERTAINTY");
    1217          18 :     ret =
    1218          36 :         ret &&
    1219          18 :         WriteHorizontalPositionUncertainty(
    1220             :             m_featureGroup,
    1221           0 :             pszHorizontalPositionUncertainty &&
    1222           0 :                     pszHorizontalPositionUncertainty[0]
    1223           0 :                 ? static_cast<float>(CPLAtof(pszHorizontalPositionUncertainty))
    1224             :                 : -1.0f);
    1225             :     const char *pszVerticalUncertainty =
    1226          18 :         m_aosOptions.FetchNameValue("VERTICAL_UNCERTAINTY");
    1227          18 :     ret = ret && WriteVerticalUncertainty(
    1228             :                      m_featureGroup,
    1229           0 :                      pszVerticalUncertainty && pszVerticalUncertainty[0]
    1230           0 :                          ? static_cast<float>(CPLAtof(pszVerticalUncertainty))
    1231             :                          : -1.0f);
    1232          18 :     ret = ret && WriteInterpolationType(m_featureGroup, 1);  // Nearest neighbor
    1233          18 :     ret = ret && WriteNumInstances(m_featureGroup, H5T_STD_U8LE, 1);
    1234          36 :     ret = ret && WriteSequencingRuleScanDirection(m_featureGroup,
    1235          18 :                                                   m_poSRS->IsProjected()
    1236             :                                                       ? "Easting, Northing"
    1237             :                                                       : "Longitude, Latitude");
    1238          18 :     ret = ret && WriteSequencingRuleType(m_featureGroup, 1);  // Linear
    1239          18 :     return ret;
    1240             : }
    1241             : 
    1242             : /************************************************************************/
    1243             : /*              S102Creator::WriteFeatureAttributeTable()               */
    1244             : /************************************************************************/
    1245             : 
    1246           3 : bool S102Creator::WriteFeatureAttributeTable(
    1247             :     const GDALRasterAttributeTable *poRAT)
    1248             : {
    1249           3 :     CPLAssert(m_featureGroup);
    1250             : 
    1251           6 :     std::map<std::string_view, const char *> mapKnownColumns;
    1252          48 :     for (const auto &entry : gasFeatureAttributeTableMembers)
    1253          45 :         mapKnownColumns[entry.pszName] = entry.pszType;
    1254             : 
    1255           3 :     const int nColCount = poRAT->GetColumnCount();
    1256             : 
    1257           3 :     size_t nCompoundSize = 0;
    1258           3 :     size_t nMEMCompoundSize = 0;
    1259          16 :     for (int i = 0; i < nColCount; ++i)
    1260             :     {
    1261          13 :         const char *pszColName = poRAT->GetNameOfCol(i);
    1262          13 :         const auto iter = mapKnownColumns.find(pszColName);
    1263          13 :         size_t nMemberSize = sizeof(char *);
    1264          13 :         if (iter != mapKnownColumns.end())
    1265             :         {
    1266           9 :             const char *pszType = iter->second;
    1267           9 :             if (strcmp(pszType, "uint8") == 0 ||
    1268           8 :                 strcmp(pszType, "boolean") == 0 ||
    1269           7 :                 strcmp(pszType, "enumeration") == 0)
    1270             :             {
    1271           3 :                 nMemberSize = sizeof(uint8_t);
    1272             :             }
    1273           6 :             else if (strcmp(pszType, "uint32") == 0)
    1274             :             {
    1275           3 :                 nMemberSize = sizeof(uint32_t);
    1276             :             }
    1277           3 :             else if (strcmp(pszType, "float32") == 0)
    1278             :             {
    1279           1 :                 nMemberSize = sizeof(float);
    1280             :             }
    1281           2 :             else if (strcmp(pszType, "string") == 0 ||
    1282           1 :                      strcmp(pszType, "date") == 0)
    1283             :             {
    1284           2 :                 nMemberSize = sizeof(char *);
    1285             :             }
    1286             :             else
    1287             :             {
    1288           0 :                 CPLAssert(false);
    1289             :             }
    1290             :         }
    1291             :         else
    1292             :         {
    1293           4 :             GDALRATFieldType eType = poRAT->GetTypeOfCol(i);
    1294           4 :             switch (eType)
    1295             :             {
    1296           1 :                 case GFT_Integer:
    1297           1 :                     nMemberSize = sizeof(int32_t);
    1298           1 :                     break;
    1299           1 :                 case GFT_Real:
    1300           1 :                     nMemberSize = sizeof(double);
    1301           1 :                     break;
    1302           1 :                 case GFT_Boolean:
    1303           1 :                     nMemberSize = sizeof(uint8_t);
    1304           1 :                     break;
    1305           1 :                 case GFT_String:
    1306             :                 case GFT_DateTime:
    1307             :                 case GFT_WKBGeometry:
    1308           1 :                     nMemberSize = sizeof(char *);
    1309           1 :                     break;
    1310             :             }
    1311             :         }
    1312          13 :         nCompoundSize += nMemberSize;
    1313          13 :         if ((nMEMCompoundSize % nMemberSize) != 0)
    1314           2 :             nMEMCompoundSize += nMemberSize - (nMEMCompoundSize % nMemberSize);
    1315          13 :         nMEMCompoundSize += nMemberSize;
    1316             :     }
    1317             : 
    1318             :     GH5_HIDTypeHolder hDataType(
    1319           6 :         H5_CHECK(H5Tcreate(H5T_COMPOUND, nCompoundSize)));
    1320             :     GH5_HIDTypeHolder hDataTypeMEM(
    1321           6 :         H5_CHECK(H5Tcreate(H5T_COMPOUND, nMEMCompoundSize)));
    1322           6 :     GH5_HIDTypeHolder hVarLengthType(H5_CHECK(H5Tcopy(H5T_C_S1)));
    1323           6 :     bool bRet = hDataType && hDataTypeMEM && hVarLengthType &&
    1324           9 :                 H5_CHECK(H5Tset_size(hVarLengthType, H5T_VARIABLE)) >= 0 &&
    1325           3 :                 H5_CHECK(H5Tset_strpad(hVarLengthType, H5T_STR_NULLTERM)) >= 0;
    1326             : 
    1327           6 :     GH5_HIDTypeHolder hEnumType;
    1328           6 :     std::vector<const char *> apszTypes;
    1329             : 
    1330           3 :     size_t nOffset = 0;
    1331           3 :     size_t nMEMOffset = 0;
    1332           6 :     std::vector<size_t> anMEMOffsets;
    1333          16 :     for (int i = 0; i < nColCount && bRet; ++i)
    1334             :     {
    1335          13 :         const char *pszColName = poRAT->GetNameOfCol(i);
    1336          13 :         const auto iter = mapKnownColumns.find(pszColName);
    1337          13 :         hid_t hMemberType = hVarLengthType.get();
    1338          13 :         hid_t hMemberNativeType = hVarLengthType.get();
    1339          13 :         if (iter != mapKnownColumns.end())
    1340             :         {
    1341           9 :             const char *pszType = iter->second;
    1342           9 :             if (strcmp(pszType, "uint8") == 0 ||
    1343           8 :                 strcmp(pszType, "boolean") == 0)
    1344             :             {
    1345           2 :                 hMemberType = H5T_STD_U8LE;
    1346           2 :                 hMemberNativeType = H5T_NATIVE_UCHAR;
    1347             :             }
    1348           7 :             else if (strcmp(pszType, "uint32") == 0)
    1349             :             {
    1350           3 :                 hMemberType = H5T_STD_U32LE;
    1351           3 :                 hMemberNativeType = H5T_NATIVE_UINT;
    1352             :             }
    1353           4 :             else if (strcmp(pszType, "float32") == 0)
    1354             :             {
    1355           1 :                 hMemberType = H5T_IEEE_F32LE;
    1356           1 :                 hMemberNativeType = H5T_NATIVE_FLOAT;
    1357             :             }
    1358           3 :             else if (strcmp(pszType, "string") == 0 ||
    1359           2 :                      strcmp(pszType, "date") == 0)
    1360             :             {
    1361           2 :                 hMemberType = hVarLengthType.get();
    1362           2 :                 hMemberNativeType = hVarLengthType.get();
    1363             :             }
    1364           1 :             else if (strcmp(pszType, "enumeration") == 0 &&
    1365           1 :                      strcmp(pszColName,
    1366             :                             "typeOfBathymetricEstimationUncertainty") == 0)
    1367             :             {
    1368           1 :                 hEnumType.reset(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1369           1 :                 bRet = hEnumType;
    1370           1 :                 if (bRet)
    1371             :                 {
    1372             :                     uint8_t val;
    1373           1 :                     val = 1;
    1374           2 :                     bRet = bRet &&
    1375           1 :                            H5_CHECK(H5Tenum_insert(
    1376             :                                hEnumType, "rawStandardDeviation", &val)) >= 0;
    1377           1 :                     val = 2;
    1378           2 :                     bRet = bRet &&
    1379           1 :                            H5_CHECK(H5Tenum_insert(
    1380             :                                hEnumType, "cUBEStandardDeviation", &val)) >= 0;
    1381           1 :                     val = 3;
    1382           2 :                     bRet = bRet &&
    1383           1 :                            H5_CHECK(H5Tenum_insert(
    1384             :                                hEnumType, "productUncertainty", &val)) >= 0;
    1385           1 :                     val = 4;
    1386           1 :                     bRet = bRet && H5_CHECK(H5Tenum_insert(
    1387             :                                        hEnumType, "historicalStandardDeviation",
    1388             :                                        &val)) >= 0;
    1389             : 
    1390           1 :                     hMemberType = hEnumType.get();
    1391           1 :                     hMemberNativeType = hEnumType.get();
    1392           1 :                 }
    1393             :             }
    1394             :             else
    1395             :             {
    1396           0 :                 CPLAssert(false);
    1397             :             }
    1398           9 :             apszTypes.push_back(pszType);
    1399             :         }
    1400             :         else
    1401             :         {
    1402           4 :             GDALRATFieldType eType = poRAT->GetTypeOfCol(i);
    1403           4 :             switch (eType)
    1404             :             {
    1405           1 :                 case GFT_Integer:
    1406           1 :                     hMemberType = H5T_STD_I32LE;
    1407           1 :                     hMemberNativeType = H5T_NATIVE_INT;
    1408           1 :                     apszTypes.push_back("int32");
    1409           1 :                     break;
    1410           1 :                 case GFT_Real:
    1411           1 :                     hMemberType = H5T_IEEE_F64LE;
    1412           1 :                     hMemberNativeType = H5T_NATIVE_DOUBLE;
    1413           1 :                     apszTypes.push_back("float64");
    1414           1 :                     break;
    1415           1 :                 case GFT_Boolean:
    1416           1 :                     hMemberType = H5T_STD_U8LE;
    1417           1 :                     hMemberNativeType = H5T_NATIVE_UCHAR;
    1418           1 :                     apszTypes.push_back("boolean");
    1419           1 :                     break;
    1420           1 :                 case GFT_String:
    1421             :                 case GFT_DateTime:
    1422             :                 case GFT_WKBGeometry:
    1423           1 :                     apszTypes.push_back("string");
    1424           1 :                     break;
    1425             :             }
    1426             :         }
    1427             : 
    1428          13 :         CPLAssert(H5Tget_size(hMemberType) == H5Tget_size(hMemberNativeType));
    1429             : 
    1430          13 :         bRet = bRet && H5_CHECK(H5Tinsert(hDataType, pszColName, nOffset,
    1431             :                                           hMemberType)) >= 0;
    1432             : 
    1433          13 :         const size_t nMemberSize = H5Tget_size(hMemberType);
    1434          13 :         if ((nMEMOffset % nMemberSize) != 0)
    1435           2 :             nMEMOffset += nMemberSize - (nMEMOffset % nMemberSize);
    1436          13 :         anMEMOffsets.push_back(nMEMOffset);
    1437          13 :         bRet = bRet && H5_CHECK(H5Tinsert(hDataTypeMEM, pszColName, nMEMOffset,
    1438             :                                           hMemberNativeType)) >= 0;
    1439          13 :         nOffset += nMemberSize;
    1440          13 :         nMEMOffset += nMemberSize;
    1441             :     }
    1442           3 :     CPLAssert(nOffset == nCompoundSize);
    1443           3 :     CPLAssert(nMEMOffset == nMEMCompoundSize);
    1444             : 
    1445           3 :     CPLAssert(apszTypes.size() == static_cast<size_t>(nColCount));
    1446             : 
    1447           3 :     const int nRowCount = poRAT->GetRowCount();
    1448           3 :     hsize_t dims[] = {static_cast<hsize_t>(nRowCount)};
    1449           6 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(1, dims, nullptr)));
    1450           3 :     bRet = bRet && hDataSpace;
    1451           6 :     GH5_HIDDatasetHolder hDatasetID;
    1452           6 :     GH5_HIDSpaceHolder hFileSpace;
    1453           6 :     GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
    1454           3 :     bRet = bRet && hParams;
    1455           3 :     if (bRet)
    1456             :     {
    1457           3 :         bRet = H5_CHECK(H5Pset_layout(hParams, H5D_CHUNKED)) >= 0;
    1458           3 :         hsize_t chunk_size[] = {static_cast<hsize_t>(1)};
    1459           3 :         bRet = bRet && H5_CHECK(H5Pset_chunk(hParams, 1, chunk_size)) >= 0;
    1460           3 :         hDatasetID.reset(
    1461             :             H5_CHECK(H5Dcreate(m_featureGroup, "featureAttributeTable",
    1462             :                                hDataType, hDataSpace, hParams)));
    1463           3 :         bRet = bRet && hDatasetID;
    1464             :     }
    1465           3 :     if (bRet)
    1466             :     {
    1467           3 :         hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
    1468           3 :         bRet = hFileSpace;
    1469             :     }
    1470             : 
    1471           3 :     hsize_t count[] = {1};
    1472           6 :     GH5_HIDSpaceHolder hMemSpace(H5_CHECK(H5Screate_simple(1, count, nullptr)));
    1473           3 :     bRet = bRet && hMemSpace;
    1474             : 
    1475           6 :     std::vector<GByte> abyBuffer(nMEMCompoundSize);
    1476           3 :     std::vector<std::string> asBuffers(nColCount);
    1477           7 :     for (int iRow = 0; iRow < nRowCount && bRet; ++iRow)
    1478             :     {
    1479          24 :         for (int iCol = 0; iCol < nColCount && bRet; ++iCol)
    1480             :         {
    1481          20 :             const char *const pszType = apszTypes[iCol];
    1482          20 :             GByte *const pabyDst = abyBuffer.data() + anMEMOffsets[iCol];
    1483          20 :             if (strcmp(pszType, "uint8") == 0 ||
    1484          18 :                 strcmp(pszType, "boolean") == 0 ||
    1485          15 :                 strcmp(pszType, "enumeration") == 0)
    1486             :             {
    1487             :                 const uint8_t nVal =
    1488           7 :                     static_cast<uint8_t>(poRAT->GetValueAsInt(iRow, iCol));
    1489           7 :                 *pabyDst = nVal;
    1490             :             }
    1491          13 :             else if (strcmp(pszType, "int32") == 0 ||
    1492          12 :                      strcmp(pszType, "uint32") == 0)
    1493             :             {
    1494           5 :                 const int nVal = poRAT->GetValueAsInt(iRow, iCol);
    1495           5 :                 memcpy(pabyDst, &nVal, sizeof(nVal));
    1496             :             }
    1497           8 :             else if (strcmp(pszType, "float32") == 0)
    1498             :             {
    1499             :                 const float fVal =
    1500           2 :                     static_cast<float>(poRAT->GetValueAsDouble(iRow, iCol));
    1501           2 :                 memcpy(pabyDst, &fVal, sizeof(fVal));
    1502             :             }
    1503           6 :             else if (strcmp(pszType, "float64") == 0)
    1504             :             {
    1505           1 :                 const double dfVal = poRAT->GetValueAsDouble(iRow, iCol);
    1506           1 :                 memcpy(pabyDst, &dfVal, sizeof(dfVal));
    1507             :             }
    1508           5 :             else if (strcmp(pszType, "string") == 0)
    1509             :             {
    1510           3 :                 asBuffers[iCol] = poRAT->GetValueAsString(iRow, iCol);
    1511           3 :                 const char *pszStr = asBuffers[iCol].c_str();
    1512           3 :                 memcpy(pabyDst, &pszStr, sizeof(pszStr));
    1513             :             }
    1514           2 :             else if (strcmp(pszType, "date") == 0)
    1515             :             {
    1516           2 :                 asBuffers[iCol] = poRAT->GetValueAsString(iRow, iCol);
    1517           2 :                 if (asBuffers[iCol].size() != 8)
    1518             :                 {
    1519             :                     OGRField sField;
    1520           1 :                     if (OGRParseDate(asBuffers[iCol].c_str(), &sField, 0))
    1521             :                     {
    1522           2 :                         asBuffers[iCol] = CPLString().Printf(
    1523           1 :                             "%04d%02d%02d", sField.Date.Year, sField.Date.Month,
    1524           1 :                             sField.Date.Day);
    1525             :                     }
    1526             :                 }
    1527           2 :                 const char *pszStr = asBuffers[iCol].c_str();
    1528           2 :                 memcpy(pabyDst, &pszStr, sizeof(pszStr));
    1529             :             }
    1530             :             else
    1531             :             {
    1532           0 :                 CPLAssert(false);
    1533             :             }
    1534             :         }
    1535             : 
    1536           4 :         H5OFFSET_TYPE offset[] = {static_cast<H5OFFSET_TYPE>(iRow)};
    1537           4 :         bRet =
    1538           4 :             bRet &&
    1539           4 :             H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    1540           8 :                                          nullptr, count, nullptr)) >= 0 &&
    1541           4 :             H5_CHECK(H5Dwrite(hDatasetID, hDataTypeMEM, hMemSpace, hFileSpace,
    1542             :                               H5P_DEFAULT, abyBuffer.data())) >= 0;
    1543             :     }
    1544             : 
    1545           6 :     return bRet;
    1546             : }
    1547             : 
    1548             : /************************************************************************/
    1549             : /*                     S102Creator::CreateGroupF()                      */
    1550             : /************************************************************************/
    1551             : 
    1552             : // Per S-102 v3.0 spec
    1553             : #define MIN_DEPTH_VALUE -14
    1554             : #define MAX_DEPTH_VALUE 11050
    1555             : 
    1556             : #define STRINGIFY(x) #x
    1557             : #define XSTRINGIFY(x) STRINGIFY(x)
    1558             : 
    1559          15 : bool S102Creator::CreateGroupF(bool hasQualityOfBathymetryCoverage)
    1560             : {
    1561          15 :     bool ret = S100BaseWriter::CreateGroupF();
    1562             : 
    1563          15 :     CPLStringList aosFeatureCodes;
    1564          15 :     aosFeatureCodes.push_back(FEATURE_TYPE);
    1565          15 :     if (hasQualityOfBathymetryCoverage)
    1566           3 :         aosFeatureCodes.push_back(QUALITY_FEATURE_TYPE);
    1567          30 :     ret = ret && WriteOneDimensionalVarLengthStringArray(
    1568          15 :                      m_GroupF, "featureCode", aosFeatureCodes.List());
    1569             : 
    1570             :     {
    1571             :         std::vector<std::array<const char *, GROUP_F_DATASET_FIELD_COUNT>> rows{
    1572             :             {"depth", "depth", "metres", "1000000", "H5T_FLOAT",
    1573             :              XSTRINGIFY(MIN_DEPTH_VALUE), XSTRINGIFY(MAX_DEPTH_VALUE),
    1574             :              "closedInterval"},
    1575             :             {"uncertainty", "uncertainty", "metres", "1000000", "H5T_FLOAT",
    1576          15 :              "0", "", "geSemiInterval"}};
    1577          15 :         rows.resize(m_poSrcDS->GetRasterCount());
    1578          15 :         ret = ret && WriteGroupFDataset(FEATURE_TYPE, rows);
    1579             :     }
    1580             :     {
    1581             :         std::vector<std::array<const char *, GROUP_F_DATASET_FIELD_COUNT>> rows{
    1582          15 :             {"iD", "ID", "", "0", "H5T_INTEGER", "1", "", "geSemiInterval"}};
    1583          15 :         ret = ret && WriteGroupFDataset(QUALITY_FEATURE_TYPE, rows);
    1584             :     }
    1585             : 
    1586          30 :     return ret;
    1587             : }
    1588             : 
    1589             : /************************************************************************/
    1590             : /*                      S102Creator::CopyValues()                       */
    1591             : /************************************************************************/
    1592             : 
    1593          16 : bool S102Creator::CopyValues(GDALProgressFunc pfnProgress, void *pProgressData)
    1594             : {
    1595          16 :     CPLAssert(m_valuesGroup.get() >= 0);
    1596             : 
    1597          16 :     const int nYSize = m_poSrcDS->GetRasterYSize();
    1598          16 :     const int nXSize = m_poSrcDS->GetRasterXSize();
    1599             : 
    1600          16 :     hsize_t dims[] = {static_cast<hsize_t>(nYSize),
    1601          16 :                       static_cast<hsize_t>(nXSize)};
    1602             : 
    1603          32 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(2, dims, nullptr)));
    1604          16 :     bool bRet = hDataSpace;
    1605             : 
    1606             :     const bool bDeflate =
    1607          16 :         EQUAL(m_aosOptions.FetchNameValueDef("COMPRESS", "DEFLATE"), "DEFLATE");
    1608             :     const int nCompressionLevel =
    1609          16 :         atoi(m_aosOptions.FetchNameValueDef("ZLEVEL", "6"));
    1610             :     const int nBlockSize =
    1611          16 :         std::min(4096, std::max(100, atoi(m_aosOptions.FetchNameValueDef(
    1612          16 :                                          "BLOCK_SIZE", "100"))));
    1613          16 :     const int nBlockXSize = std::min(nXSize, nBlockSize);
    1614          16 :     const int nBlockYSize = std::min(nYSize, nBlockSize);
    1615          16 :     const float fNoDataValue = NODATA;
    1616          16 :     const int nComponents = m_poSrcDS->GetRasterCount();
    1617             : 
    1618             :     GH5_HIDTypeHolder hDataType(
    1619          32 :         H5_CHECK(H5Tcreate(H5T_COMPOUND, nComponents * sizeof(float))));
    1620          16 :     bRet = bRet && hDataType &&
    1621          35 :            H5_CHECK(H5Tinsert(hDataType, "depth", 0, H5T_IEEE_F32LE)) >= 0 &&
    1622           3 :            (nComponents == 1 ||
    1623           3 :             H5_CHECK(H5Tinsert(hDataType, "uncertainty", sizeof(float),
    1624             :                                H5T_IEEE_F32LE)) >= 0);
    1625             : 
    1626          16 :     hsize_t chunk_size[] = {static_cast<hsize_t>(nBlockYSize),
    1627          16 :                             static_cast<hsize_t>(nBlockXSize)};
    1628             : 
    1629          16 :     const float afFillValue[] = {fNoDataValue, fNoDataValue};
    1630          32 :     GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
    1631          16 :     bRet = bRet && hParams &&
    1632          16 :            H5_CHECK(H5Pset_fill_time(hParams, H5D_FILL_TIME_ALLOC)) >= 0 &&
    1633          16 :            H5_CHECK(H5Pset_fill_value(hParams, hDataType, afFillValue)) >= 0 &&
    1634          48 :            H5_CHECK(H5Pset_layout(hParams, H5D_CHUNKED)) >= 0 &&
    1635          16 :            H5_CHECK(H5Pset_chunk(hParams, 2, chunk_size)) >= 0;
    1636             : 
    1637          16 :     if (bRet && bDeflate)
    1638             :     {
    1639          15 :         bRet = H5_CHECK(H5Pset_deflate(hParams, nCompressionLevel)) >= 0;
    1640             :     }
    1641             : 
    1642          32 :     GH5_HIDDatasetHolder hDatasetID;
    1643          16 :     if (bRet)
    1644             :     {
    1645          16 :         hDatasetID.reset(H5_CHECK(H5Dcreate(m_valuesGroup, "values", hDataType,
    1646             :                                             hDataSpace, hParams)));
    1647          16 :         bRet = hDatasetID;
    1648             :     }
    1649             : 
    1650          32 :     GH5_HIDSpaceHolder hFileSpace;
    1651          16 :     if (bRet)
    1652             :     {
    1653          16 :         hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
    1654          16 :         bRet = hFileSpace;
    1655             :     }
    1656             : 
    1657          16 :     const int nYBlocks = static_cast<int>(DIV_ROUND_UP(nYSize, nBlockYSize));
    1658          16 :     const int nXBlocks = static_cast<int>(DIV_ROUND_UP(nXSize, nBlockXSize));
    1659          16 :     std::vector<float> afValues(static_cast<size_t>(nBlockYSize) * nBlockXSize *
    1660          16 :                                 nComponents);
    1661          16 :     const bool bReverseY = m_gt.yscale < 0;
    1662             : 
    1663          16 :     float fMinDepth = std::numeric_limits<float>::infinity();
    1664          16 :     float fMaxDepth = -std::numeric_limits<float>::infinity();
    1665          16 :     float fMinUncertainty = std::numeric_limits<float>::infinity();
    1666          16 :     float fMaxUncertainty = -std::numeric_limits<float>::infinity();
    1667             : 
    1668          16 :     int bHasNoDataBand1 = FALSE;
    1669             :     const char *pszFirstBandDesc =
    1670          16 :         m_poSrcDS->GetRasterBand(1)->GetDescription();
    1671          16 :     const float fMulFactor =
    1672          16 :         EQUAL(pszFirstBandDesc, "elevation") ? -1.0f : 1.0f;
    1673          16 :     if (fMulFactor < 0.0f)
    1674             :     {
    1675           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1676             :                  "Automatically convert from elevation to depth by negating "
    1677             :                  "elevation values");
    1678             :     }
    1679             :     const double dfSrcNoDataBand1 =
    1680          16 :         m_poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoDataBand1);
    1681          16 :     const float fSrcNoDataBand1 = static_cast<float>(dfSrcNoDataBand1);
    1682          16 :     int bHasNoDataBand2 = FALSE;
    1683             :     const double dfSrcNoDataBand2 =
    1684             :         nComponents == 2
    1685          16 :             ? m_poSrcDS->GetRasterBand(2)->GetNoDataValue(&bHasNoDataBand2)
    1686          16 :             : 0.0;
    1687          16 :     const float fSrcNoDataBand2 = static_cast<float>(dfSrcNoDataBand2);
    1688             : 
    1689          43 :     for (int iY = 0; iY < nYBlocks && bRet; iY++)
    1690             :     {
    1691             :         const int nSrcYOff = bReverseY
    1692          27 :                                  ? std::max(0, nYSize - (iY + 1) * nBlockYSize)
    1693          19 :                                  : iY * nBlockYSize;
    1694          27 :         const int nReqCountY = std::min(nBlockYSize, nYSize - iY * nBlockYSize);
    1695         186 :         for (int iX = 0; iX < nXBlocks && bRet; iX++)
    1696             :         {
    1697             :             const int nReqCountX =
    1698         159 :                 std::min(nBlockXSize, nXSize - iX * nBlockXSize);
    1699             : 
    1700         159 :             bRet =
    1701         159 :                 m_poSrcDS->RasterIO(
    1702         159 :                     GF_Read, iX * nBlockXSize, nSrcYOff, nReqCountX, nReqCountY,
    1703           8 :                     bReverseY ? afValues.data() +
    1704           8 :                                     (nReqCountY - 1) * nReqCountX * nComponents
    1705         151 :                               : afValues.data(),
    1706             :                     nReqCountX, nReqCountY, GDT_Float32, nComponents, nullptr,
    1707         159 :                     static_cast<int>(sizeof(float)) * nComponents,
    1708             :                     bReverseY ? -static_cast<GPtrDiff_t>(sizeof(float)) *
    1709           8 :                                     nComponents * nReqCountX
    1710             :                               : 0,
    1711             :                     sizeof(float), nullptr) == CE_None;
    1712             : 
    1713         159 :             if (bRet)
    1714             :             {
    1715     1441810 :                 for (size_t i = 0;
    1716     1441810 :                      i < static_cast<size_t>(nReqCountY) * nReqCountX; i++)
    1717             :                 {
    1718             :                     {
    1719     1441650 :                         float fVal = afValues[i * nComponents];
    1720     2883290 :                         if ((bHasNoDataBand1 && fVal == fSrcNoDataBand1) ||
    1721     1441650 :                             std::isnan(fVal))
    1722             :                         {
    1723           8 :                             afValues[i * nComponents] = fNoDataValue;
    1724             :                         }
    1725             :                         else
    1726             :                         {
    1727     1441640 :                             fVal *= fMulFactor;
    1728     1441640 :                             afValues[i * nComponents] = fVal;
    1729     1441640 :                             fMinDepth = std::min(fMinDepth, fVal);
    1730     1441640 :                             fMaxDepth = std::max(fMaxDepth, fVal);
    1731             :                         }
    1732             :                     }
    1733     1441650 :                     if (nComponents == 2)
    1734             :                     {
    1735     1440010 :                         const float fVal = afValues[i * nComponents + 1];
    1736     2880020 :                         if ((bHasNoDataBand2 && fVal == fSrcNoDataBand2) ||
    1737     1440010 :                             std::isnan(fVal))
    1738             :                         {
    1739           1 :                             afValues[i * nComponents + 1] = fNoDataValue;
    1740             :                         }
    1741             :                         else
    1742             :                         {
    1743     1440010 :                             fMinUncertainty = std::min(fMinUncertainty, fVal);
    1744     1440010 :                             fMaxUncertainty = std::max(fMaxUncertainty, fVal);
    1745             :                         }
    1746             :                     }
    1747             :                 }
    1748             :             }
    1749             : 
    1750             :             H5OFFSET_TYPE offset[] = {
    1751         159 :                 static_cast<H5OFFSET_TYPE>(iY) *
    1752         159 :                     static_cast<H5OFFSET_TYPE>(nBlockYSize),
    1753         159 :                 static_cast<H5OFFSET_TYPE>(iX) *
    1754         159 :                     static_cast<H5OFFSET_TYPE>(nBlockXSize)};
    1755         159 :             hsize_t count[2] = {static_cast<hsize_t>(nReqCountY),
    1756         159 :                                 static_cast<hsize_t>(nReqCountX)};
    1757             :             GH5_HIDSpaceHolder hMemSpace(
    1758         159 :                 H5_CHECK(H5Screate_simple(2, count, nullptr)));
    1759         159 :             bRet =
    1760         159 :                 bRet &&
    1761         159 :                 H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    1762         159 :                                              nullptr, count, nullptr)) >= 0 &&
    1763         159 :                 hMemSpace &&
    1764         159 :                 H5_CHECK(H5Dwrite(hDatasetID, hDataType, hMemSpace, hFileSpace,
    1765         318 :                                   H5P_DEFAULT, afValues.data())) >= 0 &&
    1766         159 :                 pfnProgress((static_cast<double>(iY) * nXBlocks + iX + 1) /
    1767         159 :                                 (static_cast<double>(nXBlocks) * nYBlocks),
    1768             :                             "", pProgressData) != 0;
    1769             :         }
    1770             :     }
    1771             : 
    1772          16 :     if (fMinDepth > fMaxDepth)
    1773             :     {
    1774           0 :         fMinDepth = fMaxDepth = fNoDataValue;
    1775             :     }
    1776          16 :     else if (!(fMinDepth >= MIN_DEPTH_VALUE && fMaxDepth <= MAX_DEPTH_VALUE))
    1777             :     {
    1778           2 :         CPLError(CE_Warning, CPLE_AppDefined,
    1779             :                  "Range of depth in the dataset is [%f, %f] whereas the "
    1780             :                  "allowed range is [%d, %d]",
    1781             :                  fMinDepth, fMaxDepth, MIN_DEPTH_VALUE, MAX_DEPTH_VALUE);
    1782             :     }
    1783             : 
    1784          16 :     if (fMinUncertainty > fMaxUncertainty)
    1785             :     {
    1786          13 :         fMinUncertainty = fMaxUncertainty = fNoDataValue;
    1787             :     }
    1788           3 :     else if (fMinUncertainty < 0)
    1789             :     {
    1790           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1791             :                  "Negative uncertainty value found, which is not allowed");
    1792             :     }
    1793             : 
    1794          16 :     return bRet &&
    1795          16 :            WriteFloat32Value(m_valuesGroup, "minimumDepth", fMinDepth) &&
    1796          16 :            WriteFloat32Value(m_valuesGroup, "maximumDepth", fMaxDepth) &&
    1797          16 :            WriteFloat32Value(m_valuesGroup, "minimumUncertainty",
    1798          32 :                              fMinUncertainty) &&
    1799          16 :            WriteFloat32Value(m_valuesGroup, "maximumUncertainty",
    1800          32 :                              fMaxUncertainty);
    1801             : }
    1802             : 
    1803             : /************************************************************************/
    1804             : /*                   S102Creator::CopyQualityValues()                   */
    1805             : /************************************************************************/
    1806             : 
    1807           3 : bool S102Creator::CopyQualityValues(GDALDataset *poQualityDS,
    1808             :                                     const std::set<int> &oSetRATId,
    1809             :                                     GDALProgressFunc pfnProgress,
    1810             :                                     void *pProgressData)
    1811             : {
    1812           3 :     CPLAssert(m_valuesGroup.get() >= 0);
    1813             : 
    1814           3 :     const int nYSize = poQualityDS->GetRasterYSize();
    1815           3 :     const int nXSize = poQualityDS->GetRasterXSize();
    1816             : 
    1817           3 :     hsize_t dims[] = {static_cast<hsize_t>(nYSize),
    1818           3 :                       static_cast<hsize_t>(nXSize)};
    1819             : 
    1820           6 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(2, dims, nullptr)));
    1821           3 :     bool bRet = hDataSpace;
    1822             : 
    1823             :     const bool bDeflate =
    1824           3 :         EQUAL(m_aosOptions.FetchNameValueDef("COMPRESS", "DEFLATE"), "DEFLATE");
    1825             :     const int nCompressionLevel =
    1826           3 :         atoi(m_aosOptions.FetchNameValueDef("ZLEVEL", "6"));
    1827             :     const int nBlockSize =
    1828           3 :         std::min(4096, std::max(100, atoi(m_aosOptions.FetchNameValueDef(
    1829           3 :                                          "BLOCK_SIZE", "100"))));
    1830           3 :     const int nBlockXSize = std::min(nXSize, nBlockSize);
    1831           3 :     const int nBlockYSize = std::min(nYSize, nBlockSize);
    1832           3 :     constexpr uint32_t nNoDataValue = 0;
    1833             : 
    1834           3 :     hsize_t chunk_size[] = {static_cast<hsize_t>(nBlockYSize),
    1835           3 :                             static_cast<hsize_t>(nBlockXSize)};
    1836             : 
    1837           6 :     GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
    1838           3 :     bRet = bRet && hParams &&
    1839           3 :            H5_CHECK(H5Pset_fill_time(hParams, H5D_FILL_TIME_ALLOC)) >= 0 &&
    1840           3 :            H5_CHECK(H5Pset_fill_value(hParams, H5T_STD_U32LE, &nNoDataValue)) >=
    1841           3 :                0 &&
    1842           9 :            H5_CHECK(H5Pset_layout(hParams, H5D_CHUNKED)) >= 0 &&
    1843           3 :            H5_CHECK(H5Pset_chunk(hParams, 2, chunk_size)) >= 0;
    1844             : 
    1845           3 :     if (bRet && bDeflate)
    1846             :     {
    1847           3 :         bRet = H5_CHECK(H5Pset_deflate(hParams, nCompressionLevel)) >= 0;
    1848             :     }
    1849             : 
    1850           6 :     GH5_HIDDatasetHolder hDatasetID;
    1851           3 :     if (bRet)
    1852             :     {
    1853           3 :         hDatasetID.reset(H5_CHECK(H5Dcreate(
    1854             :             m_valuesGroup, "values", H5T_STD_U32LE, hDataSpace, hParams)));
    1855           3 :         bRet = hDatasetID;
    1856             :     }
    1857             : 
    1858           6 :     GH5_HIDSpaceHolder hFileSpace(H5_CHECK(H5Dget_space(hDatasetID)));
    1859           3 :     bRet = bRet && hFileSpace;
    1860             : 
    1861           3 :     const int nYBlocks = static_cast<int>(DIV_ROUND_UP(nYSize, nBlockYSize));
    1862           3 :     const int nXBlocks = static_cast<int>(DIV_ROUND_UP(nXSize, nBlockXSize));
    1863           3 :     std::vector<uint32_t> anValues(static_cast<size_t>(nBlockYSize) *
    1864           6 :                                    nBlockXSize);
    1865           3 :     const bool bReverseY = m_gt.yscale < 0;
    1866             : 
    1867           3 :     int bHasSrcNoData = FALSE;
    1868             :     const double dfSrcNoData =
    1869           3 :         poQualityDS->GetRasterBand(1)->GetNoDataValue(&bHasSrcNoData);
    1870           3 :     const uint32_t nSrcNoData = static_cast<uint32_t>(dfSrcNoData);
    1871             : 
    1872           3 :     std::set<int> oSetRATIdCopy(oSetRATId);
    1873           6 :     for (int iY = 0; iY < nYBlocks && bRet; iY++)
    1874             :     {
    1875             :         const int nSrcYOff = bReverseY
    1876           3 :                                  ? std::max(0, nYSize - (iY + 1) * nBlockYSize)
    1877           3 :                                  : iY * nBlockYSize;
    1878           3 :         const int nReqCountY = std::min(nBlockYSize, nYSize - iY * nBlockYSize);
    1879           6 :         for (int iX = 0; iX < nXBlocks && bRet; iX++)
    1880             :         {
    1881             :             const int nReqCountX =
    1882           3 :                 std::min(nBlockXSize, nXSize - iX * nBlockXSize);
    1883             : 
    1884           3 :             bRet =
    1885           3 :                 poQualityDS->GetRasterBand(1)->RasterIO(
    1886           3 :                     GF_Read, iX * nBlockXSize, nSrcYOff, nReqCountX, nReqCountY,
    1887           0 :                     bReverseY ? anValues.data() + (nReqCountY - 1) * nReqCountX
    1888           3 :                               : anValues.data(),
    1889             :                     nReqCountX, nReqCountY, GDT_UInt32, 0,
    1890             :                     bReverseY ? -static_cast<GPtrDiff_t>(sizeof(uint32_t)) *
    1891           0 :                                     nReqCountX
    1892             :                               : 0,
    1893             :                     nullptr) == CE_None;
    1894             : 
    1895           3 :             if (bRet)
    1896             :             {
    1897          15 :                 for (int i = 0; i < nReqCountY * nReqCountX; i++)
    1898             :                 {
    1899          12 :                     if (bHasSrcNoData && anValues[i] == nSrcNoData)
    1900             :                     {
    1901           0 :                         anValues[i] = nNoDataValue;
    1902             :                     }
    1903          24 :                     else if (anValues[i] != 0 &&
    1904          12 :                              !cpl::contains(oSetRATIdCopy, anValues[i]))
    1905             :                     {
    1906           1 :                         CPLError(
    1907             :                             CE_Warning, CPLE_AppDefined,
    1908             :                             "Quality grid contains nodes with id %u, but there "
    1909             :                             "is no such entry in the feature attribute table",
    1910           1 :                             anValues[i]);
    1911           1 :                         oSetRATIdCopy.insert(anValues[i]);
    1912             :                     }
    1913             :                 }
    1914             :             }
    1915             : 
    1916             :             H5OFFSET_TYPE offset[] = {
    1917           3 :                 static_cast<H5OFFSET_TYPE>(iY) *
    1918           3 :                     static_cast<H5OFFSET_TYPE>(nBlockYSize),
    1919           3 :                 static_cast<H5OFFSET_TYPE>(iX) *
    1920           3 :                     static_cast<H5OFFSET_TYPE>(nBlockXSize)};
    1921           3 :             hsize_t count[2] = {static_cast<hsize_t>(nReqCountY),
    1922           3 :                                 static_cast<hsize_t>(nReqCountX)};
    1923           3 :             GH5_HIDSpaceHolder hMemSpace(H5Screate_simple(2, count, nullptr));
    1924           3 :             bRet =
    1925           3 :                 bRet && hMemSpace &&
    1926           3 :                 H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    1927           3 :                                              nullptr, count, nullptr)) >= 0 &&
    1928           3 :                 H5_CHECK(H5Dwrite(hDatasetID, H5T_NATIVE_UINT, hMemSpace,
    1929             :                                   hFileSpace, H5P_DEFAULT, anValues.data())) >=
    1930           6 :                     0 &&
    1931           3 :                 pfnProgress((static_cast<double>(iY) * nXBlocks + iX + 1) /
    1932           3 :                                 (static_cast<double>(nXBlocks) * nYBlocks),
    1933             :                             "", pProgressData) != 0;
    1934             :         }
    1935             :     }
    1936             : 
    1937           6 :     return bRet;
    1938             : }
    1939             : 
    1940             : /************************************************************************/
    1941             : /*                      S102Dataset::CreateCopy()                       */
    1942             : /************************************************************************/
    1943             : 
    1944             : /* static */
    1945          56 : GDALDataset *S102Dataset::CreateCopy(const char *pszFilename,
    1946             :                                      GDALDataset *poSrcDS, int /* bStrict*/,
    1947             :                                      CSLConstList papszOptions,
    1948             :                                      GDALProgressFunc pfnProgress,
    1949             :                                      void *pProgressData)
    1950             : {
    1951         112 :     S102Creator creator(pszFilename, poSrcDS, papszOptions);
    1952          56 :     if (!creator.Create(pfnProgress, pProgressData))
    1953          40 :         return nullptr;
    1954             : 
    1955             :     VSIStatBufL sStatBuf;
    1956          32 :     if (VSIStatL(pszFilename, &sStatBuf) == 0 &&
    1957          16 :         sStatBuf.st_size > 10 * 1024 * 1024)
    1958             :     {
    1959           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1960             :                  "%s file size exceeds 10 MB, which is the upper limit "
    1961             :                  "suggested for wireless transmission to marine vessels",
    1962             :                  pszFilename);
    1963             :     }
    1964             : 
    1965          32 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    1966          16 :     return Open(&oOpenInfo);
    1967             : }
    1968             : 
    1969             : /************************************************************************/
    1970             : /*                      S102DatasetDriverUnload()                       */
    1971             : /************************************************************************/
    1972             : 
    1973           9 : static void S102DatasetDriverUnload(GDALDriver *)
    1974             : {
    1975           9 :     HDF5UnloadFileDriver();
    1976           9 : }
    1977             : 
    1978             : /************************************************************************/
    1979             : /*                         GDALRegister_S102()                          */
    1980             : /************************************************************************/
    1981          14 : void GDALRegister_S102()
    1982             : 
    1983             : {
    1984          14 :     if (!GDAL_CHECK_VERSION("S102"))
    1985           0 :         return;
    1986             : 
    1987          14 :     if (GDALGetDriverByName(S102_DRIVER_NAME) != nullptr)
    1988           0 :         return;
    1989             : 
    1990          14 :     GDALDriver *poDriver = new GDALDriver();
    1991             : 
    1992          14 :     S102DriverSetCommonMetadata(poDriver);
    1993          14 :     poDriver->pfnOpen = S102Dataset::Open;
    1994          14 :     poDriver->pfnCreateCopy = S102Dataset::CreateCopy;
    1995          14 :     poDriver->pfnUnloadDriver = S102DatasetDriverUnload;
    1996             : 
    1997          14 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1998             : }

Generated by: LCOV version 1.14