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

Generated by: LCOV version 1.14