LCOV - code coverage report
Current view: top level - frmts/hdf5 - s104dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 794 888 89.4 %
Date: 2026-05-07 11:45:54 Functions: 20 21 95.2 %

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

Generated by: LCOV version 1.14