1 /******************************************************************************
2  *
3  * Project:  GDAL Utilities
4  * Purpose:  Command line application to list info about a multidimensional raster
5  * Author:   Even Rouault,<even.rouault at spatialys.com>
6  *
7  * ****************************************************************************
8  * Copyright (c) 2019, Even Rouault <even.rouault at spatialys.com>
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included
18  * in all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  ****************************************************************************/
28 
29 #include "cpl_port.h"
30 #include "gdal_utils.h"
31 #include "gdal_utils_priv.h"
32 
33 #include "cpl_json_streaming_writer.h"
34 #include "gdal_priv.h"
35 #include <limits>
36 #include <set>
37 
38 CPL_CVSID("$Id: gdalmdiminfo_lib.cpp 2e6733101e6356653e8c999cb7fe808abc22ed11 2020-08-11 14:57:51 +0200 Even Rouault $")
39 
40 /************************************************************************/
41 /*                       GDALMultiDimInfoOptions                        */
42 /************************************************************************/
43 
44 struct GDALMultiDimInfoOptions
45 {
46     bool bStdoutOutput = false;
47     bool bDetailed = false;
48     bool bPretty = true;
49     size_t nLimitValuesByDim = 0;
50     CPLStringList aosArrayOptions{};
51     std::string osArrayName{};
52     bool bStats = false;
53 };
54 
55 /************************************************************************/
56 /*                         HasUniqueNames()                             */
57 /************************************************************************/
58 
HasUniqueNames(const std::vector<std::string> & oNames)59 static bool HasUniqueNames(const std::vector<std::string>& oNames)
60 {
61     std::set<std::string> oSetNames;
62     for( const auto& subgroupName: oNames )
63     {
64         if( oSetNames.find(subgroupName) != oSetNames.end() )
65         {
66             return false;
67         }
68         oSetNames.insert(subgroupName);
69     }
70     return true;
71 }
72 
73 /************************************************************************/
74 /*                          DumpDataType()                              */
75 /************************************************************************/
76 
DumpDataType(const GDALExtendedDataType & dt,CPLJSonStreamingWriter & serializer)77 static void DumpDataType(const GDALExtendedDataType& dt,
78                          CPLJSonStreamingWriter& serializer)
79 {
80     switch( dt.GetClass() )
81     {
82         case GEDTC_STRING:
83             serializer.Add("String");
84             break;
85 
86         case GEDTC_NUMERIC:
87             serializer.Add( GDALGetDataTypeName(dt.GetNumericDataType()) );
88             break;
89 
90         case GEDTC_COMPOUND:
91         {
92             auto compoundContext(serializer.MakeObjectContext());
93             serializer.AddObjKey("name");
94             serializer.Add(dt.GetName());
95             serializer.AddObjKey("size");
96             serializer.Add(static_cast<unsigned>(dt.GetSize()));
97             serializer.AddObjKey("components");
98             const auto& components = dt.GetComponents();
99             auto componentsContext(serializer.MakeArrayContext());
100             for( const auto& comp: components )
101             {
102                 auto compContext(serializer.MakeObjectContext());
103                 serializer.AddObjKey("name");
104                 serializer.Add(comp->GetName());
105                 serializer.AddObjKey("offset");
106                 serializer.Add(static_cast<unsigned>(comp->GetOffset()));
107                 serializer.AddObjKey("type");
108                 DumpDataType(comp->GetType(), serializer);
109             }
110             break;
111         }
112     }
113 }
114 
115 /************************************************************************/
116 /*                           DumpValue()                                */
117 /************************************************************************/
118 
119 template<typename T>
DumpValue(CPLJSonStreamingWriter & serializer,const void * bytes)120 static void DumpValue(CPLJSonStreamingWriter& serializer,
121                       const void* bytes)
122 {
123     T tmp;
124     memcpy(&tmp, bytes, sizeof(T));
125     serializer.Add(tmp);
126 }
127 
128 /************************************************************************/
129 /*                         DumpComplexValue()                           */
130 /************************************************************************/
131 
132 template<typename T>
DumpComplexValue(CPLJSonStreamingWriter & serializer,const GByte * bytes)133 static void DumpComplexValue(CPLJSonStreamingWriter& serializer,
134                              const GByte* bytes)
135 {
136     auto objectContext(serializer.MakeObjectContext());
137     serializer.AddObjKey("real");
138     DumpValue<T>(serializer, bytes);
139     serializer.AddObjKey("imag");
140     DumpValue<T>(serializer, bytes + sizeof(T));
141 }
142 
143 /************************************************************************/
144 /*                           DumpValue()                                */
145 /************************************************************************/
146 
DumpValue(CPLJSonStreamingWriter & serializer,const GByte * bytes,const GDALDataType & eDT)147 static void DumpValue(CPLJSonStreamingWriter& serializer,
148                       const GByte* bytes,
149                       const GDALDataType& eDT)
150 {
151     switch(eDT)
152     {
153         case GDT_Byte:
154             DumpValue<GByte>(serializer, bytes);
155             break;
156         case GDT_Int16:
157             DumpValue<GInt16>(serializer, bytes);
158             break;
159         case GDT_UInt16:
160             DumpValue<GUInt16>(serializer, bytes);
161             break;
162         case GDT_Int32:
163             DumpValue<GInt32>(serializer, bytes);
164             break;
165         case GDT_UInt32:
166             DumpValue<GUInt32>(serializer, bytes);
167             break;
168         case GDT_Float32:
169             DumpValue<float>(serializer, bytes);
170             break;
171         case GDT_Float64:
172             DumpValue<double>(serializer, bytes);
173             break;
174         case GDT_CInt16:
175             DumpComplexValue<GInt16>(serializer, bytes);
176             break;
177         case GDT_CInt32:
178             DumpComplexValue<GInt32>(serializer, bytes);
179             break;
180         case GDT_CFloat32:
181             DumpComplexValue<float>(serializer, bytes);
182             break;
183         case GDT_CFloat64:
184             DumpComplexValue<double>(serializer, bytes);
185             break;
186         default:
187             CPLAssert(false);
188             break;
189     }
190 }
191 
192 static void DumpValue(CPLJSonStreamingWriter& serializer,
193                          const GByte* values,
194                          const GDALExtendedDataType& dt);
195 
196 /************************************************************************/
197 /*                          DumpCompound()                              */
198 /************************************************************************/
199 
DumpCompound(CPLJSonStreamingWriter & serializer,const GByte * values,const GDALExtendedDataType & dt)200 static void DumpCompound(CPLJSonStreamingWriter& serializer,
201                          const GByte* values,
202                          const GDALExtendedDataType& dt)
203 {
204     CPLAssert(dt.GetClass() == GEDTC_COMPOUND);
205     const auto& components = dt.GetComponents();
206     auto objectContext(serializer.MakeObjectContext());
207     for( const auto& comp: components )
208     {
209         serializer.AddObjKey(comp->GetName());
210         DumpValue(serializer, values + comp->GetOffset(), comp->GetType());
211     }
212 }
213 
214 /************************************************************************/
215 /*                           DumpValue()                                */
216 /************************************************************************/
217 
DumpValue(CPLJSonStreamingWriter & serializer,const GByte * values,const GDALExtendedDataType & dt)218 static void DumpValue(CPLJSonStreamingWriter& serializer,
219                          const GByte* values,
220                          const GDALExtendedDataType& dt)
221 {
222     switch( dt.GetClass() )
223     {
224         case GEDTC_NUMERIC:
225             DumpValue(serializer, values, dt.GetNumericDataType());
226             break;
227         case GEDTC_COMPOUND:
228             DumpCompound(serializer, values, dt);
229             break;
230         case GEDTC_STRING:
231         {
232             const char* pszStr;
233             // cppcheck-suppress pointerSize
234             memcpy(&pszStr, values, sizeof(const char*));
235             if( pszStr )
236                 serializer.Add(pszStr);
237             else
238                 serializer.AddNull();
239             break;
240         }
241     }
242 }
243 
244 /************************************************************************/
245 /*                          DumpAttrValue()                             */
246 /************************************************************************/
247 
DumpAttrValue(std::shared_ptr<GDALAttribute> attr,CPLJSonStreamingWriter & serializer)248 static void DumpAttrValue(std::shared_ptr<GDALAttribute> attr,
249                           CPLJSonStreamingWriter& serializer)
250 {
251     const auto dt(attr->GetDataType());
252     const size_t nEltCount(static_cast<size_t>(attr->GetTotalElementsCount()));
253     switch( dt.GetClass() )
254     {
255         case GEDTC_STRING:
256         {
257             if( nEltCount == 1 )
258             {
259                 const char* pszStr = attr->ReadAsString();
260                 if( pszStr )
261                     serializer.Add(pszStr);
262                 else
263                     serializer.AddNull();
264             }
265             else
266             {
267                 CPLStringList aosValues(attr->ReadAsStringArray());
268                 {
269                     auto arrayContextValues(serializer.MakeArrayContext(nEltCount < 10));
270                     for( int i = 0; i < aosValues.size(); ++i )
271                     {
272                         serializer.Add(aosValues[i]);
273                     }
274                 }
275             }
276             break;
277         }
278 
279         case GEDTC_NUMERIC:
280         {
281             auto eDT = dt.GetNumericDataType();
282             const auto rawValues(attr->ReadAsRaw());
283             const GByte* bytePtr = rawValues.data();
284             if( bytePtr )
285             {
286                 const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
287                 if( nEltCount == 1 )
288                 {
289                     serializer.SetNewline(false);
290                     DumpValue(serializer, rawValues.data(), eDT);
291                     serializer.SetNewline(true);
292                 }
293                 else
294                 {
295                     auto arrayContextValues(serializer.MakeArrayContext(nEltCount < 10));
296                     for( size_t i = 0; i < nEltCount; i++ )
297                     {
298                         DumpValue(serializer, bytePtr, eDT);
299                         bytePtr += nDTSize;
300                     }
301                 }
302             }
303             else
304             {
305                 serializer.AddNull();
306             }
307             break;
308         }
309 
310         case GEDTC_COMPOUND:
311         {
312             auto rawValues(attr->ReadAsRaw());
313             const GByte* bytePtr = rawValues.data();
314             if( bytePtr )
315             {
316                 if( nEltCount == 1 )
317                 {
318                     serializer.SetNewline(false);
319                     DumpCompound(serializer, bytePtr, dt);
320                     serializer.SetNewline(true);
321                 }
322                 else
323                 {
324                     auto arrayContextValues(serializer.MakeArrayContext());
325                     for( size_t i = 0; i < nEltCount; i++ )
326                     {
327                         DumpCompound(serializer, bytePtr, dt);
328                         bytePtr += dt.GetSize();
329                     }
330                 }
331             }
332             else
333             {
334                 serializer.AddNull();
335             }
336             break;
337         }
338     }
339 }
340 
341 /************************************************************************/
342 /*                              DumpAttr()                              */
343 /************************************************************************/
344 
DumpAttr(std::shared_ptr<GDALAttribute> attr,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions * psOptions,bool bOutputObjType,bool bOutputName)345 static void DumpAttr(std::shared_ptr<GDALAttribute> attr,
346                      CPLJSonStreamingWriter& serializer,
347                      const GDALMultiDimInfoOptions *psOptions,
348                      bool bOutputObjType, bool bOutputName)
349 {
350     if( !bOutputObjType && !bOutputName && !psOptions->bDetailed )
351     {
352         DumpAttrValue(attr, serializer);
353         return;
354     }
355 
356     const auto dt(attr->GetDataType());
357     auto objectContext(serializer.MakeObjectContext());
358     if( bOutputObjType )
359     {
360         serializer.AddObjKey("type");
361         serializer.Add("attribute");
362     }
363     if( bOutputName )
364     {
365         serializer.AddObjKey("name");
366         serializer.Add(attr->GetName());
367     }
368 
369     if( psOptions->bDetailed )
370     {
371         serializer.AddObjKey("datatype");
372         DumpDataType(dt, serializer);
373 
374         serializer.AddObjKey("value");
375     }
376 
377     DumpAttrValue(attr, serializer);
378 }
379 
380 /************************************************************************/
381 /*                              DumpAttrs()                             */
382 /************************************************************************/
383 
DumpAttrs(const std::vector<std::shared_ptr<GDALAttribute>> & attrs,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions * psOptions)384 static void DumpAttrs(const std::vector<std::shared_ptr<GDALAttribute>>& attrs,
385                       CPLJSonStreamingWriter& serializer,
386                       const GDALMultiDimInfoOptions *psOptions)
387 {
388     std::vector<std::string> attributeNames;
389     for( const auto& poAttr: attrs )
390         attributeNames.emplace_back(poAttr->GetName());
391     if( HasUniqueNames(attributeNames) )
392     {
393         auto objectContext(serializer.MakeObjectContext());
394         for( const auto& poAttr: attrs )
395         {
396             serializer.AddObjKey(poAttr->GetName());
397             DumpAttr( poAttr, serializer, psOptions, false, false );
398         }
399     }
400     else
401     {
402         auto arrayContext(serializer.MakeArrayContext());
403         for( const auto& poAttr: attrs )
404         {
405             DumpAttr( poAttr, serializer, psOptions, false, true );
406         }
407     }
408 }
409 
410 /************************************************************************/
411 /*                            DumpArrayRec()                            */
412 /************************************************************************/
413 
DumpArrayRec(std::shared_ptr<GDALMDArray> array,CPLJSonStreamingWriter & serializer,size_t nCurDim,const std::vector<GUInt64> & dimSizes,std::vector<GUInt64> & startIdx,const GDALMultiDimInfoOptions * psOptions)414 static void DumpArrayRec(std::shared_ptr<GDALMDArray> array,
415                          CPLJSonStreamingWriter& serializer,
416                          size_t nCurDim,
417                          const std::vector<GUInt64>& dimSizes,
418                          std::vector<GUInt64>& startIdx,
419                          const GDALMultiDimInfoOptions *psOptions)
420 {
421     do
422     {
423         auto arrayContext(serializer.MakeArrayContext());
424         if( nCurDim + 1 == dimSizes.size() )
425         {
426             const auto dt(array->GetDataType());
427             const auto nDTSize(dt.GetSize());
428             const auto lambdaDumpValue = [&serializer, &dt, nDTSize](std::vector<GByte>& abyTmp, size_t nCount)
429             {
430                 GByte* pabyPtr = &abyTmp[0];
431                 for( size_t i = 0; i < nCount; ++i )
432                 {
433                     DumpValue(serializer, pabyPtr, dt);
434                     dt.FreeDynamicMemory(pabyPtr);
435                     pabyPtr += nDTSize;
436                 }
437             };
438 
439             serializer.SetNewline(false);
440             std::vector<size_t> count(dimSizes.size(), 1);
441             if( psOptions->nLimitValuesByDim == 0 ||
442                 dimSizes.back() <= psOptions->nLimitValuesByDim )
443             {
444                 const size_t nCount = static_cast<size_t>(dimSizes.back());
445                 if( nCount > 0 )
446                 {
447                     if( nCount != dimSizes.back() ||
448                         nDTSize > std::numeric_limits<size_t>::max() / nCount )
449                     {
450                         serializer.Add("[too many values]");
451                         break;
452                     }
453                     std::vector<GByte> abyTmp(nDTSize * nCount);
454                     count.back() = nCount;
455                     if( !array->Read(startIdx.data(), count.data(), nullptr, nullptr, dt, &abyTmp[0]) )
456                         break;
457                     lambdaDumpValue(abyTmp, count.back());
458                 }
459             }
460             else
461             {
462                 std::vector<GByte> abyTmp(nDTSize * (psOptions->nLimitValuesByDim + 1) / 2);
463                 startIdx.back() = 0;
464                 size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
465                 count.back() = nStartCount;
466                 if( !array->Read(startIdx.data(), count.data(), nullptr, nullptr, dt, &abyTmp[0]) )
467                     break;
468                 lambdaDumpValue(abyTmp, count.back());
469                 serializer.Add("[...]");
470 
471                 count.back() = psOptions->nLimitValuesByDim / 2;
472                 if( count.back() )
473                 {
474                     startIdx.back() = dimSizes.back() - count.back();
475                     if( !array->Read(startIdx.data(), count.data(), nullptr, nullptr, dt, &abyTmp[0]) )
476                         break;
477                     lambdaDumpValue(abyTmp, count.back());
478                 }
479             }
480         }
481         else
482         {
483             if( psOptions->nLimitValuesByDim == 0 ||
484                 dimSizes[nCurDim] <= psOptions->nLimitValuesByDim )
485             {
486                 for( startIdx[nCurDim] = 0;
487                      startIdx[nCurDim] < dimSizes[nCurDim];
488                      ++startIdx[nCurDim] )
489                 {
490                     DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
491                                 startIdx, psOptions);
492                 }
493             }
494             else
495             {
496                 size_t nStartCount = (psOptions->nLimitValuesByDim + 1) / 2;
497                 for( startIdx[nCurDim] = 0;
498                      startIdx[nCurDim] < nStartCount;
499                      ++startIdx[nCurDim] )
500                 {
501                     DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
502                                 startIdx, psOptions);
503                 }
504                 serializer.Add("[...]");
505                 size_t nEndCount = psOptions->nLimitValuesByDim / 2;
506                 for( startIdx[nCurDim] = dimSizes[nCurDim] - nEndCount;
507                     startIdx[nCurDim] < dimSizes[nCurDim];
508                     ++startIdx[nCurDim] )
509                 {
510                     DumpArrayRec(array, serializer, nCurDim + 1, dimSizes,
511                                 startIdx, psOptions);
512                 }
513             }
514         }
515     } while(false);
516     serializer.SetNewline(true);
517 }
518 
519 /************************************************************************/
520 /*                        DumpDimensions()                               */
521 /************************************************************************/
522 
DumpDimensions(const std::vector<std::shared_ptr<GDALDimension>> & dims,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions *,std::set<std::string> & alreadyDumpedDimensions)523 static void DumpDimensions(const std::vector<std::shared_ptr<GDALDimension>>& dims,
524                            CPLJSonStreamingWriter& serializer,
525                            const GDALMultiDimInfoOptions *,
526                            std::set<std::string>& alreadyDumpedDimensions)
527 {
528     auto arrayContext(serializer.MakeArrayContext());
529     for( const auto& dim: dims )
530     {
531         const std::string osFullname(dim->GetFullName());
532         if( alreadyDumpedDimensions.find(osFullname) !=
533                 alreadyDumpedDimensions.end() )
534         {
535             serializer.Add(osFullname);
536             continue;
537         }
538 
539         auto dimObjectContext(serializer.MakeObjectContext());
540         if( !osFullname.empty() && osFullname[0] == '/' )
541             alreadyDumpedDimensions.insert(osFullname);
542 
543         serializer.AddObjKey("name");
544         serializer.Add(dim->GetName());
545 
546         serializer.AddObjKey("full_name");
547         serializer.Add(osFullname);
548 
549         serializer.AddObjKey("size");
550         serializer.Add(dim->GetSize());
551 
552         const auto& type(dim->GetType());
553         if( !type.empty() )
554         {
555             serializer.AddObjKey("type");
556             serializer.Add(type);
557         }
558 
559         const auto& direction(dim->GetDirection());
560         if( !direction.empty() )
561         {
562             serializer.AddObjKey("direction");
563             serializer.Add(direction);
564         }
565 
566         auto poIndexingVariable(dim->GetIndexingVariable());
567         if( poIndexingVariable )
568         {
569             serializer.AddObjKey("indexing_variable");
570             serializer.Add(poIndexingVariable->GetFullName());
571         }
572     }
573 }
574 
575 /************************************************************************/
576 /*                        DumpStructuralInfo()                          */
577 /************************************************************************/
578 
DumpStructuralInfo(CSLConstList papszStructuralInfo,CPLJSonStreamingWriter & serializer)579 static void DumpStructuralInfo(CSLConstList papszStructuralInfo,
580                                CPLJSonStreamingWriter& serializer)
581 {
582     auto objectContext(serializer.MakeObjectContext());
583     for(int i = 0; papszStructuralInfo && papszStructuralInfo[i]; ++i )
584     {
585         char* pszKey = nullptr;
586         const char* pszValue = CPLParseNameValue(papszStructuralInfo[i], &pszKey);
587         if( pszKey )
588         {
589             serializer.AddObjKey(pszKey);
590         }
591         else
592         {
593             serializer.AddObjKey(CPLSPrintf("metadata_%d", i+1));
594         }
595         serializer.Add(pszValue);
596         CPLFree(pszKey);
597     }
598 }
599 
600 /************************************************************************/
601 /*                             DumpArray()                              */
602 /************************************************************************/
603 
DumpArray(GDALDataset * poDS,std::shared_ptr<GDALMDArray> array,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions * psOptions,std::set<std::string> & alreadyDumpedDimensions,bool bOutputObjType,bool bOutputName)604 static void DumpArray(GDALDataset* poDS,
605                       std::shared_ptr<GDALMDArray> array,
606                      CPLJSonStreamingWriter& serializer,
607                      const GDALMultiDimInfoOptions *psOptions,
608                      std::set<std::string>& alreadyDumpedDimensions,
609                      bool bOutputObjType, bool bOutputName)
610 {
611     auto objectContext(serializer.MakeObjectContext());
612     if( bOutputObjType )
613     {
614         serializer.AddObjKey("type");
615         serializer.Add("array");
616     }
617     if( bOutputName )
618     {
619         serializer.AddObjKey("name");
620         serializer.Add(array->GetName());
621     }
622 
623     serializer.AddObjKey("datatype");
624     const auto dt(array->GetDataType());
625     DumpDataType(dt, serializer);
626 
627     auto dims = array->GetDimensions();
628     if( !dims.empty() )
629     {
630         serializer.AddObjKey("dimensions");
631         DumpDimensions(dims, serializer, psOptions, alreadyDumpedDimensions);
632     }
633 
634     CPLStringList aosOptions;
635     if( psOptions->bDetailed )
636         aosOptions.SetNameValue("SHOW_ALL", "YES");
637     auto attrs = array->GetAttributes(aosOptions.List());
638     if( !attrs.empty() )
639     {
640         serializer.AddObjKey("attributes");
641         DumpAttrs(attrs, serializer, psOptions);
642     }
643 
644     auto unit = array->GetUnit();
645     if( !unit.empty() )
646     {
647         serializer.AddObjKey("unit");
648         serializer.Add(unit);
649     }
650 
651     auto nodata = array->GetRawNoDataValue();
652     if( nodata )
653     {
654         serializer.AddObjKey("nodata_value");
655         DumpValue(serializer, static_cast<const GByte*>(nodata), dt);
656     }
657 
658     bool bValid = false;
659     double dfOffset = array->GetOffset(&bValid);
660     if( bValid )
661     {
662         serializer.AddObjKey("offset");
663         serializer.Add(dfOffset);
664     }
665     double dfScale = array->GetScale(&bValid);
666     if( bValid )
667     {
668         serializer.AddObjKey("scale");
669         serializer.Add(dfScale);
670     }
671 
672     auto srs = array->GetSpatialRef();
673     if( srs )
674     {
675         char* pszWKT = nullptr;
676         CPLStringList wktOptions;
677         wktOptions.SetNameValue("FORMAT", "WKT2_2018");
678         if( srs->exportToWkt(&pszWKT, wktOptions.List()) == OGRERR_NONE )
679         {
680             serializer.AddObjKey("srs");
681             {
682                 auto srsContext(serializer.MakeObjectContext());
683                 serializer.AddObjKey("wkt");
684                 serializer.Add(pszWKT);
685                 serializer.AddObjKey("data_axis_to_srs_axis_mapping");
686                 {
687                     auto dataAxisContext(serializer.MakeArrayContext(true));
688                     auto mapping = srs->GetDataAxisToSRSAxisMapping();
689                     for( const auto& axisNumber: mapping )
690                         serializer.Add(axisNumber);
691                 }
692             }
693         }
694         CPLFree(pszWKT);
695     }
696 
697     auto papszStructuralInfo = array->GetStructuralInfo();
698     if( papszStructuralInfo )
699     {
700         serializer.AddObjKey("structural_info");
701         DumpStructuralInfo(papszStructuralInfo, serializer);
702     }
703 
704     if( psOptions->bDetailed )
705     {
706         serializer.AddObjKey("values");
707         if( dims.empty() )
708         {
709             std::vector<GByte> abyTmp(dt.GetSize());
710             array->Read(nullptr, nullptr, nullptr, nullptr, dt, &abyTmp[0]);
711             DumpValue(serializer, &abyTmp[0], dt);
712         }
713         else
714         {
715             std::vector<GUInt64> startIdx(dims.size());
716             std::vector<GUInt64> dimSizes;
717             for( const auto& dim: dims )
718                 dimSizes.emplace_back(dim->GetSize());
719             DumpArrayRec(array, serializer, 0, dimSizes, startIdx, psOptions);
720         }
721     }
722 
723     if( psOptions->bStats )
724     {
725         double dfMin = 0.0;
726         double dfMax = 0.0;
727         double dfMean = 0.0;
728         double dfStdDev = 0.0;
729         GUInt64 nValidCount = 0;
730         if( array->GetStatistics( poDS, false, true,
731                               &dfMin, &dfMax, &dfMean, &dfStdDev,
732                               &nValidCount,
733                               nullptr, nullptr ) == CE_None )
734         {
735             serializer.AddObjKey("statistics");
736             auto statContext(serializer.MakeObjectContext());
737             if( nValidCount > 0 )
738             {
739                 serializer.AddObjKey("min");
740                 serializer.Add(dfMin);
741 
742                 serializer.AddObjKey("max");
743                 serializer.Add(dfMax);
744 
745                 serializer.AddObjKey("mean");
746                 serializer.Add(dfMean);
747 
748                 serializer.AddObjKey("stddev");
749                 serializer.Add(dfStdDev);
750             }
751 
752             serializer.AddObjKey("valid_sample_count");
753             serializer.Add(nValidCount);
754         }
755     }
756 }
757 
758 /************************************************************************/
759 /*                            DumpArrays()                              */
760 /************************************************************************/
761 
DumpArrays(GDALDataset * poDS,std::shared_ptr<GDALGroup> group,const std::vector<std::string> & arrayNames,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions * psOptions,std::set<std::string> & alreadyDumpedDimensions)762 static void DumpArrays(GDALDataset* poDS,
763                        std::shared_ptr<GDALGroup> group,
764                        const std::vector<std::string>& arrayNames,
765                        CPLJSonStreamingWriter& serializer,
766                        const GDALMultiDimInfoOptions *psOptions,
767                        std::set<std::string>& alreadyDumpedDimensions)
768 {
769     std::set<std::string> oSetNames;
770     auto objectContext(serializer.MakeObjectContext());
771     for( const auto& name: arrayNames )
772     {
773         if( oSetNames.find(name) != oSetNames.end() )
774             continue; // should not happen on well behaved drivers
775         oSetNames.insert(name);
776         auto array = group->OpenMDArray(name);
777         if( array )
778         {
779             serializer.AddObjKey(array->GetName());
780             DumpArray( poDS, array, serializer, psOptions,
781                        alreadyDumpedDimensions, false, false );
782         }
783     }
784 }
785 
786 /************************************************************************/
787 /*                             DumpGroup()                              */
788 /************************************************************************/
789 
DumpGroup(GDALDataset * poDS,std::shared_ptr<GDALGroup> group,const char * pszDriverName,CPLJSonStreamingWriter & serializer,const GDALMultiDimInfoOptions * psOptions,std::set<std::string> & alreadyDumpedDimensions,bool bOutputObjType,bool bOutputName)790 static void DumpGroup(GDALDataset* poDS,
791                       std::shared_ptr<GDALGroup> group,
792                       const char* pszDriverName,
793                       CPLJSonStreamingWriter& serializer,
794                       const GDALMultiDimInfoOptions *psOptions,
795                       std::set<std::string>& alreadyDumpedDimensions,
796                       bool bOutputObjType, bool bOutputName)
797 {
798     auto objectContext(serializer.MakeObjectContext());
799     if( bOutputObjType )
800     {
801         serializer.AddObjKey("type");
802         serializer.Add("group");
803     }
804     if( pszDriverName )
805     {
806         serializer.AddObjKey("driver");
807         serializer.Add(pszDriverName);
808     }
809     if( bOutputName )
810     {
811         serializer.AddObjKey("name");
812         serializer.Add(group->GetName());
813     }
814 
815     CPLStringList aosOptionsGetAttr;
816     if( psOptions->bDetailed )
817         aosOptionsGetAttr.SetNameValue("SHOW_ALL", "YES");
818     auto attrs = group->GetAttributes(aosOptionsGetAttr.List());
819     if( !attrs.empty() )
820     {
821         serializer.AddObjKey("attributes");
822         DumpAttrs(attrs, serializer, psOptions);
823     }
824 
825     auto dims = group->GetDimensions();
826     if( !dims.empty() )
827     {
828         serializer.AddObjKey("dimensions");
829         DumpDimensions(dims, serializer, psOptions, alreadyDumpedDimensions);
830     }
831 
832     CPLStringList aosOptionsGetArray(psOptions->aosArrayOptions);
833     if( psOptions->bDetailed )
834         aosOptionsGetArray.SetNameValue("SHOW_ALL", "YES");
835     auto arrayNames = group->GetMDArrayNames(aosOptionsGetArray.List());
836     if( !arrayNames.empty() )
837     {
838         serializer.AddObjKey("arrays");
839         DumpArrays(poDS, group, arrayNames, serializer, psOptions,
840                    alreadyDumpedDimensions);
841     }
842 
843     auto papszStructuralInfo = group->GetStructuralInfo();
844     if( papszStructuralInfo )
845     {
846         serializer.AddObjKey("structural_info");
847         DumpStructuralInfo(papszStructuralInfo, serializer);
848     }
849 
850     auto subgroupNames = group->GetGroupNames();
851     if( !subgroupNames.empty() )
852     {
853         serializer.AddObjKey("groups");
854         if( HasUniqueNames(subgroupNames) )
855         {
856             auto groupContext(serializer.MakeObjectContext());
857             for( const auto& subgroupName: subgroupNames )
858             {
859                 auto subgroup = group->OpenGroup(subgroupName);
860                 if( subgroup )
861                 {
862                     serializer.AddObjKey(subgroupName);
863                     DumpGroup( poDS, subgroup, nullptr, serializer, psOptions,
864                                alreadyDumpedDimensions, false, false );
865                 }
866             }
867         }
868         else
869         {
870             auto arrayContext(serializer.MakeArrayContext());
871             for( const auto& subgroupName: subgroupNames )
872             {
873                 auto subgroup = group->OpenGroup(subgroupName);
874                 if( subgroup )
875                 {
876                     DumpGroup( poDS, subgroup, nullptr, serializer, psOptions,
877                                alreadyDumpedDimensions, false, true );
878                 }
879             }
880         }
881     }
882 }
883 
884 /************************************************************************/
885 /*                           WriteToStdout()                            */
886 /************************************************************************/
887 
WriteToStdout(const char * pszText,void *)888 static void WriteToStdout(const char* pszText, void*)
889 {
890     printf("%s", pszText);
891 }
892 
893 /************************************************************************/
894 /*                         GDALMultiDimInfo()                           */
895 /************************************************************************/
896 
897 /**
898  * Lists various information about a GDAL multidimensional dataset.
899  *
900  * This is the equivalent of the <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a>utility.
901  *
902  * GDALMultiDimInfoOptions* must be allocated and freed with GDALMultiDimInfoOptionsNew()
903  * and GDALMultiDimInfoOptionsFree() respectively.
904  *
905  * @param hDataset the dataset handle.
906  * @param psOptionsIn the options structure returned by GDALMultiDimInfoOptionsNew() or NULL.
907  * @return string corresponding to the information about the raster dataset (must be freed with CPLFree()), or NULL in case of error.
908  *
909  * @since GDAL 3.1
910  */
911 
GDALMultiDimInfo(GDALDatasetH hDataset,const GDALMultiDimInfoOptions * psOptionsIn)912 char *GDALMultiDimInfo( GDALDatasetH hDataset,
913                         const GDALMultiDimInfoOptions *psOptionsIn )
914 {
915     if( hDataset == nullptr )
916         return nullptr;
917 
918     GDALMultiDimInfoOptions oOptionsDefault;
919     const GDALMultiDimInfoOptions* psOptions = psOptionsIn ? psOptionsIn : &oOptionsDefault;
920     CPLJSonStreamingWriter serializer(
921         psOptions->bStdoutOutput ? WriteToStdout : nullptr ,
922         nullptr);
923     serializer.SetPrettyFormatting(psOptions->bPretty);
924     GDALDataset* poDS = GDALDataset::FromHandle(hDataset);
925     auto group = poDS->GetRootGroup();
926     if( !group )
927         return nullptr;
928 
929     std::set<std::string> alreadyDumpedDimensions;
930     try
931     {
932         if( psOptions->osArrayName.empty() )
933         {
934             const char* pszDriverName = nullptr;
935             auto poDriver = poDS->GetDriver();
936             if( poDriver )
937                 pszDriverName = poDriver->GetDescription();
938             DumpGroup(poDS, group, pszDriverName, serializer, psOptions,
939                       alreadyDumpedDimensions, true, true);
940         }
941         else
942         {
943             auto curGroup = group;
944             CPLStringList aosTokens(CSLTokenizeString2(
945                 psOptions->osArrayName.c_str(), "/", 0));
946             for( int i = 0; i < aosTokens.size() - 1; i++ )
947             {
948                 curGroup = curGroup->OpenGroup(aosTokens[i]);
949                 if( !curGroup )
950                 {
951                     CPLError(CE_Failure, CPLE_AppDefined,
952                              "Cannot find group %s", aosTokens[i]);
953                     return nullptr;
954                 }
955             }
956             const char* pszArrayName = aosTokens[aosTokens.size()-1];
957             auto array(curGroup->OpenMDArray(pszArrayName));
958             if( !array )
959             {
960                 CPLError(CE_Failure, CPLE_AppDefined,
961                          "Cannot find array %s", pszArrayName);
962                 return nullptr;
963             }
964             DumpArray(poDS, array, serializer, psOptions,
965                       alreadyDumpedDimensions, true, true);
966         }
967     }
968     catch( const std::exception& e)
969     {
970         CPLError(CE_Failure, CPLE_AppDefined, "%s", e.what());
971         return nullptr;
972     }
973 
974     if( psOptions->bStdoutOutput)
975     {
976         printf("\n");
977     }
978     else
979     {
980         return VSIStrdup(serializer.GetString().c_str());
981     }
982     return nullptr;
983 }
984 
985 /************************************************************************/
986 /*                       GDALMultiDimInfoOptionsNew()                   */
987 /************************************************************************/
988 
989 /**
990  * Allocates a GDALMultiDimInfo struct.
991  *
992  * @param papszArgv NULL terminated list of options (potentially including filename and open options too), or NULL.
993  *                  The accepted options are the ones of the <a href="/programs/gdalmdiminfo.html">gdalmdiminfo</a> utility.
994  * @param psOptionsForBinary (output) may be NULL (and should generally be NULL),
995  *                           otherwise (gdalmultidiminfo_bin.cpp use case) must be allocated with
996  *                           GDALMultiDimInfoOptionsForBinaryNew() prior to this function. Will be
997  *                           filled with potentially present filename, open options, subdataset number...
998  * @return pointer to the allocated GDALMultiDimInfoOptions struct. Must be freed with GDALMultiDimInfoOptionsFree().
999  *
1000  * @since GDAL 3.1
1001  */
1002 
GDALMultiDimInfoOptionsNew(char ** papszArgv,GDALMultiDimInfoOptionsForBinary * psOptionsForBinary)1003 GDALMultiDimInfoOptions *GDALMultiDimInfoOptionsNew(
1004     char** papszArgv,
1005     GDALMultiDimInfoOptionsForBinary* psOptionsForBinary )
1006 {
1007     bool bGotFilename = false;
1008     GDALMultiDimInfoOptions *psOptions = new GDALMultiDimInfoOptions;
1009 
1010 /* -------------------------------------------------------------------- */
1011 /*      Parse arguments.                                                */
1012 /* -------------------------------------------------------------------- */
1013     for( int i = 0; papszArgv != nullptr && papszArgv[i] != nullptr; i++ )
1014     {
1015         if( EQUAL(papszArgv[i], "-oo") && papszArgv[i+1] != nullptr )
1016         {
1017             i++;
1018             if( psOptionsForBinary )
1019             {
1020                 psOptionsForBinary->papszOpenOptions = CSLAddString(
1021                      psOptionsForBinary->papszOpenOptions, papszArgv[i] );
1022             }
1023         }
1024         /* Not documented: used by gdalinfo_bin.cpp only */
1025         else if( EQUAL(papszArgv[i], "-stdout") )
1026             psOptions->bStdoutOutput = true;
1027         else if( EQUAL(papszArgv[i], "-detailed") )
1028             psOptions->bDetailed = true;
1029         else if( EQUAL(papszArgv[i], "-nopretty") )
1030             psOptions->bPretty = false;
1031         else if( EQUAL(papszArgv[i], "-array") && papszArgv[i+1] != nullptr )
1032         {
1033             ++i;
1034             psOptions->osArrayName = papszArgv[i];
1035         }
1036         else if( EQUAL(papszArgv[i], "-arrayoption") && papszArgv[i+1] != nullptr )
1037         {
1038             ++i;
1039             psOptions->aosArrayOptions.AddString(papszArgv[i]);
1040         }
1041         else if( EQUAL(papszArgv[i], "-limit") && papszArgv[i+1] != nullptr )
1042         {
1043             ++i;
1044             psOptions->nLimitValuesByDim = atoi(papszArgv[i]);
1045         }
1046         else if( EQUAL(papszArgv[i], "-stats") )
1047         {
1048             psOptions->bStats = true;
1049         }
1050         else if( papszArgv[i][0] == '-' )
1051         {
1052             CPLError(CE_Failure, CPLE_NotSupported,
1053                      "Unknown option name '%s'", papszArgv[i]);
1054             GDALMultiDimInfoOptionsFree(psOptions);
1055             return nullptr;
1056         }
1057         else if( !bGotFilename )
1058         {
1059             bGotFilename = true;
1060             if( psOptionsForBinary )
1061                 psOptionsForBinary->pszFilename = CPLStrdup(papszArgv[i]);
1062         }
1063         else
1064         {
1065             CPLError(CE_Failure, CPLE_NotSupported,
1066                      "Too many command options '%s'", papszArgv[i]);
1067             GDALMultiDimInfoOptionsFree(psOptions);
1068             return nullptr;
1069         }
1070     }
1071 
1072     return psOptions;
1073 }
1074 
1075 /************************************************************************/
1076 /*                         GDALMultiDimInfoOptionsFree()                */
1077 /************************************************************************/
1078 
1079 /**
1080  * Frees the GDALMultiDimInfoOptions struct.
1081  *
1082  * @param psOptions the options struct for GDALMultiDimInfo().
1083  *
1084  * @since GDAL 3.1
1085  */
1086 
GDALMultiDimInfoOptionsFree(GDALMultiDimInfoOptions * psOptions)1087 void GDALMultiDimInfoOptionsFree( GDALMultiDimInfoOptions *psOptions )
1088 {
1089     delete psOptions;
1090 }
1091 
1092