LCOV - code coverage report
Current view: top level - frmts/hdf5 - s111dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 822 943 87.2 %
Date: 2026-05-07 11:45:54 Functions: 20 22 90.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Hierarchical Data Format Release 5 (HDF5)
       4             :  * Purpose:  Read/create S111 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 "hdf5dataset.h"
      19             : #include "hdf5drivercore.h"
      20             : #include "gh5_convenience.h"
      21             : #include "rat.h"
      22             : #include "s100.h"
      23             : 
      24             : #include "gdal_frmts.h"
      25             : #include "gdal_priv.h"
      26             : #include "gdal_proxy.h"
      27             : #include "gdal_rat.h"
      28             : 
      29             : #include "cpl_time.h"
      30             : 
      31             : #include <algorithm>
      32             : #include <array>
      33             : #include <charconv>
      34             : #include <cmath>
      35             : #include <limits>
      36             : #include <map>
      37             : #include <variant>
      38             : 
      39             : /************************************************************************/
      40             : /*                             S111Dataset                              */
      41             : /************************************************************************/
      42             : 
      43         162 : class S111Dataset final : public S100BaseDataset
      44             : {
      45             :   public:
      46          81 :     explicit S111Dataset(const std::string &osFilename)
      47          81 :         : S100BaseDataset(osFilename)
      48             :     {
      49          81 :     }
      50             : 
      51             :     ~S111Dataset() override;
      52             : 
      53             :     static GDALDataset *Open(GDALOpenInfo *);
      54             :     static GDALDataset *CreateCopy(const char *pszFilename,
      55             :                                    GDALDataset *poSrcDS, int bStrict,
      56             :                                    CSLConstList papszOptions,
      57             :                                    GDALProgressFunc pfnProgress,
      58             :                                    void *pProgressData);
      59             : };
      60             : 
      61             : S111Dataset::~S111Dataset() = default;
      62             : 
      63             : /************************************************************************/
      64             : /*                            S111RasterBand                            */
      65             : /************************************************************************/
      66             : 
      67             : class S111RasterBand final : public GDALProxyRasterBand
      68             : {
      69             :     friend class S111Dataset;
      70             :     std::unique_ptr<GDALDataset> m_poDS{};
      71             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      72             :     std::string m_osUnitType{};
      73             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
      74             : 
      75             :     CPL_DISALLOW_COPY_ASSIGN(S111RasterBand)
      76             : 
      77             :   public:
      78          72 :     explicit S111RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      79         144 :         : m_poDS(std::move(poDSIn)),
      80          72 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      81             :     {
      82          72 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      83          72 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      84          72 :     }
      85             : 
      86             :     GDALRasterBand *
      87             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
      88             : 
      89           4 :     const char *GetUnitType() override
      90             :     {
      91           4 :         return m_osUnitType.c_str();
      92             :     }
      93             : 
      94           1 :     GDALRasterAttributeTable *GetDefaultRAT() override
      95             :     {
      96           1 :         return m_poRAT.get();
      97             :     }
      98             : 
      99           0 :     CSLConstList GetMetadata(const char *pszDomain) override
     100             :     {
     101             :         // Short-circuit GDALProxyRasterBand...
     102           0 :         return GDALRasterBand::GetMetadata(pszDomain);
     103             :     }
     104             : };
     105             : 
     106             : GDALRasterBand *
     107          50 : S111RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
     108             : {
     109          50 :     return m_poUnderlyingBand;
     110             : }
     111             : 
     112             : /************************************************************************/
     113             : /*                                Open()                                */
     114             : /************************************************************************/
     115             : 
     116          82 : GDALDataset *S111Dataset::Open(GDALOpenInfo *poOpenInfo)
     117             : 
     118             : {
     119             :     // Confirm that this appears to be a S111 file.
     120          82 :     if (!S111DatasetIdentify(poOpenInfo))
     121           0 :         return nullptr;
     122             : 
     123             :     HDF5_GLOBAL_LOCK();
     124             : 
     125          82 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
     126             :     {
     127           1 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     128             :     }
     129             : 
     130             :     // Confirm the requested access is supported.
     131          81 :     if (poOpenInfo->eAccess == GA_Update)
     132             :     {
     133           0 :         ReportUpdateNotSupportedByDriver("S111");
     134           0 :         return nullptr;
     135             :     }
     136             : 
     137         162 :     std::string osFilename(poOpenInfo->pszFilename);
     138         162 :     std::string osFeatureInstance = "SurfaceCurrent.01";
     139         162 :     std::string osGroup;
     140          81 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S111:"))
     141             :     {
     142             :         const CPLStringList aosTokens(
     143          37 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     144          37 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     145             : 
     146          37 :         if (aosTokens.size() == 2)
     147             :         {
     148           1 :             osFilename = aosTokens[1];
     149             :         }
     150          36 :         else if (aosTokens.size() == 3)
     151             :         {
     152          28 :             osFilename = aosTokens[1];
     153          28 :             osGroup = aosTokens[2];
     154             :         }
     155           8 :         else if (aosTokens.size() == 4)
     156             :         {
     157           8 :             osFilename = aosTokens[1];
     158           8 :             osFeatureInstance = aosTokens[2];
     159           8 :             osGroup = aosTokens[3];
     160             :         }
     161             :         else
     162             :         {
     163           0 :             return nullptr;
     164             :         }
     165             :     }
     166             : 
     167         162 :     auto poDS = std::make_unique<S111Dataset>(osFilename);
     168          81 :     if (!poDS->Init())
     169           0 :         return nullptr;
     170             : 
     171          81 :     const auto &poRootGroup = poDS->m_poRootGroup;
     172             : 
     173             :     // Read additional metadata
     174         162 :     for (const char *pszAttrName : {"depthTypeIndex"})
     175             :     {
     176         243 :         auto poAttr = poRootGroup->GetAttribute(pszAttrName);
     177          81 :         if (poAttr)
     178             :         {
     179          75 :             const char *pszVal = poAttr->ReadAsString();
     180          75 :             if (pszVal)
     181             :             {
     182          75 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     183             :             }
     184             :         }
     185             :     }
     186             : 
     187          81 :     if (const auto poDepthTypeIndex =
     188         243 :             poRootGroup->GetAttribute("depthTypeIndex"))
     189             :     {
     190          75 :         if (poDepthTypeIndex->GetDataType().GetClass() == GEDTC_NUMERIC)
     191             :         {
     192          75 :             const int nDepthType = poDepthTypeIndex->ReadAsInt();
     193          75 :             if (nDepthType == 1)
     194             :             {
     195          16 :                 poDS->GDALDataset::SetMetadataItem("DEPTH_TYPE_INDEX_NAME",
     196             :                                                    "heightOrDepth");
     197          16 :                 poDS->GDALDataset::SetMetadataItem(
     198             :                     "DEPTH_TYPE_INDEX_DEFINITION", "Height or depth");
     199             :             }
     200          59 :             else if (nDepthType == 2)
     201             :             {
     202          59 :                 poDS->GDALDataset::SetMetadataItem("DEPTH_TYPE_INDEX_NAME",
     203             :                                                    "layerAverage");
     204          59 :                 poDS->GDALDataset::SetMetadataItem(
     205             :                     "DEPTH_TYPE_INDEX_DEFINITION", "Layer average");
     206             :             }
     207         150 :             poDS->GDALDataset::SetMetadataItem(
     208         150 :                 "depthTypeIndex", std::to_string(nDepthType).c_str());
     209             :         }
     210             :     }
     211             : 
     212         243 :     auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
     213          81 :     if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
     214             :     {
     215          18 :         const int nVerticalCS = poVerticalCS->ReadAsInt();
     216          18 :         if (nVerticalCS == 6498)
     217          18 :             poDS->GDALDataset::SetMetadataItem(
     218             :                 "VERTICAL_CS_DEFINITION", "depth, meters, orientation down");
     219           0 :         else if (nVerticalCS == 6499)
     220           0 :             poDS->GDALDataset::SetMetadataItem(
     221             :                 "VERTICAL_CS_DEFINITION", "height, meters, orientation up");
     222             : 
     223          36 :         poDS->GDALDataset::SetMetadataItem("verticalCS",
     224          36 :                                            std::to_string(nVerticalCS).c_str());
     225             :     }
     226             : 
     227         243 :     auto poSurfaceCurrent = poRootGroup->OpenGroup("SurfaceCurrent");
     228          81 :     if (!poSurfaceCurrent)
     229             :     {
     230           7 :         CPLError(CE_Failure, CPLE_AppDefined,
     231             :                  "Cannot find /SurfaceCurrent group");
     232           7 :         return nullptr;
     233             :     }
     234             : 
     235             :     auto poDataCodingFormat =
     236         222 :         poSurfaceCurrent->GetAttribute("dataCodingFormat");
     237         148 :     if (!poDataCodingFormat ||
     238          74 :         poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
     239             :     {
     240           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     241             :                  "Cannot find /SurfaceCurrent/dataCodingFormat attribute");
     242           0 :         return nullptr;
     243             :     }
     244          74 :     const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
     245          74 :     if (nDataCodingFormat != 2)
     246             :     {
     247           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     248             :                  "dataCodingFormat=%d is not supported by the S111 driver",
     249             :                  nDataCodingFormat);
     250           0 :         return nullptr;
     251             :     }
     252             : 
     253             :     // Read additional metadata
     254         370 :     for (const char *pszAttrName :
     255             :          {"methodCurrentsProduct", "minDatasetCurrentSpeed",
     256         444 :           "maxDatasetCurrentSpeed", "commonPointRule", "interpolationType"})
     257             :     {
     258        1110 :         auto poAttr = poSurfaceCurrent->GetAttribute(pszAttrName);
     259         370 :         if (poAttr)
     260             :         {
     261         284 :             const char *pszVal = poAttr->ReadAsString();
     262         284 :             if (pszVal)
     263             :             {
     264         284 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     265             :             }
     266             :         }
     267             :     }
     268             : 
     269          74 :     if (auto poCommonPointRule =
     270         222 :             poSurfaceCurrent->GetAttribute("commonPointRule"))
     271             :     {
     272          70 :         poDS->SetMetadataForCommonPointRule(poCommonPointRule.get());
     273             :     }
     274             : 
     275          74 :     if (auto poInterpolationType =
     276         222 :             poSurfaceCurrent->GetAttribute("interpolationType"))
     277             :     {
     278          70 :         poDS->SetMetadataForInterpolationType(poInterpolationType.get());
     279             :     }
     280             : 
     281          74 :     int nNumInstances = 1;
     282          74 :     if (osGroup.empty())
     283             :     {
     284         114 :         auto poNumInstances = poSurfaceCurrent->GetAttribute("numInstances");
     285          75 :         if (poNumInstances &&
     286          75 :             poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
     287             :         {
     288          37 :             nNumInstances = poNumInstances->ReadAsInt();
     289             :         }
     290             :     }
     291          74 :     if (nNumInstances != 1)
     292             :     {
     293           8 :         CPLStringList aosSubDSList;
     294           4 :         int iSubDS = 0;
     295           8 :         for (const std::string &featureInstanceName :
     296          20 :              poSurfaceCurrent->GetGroupNames())
     297             :         {
     298             :             auto poFeatureInstance =
     299          16 :                 poSurfaceCurrent->OpenGroup(featureInstanceName);
     300           8 :             if (poFeatureInstance)
     301             :             {
     302          16 :                 GDALMajorObject mo;
     303             :                 // Read first vertical datum from root group and let the
     304             :                 // coverage override it.
     305           8 :                 S100ReadVerticalDatum(&mo, poRootGroup.get());
     306           8 :                 S100ReadVerticalDatum(&mo, poFeatureInstance.get());
     307             : 
     308          16 :                 const auto aosGroupNames = poFeatureInstance->GetGroupNames();
     309          16 :                 for (const auto &osSubGroup : aosGroupNames)
     310             :                 {
     311           8 :                     if (auto poSubGroup =
     312          16 :                             poFeatureInstance->OpenGroup(osSubGroup))
     313             :                     {
     314           8 :                         ++iSubDS;
     315             :                         aosSubDSList.SetNameValue(
     316             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     317             :                             CPLSPrintf("S111:\"%s\":%s:%s", osFilename.c_str(),
     318             :                                        featureInstanceName.c_str(),
     319           8 :                                        osSubGroup.c_str()));
     320             : 
     321          16 :                         std::string verticalDatum;
     322             :                         const char *pszValue =
     323           8 :                             mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
     324           8 :                         if (pszValue)
     325             :                         {
     326           2 :                             verticalDatum = ", vertical datum ";
     327           2 :                             verticalDatum += pszValue;
     328             :                             pszValue =
     329           2 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
     330           2 :                             if (pszValue)
     331             :                             {
     332           2 :                                 verticalDatum += " (";
     333           2 :                                 verticalDatum += pszValue;
     334           2 :                                 verticalDatum += ')';
     335             :                             }
     336             :                         }
     337             : 
     338          16 :                         std::string osSubDSDesc;
     339             :                         const auto poTimePoint =
     340          24 :                             poSubGroup->GetAttribute("timePoint");
     341           8 :                         if (poTimePoint)
     342             :                         {
     343           8 :                             const char *pszVal = poTimePoint->ReadAsString();
     344           8 :                             if (pszVal)
     345             :                             {
     346           8 :                                 osSubDSDesc = "Values for feature instance ";
     347           8 :                                 osSubDSDesc += featureInstanceName;
     348           8 :                                 osSubDSDesc += verticalDatum;
     349           8 :                                 osSubDSDesc += " at timestamp ";
     350           8 :                                 osSubDSDesc += pszVal;
     351             :                             }
     352             :                         }
     353           8 :                         if (osSubDSDesc.empty())
     354             :                         {
     355           0 :                             osSubDSDesc = "Values for feature instance ";
     356           0 :                             osSubDSDesc += featureInstanceName;
     357           0 :                             osSubDSDesc += verticalDatum;
     358           0 :                             osSubDSDesc += " and group ";
     359           0 :                             osSubDSDesc += osSubGroup;
     360             :                         }
     361             : 
     362             :                         aosSubDSList.SetNameValue(
     363             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     364           8 :                             osSubDSDesc.c_str());
     365             :                     }
     366             :                 }
     367             :             }
     368             :         }
     369             : 
     370           4 :         poDS->GDALDataset::SetMetadata(aosSubDSList.List(), "SUBDATASETS");
     371             : 
     372             :         // Setup/check for pam .aux.xml.
     373           4 :         poDS->SetDescription(osFilename.c_str());
     374           4 :         poDS->TryLoadXML();
     375             : 
     376             :         // Setup overviews.
     377           4 :         poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     378             : 
     379           4 :         return poDS.release();
     380             :     }
     381             : 
     382         140 :     auto poFeatureInstance = poSurfaceCurrent->OpenGroup(osFeatureInstance);
     383          70 :     if (!poFeatureInstance)
     384             :     {
     385           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     386             :                  "Cannot find /SurfaceCurrent/%s group",
     387             :                  osFeatureInstance.c_str());
     388           0 :         return nullptr;
     389             :     }
     390             : 
     391             :     // Read additional metadata
     392         350 :     for (const char *pszAttrName :
     393             :          {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
     394         420 :           "numberOfTimes", "dataDynamicity"})
     395             :     {
     396        1050 :         auto poAttr = poFeatureInstance->GetAttribute(pszAttrName);
     397         350 :         if (poAttr)
     398             :         {
     399         292 :             const char *pszVal = poAttr->ReadAsString();
     400         292 :             if (pszVal)
     401             :             {
     402         292 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     403             :             }
     404             :         }
     405             :     }
     406             : 
     407          70 :     if (auto poDataDynamicity =
     408         210 :             poFeatureInstance->GetAttribute("dataDynamicity"))
     409             :     {
     410          64 :         poDS->SetMetadataForDataDynamicity(poDataDynamicity.get());
     411             :     }
     412             : 
     413             :     // Read optional uncertainty array
     414         198 :     if (auto poUncertainty = poFeatureInstance->OpenMDArray("uncertainty"))
     415             :     {
     416          58 :         auto &apoDims = poUncertainty->GetDimensions();
     417         116 :         if (poUncertainty->GetDataType().GetClass() == GEDTC_COMPOUND &&
     418         116 :             apoDims.size() == 1 && apoDims[0]->GetSize() == 2)
     419             :         {
     420             :             const auto &oComponents =
     421          58 :                 poUncertainty->GetDataType().GetComponents();
     422         116 :             if (oComponents.size() == 2 &&
     423         116 :                 oComponents[0]->GetName() == "name" &&
     424         116 :                 oComponents[0]->GetType().GetClass() == GEDTC_STRING &&
     425         175 :                 oComponents[1]->GetName() == "value" &&
     426          58 :                 (oComponents[1]->GetType().GetNumericDataType() ==
     427           1 :                      GDT_Float32 ||
     428           1 :                  oComponents[1]->GetType().GetNumericDataType() == GDT_Float64))
     429             :             {
     430         174 :                 auto poName = poUncertainty->GetView("[\"name\"]");
     431         174 :                 auto poValue = poUncertainty->GetView("[\"value\"]");
     432          58 :                 if (poName && poValue)
     433             :                 {
     434          58 :                     char *apszStr[2] = {nullptr, nullptr};
     435          58 :                     double adfVals[2] = {0, 0};
     436          58 :                     GUInt64 arrayStartIdx[] = {0};
     437          58 :                     size_t count[] = {2};
     438          58 :                     GInt64 arrayStep[] = {1};
     439          58 :                     GPtrDiff_t bufferStride[] = {1};
     440          58 :                     if (poName->Read(arrayStartIdx, count, arrayStep,
     441          58 :                                      bufferStride, oComponents[0]->GetType(),
     442         174 :                                      apszStr) &&
     443         116 :                         poValue->Read(
     444             :                             arrayStartIdx, count, arrayStep, bufferStride,
     445         116 :                             GDALExtendedDataType::Create(GDT_Float64), adfVals))
     446             :                     {
     447         174 :                         for (int i = 0; i < 2; ++i)
     448             :                         {
     449         232 :                             std::string osName = apszStr[i];
     450         116 :                             if (osName[0] >= 'a' && osName[0] <= 'z')
     451         116 :                                 osName[0] = osName[0] - 'a' + 'A';
     452         116 :                             osName = "uncertainty" + osName;
     453         116 :                             poDS->GDALDataset::SetMetadataItem(
     454             :                                 osName.c_str(), CPLSPrintf("%f", adfVals[i]));
     455             :                         }
     456             :                     }
     457          58 :                     VSIFree(apszStr[0]);
     458          58 :                     VSIFree(apszStr[1]);
     459             :                 }
     460             :             }
     461             :         }
     462             :     }
     463             : 
     464         140 :     if (auto poStartSequence = poFeatureInstance->GetAttribute("startSequence"))
     465             :     {
     466          70 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     467          70 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     468             :         {
     469           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     470             :                      "startSequence (=%s) != 0,0 is not supported",
     471             :                      pszStartSequence);
     472           0 :             return nullptr;
     473             :         }
     474             :     }
     475             : 
     476          70 :     if (!S100GetNumPointsLongitudinalLatitudinal(
     477          70 :             poFeatureInstance.get(), poDS->nRasterXSize, poDS->nRasterYSize))
     478             :     {
     479           0 :         return nullptr;
     480             :     }
     481             : 
     482             :     // Potentially override vertical datum
     483          70 :     S100ReadVerticalDatum(poDS.get(), poFeatureInstance.get());
     484             : 
     485          70 :     const bool bNorthUp = CPLTestBool(
     486          70 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     487             : 
     488             :     // Compute geotransform
     489         140 :     poDS->m_bHasGT =
     490          70 :         S100GetGeoTransform(poFeatureInstance.get(), poDS->m_gt, bNorthUp);
     491             : 
     492          70 :     if (osGroup.empty())
     493             :     {
     494          68 :         const auto aosGroupNames = poFeatureInstance->GetGroupNames();
     495          34 :         int iSubDS = 1;
     496          72 :         for (const auto &osSubGroup : aosGroupNames)
     497             :         {
     498          76 :             if (auto poSubGroup = poFeatureInstance->OpenGroup(osSubGroup))
     499             :             {
     500          38 :                 poDS->GDALDataset::SetMetadataItem(
     501             :                     CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     502             :                     CPLSPrintf("S111:\"%s\":%s", osFilename.c_str(),
     503             :                                osSubGroup.c_str()),
     504             :                     "SUBDATASETS");
     505          76 :                 std::string osSubDSDesc = "Values for group ";
     506          38 :                 osSubDSDesc += osSubGroup;
     507          76 :                 const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
     508          38 :                 if (poTimePoint)
     509             :                 {
     510          38 :                     const char *pszVal = poTimePoint->ReadAsString();
     511          38 :                     if (pszVal)
     512             :                     {
     513          38 :                         osSubDSDesc = "Values at timestamp ";
     514          38 :                         osSubDSDesc += pszVal;
     515             :                     }
     516             :                 }
     517          38 :                 poDS->GDALDataset::SetMetadataItem(
     518             :                     CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     519             :                     osSubDSDesc.c_str(), "SUBDATASETS");
     520          38 :                 ++iSubDS;
     521             :             }
     522             :         }
     523             :     }
     524             :     else
     525             :     {
     526          36 :         auto poGroup = poFeatureInstance->OpenGroup(osGroup);
     527          36 :         if (!poGroup)
     528             :         {
     529           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     530             :                      "Cannot find /SurfaceCurrent/%s/%s group",
     531             :                      osFeatureInstance.c_str(), osGroup.c_str());
     532           1 :             return nullptr;
     533             :         }
     534             : 
     535             :         // Read additional metadata
     536          70 :         for (const char *pszAttrName : {"timePoint"})
     537             :         {
     538         105 :             auto poAttr = poGroup->GetAttribute(pszAttrName);
     539          35 :             if (poAttr)
     540             :             {
     541          35 :                 const char *pszVal = poAttr->ReadAsString();
     542          35 :                 if (pszVal)
     543             :                 {
     544          35 :                     poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     545             :                 }
     546             :             }
     547             :         }
     548             : 
     549          70 :         auto poValuesArray = poGroup->OpenMDArray("values");
     550          35 :         if (!poValuesArray)
     551             :         {
     552           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     553             :                      "Cannot find /SurfaceCurrent/%s/%s/values array",
     554             :                      osFeatureInstance.c_str(), osGroup.c_str());
     555           0 :             return nullptr;
     556             :         }
     557             : 
     558          35 :         if (poValuesArray->GetDimensionCount() != 2)
     559             :         {
     560           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     561             :                      "Wrong dimension count for %s",
     562           0 :                      poValuesArray->GetFullName().c_str());
     563           0 :             return nullptr;
     564             :         }
     565             : 
     566          35 :         const auto &oType = poValuesArray->GetDataType();
     567          35 :         if (oType.GetClass() != GEDTC_COMPOUND)
     568             :         {
     569           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     570           0 :                      poValuesArray->GetFullName().c_str());
     571           0 :             return nullptr;
     572             :         }
     573             : 
     574          35 :         const auto &oComponents = oType.GetComponents();
     575          70 :         if (!(oComponents.size() >= 2 &&
     576          70 :               ((oComponents[0]->GetName() == "surfaceCurrentSpeed" &&
     577          35 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     578          70 :                 oComponents[1]->GetName() == "surfaceCurrentDirection" &&
     579          35 :                 oComponents[1]->GetType().GetNumericDataType() ==
     580           0 :                     GDT_Float32) ||
     581             :                // S111US_20170829.0100_W078.N44_F2_loofs_type2.h5 has direction first...
     582           0 :                (oComponents[0]->GetName() == "surfaceCurrentDirection" &&
     583           0 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     584           0 :                 oComponents[1]->GetName() == "surfaceCurrentSpeed" &&
     585           0 :                 oComponents[1]->GetType().GetNumericDataType() ==
     586             :                     GDT_Float32))))
     587             :         {
     588           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     589           0 :                      poValuesArray->GetFullName().c_str());
     590           0 :             return nullptr;
     591             :         }
     592             : 
     593          35 :         const auto &apoDims = poValuesArray->GetDimensions();
     594          35 :         if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
     595             :         {
     596           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     597             :                      "numPointsLatitudinal(=%d) doesn't match first dimension "
     598             :                      "size of %s (=%d)",
     599           0 :                      poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
     600           0 :                      static_cast<int>(apoDims[0]->GetSize()));
     601           0 :             return nullptr;
     602             :         }
     603          35 :         if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
     604             :         {
     605           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     606             :                      "numPointsLongitudinal(=%d) doesn't match second "
     607             :                      "dimension size of %s (=%d)",
     608           0 :                      poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
     609           0 :                      static_cast<int>(apoDims[1]->GetSize()));
     610           0 :             return nullptr;
     611             :         }
     612             : 
     613          35 :         if (bNorthUp)
     614          34 :             poValuesArray = poValuesArray->GetView("[::-1,...]");
     615             : 
     616             :         // Create surfaceCurrentSpeed band
     617             :         auto poSurfaceCurrentSpeed =
     618         105 :             poValuesArray->GetView("[\"surfaceCurrentSpeed\"]");
     619             :         auto poSurfaceCurrentSpeedDS = std::unique_ptr<GDALDataset>(
     620          70 :             poSurfaceCurrentSpeed->AsClassicDataset(1, 0));
     621             :         auto poSurfaceCurrentSpeedBand = std::make_unique<S111RasterBand>(
     622          70 :             std::move(poSurfaceCurrentSpeedDS));
     623          35 :         poSurfaceCurrentSpeedBand->SetDescription("surfaceCurrentSpeed");
     624          35 :         poSurfaceCurrentSpeedBand->m_osUnitType = "knots";
     625             : 
     626             :         // From S-111 v1.2 table 9.1 (Speed ranges) and 9.2 (Colour schemas)
     627          70 :         auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
     628          35 :         poRAT->CreateColumn("speed_band", GFT_Integer, GFU_Generic);
     629          35 :         poRAT->CreateColumn("min_speed", GFT_Real, GFU_Min);
     630          35 :         poRAT->CreateColumn("width_band", GFT_Real, GFU_Generic);
     631          35 :         poRAT->CreateColumn("color", GFT_String, GFU_Generic);
     632          35 :         poRAT->CreateColumn("red", GFT_Integer, GFU_RedMin);
     633          35 :         poRAT->CreateColumn("green", GFT_Integer, GFU_GreenMin);
     634          35 :         poRAT->CreateColumn("blue", GFT_Integer, GFU_BlueMin);
     635             : 
     636             :         const struct
     637             :         {
     638             :             int nSpeedBand;
     639             :             double dfMinSpeed;
     640             :             double dfWidthBand;
     641             :             const char *pszColor;
     642             :             int nRed;
     643             :             int nGreen;
     644             :             int nBlue;
     645          35 :         } aoRatValues[] = {
     646             :             {1, 0.0, 0.5, "purple", 118, 82, 226},
     647             :             {2, 0.5, 0.5, "dark blue", 72, 152, 211},
     648             :             {3, 1.0, 1.0, "light blue", 97, 203, 229},
     649             :             {4, 2.0, 1.0, "dark green", 109, 188, 69},
     650             :             {5, 3.0, 2.0, "light green", 180, 220, 0},
     651             :             {6, 5.0, 2.0, "yellow green", 205, 193, 0},
     652             :             {7, 7.0, 3.0, "orange", 248, 167, 24},
     653             :             {8, 10.0, 3.0, "pink", 247, 162, 157},
     654             :             {9, 13.0, 86.0, "red", 255, 30, 30},
     655             :         };
     656             : 
     657          35 :         int iRow = 0;
     658         350 :         for (const auto &oRecord : aoRatValues)
     659             :         {
     660         315 :             int iCol = 0;
     661         315 :             poRAT->SetValue(iRow, iCol++, oRecord.nSpeedBand);
     662         315 :             poRAT->SetValue(iRow, iCol++, oRecord.dfMinSpeed);
     663         315 :             poRAT->SetValue(iRow, iCol++, oRecord.dfWidthBand);
     664         315 :             poRAT->SetValue(iRow, iCol++, oRecord.pszColor);
     665         315 :             poRAT->SetValue(iRow, iCol++, oRecord.nRed);
     666         315 :             poRAT->SetValue(iRow, iCol++, oRecord.nGreen);
     667         315 :             poRAT->SetValue(iRow, iCol++, oRecord.nBlue);
     668         315 :             ++iRow;
     669             :         }
     670             : 
     671          35 :         poSurfaceCurrentSpeedBand->m_poRAT = std::move(poRAT);
     672             : 
     673          35 :         poDS->SetBand(1, poSurfaceCurrentSpeedBand.release());
     674             : 
     675             :         // Create surfaceCurrentDirection band
     676             :         auto poSurfaceCurrentDirection =
     677         105 :             poValuesArray->GetView("[\"surfaceCurrentDirection\"]");
     678             :         auto poSurfaceCurrentDirectionDS = std::unique_ptr<GDALDataset>(
     679          70 :             poSurfaceCurrentDirection->AsClassicDataset(1, 0));
     680             :         auto poSurfaceCurrentDirectionBand = std::make_unique<S111RasterBand>(
     681          70 :             std::move(poSurfaceCurrentDirectionDS));
     682          35 :         poSurfaceCurrentDirectionBand->SetDescription(
     683          35 :             "surfaceCurrentDirection");
     684          35 :         poSurfaceCurrentDirectionBand->m_osUnitType = "degree";
     685          35 :         poSurfaceCurrentDirectionBand->GDALRasterBand::SetMetadataItem(
     686             :             "ANGLE_CONVENTION", "From true north, clockwise");
     687          35 :         poDS->SetBand(2, poSurfaceCurrentDirectionBand.release());
     688             : 
     689          37 :         for (size_t i = 2; i < oComponents.size(); ++i)
     690             :         {
     691           3 :             if (oComponents[i]->GetName() == "speedUncertainty" &&
     692           1 :                 oComponents[i]->GetType().GetNumericDataType() == GDT_Float32)
     693             :             {
     694             :                 auto poSubArray =
     695           3 :                     poValuesArray->GetView("[\"speedUncertainty\"]");
     696             :                 auto poSubArrayDS = std::unique_ptr<GDALDataset>(
     697           2 :                     poSubArray->AsClassicDataset(1, 0));
     698             :                 auto poSubArrayBand =
     699           2 :                     std::make_unique<S111RasterBand>(std::move(poSubArrayDS));
     700           1 :                 poSubArrayBand->SetDescription("speedUncertainty");
     701           1 :                 poSubArrayBand->m_osUnitType = "knot";
     702           1 :                 poDS->SetBand(poDS->nBands + 1, poSubArrayBand.release());
     703             :             }
     704           2 :             else if (oComponents[i]->GetName() == "directionUncertainty" &&
     705           1 :                      oComponents[i]->GetType().GetNumericDataType() ==
     706             :                          GDT_Float32)
     707             :             {
     708             :                 auto poSubArray =
     709           3 :                     poValuesArray->GetView("[\"directionUncertainty\"]");
     710             :                 auto poSubArrayDS = std::unique_ptr<GDALDataset>(
     711           2 :                     poSubArray->AsClassicDataset(1, 0));
     712             :                 auto poSubArrayBand =
     713           2 :                     std::make_unique<S111RasterBand>(std::move(poSubArrayDS));
     714           1 :                 poSubArrayBand->SetDescription("directionUncertainty");
     715           1 :                 poSubArrayBand->m_osUnitType = "degree";
     716           1 :                 poDS->SetBand(poDS->nBands + 1, poSubArrayBand.release());
     717             :             }
     718             :         }
     719             :     }
     720             : 
     721          69 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     722             : 
     723             :     // Setup/check for pam .aux.xml.
     724          69 :     if (osFilename != poOpenInfo->pszFilename)
     725             :     {
     726          36 :         poDS->SetSubdatasetName((osFeatureInstance + "/" + osGroup).c_str());
     727          36 :         poDS->SetPhysicalFilename(osFilename.c_str());
     728             :     }
     729          69 :     poDS->SetDescription(poOpenInfo->pszFilename);
     730          69 :     poDS->TryLoadXML();
     731             : 
     732             :     // Setup overviews.
     733          69 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     734             : 
     735          69 :     return poDS.release();
     736             : }
     737             : 
     738             : /************************************************************************/
     739             : /*                             S111Creator                              */
     740             : /************************************************************************/
     741             : 
     742             : class S111Creator final : public S100BaseWriter
     743             : {
     744             :   public:
     745          69 :     S111Creator(const char *pszDestFilename, GDALDataset *poSrcDS,
     746             :                 CSLConstList papszOptions)
     747          69 :         : S100BaseWriter(pszDestFilename, poSrcDS, papszOptions)
     748             :     {
     749          69 :     }
     750             : 
     751             :     ~S111Creator() override;
     752             : 
     753             :     bool Create(GDALProgressFunc pfnProgress, void *pProgressData);
     754             : 
     755             :     static constexpr const char *FEATURE_TYPE = "SurfaceCurrent";
     756             : 
     757             :   protected:
     758         102 :     bool Close() override
     759             :     {
     760         102 :         return BaseClose();
     761             :     }
     762             : 
     763             :   private:
     764             :     bool WriteFeatureGroupAttributes();
     765             :     bool WriteUncertaintyDataset();
     766             :     bool FillFeatureInstanceGroup(
     767             :         const std::map<std::string, std::variant<GDALDataset *, std::string>>
     768             :             &oMapTimestampToDS,
     769             :         GDALProgressFunc pfnProgress, void *pProgressData);
     770             :     bool CopyValues(GDALDataset *poSrcDS, GDALProgressFunc pfnProgress,
     771             :                     void *pProgressData);
     772             :     bool CreateGroupF();
     773             : };
     774             : 
     775             : /************************************************************************/
     776             : /*                     S111Creator::~S111Creator()                      */
     777             : /************************************************************************/
     778             : 
     779          69 : S111Creator::~S111Creator()
     780             : {
     781          69 :     S111Creator::Close();
     782          69 : }
     783             : 
     784             : /************************************************************************/
     785             : /*                        S111Creator::Create()                         */
     786             : /************************************************************************/
     787             : 
     788          69 : bool S111Creator::Create(GDALProgressFunc pfnProgress, void *pProgressData)
     789             : {
     790             :     CPLStringList aosDatasets(
     791         138 :         CSLTokenizeString2(m_aosOptions.FetchNameValue("DATASETS"), ",", 0));
     792          69 :     if (m_poSrcDS->GetRasterCount() == 0 && aosDatasets.empty())
     793             :     {
     794             :         // Deal with S111 -> S111 translation;
     795           3 :         CSLConstList papszSubdatasets = m_poSrcDS->GetMetadata("SUBDATASETS");
     796           3 :         if (papszSubdatasets)
     797             :         {
     798           2 :             int iSubDS = 0;
     799           2 :             std::string osFirstDataset;
     800           2 :             std::string osDatasets;
     801          20 :             for (const auto &[pszItem, pszValue] :
     802          22 :                  cpl::IterateNameValue(papszSubdatasets))
     803             :             {
     804          30 :                 if (STARTS_WITH(pszItem, "SUBDATASET_") &&
     805          15 :                     cpl::ends_with(std::string_view(pszItem), "_NAME") &&
     806           5 :                     STARTS_WITH(pszValue, "S111:"))
     807             :                 {
     808           5 :                     if (strstr(pszValue, ":SurfaceCurrent."))
     809             :                     {
     810             :                         auto poTmpDS =
     811             :                             std::unique_ptr<GDALDataset>(GDALDataset::Open(
     812             :                                 pszValue,
     813           2 :                                 GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
     814           2 :                         if (!poTmpDS)
     815           0 :                             return false;
     816           2 :                         CPLStringList aosOptions(m_aosOptions);
     817           2 :                         if (iSubDS > 0)
     818           1 :                             aosOptions.SetNameValue("APPEND_SUBDATASET", "YES");
     819             :                         S111Creator oAuxCreator(m_osDestFilename.c_str(),
     820             :                                                 poTmpDS.get(),
     821           2 :                                                 aosOptions.List());
     822             :                         const int nSubDSCount =
     823           2 :                             ((CSLCount(papszSubdatasets) + 1) / 2);
     824             :                         std::unique_ptr<void,
     825             :                                         decltype(&GDALDestroyScaledProgress)>
     826             :                             pScaledProgressData(
     827             :                                 GDALCreateScaledProgress(
     828           2 :                                     static_cast<double>(iSubDS) / nSubDSCount,
     829           2 :                                     static_cast<double>(iSubDS + 1) /
     830             :                                         nSubDSCount,
     831             :                                     pfnProgress, pProgressData),
     832           2 :                                 GDALDestroyScaledProgress);
     833           2 :                         ++iSubDS;
     834           2 :                         if (!oAuxCreator.Create(GDALScaledProgress,
     835             :                                                 pScaledProgressData.get()))
     836           0 :                             return false;
     837             :                     }
     838             :                     else
     839             :                     {
     840           3 :                         if (osFirstDataset.empty())
     841           1 :                             osFirstDataset = pszValue;
     842           3 :                         if (!osDatasets.empty())
     843           2 :                             osDatasets += ',';
     844           3 :                         osDatasets += pszValue;
     845             :                     }
     846             :                 }
     847             :             }
     848           2 :             if (iSubDS > 0)
     849             :             {
     850           1 :                 return true;
     851             :             }
     852           1 :             else if (!osDatasets.empty())
     853             :             {
     854             :                 auto poTmpDS = std::unique_ptr<GDALDataset>(
     855             :                     GDALDataset::Open(osFirstDataset.c_str(),
     856           2 :                                       GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
     857           1 :                 if (!poTmpDS)
     858           0 :                     return false;
     859           2 :                 CPLStringList aosOptions(m_aosOptions);
     860           1 :                 aosOptions.SetNameValue("DATASETS", osDatasets.c_str());
     861             :                 S111Creator oAuxCreator(m_osDestFilename.c_str(), poTmpDS.get(),
     862           2 :                                         aosOptions.List());
     863           1 :                 return oAuxCreator.Create(pfnProgress, pProgressData);
     864             :             }
     865             :         }
     866             :     }
     867             : 
     868          67 :     if (m_poSrcDS->GetRasterCount() != 2 && m_poSrcDS->GetRasterCount() != 4)
     869             :     {
     870          17 :         CPLError(CE_Failure, CPLE_NotSupported,
     871             :                  "Source dataset %s must have 2 or 4 bands",
     872          17 :                  m_poSrcDS->GetDescription());
     873          17 :         return false;
     874             :     }
     875             : 
     876          50 :     if (!BaseChecks("S111", /* crsMustBeEPSG = */ false,
     877             :                     /* verticalDatumRequired = */ false))
     878           6 :         return false;
     879             : 
     880             :     std::map<std::string, std::variant<GDALDataset *, std::string>>
     881          88 :         oMapTimestampToDS;
     882             :     CPLStringList aosDatasetsTimePoint(CSLTokenizeString2(
     883          88 :         m_aosOptions.FetchNameValue("DATASETS_TIME_POINT"), ",", 0));
     884          44 :     if (!aosDatasets.empty())
     885             :     {
     886           2 :         if (!aosDatasetsTimePoint.empty() &&
     887           0 :             aosDatasetsTimePoint.size() != aosDatasets.size())
     888             :         {
     889           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     890             :                      "DATASETS_TIME_POINT does not have the same number of "
     891             :                      "values as DATASETS");
     892           0 :             return false;
     893             :         }
     894           2 :         int i = 0;
     895           8 :         for (const char *pszDataset : aosDatasets)
     896             :         {
     897             :             auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
     898           6 :                 pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
     899           6 :             if (!poDS)
     900           0 :                 return false;
     901          12 :             if (poDS->GetRasterXSize() != m_poSrcDS->GetRasterXSize() ||
     902           6 :                 poDS->GetRasterYSize() != m_poSrcDS->GetRasterYSize())
     903             :             {
     904           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     905             :                          "Dataset %s does not have the same dimensions as %s",
     906           0 :                          poDS->GetDescription(), m_poSrcDS->GetDescription());
     907           0 :                 return false;
     908             :             }
     909           6 :             if (poDS->GetRasterCount() != m_poSrcDS->GetRasterCount())
     910             :             {
     911           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     912             :                          "Dataset %s must have %d bands",
     913           0 :                          poDS->GetDescription(), m_poSrcDS->GetRasterCount());
     914           0 :                 return false;
     915             :             }
     916           6 :             auto poSRS = poDS->GetSpatialRef();
     917           6 :             if (!poSRS || !poSRS->IsSame(m_poSRS))
     918             :             {
     919           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     920             :                          "Dataset %s does not have the same CRS as %s",
     921           0 :                          poDS->GetDescription(), m_poSrcDS->GetDescription());
     922           0 :                 return false;
     923             :             }
     924           6 :             GDALGeoTransform gt;
     925           6 :             if (poDS->GetGeoTransform(gt) != CE_None || gt != m_gt)
     926             :             {
     927           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
     928             :                          "Dataset %s does not have the same geotransform as %s",
     929           0 :                          poDS->GetDescription(), m_poSrcDS->GetDescription());
     930           0 :                 return false;
     931             :             }
     932             :             const char *pszVerticalDatum =
     933           6 :                 poDS->GetMetadataItem("VERTICAL_DATUM");
     934           6 :             if (pszVerticalDatum)
     935             :             {
     936             :                 const int nVerticalDatum =
     937           0 :                     S100GetVerticalDatumCodeFromNameOrAbbrev(pszVerticalDatum);
     938           0 :                 if (nVerticalDatum != m_nVerticalDatum)
     939             :                 {
     940           0 :                     CPLError(CE_Failure, CPLE_NotSupported,
     941             :                              "Dataset %s does not have the same vertical datum "
     942             :                              "as %s",
     943           0 :                              poDS->GetDescription(),
     944           0 :                              m_poSrcDS->GetDescription());
     945           0 :                     return false;
     946             :                 }
     947             :             }
     948           6 :             const char *pszTimePoint = poDS->GetMetadataItem("timePoint");
     949           6 :             if (!pszTimePoint && !aosDatasetsTimePoint.empty())
     950           0 :                 pszTimePoint = aosDatasetsTimePoint[i];
     951           6 :             if (!pszTimePoint)
     952             :             {
     953           0 :                 CPLError(
     954             :                     CE_Failure, CPLE_NotSupported,
     955             :                     "Dataset %s does not have a timePoint metadata item, and "
     956             :                     "the DATASETS_TIME_POINT creation option is not set",
     957           0 :                     poDS->GetDescription());
     958           0 :                 return false;
     959             :             }
     960           6 :             if (strlen(pszTimePoint) != strlen("YYYYMMDDTHHMMSSZ") ||
     961           6 :                 pszTimePoint[8] != 'T' || pszTimePoint[15] != 'Z')
     962             :             {
     963           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     964             :                          "timePoint value for dataset %s is %s, but does not "
     965             :                          "conform to a YYYYMMDDTHHMMSSZ datetime value.",
     966           0 :                          poDS->GetDescription(), pszTimePoint);
     967           0 :                 return false;
     968             :             }
     969           6 :             if (cpl::contains(oMapTimestampToDS, pszTimePoint))
     970             :             {
     971           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     972             :                          "Several datasets are at timePoint %s.", pszTimePoint);
     973           0 :                 return false;
     974             :             }
     975           6 :             oMapTimestampToDS[pszTimePoint] = pszDataset;
     976           6 :             ++i;
     977             :         }
     978             :     }
     979             : 
     980             :     {
     981          44 :         const char *pszTimePoint = m_aosOptions.FetchNameValueDef(
     982          44 :             "TIME_POINT", m_poSrcDS->GetMetadataItem("timePoint"));
     983          44 :         if (!pszTimePoint)
     984             :         {
     985           3 :             CPLError(CE_Failure, CPLE_AppDefined,
     986             :                      "TIME_POINT creation option value must "
     987             :                      "be set, or source dataset must have a timePoint metadata "
     988             :                      "item.");
     989           3 :             return false;
     990             :         }
     991          41 :         if (strlen(pszTimePoint) != strlen("YYYYMMDDTHHMMSSZ") ||
     992          40 :             pszTimePoint[8] != 'T' || pszTimePoint[15] != 'Z')
     993             :         {
     994           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     995             :                      "TIME_POINT creation option value must "
     996             :                      "be set to a YYYYMMDDTHHMMSSZ datetime value.");
     997           1 :             return false;
     998             :         }
     999             : 
    1000          40 :         if (oMapTimestampToDS.empty())
    1001             :         {
    1002          38 :             oMapTimestampToDS[pszTimePoint] = m_poSrcDS;
    1003             :         }
    1004             :         else
    1005             :         {
    1006           2 :             const auto oIter = oMapTimestampToDS.find(pszTimePoint);
    1007           3 :             if (oIter != oMapTimestampToDS.end() &&
    1008           3 :                 CPLString(std::get<std::string>(oIter->second))
    1009           1 :                         .replaceAll('\\', '/') !=
    1010           3 :                     CPLString(m_poSrcDS->GetDescription())
    1011           1 :                         .replaceAll('\\', '/'))
    1012             :             {
    1013           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1014             :                          "Several datasets are at timePoint %s (%s vs %s).",
    1015             :                          pszTimePoint,
    1016           0 :                          std::get<std::string>(oIter->second).c_str(),
    1017           0 :                          m_poSrcDS->GetDescription());
    1018           0 :                 return false;
    1019             :             }
    1020             :         }
    1021             :     }
    1022          40 :     if (oMapTimestampToDS.size() > 999)
    1023             :     {
    1024           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1025             :                  "Only up to 999 datasets are supported for a same feature "
    1026             :                  "instance group");
    1027           0 :         return false;
    1028             :     }
    1029             : 
    1030          80 :     if (m_poSRS->IsVertical() || m_poSRS->IsCompound() || m_poSRS->IsLocal() ||
    1031          40 :         m_poSRS->GetAxesCount() != 2)
    1032             :     {
    1033           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1034             :                  "The CRS must be a geographic 2D or projected 2D CRS");
    1035           0 :         return false;
    1036             :     }
    1037             : 
    1038             :     const bool bAppendSubdataset =
    1039          40 :         CPLTestBool(m_aosOptions.FetchNameValueDef("APPEND_SUBDATASET", "NO"));
    1040          40 :     if (bAppendSubdataset)
    1041             :     {
    1042           4 :         GDALOpenInfo oOpenInfo(m_osDestFilename.c_str(), GA_ReadOnly);
    1043             :         auto poOriDS =
    1044           4 :             std::unique_ptr<GDALDataset>(S111Dataset::Open(&oOpenInfo));
    1045           2 :         if (!poOriDS)
    1046             :         {
    1047           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1048             :                      "%s is not a valid existing S111 dataset",
    1049             :                      m_osDestFilename.c_str());
    1050           0 :             return false;
    1051             :         }
    1052           2 :         const auto poOriSRS = poOriDS->GetSpatialRef();
    1053           2 :         if (!poOriSRS)
    1054             :         {
    1055             :             // shouldn't happen
    1056           0 :             return false;
    1057             :         }
    1058           2 :         if (!poOriSRS->IsSame(m_poSRS))
    1059             :         {
    1060           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1061             :                      "CRS of %s is not the same as the one of %s",
    1062           0 :                      m_osDestFilename.c_str(), m_poSrcDS->GetDescription());
    1063           0 :             return false;
    1064             :         }
    1065           2 :         poOriDS.reset();
    1066             : 
    1067           2 :         OGREnvelope sExtent;
    1068           2 :         if (m_poSrcDS->GetExtentWGS84LongLat(&sExtent) != OGRERR_NONE)
    1069             :         {
    1070           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1071             :                      "Cannot get dataset extent in WGS84 longitude/latitude");
    1072           0 :             return false;
    1073             :         }
    1074             : 
    1075           2 :         bool ret = OpenFileUpdateMode();
    1076           2 :         if (ret)
    1077             :         {
    1078           2 :             m_featureGroup.reset(H5_CHECK(H5Gopen(m_hdf5, "SurfaceCurrent")));
    1079             :         }
    1080             : 
    1081           2 :         ret = ret && m_featureGroup;
    1082           2 :         double dfNumInstances = 0;
    1083           2 :         ret = ret && GH5_FetchAttribute(m_featureGroup, "numInstances",
    1084             :                                         dfNumInstances, true);
    1085           2 :         if (ret && !(dfNumInstances >= 1 && dfNumInstances <= 99 &&
    1086           2 :                      std::round(dfNumInstances) == dfNumInstances))
    1087             :         {
    1088           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1089             :                      "Invalid value for numInstances");
    1090           0 :             ret = false;
    1091             :         }
    1092           2 :         else if (ret && dfNumInstances == 99)
    1093             :         {
    1094           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1095             :                      "Too many existing feature instances");
    1096           0 :             ret = false;
    1097             :         }
    1098             :         else
    1099             :         {
    1100           2 :             const int newNumInstances = static_cast<int>(dfNumInstances) + 1;
    1101           2 :             ret = ret && GH5_WriteAttribute(m_featureGroup, "numInstances",
    1102             :                                             newNumInstances);
    1103           2 :             ret = ret && CreateFeatureInstanceGroup(CPLSPrintf(
    1104             :                              "SurfaceCurrent.%02d", newNumInstances));
    1105           2 :             ret = ret && FillFeatureInstanceGroup(oMapTimestampToDS,
    1106             :                                                   pfnProgress, pProgressData);
    1107             :         }
    1108             : 
    1109           2 :         return Close() && ret;
    1110             :     }
    1111             :     else
    1112             :     {
    1113          38 :         bool ret = CreateFile();
    1114          38 :         ret = ret && WriteProductSpecification("INT.IHO.S-111.2.0");
    1115          38 :         ret = ret && WriteIssueDate();
    1116          38 :         ret = ret && WriteIssueTime(/* bAutogenerateFromCurrent = */ true);
    1117          38 :         ret = ret && WriteHorizontalCRS();
    1118          38 :         ret = ret && WriteTopLevelBoundingBox();
    1119             : 
    1120          38 :         const char *pszGeographicIdentifier = m_aosOptions.FetchNameValueDef(
    1121             :             "GEOGRAPHIC_IDENTIFIER",
    1122          38 :             m_poSrcDS->GetMetadataItem("geographicIdentifier"));
    1123          38 :         if (pszGeographicIdentifier)
    1124             :         {
    1125           0 :             ret =
    1126           0 :                 ret && WriteVarLengthStringValue(m_hdf5, "geographicIdentifier",
    1127             :                                                  pszGeographicIdentifier);
    1128             :         }
    1129             : 
    1130          38 :         const char *pszDepthTypeIndex = m_aosOptions.FetchNameValueDef(
    1131          38 :             "DEPTH_TYPE", m_poSrcDS->GetMetadataItem("depthTypeIndex"));
    1132          38 :         if (!pszDepthTypeIndex)
    1133             :         {
    1134           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1135             :                      "DEPTH_TYPE creation option must be specified.");
    1136           1 :             return false;
    1137             :         }
    1138             : 
    1139             :         GH5_HIDTypeHolder hDepthTypeIndexEnumDataType(
    1140          74 :             H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1141          37 :         ret = ret && hDepthTypeIndexEnumDataType;
    1142             : 
    1143             :         uint8_t val;
    1144          37 :         val = 1;
    1145          37 :         ret = ret && H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1146             :                                              "heightOrDepth", &val)) >= 0;
    1147          37 :         val = 2;
    1148          37 :         ret = ret && H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1149             :                                              "layerAverage", &val)) >= 0;
    1150             : 
    1151          74 :         const int nDepthTypeIndex = EQUAL(pszDepthTypeIndex, "heightOrDepth")
    1152          74 :                                         ? 1
    1153          37 :                                     : EQUAL(pszDepthTypeIndex, "layerAverage")
    1154          37 :                                         ? 2
    1155          37 :                                         : atoi(pszDepthTypeIndex);
    1156          37 :         if (nDepthTypeIndex != 1 && nDepthTypeIndex != 2)
    1157             :         {
    1158           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1159             :                      "DEPTH_TYPE creation option must "
    1160             :                      "be set to heightOrDepth/1 or layerAverage/2.");
    1161           1 :             return false;
    1162             :         }
    1163          72 :         ret = ret &&
    1164          36 :               GH5_CreateAttribute(m_hdf5, "depthTypeIndex",
    1165          72 :                                   hDepthTypeIndexEnumDataType) &&
    1166          36 :               GH5_WriteAttribute(m_hdf5, "depthTypeIndex", nDepthTypeIndex);
    1167             : 
    1168          36 :         const char *pszVerticalCS = m_aosOptions.FetchNameValueDef(
    1169          36 :             "VERTICAL_CS", m_poSrcDS->GetMetadataItem("verticalCS"));
    1170          36 :         if (!pszVerticalCS)
    1171             :         {
    1172          26 :             if (nDepthTypeIndex == 1)
    1173             :             {
    1174           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1175             :                          "VERTICAL_CS creation option must be specified when "
    1176             :                          "DEPTH_TYPE = heightOrDepth");
    1177           1 :                 return false;
    1178             :             }
    1179             :         }
    1180             :         else
    1181             :         {
    1182          20 :             const int nVerticalCS = EQUAL(pszVerticalCS, "DEPTH") ? 6498
    1183          10 :                                     : EQUAL(pszVerticalCS, "HEIGHT")
    1184          10 :                                         ? 6499
    1185          10 :                                         : atoi(pszVerticalCS);
    1186          10 :             if (nVerticalCS != 6498 && nVerticalCS != 6499)
    1187             :             {
    1188           1 :                 CPLError(
    1189             :                     CE_Failure, CPLE_NotSupported,
    1190             :                     "VERTICAL_CS creation option must be set either to 6498 "
    1191             :                     "(depth/down, metre), or 6499 (height/up, metre)");
    1192           1 :                 return false;
    1193             :             }
    1194             : 
    1195           9 :             ret = ret && WriteVerticalCS(nVerticalCS);
    1196             :         }
    1197             : 
    1198          34 :         ret = ret && WriteVerticalCoordinateBase(2);  // verticalDatum
    1199             : 
    1200          34 :         if (m_nVerticalDatum > 0)
    1201             :         {
    1202             :             // 1=s100VerticalDatum, 2=EPSG
    1203          16 :             ret = ret && WriteVerticalDatumReference(
    1204           8 :                              m_hdf5, m_nVerticalDatum <= 1024 ? 1 : 2);
    1205          16 :             ret = ret &&
    1206           8 :                   WriteVerticalDatum(m_hdf5, H5T_STD_I32LE, m_nVerticalDatum);
    1207             :         }
    1208          26 :         else if (nDepthTypeIndex == 1)
    1209             :         {
    1210           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1211             :                      "VERTICAL_DATUM creation option must be specified when "
    1212             :                      "DEPTH_TYPE = heightOrDepth");
    1213           1 :             return false;
    1214             :         }
    1215             : 
    1216             :         const char *pszSurfaceCurrentTrendThreshold =
    1217          33 :             m_aosOptions.FetchNameValueDef(
    1218             :                 "SURFACE_CURRENT_DEPTH",
    1219          33 :                 m_poSrcDS->GetMetadataItem("surfaceCurrentDepth"));
    1220          33 :         if (!pszSurfaceCurrentTrendThreshold)
    1221             :         {
    1222           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1223             :                      "SURFACE_CURRENT_DEPTH creation option must be "
    1224             :                      "specified.");
    1225           1 :             return false;
    1226             :         }
    1227          32 :         if (CPLGetValueType(pszSurfaceCurrentTrendThreshold) ==
    1228             :             CPL_VALUE_STRING)
    1229             :         {
    1230           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1231             :                      "SURFACE_CURRENT_DEPTH creation option value must "
    1232             :                      "be a numeric value.");
    1233           1 :             return false;
    1234             :         }
    1235          31 :         ret =
    1236          31 :             ret && WriteFloat32Value(m_hdf5, "surfaceCurrentDepth",
    1237             :                                      CPLAtof(pszSurfaceCurrentTrendThreshold));
    1238             : 
    1239          31 :         const char *pszDatasetDeliveryInterval = m_aosOptions.FetchNameValueDef(
    1240             :             "DATASET_DELIVERY_INTERVAL",
    1241          31 :             m_poSrcDS->GetMetadataItem("datasetDeliveryInterval"));
    1242          31 :         if (pszDatasetDeliveryInterval)
    1243             :         {
    1244           0 :             ret = ret &&
    1245           0 :                   WriteVarLengthStringValue(m_hdf5, "datasetDeliveryInterval",
    1246             :                                             pszDatasetDeliveryInterval);
    1247             :         }
    1248             : 
    1249             :         // SurfaceCurrent
    1250          31 :         ret = ret && CreateFeatureGroup(FEATURE_TYPE);
    1251          31 :         ret = ret && WriteFeatureGroupAttributes();
    1252          31 :         ret = ret && WriteAxisNames(m_featureGroup);
    1253             : 
    1254          31 :         ret = ret && CreateFeatureInstanceGroup("SurfaceCurrent.01");
    1255          31 :         ret = ret && FillFeatureInstanceGroup(oMapTimestampToDS, pfnProgress,
    1256             :                                               pProgressData);
    1257             : 
    1258          31 :         ret = ret && CreateGroupF();
    1259             : 
    1260          31 :         return Close() && ret;
    1261             :     }
    1262             : }
    1263             : 
    1264             : /************************************************************************/
    1265             : /*              S111Creator::WriteFeatureGroupAttributes()              */
    1266             : /************************************************************************/
    1267             : 
    1268          31 : bool S111Creator::WriteFeatureGroupAttributes()
    1269             : {
    1270          31 :     CPLAssert(m_featureGroup);
    1271             : 
    1272             :     // 3 = high (recommended)
    1273          31 :     const char *pszCommonPointRule = m_aosOptions.FetchNameValueDef(
    1274          31 :         "COMMON_POINT_RULE", m_poSrcDS->GetMetadataItem("commonPointRule"));
    1275          31 :     if (!pszCommonPointRule)
    1276          29 :         pszCommonPointRule = "3";  // all (recommended)
    1277          31 :     const int nCommonPointRule = atoi(pszCommonPointRule);
    1278          31 :     bool ret = WriteCommonPointRule(m_featureGroup, nCommonPointRule);
    1279          31 :     ret = ret && WriteDataCodingFormat(m_featureGroup, 2);  // Regular grid
    1280          31 :     ret = ret && WriteDataOffsetCode(m_featureGroup, 5);    // Center of cell
    1281          31 :     ret = ret && WriteDimension(m_featureGroup, 2);
    1282             :     const char *pszHorizontalPositionUncertainty =
    1283          31 :         m_aosOptions.FetchNameValueDef(
    1284             :             "HORIZONTAL_POSITION_UNCERTAINTY",
    1285          31 :             m_poSrcDS->GetMetadataItem("horizontalPositionUncertainty"));
    1286          31 :     ret =
    1287          62 :         ret &&
    1288          31 :         WriteHorizontalPositionUncertainty(
    1289             :             m_featureGroup,
    1290           0 :             pszHorizontalPositionUncertainty &&
    1291           0 :                     pszHorizontalPositionUncertainty[0]
    1292           0 :                 ? static_cast<float>(CPLAtof(pszHorizontalPositionUncertainty))
    1293             :                 : -1.0f);
    1294          31 :     const char *pszVerticalUncertainty = m_aosOptions.FetchNameValueDef(
    1295             :         "VERTICAL_UNCERTAINTY",
    1296          31 :         m_poSrcDS->GetMetadataItem("verticalUncertainty"));
    1297          31 :     ret = ret && WriteVerticalUncertainty(
    1298             :                      m_featureGroup,
    1299           0 :                      pszVerticalUncertainty && pszVerticalUncertainty[0]
    1300           0 :                          ? static_cast<float>(CPLAtof(pszVerticalUncertainty))
    1301             :                          : -1.0f);
    1302          31 :     const char *pszTimeUncertainty = m_aosOptions.FetchNameValueDef(
    1303          31 :         "TIME_UNCERTAINTY", m_poSrcDS->GetMetadataItem("timeUncertainty"));
    1304          31 :     if (pszTimeUncertainty)
    1305           0 :         WriteFloat32Value(m_featureGroup, "timeUncertainty",
    1306             :                           CPLAtof(pszTimeUncertainty));
    1307          31 :     const char *pszMethodCurrentsProduct = m_aosOptions.FetchNameValueDef(
    1308             :         "METHOD_CURRENTS_PRODUCT",
    1309          31 :         m_poSrcDS->GetMetadataItem("methodCurrentsProduct"));
    1310          31 :     if (pszMethodCurrentsProduct)
    1311           0 :         WriteVarLengthStringValue(m_featureGroup, "methodCurrentsProduct",
    1312             :                                   pszMethodCurrentsProduct);
    1313          31 :     ret = ret && WriteInterpolationType(m_featureGroup, 10);  // discrete
    1314          31 :     ret = ret && WriteNumInstances(m_featureGroup, H5T_STD_U32LE, 1);
    1315          62 :     ret = ret && WriteSequencingRuleScanDirection(m_featureGroup,
    1316          31 :                                                   m_poSRS->IsProjected()
    1317             :                                                       ? "Easting, Northing"
    1318             :                                                       : "Longitude, Latitude");
    1319          31 :     ret = ret && WriteSequencingRuleType(m_featureGroup, 1);  // Linear
    1320          31 :     return ret;
    1321             : }
    1322             : 
    1323             : /************************************************************************/
    1324             : /*                S111Creator::WriteUncertaintyDataset()                */
    1325             : /************************************************************************/
    1326             : 
    1327          27 : bool S111Creator::WriteUncertaintyDataset()
    1328             : {
    1329          27 :     CPLAssert(m_featureInstanceGroup);
    1330             : 
    1331             :     GH5_HIDTypeHolder hDataType(
    1332          54 :         H5_CHECK(H5Tcreate(H5T_COMPOUND, sizeof(char *) + sizeof(float))));
    1333          54 :     GH5_HIDTypeHolder hVarLengthStringDataType(H5_CHECK(H5Tcopy(H5T_C_S1)));
    1334             :     bool bRet =
    1335          54 :         hVarLengthStringDataType &&
    1336          54 :         H5_CHECK(H5Tset_size(hVarLengthStringDataType, H5T_VARIABLE)) >= 0;
    1337          54 :     bRet = bRet && hVarLengthStringDataType &&
    1338          27 :            H5_CHECK(
    1339             :                H5Tset_strpad(hVarLengthStringDataType, H5T_STR_NULLTERM)) >= 0;
    1340          27 :     bRet = bRet && hDataType &&
    1341          27 :            H5_CHECK(H5Tinsert(hDataType, "name", 0,
    1342          54 :                               hVarLengthStringDataType)) >= 0 &&
    1343          27 :            H5_CHECK(H5Tinsert(hDataType, "value", sizeof(char *),
    1344             :                               H5T_IEEE_F32LE)) >= 0;
    1345          27 :     constexpr hsize_t NUM_ROWS = 2;
    1346          27 :     hsize_t dims[] = {NUM_ROWS};
    1347          54 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(1, dims, nullptr)));
    1348          54 :     GH5_HIDDatasetHolder hDatasetID;
    1349          54 :     GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
    1350          27 :     bRet = bRet && hParams;
    1351          27 :     if (bRet)
    1352             :     {
    1353          27 :         hDatasetID.reset(
    1354             :             H5_CHECK(H5Dcreate(m_featureInstanceGroup, "uncertainty", hDataType,
    1355             :                                hDataSpace, hParams)));
    1356          27 :         bRet = hDatasetID;
    1357             :     }
    1358             : 
    1359          27 :     GH5_HIDSpaceHolder hFileSpace;
    1360          27 :     if (bRet)
    1361             :     {
    1362          27 :         hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
    1363          27 :         bRet = hFileSpace;
    1364             :     }
    1365             :     GByte abyValues[NUM_ROWS * (sizeof(char *) + sizeof(float))];
    1366             :     {
    1367          27 :         const char *pszName = "surfaceCurrentSpeed";
    1368          27 :         const char *pszVal = m_aosOptions.FetchNameValueDef(
    1369             :             "UNCERTAINTY_SPEED",
    1370          27 :             m_poSrcDS->GetMetadataItem("uncertaintySurfaceCurrentSpeed"));
    1371          27 :         float fVal = pszVal ? static_cast<float>(CPLAtof(pszVal)) : -1.0f;
    1372          27 :         CPL_LSBPTR32(&fVal);
    1373          27 :         memcpy(abyValues, &pszName, sizeof(char *));
    1374          27 :         memcpy(abyValues + sizeof(char *), &fVal, sizeof(fVal));
    1375             :     }
    1376             :     {
    1377          27 :         const char *pszName = "surfaceCurrentDirection";
    1378          27 :         const char *pszVal = m_aosOptions.FetchNameValueDef(
    1379             :             "UNCERTAINTY_DIRECTION",
    1380          27 :             m_poSrcDS->GetMetadataItem("uncertaintySurfaceCurrentDirection"));
    1381          27 :         float fVal = pszVal ? static_cast<float>(CPLAtof(pszVal)) : -1.0f;
    1382          27 :         CPL_LSBPTR32(&fVal);
    1383          27 :         memcpy(abyValues + sizeof(char *) + sizeof(float), &pszName,
    1384             :                sizeof(char *));
    1385          27 :         memcpy(abyValues + sizeof(char *) + sizeof(float) + sizeof(char *),
    1386             :                &fVal, sizeof(fVal));
    1387             :     }
    1388             : 
    1389          27 :     H5OFFSET_TYPE offset[] = {0};
    1390          54 :     bRet = bRet &&
    1391          27 :            H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    1392          54 :                                         nullptr, dims, nullptr)) >= 0 &&
    1393          27 :            H5_CHECK(H5Dwrite(hDatasetID, hDataType, hDataSpace, hFileSpace,
    1394             :                              H5P_DEFAULT, abyValues)) >= 0;
    1395          54 :     return bRet;
    1396             : }
    1397             : 
    1398             : /************************************************************************/
    1399             : /*               S111Creator::FillFeatureInstanceGroup()                */
    1400             : /************************************************************************/
    1401             : 
    1402          33 : bool S111Creator::FillFeatureInstanceGroup(
    1403             :     const std::map<std::string, std::variant<GDALDataset *, std::string>>
    1404             :         &oMapTimestampToDS,
    1405             :     GDALProgressFunc pfnProgress, void *pProgressData)
    1406             : {
    1407          33 :     bool ret = WriteFIGGridRelatedParameters(m_featureInstanceGroup);
    1408             : 
    1409          33 :     const int numInstances = static_cast<int>(oMapTimestampToDS.size());
    1410             : 
    1411          33 :     ret =
    1412          33 :         ret && WriteNumGRP(m_featureInstanceGroup, H5T_STD_U32LE, numInstances);
    1413          33 :     ret = ret && WriteUInt32Value(m_featureInstanceGroup, "numberOfTimes",
    1414             :                                   numInstances);
    1415             : 
    1416             :     // Check if value groups are spaced at a regular time interval
    1417          33 :     GIntBig nLastInterval = 0;
    1418          33 :     GIntBig nLastTS = 0;
    1419          70 :     for (const auto &[key, value] : oMapTimestampToDS)
    1420             :     {
    1421          37 :         CPL_IGNORE_RET_VAL(value);
    1422             :         int nYear, nMonth, nDay, nHour, nMinute, nSecond;
    1423          37 :         if (sscanf(key.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
    1424          37 :                    &nDay, &nHour, &nMinute, &nSecond) == 6)
    1425             :         {
    1426             :             struct tm brokenDown;
    1427          37 :             memset(&brokenDown, 0, sizeof(brokenDown));
    1428          37 :             brokenDown.tm_year = nYear - 1900;
    1429          37 :             brokenDown.tm_mon = nMonth - 1;
    1430          37 :             brokenDown.tm_mday = nDay;
    1431          37 :             brokenDown.tm_hour = nHour;
    1432          37 :             brokenDown.tm_min = nMinute;
    1433          37 :             brokenDown.tm_sec = nMinute;
    1434          37 :             const GIntBig nTS = CPLYMDHMSToUnixTime(&brokenDown);
    1435          37 :             if (nLastTS != 0)
    1436             :             {
    1437           4 :                 if (nLastInterval == 0)
    1438             :                 {
    1439           2 :                     nLastInterval = nTS - nLastTS;
    1440             :                 }
    1441           2 :                 else if (nLastInterval != nTS - nLastTS)
    1442             :                 {
    1443           0 :                     nLastInterval = 0;
    1444           0 :                     break;
    1445             :                 }
    1446             :             }
    1447          37 :             nLastTS = nTS;
    1448             :         }
    1449             :     }
    1450             : 
    1451          33 :     const char *pszTimeRecordInterval = m_aosOptions.FetchNameValueDef(
    1452             :         "TIME_RECORD_INTERVAL",
    1453          33 :         m_poSrcDS->GetMetadataItem("timeRecordInterval"));
    1454          33 :     if (pszTimeRecordInterval)
    1455             :     {
    1456           2 :         ret = ret &&
    1457           1 :               WriteUInt16Value(m_featureInstanceGroup, "timeRecordInterval",
    1458             :                                atoi(pszTimeRecordInterval));
    1459             :     }
    1460          32 :     else if (nLastInterval > 0 && nLastInterval < 65536)
    1461             :     {
    1462           2 :         ret = ret &&
    1463           1 :               WriteUInt16Value(m_featureInstanceGroup, "timeRecordInterval",
    1464             :                                static_cast<int>(nLastInterval));
    1465             :     }
    1466             : 
    1467          66 :     ret = ret && WriteVarLengthStringValue(
    1468             :                      m_featureInstanceGroup, "dateTimeOfFirstRecord",
    1469          33 :                      oMapTimestampToDS.begin()->first.c_str());
    1470          66 :     ret = ret && WriteVarLengthStringValue(
    1471             :                      m_featureInstanceGroup, "dateTimeOfLastRecord",
    1472          33 :                      oMapTimestampToDS.rbegin()->first.c_str());
    1473             : 
    1474          33 :     const char *pszDataDynamicity = m_aosOptions.FetchNameValueDef(
    1475          33 :         "DATA_DYNAMICITY", m_poSrcDS->GetMetadataItem("dataDynamicity"));
    1476          33 :     if (!pszDataDynamicity)
    1477             :     {
    1478           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1479             :                  "DATA_DYNAMICITY creation option must "
    1480             :                  "be specified.");
    1481           1 :         return false;
    1482             :     }
    1483             :     {
    1484             :         GH5_HIDTypeHolder hDepthTypeIndexEnumDataType(
    1485          32 :             H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1486          32 :         ret = ret && hDepthTypeIndexEnumDataType;
    1487             : 
    1488             :         uint8_t val;
    1489          32 :         val = 1;
    1490          32 :         ret = ret && H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1491             :                                              "observation", &val)) >= 0;
    1492          32 :         val = 2;
    1493          64 :         ret = ret &&
    1494          32 :               H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1495             :                                       "astronomicalPrediction", &val)) >= 0;
    1496          32 :         val = 3;
    1497          32 :         ret = ret && H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1498             :                                              "analysisOrHybrid", &val)) >= 0;
    1499          32 :         val = 5;
    1500          32 :         ret =
    1501          32 :             ret && H5_CHECK(H5Tenum_insert(hDepthTypeIndexEnumDataType,
    1502             :                                            "hydrodynamicForecast", &val)) >= 0;
    1503             : 
    1504          32 :         const int nDataDynamicity =
    1505          64 :             EQUAL(pszDataDynamicity, "observation")              ? 1
    1506          64 :             : EQUAL(pszDataDynamicity, "astronomicalPrediction") ? 2
    1507          64 :             : EQUAL(pszDataDynamicity, "analysisOrHybrid")       ? 3
    1508          32 :             : EQUAL(pszDataDynamicity, "hydrodynamicForecast")
    1509          32 :                 ? 5
    1510          32 :                 : atoi(pszDataDynamicity);
    1511          32 :         if (nDataDynamicity != 1 && nDataDynamicity != 2 &&
    1512          32 :             nDataDynamicity != 3 && nDataDynamicity != 5)
    1513             :         {
    1514           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1515             :                      "DATA_DYNAMICITY creation option must "
    1516             :                      "be set to observation/1, astronomicalPrediction/2, "
    1517             :                      "analysisOrHybrid/3 or hydrodynamicForecast/5.");
    1518           1 :             return false;
    1519             :         }
    1520          31 :         ret = ret &&
    1521          31 :               GH5_CreateAttribute(m_featureInstanceGroup, "dataDynamicity",
    1522          62 :                                   hDepthTypeIndexEnumDataType) &&
    1523          31 :               GH5_WriteAttribute(m_featureInstanceGroup, "dataDynamicity",
    1524             :                                  nDataDynamicity);
    1525             :     }
    1526             : 
    1527          31 :     if (m_poSrcDS->GetRasterCount() == 2 ||
    1528          35 :         m_aosOptions.FetchNameValue("UNCERTAINTY_SPEED") ||
    1529           4 :         m_aosOptions.FetchNameValue("UNCERTAINTY_DIRECTION"))
    1530             :     {
    1531          27 :         ret = ret && WriteUncertaintyDataset();
    1532             :     }
    1533             : 
    1534          31 :     int iInstance = 0;
    1535          31 :     double dfLastRatio = 0;
    1536          66 :     for (const auto &iter : oMapTimestampToDS)
    1537             :     {
    1538          35 :         ++iInstance;
    1539          35 :         ret = ret && CreateValuesGroup(CPLSPrintf("Group_%03d", iInstance));
    1540             : 
    1541          35 :         ret = ret && WriteVarLengthStringValue(m_valuesGroup, "timePoint",
    1542             :                                                iter.first.c_str());
    1543             : 
    1544           0 :         std::unique_ptr<GDALDataset> poTmpDSHolder;
    1545             :         GDALDataset *poSrcDS;
    1546          35 :         if (std::holds_alternative<std::string>(iter.second))
    1547             :         {
    1548           6 :             poTmpDSHolder.reset(
    1549           6 :                 GDALDataset::Open(std::get<std::string>(iter.second).c_str(),
    1550             :                                   GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
    1551           6 :             if (!poTmpDSHolder)
    1552             :             {
    1553           0 :                 return false;
    1554             :             }
    1555           6 :             poSrcDS = poTmpDSHolder.get();
    1556             :         }
    1557             :         else
    1558             :         {
    1559          29 :             CPLAssert(std::holds_alternative<GDALDataset *>(iter.second));
    1560          29 :             poSrcDS = std::get<GDALDataset *>(iter.second);
    1561             :         }
    1562             : 
    1563          35 :         const double dfNewRatio = static_cast<double>(iInstance) / numInstances;
    1564             :         std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
    1565             :             pScaledProgressData(
    1566             :                 GDALCreateScaledProgress(dfLastRatio, dfNewRatio, pfnProgress,
    1567             :                                          pProgressData),
    1568          35 :                 GDALDestroyScaledProgress);
    1569          35 :         ret = ret && CopyValues(poSrcDS, GDALScaledProgress,
    1570             :                                 pScaledProgressData.get());
    1571          35 :         dfLastRatio = dfNewRatio;
    1572             :     }
    1573             : 
    1574          31 :     return ret;
    1575             : }
    1576             : 
    1577             : /************************************************************************/
    1578             : /*                     S111Creator::CreateGroupF()                      */
    1579             : /************************************************************************/
    1580             : 
    1581             : // Per S-111 v2.0 spec
    1582             : 
    1583             : constexpr float string_to_float(std::string_view str)
    1584             : {
    1585             :     if (str.empty())
    1586             :         return 0.0f;
    1587             : 
    1588             :     size_t i = 0;
    1589             :     bool neg = false;
    1590             :     if (str[0] == '-')
    1591             :     {
    1592             :         neg = true;
    1593             :         ++i;
    1594             :     }
    1595             :     else if (str[0] == '+')
    1596             :     {
    1597             :         ++i;
    1598             :     }
    1599             : 
    1600             :     float int_part = 0.0f;
    1601             :     for (; i < str.size() && str[i] >= '0' && str[i] <= '9'; ++i)
    1602             :     {
    1603             :         int_part = int_part * 10.0f + (str[i] - '0');
    1604             :     }
    1605             : 
    1606             :     float frac_part = 0.0f;
    1607             :     float divisor = 1.0f;
    1608             :     if (i < str.size() && str[i] == '.')
    1609             :     {
    1610             :         for (++i; i < str.size() && str[i] >= '0' && str[i] <= '9'; ++i)
    1611             :         {
    1612             :             frac_part = frac_part * 10.0f + (str[i] - '0');
    1613             :             divisor *= 10.0f;
    1614             :         }
    1615             :     }
    1616             : 
    1617             :     float result = int_part + frac_part / divisor;
    1618             :     return neg ? -result : result;
    1619             : }
    1620             : 
    1621             : constexpr const char *MIN_SPEED_STR = "0.00";
    1622             : constexpr float MIN_SPEED = string_to_float(MIN_SPEED_STR);
    1623             : constexpr const char *MAX_SPEED_STR = "99.00";
    1624             : constexpr float MAX_SPEED = string_to_float(MAX_SPEED_STR);
    1625             : constexpr const char *NODATA_SPEED_STR = "-9999.00";
    1626             : constexpr float NODATA_SPEED = string_to_float(NODATA_SPEED_STR);
    1627             : 
    1628             : constexpr const char *MIN_DIR_STR = "0.0";
    1629             : constexpr float MIN_DIR = string_to_float(MIN_DIR_STR);
    1630             : constexpr const char *MAX_DIR_STR = "359.9";
    1631             : constexpr float MAX_DIR = string_to_float(MAX_DIR_STR);
    1632             : constexpr const char *NODATA_DIR_STR = "-9999.0";
    1633             : constexpr float NODATA_DIR = string_to_float(NODATA_DIR_STR);
    1634             : 
    1635             : constexpr const char *NODATA_UNCT_STR = "-1.0";
    1636             : constexpr float NODATA_UNCT = string_to_float(NODATA_UNCT_STR);
    1637             : 
    1638          29 : bool S111Creator::CreateGroupF()
    1639             : {
    1640          29 :     bool ret = S100BaseWriter::CreateGroupF();
    1641             : 
    1642          29 :     CPLStringList aosFeatureCodes;
    1643          29 :     aosFeatureCodes.push_back(FEATURE_TYPE);
    1644          58 :     ret = ret && WriteOneDimensionalVarLengthStringArray(
    1645          29 :                      m_GroupF, "featureCode", aosFeatureCodes.List());
    1646             : 
    1647             :     {
    1648             :         std::vector<std::array<const char *, GROUP_F_DATASET_FIELD_COUNT>> rows{
    1649             :             {"surfaceCurrentSpeed", "Surface Current Speed", "knot",
    1650             :              NODATA_SPEED_STR, "H5T_FLOAT", MIN_SPEED_STR, MAX_SPEED_STR,
    1651             :              "geSemiInterval"},
    1652             :             {"surfaceCurrentDirection", "Surface Current Direction", "degree",
    1653             :              "-9999.0", "H5T_FLOAT", MIN_DIR_STR, MAX_DIR_STR,
    1654             :              "closedInterval"},
    1655             :             {"speedUncertainty", "Speed Uncertainty", "knot", NODATA_UNCT_STR,
    1656             :              "H5T_FLOAT", MIN_SPEED_STR, MAX_SPEED_STR, "geSemiInterval"},
    1657             :             {"directionUncertainty", "Direction Uncertainty", "degree",
    1658             :              NODATA_UNCT_STR, "H5T_FLOAT", MIN_DIR_STR, MAX_DIR_STR,
    1659             :              "closedInterval"},
    1660          29 :         };
    1661          29 :         rows.resize(m_poSrcDS->GetRasterCount());
    1662          29 :         ret = ret && WriteGroupFDataset(FEATURE_TYPE, rows);
    1663             :     }
    1664             : 
    1665          58 :     return ret;
    1666             : }
    1667             : 
    1668             : /************************************************************************/
    1669             : /*                      S111Creator::CopyValues()                       */
    1670             : /************************************************************************/
    1671             : 
    1672          35 : bool S111Creator::CopyValues(GDALDataset *poSrcDS, GDALProgressFunc pfnProgress,
    1673             :                              void *pProgressData)
    1674             : {
    1675          35 :     CPLAssert(m_valuesGroup.get() >= 0);
    1676             : 
    1677          35 :     const int nYSize = poSrcDS->GetRasterYSize();
    1678          35 :     const int nXSize = poSrcDS->GetRasterXSize();
    1679             : 
    1680          35 :     hsize_t dims[] = {static_cast<hsize_t>(nYSize),
    1681          35 :                       static_cast<hsize_t>(nXSize)};
    1682             : 
    1683          70 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(2, dims, nullptr)));
    1684          35 :     bool bRet = hDataSpace;
    1685             : 
    1686             :     const bool bDeflate =
    1687          35 :         EQUAL(m_aosOptions.FetchNameValueDef("COMPRESS", "DEFLATE"), "DEFLATE");
    1688             :     const int nCompressionLevel =
    1689          35 :         atoi(m_aosOptions.FetchNameValueDef("ZLEVEL", "6"));
    1690             :     const int nBlockSize =
    1691          35 :         std::min(4096, std::max(100, atoi(m_aosOptions.FetchNameValueDef(
    1692          35 :                                          "BLOCK_SIZE", "100"))));
    1693          35 :     const int nBlockXSize = std::min(nXSize, nBlockSize);
    1694          35 :     const int nBlockYSize = std::min(nYSize, nBlockSize);
    1695          35 :     const int nComponents = poSrcDS->GetRasterCount();
    1696          35 :     CPLAssert(nComponents == 2 || nComponents == 4);
    1697             : 
    1698             :     GH5_HIDTypeHolder hDataType(
    1699          70 :         H5_CHECK(H5Tcreate(H5T_COMPOUND, sizeof(float) * nComponents)));
    1700          35 :     bRet = bRet && hDataType &&
    1701          35 :            H5_CHECK(H5Tinsert(hDataType, "surfaceCurrentSpeed", 0,
    1702          70 :                               H5T_IEEE_F32LE)) >= 0 &&
    1703          35 :            H5_CHECK(H5Tinsert(hDataType, "surfaceCurrentDirection",
    1704             :                               sizeof(float), H5T_IEEE_F32LE)) >= 0;
    1705          35 :     if (nComponents == 4 && bRet)
    1706             :     {
    1707           8 :         bRet = H5_CHECK(H5Tinsert(hDataType, "speedUncertainty",
    1708           8 :                                   2 * sizeof(float), H5T_IEEE_F32LE)) >= 0 &&
    1709           4 :                H5_CHECK(H5Tinsert(hDataType, "directionUncertainty",
    1710             :                                   3 * sizeof(float), H5T_IEEE_F32LE)) >= 0;
    1711             :     }
    1712             : 
    1713          35 :     hsize_t chunk_size[] = {static_cast<hsize_t>(nBlockYSize),
    1714          35 :                             static_cast<hsize_t>(nBlockXSize)};
    1715             : 
    1716          70 :     GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
    1717          35 :     bRet = bRet && hParams &&
    1718          35 :            H5_CHECK(H5Pset_fill_time(hParams, H5D_FILL_TIME_ALLOC)) >= 0 &&
    1719         105 :            H5_CHECK(H5Pset_layout(hParams, H5D_CHUNKED)) >= 0 &&
    1720          35 :            H5_CHECK(H5Pset_chunk(hParams, 2, chunk_size)) >= 0;
    1721             : 
    1722          35 :     if (bRet && bDeflate)
    1723             :     {
    1724          34 :         bRet = H5_CHECK(H5Pset_deflate(hParams, nCompressionLevel)) >= 0;
    1725             :     }
    1726             : 
    1727          70 :     GH5_HIDDatasetHolder hDatasetID;
    1728          35 :     if (bRet)
    1729             :     {
    1730          35 :         hDatasetID.reset(H5_CHECK(H5Dcreate(m_valuesGroup, "values", hDataType,
    1731             :                                             hDataSpace, hParams)));
    1732          35 :         bRet = hDatasetID;
    1733             :     }
    1734             : 
    1735          70 :     GH5_HIDSpaceHolder hFileSpace;
    1736          35 :     if (bRet)
    1737             :     {
    1738          35 :         hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
    1739          35 :         bRet = hFileSpace;
    1740             :     }
    1741             : 
    1742          35 :     const int nYBlocks = static_cast<int>(DIV_ROUND_UP(nYSize, nBlockYSize));
    1743          35 :     const int nXBlocks = static_cast<int>(DIV_ROUND_UP(nXSize, nBlockXSize));
    1744          35 :     std::vector<float> afValues(static_cast<size_t>(nBlockYSize) * nBlockXSize *
    1745          35 :                                 nComponents);
    1746          35 :     const bool bReverseY = m_gt.yscale < 0;
    1747             : 
    1748          35 :     constexpr std::array<float, 4> afNoDataValue{NODATA_SPEED, NODATA_DIR,
    1749             :                                                  NODATA_UNCT, NODATA_UNCT};
    1750             : 
    1751          35 :     constexpr float INF = std::numeric_limits<float>::infinity();
    1752          35 :     std::array<float, 4> afMin{INF, INF, INF, INF};
    1753          35 :     std::array<float, 4> afMax{-INF, -INF, -INF, -INF};
    1754             : 
    1755          35 :     std::array<int, 4> abHasNoDataBand{FALSE, FALSE, FALSE, FALSE};
    1756          35 :     std::array<float, 4> afSrcNoData{0, 0, 0, 0};
    1757         113 :     for (int i = 0; i < nComponents; ++i)
    1758             :     {
    1759          78 :         afSrcNoData[i] = static_cast<float>(
    1760          78 :             poSrcDS->GetRasterBand(i + 1)->GetNoDataValue(&abHasNoDataBand[i]));
    1761             :     }
    1762             : 
    1763          81 :     for (int iY = 0; iY < nYBlocks && bRet; iY++)
    1764             :     {
    1765             :         const int nSrcYOff = bReverseY
    1766          46 :                                  ? std::max(0, nYSize - (iY + 1) * nBlockYSize)
    1767          41 :                                  : iY * nBlockYSize;
    1768          46 :         const int nReqCountY = std::min(nBlockYSize, nYSize - iY * nBlockYSize);
    1769         224 :         for (int iX = 0; iX < nXBlocks && bRet; iX++)
    1770             :         {
    1771             :             const int nReqCountX =
    1772         178 :                 std::min(nBlockXSize, nXSize - iX * nBlockXSize);
    1773             : 
    1774         178 :             bRet =
    1775         178 :                 poSrcDS->RasterIO(
    1776         178 :                     GF_Read, iX * nBlockXSize, nSrcYOff, nReqCountX, nReqCountY,
    1777           5 :                     bReverseY ? afValues.data() +
    1778           5 :                                     (nReqCountY - 1) * nReqCountX * nComponents
    1779         173 :                               : afValues.data(),
    1780             :                     nReqCountX, nReqCountY, GDT_Float32, nComponents, nullptr,
    1781         178 :                     static_cast<int>(sizeof(float)) * nComponents,
    1782             :                     bReverseY ? -static_cast<GPtrDiff_t>(sizeof(float)) *
    1783           5 :                                     nComponents * nReqCountX
    1784             :                               : 0,
    1785             :                     sizeof(float), nullptr) == CE_None;
    1786             : 
    1787         178 :             if (bRet)
    1788             :             {
    1789     1440470 :                 for (int i = 0; i < nReqCountY * nReqCountX; i++)
    1790             :                 {
    1791     7200920 :                     for (int iC = 0; iC < nComponents; ++iC)
    1792             :                     {
    1793     5760630 :                         float fVal = afValues[i * nComponents + iC];
    1794    11521300 :                         if ((abHasNoDataBand[iC] && fVal == afSrcNoData[iC]) ||
    1795     5760630 :                             std::isnan(fVal))
    1796             :                         {
    1797           0 :                             afValues[i * nComponents + iC] = afNoDataValue[iC];
    1798             :                         }
    1799             :                         else
    1800             :                         {
    1801     5760630 :                             afMin[iC] = std::min(afMin[iC], fVal);
    1802     5760630 :                             afMax[iC] = std::max(afMax[iC], fVal);
    1803             :                         }
    1804     5760630 :                         CPL_LSBPTR32(&afValues[i * nComponents + iC]);
    1805             :                     }
    1806             :                 }
    1807             :             }
    1808             : 
    1809             :             H5OFFSET_TYPE offset[] = {
    1810         178 :                 static_cast<H5OFFSET_TYPE>(iY) *
    1811         178 :                     static_cast<H5OFFSET_TYPE>(nBlockYSize),
    1812         178 :                 static_cast<H5OFFSET_TYPE>(iX) *
    1813         178 :                     static_cast<H5OFFSET_TYPE>(nBlockXSize)};
    1814         178 :             hsize_t count[2] = {static_cast<hsize_t>(nReqCountY),
    1815         178 :                                 static_cast<hsize_t>(nReqCountX)};
    1816             :             GH5_HIDSpaceHolder hMemSpace(
    1817         178 :                 H5_CHECK(H5Screate_simple(2, count, nullptr)));
    1818         178 :             bRet =
    1819         178 :                 bRet &&
    1820         178 :                 H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    1821         178 :                                              nullptr, count, nullptr)) >= 0 &&
    1822         178 :                 hMemSpace &&
    1823         178 :                 H5_CHECK(H5Dwrite(hDatasetID, hDataType, hMemSpace, hFileSpace,
    1824         356 :                                   H5P_DEFAULT, afValues.data())) >= 0 &&
    1825         178 :                 pfnProgress((static_cast<double>(iY) * nXBlocks + iX + 1) /
    1826         178 :                                 (static_cast<double>(nXBlocks) * nYBlocks),
    1827             :                             "", pProgressData) != 0;
    1828             :         }
    1829             :     }
    1830             : 
    1831          35 :     constexpr int IDX_SPEED = 0;
    1832          35 :     constexpr int IDX_DIR = 1;
    1833          35 :     constexpr int IDX_UNC_SPEED = 2;
    1834          35 :     constexpr int IDX_UNC_DIR = 3;
    1835             : 
    1836          35 :     if (afMin[IDX_SPEED] > afMax[IDX_SPEED])
    1837             :     {
    1838           0 :         afMin[IDX_SPEED] = afMax[IDX_SPEED] = NODATA_SPEED;
    1839             :     }
    1840          35 :     else if (!(afMin[IDX_SPEED] >= MIN_SPEED && afMax[IDX_SPEED] <= MAX_SPEED))
    1841             :     {
    1842           2 :         CPLError(CE_Warning, CPLE_AppDefined,
    1843             :                  "Range of surface current speed in the dataset is [%f, %f] "
    1844             :                  "whereas the "
    1845             :                  "allowed range is [%.2f, %.2f]",
    1846           2 :                  afMin[IDX_SPEED], afMax[IDX_SPEED], MIN_SPEED, MAX_SPEED);
    1847             :     }
    1848             : 
    1849          35 :     if (afMin[IDX_DIR] > afMax[IDX_DIR])
    1850             :     {
    1851           0 :         afMin[IDX_DIR] = afMax[IDX_DIR] = NODATA_DIR;
    1852             :     }
    1853          35 :     else if (!(afMin[IDX_DIR] >= MIN_DIR && afMax[IDX_DIR] <= MAX_DIR))
    1854             :     {
    1855           2 :         CPLError(
    1856             :             CE_Warning, CPLE_AppDefined,
    1857             :             "Range of surface current direction in the dataset is [%f, %f] "
    1858             :             "whereas the "
    1859             :             "allowed range is [%.2f, %.2f]",
    1860           2 :             afMin[IDX_DIR], afMax[IDX_DIR], MIN_DIR, MAX_DIR);
    1861             :     }
    1862             : 
    1863          39 :     if (afMax[IDX_UNC_SPEED] >= afMin[IDX_UNC_SPEED] &&
    1864           4 :         afMin[IDX_UNC_SPEED] < 0)
    1865             :     {
    1866           1 :         CPLError(
    1867             :             CE_Warning, CPLE_AppDefined,
    1868             :             "Negative speed uncertainty value found (%f), which is not allowed "
    1869             :             "(except nodata value %s)",
    1870           1 :             afMin[IDX_UNC_SPEED], NODATA_UNCT_STR);
    1871             :     }
    1872             : 
    1873          35 :     if (afMax[IDX_UNC_DIR] >= afMin[IDX_UNC_DIR] && afMin[IDX_UNC_DIR] < 0)
    1874             :     {
    1875           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1876             :                  "Negative direction uncertainty value found (%f), which is "
    1877             :                  "not allowed (except nodata value %s)",
    1878           1 :                  afMin[IDX_UNC_DIR], NODATA_UNCT_STR);
    1879             :     }
    1880             : 
    1881          35 :     if (bRet)
    1882             :     {
    1883          35 :         double prevMinSpeed = 0;
    1884          35 :         double prevMaxSpeed = 0;
    1885          35 :         if (GH5_FetchAttribute(m_featureGroup, "minDatasetCurrentSpeed",
    1886          41 :                                prevMinSpeed) &&
    1887           6 :             GH5_FetchAttribute(m_featureGroup, "maxDatasetCurrentSpeed",
    1888             :                                prevMaxSpeed))
    1889             :         {
    1890           6 :             if (afMin[IDX_SPEED] != NODATA_SPEED)
    1891             :             {
    1892           6 :                 prevMinSpeed = std::min<double>(prevMinSpeed, afMin[IDX_SPEED]);
    1893           6 :                 prevMaxSpeed = std::max<double>(prevMaxSpeed, afMax[IDX_SPEED]);
    1894           6 :                 bRet =
    1895           6 :                     GH5_WriteAttribute(m_featureGroup, "minDatasetCurrentSpeed",
    1896          12 :                                        prevMinSpeed) &&
    1897           6 :                     GH5_WriteAttribute(m_featureGroup, "maxDatasetCurrentSpeed",
    1898             :                                        prevMaxSpeed);
    1899             :             }
    1900             :         }
    1901             :         else
    1902             :         {
    1903          58 :             bRet = WriteFloat64Value(m_featureGroup, "minDatasetCurrentSpeed",
    1904          87 :                                      afMin[IDX_SPEED]) &&
    1905          29 :                    WriteFloat64Value(m_featureGroup, "maxDatasetCurrentSpeed",
    1906          29 :                                      afMax[IDX_SPEED]);
    1907             :         }
    1908             :     }
    1909             : 
    1910          70 :     return bRet;
    1911             : }
    1912             : 
    1913             : /************************************************************************/
    1914             : /*                      S111Dataset::CreateCopy()                       */
    1915             : /************************************************************************/
    1916             : 
    1917             : /* static */
    1918          66 : GDALDataset *S111Dataset::CreateCopy(const char *pszFilename,
    1919             :                                      GDALDataset *poSrcDS, int /* bStrict*/,
    1920             :                                      CSLConstList papszOptions,
    1921             :                                      GDALProgressFunc pfnProgress,
    1922             :                                      void *pProgressData)
    1923             : {
    1924         132 :     S111Creator creator(pszFilename, poSrcDS, papszOptions);
    1925          66 :     if (!creator.Create(pfnProgress, pProgressData))
    1926          36 :         return nullptr;
    1927             : 
    1928             :     VSIStatBufL sStatBuf;
    1929          60 :     if (VSIStatL(pszFilename, &sStatBuf) == 0 &&
    1930          30 :         sStatBuf.st_size > 10 * 1024 * 1024)
    1931             :     {
    1932           1 :         CPLError(CE_Warning, CPLE_AppDefined,
    1933             :                  "%s file size exceeds 10 MB, which is the upper limit "
    1934             :                  "suggested for wireless transmission to marine vessels",
    1935             :                  pszFilename);
    1936             :     }
    1937             : 
    1938          60 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    1939          30 :     return Open(&oOpenInfo);
    1940             : }
    1941             : 
    1942             : /************************************************************************/
    1943             : /*                      S111DatasetDriverUnload()                       */
    1944             : /************************************************************************/
    1945             : 
    1946           9 : static void S111DatasetDriverUnload(GDALDriver *)
    1947             : {
    1948           9 :     HDF5UnloadFileDriver();
    1949           9 : }
    1950             : 
    1951             : /************************************************************************/
    1952             : /*                         GDALRegister_S111()                          */
    1953             : /************************************************************************/
    1954          14 : void GDALRegister_S111()
    1955             : 
    1956             : {
    1957          14 :     if (!GDAL_CHECK_VERSION("S111"))
    1958           0 :         return;
    1959             : 
    1960          14 :     if (GDALGetDriverByName(S111_DRIVER_NAME) != nullptr)
    1961           0 :         return;
    1962             : 
    1963          14 :     GDALDriver *poDriver = new GDALDriver();
    1964             : 
    1965          14 :     S111DriverSetCommonMetadata(poDriver);
    1966          14 :     poDriver->pfnOpen = S111Dataset::Open;
    1967          14 :     poDriver->pfnCreateCopy = S111Dataset::CreateCopy;
    1968          14 :     poDriver->pfnUnloadDriver = S111DatasetDriverUnload;
    1969             : 
    1970          14 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1971             : }

Generated by: LCOV version 1.14