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
|