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

Generated by: LCOV version 1.14