LCOV - code coverage report
Current view: top level - frmts/hdf5 - s111dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 152 197 77.2 %
Date: 2024-11-21 22:18:42 Functions: 8 9 88.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_priv.h"
      21             : #include "gdal_proxy.h"
      22             : #include "gdal_rat.h"
      23             : 
      24             : #include <limits>
      25             : 
      26             : /************************************************************************/
      27             : /*                             S111Dataset                              */
      28             : /************************************************************************/
      29             : 
      30             : class S111Dataset final : public S100BaseDataset
      31             : {
      32             :   public:
      33           4 :     explicit S111Dataset(const std::string &osFilename)
      34           4 :         : S100BaseDataset(osFilename)
      35             :     {
      36           4 :     }
      37             : 
      38             :     static GDALDataset *Open(GDALOpenInfo *);
      39             : };
      40             : 
      41             : /************************************************************************/
      42             : /*                            S111RasterBand                            */
      43             : /************************************************************************/
      44             : 
      45             : class S111RasterBand final : public GDALProxyRasterBand
      46             : {
      47             :     friend class S111Dataset;
      48             :     std::unique_ptr<GDALDataset> m_poDS{};
      49             :     GDALRasterBand *m_poUnderlyingBand = nullptr;
      50             :     std::string m_osUnitType{};
      51             :     std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
      52             : 
      53             :   public:
      54           4 :     explicit S111RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
      55           8 :         : m_poDS(std::move(poDSIn)),
      56           4 :           m_poUnderlyingBand(m_poDS->GetRasterBand(1))
      57             :     {
      58           4 :         eDataType = m_poUnderlyingBand->GetRasterDataType();
      59           4 :         m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      60           4 :     }
      61             : 
      62             :     GDALRasterBand *
      63           8 :     RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
      64             :     {
      65           8 :         return m_poUnderlyingBand;
      66             :     }
      67             : 
      68           4 :     const char *GetUnitType() override
      69             :     {
      70           4 :         return m_osUnitType.c_str();
      71             :     }
      72             : 
      73           1 :     GDALRasterAttributeTable *GetDefaultRAT() override
      74             :     {
      75           1 :         return m_poRAT.get();
      76             :     }
      77             : 
      78           0 :     char **GetMetadata(const char *pszDomain) override
      79             :     {
      80             :         // Short-circuit GDALProxyRasterBand...
      81           0 :         return GDALRasterBand::GetMetadata(pszDomain);
      82             :     }
      83             : };
      84             : 
      85             : /************************************************************************/
      86             : /*                                Open()                                */
      87             : /************************************************************************/
      88             : 
      89           5 : GDALDataset *S111Dataset::Open(GDALOpenInfo *poOpenInfo)
      90             : 
      91             : {
      92             :     // Confirm that this appears to be a S111 file.
      93           5 :     if (!S111DatasetIdentify(poOpenInfo))
      94           0 :         return nullptr;
      95             : 
      96             :     HDF5_GLOBAL_LOCK();
      97             : 
      98           5 :     if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
      99             :     {
     100           1 :         return HDF5Dataset::OpenMultiDim(poOpenInfo);
     101             :     }
     102             : 
     103             :     // Confirm the requested access is supported.
     104           4 :     if (poOpenInfo->eAccess == GA_Update)
     105             :     {
     106           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     107             :                  "The S111 driver does not support update access.");
     108           0 :         return nullptr;
     109             :     }
     110             : 
     111           8 :     std::string osFilename(poOpenInfo->pszFilename);
     112           8 :     std::string osGroup;
     113           4 :     if (STARTS_WITH(poOpenInfo->pszFilename, "S111:"))
     114             :     {
     115             :         const CPLStringList aosTokens(
     116           3 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":",
     117           3 :                                CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
     118             : 
     119           3 :         if (aosTokens.size() == 2)
     120             :         {
     121           0 :             osFilename = aosTokens[1];
     122             :         }
     123           3 :         else if (aosTokens.size() == 3)
     124             :         {
     125           3 :             osFilename = aosTokens[1];
     126           3 :             osGroup = aosTokens[2];
     127             :         }
     128             :         else
     129             :         {
     130           0 :             return nullptr;
     131             :         }
     132             :     }
     133             : 
     134           8 :     auto poDS = std::make_unique<S111Dataset>(osFilename);
     135           4 :     if (!poDS->Init())
     136           0 :         return nullptr;
     137             : 
     138           4 :     const auto &poRootGroup = poDS->m_poRootGroup;
     139             : 
     140          12 :     auto poSurfaceCurrent = poRootGroup->OpenGroup("SurfaceCurrent");
     141           4 :     if (!poSurfaceCurrent)
     142             :     {
     143           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     144             :                  "Cannot find /SurfaceCurrent group");
     145           0 :         return nullptr;
     146             :     }
     147             : 
     148             :     auto poDataCodingFormat =
     149          12 :         poSurfaceCurrent->GetAttribute("dataCodingFormat");
     150           8 :     if (!poDataCodingFormat ||
     151           4 :         poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
     152             :     {
     153           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     154             :                  "Cannot find /SurfaceCurrent/dataCodingFormat attribute");
     155           0 :         return nullptr;
     156             :     }
     157           4 :     const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
     158           4 :     if (nDataCodingFormat != 2)
     159             :     {
     160           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     161             :                  "dataCodingFormat=%d is not supported by the S111 driver",
     162             :                  nDataCodingFormat);
     163           0 :         return nullptr;
     164             :     }
     165             : 
     166             :     // Read additional metadata
     167          12 :     for (const char *pszAttrName :
     168             :          {"methodCurrentsProduct", "minDatasetCurrentSpeed",
     169          16 :           "maxDatasetCurrentSpeed"})
     170             :     {
     171          36 :         auto poAttr = poSurfaceCurrent->GetAttribute(pszAttrName);
     172          12 :         if (poAttr)
     173             :         {
     174           8 :             const char *pszVal = poAttr->ReadAsString();
     175           8 :             if (pszVal)
     176             :             {
     177           8 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     178             :             }
     179             :         }
     180             :     }
     181             : 
     182          12 :     auto poSurfaceCurrent01 = poSurfaceCurrent->OpenGroup("SurfaceCurrent.01");
     183           4 :     if (!poSurfaceCurrent01)
     184             :     {
     185           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     186             :                  "Cannot find /SurfaceCurrent/SurfaceCurrent.01 group");
     187           0 :         return nullptr;
     188             :     }
     189             : 
     190             :     // Read additional metadata
     191          16 :     for (const char *pszAttrName :
     192             :          {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
     193          20 :           "numberOfTimes"})
     194             :     {
     195          48 :         auto poAttr = poSurfaceCurrent01->GetAttribute(pszAttrName);
     196          16 :         if (poAttr)
     197             :         {
     198          16 :             const char *pszVal = poAttr->ReadAsString();
     199          16 :             if (pszVal)
     200             :             {
     201          16 :                 poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
     202             :             }
     203             :         }
     204             :     }
     205             : 
     206           4 :     if (auto poStartSequence =
     207           8 :             poSurfaceCurrent01->GetAttribute("startSequence"))
     208             :     {
     209           4 :         const char *pszStartSequence = poStartSequence->ReadAsString();
     210           4 :         if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
     211             :         {
     212           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     213             :                      "startSequence (=%s) != 0,0 is not supported",
     214             :                      pszStartSequence);
     215           0 :             return nullptr;
     216             :         }
     217             :     }
     218             : 
     219           4 :     if (!S100GetNumPointsLongitudinalLatitudinal(
     220           4 :             poSurfaceCurrent01.get(), poDS->nRasterXSize, poDS->nRasterYSize))
     221             :     {
     222           0 :         return nullptr;
     223             :     }
     224             : 
     225           4 :     const bool bNorthUp = CPLTestBool(
     226           4 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
     227             : 
     228             :     // Compute geotransform
     229           4 :     poDS->m_bHasGT = S100GetGeoTransform(poSurfaceCurrent01.get(),
     230           4 :                                          poDS->m_adfGeoTransform, bNorthUp);
     231             : 
     232           4 :     if (osGroup.empty())
     233             :     {
     234           2 :         const auto aosGroupNames = poSurfaceCurrent01->GetGroupNames();
     235           1 :         int iSubDS = 1;
     236           2 :         for (const auto &osSubGroup : aosGroupNames)
     237             :         {
     238           2 :             if (auto poSubGroup = poSurfaceCurrent01->OpenGroup(osSubGroup))
     239             :             {
     240           1 :                 poDS->GDALDataset::SetMetadataItem(
     241             :                     CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
     242             :                     CPLSPrintf("S111:\"%s\":%s", osFilename.c_str(),
     243             :                                osSubGroup.c_str()),
     244             :                     "SUBDATASETS");
     245           2 :                 std::string osSubDSDesc = "Values for group ";
     246           1 :                 osSubDSDesc += osSubGroup;
     247           2 :                 const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
     248           1 :                 if (poTimePoint)
     249             :                 {
     250           1 :                     const char *pszVal = poTimePoint->ReadAsString();
     251           1 :                     if (pszVal)
     252             :                     {
     253           1 :                         osSubDSDesc = "Values at timestamp ";
     254           1 :                         osSubDSDesc += pszVal;
     255             :                     }
     256             :                 }
     257           1 :                 poDS->GDALDataset::SetMetadataItem(
     258             :                     CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
     259             :                     osSubDSDesc.c_str(), "SUBDATASETS");
     260           1 :                 ++iSubDS;
     261             :             }
     262             :         }
     263             :     }
     264             :     else
     265             :     {
     266           3 :         auto poGroup = poSurfaceCurrent01->OpenGroup(osGroup);
     267           3 :         if (!poGroup)
     268             :         {
     269           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     270             :                      "Cannot find /SurfaceCurrent/SurfaceCurrent.01/%s group",
     271             :                      osGroup.c_str());
     272           1 :             return nullptr;
     273             :         }
     274             : 
     275           4 :         auto poValuesArray = poGroup->OpenMDArray("values");
     276           2 :         if (!poValuesArray)
     277             :         {
     278           0 :             CPLError(
     279             :                 CE_Failure, CPLE_AppDefined,
     280             :                 "Cannot find /SurfaceCurrent/SurfaceCurrent.01/%s/values array",
     281             :                 osGroup.c_str());
     282           0 :             return nullptr;
     283             :         }
     284             : 
     285           2 :         if (poValuesArray->GetDimensionCount() != 2)
     286             :         {
     287           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     288             :                      "Wrong dimension count for %s",
     289           0 :                      poValuesArray->GetFullName().c_str());
     290           0 :             return nullptr;
     291             :         }
     292             : 
     293           2 :         const auto &oType = poValuesArray->GetDataType();
     294           2 :         if (oType.GetClass() != GEDTC_COMPOUND)
     295             :         {
     296           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     297           0 :                      poValuesArray->GetFullName().c_str());
     298           0 :             return nullptr;
     299             :         }
     300             : 
     301           2 :         const auto &oComponents = oType.GetComponents();
     302           4 :         if (!(oComponents.size() == 2 &&
     303           4 :               ((oComponents[0]->GetName() == "surfaceCurrentSpeed" &&
     304           2 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     305           4 :                 oComponents[1]->GetName() == "surfaceCurrentDirection" &&
     306           2 :                 oComponents[1]->GetType().GetNumericDataType() ==
     307           0 :                     GDT_Float32) ||
     308             :                // S111US_20170829.0100_W078.N44_F2_loofs_type2.h5 has direction first...
     309           0 :                (oComponents[0]->GetName() == "surfaceCurrentDirection" &&
     310           0 :                 oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
     311           0 :                 oComponents[1]->GetName() == "surfaceCurrentSpeed" &&
     312           0 :                 oComponents[1]->GetType().GetNumericDataType() ==
     313             :                     GDT_Float32))))
     314             :         {
     315           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
     316           0 :                      poValuesArray->GetFullName().c_str());
     317           0 :             return nullptr;
     318             :         }
     319             : 
     320           2 :         const auto &apoDims = poValuesArray->GetDimensions();
     321           2 :         if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
     322             :         {
     323           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     324             :                      "numPointsLatitudinal(=%d) doesn't match first dimension "
     325             :                      "size of %s (=%d)",
     326           0 :                      poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
     327           0 :                      static_cast<int>(apoDims[0]->GetSize()));
     328           0 :             return nullptr;
     329             :         }
     330           2 :         if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
     331             :         {
     332           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     333             :                      "numPointsLongitudinal(=%d) doesn't match second "
     334             :                      "dimension size of %s (=%d)",
     335           0 :                      poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
     336           0 :                      static_cast<int>(apoDims[1]->GetSize()));
     337           0 :             return nullptr;
     338             :         }
     339             : 
     340           2 :         if (bNorthUp)
     341           1 :             poValuesArray = poValuesArray->GetView("[::-1,...]");
     342             : 
     343             :         // Create surfaceCurrentSpeed band
     344             :         auto poSurfaceCurrentSpeed =
     345           6 :             poValuesArray->GetView("[\"surfaceCurrentSpeed\"]");
     346             :         auto poSurfaceCurrentSpeedDS = std::unique_ptr<GDALDataset>(
     347           4 :             poSurfaceCurrentSpeed->AsClassicDataset(1, 0));
     348             :         auto poSurfaceCurrentSpeedBand = std::make_unique<S111RasterBand>(
     349           4 :             std::move(poSurfaceCurrentSpeedDS));
     350           2 :         poSurfaceCurrentSpeedBand->SetDescription("surfaceCurrentSpeed");
     351           2 :         poSurfaceCurrentSpeedBand->m_osUnitType = "knots";
     352             : 
     353             :         // From S-111 v1.2 table 9.1 (Speed ranges) and 9.2 (Colour schemas)
     354           4 :         auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
     355           2 :         poRAT->CreateColumn("speed_band", GFT_Integer, GFU_Generic);
     356           2 :         poRAT->CreateColumn("min_speed", GFT_Real, GFU_Min);
     357           2 :         poRAT->CreateColumn("width_band", GFT_Real, GFU_Generic);
     358           2 :         poRAT->CreateColumn("color", GFT_String, GFU_Generic);
     359           2 :         poRAT->CreateColumn("red", GFT_Integer, GFU_RedMin);
     360           2 :         poRAT->CreateColumn("green", GFT_Integer, GFU_GreenMin);
     361           2 :         poRAT->CreateColumn("blue", GFT_Integer, GFU_BlueMin);
     362             : 
     363             :         const struct
     364             :         {
     365             :             int nSpeedBand;
     366             :             double dfMinSpeed;
     367             :             double dfWidthBand;
     368             :             const char *pszColor;
     369             :             int nRed;
     370             :             int nGreen;
     371             :             int nBlue;
     372           2 :         } aoRatValues[] = {
     373             :             {1, 0.0, 0.5, "purple", 118, 82, 226},
     374             :             {2, 0.5, 0.5, "dark blue", 72, 152, 211},
     375             :             {3, 1.0, 1.0, "light blue", 97, 203, 229},
     376             :             {4, 2.0, 1.0, "dark green", 109, 188, 69},
     377             :             {5, 3.0, 2.0, "light green", 180, 220, 0},
     378             :             {6, 5.0, 2.0, "yellow green", 205, 193, 0},
     379             :             {7, 7.0, 3.0, "orange", 248, 167, 24},
     380             :             {8, 10.0, 3.0, "pink", 247, 162, 157},
     381             :             {9, 13.0, 86.0, "red", 255, 30, 30},
     382             :         };
     383             : 
     384           2 :         int iRow = 0;
     385          20 :         for (const auto &oRecord : aoRatValues)
     386             :         {
     387          18 :             int iCol = 0;
     388          18 :             poRAT->SetValue(iRow, iCol++, oRecord.nSpeedBand);
     389          18 :             poRAT->SetValue(iRow, iCol++, oRecord.dfMinSpeed);
     390          18 :             poRAT->SetValue(iRow, iCol++, oRecord.dfWidthBand);
     391          18 :             poRAT->SetValue(iRow, iCol++, oRecord.pszColor);
     392          18 :             poRAT->SetValue(iRow, iCol++, oRecord.nRed);
     393          18 :             poRAT->SetValue(iRow, iCol++, oRecord.nGreen);
     394          18 :             poRAT->SetValue(iRow, iCol++, oRecord.nBlue);
     395          18 :             ++iRow;
     396             :         }
     397             : 
     398           2 :         poSurfaceCurrentSpeedBand->m_poRAT = std::move(poRAT);
     399             : 
     400           2 :         poDS->SetBand(1, poSurfaceCurrentSpeedBand.release());
     401             : 
     402             :         // Create surfaceCurrentDirection band
     403             :         auto poSurfaceCurrentDirection =
     404           6 :             poValuesArray->GetView("[\"surfaceCurrentDirection\"]");
     405             :         auto poSurfaceCurrentDirectionDS = std::unique_ptr<GDALDataset>(
     406           4 :             poSurfaceCurrentDirection->AsClassicDataset(1, 0));
     407             :         auto poSurfaceCurrentDirectionBand = std::make_unique<S111RasterBand>(
     408           4 :             std::move(poSurfaceCurrentDirectionDS));
     409           2 :         poSurfaceCurrentDirectionBand->SetDescription(
     410           2 :             "surfaceCurrentDirection");
     411           2 :         poSurfaceCurrentDirectionBand->m_osUnitType = "degree";
     412           2 :         poSurfaceCurrentDirectionBand->GDALRasterBand::SetMetadataItem(
     413             :             "ANGLE_CONVENTION", "From true north, clockwise");
     414           2 :         poDS->SetBand(2, poSurfaceCurrentDirectionBand.release());
     415             :     }
     416             : 
     417           3 :     poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
     418             : 
     419             :     // Setup/check for pam .aux.xml.
     420           3 :     poDS->SetDescription(osFilename.c_str());
     421           3 :     poDS->TryLoadXML();
     422             : 
     423             :     // Setup overviews.
     424           3 :     poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
     425             : 
     426           3 :     return poDS.release();
     427             : }
     428             : 
     429             : /************************************************************************/
     430             : /*                      S111DatasetDriverUnload()                       */
     431             : /************************************************************************/
     432             : 
     433           6 : static void S111DatasetDriverUnload(GDALDriver *)
     434             : {
     435           6 :     HDF5UnloadFileDriver();
     436           6 : }
     437             : 
     438             : /************************************************************************/
     439             : /*                         GDALRegister_S111()                          */
     440             : /************************************************************************/
     441          10 : void GDALRegister_S111()
     442             : 
     443             : {
     444          10 :     if (!GDAL_CHECK_VERSION("S111"))
     445           0 :         return;
     446             : 
     447          10 :     if (GDALGetDriverByName(S111_DRIVER_NAME) != nullptr)
     448           0 :         return;
     449             : 
     450          10 :     GDALDriver *poDriver = new GDALDriver();
     451             : 
     452          10 :     S111DriverSetCommonMetadata(poDriver);
     453          10 :     poDriver->pfnOpen = S111Dataset::Open;
     454          10 :     poDriver->pfnUnloadDriver = S111DatasetDriverUnload;
     455             : 
     456          10 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     457             : }

Generated by: LCOV version 1.14