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
|