LCOV - code coverage report
Current view: top level - frmts/hdf5 - s100.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 367 417 88.0 %
Date: 2025-10-21 22:35:35 Functions: 12 12 100.0 %

          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 "s100.h"
      14             : #include "hdf5dataset.h"
      15             : 
      16             : #include "proj.h"
      17             : #include "proj_experimental.h"
      18             : #include "proj_constants.h"
      19             : #include "ogr_proj_p.h"
      20             : 
      21             : #include <algorithm>
      22             : #include <cmath>
      23             : 
      24             : /************************************************************************/
      25             : /*                       S100BaseDataset()                              */
      26             : /************************************************************************/
      27             : 
      28          55 : S100BaseDataset::S100BaseDataset(const std::string &osFilename)
      29          55 :     : m_osFilename(osFilename)
      30             : {
      31          55 : }
      32             : 
      33             : /************************************************************************/
      34             : /*                              Init()                                  */
      35             : /************************************************************************/
      36             : 
      37          55 : bool S100BaseDataset::Init()
      38             : {
      39             :     // Open the file as an HDF5 file.
      40          55 :     hid_t fapl = H5Pcreate(H5P_FILE_ACCESS);
      41          55 :     H5Pset_driver(fapl, HDF5GetFileDriver(), nullptr);
      42          55 :     hid_t hHDF5 = H5Fopen(m_osFilename.c_str(), H5F_ACC_RDONLY, fapl);
      43          55 :     H5Pclose(fapl);
      44          55 :     if (hHDF5 < 0)
      45           0 :         return false;
      46             : 
      47         110 :     auto poSharedResources = GDAL::HDF5SharedResources::Create(m_osFilename);
      48          55 :     poSharedResources->m_hHDF5 = hHDF5;
      49             : 
      50          55 :     m_poRootGroup = HDF5Dataset::OpenGroup(poSharedResources);
      51          55 :     if (m_poRootGroup == nullptr)
      52           0 :         return false;
      53             : 
      54          55 :     S100ReadSRS(m_poRootGroup.get(), m_oSRS);
      55             : 
      56          55 :     S100ReadVerticalDatum(this, m_poRootGroup.get());
      57             : 
      58             :     m_osMetadataFile =
      59          55 :         S100ReadMetadata(this, m_osFilename, m_poRootGroup.get());
      60             : 
      61          55 :     return true;
      62             : }
      63             : 
      64             : /************************************************************************/
      65             : /*                          GetGeoTransform()                           */
      66             : /************************************************************************/
      67             : 
      68          12 : CPLErr S100BaseDataset::GetGeoTransform(GDALGeoTransform &gt) const
      69             : 
      70             : {
      71          12 :     if (m_bHasGT)
      72             :     {
      73          12 :         gt = m_gt;
      74          12 :         return CE_None;
      75             :     }
      76             : 
      77           0 :     return GDALPamDataset::GetGeoTransform(gt);
      78             : }
      79             : 
      80             : /************************************************************************/
      81             : /*                         GetSpatialRef()                              */
      82             : /************************************************************************/
      83             : 
      84          24 : const OGRSpatialReference *S100BaseDataset::GetSpatialRef() const
      85             : {
      86          24 :     if (!m_oSRS.IsEmpty())
      87          24 :         return &m_oSRS;
      88           0 :     return GDALPamDataset::GetSpatialRef();
      89             : }
      90             : 
      91             : /************************************************************************/
      92             : /*                         GetFileList()                                */
      93             : /************************************************************************/
      94             : 
      95           4 : char **S100BaseDataset::GetFileList()
      96             : {
      97           4 :     char **papszFileList = GDALPamDataset::GetFileList();
      98           4 :     if (!m_osMetadataFile.empty())
      99           4 :         papszFileList = CSLAddString(papszFileList, m_osMetadataFile.c_str());
     100           4 :     return papszFileList;
     101             : }
     102             : 
     103             : /************************************************************************/
     104             : /*                            S100ReadSRS()                             */
     105             : /************************************************************************/
     106             : 
     107          85 : bool S100ReadSRS(const GDALGroup *poRootGroup, OGRSpatialReference &oSRS)
     108             : {
     109          85 :     constexpr int PROJECTION_METHOD_MERCATOR = 9805;
     110             :     static_assert(PROJECTION_METHOD_MERCATOR ==
     111             :                   EPSG_CODE_METHOD_MERCATOR_VARIANT_B);
     112          85 :     constexpr int PROJECTION_METHOD_TRANSVERSE_MERCATOR = 9807;
     113             :     static_assert(PROJECTION_METHOD_TRANSVERSE_MERCATOR ==
     114             :                   EPSG_CODE_METHOD_TRANSVERSE_MERCATOR);
     115          85 :     constexpr int PROJECTION_METHOD_OBLIQUE_MERCATOR = 9815;
     116             :     static_assert(PROJECTION_METHOD_OBLIQUE_MERCATOR ==
     117             :                   EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_B);
     118          85 :     constexpr int PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR = 9812;
     119             :     static_assert(PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR ==
     120             :                   EPSG_CODE_METHOD_HOTINE_OBLIQUE_MERCATOR_VARIANT_A);
     121          85 :     constexpr int PROJECTION_METHOD_LCC_1SP = 9801;
     122             :     static_assert(PROJECTION_METHOD_LCC_1SP ==
     123             :                   EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_1SP);
     124          85 :     constexpr int PROJECTION_METHOD_LCC_2SP = 9802;
     125             :     static_assert(PROJECTION_METHOD_LCC_2SP ==
     126             :                   EPSG_CODE_METHOD_LAMBERT_CONIC_CONFORMAL_2SP);
     127          85 :     constexpr int PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC = 9809;
     128             :     static_assert(PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC ==
     129             :                   EPSG_CODE_METHOD_OBLIQUE_STEREOGRAPHIC);
     130          85 :     constexpr int PROJECTION_METHOD_POLAR_STEREOGRAPHIC = 9810;
     131             :     static_assert(PROJECTION_METHOD_POLAR_STEREOGRAPHIC ==
     132             :                   EPSG_CODE_METHOD_POLAR_STEREOGRAPHIC_VARIANT_A);
     133          85 :     constexpr int PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL = 9819;
     134             :     static_assert(PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL ==
     135             :                   EPSG_CODE_METHOD_KROVAK);
     136          85 :     constexpr int PROJECTION_METHOD_AMERICAN_POLYCONIC = 9818;
     137             :     static_assert(PROJECTION_METHOD_AMERICAN_POLYCONIC ==
     138             :                   EPSG_CODE_METHOD_AMERICAN_POLYCONIC);
     139          85 :     constexpr int PROJECTION_METHOD_ALBERS_EQUAL_AREA = 9822;
     140             :     static_assert(PROJECTION_METHOD_ALBERS_EQUAL_AREA ==
     141             :                   EPSG_CODE_METHOD_ALBERS_EQUAL_AREA);
     142          85 :     constexpr int PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA = 9820;
     143             :     static_assert(PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA ==
     144             :                   EPSG_CODE_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA);
     145             : 
     146             :     // Get SRS
     147          85 :     oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     148         255 :     auto poHorizontalCRS = poRootGroup->GetAttribute("horizontalCRS");
     149         147 :     if (poHorizontalCRS &&
     150         147 :         poHorizontalCRS->GetDataType().GetClass() == GEDTC_NUMERIC)
     151             :     {
     152             :         // horizontalCRS is v2.2
     153          62 :         const int nHorizontalCRS = poHorizontalCRS->ReadAsInt();
     154          62 :         if (nHorizontalCRS > 0)
     155             :         {
     156          48 :             if (oSRS.importFromEPSG(nHorizontalCRS) != OGRERR_NONE)
     157             :             {
     158           0 :                 oSRS.Clear();
     159             :             }
     160             :         }
     161             :         else
     162             :         {
     163             :             auto poNameOfHorizontalCRS =
     164          28 :                 poRootGroup->GetAttribute("nameOfHorizontalCRS");
     165             :             auto poTypeOfHorizontalCRS =
     166          28 :                 poRootGroup->GetAttribute("typeOfHorizontalCRS");
     167          28 :             auto poHorizontalCS = poRootGroup->GetAttribute("horizontalCS");
     168             :             auto poHorizontalDatum =
     169          28 :                 poRootGroup->GetAttribute("horizontalDatum");
     170          14 :             if (poNameOfHorizontalCRS &&
     171          14 :                 poNameOfHorizontalCRS->GetDataType().GetClass() ==
     172          28 :                     GEDTC_STRING &&
     173          28 :                 poTypeOfHorizontalCRS &&
     174          14 :                 poTypeOfHorizontalCRS->GetDataType().GetClass() ==
     175          14 :                     GEDTC_NUMERIC &&
     176          28 :                 poHorizontalCS &&
     177          28 :                 poHorizontalCS->GetDataType().GetClass() == GEDTC_NUMERIC &&
     178          42 :                 poHorizontalDatum &&
     179          14 :                 poHorizontalDatum->GetDataType().GetClass() == GEDTC_NUMERIC)
     180             :             {
     181          14 :                 const int nCRSType = poTypeOfHorizontalCRS->ReadAsInt();
     182          14 :                 constexpr int CRS_TYPE_GEOGRAPHIC = 1;
     183          14 :                 constexpr int CRS_TYPE_PROJECTED = 2;
     184             : 
     185          14 :                 PJ *crs = nullptr;
     186          14 :                 const char *pszCRSName = poNameOfHorizontalCRS->ReadAsString();
     187             : 
     188          14 :                 const int nHorizontalCS = poHorizontalCS->ReadAsInt();
     189          14 :                 const int nHorizontalDatum = poHorizontalDatum->ReadAsInt();
     190             : 
     191          14 :                 auto pjCtxt = OSRGetProjTLSContext();
     192          14 :                 if (nHorizontalDatum < 0)
     193             :                 {
     194             :                     auto poNameOfHorizontalDatum =
     195           2 :                         poRootGroup->GetAttribute("nameOfHorizontalDatum");
     196             :                     auto poPrimeMeridian =
     197           2 :                         poRootGroup->GetAttribute("primeMeridian");
     198           2 :                     auto poSpheroid = poRootGroup->GetAttribute("spheroid");
     199           1 :                     if (poNameOfHorizontalDatum &&
     200           1 :                         poNameOfHorizontalDatum->GetDataType().GetClass() ==
     201           2 :                             GEDTC_STRING &&
     202           2 :                         poPrimeMeridian &&
     203           1 :                         poPrimeMeridian->GetDataType().GetClass() ==
     204           1 :                             GEDTC_NUMERIC &&
     205           3 :                         poSpheroid &&
     206           1 :                         poSpheroid->GetDataType().GetClass() == GEDTC_NUMERIC)
     207             :                     {
     208             :                         const char *pszDatumName =
     209           1 :                             poNameOfHorizontalDatum->ReadAsString();
     210           1 :                         const char *pszGeogCRSName =
     211             :                             nCRSType == CRS_TYPE_PROJECTED
     212           2 :                                 ? (pszDatumName ? pszDatumName : "unknown")
     213           1 :                                 : (pszCRSName ? pszCRSName : "unknown");
     214             : 
     215           1 :                         const int nPrimeMeridian = poPrimeMeridian->ReadAsInt();
     216           1 :                         const int nSpheroid = poSpheroid->ReadAsInt();
     217           1 :                         PJ *prime_meridian = proj_create_from_database(
     218             :                             pjCtxt, "EPSG", CPLSPrintf("%d", nPrimeMeridian),
     219             :                             PJ_CATEGORY_PRIME_MERIDIAN, false, nullptr);
     220           1 :                         if (!prime_meridian)
     221           0 :                             return false;
     222           1 :                         PJ *spheroid = proj_create_from_database(
     223             :                             pjCtxt, "EPSG", CPLSPrintf("%d", nSpheroid),
     224             :                             PJ_CATEGORY_ELLIPSOID, false, nullptr);
     225           1 :                         if (!spheroid)
     226             :                         {
     227           0 :                             proj_destroy(prime_meridian);
     228           0 :                             return false;
     229             :                         }
     230             : 
     231           1 :                         double semiMajorMetre = 0;
     232           1 :                         double invFlattening = 0;
     233           1 :                         proj_ellipsoid_get_parameters(pjCtxt, spheroid,
     234             :                                                       &semiMajorMetre, nullptr,
     235             :                                                       nullptr, &invFlattening);
     236             : 
     237           1 :                         double primeMeridianOffset = 0;
     238           1 :                         double primeMeridianUnitConv = 1;
     239           1 :                         const char *primeMeridianUnitName = nullptr;
     240           1 :                         proj_prime_meridian_get_parameters(
     241             :                             pjCtxt, prime_meridian, &primeMeridianOffset,
     242             :                             &primeMeridianUnitConv, &primeMeridianUnitName);
     243             : 
     244           1 :                         PJ *ellipsoidalCS = proj_create_ellipsoidal_2D_cs(
     245             :                             pjCtxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr,
     246             :                             0.0);
     247             : 
     248           1 :                         crs = proj_create_geographic_crs(
     249             :                             pjCtxt, pszGeogCRSName,
     250             :                             pszDatumName ? pszDatumName : "unknown",
     251             :                             proj_get_name(spheroid), semiMajorMetre,
     252             :                             invFlattening, proj_get_name(prime_meridian),
     253             :                             primeMeridianOffset, primeMeridianUnitName,
     254             :                             primeMeridianUnitConv, ellipsoidalCS);
     255             : 
     256           1 :                         proj_destroy(ellipsoidalCS);
     257           1 :                         proj_destroy(spheroid);
     258           1 :                         proj_destroy(prime_meridian);
     259             :                     }
     260             :                     else
     261             :                     {
     262           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     263             :                                  "Cannot establish CRS because "
     264             :                                  "nameOfHorizontalDatum, primeMeridian and/or "
     265             :                                  "spheroid are missing");
     266           0 :                         return false;
     267             :                     }
     268             :                 }
     269             :                 else
     270             :                 {
     271          13 :                     PJ *datum = proj_create_from_database(
     272             :                         pjCtxt, "EPSG", CPLSPrintf("%d", nHorizontalDatum),
     273             :                         PJ_CATEGORY_DATUM, false, nullptr);
     274          13 :                     if (!datum)
     275           0 :                         return false;
     276          13 :                     const char *pszDatumName = proj_get_name(datum);
     277          13 :                     const char *pszGeogCRSName =
     278             :                         nCRSType == CRS_TYPE_PROJECTED
     279          14 :                             ? pszDatumName
     280           1 :                             : (pszCRSName ? pszCRSName : "unknown");
     281             : 
     282          13 :                     PJ *ellipsoidalCS = proj_create_ellipsoidal_2D_cs(
     283             :                         pjCtxt, PJ_ELLPS2D_LATITUDE_LONGITUDE, nullptr, 0.0);
     284             : 
     285          13 :                     crs = proj_create_geographic_crs_from_datum(
     286             :                         pjCtxt, pszGeogCRSName, datum, ellipsoidalCS);
     287             : 
     288          13 :                     proj_destroy(ellipsoidalCS);
     289          13 :                     proj_destroy(datum);
     290             :                 }
     291             : 
     292          14 :                 if (nCRSType == CRS_TYPE_PROJECTED)
     293             :                 {
     294             :                     auto poProjectionMethod =
     295          24 :                         poRootGroup->GetAttribute("projectionMethod");
     296          24 :                     if (poProjectionMethod &&
     297          12 :                         poProjectionMethod->GetDataType().GetClass() ==
     298          12 :                             GEDTC_NUMERIC)
     299             :                     {
     300             :                         const int nProjectionMethod =
     301          12 :                             poProjectionMethod->ReadAsInt();
     302             : 
     303             :                         auto poFalseEasting =
     304          24 :                             poRootGroup->GetAttribute("falseEasting");
     305             :                         const double falseEasting =
     306          12 :                             poFalseEasting &&
     307          12 :                                     poFalseEasting->GetDataType().GetClass() ==
     308          12 :                                         GEDTC_NUMERIC
     309          24 :                                 ? poFalseEasting->ReadAsDouble()
     310          12 :                                 : 0.0;
     311             : 
     312             :                         auto poFalseNorthing =
     313          24 :                             poRootGroup->GetAttribute("falseNorthing");
     314             :                         const double falseNorthing =
     315          12 :                             poFalseNorthing &&
     316          12 :                                     poFalseNorthing->GetDataType().GetClass() ==
     317          12 :                                         GEDTC_NUMERIC
     318          24 :                                 ? poFalseNorthing->ReadAsDouble()
     319          12 :                                 : 0.0;
     320             : 
     321          41 :                         const auto getParameter = [poRootGroup](int nParam)
     322             :                         {
     323             :                             auto poParam = poRootGroup->GetAttribute(
     324         123 :                                 "projectionParameter" + std::to_string(nParam));
     325          41 :                             if (poParam && poParam->GetDataType().GetClass() ==
     326          41 :                                                GEDTC_NUMERIC)
     327          41 :                                 return poParam->ReadAsDouble();
     328           0 :                             CPLError(
     329             :                                 CE_Warning, CPLE_AppDefined, "%s",
     330           0 :                                 std::string(
     331             :                                     "Missing attribute projectionParameter")
     332           0 :                                     .append(std::to_string(nParam))
     333             :                                     .c_str());
     334           0 :                             return 0.0;
     335          12 :                         };
     336             : 
     337          12 :                         PJ *conv = nullptr;
     338          12 :                         switch (nProjectionMethod)
     339             :                         {
     340           1 :                             case PROJECTION_METHOD_MERCATOR:
     341             :                             {
     342             :                                 conv =
     343           1 :                                     proj_create_conversion_mercator_variant_b(
     344             :                                         pjCtxt,
     345             :                                         /* latitude_first_parallel = */
     346             :                                         getParameter(1),
     347             :                                         /* center_long = */ getParameter(2),
     348             :                                         falseEasting, falseNorthing, nullptr,
     349             :                                         0.0, nullptr, 0.0);
     350           1 :                                 break;
     351             :                             }
     352             : 
     353           1 :                             case PROJECTION_METHOD_TRANSVERSE_MERCATOR:
     354             :                             {
     355             :                                 conv =
     356           1 :                                     proj_create_conversion_transverse_mercator(
     357             :                                         pjCtxt,
     358             :                                         /* center_lat = */ getParameter(1),
     359             :                                         /* center_long = */ getParameter(2),
     360             :                                         /* scale = */ getParameter(3),
     361             :                                         falseEasting, falseNorthing, nullptr,
     362             :                                         0.0, nullptr, 0.0);
     363           1 :                                 break;
     364             :                             }
     365             : 
     366           1 :                             case PROJECTION_METHOD_OBLIQUE_MERCATOR:
     367             :                             {
     368             :                                 conv =
     369           1 :                                     proj_create_conversion_hotine_oblique_mercator_variant_b(
     370             :                                         pjCtxt,
     371             :                                         /* latitude_projection_centre = */
     372             :                                         getParameter(1),
     373             :                                         /* longitude_projection_centre = */
     374             :                                         getParameter(2),
     375             :                                         /* azimuth_initial_line = */
     376             :                                         getParameter(3),
     377             :                                         /* angle_from_rectified_to_skrew_grid = */
     378             :                                         getParameter(4),
     379             :                                         /* scale = */ getParameter(5),
     380             :                                         falseEasting, falseNorthing, nullptr,
     381             :                                         0.0, nullptr, 0.0);
     382           1 :                                 break;
     383             :                             }
     384             : 
     385           1 :                             case PROJECTION_METHOD_HOTINE_OBLIQUE_MERCATOR:
     386             :                             {
     387             :                                 conv =
     388           1 :                                     proj_create_conversion_hotine_oblique_mercator_variant_a(
     389             :                                         pjCtxt,
     390             :                                         /* latitude_projection_centre = */
     391             :                                         getParameter(1),
     392             :                                         /* longitude_projection_centre = */
     393             :                                         getParameter(2),
     394             :                                         /* azimuth_initial_line = */
     395             :                                         getParameter(3),
     396             :                                         /* angle_from_rectified_to_skrew_grid = */
     397             :                                         getParameter(4),
     398             :                                         /* scale = */ getParameter(5),
     399             :                                         falseEasting, falseNorthing, nullptr,
     400             :                                         0.0, nullptr, 0.0);
     401           1 :                                 break;
     402             :                             }
     403             : 
     404           1 :                             case PROJECTION_METHOD_LCC_1SP:
     405             :                             {
     406             :                                 conv =
     407           1 :                                     proj_create_conversion_lambert_conic_conformal_1sp(
     408             :                                         pjCtxt,
     409             :                                         /* center_lat = */ getParameter(1),
     410             :                                         /* center_long = */ getParameter(2),
     411             :                                         /* scale = */ getParameter(3),
     412             :                                         falseEasting, falseNorthing, nullptr,
     413             :                                         0.0, nullptr, 0.0);
     414           1 :                                 break;
     415             :                             }
     416             : 
     417           1 :                             case PROJECTION_METHOD_LCC_2SP:
     418             :                             {
     419             :                                 conv =
     420           1 :                                     proj_create_conversion_lambert_conic_conformal_2sp(
     421             :                                         pjCtxt,
     422             :                                         /* latitude_false_origin = */
     423             :                                         getParameter(1),
     424             :                                         /* longitude_false_origin = */
     425             :                                         getParameter(2),
     426             :                                         /* latitude_first_parallel = */
     427             :                                         getParameter(3),
     428             :                                         /* latitude_second_parallel = */
     429             :                                         getParameter(4), falseEasting,
     430             :                                         falseNorthing, nullptr, 0.0, nullptr,
     431             :                                         0.0);
     432           1 :                                 break;
     433             :                             }
     434             : 
     435           1 :                             case PROJECTION_METHOD_OBLIQUE_STEREOGRAPHIC:
     436             :                             {
     437             :                                 conv =
     438           1 :                                     proj_create_conversion_oblique_stereographic(
     439             :                                         pjCtxt,
     440             :                                         /* center_lat = */ getParameter(1),
     441             :                                         /* center_long = */ getParameter(2),
     442             :                                         /* scale = */ getParameter(3),
     443             :                                         falseEasting, falseNorthing, nullptr,
     444             :                                         0.0, nullptr, 0.0);
     445           1 :                                 break;
     446             :                             }
     447             : 
     448           1 :                             case PROJECTION_METHOD_POLAR_STEREOGRAPHIC:
     449             :                             {
     450             :                                 conv =
     451           1 :                                     proj_create_conversion_polar_stereographic_variant_a(
     452             :                                         pjCtxt,
     453             :                                         /* center_lat = */ getParameter(1),
     454             :                                         /* center_long = */ getParameter(2),
     455             :                                         /* scale = */ getParameter(3),
     456             :                                         falseEasting, falseNorthing, nullptr,
     457             :                                         0.0, nullptr, 0.0);
     458           1 :                                 break;
     459             :                             }
     460             : 
     461           1 :                             case PROJECTION_METHOD_KROVAK_OBLIQUE_CONIC_CONFORMAL:
     462             :                             {
     463           1 :                                 conv = proj_create_conversion_krovak(
     464             :                                     pjCtxt,
     465             :                                     /* latitude_projection_centre = */
     466             :                                     getParameter(1),
     467             :                                     /* longitude_of_origin = */ getParameter(2),
     468             :                                     /* colatitude_cone_axis = */
     469             :                                     getParameter(3),
     470             :                                     /* latitude_pseudo_standard_parallel = */
     471             :                                     getParameter(4),
     472             :                                     /* scale_factor_pseudo_standard_parallel = */
     473             :                                     getParameter(5), falseEasting,
     474             :                                     falseNorthing, nullptr, 0.0, nullptr, 0.0);
     475           1 :                                 break;
     476             :                             }
     477             : 
     478           1 :                             case PROJECTION_METHOD_AMERICAN_POLYCONIC:
     479             :                             {
     480             :                                 conv =
     481           1 :                                     proj_create_conversion_american_polyconic(
     482             :                                         pjCtxt,
     483             :                                         /* center_lat = */ getParameter(1),
     484             :                                         /* center_long = */ getParameter(2),
     485             :                                         falseEasting, falseNorthing, nullptr,
     486             :                                         0.0, nullptr, 0.0);
     487           1 :                                 break;
     488             :                             }
     489             : 
     490           1 :                             case PROJECTION_METHOD_ALBERS_EQUAL_AREA:
     491             :                             {
     492           1 :                                 conv = proj_create_conversion_albers_equal_area(
     493             :                                     pjCtxt,
     494             :                                     /* latitude_false_origin = */
     495             :                                     getParameter(1),
     496             :                                     /* longitude_false_origin = */
     497             :                                     getParameter(2),
     498             :                                     /* latitude_first_parallel = */
     499             :                                     getParameter(3),
     500             :                                     /* latitude_second_parallel = */
     501             :                                     getParameter(4), falseEasting,
     502             :                                     falseNorthing, nullptr, 0.0, nullptr, 0.0);
     503           1 :                                 break;
     504             :                             }
     505             : 
     506           1 :                             case PROJECTION_METHOD_LAMBERT_AZIMUTHAL_EQUAL_AREA:
     507             :                             {
     508             :                                 conv =
     509           1 :                                     proj_create_conversion_lambert_azimuthal_equal_area(
     510             :                                         pjCtxt,
     511             :                                         /* latitude_nat_origin = */
     512             :                                         getParameter(1),
     513             :                                         /* longitude_nat_origin = */
     514             :                                         getParameter(2), falseEasting,
     515             :                                         falseNorthing, nullptr, 0.0, nullptr,
     516             :                                         0.0);
     517           1 :                                 break;
     518             :                             }
     519             : 
     520           0 :                             default:
     521             :                             {
     522           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
     523             :                                          "Cannot establish CRS because "
     524             :                                          "projectionMethod = %d is unhandled",
     525             :                                          nProjectionMethod);
     526           0 :                                 proj_destroy(crs);
     527           0 :                                 return false;
     528             :                             }
     529             :                         }
     530             : 
     531          12 :                         constexpr int HORIZONTAL_CS_EASTING_NORTHING_METRE =
     532             :                             4400;
     533             :                         // constexpr int HORIZONTAL_CS_NORTHING_EASTING_METRE = 4500;
     534          24 :                         PJ *cartesianCS = proj_create_cartesian_2D_cs(
     535             :                             pjCtxt,
     536             :                             nHorizontalCS ==
     537             :                                     HORIZONTAL_CS_EASTING_NORTHING_METRE
     538          12 :                                 ? PJ_CART2D_EASTING_NORTHING
     539             :                                 : PJ_CART2D_NORTHING_EASTING,
     540             :                             nullptr, 0.0);
     541             : 
     542          12 :                         PJ *projCRS = proj_create_projected_crs(
     543             :                             pjCtxt, pszCRSName ? pszCRSName : "unknown", crs,
     544             :                             conv, cartesianCS);
     545          12 :                         proj_destroy(crs);
     546          12 :                         crs = projCRS;
     547          12 :                         proj_destroy(conv);
     548          12 :                         proj_destroy(cartesianCS);
     549             :                     }
     550             :                     else
     551             :                     {
     552           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
     553             :                                  "Cannot establish CRS because "
     554             :                                  "projectionMethod is missing");
     555           0 :                         proj_destroy(crs);
     556           0 :                         return false;
     557             :                     }
     558             :                 }
     559           2 :                 else if (nCRSType != CRS_TYPE_GEOGRAPHIC)
     560             :                 {
     561           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     562             :                              "Cannot establish CRS because of invalid value "
     563             :                              "for typeOfHorizontalCRS: %d",
     564             :                              nCRSType);
     565           0 :                     proj_destroy(crs);
     566           0 :                     return false;
     567             :                 }
     568             : 
     569             :                 const char *pszPROJJSON =
     570          14 :                     proj_as_projjson(pjCtxt, crs, nullptr);
     571          14 :                 oSRS.SetFromUserInput(pszPROJJSON);
     572             : 
     573          14 :                 proj_destroy(crs);
     574             :             }
     575             :             else
     576             :             {
     577           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     578             :                          "Cannot establish CRS because nameOfHorizontalCRS, "
     579             :                          "typeOfHorizontalCRS, horizontalCS and/or "
     580             :                          "horizontalDatum are missing");
     581           0 :                 return false;
     582             :             }
     583             :         }
     584             :     }
     585             :     else
     586             :     {
     587             :         auto poHorizontalDatumReference =
     588          69 :             poRootGroup->GetAttribute("horizontalDatumReference");
     589             :         auto poHorizontalDatumValue =
     590          69 :             poRootGroup->GetAttribute("horizontalDatumValue");
     591          23 :         if (poHorizontalDatumReference && poHorizontalDatumValue)
     592             :         {
     593             :             const char *pszAuthName =
     594          23 :                 poHorizontalDatumReference->ReadAsString();
     595          23 :             const char *pszAuthCode = poHorizontalDatumValue->ReadAsString();
     596          23 :             if (pszAuthName && pszAuthCode)
     597             :             {
     598          46 :                 if (oSRS.SetFromUserInput(
     599          46 :                         (std::string(pszAuthName) + ':' + pszAuthCode).c_str(),
     600             :                         OGRSpatialReference::
     601          23 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
     602             :                     OGRERR_NONE)
     603             :                 {
     604           0 :                     oSRS.Clear();
     605             :                 }
     606             :             }
     607             :         }
     608             :     }
     609          85 :     return !oSRS.IsEmpty();
     610             : }
     611             : 
     612             : /************************************************************************/
     613             : /*               S100GetNumPointsLongitudinalLatitudinal()              */
     614             : /************************************************************************/
     615             : 
     616         105 : bool S100GetNumPointsLongitudinalLatitudinal(const GDALGroup *poGroup,
     617             :                                              int &nNumPointsLongitudinal,
     618             :                                              int &nNumPointsLatitudinal)
     619             : {
     620         315 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     621         315 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     622             :     auto poNumPointsLongitudinal =
     623         315 :         poGroup->GetAttribute("numPointsLongitudinal");
     624         315 :     auto poNumPointsLatitudinal = poGroup->GetAttribute("numPointsLatitudinal");
     625         105 :     if (poSpacingX &&
     626         315 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     627         210 :         poSpacingY &&
     628         210 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     629         105 :         poNumPointsLongitudinal &&
     630         105 :         GDALDataTypeIsInteger(
     631         210 :             poNumPointsLongitudinal->GetDataType().GetNumericDataType()) &&
     632         315 :         poNumPointsLatitudinal &&
     633         105 :         GDALDataTypeIsInteger(
     634         105 :             poNumPointsLatitudinal->GetDataType().GetNumericDataType()))
     635             :     {
     636         105 :         nNumPointsLongitudinal = poNumPointsLongitudinal->ReadAsInt();
     637         105 :         nNumPointsLatitudinal = poNumPointsLatitudinal->ReadAsInt();
     638             : 
     639             :         // Those are optional, but use them when available, to detect
     640             :         // potential inconsistency
     641         315 :         auto poEastBoundLongitude = poGroup->GetAttribute("eastBoundLongitude");
     642         315 :         auto poWestBoundLongitude = poGroup->GetAttribute("westBoundLongitude");
     643             :         auto poSouthBoundLongitude =
     644         315 :             poGroup->GetAttribute("southBoundLatitude");
     645         210 :         auto poNorthBoundLatitude = poGroup->GetAttribute("northBoundLatitude");
     646         109 :         if (poEastBoundLongitude &&
     647           4 :             GDALDataTypeIsFloating(
     648         113 :                 poEastBoundLongitude->GetDataType().GetNumericDataType()) &&
     649           4 :             poWestBoundLongitude &&
     650           4 :             GDALDataTypeIsFloating(
     651           8 :                 poWestBoundLongitude->GetDataType().GetNumericDataType()) &&
     652           4 :             poSouthBoundLongitude &&
     653           4 :             GDALDataTypeIsFloating(
     654           8 :                 poSouthBoundLongitude->GetDataType().GetNumericDataType()) &&
     655         113 :             poNorthBoundLatitude &&
     656           4 :             GDALDataTypeIsFloating(
     657           4 :                 poNorthBoundLatitude->GetDataType().GetNumericDataType()))
     658             :         {
     659           4 :             const double dfSpacingX = poSpacingX->ReadAsDouble();
     660           4 :             const double dfSpacingY = poSpacingY->ReadAsDouble();
     661             : 
     662           4 :             const double dfEast = poEastBoundLongitude->ReadAsDouble();
     663           4 :             const double dfWest = poWestBoundLongitude->ReadAsDouble();
     664           4 :             const double dfSouth = poSouthBoundLongitude->ReadAsDouble();
     665           4 :             const double dfNorth = poNorthBoundLatitude->ReadAsDouble();
     666           4 :             if (std::fabs((dfWest + nNumPointsLongitudinal * dfSpacingX) -
     667           4 :                           dfEast) < 5 * dfSpacingX &&
     668           4 :                 std::fabs((dfSouth + nNumPointsLatitudinal * dfSpacingY) -
     669           4 :                           dfNorth) < 5 * dfSpacingY)
     670             :             {
     671             :                 // We need up to 5 spacings for product
     672             :                 // S-111 Trial Data Set Release 1.1/111UK_20210401T000000Z_SolentAndAppr_dcf2.h5
     673             :             }
     674             :             else
     675             :             {
     676           0 :                 CPLError(
     677             :                     CE_Warning, CPLE_AppDefined,
     678             :                     "Caution: "
     679             :                     "eastBoundLongitude/westBoundLongitude/southBoundLatitude/"
     680             :                     "northBoundLatitude/gridSpacingLongitudinal/"
     681             :                     "gridSpacingLatitudinal/numPointsLongitudinal/"
     682             :                     "numPointsLatitudinal do not seem to be consistent");
     683           0 :                 CPLDebug("S100", "Computed east = %f. Actual = %f",
     684           0 :                          dfWest + nNumPointsLongitudinal * dfSpacingX, dfEast);
     685           0 :                 CPLDebug("S100", "Computed north = %f. Actual = %f",
     686           0 :                          dfSouth + nNumPointsLatitudinal * dfSpacingY, dfNorth);
     687             :             }
     688             :         }
     689             : 
     690         105 :         return true;
     691             :     }
     692           0 :     return false;
     693             : }
     694             : 
     695             : /************************************************************************/
     696             : /*                        S100GetGeoTransform()                         */
     697             : /************************************************************************/
     698             : 
     699          49 : bool S100GetGeoTransform(const GDALGroup *poGroup, GDALGeoTransform &gt,
     700             :                          bool bNorthUp)
     701             : {
     702         147 :     auto poOriginX = poGroup->GetAttribute("gridOriginLongitude");
     703         147 :     auto poOriginY = poGroup->GetAttribute("gridOriginLatitude");
     704         147 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     705         147 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     706          49 :     if (poOriginX &&
     707         147 :         poOriginX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     708          98 :         poOriginY &&
     709          98 :         poOriginY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     710          98 :         poSpacingX &&
     711          98 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     712         147 :         poSpacingY &&
     713          49 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64)
     714             :     {
     715          49 :         int nNumPointsLongitudinal = 0;
     716          49 :         int nNumPointsLatitudinal = 0;
     717          49 :         if (!S100GetNumPointsLongitudinalLatitudinal(
     718             :                 poGroup, nNumPointsLongitudinal, nNumPointsLatitudinal))
     719           0 :             return false;
     720             : 
     721          49 :         const double dfSpacingX = poSpacingX->ReadAsDouble();
     722          49 :         const double dfSpacingY = poSpacingY->ReadAsDouble();
     723             : 
     724          49 :         gt[0] = poOriginX->ReadAsDouble();
     725          98 :         gt[3] = poOriginY->ReadAsDouble() +
     726          49 :                 (bNorthUp ? dfSpacingY * (nNumPointsLatitudinal - 1) : 0);
     727          49 :         gt[1] = dfSpacingX;
     728          49 :         gt[5] = bNorthUp ? -dfSpacingY : dfSpacingY;
     729             : 
     730             :         // From pixel-center convention to pixel-corner convention
     731          49 :         gt[0] -= gt[1] / 2;
     732          49 :         gt[3] -= gt[5] / 2;
     733             : 
     734          49 :         return true;
     735             :     }
     736           0 :     return false;
     737             : }
     738             : 
     739             : /************************************************************************/
     740             : /*                        S100GetDimensions()                           */
     741             : /************************************************************************/
     742             : 
     743          30 : bool S100GetDimensions(
     744             :     const GDALGroup *poGroup,
     745             :     std::vector<std::shared_ptr<GDALDimension>> &apoDims,
     746             :     std::vector<std::shared_ptr<GDALMDArray>> &apoIndexingVars)
     747             : {
     748          90 :     auto poOriginX = poGroup->GetAttribute("gridOriginLongitude");
     749          90 :     auto poOriginY = poGroup->GetAttribute("gridOriginLatitude");
     750          90 :     auto poSpacingX = poGroup->GetAttribute("gridSpacingLongitudinal");
     751          90 :     auto poSpacingY = poGroup->GetAttribute("gridSpacingLatitudinal");
     752          30 :     if (poOriginX &&
     753          90 :         poOriginX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     754          60 :         poOriginY &&
     755          60 :         poOriginY->GetDataType().GetNumericDataType() == GDT_Float64 &&
     756          60 :         poSpacingX &&
     757          60 :         poSpacingX->GetDataType().GetNumericDataType() == GDT_Float64 &&
     758          90 :         poSpacingY &&
     759          30 :         poSpacingY->GetDataType().GetNumericDataType() == GDT_Float64)
     760             :     {
     761          30 :         int nNumPointsLongitudinal = 0;
     762          30 :         int nNumPointsLatitudinal = 0;
     763          30 :         if (!S100GetNumPointsLongitudinalLatitudinal(
     764             :                 poGroup, nNumPointsLongitudinal, nNumPointsLatitudinal))
     765           0 :             return false;
     766             : 
     767             :         {
     768             :             auto poDim = std::make_shared<GDALDimensionWeakIndexingVar>(
     769          60 :                 std::string(), "Y", GDAL_DIM_TYPE_HORIZONTAL_Y, std::string(),
     770          60 :                 nNumPointsLatitudinal);
     771             :             auto poIndexingVar = GDALMDArrayRegularlySpaced::Create(
     772          90 :                 std::string(), poDim->GetName(), poDim,
     773         120 :                 poOriginY->ReadAsDouble(), poSpacingY->ReadAsDouble(), 0);
     774          30 :             poDim->SetIndexingVariable(poIndexingVar);
     775          30 :             apoDims.emplace_back(poDim);
     776          30 :             apoIndexingVars.emplace_back(poIndexingVar);
     777             :         }
     778             : 
     779             :         {
     780             :             auto poDim = std::make_shared<GDALDimensionWeakIndexingVar>(
     781          60 :                 std::string(), "X", GDAL_DIM_TYPE_HORIZONTAL_X, std::string(),
     782          60 :                 nNumPointsLongitudinal);
     783             :             auto poIndexingVar = GDALMDArrayRegularlySpaced::Create(
     784          90 :                 std::string(), poDim->GetName(), poDim,
     785         120 :                 poOriginX->ReadAsDouble(), poSpacingX->ReadAsDouble(), 0);
     786          30 :             poDim->SetIndexingVariable(poIndexingVar);
     787          30 :             apoDims.emplace_back(poDim);
     788          30 :             apoIndexingVars.emplace_back(poIndexingVar);
     789             :         }
     790             : 
     791          30 :         return true;
     792             :     }
     793           0 :     return false;
     794             : }
     795             : 
     796             : /************************************************************************/
     797             : /*                       S100ReadVerticalDatum()                        */
     798             : /************************************************************************/
     799             : 
     800         111 : void S100ReadVerticalDatum(GDALMajorObject *poMO, const GDALGroup *poGroup)
     801             : {
     802             :     // https://iho.int/uploads/user/pubs/standards/s-100/S-100_5.2.0_Final_Clean.pdf
     803             :     // Table 10c-25 - Vertical and sounding datum, page 53
     804             :     static const struct
     805             :     {
     806             :         int nCode;
     807             :         const char *pszMeaning;
     808             :         const char *pszAbbrev;
     809             :     } asVerticalDatums[] = {
     810             :         {1, "meanLowWaterSprings", "MLWS"},
     811             :         {2, "meanLowerLowWaterSprings", nullptr},
     812             :         {3, "meanSeaLevel", "MSL"},
     813             :         {4, "lowestLowWater", nullptr},
     814             :         {5, "meanLowWater", "MLW"},
     815             :         {6, "lowestLowWaterSprings", nullptr},
     816             :         {7, "approximateMeanLowWaterSprings", nullptr},
     817             :         {8, "indianSpringLowWater", nullptr},
     818             :         {9, "lowWaterSprings", nullptr},
     819             :         {10, "approximateLowestAstronomicalTide", nullptr},
     820             :         {11, "nearlyLowestLowWater", nullptr},
     821             :         {12, "meanLowerLowWater", "MLLW"},
     822             :         {13, "lowWater", "LW"},
     823             :         {14, "approximateMeanLowWater", nullptr},
     824             :         {15, "approximateMeanLowerLowWater", nullptr},
     825             :         {16, "meanHighWater", "MHW"},
     826             :         {17, "meanHighWaterSprings", "MHWS"},
     827             :         {18, "highWater", "HW"},
     828             :         {19, "approximateMeanSeaLevel", nullptr},
     829             :         {20, "highWaterSprings", nullptr},
     830             :         {21, "meanHigherHighWater", "MHHW"},
     831             :         {22, "equinoctialSpringLowWater", nullptr},
     832             :         {23, "lowestAstronomicalTide", "LAT"},
     833             :         {24, "localDatum", nullptr},
     834             :         {25, "internationalGreatLakesDatum1985", nullptr},
     835             :         {26, "meanWaterLevel", nullptr},
     836             :         {27, "lowerLowWaterLargeTide", nullptr},
     837             :         {28, "higherHighWaterLargeTide", nullptr},
     838             :         {29, "nearlyHighestHighWater", nullptr},
     839             :         {30, "highestAstronomicalTide", "HAT"},
     840             :         {44, "balticSeaChartDatum2000", nullptr},
     841             :         {46, "internationalGreatLakesDatum2020", nullptr},
     842             :         {47, "seaFloor", nullptr},
     843             :         {48, "seaSurface", nullptr},
     844             :         {49, "hydrographicZero", nullptr},
     845             :     };
     846             : 
     847         111 :     int nVerticalDatumReference = 1;
     848             :     auto poVerticalDatumReference =
     849         333 :         poGroup->GetAttribute("verticalDatumReference");
     850         167 :     if (poVerticalDatumReference &&
     851         167 :         poVerticalDatumReference->GetDataType().GetClass() == GEDTC_NUMERIC)
     852             :     {
     853          56 :         nVerticalDatumReference = poVerticalDatumReference->ReadAsInt();
     854             :     }
     855             : 
     856         333 :     auto poVerticalDatum = poGroup->GetAttribute("verticalDatum");
     857         178 :     if (poVerticalDatum &&
     858         178 :         poVerticalDatum->GetDataType().GetClass() == GEDTC_NUMERIC)
     859             :     {
     860          67 :         if (nVerticalDatumReference == 1)
     861             :         {
     862          67 :             bool bFound = false;
     863          67 :             const auto nVal = poVerticalDatum->ReadAsInt();
     864         810 :             for (const auto &sVerticalDatum : asVerticalDatums)
     865             :             {
     866         810 :                 if (sVerticalDatum.nCode == nVal)
     867             :                 {
     868          67 :                     bFound = true;
     869          67 :                     poMO->GDALMajorObject::SetMetadataItem(
     870          67 :                         S100_VERTICAL_DATUM_MEANING, sVerticalDatum.pszMeaning);
     871          67 :                     if (sVerticalDatum.pszAbbrev)
     872          67 :                         poMO->GDALMajorObject::SetMetadataItem(
     873             :                             S100_VERTICAL_DATUM_ABBREV,
     874          67 :                             sVerticalDatum.pszAbbrev);
     875          67 :                     break;
     876             :                 }
     877             :             }
     878          67 :             if (!bFound)
     879             :             {
     880           0 :                 poMO->GDALMajorObject::SetMetadataItem("verticalDatum",
     881             :                                                        CPLSPrintf("%d", nVal));
     882             :             }
     883             :         }
     884           0 :         else if (nVerticalDatumReference == 2)
     885             :         {
     886           0 :             const auto nVal = poVerticalDatum->ReadAsInt();
     887           0 :             PJ *datum = proj_create_from_database(
     888             :                 OSRGetProjTLSContext(), "EPSG", CPLSPrintf("%d", nVal),
     889             :                 PJ_CATEGORY_DATUM, false, nullptr);
     890           0 :             poMO->GDALMajorObject::SetMetadataItem("VERTICAL_DATUM_EPSG_CODE",
     891             :                                                    CPLSPrintf("%d", nVal));
     892           0 :             if (datum)
     893             :             {
     894           0 :                 poMO->GDALMajorObject::SetMetadataItem(S100_VERTICAL_DATUM_NAME,
     895             :                                                        proj_get_name(datum));
     896           0 :                 proj_destroy(datum);
     897             :             }
     898             :         }
     899             :         else
     900             :         {
     901           0 :             CPLDebug("S100", "Unhandled verticalDatumReference = %d",
     902             :                      nVerticalDatumReference);
     903             :         }
     904             :     }
     905         111 : }
     906             : 
     907             : /************************************************************************/
     908             : /*                         S100ReadMetadata()                           */
     909             : /************************************************************************/
     910             : 
     911          55 : std::string S100ReadMetadata(GDALDataset *poDS, const std::string &osFilename,
     912             :                              const GDALGroup *poRootGroup)
     913             : {
     914          55 :     std::string osMetadataFile;
     915         747 :     for (const auto &poAttr : poRootGroup->GetAttributes())
     916             :     {
     917         692 :         const auto &osName = poAttr->GetName();
     918         692 :         if (osName == "metadata")
     919             :         {
     920          30 :             const char *pszVal = poAttr->ReadAsString();
     921          30 :             if (pszVal && pszVal[0])
     922             :             {
     923          30 :                 if (CPLHasPathTraversal(pszVal))
     924             :                 {
     925           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     926             :                              "Path traversal detected in %s", pszVal);
     927           0 :                     continue;
     928             :                 }
     929          60 :                 osMetadataFile = CPLFormFilenameSafe(
     930          60 :                     CPLGetPathSafe(osFilename.c_str()).c_str(), pszVal,
     931          30 :                     nullptr);
     932             :                 VSIStatBufL sStat;
     933          30 :                 if (VSIStatL(osMetadataFile.c_str(), &sStat) != 0)
     934             :                 {
     935             :                     // Test products from https://data.admiralty.co.uk/portal/apps/sites/#/marine-data-portal/pages/s-100
     936             :                     // advertise a metadata filename starting with "MD_", per the spec,
     937             :                     // but the actual filename does not start with "MD_"...
     938           6 :                     if (STARTS_WITH(pszVal, "MD_"))
     939             :                     {
     940          12 :                         osMetadataFile = CPLFormFilenameSafe(
     941          12 :                             CPLGetPathSafe(osFilename.c_str()).c_str(),
     942           6 :                             pszVal + strlen("MD_"), nullptr);
     943           6 :                         if (VSIStatL(osMetadataFile.c_str(), &sStat) != 0)
     944             :                         {
     945           6 :                             osMetadataFile.clear();
     946             :                         }
     947             :                     }
     948             :                     else
     949             :                     {
     950           0 :                         osMetadataFile.clear();
     951             :                     }
     952             :                 }
     953             :             }
     954             :         }
     955        1280 :         else if (osName != "horizontalCRS" &&
     956        1225 :                  osName != "horizontalDatumReference" &&
     957        1203 :                  osName != "horizontalDatumValue" &&
     958        1137 :                  osName != "productSpecification" &&
     959        1071 :                  osName != "eastBoundLongitude" &&
     960        1049 :                  osName != "northBoundLatitude" &&
     961        1027 :                  osName != "southBoundLatitude" &&
     962        1502 :                  osName != "westBoundLongitude" && osName != "extentTypeCode" &&
     963        1359 :                  osName != "verticalCS" && osName != "verticalCoordinateBase" &&
     964         774 :                  osName != "verticalDatumReference" &&
     965         971 :                  osName != "verticalDatum" && osName != "nameOfHorizontalCRS" &&
     966         846 :                  osName != "typeOfHorizontalCRS" && osName != "horizontalCS" &&
     967         522 :                  osName != "horizontalDatum" &&
     968         507 :                  osName != "nameOfHorizontalDatum" &&
     969         756 :                  osName != "primeMeridian" && osName != "spheroid" &&
     970         490 :                  osName != "projectionMethod" &&
     971         466 :                  osName != "projectionParameter1" &&
     972         442 :                  osName != "projectionParameter2" &&
     973         421 :                  osName != "projectionParameter3" &&
     974         407 :                  osName != "projectionParameter4" &&
     975         399 :                  osName != "projectionParameter5" &&
     976        1478 :                  osName != "falseNorthing" && osName != "falseEasting")
     977             :         {
     978         174 :             const char *pszVal = poAttr->ReadAsString();
     979         174 :             if (pszVal && pszVal[0])
     980         174 :                 poDS->GDALDataset::SetMetadataItem(osName.c_str(), pszVal);
     981             :         }
     982             :     }
     983          55 :     return osMetadataFile;
     984             : }

Generated by: LCOV version 1.14