LCOV - code coverage report
Current view: top level - apps - gdalalg_dataset_check.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 232 252 92.1 %
Date: 2026-01-11 15:50:51 Functions: 6 6 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  gdal "dataset check" subcommand
       5             :  * Author:   Even Rouault <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2026, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : //! @cond Doxygen_Suppress
      14             : 
      15             : #include "gdalalg_dataset_check.h"
      16             : 
      17             : #include "cpl_progress.h"
      18             : #include "cpl_string.h"
      19             : #include "gdal_dataset.h"
      20             : #include "gdal_multidim.h"
      21             : #include "gdal_rasterband.h"
      22             : #include "ogrsf_frmts.h"
      23             : #include "ogr_recordbatch.h"
      24             : 
      25             : #include <algorithm>
      26             : #include <limits>
      27             : 
      28             : #ifndef _
      29             : #define _(x) (x)
      30             : #endif
      31             : 
      32             : /************************************************************************/
      33             : /*                       GDALDatasetCheckAlgorithm()                    */
      34             : /************************************************************************/
      35             : 
      36          37 : GDALDatasetCheckAlgorithm::GDALDatasetCheckAlgorithm()
      37          37 :     : GDALAlgorithm(NAME, DESCRIPTION, HELP_URL)
      38             : {
      39          37 :     AddProgressArg();
      40             : 
      41             :     AddInputDatasetArg(&m_input, GDAL_OF_RASTER | GDAL_OF_VECTOR |
      42          37 :                                      GDAL_OF_MULTIDIM_RASTER);
      43             : 
      44          74 :     AddArg("return-code", 0, _("Return code"), &m_retCode)
      45          37 :         .SetHiddenForCLI()
      46          37 :         .SetIsInput(false)
      47          37 :         .SetIsOutput(true);
      48          37 : }
      49             : 
      50             : /************************************************************************/
      51             : /*                   GDALDatasetCheckAlgorithm::RunImpl()               */
      52             : /************************************************************************/
      53             : 
      54          31 : bool GDALDatasetCheckAlgorithm::RunImpl(GDALProgressFunc pfnProgress,
      55             :                                         void *pProgressData)
      56             : {
      57          31 :     auto poDS = m_input.GetDatasetRef();
      58          31 :     CPLAssert(poDS);
      59             : 
      60             :     const CPLStringList aosSubdatasets(
      61          31 :         CSLDuplicate(poDS->GetMetadata("SUBDATASETS")));
      62          31 :     const int nSubdatasets = aosSubdatasets.size() / 2;
      63             : 
      64          31 :     bool bRet = true;
      65          31 :     if (nSubdatasets)
      66             :     {
      67           3 :         int i = 0;
      68          12 :         for (auto [pszKey, pszValue] : cpl::IterateNameValue(aosSubdatasets))
      69             :         {
      70           9 :             if (cpl::ends_with(std::string_view(pszKey), "_NAME"))
      71             :             {
      72             :                 auto poSubDS =
      73           5 :                     std::unique_ptr<GDALDataset>(GDALDataset::Open(pszValue));
      74           5 :                 if (poSubDS)
      75             :                 {
      76             :                     std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
      77             :                         pScaled(GDALCreateScaledProgress(
      78           5 :                                     static_cast<double>(i) / nSubdatasets,
      79           5 :                                     static_cast<double>(i + 1) / nSubdatasets,
      80             :                                     pfnProgress, pProgressData),
      81           5 :                                 GDALDestroyScaledProgress);
      82           5 :                     ++i;
      83           5 :                     bRet = CheckDataset(poSubDS.get(), false,
      84             :                                         GDALScaledProgress, pScaled.get());
      85           5 :                     if (!bRet)
      86           1 :                         break;
      87             :                 }
      88             :                 else
      89             :                 {
      90           0 :                     m_retCode = 1;
      91             :                 }
      92             :             }
      93             :         }
      94             :     }
      95             :     else
      96             :     {
      97          28 :         bRet = CheckDataset(poDS, /* bRasterOnly=*/false, pfnProgress,
      98             :                             pProgressData);
      99             :     }
     100             : 
     101          62 :     return bRet;
     102             : }
     103             : 
     104             : /************************************************************************/
     105             : /*                          GetGroupPixelCount()                         */
     106             : /************************************************************************/
     107             : 
     108          13 : static GIntBig GetGroupPixelCount(const GDALGroup *poGroup)
     109             : {
     110          13 :     GIntBig nPixelCount = 0;
     111          22 :     for (const std::string &osArrayName : poGroup->GetMDArrayNames())
     112             :     {
     113          18 :         auto poArray = poGroup->OpenMDArray(osArrayName);
     114           9 :         if (poArray)
     115             :         {
     116           9 :             GIntBig nPixels = 1;
     117          22 :             for (auto &poDim : poArray->GetDimensions())
     118          13 :                 nPixels *= poDim->GetSize();
     119           9 :             nPixelCount += nPixels;
     120             :         }
     121             :     }
     122          21 :     for (const std::string &osGroupName : poGroup->GetGroupNames())
     123             :     {
     124          16 :         auto poSubGroup = poGroup->OpenGroup(osGroupName);
     125           8 :         if (poSubGroup)
     126           8 :             nPixelCount += GetGroupPixelCount(poSubGroup.get());
     127             :     }
     128          13 :     return nPixelCount;
     129             : }
     130             : 
     131             : /************************************************************************/
     132             : /*                              ProgressStruct                          */
     133             : /************************************************************************/
     134             : 
     135             : namespace
     136             : {
     137             : struct ProgressStruct
     138             : {
     139             :     GIntBig nTotalContent = 0;
     140             :     GDALProgressFunc pfnProgress = nullptr;
     141             :     void *pProgressData = nullptr;
     142             : 
     143             :     // In-out variable
     144             :     GIntBig nProgress = 0;
     145             : 
     146             :     // Work variable
     147             :     std::vector<GByte> *pabyData = nullptr;
     148             : 
     149             :     // Output variables
     150             :     bool bError = false;
     151             :     bool bInterrupted = false;
     152             : };
     153             : }  // namespace
     154             : 
     155             : /************************************************************************/
     156             : /*                           MDArrayProcessFunc()                       */
     157             : /************************************************************************/
     158             : 
     159             : /** Read a chunk of a multidimensional array */
     160           9 : static bool MDArrayProcessFunc(GDALAbstractMDArray *array,
     161             :                                const GUInt64 *startIdx,
     162             :                                const size_t *chunkCount,
     163             :                                GUInt64 /* iCurChunk */,
     164             :                                GUInt64 /* nChunkCount */, void *pUserData)
     165             : {
     166           9 :     ProgressStruct *psProgress = static_cast<ProgressStruct *>(pUserData);
     167           9 :     size_t nPixels = 1;
     168           9 :     const auto nDimCount = array->GetDimensionCount();
     169          22 :     for (size_t i = 0; i < nDimCount; ++i)
     170          13 :         nPixels *= chunkCount[i];
     171           9 :     auto &dt = array->GetDataType();
     172           9 :     const size_t nDTSize = dt.GetSize();
     173           9 :     const size_t nReqSize = nPixels * nDTSize;
     174           9 :     if (psProgress->pabyData->size() < nReqSize)
     175             :     {
     176             :         try
     177             :         {
     178           9 :             psProgress->pabyData->resize(nReqSize);
     179             :         }
     180           0 :         catch (const std::exception &)
     181             :         {
     182           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     183             :                      "Out of memory while allocating memory chunk");
     184           0 :             psProgress->bError = true;
     185           0 :             return false;
     186             :         }
     187             :     }
     188           9 :     if (!array->Read(startIdx, chunkCount, /* arrayStep = */ nullptr,
     189             :                      /* bufferStride = */ nullptr, dt,
     190           9 :                      psProgress->pabyData->data()))
     191             :     {
     192           1 :         psProgress->bError = true;
     193           1 :         return false;
     194             :     }
     195           8 :     if (dt.NeedsFreeDynamicMemory())
     196             :     {
     197           2 :         for (size_t i = 0; i < nPixels; ++i)
     198             :         {
     199           1 :             dt.FreeDynamicMemory(psProgress->pabyData->data() + i * nDTSize);
     200             :         }
     201             :     }
     202           8 :     psProgress->nProgress += nPixels;
     203          12 :     if (psProgress->pfnProgress &&
     204           4 :         !psProgress->pfnProgress(
     205           4 :             static_cast<double>(psProgress->nProgress) /
     206           4 :                 static_cast<double>(psProgress->nTotalContent),
     207             :             "", psProgress->pProgressData))
     208             :     {
     209           1 :         psProgress->bInterrupted = true;
     210           1 :         return false;
     211             :     }
     212           7 :     return true;
     213             : }
     214             : 
     215             : /************************************************************************/
     216             : /*                GDALDatasetCheckAlgorithm::CheckGroup()               */
     217             : /************************************************************************/
     218             : 
     219          11 : bool GDALDatasetCheckAlgorithm::CheckGroup(GDALGroup *poGroup,
     220             :                                            GIntBig &nProgress,
     221             :                                            GIntBig nTotalContent,
     222             :                                            GDALProgressFunc pfnProgress,
     223             :                                            void *pProgressData)
     224             : {
     225          11 :     CPLDebug("GDALDatasetCheckAlgorithm", "Checking group %s",
     226          11 :              poGroup->GetFullName().c_str());
     227          18 :     for (const std::string &osArrayName : poGroup->GetMDArrayNames())
     228             :     {
     229           9 :         auto poArray = poGroup->OpenMDArray(osArrayName);
     230           9 :         if (poArray)
     231             :         {
     232           9 :             CPLDebug("GDALDatasetCheckAlgorithm", "Checking array %s",
     233           9 :                      poArray->GetFullName().c_str());
     234           9 :             std::vector<GUInt64> anStartIdx(poArray->GetDimensionCount());
     235           9 :             std::vector<GUInt64> anCount;
     236          22 :             for (auto &poDim : poArray->GetDimensions())
     237          13 :                 anCount.push_back(poDim->GetSize());
     238           9 :             constexpr size_t BUFFER_SIZE = 10 * 1024 * 1024;
     239             : 
     240           9 :             std::vector<GByte> abyData;
     241             : 
     242           9 :             ProgressStruct sProgress;
     243           9 :             sProgress.pabyData = &abyData;
     244           9 :             sProgress.nProgress = nProgress;
     245           9 :             sProgress.nTotalContent = nTotalContent;
     246           9 :             sProgress.pfnProgress = pfnProgress;
     247           9 :             sProgress.pProgressData = pProgressData;
     248           9 :             if (!poArray->ProcessPerChunk(
     249           9 :                     anStartIdx.data(), anCount.data(),
     250          18 :                     poArray->GetProcessingChunkSize(BUFFER_SIZE).data(),
     251          34 :                     MDArrayProcessFunc, &sProgress) ||
     252           7 :                 sProgress.bError)
     253             :             {
     254           2 :                 if (sProgress.bInterrupted)
     255             :                 {
     256           1 :                     ReportError(CE_Failure, CPLE_UserInterrupt,
     257             :                                 "Interrupted by user");
     258             :                 }
     259           2 :                 m_retCode = 1;
     260           2 :                 return false;
     261             :             }
     262           7 :             nProgress = sProgress.nProgress;
     263             :         }
     264             :     }
     265          13 :     for (const std::string &osGroupName : poGroup->GetGroupNames())
     266             :     {
     267           6 :         auto poSubGroup = poGroup->OpenGroup(osGroupName);
     268          12 :         if (poSubGroup &&
     269           6 :             !CheckGroup(poSubGroup.get(), nProgress, nTotalContent, pfnProgress,
     270           6 :                         pProgressData))
     271             :         {
     272           2 :             return false;
     273             :         }
     274             :     }
     275           7 :     return true;
     276             : }
     277             : 
     278             : /************************************************************************/
     279             : /*             GDALDatasetCheckAlgorithm::CheckDataset()                */
     280             : /************************************************************************/
     281             : 
     282          33 : bool GDALDatasetCheckAlgorithm::CheckDataset(GDALDataset *poDS,
     283             :                                              bool bRasterOnly,
     284             :                                              GDALProgressFunc pfnProgress,
     285             :                                              void *pProgressData)
     286             : {
     287          33 :     const int nBands = poDS->GetRasterCount();
     288          66 :     auto poRootGroup = poDS->GetRootGroup();
     289             :     const GIntBig nTotalPixelsMD =
     290          33 :         poRootGroup ? GetGroupPixelCount(poRootGroup.get()) : 0;
     291             :     const GIntBig nTotalPixelsRegularRaster =
     292          33 :         nTotalPixelsMD ? 0
     293          28 :                        : static_cast<GIntBig>(nBands) * poDS->GetRasterXSize() *
     294          28 :                              poDS->GetRasterYSize();
     295          33 :     GIntBig nTotalFeatures = 0;
     296          33 :     bool bFastArrow = true;
     297          33 :     if (!bRasterOnly)
     298             :     {
     299          59 :         for (auto *poLayer : poDS->GetLayers())
     300             :         {
     301          26 :             bFastArrow =
     302          26 :                 bFastArrow && poLayer->TestCapability(OLCFastGetArrowStream);
     303          26 :             const auto nFeatures = poLayer->GetFeatureCount(false);
     304          26 :             if (nFeatures >= 0)
     305           8 :                 nTotalFeatures += nFeatures;
     306             :         }
     307             :     }
     308             : 
     309             :     // Totally arbitrary "equivalence" between a vector feature and a pixel
     310             :     // in terms of computation / I/O effort.
     311          33 :     constexpr int RATIO_FEATURE_TO_PIXEL = 100;
     312          33 :     const GIntBig nTotalContent = nTotalPixelsMD + nTotalPixelsRegularRaster +
     313          33 :                                   nTotalFeatures * RATIO_FEATURE_TO_PIXEL;
     314             : 
     315          33 :     if (!bRasterOnly)
     316             :     {
     317          33 :         const double dfRatioFeatures =
     318             :             (nTotalFeatures == nTotalContent)
     319          33 :                 ? 1.0
     320          27 :                 : static_cast<double>(nTotalFeatures * RATIO_FEATURE_TO_PIXEL) /
     321             :                       nTotalContent;
     322             : 
     323          33 :         if (bFastArrow)
     324             :         {
     325          27 :             GIntBig nCountFeatures = 0;
     326          31 :             for (auto *poLayer : poDS->GetLayers())
     327             :             {
     328             :                 struct ArrowArrayStream stream;
     329           8 :                 if (!poLayer->GetArrowStream(&stream))
     330             :                 {
     331           0 :                     ReportError(CE_Failure, CPLE_AppDefined,
     332             :                                 "GetArrowStream() failed");
     333           0 :                     m_retCode = 1;
     334           4 :                     return false;
     335             :                 }
     336             :                 while (true)
     337             :                 {
     338             :                     struct ArrowArray array;
     339          12 :                     int ret = stream.get_next(&stream, &array);
     340          12 :                     if (ret != 0 || CPLGetLastErrorType() == CE_Failure)
     341             :                     {
     342           2 :                         if (array.release)
     343           1 :                             array.release(&array);
     344           2 :                         ReportError(CE_Failure, CPLE_AppDefined,
     345             :                                     "ArrowArrayStream::get_next() failed");
     346           2 :                         m_retCode = 1;
     347           2 :                         stream.release(&stream);
     348           4 :                         return false;
     349             :                     }
     350          10 :                     if (array.release == nullptr)
     351           4 :                         break;
     352           6 :                     nCountFeatures += array.length;
     353           6 :                     array.release(&array);
     354          12 :                     const double dfPct = static_cast<double>(nCountFeatures) /
     355          12 :                                          (static_cast<double>(nTotalFeatures) +
     356           6 :                                           std::numeric_limits<double>::min()) *
     357           6 :                                          dfRatioFeatures;
     358           6 :                     if (pfnProgress && !pfnProgress(dfPct, "", pProgressData))
     359             :                     {
     360           2 :                         ReportError(CE_Failure, CPLE_UserInterrupt,
     361             :                                     "Interrupted by user");
     362           2 :                         m_retCode = 1;
     363           2 :                         stream.release(&stream);
     364           2 :                         return false;
     365             :                     }
     366           4 :                 }
     367           4 :                 stream.release(&stream);
     368             :             }
     369             :         }
     370             :         else
     371             :         {
     372             :             std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)> pScaled(
     373             :                 GDALCreateScaledProgress(0, dfRatioFeatures, pfnProgress,
     374             :                                          pProgressData),
     375           6 :                 GDALDestroyScaledProgress);
     376           6 :             GIntBig nCurFeatures = 0;
     377             :             while (true)
     378             :             {
     379             :                 const bool bGotFeature =
     380          54 :                     std::unique_ptr<OGRFeature>(poDS->GetNextFeature(
     381          54 :                         nullptr, nullptr, GDALScaledProgress, pScaled.get())) !=
     382             :                     nullptr;
     383          27 :                 if (CPLGetLastErrorType() == CE_Failure)
     384             :                 {
     385           2 :                     m_retCode = 1;
     386           2 :                     return false;
     387             :                 }
     388          25 :                 if (!bGotFeature)
     389           4 :                     break;
     390          21 :                 ++nCurFeatures;
     391          21 :                 if (pfnProgress && nTotalFeatures > 0 &&
     392           0 :                     !pfnProgress(
     393          21 :                         std::min(1.0, static_cast<double>(nCurFeatures) /
     394           0 :                                           static_cast<double>(nTotalFeatures)) *
     395             :                             dfRatioFeatures,
     396             :                         "", pProgressData))
     397             :                 {
     398           0 :                     ReportError(CE_Failure, CPLE_UserInterrupt,
     399             :                                 "Interrupted by user");
     400           0 :                     m_retCode = 1;
     401           0 :                     return false;
     402             :                 }
     403          21 :             }
     404           4 :             if (pfnProgress && nTotalContent == 0)
     405           2 :                 pfnProgress(1.0, "", pProgressData);
     406             :         }
     407             :     }
     408             : 
     409          27 :     GIntBig nProgress = nTotalFeatures * RATIO_FEATURE_TO_PIXEL;
     410          27 :     if (poRootGroup && nTotalPixelsMD)
     411             :     {
     412           5 :         return CheckGroup(poRootGroup.get(), nProgress, nTotalContent,
     413           5 :                           pfnProgress, pProgressData);
     414             :     }
     415          22 :     else if (nBands)
     416             :     {
     417          16 :         std::vector<GByte> abyBuffer;
     418          16 :         const auto eDT = poDS->GetRasterBand(1)->GetRasterDataType();
     419          16 :         const auto nDTSize = GDALGetDataTypeSizeBytes(eDT);
     420          16 :         constexpr size_t BUFFER_SIZE = 10 * 1024 * 1024;
     421             :         const char *pszInterleaving =
     422          16 :             poDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
     423          16 :         if (pszInterleaving && EQUAL(pszInterleaving, "PIXEL"))
     424             :         {
     425           4 :             for (const auto &oWindow :
     426          14 :                  poDS->GetRasterBand(1)->IterateWindows(BUFFER_SIZE))
     427             :             {
     428           6 :                 const size_t nPixels = static_cast<size_t>(oWindow.nXSize) *
     429           6 :                                        oWindow.nYSize * nBands;
     430           6 :                 const size_t nReqSize = nPixels * nDTSize;
     431           6 :                 if (abyBuffer.size() < nReqSize)
     432             :                 {
     433             :                     try
     434             :                     {
     435           6 :                         abyBuffer.resize(nReqSize);
     436             :                     }
     437           0 :                     catch (const std::exception &)
     438             :                     {
     439           0 :                         ReportError(
     440             :                             CE_Failure, CPLE_OutOfMemory,
     441             :                             "Out of memory while allocating memory chunk");
     442           0 :                         m_retCode = 1;
     443           0 :                         return false;
     444             :                     }
     445             :                 }
     446          18 :                 if (poDS->RasterIO(GF_Read, oWindow.nXOff, oWindow.nYOff,
     447           6 :                                    oWindow.nXSize, oWindow.nYSize,
     448           6 :                                    abyBuffer.data(), oWindow.nXSize,
     449           6 :                                    oWindow.nYSize, eDT, nBands, nullptr, 0, 0,
     450          11 :                                    0, nullptr) != CE_None ||
     451           5 :                     CPLGetLastErrorType() == CE_Failure)
     452             :                 {
     453           1 :                     m_retCode = 1;
     454           1 :                     return false;
     455             :                 }
     456           5 :                 nProgress += nPixels;
     457           8 :                 if (pfnProgress &&
     458           3 :                     !pfnProgress(static_cast<double>(nProgress) /
     459           3 :                                      static_cast<double>(nTotalContent),
     460             :                                  "", pProgressData))
     461             :                 {
     462           1 :                     ReportError(CE_Failure, CPLE_UserInterrupt,
     463             :                                 "Interrupted by user");
     464           1 :                     m_retCode = 1;
     465           1 :                     return false;
     466             :                 }
     467           4 :             }
     468             :         }
     469             :         else
     470             :         {
     471          23 :             for (int iBand = 1; iBand <= nBands; ++iBand)
     472             :             {
     473          17 :                 auto poBand = poDS->GetRasterBand(iBand);
     474          30 :                 for (const auto &oWindow : poBand->IterateWindows(BUFFER_SIZE))
     475             :                 {
     476          17 :                     const size_t nPixels =
     477          17 :                         static_cast<size_t>(oWindow.nXSize) * oWindow.nYSize;
     478          17 :                     const size_t nReqSize = nPixels * nDTSize;
     479          17 :                     if (abyBuffer.size() < nReqSize)
     480             :                     {
     481             :                         try
     482             :                         {
     483          10 :                             abyBuffer.resize(nReqSize);
     484             :                         }
     485           0 :                         catch (const std::exception &)
     486             :                         {
     487           0 :                             ReportError(
     488             :                                 CE_Failure, CPLE_OutOfMemory,
     489             :                                 "Out of memory while allocating memory chunk");
     490           0 :                             m_retCode = 1;
     491           0 :                             return false;
     492             :                         }
     493             :                     }
     494          51 :                     if (poBand->RasterIO(GF_Read, oWindow.nXOff, oWindow.nYOff,
     495          17 :                                          oWindow.nXSize, oWindow.nYSize,
     496          17 :                                          abyBuffer.data(), oWindow.nXSize,
     497          17 :                                          oWindow.nYSize, eDT, 0, 0,
     498          33 :                                          nullptr) != CE_None ||
     499          16 :                         CPLGetLastErrorType() == CE_Failure)
     500             :                     {
     501           2 :                         m_retCode = 1;
     502           2 :                         return false;
     503             :                     }
     504          15 :                     nProgress +=
     505          15 :                         static_cast<GIntBig>(oWindow.nXSize) * oWindow.nYSize;
     506          25 :                     if (pfnProgress &&
     507          10 :                         !pfnProgress(static_cast<double>(nProgress) /
     508          10 :                                          static_cast<double>(nTotalContent),
     509             :                                      "", pProgressData))
     510             :                     {
     511           2 :                         ReportError(CE_Failure, CPLE_UserInterrupt,
     512             :                                     "Interrupted by user");
     513           2 :                         m_retCode = 1;
     514           2 :                         return false;
     515             :                     }
     516             :                 }
     517             :             }
     518             :         }
     519             :     }
     520          16 :     return true;
     521             : }
     522             : 
     523             : //! @endcond

Generated by: LCOV version 1.14