LCOV - code coverage report
Current view: top level - frmts/hdf5 - s111dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 248 302 82.1 %
Date: 2025-10-21 22:35:35 Functions: 10 11 90.9 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Hierarchical Data Format Release 5 (HDF5)
       4             :  * Purpose:  Read S111 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_port.h"
      14             : #include "hdf5dataset.h"
      15             : #include "hdf5drivercore.h"
      16             : #include "gh5_convenience.h"
      17             : #include "rat.h"
      18             : #include "s100.h"
      19             : 
      20             : #include "gdal_frmts.h"
      21             : #include "gdal_priv.h"
      22             : #include "gdal_proxy.h"
      23             : #include "gdal_rat.h"
      24             : 
      25             : #include <limits>
      26             : #include <map>
      27             : 
      28             : /************************************************************************/
      29             : /*                             S111Dataset                              */
      30             : /************************************************************************/
      31             : 
      32          14 : class S111Dataset final : public S100BaseDataset
      33             : {
      34             :   public:
      35           7 :     explicit S111Dataset(const std::string &osFilename)
      36           7 :         : S100BaseDataset(osFilename)
      37             :     {
      38           7 :     }
      39             : 
      40             :     ~S111Dataset() override;
      41             : 
      42             :     static GDALDataset *Open(GDALOpenInfo *);
      43             : };
      44             : 
      45             : S111Dataset::~S111Dataset() = default;
      46             : 
      47             : /************************************************************************/
      48             : /*                            S111RasterBand                            */
      49             : /************************************************************************/
      50             : 
      51             : class S111RasterBand final : public GDALProxyRasterBand
      52             : {
      53             :     friend class S111Dataset;
      54             :     std::unique_ptr<GDALDataset> m_poDS{};
      55             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      56             :     std::string m_osUnitType{};
      57             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
      58             : 
      59             :     CPL_DISALLOW_COPY_ASSIGN(S111RasterBand)
      60             : 
      61             :   public:
      62           8 :     explicit S111RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      63          16 :         : m_poDS(std::move(poDSIn)),
      64           8 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      65             :     {
      66           8 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      67           8 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      68           8 :     }
      69             : 
      70             :     GDALRasterBand *
      71             :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
      72             : 
      73           4 :     const char *GetUnitType() override
      74             :     {
      75           4 :         return m_osUnitType.c_str();
      76             :     }
      77             : 
      78           1 :     GDALRasterAttributeTable *GetDefaultRAT() override
      79             :     {
      80           1 :         return m_poRAT.get();
      81             :     }
      82             : 
      83           0 :     char **GetMetadata(const char *pszDomain) override
      84             :     {
      85             :         // Short-circuit GDALProxyRasterBand...
      86           0 :         return GDALRasterBand::GetMetadata(pszDomain);
      87             :     }
      88             : };
      89             : 
      90             : GDALRasterBand *
      91          12 : S111RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
      92             : {
      93          12 :     return m_poUnderlyingBand;
      94             : }
      95             : 
      96             : /************************************************************************/
      97             : /*                                Open()                                */
      98             : /************************************************************************/
      99             : 
     100           8 : GDALDataset *S111Dataset::Open(GDALOpenInfo *poOpenInfo)
     101             : 
     102             : {
     103             :     // Confirm that this appears to be a S111 file.
     104           8 :     if (!S111DatasetIdentify(poOpenInfo))
     105           0 :         return nullptr;
     106             : 
     107             :     HDF5_GLOBAL_LOCK();
     108             : 
     109           8 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
     110             :     {
     111           1 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     112             :     }
     113             : 
     114             :     // Confirm the requested access is supported.
     115           7 :     if (poOpenInfo->eAccess == GA_Update)
     116             :     {
     117           0 :         ReportUpdateNotSupportedByDriver("S111");
     118           0 :         return nullptr;
     119             :     }
     120             : 
     121          14 :     std::string osFilename(poOpenInfo->pszFilename);
     122          14 :     std::string osFeatureInstance = "SurfaceCurrent.01";
     123          14 :     std::string osGroup;
     124           7 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S111:"))
     125             :     {
     126             :         const CPLStringList aosTokens(
     127           5 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     128           5 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     129             : 
     130           5 :         if (aosTokens.size() == 2)
     131             :         {
     132           0 :             osFilename = aosTokens[1];
     133             :         }
     134           5 :         else if (aosTokens.size() == 3)
     135             :         {
     136           3 :             osFilename = aosTokens[1];
     137           3 :             osGroup = aosTokens[2];
     138             :         }
     139           2 :         else if (aosTokens.size() == 4)
     140             :         {
     141           2 :             osFilename = aosTokens[1];
     142           2 :             osFeatureInstance = aosTokens[2];
     143           2 :             osGroup = aosTokens[3];
     144             :         }
     145             :         else
     146             :         {
     147           0 :             return nullptr;
     148             :         }
     149             :     }
     150             : 
     151          14 :     auto poDS = std::make_unique<S111Dataset>(osFilename);
     152           7 :     if (!poDS->Init())
     153           0 :         return nullptr;
     154             : 
     155           7 :     const auto &poRootGroup = poDS->m_poRootGroup;
     156             : 
     157          21 :     auto poSurfaceCurrent = poRootGroup->OpenGroup("SurfaceCurrent");
     158           7 :     if (!poSurfaceCurrent)
     159             :     {
     160           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     161             :                  "Cannot find /SurfaceCurrent group");
     162           0 :         return nullptr;
     163             :     }
     164             : 
     165             :     auto poDataCodingFormat =
     166          21 :         poSurfaceCurrent->GetAttribute("dataCodingFormat");
     167          14 :     if (!poDataCodingFormat ||
     168           7 :         poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
     169             :     {
     170           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     171             :                  "Cannot find /SurfaceCurrent/dataCodingFormat attribute");
     172           0 :         return nullptr;
     173             :     }
     174           7 :     const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
     175           7 :     if (nDataCodingFormat != 2)
     176             :     {
     177           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     178             :                  "dataCodingFormat=%d is not supported by the S111 driver",
     179             :                  nDataCodingFormat);
     180           0 :         return nullptr;
     181             :     }
     182             : 
     183             :     // Read additional metadata
     184          21 :     for (const char *pszAttrName :
     185             :          {"methodCurrentsProduct", "minDatasetCurrentSpeed",
     186          28 :           "maxDatasetCurrentSpeed"})
     187             :     {
     188          63 :         auto poAttr = poSurfaceCurrent->GetAttribute(pszAttrName);
     189          21 :         if (poAttr)
     190             :         {
     191          14 :             const char *pszVal = poAttr->ReadAsString();
     192          14 :             if (pszVal)
     193             :             {
     194          14 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     195             :             }
     196             :         }
     197             :     }
     198             : 
     199           7 :     int nNumInstances = 1;
     200           7 :     if (osGroup.empty())
     201             :     {
     202           6 :         auto poNumInstances = poSurfaceCurrent->GetAttribute("numInstances");
     203           3 :         if (poNumInstances &&
     204           3 :             poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
     205             :         {
     206           1 :             nNumInstances = poNumInstances->ReadAsInt();
     207             :         }
     208             :     }
     209           7 :     if (nNumInstances != 1)
     210             :     {
     211           2 :         CPLStringList aosSubDSList;
     212           1 :         int iSubDS = 0;
     213           2 :         for (const std::string &featureInstanceName :
     214           5 :              poSurfaceCurrent->GetGroupNames())
     215             :         {
     216             :             auto poFeatureInstance =
     217           4 :                 poSurfaceCurrent->OpenGroup(featureInstanceName);
     218           2 :             if (poFeatureInstance)
     219             :             {
     220           4 :                 GDALMajorObject mo;
     221             :                 // Read first vertical datum from root group and let the
     222             :                 // coverage override it.
     223           2 :                 S100ReadVerticalDatum(&mo, poRootGroup.get());
     224           2 :                 S100ReadVerticalDatum(&mo, poFeatureInstance.get());
     225             : 
     226           4 :                 const auto aosGroupNames = poFeatureInstance->GetGroupNames();
     227           4 :                 for (const auto &osSubGroup : aosGroupNames)
     228             :                 {
     229           2 :                     if (auto poSubGroup =
     230           4 :                             poFeatureInstance->OpenGroup(osSubGroup))
     231             :                     {
     232           2 :                         ++iSubDS;
     233             :                         aosSubDSList.SetNameValue(
     234             :                             CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     235             :                             CPLSPrintf("S111:\"%s\":%s:%s", osFilename.c_str(),
     236             :                                        featureInstanceName.c_str(),
     237           2 :                                        osSubGroup.c_str()));
     238             : 
     239           4 :                         std::string verticalDatum;
     240             :                         const char *pszValue =
     241           2 :                             mo.GetMetadataItem(S100_VERTICAL_DATUM_MEANING);
     242           2 :                         if (pszValue)
     243             :                         {
     244           2 :                             verticalDatum = ", vertical datum ";
     245           2 :                             verticalDatum += pszValue;
     246             :                             pszValue =
     247           2 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
     248           2 :                             if (pszValue)
     249             :                             {
     250           2 :                                 verticalDatum += " (";
     251           2 :                                 verticalDatum += pszValue;
     252           2 :                                 verticalDatum += ')';
     253             :                             }
     254             :                         }
     255             :                         else
     256             :                         {
     257             :                             pszValue =
     258           0 :                                 mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
     259           0 :                             if (pszValue)
     260             :                             {
     261           0 :                                 verticalDatum = ", vertical datum ";
     262           0 :                                 verticalDatum += pszValue;
     263             :                             }
     264             :                         }
     265             : 
     266           4 :                         std::string osSubDSDesc;
     267             :                         const auto poTimePoint =
     268           6 :                             poSubGroup->GetAttribute("timePoint");
     269           2 :                         if (poTimePoint)
     270             :                         {
     271           2 :                             const char *pszVal = poTimePoint->ReadAsString();
     272           2 :                             if (pszVal)
     273             :                             {
     274           2 :                                 osSubDSDesc = "Values for feature instance ";
     275           2 :                                 osSubDSDesc += featureInstanceName;
     276           2 :                                 osSubDSDesc += verticalDatum;
     277           2 :                                 osSubDSDesc += " at timestamp ";
     278           2 :                                 osSubDSDesc += pszVal;
     279             :                             }
     280             :                         }
     281           2 :                         if (osSubDSDesc.empty())
     282             :                         {
     283           0 :                             osSubDSDesc = "Values for feature instance ";
     284           0 :                             osSubDSDesc += featureInstanceName;
     285           0 :                             osSubDSDesc += verticalDatum;
     286           0 :                             osSubDSDesc += " and group ";
     287           0 :                             osSubDSDesc += osSubGroup;
     288             :                         }
     289             : 
     290             :                         aosSubDSList.SetNameValue(
     291             :                             CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     292           2 :                             osSubDSDesc.c_str());
     293             :                     }
     294             :                 }
     295             :             }
     296             :         }
     297             : 
     298           1 :         poDS->GDALDataset::SetMetadata(aosSubDSList.List(), "SUBDATASETS");
     299             : 
     300             :         // Setup/check for pam .aux.xml.
     301           1 :         poDS->SetDescription(osFilename.c_str());
     302           1 :         poDS->TryLoadXML();
     303             : 
     304             :         // Setup overviews.
     305           1 :         poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     306             : 
     307           1 :         return poDS.release();
     308             :     }
     309             : 
     310          12 :     auto poFeatureInstance = poSurfaceCurrent->OpenGroup(osFeatureInstance);
     311           6 :     if (!poFeatureInstance)
     312             :     {
     313           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     314             :                  "Cannot find /SurfaceCurrent/%s group",
     315             :                  osFeatureInstance.c_str());
     316           0 :         return nullptr;
     317             :     }
     318             : 
     319             :     // Read additional metadata
     320          24 :     for (const char *pszAttrName :
     321             :          {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
     322          30 :           "numberOfTimes"})
     323             :     {
     324          72 :         auto poAttr = poFeatureInstance->GetAttribute(pszAttrName);
     325          24 :         if (poAttr)
     326             :         {
     327          24 :             const char *pszVal = poAttr->ReadAsString();
     328          24 :             if (pszVal)
     329             :             {
     330          24 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     331             :             }
     332             :         }
     333             :     }
     334             : 
     335           6 :     if (auto poDataDynamicity =
     336          18 :             poFeatureInstance->GetAttribute("dataDynamicity"))
     337             :     {
     338           2 :         if (poDataDynamicity->GetDataType().GetClass() == GEDTC_NUMERIC)
     339             :         {
     340           2 :             const int nVal = poDataDynamicity->ReadAsInt();
     341             :             const std::map<int, const char *> oDataDynamicityMap = {
     342             :                 {1, "Observation"},
     343             :                 {2, "Astronomical prediction"},
     344             :                 {3, "Analysis or hybrid method"},
     345             :                 {4, "Hydrodynamic model hindcast"},
     346             :                 {5, "Hydrodynamic model forecast"},
     347             :                 {6, "Observed minus predicted"},
     348             :                 {7, "Observed minus analysis"},
     349             :                 {8, "Observed minus hindcast"},
     350             :                 {9, "Observed minus forecast"},
     351             :                 {10, "Forecast minus predicted"},
     352           4 :             };
     353           2 :             const auto oIter = oDataDynamicityMap.find(nVal);
     354           2 :             if (oIter != oDataDynamicityMap.end())
     355           2 :                 poDS->GDALDataset::SetMetadataItem("DATA_DYNAMICITY_MEANING",
     356           2 :                                                    oIter->second);
     357             :         }
     358             :     }
     359             : 
     360             :     // Read optional uncertainty array
     361          13 :     if (auto poUncertainty = poFeatureInstance->OpenMDArray("uncertainty"))
     362             :     {
     363           1 :         auto &apoDims = poUncertainty->GetDimensions();
     364           2 :         if (poUncertainty->GetDataType().GetClass() == GEDTC_COMPOUND &&
     365           2 :             apoDims.size() == 1 && apoDims[0]->GetSize() == 2)
     366             :         {
     367             :             const auto &oComponents =
     368           1 :                 poUncertainty->GetDataType().GetComponents();
     369           2 :             if (oComponents.size() == 2 &&
     370           2 :                 oComponents[0]->GetName() == "name" &&
     371           2 :                 oComponents[0]->GetType().GetClass() == GEDTC_STRING &&
     372           3 :                 oComponents[1]->GetName() == "value" &&
     373           1 :                 oComponents[1]->GetType().GetNumericDataType() == GDT_Float64)
     374             :             {
     375           3 :                 auto poName = poUncertainty->GetView("[\"name\"]");
     376           3 :                 auto poValue = poUncertainty->GetView("[\"value\"]");
     377           1 :                 if (poName && poValue)
     378             :                 {
     379           1 :                     char *apszStr[2] = {nullptr, nullptr};
     380           1 :                     double adfVals[2] = {0, 0};
     381           1 :                     GUInt64 arrayStartIdx[] = {0};
     382           1 :                     size_t count[] = {2};
     383           1 :                     GInt64 arrayStep[] = {1};
     384           1 :                     GPtrDiff_t bufferStride[] = {1};
     385           2 :                     if (poName->Read(arrayStartIdx, count, arrayStep,
     386           1 :                                      bufferStride, oComponents[0]->GetType(),
     387           2 :                                      apszStr) &&
     388           2 :                         poValue->Read(arrayStartIdx, count, arrayStep,
     389           1 :                                       bufferStride, oComponents[1]->GetType(),
     390             :                                       adfVals))
     391             :                     {
     392           3 :                         for (int i = 0; i < 2; ++i)
     393             :                         {
     394           4 :                             std::string osName = apszStr[i];
     395           2 :                             if (osName[0] >= 'a' && osName[0] <= 'z')
     396           2 :                                 osName[0] = osName[0] - 'a' + 'A';
     397           2 :                             osName = "uncertainty" + osName;
     398           2 :                             poDS->GDALDataset::SetMetadataItem(
     399             :                                 osName.c_str(), CPLSPrintf("%f", adfVals[i]));
     400             :                         }
     401             :                     }
     402           1 :                     VSIFree(apszStr[0]);
     403           1 :                     VSIFree(apszStr[1]);
     404             :                 }
     405             :             }
     406             :         }
     407             :     }
     408             : 
     409          12 :     if (auto poStartSequence = poFeatureInstance->GetAttribute("startSequence"))
     410             :     {
     411           6 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     412           6 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     413             :         {
     414           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     415             :                      "startSequence (=%s) != 0,0 is not supported",
     416             :                      pszStartSequence);
     417           0 :             return nullptr;
     418             :         }
     419             :     }
     420             : 
     421           6 :     if (!S100GetNumPointsLongitudinalLatitudinal(
     422           6 :             poFeatureInstance.get(), poDS->nRasterXSize, poDS->nRasterYSize))
     423             :     {
     424           0 :         return nullptr;
     425             :     }
     426             : 
     427             :     // Potentially override vertical datum
     428           6 :     S100ReadVerticalDatum(poDS.get(), poFeatureInstance.get());
     429             : 
     430           6 :     const bool bNorthUp = CPLTestBool(
     431           6 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     432             : 
     433             :     // Compute geotransform
     434          12 :     poDS->m_bHasGT =
     435           6 :         S100GetGeoTransform(poFeatureInstance.get(), poDS->m_gt, bNorthUp);
     436             : 
     437           6 :     if (osGroup.empty())
     438             :     {
     439           2 :         const auto aosGroupNames = poFeatureInstance->GetGroupNames();
     440           1 :         int iSubDS = 1;
     441           2 :         for (const auto &osSubGroup : aosGroupNames)
     442             :         {
     443           2 :             if (auto poSubGroup = poFeatureInstance->OpenGroup(osSubGroup))
     444             :             {
     445           1 :                 poDS->GDALDataset::SetMetadataItem(
     446             :                     CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     447             :                     CPLSPrintf("S111:\"%s\":%s", osFilename.c_str(),
     448             :                                osSubGroup.c_str()),
     449             :                     "SUBDATASETS");
     450           2 :                 std::string osSubDSDesc = "Values for group ";
     451           1 :                 osSubDSDesc += osSubGroup;
     452           2 :                 const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
     453           1 :                 if (poTimePoint)
     454             :                 {
     455           1 :                     const char *pszVal = poTimePoint->ReadAsString();
     456           1 :                     if (pszVal)
     457             :                     {
     458           1 :                         osSubDSDesc = "Values at timestamp ";
     459           1 :                         osSubDSDesc += pszVal;
     460             :                     }
     461             :                 }
     462           1 :                 poDS->GDALDataset::SetMetadataItem(
     463             :                     CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     464             :                     osSubDSDesc.c_str(), "SUBDATASETS");
     465           1 :                 ++iSubDS;
     466             :             }
     467             :         }
     468             :     }
     469             :     else
     470             :     {
     471           5 :         auto poGroup = poFeatureInstance->OpenGroup(osGroup);
     472           5 :         if (!poGroup)
     473             :         {
     474           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     475             :                      "Cannot find /SurfaceCurrent/%s/%s group",
     476             :                      osFeatureInstance.c_str(), osGroup.c_str());
     477           1 :             return nullptr;
     478             :         }
     479             : 
     480           8 :         auto poValuesArray = poGroup->OpenMDArray("values");
     481           4 :         if (!poValuesArray)
     482             :         {
     483           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     484             :                      "Cannot find /SurfaceCurrent/%s/%s/values array",
     485             :                      osFeatureInstance.c_str(), osGroup.c_str());
     486           0 :             return nullptr;
     487             :         }
     488             : 
     489           4 :         if (poValuesArray->GetDimensionCount() != 2)
     490             :         {
     491           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     492             :                      "Wrong dimension count for %s",
     493           0 :                      poValuesArray->GetFullName().c_str());
     494           0 :             return nullptr;
     495             :         }
     496             : 
     497           4 :         const auto &oType = poValuesArray->GetDataType();
     498           4 :         if (oType.GetClass() != GEDTC_COMPOUND)
     499             :         {
     500           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     501           0 :                      poValuesArray->GetFullName().c_str());
     502           0 :             return nullptr;
     503             :         }
     504             : 
     505           4 :         const auto &oComponents = oType.GetComponents();
     506           8 :         if (!(oComponents.size() == 2 &&
     507           8 :               ((oComponents[0]->GetName() == "surfaceCurrentSpeed" &&
     508           4 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     509           8 :                 oComponents[1]->GetName() == "surfaceCurrentDirection" &&
     510           4 :                 oComponents[1]->GetType().GetNumericDataType() ==
     511           0 :                     GDT_Float32) ||
     512             :                // S111US_20170829.0100_W078.N44_F2_loofs_type2.h5 has direction first...
     513           0 :                (oComponents[0]->GetName() == "surfaceCurrentDirection" &&
     514           0 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     515           0 :                 oComponents[1]->GetName() == "surfaceCurrentSpeed" &&
     516           0 :                 oComponents[1]->GetType().GetNumericDataType() ==
     517             :                     GDT_Float32))))
     518             :         {
     519           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     520           0 :                      poValuesArray->GetFullName().c_str());
     521           0 :             return nullptr;
     522             :         }
     523             : 
     524           4 :         const auto &apoDims = poValuesArray->GetDimensions();
     525           4 :         if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
     526             :         {
     527           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     528             :                      "numPointsLatitudinal(=%d) doesn't match first dimension "
     529             :                      "size of %s (=%d)",
     530           0 :                      poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
     531           0 :                      static_cast<int>(apoDims[0]->GetSize()));
     532           0 :             return nullptr;
     533             :         }
     534           4 :         if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
     535             :         {
     536           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     537             :                      "numPointsLongitudinal(=%d) doesn't match second "
     538             :                      "dimension size of %s (=%d)",
     539           0 :                      poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
     540           0 :                      static_cast<int>(apoDims[1]->GetSize()));
     541           0 :             return nullptr;
     542             :         }
     543             : 
     544           4 :         if (bNorthUp)
     545           3 :             poValuesArray = poValuesArray->GetView("[::-1,...]");
     546             : 
     547             :         // Create surfaceCurrentSpeed band
     548             :         auto poSurfaceCurrentSpeed =
     549          12 :             poValuesArray->GetView("[\"surfaceCurrentSpeed\"]");
     550             :         auto poSurfaceCurrentSpeedDS = std::unique_ptr<GDALDataset>(
     551           8 :             poSurfaceCurrentSpeed->AsClassicDataset(1, 0));
     552             :         auto poSurfaceCurrentSpeedBand = std::make_unique<S111RasterBand>(
     553           8 :             std::move(poSurfaceCurrentSpeedDS));
     554           4 :         poSurfaceCurrentSpeedBand->SetDescription("surfaceCurrentSpeed");
     555           4 :         poSurfaceCurrentSpeedBand->m_osUnitType = "knots";
     556             : 
     557             :         // From S-111 v1.2 table 9.1 (Speed ranges) and 9.2 (Colour schemas)
     558           8 :         auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
     559           4 :         poRAT->CreateColumn("speed_band", GFT_Integer, GFU_Generic);
     560           4 :         poRAT->CreateColumn("min_speed", GFT_Real, GFU_Min);
     561           4 :         poRAT->CreateColumn("width_band", GFT_Real, GFU_Generic);
     562           4 :         poRAT->CreateColumn("color", GFT_String, GFU_Generic);
     563           4 :         poRAT->CreateColumn("red", GFT_Integer, GFU_RedMin);
     564           4 :         poRAT->CreateColumn("green", GFT_Integer, GFU_GreenMin);
     565           4 :         poRAT->CreateColumn("blue", GFT_Integer, GFU_BlueMin);
     566             : 
     567             :         const struct
     568             :         {
     569             :             int nSpeedBand;
     570             :             double dfMinSpeed;
     571             :             double dfWidthBand;
     572             :             const char *pszColor;
     573             :             int nRed;
     574             :             int nGreen;
     575             :             int nBlue;
     576           4 :         } aoRatValues[] = {
     577             :             {1, 0.0, 0.5, "purple", 118, 82, 226},
     578             :             {2, 0.5, 0.5, "dark blue", 72, 152, 211},
     579             :             {3, 1.0, 1.0, "light blue", 97, 203, 229},
     580             :             {4, 2.0, 1.0, "dark green", 109, 188, 69},
     581             :             {5, 3.0, 2.0, "light green", 180, 220, 0},
     582             :             {6, 5.0, 2.0, "yellow green", 205, 193, 0},
     583             :             {7, 7.0, 3.0, "orange", 248, 167, 24},
     584             :             {8, 10.0, 3.0, "pink", 247, 162, 157},
     585             :             {9, 13.0, 86.0, "red", 255, 30, 30},
     586             :         };
     587             : 
     588           4 :         int iRow = 0;
     589          40 :         for (const auto &oRecord : aoRatValues)
     590             :         {
     591          36 :             int iCol = 0;
     592          36 :             poRAT->SetValue(iRow, iCol++, oRecord.nSpeedBand);
     593          36 :             poRAT->SetValue(iRow, iCol++, oRecord.dfMinSpeed);
     594          36 :             poRAT->SetValue(iRow, iCol++, oRecord.dfWidthBand);
     595          36 :             poRAT->SetValue(iRow, iCol++, oRecord.pszColor);
     596          36 :             poRAT->SetValue(iRow, iCol++, oRecord.nRed);
     597          36 :             poRAT->SetValue(iRow, iCol++, oRecord.nGreen);
     598          36 :             poRAT->SetValue(iRow, iCol++, oRecord.nBlue);
     599          36 :             ++iRow;
     600             :         }
     601             : 
     602           4 :         poSurfaceCurrentSpeedBand->m_poRAT = std::move(poRAT);
     603             : 
     604           4 :         poDS->SetBand(1, poSurfaceCurrentSpeedBand.release());
     605             : 
     606             :         // Create surfaceCurrentDirection band
     607             :         auto poSurfaceCurrentDirection =
     608          12 :             poValuesArray->GetView("[\"surfaceCurrentDirection\"]");
     609             :         auto poSurfaceCurrentDirectionDS = std::unique_ptr<GDALDataset>(
     610           8 :             poSurfaceCurrentDirection->AsClassicDataset(1, 0));
     611             :         auto poSurfaceCurrentDirectionBand = std::make_unique<S111RasterBand>(
     612           8 :             std::move(poSurfaceCurrentDirectionDS));
     613           4 :         poSurfaceCurrentDirectionBand->SetDescription(
     614           4 :             "surfaceCurrentDirection");
     615           4 :         poSurfaceCurrentDirectionBand->m_osUnitType = "degree";
     616           4 :         poSurfaceCurrentDirectionBand->GDALRasterBand::SetMetadataItem(
     617             :             "ANGLE_CONVENTION", "From true north, clockwise");
     618           4 :         poDS->SetBand(2, poSurfaceCurrentDirectionBand.release());
     619             :     }
     620             : 
     621           5 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     622             : 
     623             :     // Setup/check for pam .aux.xml.
     624           5 :     poDS->SetDescription(osFilename.c_str());
     625           5 :     poDS->TryLoadXML();
     626             : 
     627             :     // Setup overviews.
     628           5 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     629             : 
     630           5 :     return poDS.release();
     631             : }
     632             : 
     633             : /************************************************************************/
     634             : /*                      S111DatasetDriverUnload()                       */
     635             : /************************************************************************/
     636             : 
     637         323 : static void S111DatasetDriverUnload(GDALDriver *)
     638             : {
     639         323 :     HDF5UnloadFileDriver();
     640         323 : }
     641             : 
     642             : /************************************************************************/
     643             : /*                         GDALRegister_S111()                          */
     644             : /************************************************************************/
     645         355 : void GDALRegister_S111()
     646             : 
     647             : {
     648         355 :     if (!GDAL_CHECK_VERSION("S111"))
     649           0 :         return;
     650             : 
     651         355 :     if (GDALGetDriverByName(S111_DRIVER_NAME) != nullptr)
     652           0 :         return;
     653             : 
     654         355 :     GDALDriver *poDriver = new GDALDriver();
     655             : 
     656         355 :     S111DriverSetCommonMetadata(poDriver);
     657         355 :     poDriver->pfnOpen = S111Dataset::Open;
     658         355 :     poDriver->pfnUnloadDriver = S111DatasetDriverUnload;
     659             : 
     660         355 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     661             : }

Generated by: LCOV version 1.14