LCOV - code coverage report
Current view: top level - frmts/hdf5 - s100.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 972 1020 95.3 %
Date: 2026-05-07 11:45:54 Functions: 57 59 96.6 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Hierarchical Data Format Release 5 (HDF5)
       4             :  * Purpose:  Read S100 bathymetric datasets.
       5             :  * Author:   Even Rouault <even dot rouault at spatialys dot com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #ifdef _POSIX_C_SOURCE
      14             : #undef _POSIX_C_SOURCE
      15             : #endif
      16             : 
      17             : #include "cpl_time.h"
      18             : 
      19             : #include "s100.h"
      20             : #include "hdf5dataset.h"
      21             : #include "gh5_convenience.h"
      22             : 
      23             : #include "proj.h"
      24             : #include "proj_experimental.h"
      25             : #include "proj_constants.h"
      26             : #include "ogr_proj_p.h"
      27             : 
      28             : #include <algorithm>
      29             : #include <array>
      30             : #include <cmath>
      31             : #include <limits>
      32             : 
      33             : /************************************************************************/
      34             : /*                          S100BaseDataset()                           */
      35             : /************************************************************************/
      36             : 
      37         224 : S100BaseDataset::S100BaseDataset(const std::string &osFilename)
      38         224 :     : m_osFilename(osFilename)
      39             : {
      40         224 : }
      41             : 
      42             : /************************************************************************/
      43             : /*                                Init()                                */
      44             : /************************************************************************/
      45             : 
      46         224 : bool S100BaseDataset::Init()
      47             : {
      48             :     // Open the file as an HDF5 file.
      49         224 :     hid_t fapl = H5Pcreate(H5P_FILE_ACCESS);
      50         224 :     H5Pset_driver(fapl, HDF5GetFileDriver(), nullptr);
      51         224 :     hid_t hHDF5 = H5Fopen(m_osFilename.c_str(), H5F_ACC_RDONLY, fapl);
      52         224 :     H5Pclose(fapl);
      53         224 :     if (hHDF5 < 0)
      54           0 :         return false;
      55             : 
      56         448 :     auto poSharedResources = GDAL::HDF5SharedResources::Create(m_osFilename);
      57         224 :     poSharedResources->m_hHDF5 = hHDF5;
      58             : 
      59         224 :     m_poRootGroup = HDF5Dataset::OpenGroup(poSharedResources);
      60         224 :     if (m_poRootGroup == nullptr)
      61           0 :         return false;
      62             : 
      63         224 :     S100ReadSRS(m_poRootGroup.get(), m_oSRS);
      64             : 
      65         224 :     S100ReadVerticalDatum(this, m_poRootGroup.get());
      66             : 
      67             :     m_osMetadataFile =
      68         224 :         S100ReadMetadata(this, m_osFilename, m_poRootGroup.get());
      69             : 
      70         224 :     return true;
      71             : }
      72             : 
      73             : /************************************************************************/
      74             : /*                          GetGeoTransform()                           */
      75             : /************************************************************************/
      76             : 
      77          65 : CPLErr S100BaseDataset::GetGeoTransform(GDALGeoTransform &gt) const
      78             : 
      79             : {
      80          65 :     if (m_bHasGT)
      81             :     {
      82          65 :         gt = m_gt;
      83          65 :         return CE_None;
      84             :     }
      85             : 
      86           0 :     return GDALPamDataset::GetGeoTransform(gt);
      87             : }
      88             : 
      89             : /************************************************************************/
      90             : /*                           GetSpatialRef()                            */
      91             : /************************************************************************/
      92             : 
      93          97 : const OGRSpatialReference *S100BaseDataset::GetSpatialRef() const
      94             : {
      95          97 :     if (!m_oSRS.IsEmpty())
      96          97 :         return &m_oSRS;
      97           0 :     return GDALPamDataset::GetSpatialRef();
      98             : }
      99             : 
     100             : /************************************************************************/
     101             : /*                            GetFileList()                             */
     102             : /************************************************************************/
     103             : 
     104          12 : char **S100BaseDataset::GetFileList()
     105             : {
     106          12 :     char **papszFileList = GDALPamDataset::GetFileList();
     107          12 :     if (!m_osMetadataFile.empty())
     108           4 :         papszFileList = CSLAddString(papszFileList, m_osMetadataFile.c_str());
     109          12 :     return papszFileList;
     110             : }
     111             : 
     112             : /************************************************************************/
     113             : /*                            S100ReadSRS()                             */
     114             : /************************************************************************/
     115             : 
     116             : constexpr int PROJECTION_METHOD_MERCATOR = 9805;
     117             : static_assert(PROJECTION_METHOD_MERCATOR ==
     118             :               EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
     119             : constexpr int PROJECTION_METHOD_TRANSVERSE_MERCATOR = 9807;
     120             : static_assert(PROJECTION_METHOD_TRANSVERSE_MERCATOR ==
     121             :               EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
     122             : constexpr int PROJECTION_METHOD_OBLIQUE_MERCATOR = 9815;
     123             : static_assert(PROJECTION_METHOD_OBLIQUE_MERCATOR ==
     124             :               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
     125             : constexpr int PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR = 9812;
     126             : static_assert(PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR ==
     127             :               EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
     128             : constexpr int PROJECTION_METHOD_LCC_1SP = 9801;
     129             : static_assert(PROJECTION_METHOD_LCC_1SP ==
     130             :               EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
     131             : constexpr int PROJECTION_METHOD_LCC_2SP = 9802;
     132             : static_assert(PROJECTION_METHOD_LCC_2SP ==
     133             :               EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
     134             : constexpr int PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC = 9809;
     135             : static_assert(PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC ==
     136             :               EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC);
     137             : constexpr int PROJECTION_METHOD_POLAR_STEREOGRAPHIC = 9810;
     138             : static_assert(PROJECTION_METHOD_POLAR_STEREOGRAPHIC ==
     139             :               EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
     140             : constexpr int PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL = 9819;
     141             : static_assert(PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL ==
     142             :               EPSG_CODE_METHOD_KROVAK);
     143             : constexpr int PROJECTION_METHOD_AMERICAN_POLYCONIC = 9818;
     144             : static_assert(PROJECTION_METHOD_AMERICAN_POLYCONIC ==
     145             :               EPSG_CODE_METHOD_AMERICAN_POLYCONIC);
     146             : constexpr int PROJECTION_METHOD_ALBERS_EQUAL_AREA = 9822;
     147             : static_assert(PROJECTION_METHOD_ALBERS_EQUAL_AREA ==
     148             :               EPSG_CODE_METHOD_ALBERS_EQUAL_AREA);
     149             : constexpr int PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA = 9820;
     150             : static_assert(PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA ==
     151             :               EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
     152             : 
     153         328 : bool S100ReadSRS(const GDALGroup *poRootGroup, OGRSpatialReference &oSRS)
     154             : {
     155             :     // Get SRS
     156         328 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     157         984 :     auto poHorizontalCRS = poRootGroup->GetAttribute("horizontalCRS");
     158         633 :     if (poHorizontalCRS &&
     159         633 :         poHorizontalCRS->GetDataType().GetClass() == GEDTC_NUMERIC)
     160             :     {
     161             :         // horizontalCRS is v2.2
     162         305 :         const int nHorizontalCRS = poHorizontalCRS->ReadAsInt();
     163         305 :         if (nHorizontalCRS > 0)
     164             :         {
     165         195 :             if (oSRS.importFromEPSG(nHorizontalCRS) != OGRERR_NONE)
     166             :             {
     167           0 :                 oSRS.Clear();
     168             :             }
     169             :         }
     170             :         else
     171             :         {
     172             :             auto poNameOfHorizontalCRS =
     173         220 :                 poRootGroup->GetAttribute("nameOfHorizontalCRS");
     174             :             auto poTypeOfHorizontalCRS =
     175         220 :                 poRootGroup->GetAttribute("typeOfHorizontalCRS");
     176         220 :             auto poHorizontalCS = poRootGroup->GetAttribute("horizontalCS");
     177             :             auto poHorizontalDatum =
     178         220 :                 poRootGroup->GetAttribute("horizontalDatum");
     179         110 :             if (poNameOfHorizontalCRS &&
     180         110 :                 poNameOfHorizontalCRS->GetDataType().GetClass() ==
     181         220 :                     GEDTC_STRING &&
     182         220 :                 poTypeOfHorizontalCRS &&
     183         110 :                 poTypeOfHorizontalCRS->GetDataType().GetClass() ==
     184         110 :                     GEDTC_NUMERIC &&
     185         220 :                 poHorizontalCS &&
     186         220 :                 poHorizontalCS->GetDataType().GetClass() == GEDTC_NUMERIC &&
     187         330 :                 poHorizontalDatum &&
     188         110 :                 poHorizontalDatum->GetDataType().GetClass() == GEDTC_NUMERIC)
     189             :             {
     190         110 :                 const int nCRSType = poTypeOfHorizontalCRS->ReadAsInt();
     191         110 :                 constexpr int CRS_TYPE_GEOGRAPHIC = 1;
     192         110 :                 constexpr int CRS_TYPE_PROJECTED = 2;
     193             : 
     194         110 :                 PJ *crs = nullptr;
     195         110 :                 const char *pszCRSName = poNameOfHorizontalCRS->ReadAsString();
     196             : 
     197         110 :                 const int nHorizontalCS = poHorizontalCS->ReadAsInt();
     198         110 :                 const int nHorizontalDatum = poHorizontalDatum->ReadAsInt();
     199             : 
     200         110 :                 auto pjCtxt = OSRGetProjTLSContext();
     201         110 :                 if (nHorizontalDatum < 0)
     202             :                 {
     203             :                     auto poNameOfHorizontalDatum =
     204          30 :                         poRootGroup->GetAttribute("nameOfHorizontalDatum");
     205             :                     auto poPrimeMeridian =
     206          30 :                         poRootGroup->GetAttribute("primeMeridian");
     207          30 :                     auto poSpheroid = poRootGroup->GetAttribute("spheroid");
     208          15 :                     if (poNameOfHorizontalDatum &&
     209          15 :                         poNameOfHorizontalDatum->GetDataType().GetClass() ==
     210          30 :                             GEDTC_STRING &&
     211          28 :                         poPrimeMeridian &&
     212          13 :                         poPrimeMeridian->GetDataType().GetClass() ==
     213          13 :                             GEDTC_NUMERIC &&
     214          43 :                         poSpheroid &&
     215          13 :                         poSpheroid->GetDataType().GetClass() == GEDTC_NUMERIC)
     216             :                     {
     217             :                         const char *pszDatumName =
     218          13 :                             poNameOfHorizontalDatum->ReadAsString();
     219          13 :                         const char *pszGeogCRSName =
     220             :                             nCRSType == CRS_TYPE_PROJECTED
     221          20 :                                 ? (pszDatumName ? pszDatumName : "unknown")
     222           7 :                                 : (pszCRSName ? pszCRSName : "unknown");
     223             : 
     224          13 :                         const int nPrimeMeridian = poPrimeMeridian->ReadAsInt();
     225          13 :                         const int nSpheroid = poSpheroid->ReadAsInt();
     226          13 :                         PJ *prime_meridian = proj_create_from_database(
     227             :                             pjCtxt, "EPSG", CPLSPrintf("%d", nPrimeMeridian),
     228             :                             PJ_CATEGORY_PRIME_MERIDIAN, false, nullptr);
     229          13 :                         if (!prime_meridian)
     230           0 :                             return false;
     231          13 :                         PJ *spheroid = proj_create_from_database(
     232             :                             pjCtxt, "EPSG", CPLSPrintf("%d", nSpheroid),
     233             :                             PJ_CATEGORY_ELLIPSOID, false, nullptr);
     234          13 :                         if (!spheroid)
     235             :                         {
     236           0 :                             proj_destroy(prime_meridian);
     237           0 :                             return false;
     238             :                         }
     239             : 
     240          13 :                         double semiMajorMetre = 0;
     241          13 :                         double invFlattening = 0;
     242          13 :                         proj_ellipsoid_get_parameters(pjCtxt, spheroid,
     243             :                                                       &semiMajorMetre, nullptr,
     244             :                                                       nullptr, &invFlattening);
     245             : 
     246          13 :                         double primeMeridianOffset = 0;
     247          13 :                         double primeMeridianUnitConv = 1;
     248          13 :                         const char *primeMeridianUnitName = nullptr;
     249          13 :                         proj_prime_meridian_get_parameters(
     250             :                             pjCtxt, prime_meridian, &primeMeridianOffset,
     251             :                             &primeMeridianUnitConv, &primeMeridianUnitName);
     252             : 
     253          13 :                         PJ *ellipsoidalCS = proj_create_ellipsoidal_2D_cs(
     254             :                             pjCtxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr,
     255             :                             0.0);
     256             : 
     257          13 :                         crs = proj_create_geographic_crs(
     258             :                             pjCtxt, pszGeogCRSName,
     259             :                             pszDatumName ? pszDatumName : "unknown",
     260             :                             proj_get_name(spheroid), semiMajorMetre,
     261             :                             invFlattening, proj_get_name(prime_meridian),
     262             :                             primeMeridianOffset, primeMeridianUnitName,
     263             :                             primeMeridianUnitConv, ellipsoidalCS);
     264             : 
     265          13 :                         proj_destroy(ellipsoidalCS);
     266          13 :                         proj_destroy(spheroid);
     267          13 :                         proj_destroy(prime_meridian);
     268             :                     }
     269             :                     else
     270             :                     {
     271           2 :                         CPLError(CE_Warning, CPLE_AppDefined,
     272             :                                  "Cannot establish CRS because "
     273             :                                  "nameOfHorizontalDatum, primeMeridian and/or "
     274             :                                  "spheroid are missing");
     275           2 :                         return false;
     276             :                     }
     277             :                 }
     278             :                 else
     279             :                 {
     280          95 :                     PJ *datum = proj_create_from_database(
     281             :                         pjCtxt, "EPSG", CPLSPrintf("%d", nHorizontalDatum),
     282             :                         PJ_CATEGORY_DATUM, false, nullptr);
     283          95 :                     if (!datum)
     284           0 :                         return false;
     285          95 :                     const char *pszDatumName = proj_get_name(datum);
     286          95 :                     const char *pszGeogCRSName =
     287             :                         nCRSType == CRS_TYPE_PROJECTED
     288         105 :                             ? pszDatumName
     289          10 :                             : (pszCRSName ? pszCRSName : "unknown");
     290             : 
     291          95 :                     PJ *ellipsoidalCS = proj_create_ellipsoidal_2D_cs(
     292             :                         pjCtxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr, 0.0);
     293             : 
     294          95 :                     crs = proj_create_geographic_crs_from_datum(
     295             :                         pjCtxt, pszGeogCRSName, datum, ellipsoidalCS);
     296             : 
     297          95 :                     proj_destroy(ellipsoidalCS);
     298          95 :                     proj_destroy(datum);
     299             :                 }
     300             : 
     301         108 :                 if (nCRSType == CRS_TYPE_PROJECTED)
     302             :                 {
     303             :                     auto poProjectionMethod =
     304         182 :                         poRootGroup->GetAttribute("projectionMethod");
     305         181 :                     if (poProjectionMethod &&
     306          90 :                         poProjectionMethod->GetDataType().GetClass() ==
     307          91 :                             GEDTC_NUMERIC)
     308             :                     {
     309             :                         const int nProjectionMethod =
     310          90 :                             poProjectionMethod->ReadAsInt();
     311             : 
     312             :                         auto poFalseEasting =
     313         180 :                             poRootGroup->GetAttribute("falseEasting");
     314             :                         const double falseEasting =
     315          90 :                             poFalseEasting &&
     316          90 :                                     poFalseEasting->GetDataType().GetClass() ==
     317          90 :                                         GEDTC_NUMERIC
     318         180 :                                 ? poFalseEasting->ReadAsDouble()
     319          90 :                                 : 0.0;
     320             : 
     321             :                         auto poFalseNorthing =
     322         180 :                             poRootGroup->GetAttribute("falseNorthing");
     323             :                         const double falseNorthing =
     324          90 :                             poFalseNorthing &&
     325          90 :                                     poFalseNorthing->GetDataType().GetClass() ==
     326          90 :                                         GEDTC_NUMERIC
     327         180 :                                 ? poFalseNorthing->ReadAsDouble()
     328          90 :                                 : 0.0;
     329             : 
     330         299 :                         const auto getParameter = [poRootGroup](int nParam)
     331             :                         {
     332             :                             auto poParam = poRootGroup->GetAttribute(
     333         897 :                                 "projectionParameter" + std::to_string(nParam));
     334         299 :                             if (poParam && poParam->GetDataType().GetClass() ==
     335         299 :                                                GEDTC_NUMERIC)
     336         299 :                                 return poParam->ReadAsDouble();
     337           0 :                             CPLError(
     338             :                                 CE_Warning, CPLE_AppDefined, "%s",
     339           0 :                                 std::string(
     340             :                                     "Missing attribute projectionParameter")
     341           0 :                                     .append(std::to_string(nParam))
     342             :                                     .c_str());
     343           0 :                             return 0.0;
     344          90 :                         };
     345             : 
     346          90 :                         PJ *conv = nullptr;
     347          90 :                         switch (nProjectionMethod)
     348             :                         {
     349          13 :                             case PROJECTION_METHOD_MERCATOR:
     350             :                             {
     351             :                                 conv =
     352          13 :                                     proj_create_conversion_mercator_variant_b(
     353             :                                         pjCtxt,
     354             :                                         /* latitude_first_parallel = */
     355             :                                         getParameter(1),
     356             :                                         /* center_long = */ getParameter(2),
     357             :                                         falseEasting, falseNorthing, nullptr,
     358             :                                         0.0, nullptr, 0.0);
     359          13 :                                 break;
     360             :                             }
     361             : 
     362           7 :                             case PROJECTION_METHOD_TRANSVERSE_MERCATOR:
     363             :                             {
     364             :                                 conv =
     365           7 :                                     proj_create_conversion_transverse_mercator(
     366             :                                         pjCtxt,
     367             :                                         /* center_lat = */ getParameter(1),
     368             :                                         /* center_long = */ getParameter(2),
     369             :                                         /* scale = */ getParameter(3),
     370             :                                         falseEasting, falseNorthing, nullptr,
     371             :                                         0.0, nullptr, 0.0);
     372           7 :                                 break;
     373             :                             }
     374             : 
     375           7 :                             case PROJECTION_METHOD_OBLIQUE_MERCATOR:
     376             :                             {
     377             :                                 conv =
     378           7 :                                     proj_create_conversion_hotine_oblique_mercator_variant_b(
     379             :                                         pjCtxt,
     380             :                                         /* latitude_projection_centre = */
     381             :                                         getParameter(1),
     382             :                                         /* longitude_projection_centre = */
     383             :                                         getParameter(2),
     384             :                                         /* azimuth_initial_line = */
     385             :                                         getParameter(3),
     386             :                                         /* angle_from_rectified_to_skrew_grid = */
     387             :                                         getParameter(4),
     388             :                                         /* scale = */ getParameter(5),
     389             :                                         falseEasting, falseNorthing, nullptr,
     390             :                                         0.0, nullptr, 0.0);
     391           7 :                                 break;
     392             :                             }
     393             : 
     394           7 :                             case PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR:
     395             :                             {
     396             :                                 conv =
     397           7 :                                     proj_create_conversion_hotine_oblique_mercator_variant_a(
     398             :                                         pjCtxt,
     399             :                                         /* latitude_projection_centre = */
     400             :                                         getParameter(1),
     401             :                                         /* longitude_projection_centre = */
     402             :                                         getParameter(2),
     403             :                                         /* azimuth_initial_line = */
     404             :                                         getParameter(3),
     405             :                                         /* angle_from_rectified_to_skrew_grid = */
     406             :                                         getParameter(4),
     407             :                                         /* scale = */ getParameter(5),
     408             :                                         falseEasting, falseNorthing, nullptr,
     409             :                                         0.0, nullptr, 0.0);
     410           7 :                                 break;
     411             :                             }
     412             : 
     413           7 :                             case PROJECTION_METHOD_LCC_1SP:
     414             :                             {
     415             :                                 conv =
     416           7 :                                     proj_create_conversion_lambert_conic_conformal_1sp(
     417             :                                         pjCtxt,
     418             :                                         /* center_lat = */ getParameter(1),
     419             :                                         /* center_long = */ getParameter(2),
     420             :                                         /* scale = */ getParameter(3),
     421             :                                         falseEasting, falseNorthing, nullptr,
     422             :                                         0.0, nullptr, 0.0);
     423           7 :                                 break;
     424             :                             }
     425             : 
     426           7 :                             case PROJECTION_METHOD_LCC_2SP:
     427             :                             {
     428             :                                 conv =
     429           7 :                                     proj_create_conversion_lambert_conic_conformal_2sp(
     430             :                                         pjCtxt,
     431             :                                         /* latitude_false_origin = */
     432             :                                         getParameter(1),
     433             :                                         /* longitude_false_origin = */
     434             :                                         getParameter(2),
     435             :                                         /* latitude_first_parallel = */
     436             :                                         getParameter(3),
     437             :                                         /* latitude_second_parallel = */
     438             :                                         getParameter(4), falseEasting,
     439             :                                         falseNorthing, nullptr, 0.0, nullptr,
     440             :                                         0.0);
     441           7 :                                 break;
     442             :                             }
     443             : 
     444           7 :                             case PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC:
     445             :                             {
     446             :                                 conv =
     447           7 :                                     proj_create_conversion_oblique_stereographic(
     448             :                                         pjCtxt,
     449             :                                         /* center_lat = */ getParameter(1),
     450             :                                         /* center_long = */ getParameter(2),
     451             :                                         /* scale = */ getParameter(3),
     452             :                                         falseEasting, falseNorthing, nullptr,
     453             :                                         0.0, nullptr, 0.0);
     454           7 :                                 break;
     455             :                             }
     456             : 
     457           7 :                             case PROJECTION_METHOD_POLAR_STEREOGRAPHIC:
     458             :                             {
     459             :                                 conv =
     460           7 :                                     proj_create_conversion_polar_stereographic_variant_a(
     461             :                                         pjCtxt,
     462             :                                         /* center_lat = */ getParameter(1),
     463             :                                         /* center_long = */ getParameter(2),
     464             :                                         /* scale = */ getParameter(3),
     465             :                                         falseEasting, falseNorthing, nullptr,
     466             :                                         0.0, nullptr, 0.0);
     467           7 :                                 break;
     468             :                             }
     469             : 
     470           7 :                             case PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL:
     471             :                             {
     472           7 :                                 conv = proj_create_conversion_krovak(
     473             :                                     pjCtxt,
     474             :                                     /* latitude_projection_centre = */
     475             :                                     getParameter(1),
     476             :                                     /* longitude_of_origin = */ getParameter(2),
     477             :                                     /* colatitude_cone_axis = */
     478             :                                     getParameter(3),
     479             :                                     /* latitude_pseudo_standard_parallel = */
     480             :                                     getParameter(4),
     481             :                                     /* scale_factor_pseudo_standard_parallel = */
     482             :                                     getParameter(5), falseEasting,
     483             :                                     falseNorthing, nullptr, 0.0, nullptr, 0.0);
     484           7 :                                 break;
     485             :                             }
     486             : 
     487           7 :                             case PROJECTION_METHOD_AMERICAN_POLYCONIC:
     488             :                             {
     489             :                                 conv =
     490           7 :                                     proj_create_conversion_american_polyconic(
     491             :                                         pjCtxt,
     492             :                                         /* center_lat = */ getParameter(1),
     493             :                                         /* center_long = */ getParameter(2),
     494             :                                         falseEasting, falseNorthing, nullptr,
     495             :                                         0.0, nullptr, 0.0);
     496           7 :                                 break;
     497             :                             }
     498             : 
     499           7 :                             case PROJECTION_METHOD_ALBERS_EQUAL_AREA:
     500             :                             {
     501           7 :                                 conv = proj_create_conversion_albers_equal_area(
     502             :                                     pjCtxt,
     503             :                                     /* latitude_false_origin = */
     504             :                                     getParameter(1),
     505             :                                     /* longitude_false_origin = */
     506             :                                     getParameter(2),
     507             :                                     /* latitude_first_parallel = */
     508             :                                     getParameter(3),
     509             :                                     /* latitude_second_parallel = */
     510             :                                     getParameter(4), falseEasting,
     511             :                                     falseNorthing, nullptr, 0.0, nullptr, 0.0);
     512           7 :                                 break;
     513             :                             }
     514             : 
     515           7 :                             case PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA:
     516             :                             {
     517             :                                 conv =
     518           7 :                                     proj_create_conversion_lambert_azimuthal_equal_area(
     519             :                                         pjCtxt,
     520             :                                         /* latitude_nat_origin = */
     521             :                                         getParameter(1),
     522             :                                         /* longitude_nat_origin = */
     523             :                                         getParameter(2), falseEasting,
     524             :                                         falseNorthing, nullptr, 0.0, nullptr,
     525             :                                         0.0);
     526           7 :                                 break;
     527             :                             }
     528             : 
     529           0 :                             default:
     530             :                             {
     531           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     532             :                                          "Cannot establish CRS because "
     533             :                                          "projectionMethod = %d is unhandled",
     534             :                                          nProjectionMethod);
     535           0 :                                 proj_destroy(crs);
     536           0 :                                 return false;
     537             :                             }
     538             :                         }
     539             : 
     540          90 :                         constexpr int HORIZONTAL_CS_EASTING_NORTHING_METRE =
     541             :                             4400;
     542             :                         // constexpr int HORIZONTAL_CS_NORTHING_EASTING_METRE = 4500;
     543         180 :                         PJ *cartesianCS = proj_create_cartesian_2D_cs(
     544             :                             pjCtxt,
     545             :                             nHorizontalCS ==
     546             :                                     HORIZONTAL_CS_EASTING_NORTHING_METRE
     547          90 :                                 ? PJ_CART2D_EASTING_NORTHING
     548             :                                 : PJ_CART2D_NORTHING_EASTING,
     549             :                             nullptr, 0.0);
     550             : 
     551          90 :                         PJ *projCRS = proj_create_projected_crs(
     552             :                             pjCtxt, pszCRSName ? pszCRSName : "unknown", crs,
     553             :                             conv, cartesianCS);
     554          90 :                         proj_destroy(crs);
     555          90 :                         crs = projCRS;
     556          90 :                         proj_destroy(conv);
     557          90 :                         proj_destroy(cartesianCS);
     558             :                     }
     559             :                     else
     560             :                     {
     561           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
     562             :                                  "Cannot establish CRS because "
     563             :                                  "projectionMethod is missing");
     564           1 :                         proj_destroy(crs);
     565           1 :                         return false;
     566             :                     }
     567             :                 }
     568          17 :                 else if (nCRSType != CRS_TYPE_GEOGRAPHIC)
     569             :                 {
     570           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     571             :                              "Cannot establish CRS because of invalid value "
     572             :                              "for typeOfHorizontalCRS: %d",
     573             :                              nCRSType);
     574           0 :                     proj_destroy(crs);
     575           0 :                     return false;
     576             :                 }
     577             : 
     578             :                 const char *pszPROJJSON =
     579         107 :                     proj_as_projjson(pjCtxt, crs, nullptr);
     580         107 :                 oSRS.SetFromUserInput(pszPROJJSON);
     581             : 
     582         107 :                 proj_destroy(crs);
     583             :             }
     584             :             else
     585             :             {
     586           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     587             :                          "Cannot establish CRS because nameOfHorizontalCRS, "
     588             :                          "typeOfHorizontalCRS, horizontalCS and/or "
     589             :                          "horizontalDatum are missing");
     590           0 :                 return false;
     591             :             }
     592             :         }
     593             :     }
     594             :     else
     595             :     {
     596             :         auto poHorizontalDatumReference =
     597          69 :             poRootGroup->GetAttribute("horizontalDatumReference");
     598             :         auto poHorizontalDatumValue =
     599          69 :             poRootGroup->GetAttribute("horizontalDatumValue");
     600          23 :         if (poHorizontalDatumReference && poHorizontalDatumValue)
     601             :         {
     602             :             const char *pszAuthName =
     603          23 :                 poHorizontalDatumReference->ReadAsString();
     604          23 :             const char *pszAuthCode = poHorizontalDatumValue->ReadAsString();
     605          23 :             if (pszAuthName && pszAuthCode)
     606             :             {
     607          46 :                 if (oSRS.SetFromUserInput(
     608          46 :                         (std::string(pszAuthName) + ':' + pszAuthCode).c_str(),
     609             :                         OGRSpatialReference::
     610          23 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
     611             :                     OGRERR_NONE)
     612             :                 {
     613           0 :                     oSRS.Clear();
     614             :                 }
     615             :             }
     616             :         }
     617             :     }
     618         325 :     return !oSRS.IsEmpty();
     619             : }
     620             : 
     621             : /************************************************************************/
     622             : /*              S100GetNumPointsLongitudinalLatitudinal()               */
     623             : /************************************************************************/
     624             : 
     625         454 : bool S100GetNumPointsLongitudinalLatitudinal(const GDALGroup *poGroup,
     626             :                                              int &nNumPointsLongitudinal,
     627             :                                              int &nNumPointsLatitudinal)
     628             : {
     629        1362 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     630        1362 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     631             :     auto poNumPointsLongitudinal =
     632        1362 :         poGroup->GetAttribute("numPointsLongitudinal");
     633        1362 :     auto poNumPointsLatitudinal = poGroup->GetAttribute("numPointsLatitudinal");
     634         454 :     if (poSpacingX &&
     635        1362 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     636         908 :         poSpacingY &&
     637         908 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     638         454 :         poNumPointsLongitudinal &&
     639         454 :         GDALDataTypeIsInteger(
     640         908 :             poNumPointsLongitudinal->GetDataType().GetNumericDataType()) &&
     641        1362 :         poNumPointsLatitudinal &&
     642         454 :         GDALDataTypeIsInteger(
     643         454 :             poNumPointsLatitudinal->GetDataType().GetNumericDataType()))
     644             :     {
     645         454 :         nNumPointsLongitudinal = poNumPointsLongitudinal->ReadAsInt();
     646         454 :         nNumPointsLatitudinal = poNumPointsLatitudinal->ReadAsInt();
     647             : 
     648             :         // Those are optional, but use them when available, to detect
     649             :         // potential inconsistency
     650        1362 :         auto poEastBoundLongitude = poGroup->GetAttribute("eastBoundLongitude");
     651        1362 :         auto poWestBoundLongitude = poGroup->GetAttribute("westBoundLongitude");
     652             :         auto poSouthBoundLongitude =
     653        1362 :             poGroup->GetAttribute("southBoundLatitude");
     654         908 :         auto poNorthBoundLatitude = poGroup->GetAttribute("northBoundLatitude");
     655         807 :         if (poEastBoundLongitude &&
     656         353 :             GDALDataTypeIsFloating(
     657        1160 :                 poEastBoundLongitude->GetDataType().GetNumericDataType()) &&
     658         353 :             poWestBoundLongitude &&
     659         353 :             GDALDataTypeIsFloating(
     660         706 :                 poWestBoundLongitude->GetDataType().GetNumericDataType()) &&
     661         353 :             poSouthBoundLongitude &&
     662         353 :             GDALDataTypeIsFloating(
     663         706 :                 poSouthBoundLongitude->GetDataType().GetNumericDataType()) &&
     664        1160 :             poNorthBoundLatitude &&
     665         353 :             GDALDataTypeIsFloating(
     666         353 :                 poNorthBoundLatitude->GetDataType().GetNumericDataType()))
     667             :         {
     668         353 :             const double dfSpacingX = poSpacingX->ReadAsDouble();
     669         353 :             const double dfSpacingY = poSpacingY->ReadAsDouble();
     670             : 
     671         353 :             const double dfEast = poEastBoundLongitude->ReadAsDouble();
     672         353 :             const double dfWest = poWestBoundLongitude->ReadAsDouble();
     673         353 :             const double dfSouth = poSouthBoundLongitude->ReadAsDouble();
     674         353 :             const double dfNorth = poNorthBoundLatitude->ReadAsDouble();
     675         353 :             if (std::fabs((dfWest + nNumPointsLongitudinal * dfSpacingX) -
     676         353 :                           dfEast) < 5 * dfSpacingX &&
     677         353 :                 std::fabs((dfSouth + nNumPointsLatitudinal * dfSpacingY) -
     678         353 :                           dfNorth) < 5 * dfSpacingY)
     679             :             {
     680             :                 // We need up to 5 spacings for product
     681             :                 // S-111 Trial Data Set Release 1.1/111UK_20210401T000000Z_SolentAndAppr_dcf2.h5
     682             :             }
     683             :             else
     684             :             {
     685           0 :                 CPLError(
     686             :                     CE_Warning, CPLE_AppDefined,
     687             :                     "Caution: "
     688             :                     "eastBoundLongitude/westBoundLongitude/southBoundLatitude/"
     689             :                     "northBoundLatitude/gridSpacingLongitudinal/"
     690             :                     "gridSpacingLatitudinal/numPointsLongitudinal/"
     691             :                     "numPointsLatitudinal do not seem to be consistent");
     692           0 :                 CPLDebug("S100", "Computed east = %f. Actual = %f",
     693           0 :                          dfWest + nNumPointsLongitudinal * dfSpacingX, dfEast);
     694           0 :                 CPLDebug("S100", "Computed north = %f. Actual = %f",
     695           0 :                          dfSouth + nNumPointsLatitudinal * dfSpacingY, dfNorth);
     696             :             }
     697             :         }
     698             : 
     699         454 :         return true;
     700             :     }
     701           0 :     return false;
     702             : }
     703             : 
     704             : /************************************************************************/
     705             : /*                        S100GetGeoTransform()                         */
     706             : /************************************************************************/
     707             : 
     708         197 : bool S100GetGeoTransform(const GDALGroup *poGroup, GDALGeoTransform &gt,
     709             :                          bool bNorthUp)
     710             : {
     711         591 :     auto poOriginX = poGroup->GetAttribute("gridOriginLongitude");
     712         591 :     auto poOriginY = poGroup->GetAttribute("gridOriginLatitude");
     713         591 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     714         591 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     715         197 :     if (poOriginX &&
     716         591 :         poOriginX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     717         394 :         poOriginY &&
     718         394 :         poOriginY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     719         394 :         poSpacingX &&
     720         394 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     721         591 :         poSpacingY &&
     722         197 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64)
     723             :     {
     724         197 :         int nNumPointsLongitudinal = 0;
     725         197 :         int nNumPointsLatitudinal = 0;
     726         197 :         if (!S100GetNumPointsLongitudinalLatitudinal(
     727             :                 poGroup, nNumPointsLongitudinal, nNumPointsLatitudinal))
     728           0 :             return false;
     729             : 
     730         197 :         const double dfSpacingX = poSpacingX->ReadAsDouble();
     731         197 :         const double dfSpacingY = poSpacingY->ReadAsDouble();
     732             : 
     733         197 :         gt.xorig = poOriginX->ReadAsDouble();
     734         197 :         gt.yorig = poOriginY->ReadAsDouble() +
     735         197 :                    (bNorthUp ? dfSpacingY * (nNumPointsLatitudinal - 1) : 0);
     736         197 :         gt.xscale = dfSpacingX;
     737         197 :         gt.yscale = bNorthUp ? -dfSpacingY : dfSpacingY;
     738             : 
     739             :         // From pixel-center convention to pixel-corner convention
     740         197 :         gt.xorig -= gt.xscale / 2;
     741         197 :         gt.yorig -= gt.yscale / 2;
     742             : 
     743         197 :         return true;
     744             :     }
     745           0 :     return false;
     746             : }
     747             : 
     748             : /************************************************************************/
     749             : /*                         S100GetDimensions()                          */
     750             : /************************************************************************/
     751             : 
     752         104 : bool S100GetDimensions(
     753             :     const GDALGroup *poGroup,
     754             :     std::vector<std::shared_ptr<GDALDimension>> &apoDims,
     755             :     std::vector<std::shared_ptr<GDALMDArray>> &apoIndexingVars)
     756             : {
     757         312 :     auto poOriginX = poGroup->GetAttribute("gridOriginLongitude");
     758         312 :     auto poOriginY = poGroup->GetAttribute("gridOriginLatitude");
     759         312 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     760         312 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     761         104 :     if (poOriginX &&
     762         312 :         poOriginX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     763         208 :         poOriginY &&
     764         208 :         poOriginY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     765         208 :         poSpacingX &&
     766         208 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     767         312 :         poSpacingY &&
     768         104 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64)
     769             :     {
     770         104 :         int nNumPointsLongitudinal = 0;
     771         104 :         int nNumPointsLatitudinal = 0;
     772         104 :         if (!S100GetNumPointsLongitudinalLatitudinal(
     773             :                 poGroup, nNumPointsLongitudinal, nNumPointsLatitudinal))
     774           0 :             return false;
     775             : 
     776             :         {
     777             :             auto poDim = std::make_shared<GDALDimensionWeakIndexingVar>(
     778         208 :                 std::string(), "Y", GDAL_DIM_TYPE_HORIZONTAL_Y, std::string(),
     779         208 :                 nNumPointsLatitudinal);
     780             :             auto poIndexingVar = GDALMDArrayRegularlySpaced::Create(
     781         312 :                 std::string(), poDim->GetName(), poDim,
     782         416 :                 poOriginY->ReadAsDouble(), poSpacingY->ReadAsDouble(), 0);
     783         104 :             poDim->SetIndexingVariable(poIndexingVar);
     784         104 :             apoDims.emplace_back(poDim);
     785         104 :             apoIndexingVars.emplace_back(poIndexingVar);
     786             :         }
     787             : 
     788             :         {
     789             :             auto poDim = std::make_shared<GDALDimensionWeakIndexingVar>(
     790         208 :                 std::string(), "X", GDAL_DIM_TYPE_HORIZONTAL_X, std::string(),
     791         208 :                 nNumPointsLongitudinal);
     792             :             auto poIndexingVar = GDALMDArrayRegularlySpaced::Create(
     793         312 :                 std::string(), poDim->GetName(), poDim,
     794         416 :                 poOriginX->ReadAsDouble(), poSpacingX->ReadAsDouble(), 0);
     795         104 :             poDim->SetIndexingVariable(poIndexingVar);
     796         104 :             apoDims.emplace_back(poDim);
     797         104 :             apoIndexingVars.emplace_back(poIndexingVar);
     798             :         }
     799             : 
     800         104 :         return true;
     801             :     }
     802           0 :     return false;
     803             : }
     804             : 
     805             : /************************************************************************/
     806             : /*                    SetMetadataForDataDynamicity()                    */
     807             : /************************************************************************/
     808             : 
     809         123 : void S100BaseDataset::SetMetadataForDataDynamicity(const GDALAttribute *poAttr)
     810             : {
     811         123 :     if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
     812             :     {
     813         123 :         const int nVal = poAttr->ReadAsInt();
     814             :         const std::map<int, std::pair<const char *, const char *>> map = {
     815             :             {1,
     816             :              {"observation", "Values from in-situ sensor(s); may be quality "
     817             :                              "controlled and stored after collection"}},
     818             :             {2,
     819             :              {"astronomicalPrediction",
     820             :               "Values computed using harmonic analysis or other proven method "
     821             :               "of tidal analysis"}},
     822             :             {3,
     823             :              {"analysisOrHybrid",
     824             :               "Values calculated by statistical or other indirect methods, or "
     825             :               "a combination of methods"}},
     826             :             {3,
     827             :              {"hydrodynamicHindcast",
     828             :               "Values calculated from a two- or three-dimensional dynamic "
     829             :               "simulation of past conditions using only observed data for "
     830             :               "boundary forcing, via statistical method or combination"}},
     831             :             {5,
     832             :              {"hydrodynamicForecast",
     833             :               "Values calculated from a two- or three-dimensional dynamic "
     834             :               "simulation of future conditions using predicted data for "
     835             :               "boundary forcing, via statistical method or combination"}},
     836             :             {6,
     837             :              {"observedMinusPredicted",
     838             :               "Values computed as observed minus predicted values"}},
     839             :             {7,
     840             :              {"observedMinusAnalysis",
     841             :               "Values computed as observed minus analysis values"}},
     842             :             {8,
     843             :              {"observedMinusHindcast",
     844             :               "Values computed as observed minus hindcast values"}},
     845             :             {9,
     846             :              {"observedMinusForecast",
     847             :               "Values computed as observed minus forecast values"}},
     848             :             {10,
     849             :              {"forecastMinusPredicted",
     850             :               "Values computed as forecast minus predicted values"}},
     851         246 :         };
     852         123 :         const auto oIter = map.find(nVal);
     853         123 :         if (oIter != map.end())
     854             :         {
     855         246 :             GDALDataset::SetMetadataItem("DATA_DYNAMICITY_NAME",
     856         123 :                                          oIter->second.first);
     857         246 :             GDALDataset::SetMetadataItem("DATA_DYNAMICITY_DEFINITION",
     858         123 :                                          oIter->second.second);
     859             :         }
     860             :     }
     861         123 : }
     862             : 
     863             : /************************************************************************/
     864             : /*                   SetMetadataForCommonPointRule()                    */
     865             : /************************************************************************/
     866             : 
     867         139 : void S100BaseDataset::SetMetadataForCommonPointRule(const GDALAttribute *poAttr)
     868             : {
     869         139 :     if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
     870             :     {
     871         139 :         const int nVal = poAttr->ReadAsInt();
     872             :         const std::map<int, std::pair<const char *, const char *>> map = {
     873             :             {1, {"average", "return the mean of the attribute values"}},
     874             :             {2, {"low", "use the least of the attribute values"}},
     875             :             {3, {"high", "use the greatest of the attribute values"}},
     876             :             {4,
     877             :              {"all", "return all the attribute values that can be determined "
     878             :                      "for the position"}},
     879         278 :         };
     880         139 :         const auto oIter = map.find(nVal);
     881         139 :         if (oIter != map.end())
     882             :         {
     883         278 :             GDALDataset::SetMetadataItem("COMMON_POINT_RULE_NAME",
     884         139 :                                          oIter->second.first);
     885         278 :             GDALDataset::SetMetadataItem("COMMON_POINT_RULE_DEFINITION",
     886         139 :                                          oIter->second.second);
     887             :         }
     888             :     }
     889         139 : }
     890             : 
     891             : /************************************************************************/
     892             : /*                  SetMetadataForInterpolationType()                   */
     893             : /************************************************************************/
     894             : 
     895         139 : void S100BaseDataset::SetMetadataForInterpolationType(
     896             :     const GDALAttribute *poAttr)
     897             : {
     898         139 :     if (poAttr && poAttr->GetDataType().GetClass() == GEDTC_NUMERIC)
     899             :     {
     900         139 :         const int nVal = poAttr->ReadAsInt();
     901             :         const std::map<int, std::pair<const char *, const char *>> map = {
     902             :             {1,
     903             :              {"nearestneighbor",
     904             :               "Assign the feature attribute value associated with the nearest "
     905             :               "domain object in the domain of the coverage"}},
     906             :             {5,
     907             :              {"bilinear", "Assign a value computed by using a bilinear "
     908             :                           "function of position within the grid cell"}},
     909             :             {6,
     910             :              {"biquadratic", "Assign a value computed by using a biquadratic "
     911             :                              "function of position within the grid cell"}},
     912             :             {7,
     913             :              {"bicubic", "Assign a value computed by using a bicubic function "
     914             :                          "of position within the grid cell"}},
     915             :             {8,
     916             :              {"lostarea", "Assign a value computed by using the lost area "
     917             :                           "method described in ISO 19123"}},
     918             :             {9,
     919             :              {"barycentric", "Assign a value computed by using the barycentric "
     920             :                              "method described in ISO 19123"}},
     921             :             {10,
     922             :              {"discrete", "No interpolation method applies to the coverage"}},
     923         278 :         };
     924         139 :         const auto oIter = map.find(nVal);
     925         139 :         if (oIter != map.end())
     926             :         {
     927         278 :             GDALDataset::SetMetadataItem("INTERPOLATION_TYPE_NAME",
     928         139 :                                          oIter->second.first);
     929         278 :             GDALDataset::SetMetadataItem("INTERPOLATION_TYPE_DEFINITION",
     930         139 :                                          oIter->second.second);
     931             :         }
     932             :     }
     933         139 : }
     934             : 
     935             : /************************************************************************/
     936             : /*                          gasVerticalDatums                           */
     937             : /************************************************************************/
     938             : 
     939             : // https://iho.int/uploads/user/pubs/standards/s-100/S-100_5.2.0_Final_Clean.pdf
     940             : // Table 10c-25 - Vertical and sounding datum, page 53
     941             : static const struct
     942             : {
     943             :     int nCode;
     944             :     const char *pszName;
     945             :     const char *pszAbbrev;
     946             :     const char *pszDefinition;
     947             : } gasVerticalDatums[] = {
     948             :     {1, "meanLowWaterSprings", "MLWS",
     949             :      "The average height of the low waters of spring tides. This level is used "
     950             :      "as a tidal datum in some areas. Also called spring low water."},
     951             :     {2, "meanLowerLowWaterSprings", nullptr,
     952             :      "The average height of lower low water springs at a place"},
     953             :     {3, "meanSeaLevel", "MSL",
     954             :      "The average height of the surface of the sea at a tide station for all "
     955             :      "stages of the tide over a 19-year period, usually determined from hourly "
     956             :      "height readings measured from a fixed predetermined reference level."},
     957             :     {4, "lowestLowWater", nullptr,
     958             :      "An arbitrary level conforming to the lowest tide observed at a place, or "
     959             :      "some what lower."},
     960             :     {5, "meanLowWater", "MLW",
     961             :      "The average height of all low waters at a place over a 19-year period."},
     962             :     {6, "lowestLowWaterSprings", nullptr,
     963             :      "An arbitrary level conforming to the lowest water level observed at a "
     964             :      "place at spring tides during a period of time shorter than 19 years."},
     965             :     {7, "approximateMeanLowWaterSprings", nullptr,
     966             :      "An arbitrary level, usually within 0.3m from that of Mean Low Water "
     967             :      "Springs (MLWS)."},
     968             :     {8, "indianSpringLowWater", "ISLW",
     969             :      "An arbitrary tidal datum approximating the level of the mean of the "
     970             :      "lower low water at spring tides. It was first used in waters surrounding "
     971             :      "India."},
     972             :     {9, "lowWaterSprings", nullptr,
     973             :      "An arbitrary level, approximating that of mean low water springs "
     974             :      "(MLWS)."},
     975             :     {10, "approximateLowestAstronomicalTide", nullptr,
     976             :      "An arbitrary level, usually within 0.3m from that of Lowest Astronomical "
     977             :      "Tide (LAT)."},
     978             :     {11, "nearlyLowestLowWater", nullptr,
     979             :      "An arbitrary level approximating the lowest water level observed at a "
     980             :      "place, usually equivalent to the Indian Spring Low Water (ISLW)."},
     981             :     {12, "meanLowerLowWater", "MLLW",
     982             :      "The average height of the lower low waters at a place over a 19-year "
     983             :      "period."},
     984             :     {13, "lowWater", "LW",
     985             :      "The lowest level reached at a place by the water surface in one "
     986             :      "oscillation. Also called low tide."},
     987             :     {14, "approximateMeanLowWater", nullptr,
     988             :      "An arbitrary level, usually within 0.3m from that of Mean Low Water "
     989             :      "(MLW)."},
     990             :     {15, "approximateMeanLowerLowWater", nullptr,
     991             :      "An arbitrary level, usually within 0.3m from that of Mean Lower Low "
     992             :      "Water (MLLW)."},
     993             :     {16, "meanHighWater", "MHW",
     994             :      "The average height of all high waters at a place over a 19-year period."},
     995             :     {17, "meanHighWaterSprings", "MHWS",
     996             :      "The average height of the high waters of spring tides. Also called "
     997             :      "spring high water."},
     998             :     {18, "highWater", "HW",
     999             :      "The highest level reached at a place by the water surface in one "
    1000             :      "oscillation."},
    1001             :     {19, "approximateMeanSeaLevel", nullptr,
    1002             :      "An arbitrary level, usually within 0.3m from that of Mean Sea Level "
    1003             :      "(MSL)."},
    1004             :     {20, "highWaterSprings", nullptr,
    1005             :      "An arbitrary level, approximating that of mean high water springs "
    1006             :      "(MHWS)."},
    1007             :     {21, "meanHigherHighWater", "MHHW",
    1008             :      "The average height of higher high waters at a place over a 19-year "
    1009             :      "period."},
    1010             :     {22, "equinoctialSpringLowWater", nullptr,
    1011             :      "The level of low water springs near the time of an equinox."},
    1012             :     {23, "lowestAstronomicalTide", "LAT",
    1013             :      "The lowest tide level which can be predicted to occur under average "
    1014             :      "meteorological conditions and under any combination of astronomical "
    1015             :      "conditions."},
    1016             :     {24, "localDatum", nullptr,
    1017             :      "An arbitrary datum defined by a local harbour authority, from which "
    1018             :      "levels and tidal heights are measured by this authority."},
    1019             :     {25, "internationalGreatLakesDatum1985", nullptr,
    1020             :      "A vertical reference system with its zero based on the mean water level "
    1021             :      "at Rimouski/Pointe-au-Pere, Quebec, over the period 1970 to 1988."},
    1022             :     {26, "meanWaterLevel", nullptr,
    1023             :      "The average of all hourly water levels over the available period of "
    1024             :      "record."},
    1025             :     {27, "lowerLowWaterLargeTide", nullptr,
    1026             :      "The average of the lowest low waters, one from each of 19 years of "
    1027             :      "observations."},
    1028             :     {28, "higherHighWaterLargeTide", nullptr,
    1029             :      "The average of the highest high waters, one from each of 19 years of "
    1030             :      "observations."},
    1031             :     {29, "nearlyHighestHighWater", nullptr,
    1032             :      "An arbitrary level approximating the highest water level observed at a "
    1033             :      "place, usually equivalent to the high water springs."},
    1034             :     {30, "highestAstronomicalTide", "HAT",
    1035             :      "The highest tidal level which can be predicted to occur under average "
    1036             :      "meteorological conditions and under any combination of astronomical "
    1037             :      "conditions."},
    1038             :     {44, "balticSeaChartDatum2000", nullptr,
    1039             :      "The datum refers to each Baltic country's realization of the European "
    1040             :      "Vertical Reference System (EVRS) with land-uplift epoch 2000, which is "
    1041             :      "connected to the Normaal Amsterdams Peil (NAP)"},
    1042             :     {46, "internationalGreatLakesDatum2020", nullptr,
    1043             :      "The 2020 update to the International Great Lakes Datum, the official "
    1044             :      "reference system used to measure water level heights in the Great Lakes, "
    1045             :      "connecting channels, and the St. Lawrence River system."},
    1046             :     {47, "seaFloor", nullptr,
    1047             :      "The bottom of the ocean and seas where there is a generally smooth "
    1048             :      "gentle gradient. Also referred to as sea bed (sometimes seabed or "
    1049             :      "sea-bed), and sea bottom."},
    1050             :     {48, "seaSurface", nullptr,
    1051             :      "A two-dimensional (in the horizontal plane) field representing the "
    1052             :      "air-sea interface, with high-frequency fluctuations such as wind waves "
    1053             :      "and swell, but not astronomical tides, filtered out"},
    1054             :     {49, "hydrographicZero", nullptr,
    1055             :      "A vertical reference near the lowest astronomical tide (LAT) below which "
    1056             :      "the sea level falls only very exceptionally"},
    1057             : };
    1058             : 
    1059             : /************************************************************************/
    1060             : /*              S100GetVerticalDatumCodeFromNameOrAbbrev()              */
    1061             : /************************************************************************/
    1062             : 
    1063          88 : int S100GetVerticalDatumCodeFromNameOrAbbrev(const char *pszStr)
    1064             : {
    1065          88 :     const int nCode = atoi(pszStr);
    1066        1145 :     for (const auto &sEntry : gasVerticalDatums)
    1067             :     {
    1068        1141 :         if (sEntry.nCode == nCode || EQUAL(pszStr, sEntry.pszName) ||
    1069        1139 :             (sEntry.pszAbbrev && EQUAL(pszStr, sEntry.pszAbbrev)))
    1070             :         {
    1071          84 :             return sEntry.nCode;
    1072             :         }
    1073             :     }
    1074           4 :     return -1;
    1075             : }
    1076             : 
    1077             : /************************************************************************/
    1078             : /*                       S100ReadVerticalDatum()                        */
    1079             : /************************************************************************/
    1080             : 
    1081         454 : void S100ReadVerticalDatum(GDALMajorObject *poMO, const GDALGroup *poGroup)
    1082             : {
    1083             : 
    1084         454 :     int nVerticalDatumReference = 1;
    1085             :     auto poVerticalDatumReference =
    1086        1362 :         poGroup->GetAttribute("verticalDatumReference");
    1087         626 :     if (poVerticalDatumReference &&
    1088         626 :         poVerticalDatumReference->GetDataType().GetClass() == GEDTC_NUMERIC)
    1089             :     {
    1090         172 :         nVerticalDatumReference = poVerticalDatumReference->ReadAsInt();
    1091             :     }
    1092             : 
    1093        1362 :     auto poVerticalDatum = poGroup->GetAttribute("verticalDatum");
    1094         637 :     if (poVerticalDatum &&
    1095         637 :         poVerticalDatum->GetDataType().GetClass() == GEDTC_NUMERIC)
    1096             :     {
    1097         183 :         poMO->GDALMajorObject::SetMetadataItem(S100_VERTICAL_DATUM_ABBREV,
    1098             :                                                nullptr);
    1099         183 :         poMO->GDALMajorObject::SetMetadataItem(S100_VERTICAL_DATUM_EPSG_CODE,
    1100             :                                                nullptr);
    1101         183 :         poMO->GDALMajorObject::SetMetadataItem(S100_VERTICAL_DATUM_NAME,
    1102             :                                                nullptr);
    1103         183 :         poMO->GDALMajorObject::SetMetadataItem("verticalDatum", nullptr);
    1104         183 :         if (nVerticalDatumReference == 1)
    1105             :         {
    1106         177 :             bool bFound = false;
    1107         177 :             const auto nVal = poVerticalDatum->ReadAsInt();
    1108        2116 :             for (const auto &sVerticalDatum : gasVerticalDatums)
    1109             :             {
    1110        2116 :                 if (sVerticalDatum.nCode == nVal)
    1111             :                 {
    1112         177 :                     bFound = true;
    1113         177 :                     poMO->GDALMajorObject::SetMetadataItem(
    1114         177 :                         S100_VERTICAL_DATUM_NAME, sVerticalDatum.pszName);
    1115         177 :                     if (sVerticalDatum.pszAbbrev)
    1116             :                     {
    1117         177 :                         poMO->GDALMajorObject::SetMetadataItem(
    1118             :                             S100_VERTICAL_DATUM_ABBREV,
    1119         177 :                             sVerticalDatum.pszAbbrev);
    1120             :                     }
    1121         177 :                     poMO->GDALMajorObject::SetMetadataItem(
    1122             :                         "VERTICAL_DATUM_DEFINITION",
    1123         177 :                         sVerticalDatum.pszDefinition);
    1124         177 :                     break;
    1125             :                 }
    1126             :             }
    1127         177 :             if (!bFound)
    1128             :             {
    1129           0 :                 poMO->GDALMajorObject::SetMetadataItem("verticalDatum",
    1130             :                                                        CPLSPrintf("%d", nVal));
    1131             :             }
    1132             :         }
    1133           6 :         else if (nVerticalDatumReference == 2)
    1134             :         {
    1135           6 :             const auto nVal = poVerticalDatum->ReadAsInt();
    1136           6 :             PJ *datum = proj_create_from_database(
    1137             :                 OSRGetProjTLSContext(), "EPSG", CPLSPrintf("%d", nVal),
    1138             :                 PJ_CATEGORY_DATUM, false, nullptr);
    1139           6 :             poMO->GDALMajorObject::SetMetadataItem(
    1140             :                 S100_VERTICAL_DATUM_EPSG_CODE, CPLSPrintf("%d", nVal));
    1141           6 :             if (datum)
    1142             :             {
    1143           6 :                 poMO->GDALMajorObject::SetMetadataItem(S100_VERTICAL_DATUM_NAME,
    1144             :                                                        proj_get_name(datum));
    1145           6 :                 proj_destroy(datum);
    1146             :             }
    1147             :         }
    1148             :         else
    1149             :         {
    1150           0 :             CPLDebug("S100", "Unhandled verticalDatumReference = %d",
    1151             :                      nVerticalDatumReference);
    1152             :         }
    1153             :     }
    1154         454 : }
    1155             : 
    1156             : /************************************************************************/
    1157             : /*                          S100ReadMetadata()                          */
    1158             : /************************************************************************/
    1159             : 
    1160         224 : std::string S100ReadMetadata(GDALDataset *poDS, const std::string &osFilename,
    1161             :                              const GDALGroup *poRootGroup)
    1162             : {
    1163         224 :     std::string osMetadataFile;
    1164        3527 :     for (const auto &poAttr : poRootGroup->GetAttributes())
    1165             :     {
    1166        3303 :         const auto &osName = poAttr->GetName();
    1167        3303 :         if (osName == "metadata")
    1168             :         {
    1169          30 :             const char *pszVal = poAttr->ReadAsString();
    1170          30 :             if (pszVal && pszVal[0])
    1171             :             {
    1172          30 :                 if (CPLHasPathTraversal(pszVal))
    1173             :                 {
    1174           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1175             :                              "Path traversal detected in %s", pszVal);
    1176           0 :                     continue;
    1177             :                 }
    1178          60 :                 osMetadataFile = CPLFormFilenameSafe(
    1179          60 :                     CPLGetPathSafe(osFilename.c_str()).c_str(), pszVal,
    1180          30 :                     nullptr);
    1181             :                 VSIStatBufL sStat;
    1182          30 :                 if (VSIStatL(osMetadataFile.c_str(), &sStat) != 0)
    1183             :                 {
    1184             :                     // Test products from https://data.admiralty.co.uk/portal/apps/sites/#/marine-data-portal/pages/s-100
    1185             :                     // advertise a metadata filename starting with "MD_", per the spec,
    1186             :                     // but the actual filename does not start with "MD_"...
    1187           6 :                     if (STARTS_WITH(pszVal, "MD_"))
    1188             :                     {
    1189          12 :                         osMetadataFile = CPLFormFilenameSafe(
    1190          12 :                             CPLGetPathSafe(osFilename.c_str()).c_str(),
    1191           6 :                             pszVal + strlen("MD_"), nullptr);
    1192           6 :                         if (VSIStatL(osMetadataFile.c_str(), &sStat) != 0)
    1193             :                         {
    1194           6 :                             osMetadataFile.clear();
    1195             :                         }
    1196             :                     }
    1197             :                     else
    1198             :                     {
    1199           0 :                         osMetadataFile.clear();
    1200             :                     }
    1201             :                 }
    1202             :             }
    1203             :         }
    1204        6333 :         else if (osName != "horizontalCRS" &&
    1205        6109 :                  osName != "horizontalDatumReference" &&
    1206        6087 :                  osName != "horizontalDatumValue" &&
    1207        5852 :                  osName != "productSpecification" &&
    1208        5451 :                  osName != "eastBoundLongitude" &&
    1209        5097 :                  osName != "northBoundLatitude" &&
    1210        4743 :                  osName != "southBoundLatitude" &&
    1211        6495 :                  osName != "westBoundLongitude" && osName != "extentTypeCode" &&
    1212        5824 :                  osName != "verticalCS" && osName != "verticalCoordinateBase" &&
    1213        3370 :                  osName != "verticalDatumReference" &&
    1214        4447 :                  osName != "verticalDatum" && osName != "nameOfHorizontalCRS" &&
    1215        3882 :                  osName != "typeOfHorizontalCRS" && osName != "horizontalCS" &&
    1216        2342 :                  osName != "horizontalDatum" &&
    1217        2249 :                  osName != "nameOfHorizontalDatum" &&
    1218        3329 :                  osName != "primeMeridian" && osName != "spheroid" &&
    1219        2136 :                  osName != "projectionMethod" &&
    1220        2008 :                  osName != "projectionParameter1" &&
    1221        1880 :                  osName != "projectionParameter2" &&
    1222        1771 :                  osName != "projectionParameter3" &&
    1223        1701 :                  osName != "projectionParameter4" &&
    1224        1661 :                  osName != "projectionParameter5" &&
    1225        7156 :                  osName != "falseNorthing" && osName != "falseEasting")
    1226             :         {
    1227         695 :             const char *pszVal = poAttr->ReadAsString();
    1228         695 :             if (pszVal && pszVal[0])
    1229         695 :                 poDS->GDALDataset::SetMetadataItem(osName.c_str(), pszVal);
    1230             :         }
    1231             :     }
    1232         224 :     return osMetadataFile;
    1233             : }
    1234             : 
    1235             : /************************************************************************/
    1236             : /*                   S100BaseWriter::S100BaseWriter()                   */
    1237             : /************************************************************************/
    1238             : 
    1239         200 : S100BaseWriter::S100BaseWriter(const char *pszDestFilename,
    1240         200 :                                GDALDataset *poSrcDS, CSLConstList papszOptions)
    1241             :     : m_osDestFilename(pszDestFilename), m_poSrcDS(poSrcDS),
    1242         200 :       m_aosOptions(papszOptions)
    1243             : {
    1244         200 : }
    1245             : 
    1246             : /************************************************************************/
    1247             : /*                  S100BaseWriter::~S100BaseWriter()                   */
    1248             : /************************************************************************/
    1249             : 
    1250         200 : S100BaseWriter::~S100BaseWriter()
    1251             : {
    1252             :     // Check that destructors of derived classes have called themselves their
    1253             :     // Close implementation
    1254         200 :     CPLAssert(!m_hdf5);
    1255         200 : }
    1256             : 
    1257             : /************************************************************************/
    1258             : /*                     S100BaseWriter::BaseClose()                      */
    1259             : /************************************************************************/
    1260             : 
    1261         284 : bool S100BaseWriter::BaseClose()
    1262             : {
    1263         284 :     bool ret = m_GroupF.clear();
    1264         284 :     ret = m_valuesGroup.clear() && ret;
    1265         284 :     ret = m_featureInstanceGroup.clear() && ret;
    1266         284 :     ret = m_featureGroup.clear() && ret;
    1267         284 :     ret = m_hdf5.clear() && ret;
    1268         284 :     return ret;
    1269             : }
    1270             : 
    1271             : /************************************************************************/
    1272             : /*                     S100BaseWriter::BaseChecks()                     */
    1273             : /************************************************************************/
    1274             : 
    1275         158 : bool S100BaseWriter::BaseChecks(const char *pszDriverName, bool crsMustBeEPSG,
    1276             :                                 bool verticalDatumRequired)
    1277             : {
    1278         158 :     if (m_poSrcDS->GetRasterXSize() < 1 || m_poSrcDS->GetRasterYSize() < 1)
    1279             :     {
    1280           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    1281             :                  "Source dataset dimension must be at least 1x1 pixel");
    1282           3 :         return false;
    1283             :     }
    1284             : 
    1285         155 :     if (m_poSrcDS->GetGeoTransform(m_gt) != CE_None)
    1286             :     {
    1287           6 :         CPLError(CE_Failure, CPLE_NotSupported,
    1288             :                  "%s driver requires a source dataset with a geotransform",
    1289             :                  pszDriverName);
    1290           6 :         return false;
    1291             :     }
    1292         149 :     if (m_gt.xrot != 0 || m_gt.yrot != 0)
    1293             :     {
    1294           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    1295             :                  "%s driver requires a source dataset with a non-rotated "
    1296             :                  "geotransform",
    1297             :                  pszDriverName);
    1298           3 :         return false;
    1299             :     }
    1300             : 
    1301         146 :     m_poSRS = m_poSrcDS->GetSpatialRef();
    1302         146 :     if (!m_poSRS)
    1303             :     {
    1304           3 :         CPLError(CE_Failure, CPLE_NotSupported,
    1305             :                  "%s driver requires a source dataset with a CRS",
    1306             :                  pszDriverName);
    1307           3 :         return false;
    1308             :     }
    1309             : 
    1310         143 :     const char *pszAuthName = m_poSRS->GetAuthorityName();
    1311         143 :     const char *pszAuthCode = m_poSRS->GetAuthorityCode();
    1312         143 :     if (pszAuthName && pszAuthCode && EQUAL(pszAuthName, "EPSG"))
    1313             :     {
    1314          79 :         m_nEPSGCode = atoi(pszAuthCode);
    1315             :     }
    1316         143 :     if (crsMustBeEPSG && m_nEPSGCode == 0)
    1317             :     {
    1318          15 :         CPLError(CE_Failure, CPLE_NotSupported,
    1319             :                  "%s driver requires a source dataset whose CRS has an EPSG "
    1320             :                  "identifier",
    1321             :                  pszDriverName);
    1322          15 :         return false;
    1323             :     }
    1324             : 
    1325             :     const char *pszVerticalDatum =
    1326         128 :         m_aosOptions.FetchNameValue("VERTICAL_DATUM");
    1327         128 :     if (!pszVerticalDatum)
    1328             :         pszVerticalDatum =
    1329          43 :             m_poSrcDS->GetMetadataItem(S100_VERTICAL_DATUM_EPSG_CODE);
    1330         128 :     if (!pszVerticalDatum)
    1331          42 :         pszVerticalDatum = m_poSrcDS->GetMetadataItem(S100_VERTICAL_DATUM_NAME);
    1332         128 :     if (!pszVerticalDatum)
    1333             :     {
    1334          40 :         if (verticalDatumRequired)
    1335             :         {
    1336           4 :             CPLError(CE_Failure, CPLE_AppDefined,
    1337             :                      "VERTICAL_DATUM creation option must be specified");
    1338           4 :             return false;
    1339             :         }
    1340             :     }
    1341             :     else
    1342             :     {
    1343          88 :         m_nVerticalDatum =
    1344          88 :             S100GetVerticalDatumCodeFromNameOrAbbrev(pszVerticalDatum);
    1345          88 :         if (m_nVerticalDatum <= 0)
    1346             :         {
    1347           4 :             auto pjCtxt = OSRGetProjTLSContext();
    1348             :             PJ *vertical_datum =
    1349           4 :                 proj_create_from_database(pjCtxt, "EPSG", pszVerticalDatum,
    1350             :                                           PJ_CATEGORY_DATUM, false, nullptr);
    1351           6 :             const bool bIsValid = vertical_datum != nullptr &&
    1352           2 :                                   proj_get_type(vertical_datum) ==
    1353           4 :                                       PJ_TYPE_VERTICAL_REFERENCE_FRAME;
    1354           4 :             proj_destroy(vertical_datum);
    1355           4 :             if (bIsValid)
    1356             :             {
    1357           2 :                 m_nVerticalDatum = atoi(pszVerticalDatum);
    1358             :             }
    1359             :             else
    1360             :             {
    1361           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1362             :                          "VERTICAL_DATUM value is invalid");
    1363           2 :                 return false;
    1364             :             }
    1365             :         }
    1366             :     }
    1367             : 
    1368         122 :     const std::string osFilename = CPLGetFilename(m_osDestFilename.c_str());
    1369         122 :     CPLAssert(pszDriverName[0] == 'S');
    1370         122 :     const char *pszExpectedFilenamePrefix = pszDriverName + 1;
    1371         122 :     if (!cpl::starts_with(osFilename, pszExpectedFilenamePrefix))
    1372             :     {
    1373           5 :         CPLError(CE_Warning, CPLE_AppDefined,
    1374             :                  "%s dataset filenames should start with '%s'", pszDriverName,
    1375             :                  pszExpectedFilenamePrefix);
    1376             :     }
    1377         127 :     if (!cpl::ends_with(osFilename, ".h5") &&
    1378           5 :         !cpl::ends_with(osFilename, ".H5"))
    1379             :     {
    1380           5 :         CPLError(CE_Warning, CPLE_AppDefined,
    1381             :                  "%s dataset filenames should have a '.H5' extension",
    1382             :                  pszDriverName);
    1383             :     }
    1384             : 
    1385         122 :     return true;
    1386             : }
    1387             : 
    1388             : /************************************************************************/
    1389             : /*                 S100BaseWriter::OpenFileUpdateMode()                 */
    1390             : /************************************************************************/
    1391             : 
    1392           5 : bool S100BaseWriter::OpenFileUpdateMode()
    1393             : {
    1394           5 :     hid_t fapl = H5_CHECK(H5Pcreate(H5P_FILE_ACCESS));
    1395           5 :     H5Pset_driver(fapl, HDF5GetFileDriver(), nullptr);
    1396           5 :     m_hdf5.reset(H5Fopen(m_osDestFilename.c_str(), H5F_ACC_RDWR, fapl));
    1397           5 :     H5Pclose(fapl);
    1398           5 :     if (!m_hdf5)
    1399             :     {
    1400           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1401             :                  "Cannot open file %s in update mode",
    1402             :                  m_osDestFilename.c_str());
    1403           0 :         return false;
    1404             :     }
    1405           5 :     return true;
    1406             : }
    1407             : 
    1408             : /************************************************************************/
    1409             : /*                     S100BaseWriter::CreateFile()                     */
    1410             : /************************************************************************/
    1411             : 
    1412          90 : bool S100BaseWriter::CreateFile()
    1413             : {
    1414          90 :     hid_t fapl = H5_CHECK(H5Pcreate(H5P_FILE_ACCESS));
    1415          90 :     H5Pset_driver(fapl, HDF5GetFileDriver(), nullptr);
    1416             :     {
    1417         180 :         GH5_libhdf5_error_silencer oErrorSilencer;
    1418          90 :         m_hdf5.reset(H5Fcreate(m_osDestFilename.c_str(), H5F_ACC_TRUNC,
    1419             :                                H5P_DEFAULT, fapl));
    1420             :     }
    1421          90 :     H5Pclose(fapl);
    1422          90 :     if (!m_hdf5)
    1423             :     {
    1424           2 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot create file %s",
    1425             :                  m_osDestFilename.c_str());
    1426           2 :         return false;
    1427             :     }
    1428          88 :     return true;
    1429             : }
    1430             : 
    1431             : /************************************************************************/
    1432             : /*                  S100BaseWriter::WriteUInt8Value()                   */
    1433             : /************************************************************************/
    1434             : 
    1435          77 : bool S100BaseWriter::WriteUInt8Value(hid_t hGroup, const char *pszName,
    1436             :                                      int value)
    1437             : {
    1438         154 :     return GH5_CreateAttribute(hGroup, pszName, H5T_STD_U8LE) &&
    1439         154 :            GH5_WriteAttribute(hGroup, pszName, value);
    1440             : }
    1441             : 
    1442             : /************************************************************************/
    1443             : /*                  S100BaseWriter::WriteUInt16Value()                  */
    1444             : /************************************************************************/
    1445             : 
    1446           4 : bool S100BaseWriter::WriteUInt16Value(hid_t hGroup, const char *pszName,
    1447             :                                       int value)
    1448             : {
    1449           8 :     return GH5_CreateAttribute(hGroup, pszName, H5T_STD_U16LE) &&
    1450           8 :            GH5_WriteAttribute(hGroup, pszName, value);
    1451             : }
    1452             : 
    1453             : /************************************************************************/
    1454             : /*                  S100BaseWriter::WriteUInt32Value()                  */
    1455             : /************************************************************************/
    1456             : 
    1457         227 : bool S100BaseWriter::WriteUInt32Value(hid_t hGroup, const char *pszName,
    1458             :                                       unsigned value)
    1459             : {
    1460         454 :     return GH5_CreateAttribute(hGroup, pszName, H5T_STD_U32LE) &&
    1461         454 :            GH5_WriteAttribute(hGroup, pszName, value);
    1462             : }
    1463             : 
    1464             : /************************************************************************/
    1465             : /*                  S100BaseWriter::WriteInt32Value()                   */
    1466             : /************************************************************************/
    1467             : 
    1468         203 : bool S100BaseWriter::WriteInt32Value(hid_t hGroup, const char *pszName,
    1469             :                                      int value)
    1470             : {
    1471         406 :     return GH5_CreateAttribute(hGroup, pszName, H5T_STD_I32LE) &&
    1472         406 :            GH5_WriteAttribute(hGroup, pszName, value);
    1473             : }
    1474             : 
    1475             : /************************************************************************/
    1476             : /*                 S100BaseWriter::WriteFloat32Value()                  */
    1477             : /************************************************************************/
    1478             : 
    1479         997 : bool S100BaseWriter::WriteFloat32Value(hid_t hGroup, const char *pszName,
    1480             :                                        double value)
    1481             : {
    1482        1994 :     return GH5_CreateAttribute(hGroup, pszName, H5T_IEEE_F32LE) &&
    1483        1994 :            GH5_WriteAttribute(hGroup, pszName, value);
    1484             : }
    1485             : 
    1486             : /************************************************************************/
    1487             : /*                 S100BaseWriter::WriteFloat64Value()                  */
    1488             : /************************************************************************/
    1489             : 
    1490         524 : bool S100BaseWriter::WriteFloat64Value(hid_t hGroup, const char *pszName,
    1491             :                                        double value)
    1492             : {
    1493        1048 :     return GH5_CreateAttribute(hGroup, pszName, H5T_IEEE_F64LE) &&
    1494        1048 :            GH5_WriteAttribute(hGroup, pszName, value);
    1495             : }
    1496             : 
    1497             : /************************************************************************/
    1498             : /*             S100BaseWriter::WriteVarLengthStringValue()              */
    1499             : /************************************************************************/
    1500             : 
    1501         663 : bool S100BaseWriter::WriteVarLengthStringValue(hid_t hGroup,
    1502             :                                                const char *pszName,
    1503             :                                                const char *pszValue)
    1504             : {
    1505        1326 :     return GH5_CreateAttribute(hGroup, pszName, H5T_C_S1, VARIABLE_LENGTH) &&
    1506        1326 :            GH5_WriteAttribute(hGroup, pszName, pszValue);
    1507             : }
    1508             : 
    1509             : /************************************************************************/
    1510             : /*            S100BaseWriter::WriteFixedLengthStringValue()             */
    1511             : /************************************************************************/
    1512             : 
    1513           0 : bool S100BaseWriter::WriteFixedLengthStringValue(hid_t hGroup,
    1514             :                                                  const char *pszName,
    1515             :                                                  const char *pszValue)
    1516             : {
    1517           0 :     return GH5_CreateAttribute(hGroup, pszName, H5T_C_S1,
    1518           0 :                                static_cast<unsigned>(strlen(pszValue))) &&
    1519           0 :            GH5_WriteAttribute(hGroup, pszName, pszValue);
    1520             : }
    1521             : 
    1522             : /************************************************************************/
    1523             : /*             S100BaseWriter::WriteProductSpecification()              */
    1524             : /************************************************************************/
    1525             : 
    1526          88 : bool S100BaseWriter::WriteProductSpecification(
    1527             :     const char *pszProductSpecification)
    1528             : {
    1529          88 :     return WriteVarLengthStringValue(m_hdf5, "productSpecification",
    1530          88 :                                      pszProductSpecification);
    1531             : }
    1532             : 
    1533             : /************************************************************************/
    1534             : /*                   S100BaseWriter::WriteIssueDate()                   */
    1535             : /************************************************************************/
    1536             : 
    1537          88 : bool S100BaseWriter::WriteIssueDate()
    1538             : {
    1539          88 :     const char *pszIssueDate = m_aosOptions.FetchNameValue("ISSUE_DATE");
    1540          88 :     if (!pszIssueDate)
    1541             :     {
    1542          55 :         const char *pszTmp = m_poSrcDS->GetMetadataItem("issueDate");
    1543          55 :         if (pszTmp && strlen(pszTmp) == 8)
    1544           4 :             pszIssueDate = pszTmp;
    1545             :     }
    1546             : 
    1547         176 :     std::string osIssueDate;  // keep in that scope
    1548          88 :     if (pszIssueDate)
    1549             :     {
    1550          37 :         if (strlen(pszIssueDate) != 8)
    1551           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1552             :                      "ISSUE_DATE should be 8 digits: YYYYMMDD");
    1553             :     }
    1554             :     else
    1555             :     {
    1556             :         time_t now;
    1557          51 :         time(&now);
    1558             :         struct tm brokenDown;
    1559          51 :         CPLUnixTimeToYMDHMS(now, &brokenDown);
    1560          51 :         osIssueDate = CPLSPrintf("%04d%02d%02d", brokenDown.tm_year + 1900,
    1561          51 :                                  brokenDown.tm_mon + 1, brokenDown.tm_mday);
    1562          51 :         pszIssueDate = osIssueDate.c_str();
    1563             :     }
    1564             : 
    1565         176 :     return WriteVarLengthStringValue(m_hdf5, "issueDate", pszIssueDate);
    1566             : }
    1567             : 
    1568             : /************************************************************************/
    1569             : /*                   S100BaseWriter::WriteIssueTime()                   */
    1570             : /************************************************************************/
    1571             : 
    1572          88 : bool S100BaseWriter::WriteIssueTime(bool bAutogenerateFromCurrent)
    1573             : {
    1574          88 :     const char *pszIssueTime = m_aosOptions.FetchNameValue("ISSUE_TIME");
    1575          88 :     if (!pszIssueTime)
    1576             :     {
    1577          55 :         const char *pszTmp = m_poSrcDS->GetMetadataItem("issueTime");
    1578          55 :         if (pszTmp && strlen(pszTmp) == 7 && pszTmp[6] == 'Z')
    1579           4 :             pszIssueTime = pszTmp;
    1580             :     }
    1581          88 :     std::string osIssueTime;  // keep in that scope
    1582          88 :     if (!pszIssueTime && bAutogenerateFromCurrent)
    1583             :     {
    1584             :         time_t now;
    1585          36 :         time(&now);
    1586             :         struct tm brokenDown;
    1587          36 :         CPLUnixTimeToYMDHMS(now, &brokenDown);
    1588             :         osIssueTime = CPLSPrintf("%02d%02d%02dZ", brokenDown.tm_hour,
    1589          36 :                                  brokenDown.tm_min, brokenDown.tm_sec);
    1590          36 :         pszIssueTime = osIssueTime.c_str();
    1591             :     }
    1592         161 :     return !pszIssueTime || pszIssueTime[0] == 0 ||
    1593         249 :            WriteVarLengthStringValue(m_hdf5, "issueTime", pszIssueTime);
    1594             : }
    1595             : 
    1596             : /************************************************************************/
    1597             : /*              S100BaseWriter::WriteTopLevelBoundingBox()              */
    1598             : /************************************************************************/
    1599             : 
    1600          85 : bool S100BaseWriter::WriteTopLevelBoundingBox()
    1601             : {
    1602             : 
    1603          85 :     OGREnvelope sExtent;
    1604          85 :     if (m_poSrcDS->GetExtentWGS84LongLat(&sExtent) != OGRERR_NONE)
    1605             :     {
    1606           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    1607             :                  "Cannot get dataset extent in WGS84 longitude/latitude");
    1608           0 :         return false;
    1609             :     }
    1610             : 
    1611          85 :     return WriteFloat32Value(m_hdf5, "westBoundLongitude", sExtent.MinX) &&
    1612          85 :            WriteFloat32Value(m_hdf5, "southBoundLatitude", sExtent.MinY) &&
    1613         255 :            WriteFloat32Value(m_hdf5, "eastBoundLongitude", sExtent.MaxX) &&
    1614         170 :            WriteFloat32Value(m_hdf5, "northBoundLatitude", sExtent.MaxY);
    1615             : }
    1616             : 
    1617             : /************************************************************************/
    1618             : /*                 S100BaseWriter::WriteHorizontalCRS()                 */
    1619             : /************************************************************************/
    1620             : 
    1621          88 : bool S100BaseWriter::WriteHorizontalCRS()
    1622             : {
    1623          88 :     bool ret = WriteInt32Value(m_hdf5, "horizontalCRS",
    1624          88 :                                m_nEPSGCode > 0 ? m_nEPSGCode : -1);
    1625          88 :     if (ret && m_nEPSGCode <= 0)
    1626             :     {
    1627          40 :         ret = WriteVarLengthStringValue(m_hdf5, "nameOfHorizontalCRS",
    1628          40 :                                         m_poSRS->GetName());
    1629             :         {
    1630          80 :             GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1631          40 :             ret = ret && hEnumType;
    1632          40 :             if (ret)
    1633             :             {
    1634             :                 uint8_t val;
    1635          40 :                 val = 1;
    1636          40 :                 ret = ret && H5_CHECK(H5Tenum_insert(hEnumType, "geodeticCRS2D",
    1637             :                                                      &val)) >= 0;
    1638          40 :                 val = 2;
    1639          40 :                 ret = ret && H5_CHECK(H5Tenum_insert(hEnumType, "projectedCRS",
    1640             :                                                      &val)) >= 0;
    1641          40 :                 ret = ret &&
    1642          40 :                       GH5_CreateAttribute(m_hdf5, "typeOfHorizontalCRS",
    1643          80 :                                           hEnumType) &&
    1644          40 :                       GH5_WriteAttribute(m_hdf5, "typeOfHorizontalCRS",
    1645          40 :                                          m_poSRS->IsGeographic() ? 1 : 2);
    1646             :             }
    1647             :         }
    1648             : 
    1649          69 :         const int nHorizontalCS = m_poSRS->IsGeographic() ? 6422
    1650          29 :                                   : m_poSRS->EPSGTreatsAsNorthingEasting()
    1651          29 :                                       ? 4500
    1652          40 :                                       : 4400;
    1653          40 :         ret = ret && WriteInt32Value(m_hdf5, "horizontalCS", nHorizontalCS);
    1654             : 
    1655             :         const char *pszDatumKey =
    1656          40 :             m_poSRS->IsGeographic() ? "GEOGCS|DATUM" : "PROJCS|GEOGCS|DATUM";
    1657          40 :         const char *pszDatumAuthName = m_poSRS->GetAuthorityName(pszDatumKey);
    1658          40 :         const char *pszDatumCode = m_poSRS->GetAuthorityCode(pszDatumKey);
    1659          34 :         const int nDatum = (pszDatumAuthName && pszDatumCode &&
    1660          34 :                             EQUAL(pszDatumAuthName, "EPSG"))
    1661          74 :                                ? atoi(pszDatumCode)
    1662             :                                : -1;
    1663          40 :         ret = ret && WriteInt32Value(m_hdf5, "horizontalDatum", nDatum);
    1664          40 :         if (ret && nDatum < 0)
    1665             :         {
    1666           6 :             const char *pszDatum = m_poSRS->GetAttrValue(pszDatumKey);
    1667           6 :             if (!pszDatum)
    1668           0 :                 pszDatum = "unknown";
    1669           6 :             ret = WriteVarLengthStringValue(m_hdf5, "nameOfHorizontalDatum",
    1670             :                                             pszDatum);
    1671             : 
    1672           6 :             const char *pszSpheroidKey = m_poSRS->IsGeographic()
    1673           6 :                                              ? "GEOGCS|DATUM|SPHEROID"
    1674           6 :                                              : "PROJCS|GEOGCS|DATUM|SPHEROID";
    1675             :             const char *pszSpheroidAuthName =
    1676           6 :                 m_poSRS->GetAuthorityName(pszSpheroidKey);
    1677             :             const char *pszSpheroidCode =
    1678           6 :                 m_poSRS->GetAuthorityCode(pszSpheroidKey);
    1679           6 :             const char *pszSpheroidName = m_poSRS->GetAttrValue(pszSpheroidKey);
    1680           6 :             const int nSpheroid =
    1681           3 :                 (pszSpheroidAuthName && pszSpheroidCode &&
    1682           3 :                  EQUAL(pszSpheroidAuthName, "EPSG"))
    1683          12 :                     ? atoi(pszSpheroidCode)
    1684           3 :                 : (pszSpheroidName && EQUAL(pszSpheroidName, "Bessel 1841"))
    1685           6 :                     ? 7004
    1686             :                     : -1;
    1687           6 :             if (nSpheroid <= 0)
    1688             :             {
    1689           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1690             :                          "Unknown code for ellipsoid of CRS");
    1691           1 :                 return false;
    1692             :             }
    1693           5 :             ret = ret && WriteInt32Value(m_hdf5, "spheroid", nSpheroid);
    1694             : 
    1695           5 :             const char *pszPrimeMeridianKey = m_poSRS->IsGeographic()
    1696           5 :                                                   ? "GEOGCS|PRIMEM"
    1697           5 :                                                   : "PROJCS|GEOGCS|PRIMEM";
    1698             :             const char *pszPrimeMeridianAuthName =
    1699           5 :                 m_poSRS->GetAuthorityName(pszPrimeMeridianKey);
    1700             :             const char *pszPrimeMeridianCode =
    1701           5 :                 m_poSRS->GetAuthorityCode(pszPrimeMeridianKey);
    1702             :             const char *pszPrimeMeridianName =
    1703           5 :                 m_poSRS->GetAttrValue(pszPrimeMeridianKey);
    1704           5 :             const int nPrimeMeridian =
    1705           2 :                 (pszPrimeMeridianAuthName && pszPrimeMeridianCode &&
    1706           2 :                  EQUAL(pszPrimeMeridianAuthName, "EPSG"))
    1707          10 :                     ? atoi(pszPrimeMeridianCode)
    1708           3 :                 : (pszPrimeMeridianName && EQUAL(pszPrimeMeridianName, "Ferro"))
    1709           6 :                     ? 8909
    1710             :                     : -1;
    1711           5 :             if (nPrimeMeridian <= 0)
    1712             :             {
    1713           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1714             :                          "Unknown code for prime meridian of CRS");
    1715           1 :                 return false;
    1716             :             }
    1717           4 :             ret =
    1718           4 :                 ret && WriteInt32Value(m_hdf5, "primeMeridian", nPrimeMeridian);
    1719             :         }
    1720             : 
    1721          38 :         const char *pszProjection = m_poSRS->IsProjected()
    1722          38 :                                         ? m_poSRS->GetAttrValue("PROJECTION")
    1723          38 :                                         : nullptr;
    1724          38 :         if (pszProjection)
    1725             :         {
    1726          27 :             int nProjectionMethod = 0;
    1727          27 :             double adfParams[] = {std::numeric_limits<double>::quiet_NaN(),
    1728             :                                   std::numeric_limits<double>::quiet_NaN(),
    1729             :                                   std::numeric_limits<double>::quiet_NaN(),
    1730             :                                   std::numeric_limits<double>::quiet_NaN(),
    1731             :                                   std::numeric_limits<double>::quiet_NaN()};
    1732          27 :             if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
    1733             :             {
    1734           2 :                 nProjectionMethod = PROJECTION_METHOD_MERCATOR;
    1735           2 :                 adfParams[0] =
    1736           2 :                     m_poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1737           2 :                 adfParams[1] =
    1738           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1739             :             }
    1740          25 :             else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
    1741             :             {
    1742             :                 auto poTmpSRS = std::unique_ptr<OGRSpatialReference>(
    1743           2 :                     m_poSRS->convertToOtherProjection(SRS_PT_MERCATOR_2SP));
    1744           2 :                 nProjectionMethod = PROJECTION_METHOD_MERCATOR;
    1745           2 :                 adfParams[0] =
    1746           2 :                     poTmpSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1747           2 :                 adfParams[1] =
    1748           2 :                     poTmpSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1749             :             }
    1750          23 :             else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    1751             :             {
    1752           2 :                 nProjectionMethod = PROJECTION_METHOD_TRANSVERSE_MERCATOR;
    1753           2 :                 adfParams[0] =
    1754           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1755           2 :                 adfParams[1] =
    1756           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1757           2 :                 adfParams[2] =
    1758           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1759             :             }
    1760          21 :             else if (EQUAL(pszProjection,
    1761             :                            SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
    1762             :             {
    1763           2 :                 nProjectionMethod = PROJECTION_METHOD_OBLIQUE_MERCATOR;
    1764           2 :                 adfParams[0] =
    1765           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
    1766           2 :                 adfParams[1] =
    1767           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
    1768           2 :                 adfParams[2] = m_poSRS->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
    1769           2 :                 adfParams[3] =
    1770           2 :                     m_poSRS->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, 0.0);
    1771           2 :                 adfParams[4] =
    1772           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1773             :             }
    1774          19 :             else if (EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR))
    1775             :             {
    1776           2 :                 nProjectionMethod = PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR;
    1777           2 :                 adfParams[0] =
    1778           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
    1779           2 :                 adfParams[1] =
    1780           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
    1781           2 :                 adfParams[2] = m_poSRS->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
    1782           2 :                 adfParams[3] =
    1783           2 :                     m_poSRS->GetNormProjParm(SRS_PP_RECTIFIED_GRID_ANGLE, 0.0);
    1784           2 :                 adfParams[4] =
    1785           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1786             :             }
    1787          17 :             else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
    1788             :             {
    1789           2 :                 nProjectionMethod = PROJECTION_METHOD_LCC_1SP;
    1790           2 :                 adfParams[0] =
    1791           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1792           2 :                 adfParams[1] =
    1793           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1794           2 :                 adfParams[2] =
    1795           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1796             :             }
    1797          15 :             else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
    1798             :             {
    1799           2 :                 nProjectionMethod = PROJECTION_METHOD_LCC_2SP;
    1800           2 :                 adfParams[0] =
    1801           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1802           2 :                 adfParams[1] =
    1803           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1804           2 :                 adfParams[2] =
    1805           2 :                     m_poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1806           2 :                 adfParams[3] =
    1807           2 :                     m_poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
    1808             :             }
    1809          13 :             else if (EQUAL(pszProjection, SRS_PT_OBLIQUE_STEREOGRAPHIC))
    1810             :             {
    1811           2 :                 nProjectionMethod = PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC;
    1812           2 :                 adfParams[0] =
    1813           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1814           2 :                 adfParams[1] =
    1815           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1816           2 :                 adfParams[2] =
    1817           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1818             :             }
    1819          11 :             else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
    1820             :             {
    1821           2 :                 nProjectionMethod = PROJECTION_METHOD_POLAR_STEREOGRAPHIC;
    1822           2 :                 adfParams[0] =
    1823           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1824           2 :                 adfParams[1] =
    1825           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1826           2 :                 adfParams[2] =
    1827           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1828             :             }
    1829           9 :             else if (EQUAL(pszProjection, SRS_PT_KROVAK))
    1830             :             {
    1831           2 :                 nProjectionMethod =
    1832             :                     PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL;
    1833           2 :                 adfParams[0] =
    1834           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1835           2 :                 adfParams[1] =
    1836           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1837           2 :                 adfParams[2] = m_poSRS->GetNormProjParm(SRS_PP_AZIMUTH, 0.0);
    1838           2 :                 adfParams[3] =
    1839           2 :                     m_poSRS->GetNormProjParm(SRS_PP_PSEUDO_STD_PARALLEL_1, 0.0);
    1840           2 :                 adfParams[4] =
    1841           2 :                     m_poSRS->GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0);
    1842             :             }
    1843           7 :             else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
    1844             :             {
    1845           2 :                 nProjectionMethod = PROJECTION_METHOD_AMERICAN_POLYCONIC;
    1846           2 :                 adfParams[0] =
    1847           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0);
    1848           2 :                 adfParams[1] =
    1849           2 :                     m_poSRS->GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0);
    1850             :             }
    1851           5 :             else if (EQUAL(pszProjection, SRS_PT_ALBERS_CONIC_EQUAL_AREA))
    1852             :             {
    1853           2 :                 nProjectionMethod = PROJECTION_METHOD_ALBERS_EQUAL_AREA;
    1854           2 :                 adfParams[0] =
    1855           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
    1856           2 :                 adfParams[1] =
    1857           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
    1858           2 :                 adfParams[2] =
    1859           2 :                     m_poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
    1860           2 :                 adfParams[3] =
    1861           2 :                     m_poSRS->GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0);
    1862             :             }
    1863           3 :             else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
    1864             :             {
    1865           2 :                 nProjectionMethod =
    1866             :                     PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA;
    1867           2 :                 adfParams[0] =
    1868           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LATITUDE_OF_CENTER, 0.0);
    1869           2 :                 adfParams[1] =
    1870           2 :                     m_poSRS->GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0);
    1871             :             }
    1872             :             else
    1873             :             {
    1874           1 :                 CPLError(CE_Failure, CPLE_NotSupported,
    1875             :                          "Projection method %s is not supported by S100",
    1876             :                          pszProjection);
    1877           1 :                 return false;
    1878             :             }
    1879             : 
    1880          26 :             ret = ret && WriteInt32Value(m_hdf5, "projectionMethod",
    1881             :                                          nProjectionMethod);
    1882         112 :             for (int i = 0; i < 5 && !std::isnan(adfParams[i]); ++i)
    1883             :             {
    1884             :                 const std::string osAttrName =
    1885          86 :                     "projectionParameter" + std::to_string(i + 1);
    1886          86 :                 ret = ret && WriteFloat64Value(m_hdf5, osAttrName.c_str(),
    1887             :                                                adfParams[i]);
    1888             :             }
    1889             : 
    1890          52 :             ret = ret && WriteFloat64Value(m_hdf5, "falseNorthing",
    1891          26 :                                            m_poSRS->GetNormProjParm(
    1892             :                                                SRS_PP_FALSE_NORTHING, 0.0));
    1893          52 :             ret = ret && WriteFloat64Value(m_hdf5, "falseEasting",
    1894          26 :                                            m_poSRS->GetNormProjParm(
    1895             :                                                SRS_PP_FALSE_EASTING, 0.0));
    1896             :         }
    1897             :     }
    1898          85 :     return ret;
    1899             : }
    1900             : 
    1901             : /************************************************************************/
    1902             : /*            S100BaseWriter::WriteVerticalCoordinateBase()             */
    1903             : /************************************************************************/
    1904             : 
    1905          79 : bool S100BaseWriter::WriteVerticalCoordinateBase(int nCode)
    1906             : {
    1907          79 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1908          79 :     bool ret = hEnumType;
    1909          79 :     if (hEnumType)
    1910             :     {
    1911             :         uint8_t val;
    1912          79 :         val = 1;
    1913          79 :         ret =
    1914          79 :             ret && H5_CHECK(H5Tenum_insert(hEnumType, "seaSurface", &val)) >= 0;
    1915          79 :         val = 2;
    1916         158 :         ret = ret &&
    1917          79 :               H5_CHECK(H5Tenum_insert(hEnumType, "verticalDatum", &val)) >= 0;
    1918          79 :         val = 3;
    1919          79 :         ret =
    1920          79 :             ret && H5_CHECK(H5Tenum_insert(hEnumType, "seaBottom", &val)) >= 0;
    1921             : 
    1922          79 :         ret =
    1923          79 :             ret &&
    1924         158 :             GH5_CreateAttribute(m_hdf5, "verticalCoordinateBase", hEnumType) &&
    1925          79 :             GH5_WriteAttribute(m_hdf5, "verticalCoordinateBase", nCode);
    1926             :     }
    1927         158 :     return ret;
    1928             : }
    1929             : 
    1930             : /************************************************************************/
    1931             : /*            S100BaseWriter::WriteVerticalDatumReference()             */
    1932             : /************************************************************************/
    1933             : 
    1934          55 : bool S100BaseWriter::WriteVerticalDatumReference(hid_t hGroup, int nCode)
    1935             : {
    1936          55 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    1937          55 :     bool ret = hEnumType;
    1938          55 :     if (hEnumType)
    1939             :     {
    1940             :         uint8_t val;
    1941          55 :         val = 1;
    1942          55 :         ret = ret && H5_CHECK(H5Tenum_insert(hEnumType, "s100VerticalDatum",
    1943             :                                              &val)) >= 0;
    1944          55 :         val = 2;
    1945          55 :         ret = ret && H5_CHECK(H5Tenum_insert(hEnumType, "EPSG", &val)) >= 0;
    1946             : 
    1947          55 :         ret =
    1948          55 :             ret &&
    1949         110 :             GH5_CreateAttribute(hGroup, "verticalDatumReference", hEnumType) &&
    1950          55 :             GH5_WriteAttribute(hGroup, "verticalDatumReference", nCode);
    1951             :     }
    1952         110 :     return ret;
    1953             : }
    1954             : 
    1955             : /************************************************************************/
    1956             : /*                  S100BaseWriter::WriteVerticalCS()                   */
    1957             : /************************************************************************/
    1958             : 
    1959          54 : bool S100BaseWriter::WriteVerticalCS(int nCode)
    1960             : {
    1961         108 :     return GH5_CreateAttribute(m_hdf5, "verticalCS", H5T_STD_I32LE) &&
    1962         108 :            GH5_WriteAttribute(m_hdf5, "verticalCS", nCode);
    1963             : }
    1964             : 
    1965             : /************************************************************************/
    1966             : /*                 S100BaseWriter::WriteVerticalDatum()                 */
    1967             : /************************************************************************/
    1968             : 
    1969          56 : bool S100BaseWriter::WriteVerticalDatum(hid_t hGroup, hid_t hType, int nCode)
    1970             : {
    1971         112 :     return GH5_CreateAttribute(hGroup, "verticalDatum", hType) &&
    1972         112 :            GH5_WriteAttribute(hGroup, "verticalDatum", nCode);
    1973             : }
    1974             : 
    1975             : /************************************************************************/
    1976             : /*                    S100BaseWriter::CreateGroupF()                    */
    1977             : /************************************************************************/
    1978             : 
    1979          70 : bool S100BaseWriter::CreateGroupF()
    1980             : {
    1981          70 :     m_GroupF.reset(H5_CHECK(H5Gcreate(m_hdf5, "Group_F", 0)));
    1982          70 :     return m_GroupF;
    1983             : }
    1984             : 
    1985             : /************************************************************************/
    1986             : /*                 S100BaseWriter::CreateFeatureGroup()                 */
    1987             : /************************************************************************/
    1988             : 
    1989          77 : bool S100BaseWriter::CreateFeatureGroup(const char *name)
    1990             : {
    1991          77 :     m_featureGroup.reset(H5_CHECK(H5Gcreate(m_hdf5, name, 0)));
    1992          77 :     return m_featureGroup;
    1993             : }
    1994             : 
    1995             : /************************************************************************/
    1996             : /*               S100BaseWriter::WriteDataCodingFormat()                */
    1997             : /************************************************************************/
    1998             : 
    1999          77 : bool S100BaseWriter::WriteDataCodingFormat(hid_t hGroup, int nCode)
    2000             : {
    2001          77 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    2002          77 :     bool ret = hEnumType;
    2003          77 :     if (hEnumType)
    2004             :     {
    2005          77 :         uint8_t val = 0;
    2006        1386 :         for (const char *pszEnumName :
    2007             :              {"Fixed Stations", "Regular Grid", "Ungeorectified Grid",
    2008             :               "Moving Platform", "Irregular Grid", "Variable cell size", "TIN",
    2009         770 :               "Fixed Stations (Stationwise)", "Feature oriented Regular Grid"})
    2010             :         {
    2011         693 :             ++val;
    2012        1386 :             ret = ret &&
    2013         693 :                   H5_CHECK(H5Tenum_insert(hEnumType, pszEnumName, &val)) >= 0;
    2014             :         }
    2015             : 
    2016          77 :         ret = ret &&
    2017         154 :               GH5_CreateAttribute(hGroup, "dataCodingFormat", hEnumType) &&
    2018          77 :               GH5_WriteAttribute(hGroup, "dataCodingFormat", nCode);
    2019             :     }
    2020         154 :     return ret;
    2021             : }
    2022             : 
    2023             : /************************************************************************/
    2024             : /*                S100BaseWriter::WriteCommonPointRule()                */
    2025             : /************************************************************************/
    2026             : 
    2027          77 : bool S100BaseWriter::WriteCommonPointRule(hid_t hGroup, int nCode)
    2028             : {
    2029          77 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    2030          77 :     bool ret = hEnumType;
    2031          77 :     if (hEnumType)
    2032             :     {
    2033          77 :         uint8_t val = 0;
    2034         385 :         for (const char *pszEnumName : {"average", "low", "high", "all"})
    2035             :         {
    2036         308 :             ++val;
    2037         616 :             ret = ret &&
    2038         308 :                   H5_CHECK(H5Tenum_insert(hEnumType, pszEnumName, &val)) >= 0;
    2039             :         }
    2040             : 
    2041          77 :         ret = ret &&
    2042         154 :               GH5_CreateAttribute(hGroup, "commonPointRule", hEnumType) &&
    2043          77 :               GH5_WriteAttribute(hGroup, "commonPointRule", nCode);
    2044             :     }
    2045         154 :     return ret;
    2046             : }
    2047             : 
    2048             : /************************************************************************/
    2049             : /*                S100BaseWriter::WriteDataOffsetCode()                 */
    2050             : /************************************************************************/
    2051             : 
    2052          77 : bool S100BaseWriter::WriteDataOffsetCode(hid_t hGroup, int nCode)
    2053             : {
    2054          77 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    2055          77 :     bool ret = hEnumType;
    2056          77 :     if (hEnumType)
    2057             :     {
    2058          77 :         uint8_t val = 0;
    2059         770 :         for (const char *pszEnumName :
    2060             :              {"XMin, YMin (\"Lower left\") corner (\"Cell origin\")",
    2061             :               "XMax, YMax (\"Upper right\") corner",
    2062             :               "XMax, YMin (\"Lower right\") corner",
    2063             :               "XMin, YMax (\"Upper left\") corner",
    2064         462 :               "Barycenter (centroid) of cell"})
    2065             :         {
    2066         385 :             ++val;
    2067         770 :             ret = ret &&
    2068         385 :                   H5_CHECK(H5Tenum_insert(hEnumType, pszEnumName, &val)) >= 0;
    2069             :         }
    2070             : 
    2071         154 :         ret = ret && GH5_CreateAttribute(hGroup, "dataOffsetCode", hEnumType) &&
    2072          77 :               GH5_WriteAttribute(hGroup, "dataOffsetCode", nCode);
    2073             :     }
    2074             : 
    2075         154 :     return ret;
    2076             : }
    2077             : 
    2078             : /************************************************************************/
    2079             : /*                   S100BaseWriter::WriteDimension()                   */
    2080             : /************************************************************************/
    2081             : 
    2082          77 : bool S100BaseWriter::WriteDimension(hid_t hGroup, int nCode)
    2083             : {
    2084          77 :     return WriteUInt8Value(hGroup, "dimension", nCode);
    2085             : }
    2086             : 
    2087             : /************************************************************************/
    2088             : /*         S100BaseWriter::WriteHorizontalPositionUncertainty()         */
    2089             : /************************************************************************/
    2090             : 
    2091          77 : bool S100BaseWriter::WriteHorizontalPositionUncertainty(hid_t hGroup,
    2092             :                                                         float fValue)
    2093             : {
    2094          77 :     return WriteFloat32Value(hGroup, "horizontalPositionUncertainty", fValue);
    2095             : }
    2096             : 
    2097             : /************************************************************************/
    2098             : /*               S100BaseWriter::WriteInterpolationType()               */
    2099             : /************************************************************************/
    2100             : 
    2101          77 : bool S100BaseWriter::WriteInterpolationType(hid_t hGroup, int nCode)
    2102             : {
    2103          77 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    2104          77 :     bool ret = hEnumType;
    2105          77 :     if (hEnumType)
    2106             :     {
    2107          77 :         uint8_t val = 0;
    2108          77 :         constexpr const char *NULL_STRING = nullptr;
    2109         770 :         for (const char *pszEnumName : {
    2110             :                  "nearestneighbor",  // 1
    2111             :                  NULL_STRING,        // 2
    2112             :                  NULL_STRING,        // 3
    2113             :                  NULL_STRING,        // 4
    2114             :                  "bilinear",         // 5
    2115             :                  "biquadratic",      // 6
    2116             :                  "bicubic",          // 7
    2117             :                  NULL_STRING,        // 8
    2118             :                  "barycentric",      // 9
    2119             :                  "discrete"          // 10
    2120         847 :              })
    2121             :         {
    2122         770 :             ++val;
    2123         770 :             if (pszEnumName)
    2124             :             {
    2125         462 :                 ret = ret && H5_CHECK(H5Tenum_insert(hEnumType, pszEnumName,
    2126             :                                                      &val)) >= 0;
    2127             :             }
    2128             :         }
    2129             : 
    2130          77 :         ret = ret &&
    2131         154 :               GH5_CreateAttribute(hGroup, "interpolationType", hEnumType) &&
    2132          77 :               GH5_WriteAttribute(hGroup, "interpolationType", nCode);
    2133             :     }
    2134         154 :     return ret;
    2135             : }
    2136             : 
    2137             : /************************************************************************/
    2138             : /*                 S100BaseWriter::WriteNumInstances()                  */
    2139             : /************************************************************************/
    2140             : 
    2141          77 : bool S100BaseWriter::WriteNumInstances(hid_t hGroup, hid_t hType,
    2142             :                                        int numInstances)
    2143             : {
    2144         154 :     return GH5_CreateAttribute(hGroup, "numInstances", hType) &&
    2145         154 :            GH5_WriteAttribute(hGroup, "numInstances", numInstances);
    2146             : }
    2147             : 
    2148             : /************************************************************************/
    2149             : /*          S100BaseWriter::WriteSequencingRuleScanDirection()          */
    2150             : /************************************************************************/
    2151             : 
    2152          77 : bool S100BaseWriter::WriteSequencingRuleScanDirection(hid_t hGroup,
    2153             :                                                       const char *pszValue)
    2154             : {
    2155          77 :     return WriteVarLengthStringValue(hGroup, "sequencingRule.scanDirection",
    2156          77 :                                      pszValue);
    2157             : }
    2158             : 
    2159             : /************************************************************************/
    2160             : /*              S100BaseWriter::WriteSequencingRuleType()               */
    2161             : /************************************************************************/
    2162             : 
    2163          77 : bool S100BaseWriter::WriteSequencingRuleType(hid_t hGroup, int nCode)
    2164             : {
    2165          77 :     GH5_HIDTypeHolder hEnumType(H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
    2166          77 :     bool ret = hEnumType;
    2167          77 :     if (hEnumType)
    2168             :     {
    2169          77 :         uint8_t val = 0;
    2170         924 :         for (const char *pszEnumName :
    2171             :              {"linear", "boustrophedonic", "CantorDiagonal", "spiral", "Morton",
    2172         539 :               "Hilbert"})
    2173             :         {
    2174         462 :             ++val;
    2175         924 :             ret = ret &&
    2176         462 :                   H5_CHECK(H5Tenum_insert(hEnumType, pszEnumName, &val)) >= 0;
    2177             :         }
    2178             : 
    2179          77 :         ret = ret &&
    2180         154 :               GH5_CreateAttribute(hGroup, "sequencingRule.type", hEnumType) &&
    2181          77 :               GH5_WriteAttribute(hGroup, "sequencingRule.type", nCode);
    2182             :     }
    2183         154 :     return ret;
    2184             : }
    2185             : 
    2186             : /************************************************************************/
    2187             : /*              S100BaseWriter::WriteVerticalUncertainty()              */
    2188             : /************************************************************************/
    2189             : 
    2190          77 : bool S100BaseWriter::WriteVerticalUncertainty(hid_t hGroup, float fValue)
    2191             : {
    2192          77 :     return WriteFloat32Value(hGroup, "verticalUncertainty", fValue);
    2193             : }
    2194             : 
    2195             : /************************************************************************/
    2196             : /*      S100BaseWriter::WriteOneDimensionalVarLengthStringArray()       */
    2197             : /************************************************************************/
    2198             : 
    2199         147 : bool S100BaseWriter::WriteOneDimensionalVarLengthStringArray(
    2200             :     hid_t hGroup, const char *name, CSLConstList values)
    2201             : {
    2202         147 :     bool ret = false;
    2203         147 :     hsize_t dims[1] = {static_cast<hsize_t>(CSLCount(values))};
    2204         294 :     GH5_HIDSpaceHolder hSpaceId(H5_CHECK(H5Screate_simple(1, dims, NULL)));
    2205         147 :     GH5_HIDTypeHolder hTypeId(H5_CHECK(H5Tcopy(H5T_C_S1)));
    2206         147 :     if (hSpaceId && hTypeId)
    2207             :     {
    2208         294 :         ret = H5_CHECK(H5Tset_size(hTypeId, H5T_VARIABLE)) >= 0 &&
    2209         147 :               H5_CHECK(H5Tset_strpad(hTypeId, H5T_STR_NULLTERM)) >= 0;
    2210         294 :         GH5_HIDDatasetHolder hDSId;
    2211         147 :         if (ret)
    2212             :         {
    2213         147 :             hDSId.reset(H5_CHECK(
    2214             :                 H5Dcreate(hGroup, name, hTypeId, hSpaceId, H5P_DEFAULT)));
    2215         147 :             if (hDSId)
    2216         147 :                 ret = H5Dwrite(hDSId, hTypeId, H5S_ALL, H5S_ALL, H5P_DEFAULT,
    2217             :                                values) >= 0;
    2218             :         }
    2219             :     }
    2220         294 :     return ret;
    2221             : }
    2222             : 
    2223             : /************************************************************************/
    2224             : /*                   S100BaseWriter::WriteAxisNames()                   */
    2225             : /************************************************************************/
    2226             : 
    2227          77 : bool S100BaseWriter::WriteAxisNames(hid_t hGroup)
    2228             : {
    2229          77 :     const char *axisProjected[] = {"Easting", "Northing", nullptr};
    2230          77 :     const char *axisGeographic[] = {"Latitude", "Longitude", nullptr};
    2231          77 :     return WriteOneDimensionalVarLengthStringArray(
    2232             :         hGroup, "axisNames",
    2233         231 :         m_poSRS->IsProjected() ? axisProjected : axisGeographic);
    2234             : }
    2235             : 
    2236             : /************************************************************************/
    2237             : /*             S100BaseWriter::CreateFeatureInstanceGroup()             */
    2238             : /************************************************************************/
    2239             : 
    2240          82 : bool S100BaseWriter::CreateFeatureInstanceGroup(const char *name)
    2241             : {
    2242          82 :     CPLAssert(m_featureGroup);
    2243          82 :     m_featureInstanceGroup.reset(H5_CHECK(H5Gcreate(m_featureGroup, name, 0)));
    2244          82 :     return m_featureInstanceGroup;
    2245             : }
    2246             : 
    2247             : /************************************************************************/
    2248             : /*           S100BaseWriter::WriteFIGGridRelatedParameters()            */
    2249             : /************************************************************************/
    2250             : 
    2251          82 : bool S100BaseWriter::WriteFIGGridRelatedParameters(hid_t hGroup)
    2252             : {
    2253             :     // From pixel-corner convention to pixel-center convention
    2254          82 :     const double dfMinX = m_gt.xorig + m_gt.xscale / 2;
    2255          82 :     const double dfMinY = m_gt.yscale < 0
    2256          82 :                               ? m_gt.yorig +
    2257          14 :                                     m_gt.yscale * m_poSrcDS->GetRasterYSize() -
    2258          14 :                                     m_gt.yscale / 2
    2259          68 :                               : m_gt.yorig + m_gt.yscale / 2;
    2260             :     const double dfMaxX =
    2261          82 :         dfMinX + (m_poSrcDS->GetRasterXSize() - 1) * m_gt.xscale;
    2262             :     const double dfMaxY =
    2263          82 :         dfMinY + (m_poSrcDS->GetRasterYSize() - 1) * std::fabs(m_gt.yscale);
    2264             : 
    2265          82 :     return WriteFloat32Value(hGroup, "westBoundLongitude", dfMinX) &&
    2266          82 :            WriteFloat32Value(hGroup, "southBoundLatitude", dfMinY) &&
    2267          82 :            WriteFloat32Value(hGroup, "eastBoundLongitude", dfMaxX) &&
    2268          82 :            WriteFloat32Value(hGroup, "northBoundLatitude", dfMaxY) &&
    2269          82 :            WriteFloat64Value(hGroup, "gridOriginLongitude", dfMinX) &&
    2270          82 :            WriteFloat64Value(hGroup, "gridOriginLatitude", dfMinY) &&
    2271          82 :            WriteFloat64Value(hGroup, "gridSpacingLongitudinal", m_gt.xscale) &&
    2272          82 :            WriteFloat64Value(hGroup, "gridSpacingLatitudinal",
    2273          82 :                              std::fabs(m_gt.yscale)) &&
    2274          82 :            WriteUInt32Value(hGroup, "numPointsLongitudinal",
    2275          82 :                             m_poSrcDS->GetRasterXSize()) &&
    2276          82 :            WriteUInt32Value(hGroup, "numPointsLatitudinal",
    2277         246 :                             m_poSrcDS->GetRasterYSize()) &&
    2278         164 :            WriteVarLengthStringValue(hGroup, "startSequence", "0,0");
    2279             : }
    2280             : 
    2281             : /************************************************************************/
    2282             : /*                    S100BaseWriter::WriteNumGRP()                     */
    2283             : /************************************************************************/
    2284             : 
    2285          82 : bool S100BaseWriter::WriteNumGRP(hid_t hGroup, hid_t hType, int numGRP)
    2286             : {
    2287         164 :     return GH5_CreateAttribute(hGroup, "numGRP", hType) &&
    2288         164 :            GH5_WriteAttribute(hGroup, "numGRP", numGRP);
    2289             : }
    2290             : 
    2291             : /************************************************************************/
    2292             : /*                 S100BaseWriter::CreateValuesGroup()                  */
    2293             : /************************************************************************/
    2294             : 
    2295          86 : bool S100BaseWriter::CreateValuesGroup(const char *name)
    2296             : {
    2297          86 :     CPLAssert(m_featureInstanceGroup);
    2298          86 :     m_valuesGroup.reset(H5_CHECK(H5Gcreate(m_featureInstanceGroup, name, 0)));
    2299          86 :     return m_valuesGroup;
    2300             : }
    2301             : 
    2302             : /************************************************************************/
    2303             : /*                 S100BaseWriter::WriteGroupFDataset()                 */
    2304             : /************************************************************************/
    2305             : 
    2306          85 : bool S100BaseWriter::WriteGroupFDataset(
    2307             :     const char *name,
    2308             :     const std::vector<std::array<const char *, GROUP_F_DATASET_FIELD_COUNT>>
    2309             :         &rows)
    2310             : {
    2311             :     GH5_HIDTypeHolder hDataType(H5_CHECK(
    2312         170 :         H5Tcreate(H5T_COMPOUND, GROUP_F_DATASET_FIELD_COUNT * sizeof(char *))));
    2313         170 :     GH5_HIDTypeHolder hVarLengthType(H5_CHECK(H5Tcopy(H5T_C_S1)));
    2314             :     bool bRet =
    2315         170 :         hDataType && hVarLengthType &&
    2316          85 :         H5_CHECK(H5Tset_size(hVarLengthType, H5T_VARIABLE)) >= 0 &&
    2317          85 :         H5_CHECK(H5Tset_strpad(hVarLengthType, H5T_STR_NULLTERM)) >= 0 &&
    2318          85 :         H5_CHECK(H5Tinsert(hDataType, "code", 0 * sizeof(char *),
    2319          85 :                            hVarLengthType)) >= 0 &&
    2320          85 :         H5_CHECK(H5Tinsert(hDataType, "name", 1 * sizeof(char *),
    2321          85 :                            hVarLengthType)) >= 0 &&
    2322          85 :         H5_CHECK(H5Tinsert(hDataType, "uom.name", 2 * sizeof(char *),
    2323          85 :                            hVarLengthType)) >= 0 &&
    2324          85 :         H5_CHECK(H5Tinsert(hDataType, "fillValue", 3 * sizeof(char *),
    2325          85 :                            hVarLengthType)) >= 0 &&
    2326          85 :         H5_CHECK(H5Tinsert(hDataType, "datatype", 4 * sizeof(char *),
    2327          85 :                            hVarLengthType)) >= 0 &&
    2328          85 :         H5_CHECK(H5Tinsert(hDataType, "lower", 5 * sizeof(char *),
    2329          85 :                            hVarLengthType)) >= 0 &&
    2330          85 :         H5_CHECK(H5Tinsert(hDataType, "upper", 6 * sizeof(char *),
    2331         170 :                            hVarLengthType)) >= 0 &&
    2332          85 :         H5_CHECK(H5Tinsert(hDataType, "closure", 7 * sizeof(char *),
    2333          85 :                            hVarLengthType)) >= 0;
    2334             : 
    2335          85 :     hsize_t dims[] = {static_cast<hsize_t>(rows.size())};
    2336         170 :     GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(1, dims, nullptr)));
    2337          85 :     bRet = bRet && hDataSpace;
    2338         170 :     GH5_HIDDatasetHolder hDatasetID;
    2339          85 :     if (bRet)
    2340             :     {
    2341          85 :         hDatasetID.reset(H5_CHECK(
    2342             :             H5Dcreate(m_GroupF, name, hDataType, hDataSpace, H5P_DEFAULT)));
    2343          85 :         bRet = hDatasetID;
    2344             :     }
    2345         170 :     GH5_HIDSpaceHolder hFileSpace;
    2346          85 :     if (bRet)
    2347             :     {
    2348          85 :         hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
    2349          85 :         bRet = hFileSpace;
    2350             :     }
    2351             : 
    2352          85 :     hsize_t count[] = {1};
    2353          85 :     GH5_HIDSpaceHolder hMemSpace(H5_CHECK(H5Screate_simple(1, count, nullptr)));
    2354          85 :     bRet = bRet && hMemSpace;
    2355             : 
    2356          85 :     H5OFFSET_TYPE nOffset = 0;
    2357         239 :     for (const auto &row : rows)
    2358             :     {
    2359         154 :         H5OFFSET_TYPE offset[] = {nOffset};
    2360         308 :         bRet = bRet &&
    2361         154 :                H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
    2362         308 :                                             nullptr, count, nullptr)) >= 0 &&
    2363         154 :                H5_CHECK(H5Dwrite(hDatasetID, hDataType, hMemSpace, hFileSpace,
    2364             :                                  H5P_DEFAULT, row.data())) >= 0;
    2365         154 :         ++nOffset;
    2366             :     }
    2367             : 
    2368         170 :     return bRet;
    2369             : }

Generated by: LCOV version 1.14