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

Generated by: LCOV version 1.14