Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Utilities
4 : * Purpose: Command line application to list info about a multidimensional
5 : *raster Author: Even Rouault,<even.rouault at spatialys.com>
6 : *
7 : * ****************************************************************************
8 : * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "gdal_utils.h"
15 : #include "gdal_utils_priv.h"
16 :
17 : #include "cpl_enumerate.h"
18 : #include "cpl_json.h"
19 : #include "cpl_json_streaming_writer.h"
20 : #include "gdal_priv.h"
21 : #include "gdal_rat.h"
22 : #include "gdalargumentparser.h"
23 :
24 : #include <algorithm>
25 : #include <limits>
26 : #include <set>
27 :
28 : static void DumpArray(const std::shared_ptr<GDALGroup> &rootGroup,
29 : const std::shared_ptr<GDALMDArray> &array,
30 : CPLJSonStreamingWriter &serializer,
31 : const GDALMultiDimInfoOptions *psOptions,
32 : std::set<std::string> &alreadyDumpedDimensions,
33 : std::set<std::string> &alreadyDumpedArrays,
34 : bool bOutputObjType, bool bOutputName,
35 : bool bOutputOverviews);
36 :
37 : /************************************************************************/
38 : /* GDALMultiDimInfoOptions */
39 : /************************************************************************/
40 :
41 : struct GDALMultiDimInfoOptions
42 : {
43 : bool bStdoutOutput = false;
44 : bool bSummary = false;
45 : bool bDetailed = false;
46 : bool bPretty = true;
47 : size_t nLimitValuesByDim = 0;
48 : CPLStringList aosArrayOptions{};
49 : std::string osArrayName{};
50 : bool bStats = false;
51 : std::string osFormat = "json";
52 : };
53 :
54 : /************************************************************************/
55 : /* HasUniqueNames() */
56 : /************************************************************************/
57 :
58 73 : static bool HasUniqueNames(const std::vector<std::string> &oNames)
59 : {
60 146 : std::set<std::string> oSetNames;
61 230 : for (const auto &subgroupName : oNames)
62 : {
63 157 : if (oSetNames.find(subgroupName) != oSetNames.end())
64 : {
65 0 : return false;
66 : }
67 157 : oSetNames.insert(subgroupName);
68 : }
69 73 : return true;
70 : }
71 :
72 : /************************************************************************/
73 : /* DumpDataType() */
74 : /************************************************************************/
75 :
76 166 : static void DumpDataType(const GDALExtendedDataType &dt,
77 : CPLJSonStreamingWriter &serializer)
78 : {
79 166 : switch (dt.GetClass())
80 : {
81 38 : case GEDTC_STRING:
82 38 : serializer.Add("String");
83 38 : break;
84 :
85 119 : case GEDTC_NUMERIC:
86 : {
87 119 : auto poRAT = dt.GetRAT();
88 119 : if (poRAT)
89 : {
90 2 : auto objContext(serializer.MakeObjectContext());
91 1 : serializer.AddObjKey("name");
92 1 : serializer.Add(dt.GetName());
93 1 : serializer.AddObjKey("type");
94 1 : serializer.Add(GDALGetDataTypeName(dt.GetNumericDataType()));
95 1 : serializer.AddObjKey("attribute_table");
96 2 : auto arrayContext(serializer.MakeArrayContext());
97 1 : const int nRows = poRAT->GetRowCount();
98 1 : const int nCols = poRAT->GetColumnCount();
99 4 : for (int iRow = 0; iRow < nRows; ++iRow)
100 : {
101 6 : auto obj2Context(serializer.MakeObjectContext());
102 9 : for (int iCol = 0; iCol < nCols; ++iCol)
103 : {
104 6 : serializer.AddObjKey(poRAT->GetNameOfCol(iCol));
105 6 : switch (poRAT->GetTypeOfCol(iCol))
106 : {
107 3 : case GFT_Integer:
108 3 : serializer.Add(
109 3 : poRAT->GetValueAsInt(iRow, iCol));
110 3 : break;
111 0 : case GFT_Real:
112 0 : serializer.Add(
113 0 : poRAT->GetValueAsDouble(iRow, iCol));
114 0 : break;
115 3 : case GFT_String:
116 3 : serializer.Add(
117 3 : poRAT->GetValueAsString(iRow, iCol));
118 3 : break;
119 0 : case GFT_Boolean:
120 0 : serializer.Add(
121 0 : poRAT->GetValueAsBoolean(iRow, iCol));
122 0 : break;
123 0 : case GFT_DateTime:
124 : {
125 : const auto sDateTime =
126 0 : poRAT->GetValueAsDateTime(iRow, iCol);
127 0 : serializer.Add(
128 0 : GDALRasterAttributeTable::DateTimeToString(
129 : sDateTime));
130 0 : break;
131 : }
132 0 : case GFT_WKBGeometry:
133 : {
134 0 : size_t nWKBSize = 0;
135 : const GByte *pabyWKB =
136 0 : poRAT->GetValueAsWKBGeometry(iRow, iCol,
137 0 : nWKBSize);
138 : std::string osWKT =
139 : GDALRasterAttributeTable::WKBGeometryToWKT(
140 0 : pabyWKB, nWKBSize);
141 0 : if (osWKT.empty())
142 0 : serializer.AddNull();
143 : else
144 0 : serializer.Add(osWKT);
145 0 : break;
146 : }
147 : }
148 : }
149 : }
150 : }
151 : else
152 : {
153 118 : serializer.Add(GDALGetDataTypeName(dt.GetNumericDataType()));
154 : }
155 119 : break;
156 : }
157 :
158 9 : case GEDTC_COMPOUND:
159 : {
160 18 : auto compoundContext(serializer.MakeObjectContext());
161 9 : serializer.AddObjKey("name");
162 9 : serializer.Add(dt.GetName());
163 9 : serializer.AddObjKey("size");
164 9 : serializer.Add(static_cast<unsigned>(dt.GetSize()));
165 9 : serializer.AddObjKey("components");
166 9 : const auto &components = dt.GetComponents();
167 18 : auto componentsContext(serializer.MakeArrayContext());
168 41 : for (const auto &comp : components)
169 : {
170 64 : auto compContext(serializer.MakeObjectContext());
171 32 : serializer.AddObjKey("name");
172 32 : serializer.Add(comp->GetName());
173 32 : serializer.AddObjKey("offset");
174 32 : serializer.Add(static_cast<unsigned>(comp->GetOffset()));
175 32 : serializer.AddObjKey("type");
176 32 : DumpDataType(comp->GetType(), serializer);
177 : }
178 9 : break;
179 : }
180 : }
181 166 : }
182 :
183 : /************************************************************************/
184 : /* DumpValue() */
185 : /************************************************************************/
186 :
187 : template <typename T>
188 1014 : static void DumpValue(CPLJSonStreamingWriter &serializer, const void *bytes)
189 : {
190 : T tmp;
191 1014 : memcpy(&tmp, bytes, sizeof(T));
192 1014 : serializer.Add(tmp);
193 1014 : }
194 :
195 : /************************************************************************/
196 : /* DumpComplexValue() */
197 : /************************************************************************/
198 :
199 : template <typename T>
200 0 : static void DumpComplexValue(CPLJSonStreamingWriter &serializer,
201 : const GByte *bytes)
202 : {
203 0 : auto objectContext(serializer.MakeObjectContext());
204 0 : serializer.AddObjKey("real");
205 0 : DumpValue<T>(serializer, bytes);
206 0 : serializer.AddObjKey("imag");
207 0 : DumpValue<T>(serializer, bytes + sizeof(T));
208 0 : }
209 :
210 : /************************************************************************/
211 : /* DumpValue() */
212 : /************************************************************************/
213 :
214 1014 : static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *bytes,
215 : const GDALDataType &eDT)
216 : {
217 1014 : switch (eDT)
218 : {
219 848 : case GDT_UInt8:
220 848 : DumpValue<GByte>(serializer, bytes);
221 848 : break;
222 3 : case GDT_Int8:
223 3 : DumpValue<GInt8>(serializer, bytes);
224 3 : break;
225 9 : case GDT_Int16:
226 9 : DumpValue<GInt16>(serializer, bytes);
227 9 : break;
228 39 : case GDT_UInt16:
229 39 : DumpValue<GUInt16>(serializer, bytes);
230 39 : break;
231 13 : case GDT_Int32:
232 13 : DumpValue<GInt32>(serializer, bytes);
233 13 : break;
234 2 : case GDT_UInt32:
235 2 : DumpValue<GUInt32>(serializer, bytes);
236 2 : break;
237 7 : case GDT_Int64:
238 7 : DumpValue<std::int64_t>(serializer, bytes);
239 7 : break;
240 7 : case GDT_UInt64:
241 7 : DumpValue<std::uint64_t>(serializer, bytes);
242 7 : break;
243 0 : case GDT_Float16:
244 0 : DumpValue<GFloat16>(serializer, bytes);
245 0 : break;
246 20 : case GDT_Float32:
247 20 : DumpValue<float>(serializer, bytes);
248 20 : break;
249 66 : case GDT_Float64:
250 66 : DumpValue<double>(serializer, bytes);
251 66 : break;
252 0 : case GDT_CInt16:
253 0 : DumpComplexValue<GInt16>(serializer, bytes);
254 0 : break;
255 0 : case GDT_CInt32:
256 0 : DumpComplexValue<GInt32>(serializer, bytes);
257 0 : break;
258 0 : case GDT_CFloat16:
259 0 : DumpComplexValue<GFloat16>(serializer, bytes);
260 0 : break;
261 0 : case GDT_CFloat32:
262 0 : DumpComplexValue<float>(serializer, bytes);
263 0 : break;
264 0 : case GDT_CFloat64:
265 0 : DumpComplexValue<double>(serializer, bytes);
266 0 : break;
267 0 : case GDT_Unknown:
268 : case GDT_TypeCount:
269 0 : CPLAssert(false);
270 : break;
271 : }
272 1014 : }
273 :
274 : static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *values,
275 : const GDALExtendedDataType &dt);
276 :
277 : /************************************************************************/
278 : /* DumpCompound() */
279 : /************************************************************************/
280 :
281 23 : static void DumpCompound(CPLJSonStreamingWriter &serializer,
282 : const GByte *values, const GDALExtendedDataType &dt)
283 : {
284 23 : CPLAssert(dt.GetClass() == GEDTC_COMPOUND);
285 23 : const auto &components = dt.GetComponents();
286 46 : auto objectContext(serializer.MakeObjectContext());
287 91 : for (const auto &comp : components)
288 : {
289 68 : serializer.AddObjKey(comp->GetName());
290 68 : DumpValue(serializer, values + comp->GetOffset(), comp->GetType());
291 : }
292 23 : }
293 :
294 : /************************************************************************/
295 : /* DumpValue() */
296 : /************************************************************************/
297 :
298 980 : static void DumpValue(CPLJSonStreamingWriter &serializer, const GByte *values,
299 : const GDALExtendedDataType &dt)
300 : {
301 980 : switch (dt.GetClass())
302 : {
303 950 : case GEDTC_NUMERIC:
304 950 : DumpValue(serializer, values, dt.GetNumericDataType());
305 950 : break;
306 23 : case GEDTC_COMPOUND:
307 23 : DumpCompound(serializer, values, dt);
308 23 : break;
309 7 : case GEDTC_STRING:
310 : {
311 : const char *pszStr;
312 : // cppcheck-suppress pointerSize
313 7 : memcpy(&pszStr, values, sizeof(const char *));
314 7 : if (pszStr)
315 7 : serializer.Add(pszStr);
316 : else
317 0 : serializer.AddNull();
318 7 : break;
319 : }
320 : }
321 980 : }
322 :
323 : /************************************************************************/
324 : /* SerializeJSON() */
325 : /************************************************************************/
326 :
327 45 : static void SerializeJSON(const CPLJSONObject &obj,
328 : CPLJSonStreamingWriter &serializer)
329 : {
330 45 : switch (obj.GetType())
331 : {
332 0 : case CPLJSONObject::Type::Unknown:
333 : {
334 0 : CPLAssert(false);
335 : break;
336 : }
337 :
338 0 : case CPLJSONObject::Type::Null:
339 : {
340 0 : serializer.AddNull();
341 0 : break;
342 : }
343 :
344 12 : case CPLJSONObject::Type::Object:
345 : {
346 24 : auto objectContext(serializer.MakeObjectContext());
347 32 : for (const auto &subobj : obj.GetChildren())
348 : {
349 20 : serializer.AddObjKey(subobj.GetName());
350 20 : SerializeJSON(subobj, serializer);
351 : }
352 12 : break;
353 : }
354 :
355 8 : case CPLJSONObject::Type::Array:
356 : {
357 16 : auto arrayContext(serializer.MakeArrayContext());
358 16 : const CPLJSONArray array = obj.ToArray();
359 24 : for (const auto &subobj : array)
360 : {
361 16 : SerializeJSON(subobj, serializer);
362 : }
363 8 : break;
364 : }
365 :
366 2 : case CPLJSONObject::Type::Boolean:
367 : {
368 2 : serializer.Add(obj.ToBool());
369 2 : break;
370 : }
371 :
372 16 : case CPLJSONObject::Type::String:
373 : {
374 16 : serializer.Add(obj.ToString());
375 16 : break;
376 : }
377 :
378 2 : case CPLJSONObject::Type::Integer:
379 : {
380 2 : serializer.Add(obj.ToInteger());
381 2 : break;
382 : }
383 :
384 0 : case CPLJSONObject::Type::Long:
385 : {
386 0 : serializer.Add(static_cast<int64_t>(obj.ToLong()));
387 0 : break;
388 : }
389 :
390 5 : case CPLJSONObject::Type::Double:
391 : {
392 5 : serializer.Add(obj.ToDouble());
393 5 : break;
394 : }
395 : }
396 45 : }
397 :
398 : /************************************************************************/
399 : /* DumpAttrValue() */
400 : /************************************************************************/
401 :
402 151 : static void DumpAttrValue(const std::shared_ptr<GDALAttribute> &attr,
403 : CPLJSonStreamingWriter &serializer)
404 : {
405 151 : const auto &dt = attr->GetDataType();
406 151 : const size_t nEltCount(static_cast<size_t>(attr->GetTotalElementsCount()));
407 151 : switch (dt.GetClass())
408 : {
409 106 : case GEDTC_STRING:
410 : {
411 106 : if (nEltCount == 1)
412 : {
413 104 : const char *pszStr = attr->ReadAsString();
414 104 : if (pszStr)
415 : {
416 103 : if (dt.GetSubType() == GEDTST_JSON)
417 : {
418 22 : CPLJSONDocument oDoc;
419 11 : if (oDoc.LoadMemory(std::string(pszStr)))
420 : {
421 9 : SerializeJSON(oDoc.GetRoot(), serializer);
422 : }
423 : else
424 : {
425 2 : serializer.Add(pszStr);
426 : }
427 : }
428 : else
429 : {
430 92 : serializer.Add(pszStr);
431 : }
432 : }
433 : else
434 : {
435 1 : serializer.AddNull();
436 : }
437 : }
438 : else
439 : {
440 4 : CPLStringList aosValues(attr->ReadAsStringArray());
441 : {
442 : auto arrayContextValues(
443 4 : serializer.MakeArrayContext(nEltCount < 10));
444 6 : for (int i = 0; i < aosValues.size(); ++i)
445 : {
446 4 : serializer.Add(aosValues[i]);
447 : }
448 : }
449 : }
450 106 : break;
451 : }
452 :
453 45 : case GEDTC_NUMERIC:
454 : {
455 45 : auto eDT = dt.GetNumericDataType();
456 90 : const auto rawValues(attr->ReadAsRaw());
457 45 : const GByte *bytePtr = rawValues.data();
458 45 : if (bytePtr)
459 : {
460 45 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
461 45 : if (nEltCount == 1)
462 : {
463 26 : serializer.SetNewline(false);
464 26 : DumpValue(serializer, rawValues.data(), eDT);
465 26 : serializer.SetNewline(true);
466 : }
467 : else
468 : {
469 : auto arrayContextValues(
470 38 : serializer.MakeArrayContext(nEltCount < 10));
471 57 : for (size_t i = 0; i < nEltCount; i++)
472 : {
473 38 : DumpValue(serializer, bytePtr, eDT);
474 38 : bytePtr += nDTSize;
475 : }
476 : }
477 : }
478 : else
479 : {
480 0 : serializer.AddNull();
481 : }
482 45 : break;
483 : }
484 :
485 0 : case GEDTC_COMPOUND:
486 : {
487 0 : auto rawValues(attr->ReadAsRaw());
488 0 : const GByte *bytePtr = rawValues.data();
489 0 : if (bytePtr)
490 : {
491 0 : if (nEltCount == 1)
492 : {
493 0 : serializer.SetNewline(false);
494 0 : DumpCompound(serializer, bytePtr, dt);
495 0 : serializer.SetNewline(true);
496 : }
497 : else
498 : {
499 0 : auto arrayContextValues(serializer.MakeArrayContext());
500 0 : for (size_t i = 0; i < nEltCount; i++)
501 : {
502 0 : DumpCompound(serializer, bytePtr, dt);
503 0 : bytePtr += dt.GetSize();
504 : }
505 : }
506 : }
507 : else
508 : {
509 0 : serializer.AddNull();
510 : }
511 0 : break;
512 : }
513 : }
514 151 : }
515 :
516 : /************************************************************************/
517 : /* DumpAttr() */
518 : /************************************************************************/
519 :
520 111 : static void DumpAttr(std::shared_ptr<GDALAttribute> attr,
521 : CPLJSonStreamingWriter &serializer,
522 : const GDALMultiDimInfoOptions *psOptions,
523 : bool bOutputObjType, bool bOutputName)
524 : {
525 111 : if (!bOutputObjType && !bOutputName && !psOptions->bDetailed)
526 : {
527 99 : DumpAttrValue(attr, serializer);
528 99 : return;
529 : }
530 :
531 12 : const auto &dt = attr->GetDataType();
532 24 : auto objectContext(serializer.MakeObjectContext());
533 12 : if (bOutputObjType)
534 : {
535 0 : serializer.AddObjKey("type");
536 0 : serializer.Add("attribute");
537 : }
538 12 : if (bOutputName)
539 : {
540 0 : serializer.AddObjKey("name");
541 0 : serializer.Add(attr->GetName());
542 : }
543 :
544 12 : if (psOptions->bDetailed)
545 : {
546 12 : serializer.AddObjKey("datatype");
547 12 : DumpDataType(dt, serializer);
548 :
549 12 : switch (dt.GetSubType())
550 : {
551 12 : case GEDTST_NONE:
552 12 : break;
553 0 : case GEDTST_JSON:
554 : {
555 0 : serializer.AddObjKey("subtype");
556 0 : serializer.Add("JSON");
557 0 : break;
558 : }
559 : }
560 :
561 12 : serializer.AddObjKey("value");
562 : }
563 :
564 12 : DumpAttrValue(attr, serializer);
565 : }
566 :
567 : /************************************************************************/
568 : /* DumpAttrs() */
569 : /************************************************************************/
570 :
571 41 : static void DumpAttrs(const std::vector<std::shared_ptr<GDALAttribute>> &attrs,
572 : CPLJSonStreamingWriter &serializer,
573 : const GDALMultiDimInfoOptions *psOptions)
574 : {
575 82 : std::vector<std::string> attributeNames;
576 152 : for (const auto &poAttr : attrs)
577 111 : attributeNames.emplace_back(poAttr->GetName());
578 41 : if (HasUniqueNames(attributeNames))
579 : {
580 82 : auto objectContext(serializer.MakeObjectContext());
581 152 : for (const auto &poAttr : attrs)
582 : {
583 111 : serializer.AddObjKey(poAttr->GetName());
584 111 : DumpAttr(poAttr, serializer, psOptions, false, false);
585 : }
586 : }
587 : else
588 : {
589 0 : auto arrayContext(serializer.MakeArrayContext());
590 0 : for (const auto &poAttr : attrs)
591 : {
592 0 : DumpAttr(poAttr, serializer, psOptions, false, true);
593 : }
594 : }
595 41 : }
596 :
597 : /************************************************************************/
598 : /* DumpArrayRec() */
599 : /************************************************************************/
600 :
601 72 : static void DumpArrayRec(std::shared_ptr<GDALMDArray> array,
602 : CPLJSonStreamingWriter &serializer, size_t nCurDim,
603 : const std::vector<GUInt64> &dimSizes,
604 : std::vector<GUInt64> &startIdx,
605 : const GDALMultiDimInfoOptions *psOptions)
606 : {
607 : do
608 : {
609 72 : auto arrayContext(serializer.MakeArrayContext());
610 72 : if (nCurDim + 1 == dimSizes.size())
611 : {
612 66 : const auto &dt(array->GetDataType());
613 66 : const auto nDTSize(dt.GetSize());
614 : const auto lambdaDumpValue =
615 70 : [&serializer, &dt, nDTSize](std::vector<GByte> &abyTmp,
616 2679 : size_t nCount)
617 : {
618 70 : GByte *pabyPtr = &abyTmp[0];
619 963 : for (size_t i = 0; i < nCount; ++i)
620 : {
621 893 : DumpValue(serializer, pabyPtr, dt);
622 893 : dt.FreeDynamicMemory(pabyPtr);
623 893 : pabyPtr += nDTSize;
624 : }
625 136 : };
626 :
627 66 : serializer.SetNewline(false);
628 66 : std::vector<size_t> count(dimSizes.size(), 1);
629 71 : if (psOptions->nLimitValuesByDim == 0 ||
630 5 : dimSizes.back() <= psOptions->nLimitValuesByDim)
631 : {
632 61 : const size_t nCount = static_cast<size_t>(dimSizes.back());
633 61 : if (nCount > 0)
634 : {
635 120 : if (nCount != dimSizes.back() ||
636 60 : nDTSize > std::numeric_limits<size_t>::max() / nCount)
637 : {
638 0 : serializer.Add("[too many values]");
639 0 : break;
640 : }
641 60 : std::vector<GByte> abyTmp(nDTSize * nCount);
642 60 : count.back() = nCount;
643 120 : if (!array->Read(startIdx.data(), count.data(), nullptr,
644 60 : nullptr, dt, &abyTmp[0]))
645 0 : break;
646 60 : lambdaDumpValue(abyTmp, count.back());
647 : }
648 : }
649 : else
650 : {
651 : std::vector<GByte> abyTmp(
652 5 : nDTSize * (psOptions->nLimitValuesByDim + 1) / 2);
653 5 : startIdx.back() = 0;
654 5 : size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
655 5 : count.back() = nStartCount;
656 10 : if (!array->Read(startIdx.data(), count.data(), nullptr,
657 5 : nullptr, dt, &abyTmp[0]))
658 0 : break;
659 5 : lambdaDumpValue(abyTmp, count.back());
660 5 : serializer.Add("[...]");
661 :
662 5 : count.back() = psOptions->nLimitValuesByDim / 2;
663 5 : if (count.back())
664 : {
665 5 : startIdx.back() = dimSizes.back() - count.back();
666 10 : if (!array->Read(startIdx.data(), count.data(), nullptr,
667 5 : nullptr, dt, &abyTmp[0]))
668 0 : break;
669 5 : lambdaDumpValue(abyTmp, count.back());
670 : }
671 : }
672 : }
673 : else
674 : {
675 7 : if (psOptions->nLimitValuesByDim == 0 ||
676 1 : dimSizes[nCurDim] <= psOptions->nLimitValuesByDim)
677 : {
678 53 : for (startIdx[nCurDim] = 0;
679 53 : startIdx[nCurDim] < dimSizes[nCurDim]; ++startIdx[nCurDim])
680 : {
681 48 : DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
682 : startIdx, psOptions);
683 : }
684 : }
685 : else
686 : {
687 1 : size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
688 4 : for (startIdx[nCurDim] = 0; startIdx[nCurDim] < nStartCount;
689 3 : ++startIdx[nCurDim])
690 : {
691 3 : DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
692 : startIdx, psOptions);
693 : }
694 1 : serializer.Add("[...]");
695 1 : size_t nEndCount = psOptions->nLimitValuesByDim / 2;
696 3 : for (startIdx[nCurDim] = dimSizes[nCurDim] - nEndCount;
697 3 : startIdx[nCurDim] < dimSizes[nCurDim]; ++startIdx[nCurDim])
698 : {
699 2 : DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
700 : startIdx, psOptions);
701 : }
702 : }
703 : }
704 : } while (false);
705 72 : serializer.SetNewline(true);
706 72 : }
707 :
708 : /************************************************************************/
709 : /* DumpDimensions() */
710 : /************************************************************************/
711 :
712 : static void
713 149 : DumpDimensions(const std::shared_ptr<GDALGroup> &rootGroup,
714 : const std::vector<std::shared_ptr<GDALDimension>> &dims,
715 : CPLJSonStreamingWriter &serializer,
716 : const GDALMultiDimInfoOptions *psOptions,
717 : std::set<std::string> &alreadyDumpedDimensions)
718 : {
719 298 : auto arrayContext(serializer.MakeArrayContext());
720 443 : for (const auto &dim : dims)
721 : {
722 294 : std::string osFullname(dim->GetFullName());
723 294 : if (alreadyDumpedDimensions.find(osFullname) !=
724 588 : alreadyDumpedDimensions.end())
725 : {
726 153 : serializer.Add(osFullname);
727 153 : continue;
728 : }
729 :
730 282 : auto dimObjectContext(serializer.MakeObjectContext());
731 141 : if (!osFullname.empty() && osFullname[0] == '/')
732 127 : alreadyDumpedDimensions.insert(osFullname);
733 :
734 141 : serializer.AddObjKey("name");
735 141 : serializer.Add(dim->GetName());
736 :
737 141 : serializer.AddObjKey("full_name");
738 141 : serializer.Add(osFullname);
739 :
740 141 : serializer.AddObjKey("size");
741 141 : serializer.Add(static_cast<std::uint64_t>(dim->GetSize()));
742 :
743 141 : const auto &type(dim->GetType());
744 141 : if (!type.empty())
745 : {
746 63 : serializer.AddObjKey("type");
747 63 : serializer.Add(type);
748 : }
749 :
750 141 : const auto &direction(dim->GetDirection());
751 141 : if (!direction.empty())
752 : {
753 37 : serializer.AddObjKey("direction");
754 37 : serializer.Add(direction);
755 : }
756 :
757 282 : auto poIndexingVariable(dim->GetIndexingVariable());
758 141 : if (poIndexingVariable)
759 : {
760 64 : serializer.AddObjKey("indexing_variable");
761 : bool isKnownFromRoot;
762 : {
763 : // For autotest/gdrivers/tiledb_multidim.py::test_tiledb_multidim_array_read_dim_label_and_spatial_ref
764 64 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
765 : isKnownFromRoot =
766 192 : rootGroup->OpenMDArrayFromFullname(
767 128 : poIndexingVariable->GetFullName()) != nullptr;
768 : }
769 64 : if (isKnownFromRoot)
770 : {
771 46 : serializer.Add(poIndexingVariable->GetFullName());
772 : }
773 : else
774 : {
775 : std::set<std::string> alreadyDumpedDimensionsLocal(
776 36 : alreadyDumpedDimensions);
777 18 : alreadyDumpedDimensionsLocal.insert(std::move(osFullname));
778 36 : std::set<std::string> alreadyDumpedArrays;
779 :
780 36 : auto indexingVariableContext(serializer.MakeObjectContext());
781 18 : serializer.AddObjKey(poIndexingVariable->GetName());
782 18 : DumpArray(rootGroup, poIndexingVariable, serializer, psOptions,
783 : alreadyDumpedDimensionsLocal, alreadyDumpedArrays,
784 : /* bOutputObjType = */ false,
785 : /* bOutputName = */ false,
786 : /* bOutputOverviews = */ false);
787 : }
788 : }
789 : }
790 149 : }
791 :
792 : /************************************************************************/
793 : /* DumpStructuralInfo() */
794 : /************************************************************************/
795 :
796 10 : static void DumpStructuralInfo(CSLConstList papszStructuralInfo,
797 : CPLJSonStreamingWriter &serializer)
798 : {
799 20 : auto objectContext(serializer.MakeObjectContext());
800 10 : int i = 1;
801 20 : for (const auto &[pszKey, pszValue] : cpl::IterateNameValue(
802 30 : papszStructuralInfo, /* bReturnNullKeyIfNotNameValue = */ true))
803 : {
804 10 : if (pszKey)
805 : {
806 10 : serializer.AddObjKey(pszKey);
807 : }
808 : else
809 : {
810 0 : serializer.AddObjKey(CPLSPrintf("metadata_%d", i));
811 0 : ++i;
812 : }
813 10 : serializer.Add(pszValue);
814 : }
815 10 : }
816 :
817 : /************************************************************************/
818 : /* DumpArray() */
819 : /************************************************************************/
820 :
821 124 : static void DumpArray(const std::shared_ptr<GDALGroup> &rootGroup,
822 : const std::shared_ptr<GDALMDArray> &array,
823 : CPLJSonStreamingWriter &serializer,
824 : const GDALMultiDimInfoOptions *psOptions,
825 : std::set<std::string> &alreadyDumpedDimensions,
826 : std::set<std::string> &alreadyDumpedArrays,
827 : bool bOutputObjType, bool bOutputName,
828 : bool bOutputOverviews)
829 : {
830 : // Protection against infinite recursion
831 124 : if (cpl::contains(alreadyDumpedArrays, array->GetFullName()))
832 : {
833 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s already visited",
834 0 : array->GetFullName().c_str());
835 3 : return;
836 : }
837 124 : alreadyDumpedArrays.insert(array->GetFullName());
838 :
839 124 : auto objectContext(serializer.MakeObjectContext());
840 124 : if (bOutputObjType)
841 : {
842 2 : serializer.AddObjKey("type");
843 2 : serializer.Add("array");
844 : }
845 124 : if (bOutputName)
846 : {
847 2 : serializer.AddObjKey("name");
848 2 : serializer.Add(array->GetName());
849 : }
850 : else
851 : {
852 122 : serializer.AddObjKey("full_name");
853 122 : serializer.Add(array->GetFullName());
854 : }
855 :
856 124 : if (psOptions->bSummary)
857 3 : return;
858 :
859 121 : serializer.AddObjKey("datatype");
860 121 : const auto &dt(array->GetDataType());
861 121 : DumpDataType(dt, serializer);
862 :
863 242 : auto dims = array->GetDimensions();
864 121 : if (!dims.empty())
865 : {
866 120 : serializer.AddObjKey("dimensions");
867 120 : DumpDimensions(rootGroup, dims, serializer, psOptions,
868 : alreadyDumpedDimensions);
869 :
870 120 : serializer.AddObjKey("dimension_size");
871 240 : auto arrayContext(serializer.MakeArrayContext());
872 296 : for (const auto &poDim : dims)
873 : {
874 176 : serializer.Add(static_cast<uint64_t>(poDim->GetSize()));
875 : }
876 : }
877 :
878 121 : bool hasNonNullBlockSize = false;
879 242 : const auto blockSize = array->GetBlockSize();
880 269 : for (auto v : blockSize)
881 : {
882 168 : if (v != 0)
883 : {
884 20 : hasNonNullBlockSize = true;
885 20 : break;
886 : }
887 : }
888 121 : if (hasNonNullBlockSize)
889 : {
890 20 : serializer.AddObjKey("block_size");
891 40 : auto arrayContext(serializer.MakeArrayContext());
892 48 : for (auto v : blockSize)
893 : {
894 28 : serializer.Add(static_cast<uint64_t>(v));
895 : }
896 : }
897 :
898 242 : CPLStringList aosOptions;
899 121 : if (psOptions->bDetailed)
900 17 : aosOptions.SetNameValue("SHOW_ALL", "YES");
901 242 : auto attrs = array->GetAttributes(aosOptions.List());
902 121 : if (!attrs.empty())
903 : {
904 21 : serializer.AddObjKey("attributes");
905 21 : DumpAttrs(attrs, serializer, psOptions);
906 : }
907 :
908 121 : const auto &unit = array->GetUnit();
909 121 : if (!unit.empty())
910 : {
911 10 : serializer.AddObjKey("unit");
912 10 : serializer.Add(unit);
913 : }
914 :
915 121 : auto nodata = array->GetRawNoDataValue();
916 121 : if (nodata)
917 : {
918 18 : serializer.AddObjKey("nodata_value");
919 18 : DumpValue(serializer, static_cast<const GByte *>(nodata), dt);
920 : }
921 :
922 121 : bool bValid = false;
923 121 : double dfOffset = array->GetOffset(&bValid);
924 121 : if (bValid)
925 : {
926 1 : serializer.AddObjKey("offset");
927 1 : serializer.Add(dfOffset);
928 : }
929 121 : double dfScale = array->GetScale(&bValid);
930 121 : if (bValid)
931 : {
932 1 : serializer.AddObjKey("scale");
933 1 : serializer.Add(dfScale);
934 : }
935 :
936 242 : auto srs = array->GetSpatialRef();
937 121 : if (srs)
938 : {
939 10 : char *pszWKT = nullptr;
940 20 : CPLStringList wktOptions;
941 10 : wktOptions.SetNameValue("FORMAT", "WKT2_2018");
942 10 : if (srs->exportToWkt(&pszWKT, wktOptions.List()) == OGRERR_NONE)
943 : {
944 10 : serializer.AddObjKey("srs");
945 : {
946 20 : auto srsContext(serializer.MakeObjectContext());
947 10 : serializer.AddObjKey("wkt");
948 10 : serializer.Add(pszWKT);
949 10 : serializer.AddObjKey("data_axis_to_srs_axis_mapping");
950 : {
951 20 : auto dataAxisContext(serializer.MakeArrayContext(true));
952 20 : auto mapping = srs->GetDataAxisToSRSAxisMapping();
953 30 : for (const auto &axisNumber : mapping)
954 20 : serializer.Add(axisNumber);
955 : }
956 : }
957 : }
958 10 : CPLFree(pszWKT);
959 : }
960 :
961 121 : auto papszStructuralInfo = array->GetStructuralInfo();
962 121 : if (papszStructuralInfo)
963 : {
964 1 : serializer.AddObjKey("structural_info");
965 1 : DumpStructuralInfo(papszStructuralInfo, serializer);
966 : }
967 :
968 121 : if (psOptions->bDetailed)
969 : {
970 17 : serializer.AddObjKey("values");
971 17 : if (dims.empty())
972 : {
973 0 : std::vector<GByte> abyTmp(dt.GetSize());
974 0 : array->Read(nullptr, nullptr, nullptr, nullptr, dt, &abyTmp[0]);
975 0 : DumpValue(serializer, &abyTmp[0], dt);
976 : }
977 : else
978 : {
979 34 : std::vector<GUInt64> startIdx(dims.size());
980 17 : std::vector<GUInt64> dimSizes;
981 38 : for (const auto &dim : dims)
982 21 : dimSizes.emplace_back(dim->GetSize());
983 17 : DumpArrayRec(array, serializer, 0, dimSizes, startIdx, psOptions);
984 : }
985 : }
986 :
987 121 : if (psOptions->bStats)
988 : {
989 1 : double dfMin = 0.0;
990 1 : double dfMax = 0.0;
991 1 : double dfMean = 0.0;
992 1 : double dfStdDev = 0.0;
993 1 : GUInt64 nValidCount = 0;
994 1 : if (array->GetStatistics(false, true, &dfMin, &dfMax, &dfMean,
995 : &dfStdDev, &nValidCount, nullptr,
996 1 : nullptr) == CE_None)
997 : {
998 1 : serializer.AddObjKey("statistics");
999 2 : auto statContext(serializer.MakeObjectContext());
1000 1 : if (nValidCount > 0)
1001 : {
1002 1 : serializer.AddObjKey("min");
1003 1 : serializer.Add(dfMin);
1004 :
1005 1 : serializer.AddObjKey("max");
1006 1 : serializer.Add(dfMax);
1007 :
1008 1 : serializer.AddObjKey("mean");
1009 1 : serializer.Add(dfMean);
1010 :
1011 1 : serializer.AddObjKey("stddev");
1012 1 : serializer.Add(dfStdDev);
1013 : }
1014 :
1015 1 : serializer.AddObjKey("valid_sample_count");
1016 1 : serializer.Add(static_cast<std::uint64_t>(nValidCount));
1017 : }
1018 : }
1019 :
1020 121 : if (bOutputOverviews)
1021 : {
1022 103 : const int nOverviews = array->GetOverviewCount();
1023 103 : if (nOverviews > 0)
1024 : {
1025 3 : serializer.AddObjKey("overviews");
1026 6 : auto overviewsContext(serializer.MakeArrayContext());
1027 7 : for (int i = 0; i < nOverviews; ++i)
1028 : {
1029 8 : if (auto poOvrArray = array->GetOverview(i))
1030 : {
1031 4 : bool bIsStandalone = false;
1032 : {
1033 4 : CPLErrorStateBackuper oBackuper(CPLQuietErrorHandler);
1034 : bIsStandalone =
1035 12 : rootGroup->OpenMDArrayFromFullname(
1036 8 : poOvrArray->GetFullName()) == nullptr;
1037 : }
1038 4 : if (bIsStandalone)
1039 : {
1040 0 : DumpArray(rootGroup, poOvrArray, serializer, psOptions,
1041 : alreadyDumpedDimensions, alreadyDumpedArrays,
1042 : bOutputObjType, bOutputName,
1043 : bOutputOverviews);
1044 : }
1045 : else
1046 : {
1047 4 : serializer.Add(poOvrArray->GetFullName());
1048 : }
1049 : }
1050 : }
1051 : }
1052 : }
1053 : }
1054 :
1055 : /************************************************************************/
1056 : /* DumpArrays() */
1057 : /************************************************************************/
1058 :
1059 49 : static void DumpArrays(const std::shared_ptr<GDALGroup> &rootGroup,
1060 : const std::shared_ptr<GDALGroup> &group,
1061 : const std::vector<std::string> &arrayNames,
1062 : CPLJSonStreamingWriter &serializer,
1063 : const GDALMultiDimInfoOptions *psOptions,
1064 : std::set<std::string> &alreadyDumpedDimensions,
1065 : std::set<std::string> &alreadyDumpedArrays)
1066 : {
1067 98 : std::set<std::string> oSetNames;
1068 98 : auto objectContext(serializer.MakeObjectContext());
1069 208 : for (const auto &name : arrayNames)
1070 : {
1071 159 : if (oSetNames.find(name) != oSetNames.end())
1072 0 : continue; // should not happen on well behaved drivers
1073 159 : oSetNames.insert(name);
1074 318 : auto array = group->OpenMDArray(name);
1075 159 : if (array)
1076 : {
1077 104 : serializer.AddObjKey(array->GetName());
1078 104 : DumpArray(rootGroup, array, serializer, psOptions,
1079 : alreadyDumpedDimensions, alreadyDumpedArrays, false,
1080 : false, /* bOutputOverviews = */ true);
1081 : }
1082 : }
1083 49 : }
1084 :
1085 : /************************************************************************/
1086 : /* DumpGroup() */
1087 : /************************************************************************/
1088 :
1089 85 : static void DumpGroup(const std::shared_ptr<GDALGroup> &rootGroup,
1090 : const std::shared_ptr<GDALGroup> &group,
1091 : const char *pszDriverName,
1092 : CPLJSonStreamingWriter &serializer,
1093 : const GDALMultiDimInfoOptions *psOptions,
1094 : std::set<std::string> &alreadyDumpedDimensions,
1095 : std::set<std::string> &alreadyDumpedArrays,
1096 : bool bOutputObjType, bool bOutputName)
1097 : {
1098 170 : auto objectContext(serializer.MakeObjectContext());
1099 85 : if (bOutputObjType)
1100 : {
1101 39 : serializer.AddObjKey("type");
1102 39 : serializer.Add("group");
1103 : }
1104 85 : if (pszDriverName)
1105 : {
1106 38 : serializer.AddObjKey("driver");
1107 38 : serializer.Add(pszDriverName);
1108 : }
1109 85 : if (bOutputName)
1110 : {
1111 39 : serializer.AddObjKey("name");
1112 39 : serializer.Add(group->GetName());
1113 :
1114 : // If the root group is not actually the root, print its full path
1115 39 : if (pszDriverName != nullptr && group->GetName() != "/")
1116 : {
1117 0 : serializer.AddObjKey("full_name");
1118 0 : serializer.Add(group->GetFullName());
1119 : }
1120 : }
1121 46 : else if (psOptions->bSummary)
1122 : {
1123 0 : serializer.AddObjKey("full_name");
1124 0 : serializer.Add(group->GetFullName());
1125 : }
1126 :
1127 170 : CPLStringList aosOptionsGetAttr;
1128 85 : if (psOptions->bDetailed)
1129 12 : aosOptionsGetAttr.SetNameValue("SHOW_ALL", "YES");
1130 170 : auto attrs = group->GetAttributes(aosOptionsGetAttr.List());
1131 85 : if (!psOptions->bSummary && !attrs.empty())
1132 : {
1133 20 : serializer.AddObjKey("attributes");
1134 20 : DumpAttrs(attrs, serializer, psOptions);
1135 : }
1136 :
1137 170 : auto dims = group->GetDimensions();
1138 85 : if (!psOptions->bSummary && !dims.empty())
1139 : {
1140 29 : serializer.AddObjKey("dimensions");
1141 29 : DumpDimensions(rootGroup, dims, serializer, psOptions,
1142 : alreadyDumpedDimensions);
1143 : }
1144 :
1145 85 : const auto &types = group->GetDataTypes();
1146 85 : if (!psOptions->bSummary && !types.empty())
1147 : {
1148 1 : serializer.AddObjKey("datatypes");
1149 2 : auto arrayContext(serializer.MakeArrayContext());
1150 2 : for (const auto &dt : types)
1151 : {
1152 1 : DumpDataType(*(dt.get()), serializer);
1153 : }
1154 : }
1155 :
1156 170 : CPLStringList aosOptionsGetArray(psOptions->aosArrayOptions);
1157 85 : if (psOptions->bDetailed)
1158 12 : aosOptionsGetArray.SetNameValue("SHOW_ALL", "YES");
1159 170 : auto arrayNames = group->GetMDArrayNames(aosOptionsGetArray.List());
1160 85 : if (!arrayNames.empty())
1161 : {
1162 49 : serializer.AddObjKey("arrays");
1163 49 : DumpArrays(rootGroup, group, arrayNames, serializer, psOptions,
1164 : alreadyDumpedDimensions, alreadyDumpedArrays);
1165 : }
1166 :
1167 85 : auto papszStructuralInfo = group->GetStructuralInfo();
1168 85 : if (!psOptions->bSummary && papszStructuralInfo)
1169 : {
1170 9 : serializer.AddObjKey("structural_info");
1171 9 : DumpStructuralInfo(papszStructuralInfo, serializer);
1172 : }
1173 :
1174 170 : auto subgroupNames = group->GetGroupNames();
1175 85 : if (!subgroupNames.empty())
1176 : {
1177 32 : serializer.AddObjKey("groups");
1178 32 : if (HasUniqueNames(subgroupNames))
1179 : {
1180 64 : auto groupContext(serializer.MakeObjectContext());
1181 78 : for (const auto &subgroupName : subgroupNames)
1182 : {
1183 92 : auto subgroup = group->OpenGroup(subgroupName);
1184 46 : if (subgroup)
1185 : {
1186 46 : serializer.AddObjKey(subgroupName);
1187 46 : DumpGroup(rootGroup, subgroup, nullptr, serializer,
1188 : psOptions, alreadyDumpedDimensions,
1189 : alreadyDumpedArrays, false, false);
1190 : }
1191 : }
1192 : }
1193 : else
1194 : {
1195 0 : auto arrayContext(serializer.MakeArrayContext());
1196 0 : for (const auto &subgroupName : subgroupNames)
1197 : {
1198 0 : auto subgroup = group->OpenGroup(subgroupName);
1199 0 : if (subgroup)
1200 : {
1201 0 : DumpGroup(rootGroup, subgroup, nullptr, serializer,
1202 : psOptions, alreadyDumpedDimensions,
1203 : alreadyDumpedArrays, false, true);
1204 : }
1205 : }
1206 : }
1207 : }
1208 85 : }
1209 :
1210 : /************************************************************************/
1211 : /* GDALMultiDimTextOutputDumper */
1212 : /************************************************************************/
1213 :
1214 : using TableType = std::vector<std::vector<std::string>>;
1215 :
1216 : struct GDALMultiDimTextOutputDumper
1217 : {
1218 : const GDALMultiDimInfoOptions &m_sOptions;
1219 : std::string m_osOutput{};
1220 :
1221 6 : explicit GDALMultiDimTextOutputDumper(
1222 : const GDALMultiDimInfoOptions *psOptions)
1223 6 : : m_sOptions(*psOptions)
1224 : {
1225 6 : }
1226 :
1227 307 : void AddLine(const std::string &osLine = std::string())
1228 : {
1229 307 : if (m_sOptions.bStdoutOutput)
1230 : {
1231 97 : printf("%s\n", osLine.c_str());
1232 : }
1233 : else
1234 : {
1235 210 : m_osOutput += osLine;
1236 210 : m_osOutput += '\n';
1237 : }
1238 307 : }
1239 :
1240 : void DumpTable(const TableType &lines, int nIndentSpaces = 2,
1241 : const std::vector<size_t> &anColSizeIn = {},
1242 : bool headerLineSeparator = true,
1243 : bool bLeftPadNumbers = true);
1244 :
1245 : void
1246 : DumpAttributes(const std::vector<std::shared_ptr<GDALAttribute>> &apoAttrs,
1247 : int nIndentSpaces);
1248 :
1249 : void DumpStructuralInfo(CSLConstList papszStructuralInfo,
1250 : int nIndentSpaces);
1251 :
1252 : void DumpArray(const std::shared_ptr<GDALMDArray> &poArray);
1253 :
1254 : std::set<std::string>
1255 : DumpDimensionsSummary(const std::shared_ptr<GDALGroup> &group);
1256 :
1257 : void DumpArraysSummary(
1258 : const std::shared_ptr<GDALGroup> &group,
1259 : const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays);
1260 :
1261 : void DrumpGroupsSummary(const std::shared_ptr<GDALGroup> &group);
1262 : };
1263 :
1264 : /************************************************************************/
1265 : /* DumpTable() */
1266 : /************************************************************************/
1267 :
1268 44 : void GDALMultiDimTextOutputDumper::DumpTable(
1269 : const TableType &lines, int nIndentSpaces,
1270 : const std::vector<size_t> &anColSizeIn, bool headerLineSeparator,
1271 : bool bLeftPadNumbers)
1272 : {
1273 : // Compute column max size if needed
1274 88 : std::vector<size_t> anColSizeTmp;
1275 44 : if (anColSizeIn.empty())
1276 : {
1277 176 : for (const auto &line : lines)
1278 : {
1279 140 : if (line.size() > anColSizeTmp.size())
1280 36 : anColSizeTmp.resize(line.size());
1281 535 : for (const auto [idxCol, col] : cpl::enumerate(line))
1282 : {
1283 790 : anColSizeTmp[idxCol] =
1284 395 : std::max(anColSizeTmp[idxCol], col.size());
1285 : }
1286 : }
1287 : }
1288 44 : const auto &anColSize = anColSizeIn.empty() ? anColSizeTmp : anColSizeIn;
1289 :
1290 : // Check which columns are numeric-only
1291 88 : std::vector<bool> abIsNumbersOnly(anColSize.size(), true);
1292 192 : for (const auto [idxLine, line] : cpl::enumerate(lines))
1293 : {
1294 148 : CPLAssert(abIsNumbersOnly.size() >= line.size());
1295 148 : if (!headerLineSeparator || idxLine > 0)
1296 : {
1297 468 : for (const auto [idxCol, col] : cpl::enumerate(line))
1298 : {
1299 690 : abIsNumbersOnly[idxCol] =
1300 1163 : abIsNumbersOnly[idxCol] &&
1301 818 : CPLGetValueType(col.c_str()) != CPL_VALUE_STRING;
1302 : }
1303 : }
1304 : }
1305 :
1306 : // Now actually print table
1307 192 : for (const auto [idxLine, line] : cpl::enumerate(lines))
1308 : {
1309 148 : CPLAssert(anColSize.size() >= line.size());
1310 296 : std::string osFormattedLine(nIndentSpaces, ' ');
1311 583 : for (const auto [idxCol, col] : cpl::enumerate(line))
1312 : {
1313 435 : if (idxCol > 0)
1314 287 : osFormattedLine += " ";
1315 435 : if (idxLine == 0 && headerLineSeparator)
1316 : {
1317 : const size_t nLeftPadding =
1318 90 : (anColSize[idxCol] - col.size()) / 2;
1319 90 : osFormattedLine += std::string(nLeftPadding, ' ');
1320 90 : osFormattedLine += col;
1321 180 : osFormattedLine += std::string(
1322 180 : anColSize[idxCol] - col.size() - nLeftPadding, ' ');
1323 : }
1324 345 : else if (!bLeftPadNumbers || !abIsNumbersOnly[idxCol])
1325 : {
1326 332 : osFormattedLine += col;
1327 : osFormattedLine +=
1328 332 : std::string(anColSize[idxCol] - col.size(), ' ');
1329 : }
1330 : else
1331 : {
1332 : osFormattedLine +=
1333 13 : std::string(anColSize[idxCol] - col.size(), ' ');
1334 13 : osFormattedLine += col;
1335 : }
1336 : }
1337 148 : AddLine(osFormattedLine);
1338 :
1339 148 : if (idxLine == 0 && headerLineSeparator)
1340 : {
1341 25 : osFormattedLine = std::string(nIndentSpaces, ' ');
1342 115 : for (size_t idxCol = 0; idxCol < line.size(); ++idxCol)
1343 : {
1344 90 : if (idxCol > 0)
1345 65 : osFormattedLine += " ";
1346 90 : osFormattedLine += std::string(anColSize[idxCol], '-');
1347 : }
1348 25 : AddLine(osFormattedLine);
1349 : }
1350 : }
1351 44 : }
1352 :
1353 : /************************************************************************/
1354 : /* TypeToString() */
1355 : /************************************************************************/
1356 :
1357 61 : static std::string TypeToString(const GDALExtendedDataType &dt,
1358 : bool emitCompoundName = true)
1359 : {
1360 61 : std::string ret;
1361 61 : switch (dt.GetClass())
1362 : {
1363 29 : case GEDTC_STRING:
1364 29 : ret = "String";
1365 29 : break;
1366 :
1367 32 : case GEDTC_NUMERIC:
1368 32 : ret = GDALGetDataTypeName(dt.GetNumericDataType());
1369 32 : break;
1370 :
1371 0 : case GEDTC_COMPOUND:
1372 : {
1373 0 : if (emitCompoundName && !dt.GetName().empty())
1374 : {
1375 0 : ret = dt.GetName();
1376 0 : ret += ": ";
1377 : }
1378 0 : ret += '{';
1379 0 : bool firstComp = true;
1380 0 : const auto &components = dt.GetComponents();
1381 0 : for (const auto &comp : components)
1382 : {
1383 0 : if (!firstComp)
1384 0 : ret += ", ";
1385 0 : firstComp = false;
1386 0 : ret += comp->GetName();
1387 0 : ret += ": ";
1388 0 : ret += TypeToString(comp->GetType(), false);
1389 : }
1390 0 : ret += '}';
1391 0 : break;
1392 : }
1393 : }
1394 61 : return ret;
1395 : }
1396 :
1397 : /************************************************************************/
1398 : /* DumpAttributes() */
1399 : /************************************************************************/
1400 :
1401 13 : void GDALMultiDimTextOutputDumper::DumpAttributes(
1402 : const std::vector<std::shared_ptr<GDALAttribute>> &apoAttrs,
1403 : int nIndentSpaces)
1404 : {
1405 13 : if (!apoAttrs.empty())
1406 : {
1407 13 : AddLine();
1408 13 : AddLine(std::string(nIndentSpaces, ' ').append("Attributes:"));
1409 13 : TableType attrs;
1410 52 : attrs.push_back({"Name", "Type", "Value"});
1411 :
1412 53 : for (const auto &poAttr : apoAttrs)
1413 : {
1414 80 : CPLJSonStreamingWriter serializer(nullptr, nullptr);
1415 40 : serializer.SetPrettyFormatting(false);
1416 40 : DumpAttrValue(poAttr, serializer);
1417 80 : std::string osAttrVal = serializer.GetString();
1418 40 : constexpr size_t MAX_COLS = 80;
1419 40 : if (osAttrVal.size() <= MAX_COLS)
1420 : {
1421 190 : attrs.push_back({poAttr->GetName(),
1422 38 : TypeToString(poAttr->GetDataType()),
1423 114 : osAttrVal});
1424 : }
1425 : else
1426 : {
1427 2 : bool bFirstLineAttr = true;
1428 20 : while (osAttrVal.size() > MAX_COLS)
1429 : {
1430 18 : auto nLastBreak = osAttrVal.find_last_of(" ,\n", MAX_COLS);
1431 18 : if (nLastBreak == std::string::npos)
1432 0 : nLastBreak = osAttrVal.find_first_of(" ,\n");
1433 18 : if (nLastBreak != std::string::npos)
1434 : {
1435 18 : auto osVal = osAttrVal.substr(0, nLastBreak);
1436 36 : if (osAttrVal[nLastBreak] != ' ' &&
1437 18 : osAttrVal[nLastBreak] != '\n')
1438 18 : osVal += osAttrVal[nLastBreak];
1439 76 : attrs.push_back(
1440 2 : {bFirstLineAttr ? poAttr->GetName() : std::string(),
1441 : bFirstLineAttr
1442 2 : ? TypeToString(poAttr->GetDataType())
1443 : : std::string(),
1444 54 : osVal});
1445 18 : osAttrVal = osAttrVal.substr(nLastBreak + 1);
1446 : }
1447 : else
1448 : {
1449 0 : break;
1450 : }
1451 18 : bFirstLineAttr = false;
1452 : }
1453 8 : attrs.push_back(
1454 0 : {bFirstLineAttr ? poAttr->GetName() : std::string(),
1455 0 : bFirstLineAttr ? TypeToString(poAttr->GetDataType())
1456 : : std::string(),
1457 6 : osAttrVal});
1458 : }
1459 : }
1460 13 : DumpTable(attrs, nIndentSpaces + 2);
1461 : }
1462 13 : }
1463 :
1464 : /************************************************************************/
1465 : /* DimsToShapeString() */
1466 : /************************************************************************/
1467 :
1468 : static std::string
1469 14 : DimsToShapeString(const std::vector<std::shared_ptr<GDALDimension>> &dims)
1470 : {
1471 14 : std::string s("[");
1472 36 : for (const auto &poDim : dims)
1473 : {
1474 22 : if (s.size() > 1)
1475 9 : s += ", ";
1476 22 : s += std::to_string(poDim->GetSize());
1477 : }
1478 14 : s += ']';
1479 14 : return s;
1480 : }
1481 :
1482 : /************************************************************************/
1483 : /* DimsToString() */
1484 : /************************************************************************/
1485 :
1486 : static std::string
1487 14 : DimsToString(const std::vector<std::shared_ptr<GDALDimension>> &dims,
1488 : const std::string &osSameDimGroup)
1489 : {
1490 14 : std::string s("(");
1491 36 : for (const auto &poDim : dims)
1492 : {
1493 22 : if (s.size() > 1)
1494 9 : s += ", ";
1495 13 : else if (!osSameDimGroup.empty())
1496 : {
1497 0 : s += osSameDimGroup;
1498 0 : s += '/';
1499 : }
1500 22 : const std::string &osFullname = poDim->GetFullName();
1501 22 : if (!osFullname.empty())
1502 : {
1503 22 : s += osSameDimGroup.empty()
1504 44 : ? osFullname
1505 22 : : osFullname.substr(osSameDimGroup.size() + 1);
1506 : }
1507 : else
1508 : {
1509 0 : s += "(unnamed)";
1510 : }
1511 : }
1512 14 : s += ')';
1513 14 : return s;
1514 : }
1515 :
1516 : /************************************************************************/
1517 : /* DimsToSameDimGroup() */
1518 : /************************************************************************/
1519 :
1520 : /** Return the prefix shared by all dimensions if there is one, or empty string
1521 : * otherwise.
1522 : */
1523 : static std::string
1524 14 : DimsToSameDimGroup(const std::vector<std::shared_ptr<GDALDimension>> &dims)
1525 : {
1526 28 : std::string osSameDimGroup;
1527 36 : for (const auto &poDim : dims)
1528 : {
1529 22 : std::string osDimGroup = poDim->GetFullName();
1530 22 : const auto nPos = osDimGroup.rfind('/');
1531 22 : if (nPos != std::string::npos)
1532 : {
1533 22 : osDimGroup.resize(nPos);
1534 22 : if (osSameDimGroup.empty())
1535 : {
1536 22 : osSameDimGroup = osDimGroup;
1537 : }
1538 0 : else if (osSameDimGroup != osDimGroup)
1539 : {
1540 0 : return {};
1541 : }
1542 : }
1543 : else
1544 : {
1545 0 : return {};
1546 : }
1547 : }
1548 14 : return osSameDimGroup;
1549 : }
1550 :
1551 : /************************************************************************/
1552 : /* BlockSizeToString() */
1553 : /************************************************************************/
1554 :
1555 : template <class T>
1556 14 : static std::string BlockSizeToString(const std::vector<T> &anBlockSize,
1557 : bool *pbAllZero = nullptr)
1558 : {
1559 14 : std::string s("[");
1560 14 : if (pbAllZero)
1561 14 : *pbAllZero = true;
1562 36 : for (const auto nSize : anBlockSize)
1563 : {
1564 22 : if (s.size() > 1)
1565 9 : s += ", ";
1566 22 : s += std::to_string(nSize);
1567 22 : if (pbAllZero)
1568 22 : *pbAllZero = *pbAllZero && nSize == 0;
1569 : }
1570 14 : s += ']';
1571 14 : return s;
1572 : }
1573 :
1574 : /************************************************************************/
1575 : /* WriteToStdoutWithSpaceIndent() */
1576 : /************************************************************************/
1577 :
1578 1263 : static void WriteToStdoutWithSpaceIndent(const char *pszText, void *pUserData)
1579 : {
1580 1263 : const int *pnIndent = static_cast<const int *>(pUserData);
1581 1263 : printf("%s", pszText);
1582 1263 : if (pszText[0] == '\n')
1583 21 : printf("%s", std::string(*pnIndent, ' ').c_str());
1584 1263 : }
1585 :
1586 : /************************************************************************/
1587 : /* GDALMultiDimTextOutputDumper::DumpStructuralInfo() */
1588 : /************************************************************************/
1589 :
1590 4 : void GDALMultiDimTextOutputDumper::DumpStructuralInfo(
1591 : CSLConstList papszStructuralInfo, int nIndentSpaces)
1592 : {
1593 4 : AddLine();
1594 4 : AddLine(std::string(nIndentSpaces, ' ').append("Structural metadata:"));
1595 :
1596 4 : TableType info;
1597 :
1598 4 : int i = 1;
1599 10 : for (const auto &[pszKey, pszValue] :
1600 : cpl::IterateNameValue(papszStructuralInfo,
1601 14 : /* bReturnNullKeyIfNotNameValue = */ true))
1602 : {
1603 10 : std::vector<std::string> line;
1604 5 : if (pszKey)
1605 : {
1606 5 : line.push_back(pszKey);
1607 : }
1608 : else
1609 : {
1610 0 : line.push_back(CPLSPrintf("metadata_%d", i));
1611 0 : ++i;
1612 : }
1613 5 : line.push_back(pszValue);
1614 5 : info.push_back(std::move(line));
1615 : }
1616 4 : DumpTable(info, nIndentSpaces + 2, {}, false);
1617 4 : }
1618 :
1619 : /************************************************************************/
1620 : /* GDALMultiDimTextOutputDumper::DumpArray() */
1621 : /************************************************************************/
1622 :
1623 10 : void GDALMultiDimTextOutputDumper::DumpArray(
1624 : const std::shared_ptr<GDALMDArray> &poArray)
1625 : {
1626 10 : AddLine();
1627 10 : AddLine(" - " + poArray->GetFullName() + ":");
1628 :
1629 10 : constexpr int INDENT_LEVEL = 6;
1630 :
1631 20 : TableType props;
1632 10 : const auto &dims = poArray->GetDimensions();
1633 40 : props.push_back(
1634 40 : {"Dimensions:", DimsToString(dims, DimsToSameDimGroup(dims))});
1635 30 : props.push_back({"Shape:", DimsToShapeString(dims)});
1636 10 : bool bAllZero = true;
1637 : std::string osChunkSize =
1638 20 : BlockSizeToString(poArray->GetBlockSize(), &bAllZero);
1639 10 : if (!bAllZero)
1640 3 : props.push_back({"Chunk size:", std::move(osChunkSize)});
1641 :
1642 10 : const auto &dt = poArray->GetDataType();
1643 30 : props.push_back({"Type:", TypeToString(dt)});
1644 10 : const std::string &osUnit = poArray->GetUnit();
1645 10 : if (!osUnit.empty())
1646 12 : props.push_back({"Unit:", osUnit});
1647 :
1648 10 : if (const auto nodata = poArray->GetRawNoDataValue())
1649 : {
1650 1 : CPLJSonStreamingWriter serializer(nullptr, nullptr);
1651 1 : serializer.SetPrettyFormatting(false);
1652 1 : DumpValue(serializer, static_cast<const GByte *>(nodata), dt);
1653 3 : props.push_back({"Nodata value:", serializer.GetString()});
1654 : }
1655 10 : DumpTable(props, INDENT_LEVEL, {},
1656 : /* headerLineSeparator = */ false,
1657 : /* bLeftPadNumbers = */ false);
1658 :
1659 10 : DumpAttributes(poArray->GetAttributes(), INDENT_LEVEL);
1660 :
1661 14 : if (const auto poSRS = poArray->GetSpatialRef())
1662 : {
1663 4 : AddLine();
1664 8 : std::string osCRS;
1665 4 : EmitTextDisplayOfCRS(poSRS.get(), "AUTO", "Coordinate Reference System",
1666 28 : [&osCRS](const std::string &s) { osCRS += s; });
1667 : const CPLStringList aosLines(
1668 8 : CSLTokenizeString2(osCRS.c_str(), "\n", 0));
1669 28 : for (const char *pszLine : aosLines)
1670 24 : AddLine(std::string(INDENT_LEVEL, ' ').append(pszLine));
1671 : }
1672 :
1673 10 : if (CSLConstList papszStructuralInfo = poArray->GetStructuralInfo())
1674 : {
1675 1 : DumpStructuralInfo(papszStructuralInfo, INDENT_LEVEL);
1676 : }
1677 :
1678 10 : if (m_sOptions.bStats)
1679 : {
1680 1 : double dfMin = 0.0;
1681 1 : double dfMax = 0.0;
1682 1 : double dfMean = 0.0;
1683 1 : double dfStdDev = 0.0;
1684 1 : GUInt64 nValidCount = 0;
1685 1 : if (poArray->GetStatistics(false, true, &dfMin, &dfMax, &dfMean,
1686 : &dfStdDev, &nValidCount, nullptr,
1687 1 : nullptr) == CE_None)
1688 : {
1689 1 : AddLine();
1690 1 : AddLine(std::string(INDENT_LEVEL, ' ').append("Statistics:"));
1691 :
1692 1 : TableType stats;
1693 1 : if (nValidCount > 0)
1694 : {
1695 3 : stats.push_back({"min", std::to_string(dfMin)});
1696 3 : stats.push_back({"max", std::to_string(dfMax)});
1697 3 : stats.push_back({"mean", std::to_string(dfMean)});
1698 3 : stats.push_back({"stddev", std::to_string(dfStdDev)});
1699 : }
1700 3 : stats.push_back(
1701 2 : {"valid sample count", std::to_string(nValidCount)});
1702 :
1703 1 : DumpTable(stats, INDENT_LEVEL + 2, {}, false);
1704 : }
1705 : }
1706 :
1707 10 : if (m_sOptions.bDetailed)
1708 : {
1709 2 : if (dims.empty())
1710 : {
1711 0 : std::vector<GByte> abyTmp(dt.GetSize());
1712 0 : poArray->Read(nullptr, nullptr, nullptr, nullptr, dt, &abyTmp[0]);
1713 0 : CPLJSonStreamingWriter serializer(nullptr, nullptr);
1714 0 : DumpValue(serializer, &abyTmp[0], dt);
1715 :
1716 0 : AddLine();
1717 0 : AddLine(std::string(INDENT_LEVEL, ' ')
1718 0 : .append("Value: ")
1719 0 : .append(serializer.GetString()));
1720 : }
1721 : else
1722 : {
1723 2 : AddLine();
1724 2 : AddLine(std::string(INDENT_LEVEL, ' ').append("Values:"));
1725 :
1726 2 : int nIndent = INDENT_LEVEL;
1727 2 : CPLJSonStreamingWriter serializer(m_sOptions.bStdoutOutput
1728 : ? WriteToStdoutWithSpaceIndent
1729 : : nullptr,
1730 4 : &nIndent);
1731 4 : std::vector<GUInt64> startIdx(dims.size());
1732 4 : std::vector<GUInt64> dimSizes;
1733 6 : for (const auto &dim : dims)
1734 4 : dimSizes.emplace_back(dim->GetSize());
1735 2 : if (m_sOptions.bStdoutOutput)
1736 1 : printf("%s", std::string(INDENT_LEVEL, ' ').c_str());
1737 2 : DumpArrayRec(poArray, serializer, 0, dimSizes, startIdx,
1738 2 : &m_sOptions);
1739 2 : if (!m_sOptions.bStdoutOutput)
1740 : {
1741 2 : AddLine(std::string(INDENT_LEVEL, ' ')
1742 2 : .append(CPLString(serializer.GetString())
1743 : .replaceAll(
1744 2 : '\n', std::string("\n").append(
1745 2 : std::string(INDENT_LEVEL,
1746 1 : ' ')))));
1747 : }
1748 : else
1749 : {
1750 1 : AddLine();
1751 : }
1752 : }
1753 : }
1754 10 : }
1755 :
1756 : /************************************************************************/
1757 : /* GDALMultiDimTextOutputDumper:: DumpDimensionsSummary() */
1758 : /************************************************************************/
1759 :
1760 4 : std::set<std::string> GDALMultiDimTextOutputDumper::DumpDimensionsSummary(
1761 : const std::shared_ptr<GDALGroup> &group)
1762 : {
1763 4 : std::set<std::string> oSetIndexingVariablePaths;
1764 :
1765 8 : const auto dims = group->GetDimensionsRecursive();
1766 4 : if (!dims.empty())
1767 : {
1768 4 : AddLine();
1769 4 : AddLine("Dimensions:");
1770 4 : TableType lines;
1771 20 : lines.push_back({"Name (path)", "Size", "Type", "Direction"});
1772 12 : for (const auto &poDim : dims)
1773 : {
1774 : std::vector<std::string> line{
1775 8 : poDim->GetFullName(), std::to_string(poDim->GetSize()),
1776 56 : poDim->GetType(), poDim->GetDirection()};
1777 8 : lines.push_back(std::move(line));
1778 16 : const auto poIndexingVariable = poDim->GetIndexingVariable();
1779 8 : if (poIndexingVariable)
1780 : {
1781 : const auto &osIndexingVarPath =
1782 6 : poIndexingVariable->GetFullName();
1783 6 : if (!osIndexingVarPath.empty() && osIndexingVarPath[0] == '/')
1784 : {
1785 6 : oSetIndexingVariablePaths.insert(osIndexingVarPath);
1786 : }
1787 : }
1788 : }
1789 4 : DumpTable(lines);
1790 : }
1791 :
1792 8 : return oSetIndexingVariablePaths;
1793 : }
1794 :
1795 : /************************************************************************/
1796 : /* GDALMultiDimTextOutputDumper:: DumpArraysSummary() */
1797 : /************************************************************************/
1798 :
1799 4 : void GDALMultiDimTextOutputDumper::DumpArraysSummary(
1800 : const std::shared_ptr<GDALGroup> &group,
1801 : const std::vector<std::shared_ptr<GDALMDArray>> &apoArrays)
1802 : {
1803 : const std::set<std::string> oSetIndexingVariablePaths =
1804 8 : DumpDimensionsSummary(group);
1805 :
1806 8 : TableType linesCoordinateArrays;
1807 8 : TableType linesScalarArrays;
1808 8 : std::map<std::string, TableType> linesDataArrays;
1809 15 : for (const auto &poArray : apoArrays)
1810 : {
1811 11 : const auto &dims = poArray->GetDimensions();
1812 11 : if (cpl::contains(oSetIndexingVariablePaths, poArray->GetFullName()))
1813 : {
1814 6 : if (linesCoordinateArrays.empty())
1815 15 : linesCoordinateArrays.push_back(
1816 12 : {"Name (path)", "Dimension", "Type", "Unit"});
1817 12 : std::string osDimension;
1818 12 : for (const auto &poDim : dims)
1819 : {
1820 6 : if (osDimension.empty())
1821 6 : osDimension = '(';
1822 : else
1823 0 : osDimension += ", ";
1824 6 : osDimension += poDim->GetName();
1825 : }
1826 6 : if (!osDimension.empty())
1827 6 : osDimension += ')';
1828 : std::vector<std::string> line{
1829 12 : poArray->GetFullName(), std::move(osDimension),
1830 48 : TypeToString(poArray->GetDataType()), poArray->GetUnit()};
1831 6 : linesCoordinateArrays.push_back(std::move(line));
1832 : }
1833 5 : else if (dims.empty())
1834 : {
1835 1 : if (linesScalarArrays.empty())
1836 4 : linesScalarArrays.push_back({"Name (path)", "Type", "Unit"});
1837 1 : std::vector<std::string> line{poArray->GetFullName(),
1838 1 : TypeToString(poArray->GetDataType()),
1839 7 : poArray->GetUnit()};
1840 1 : linesScalarArrays.push_back(std::move(line));
1841 : }
1842 : else
1843 : {
1844 8 : const std::string osSameDimGroup = DimsToSameDimGroup(dims);
1845 :
1846 4 : bool bAllZero = true;
1847 : std::string osChunkSize =
1848 8 : BlockSizeToString(poArray->GetBlockSize(), &bAllZero);
1849 :
1850 : std::vector<std::string> line{
1851 8 : poArray->GetFullName(), TypeToString(poArray->GetDataType()),
1852 4 : poArray->GetUnit(), DimsToShapeString(dims),
1853 40 : bAllZero ? std::string("(unknown)") : osChunkSize};
1854 8 : linesDataArrays[DimsToString(dims, osSameDimGroup)].push_back(
1855 4 : std::move(line));
1856 : }
1857 : }
1858 :
1859 4 : if (!linesCoordinateArrays.empty())
1860 : {
1861 3 : AddLine();
1862 3 : AddLine("Coordinates (indexing variables):");
1863 3 : DumpTable(linesCoordinateArrays);
1864 : }
1865 :
1866 4 : if (!linesDataArrays.empty())
1867 : {
1868 4 : AddLine();
1869 4 : AddLine("Data variables:");
1870 :
1871 8 : TableType headerLine;
1872 24 : headerLine.push_back(
1873 20 : {"Name (path)", "Type", "Unit", "Shape", "Chunk size"});
1874 :
1875 8 : std::vector<size_t> anColSizeDataVars;
1876 4 : anColSizeDataVars.resize(headerLine[0].size());
1877 24 : for (const auto [iCol, col] : cpl::enumerate(headerLine[0]))
1878 20 : anColSizeDataVars[iCol] = col.size();
1879 8 : for (const auto &[_, lines] : linesDataArrays)
1880 : {
1881 8 : for (const auto &line : lines)
1882 : {
1883 24 : for (const auto [iCol, col] : cpl::enumerate(line))
1884 : {
1885 40 : anColSizeDataVars[iCol] =
1886 20 : std::max(anColSizeDataVars[iCol], col.size());
1887 : }
1888 : }
1889 : }
1890 :
1891 4 : DumpTable(headerLine, 2, anColSizeDataVars);
1892 8 : for (const auto &[path, lines] : linesDataArrays)
1893 : {
1894 4 : AddLine();
1895 4 : AddLine(" " + path + ":");
1896 4 : DumpTable(lines, 2, anColSizeDataVars, false);
1897 : }
1898 : }
1899 :
1900 4 : if (!linesScalarArrays.empty())
1901 : {
1902 1 : AddLine();
1903 1 : AddLine("Scalar arrays:");
1904 1 : DumpTable(linesScalarArrays);
1905 : }
1906 4 : }
1907 :
1908 : /************************************************************************/
1909 : /* GDALMultiDimTextOutputDumper::DrumpGroupsSummary() */
1910 : /************************************************************************/
1911 :
1912 4 : void GDALMultiDimTextOutputDumper::DrumpGroupsSummary(
1913 : const std::shared_ptr<GDALGroup> &group)
1914 : {
1915 8 : const auto apoGroups = group->GetGroupsRecursive();
1916 4 : if (!apoGroups.empty())
1917 : {
1918 0 : AddLine();
1919 0 : AddLine("Groups:");
1920 0 : std::vector<std::vector<std::string>> groupNames;
1921 0 : for (auto &poGroup : apoGroups)
1922 0 : groupNames.push_back(CPLStringList(
1923 0 : CSLTokenizeString2(poGroup->GetFullName().c_str(), "/", 0)));
1924 0 : std::sort(groupNames.begin(), groupNames.end());
1925 0 : for (const auto &groupName : groupNames)
1926 : {
1927 0 : std::string osLine(" ");
1928 0 : for (const auto &part : groupName)
1929 : {
1930 0 : osLine += '/';
1931 0 : osLine += part;
1932 : }
1933 0 : AddLine(osLine);
1934 : }
1935 : }
1936 4 : }
1937 :
1938 : /************************************************************************/
1939 : /* DumpAsText() */
1940 : /************************************************************************/
1941 :
1942 6 : static char *DumpAsText(const std::shared_ptr<GDALGroup> &group,
1943 : const char *pszDriverName,
1944 : const GDALMultiDimInfoOptions *psOptions)
1945 : {
1946 12 : GDALMultiDimTextOutputDumper dumper(psOptions);
1947 :
1948 12 : CPLStringList aosOptionsGetArray(psOptions->aosArrayOptions);
1949 6 : if (psOptions->bDetailed)
1950 2 : aosOptionsGetArray.SetNameValue("SHOW_ALL", "YES");
1951 :
1952 : const auto apoArrays =
1953 12 : group->GetMDArraysRecursive(nullptr, aosOptionsGetArray.List());
1954 :
1955 6 : if (psOptions->osArrayName.empty())
1956 : {
1957 4 : dumper.AddLine(
1958 8 : std::string("Driver: ")
1959 4 : .append(pszDriverName ? pszDriverName : "(unknown)"));
1960 :
1961 4 : if (CSLConstList papszStructuralInfo = group->GetStructuralInfo())
1962 : {
1963 3 : dumper.DumpStructuralInfo(papszStructuralInfo, 0);
1964 : }
1965 :
1966 4 : dumper.DumpArraysSummary(group, apoArrays);
1967 :
1968 4 : dumper.DrumpGroupsSummary(group);
1969 : }
1970 :
1971 11 : if ((!psOptions->bSummary || !psOptions->osArrayName.empty()) &&
1972 5 : !apoArrays.empty())
1973 : {
1974 5 : if (psOptions->osArrayName.empty())
1975 : {
1976 3 : dumper.DumpAttributes(group->GetAttributes(), 0);
1977 3 : dumper.AddLine();
1978 : }
1979 :
1980 5 : dumper.AddLine("Arrays:");
1981 21 : for (const auto &poArray : apoArrays)
1982 : {
1983 24 : if (psOptions->osArrayName.empty() ||
1984 24 : poArray->GetName() == psOptions->osArrayName ||
1985 7 : poArray->GetFullName() == psOptions->osArrayName)
1986 : {
1987 10 : dumper.DumpArray(poArray);
1988 : }
1989 : }
1990 : }
1991 :
1992 6 : if (psOptions->bStdoutOutput)
1993 : {
1994 2 : return VSIStrdup("ok");
1995 : }
1996 : else
1997 : {
1998 4 : return VSIStrdup(dumper.m_osOutput.c_str());
1999 : }
2000 : }
2001 :
2002 : /************************************************************************/
2003 : /* WriteToStdout() */
2004 : /************************************************************************/
2005 :
2006 1571 : static void WriteToStdout(const char *pszText, void *)
2007 : {
2008 1571 : printf("%s", pszText);
2009 1571 : }
2010 :
2011 : /************************************************************************/
2012 : /* GDALMultiDimInfoAppOptionsGetParser() */
2013 : /************************************************************************/
2014 :
2015 50 : static std::unique_ptr<GDALArgumentParser> GDALMultiDimInfoAppOptionsGetParser(
2016 : GDALMultiDimInfoOptions *psOptions,
2017 : GDALMultiDimInfoOptionsForBinary *psOptionsForBinary)
2018 : {
2019 : auto argParser = std::make_unique<GDALArgumentParser>(
2020 50 : "gdalmdiminfo", /* bForBinary=*/psOptionsForBinary != nullptr);
2021 :
2022 50 : argParser->add_description(
2023 50 : _("Lists various information about a GDAL multidimensional dataset."));
2024 :
2025 50 : argParser->add_epilog(_("For more details, consult "
2026 50 : "https://gdal.org/programs/gdalmdiminfo.html"));
2027 : {
2028 50 : auto &group = argParser->add_mutually_exclusive_group();
2029 :
2030 50 : group.add_argument("-summary")
2031 50 : .flag()
2032 50 : .store_into(psOptions->bSummary)
2033 : .help(_("Report only group and array hierarchy, without detailed "
2034 50 : "information on attributes or dimensions."));
2035 :
2036 50 : group.add_argument("-detailed")
2037 50 : .flag()
2038 50 : .store_into(psOptions->bDetailed)
2039 : .help(
2040 : _("Most verbose output. Report attribute data types and array "
2041 50 : "values."));
2042 : }
2043 :
2044 : argParser->add_inverted_logic_flag(
2045 : "-nopretty", &psOptions->bPretty,
2046 50 : _("Outputs on a single line without any indentation."));
2047 :
2048 50 : argParser->add_argument("-array")
2049 100 : .metavar("<array_name>")
2050 50 : .store_into(psOptions->osArrayName)
2051 : .help(_("Name of the array, used to restrict the output to the "
2052 50 : "specified array."));
2053 :
2054 50 : argParser->add_argument("-limit")
2055 100 : .metavar("<number>")
2056 50 : .scan<'i', int>()
2057 50 : .store_into(psOptions->nLimitValuesByDim)
2058 : .help(_("Number of values in each dimension that is used to limit the "
2059 50 : "display of array values."));
2060 :
2061 50 : if (psOptionsForBinary)
2062 : {
2063 : argParser->add_open_options_argument(
2064 3 : psOptionsForBinary->aosOpenOptions);
2065 :
2066 : argParser->add_input_format_argument(
2067 3 : &psOptionsForBinary->aosAllowInputDrivers);
2068 :
2069 3 : argParser->add_argument("dataset_name")
2070 6 : .metavar("<dataset_name>")
2071 3 : .store_into(psOptionsForBinary->osFilename)
2072 3 : .help("Input dataset.");
2073 : }
2074 :
2075 50 : argParser->add_argument("-arrayoption")
2076 100 : .metavar("<NAME>=<VALUE>")
2077 50 : .append()
2078 2 : .action([psOptions](const std::string &s)
2079 52 : { psOptions->aosArrayOptions.AddString(s.c_str()); })
2080 : .help(_("Option passed to GDALGroup::GetMDArrayNames() to filter "
2081 50 : "reported arrays."));
2082 :
2083 50 : argParser->add_argument("-stats")
2084 50 : .flag()
2085 50 : .store_into(psOptions->bStats)
2086 50 : .help(_("Read and display image statistics."));
2087 :
2088 50 : argParser->add_argument("-format")
2089 50 : .hidden()
2090 50 : .store_into(psOptions->osFormat)
2091 50 : .help(_("Output format."));
2092 :
2093 : // Only used by gdalmdiminfo binary to write output to stdout instead of in a string, in JSON mode
2094 50 : argParser->add_argument("-stdout").flag().hidden().store_into(
2095 50 : psOptions->bStdoutOutput);
2096 :
2097 50 : return argParser;
2098 : }
2099 :
2100 : /************************************************************************/
2101 : /* GDALMultiDimInfoAppGetParserUsage() */
2102 : /************************************************************************/
2103 :
2104 0 : std::string GDALMultiDimInfoAppGetParserUsage()
2105 : {
2106 : try
2107 : {
2108 0 : GDALMultiDimInfoOptions sOptions;
2109 0 : GDALMultiDimInfoOptionsForBinary sOptionsForBinary;
2110 : auto argParser =
2111 0 : GDALMultiDimInfoAppOptionsGetParser(&sOptions, &sOptionsForBinary);
2112 0 : return argParser->usage();
2113 : }
2114 0 : catch (const std::exception &err)
2115 : {
2116 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
2117 0 : err.what());
2118 0 : return std::string();
2119 : }
2120 : }
2121 :
2122 : /************************************************************************/
2123 : /* GDALMultiDimInfo() */
2124 : /************************************************************************/
2125 :
2126 : /* clang-format off */
2127 : /**
2128 : * Lists various information about a GDAL multidimensional dataset.
2129 : *
2130 : * This is the equivalent of the
2131 : * <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a>utility.
2132 : *
2133 : * GDALMultiDimInfoOptions* must be allocated and freed with
2134 : * GDALMultiDimInfoOptionsNew() and GDALMultiDimInfoOptionsFree() respectively.
2135 : *
2136 : * @param hDataset the dataset handle.
2137 : * @param psOptionsIn the options structure returned by
2138 : * GDALMultiDimInfoOptionsNew() or NULL.
2139 : * @return string corresponding to the information about the raster dataset
2140 : * (must be freed with CPLFree()), or NULL in case of error.
2141 : *
2142 : * @since GDAL 3.1
2143 : */
2144 : /* clang-format on */
2145 :
2146 48 : char *GDALMultiDimInfo(GDALDatasetH hDataset,
2147 : const GDALMultiDimInfoOptions *psOptionsIn)
2148 : {
2149 48 : if (hDataset == nullptr)
2150 0 : return nullptr;
2151 :
2152 96 : GDALMultiDimInfoOptions oOptionsDefault;
2153 48 : const GDALMultiDimInfoOptions *psOptions =
2154 48 : psOptionsIn ? psOptionsIn : &oOptionsDefault;
2155 : CPLJSonStreamingWriter serializer(
2156 96 : psOptions->bStdoutOutput ? WriteToStdout : nullptr, nullptr);
2157 48 : serializer.SetPrettyFormatting(psOptions->bPretty);
2158 48 : GDALDataset *poDS = GDALDataset::FromHandle(hDataset);
2159 96 : auto group = poDS->GetRootGroup();
2160 48 : if (!group)
2161 1 : return nullptr;
2162 :
2163 94 : std::set<std::string> alreadyDumpedDimensions;
2164 94 : std::set<std::string> alreadyDumpedArrays;
2165 :
2166 : try
2167 : {
2168 47 : const char *pszDriverName = nullptr;
2169 47 : auto poDriver = poDS->GetDriver();
2170 47 : if (poDriver)
2171 46 : pszDriverName = poDriver->GetDescription();
2172 :
2173 47 : if (psOptions->osFormat == "text")
2174 : {
2175 6 : return DumpAsText(group, pszDriverName, psOptions);
2176 : }
2177 : else
2178 : {
2179 41 : if (psOptions->osArrayName.empty())
2180 : {
2181 39 : DumpGroup(group, group, pszDriverName, serializer, psOptions,
2182 : alreadyDumpedDimensions, alreadyDumpedArrays, true,
2183 : true);
2184 : }
2185 : else
2186 : {
2187 2 : auto curGroup = group;
2188 : CPLStringList aosTokens(
2189 2 : CSLTokenizeString2(psOptions->osArrayName.c_str(), "/", 0));
2190 2 : for (int i = 0; i < aosTokens.size() - 1; i++)
2191 : {
2192 0 : auto curGroupNew = curGroup->OpenGroup(aosTokens[i]);
2193 0 : if (!curGroupNew)
2194 : {
2195 0 : CPLError(CE_Failure, CPLE_AppDefined,
2196 : "Cannot find group %s", aosTokens[i]);
2197 0 : return nullptr;
2198 : }
2199 0 : curGroup = std::move(curGroupNew);
2200 : }
2201 2 : const char *pszArrayName = aosTokens.back();
2202 4 : auto array(curGroup->OpenMDArray(pszArrayName));
2203 2 : if (!array)
2204 : {
2205 0 : CPLError(CE_Failure, CPLE_AppDefined,
2206 : "Cannot find array %s", pszArrayName);
2207 0 : return nullptr;
2208 : }
2209 2 : DumpArray(group, array, serializer, psOptions,
2210 : alreadyDumpedDimensions, alreadyDumpedArrays, true,
2211 : true, true);
2212 : }
2213 :
2214 41 : if (psOptions->bStdoutOutput)
2215 : {
2216 3 : printf("\n");
2217 3 : return VSIStrdup("ok");
2218 : }
2219 : else
2220 : {
2221 38 : return VSIStrdup(serializer.GetString().c_str());
2222 : }
2223 : }
2224 : }
2225 0 : catch (const std::exception &e)
2226 : {
2227 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
2228 0 : return nullptr;
2229 : }
2230 : }
2231 :
2232 : /************************************************************************/
2233 : /* GDALMultiDimInfoOptionsNew() */
2234 : /************************************************************************/
2235 :
2236 : /**
2237 : * Allocates a GDALMultiDimInfo struct.
2238 : *
2239 : * @param papszArgv NULL terminated list of options (potentially including
2240 : * filename and open options too), or NULL. The accepted options are the ones of
2241 : * the <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a> utility.
2242 : * @param psOptionsForBinary should be nullptr, unless called from
2243 : * gdalmultidiminfo_bin.cpp
2244 : * @return pointer to the allocated GDALMultiDimInfoOptions struct. Must be
2245 : * freed with GDALMultiDimInfoOptionsFree().
2246 : *
2247 : * @since GDAL 3.1
2248 : */
2249 :
2250 : GDALMultiDimInfoOptions *
2251 50 : GDALMultiDimInfoOptionsNew(char **papszArgv,
2252 : GDALMultiDimInfoOptionsForBinary *psOptionsForBinary)
2253 : {
2254 100 : auto psOptions = std::make_unique<GDALMultiDimInfoOptions>();
2255 :
2256 : /* -------------------------------------------------------------------- */
2257 : /* Parse arguments. */
2258 : /* -------------------------------------------------------------------- */
2259 :
2260 100 : CPLStringList aosArgv;
2261 :
2262 50 : if (papszArgv)
2263 : {
2264 25 : const int nArgc = CSLCount(papszArgv);
2265 90 : for (int i = 0; i < nArgc; i++)
2266 65 : aosArgv.AddString(papszArgv[i]);
2267 : }
2268 :
2269 : try
2270 : {
2271 : auto argParser = GDALMultiDimInfoAppOptionsGetParser(
2272 100 : psOptions.get(), psOptionsForBinary);
2273 50 : argParser->parse_args_without_binary_name(aosArgv);
2274 : }
2275 0 : catch (const std::exception &err)
2276 : {
2277 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
2278 0 : err.what());
2279 0 : return nullptr;
2280 : }
2281 :
2282 50 : return psOptions.release();
2283 : }
2284 :
2285 : /************************************************************************/
2286 : /* GDALMultiDimInfoOptionsFree() */
2287 : /************************************************************************/
2288 :
2289 : /**
2290 : * Frees the GDALMultiDimInfoOptions struct.
2291 : *
2292 : * @param psOptions the options struct for GDALMultiDimInfo().
2293 : *
2294 : * @since GDAL 3.1
2295 : */
2296 :
2297 49 : void GDALMultiDimInfoOptionsFree(GDALMultiDimInfoOptions *psOptions)
2298 : {
2299 49 : delete psOptions;
2300 49 : }
|