Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Utilities
4 : * Purpose: Command line application to list info about a file.
5 : * Author: Frank Warmerdam, warmerdam@pobox.com
6 : *
7 : * ****************************************************************************
8 : * Copyright (c) 1998, Frank Warmerdam
9 : * Copyright (c) 2007-2015, Even Rouault <even.rouault at spatialys.com>
10 : * Copyright (c) 2015, Faza Mahamood
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "gdal_utils.h"
17 : #include "gdal_utils_priv.h"
18 : #include "gdalargumentparser.h"
19 :
20 : #include <cmath>
21 : #include <limits>
22 : #include <stdarg.h>
23 : #include <stdio.h>
24 : #include <stdlib.h>
25 : #include <string.h>
26 : #include <new>
27 : #include <string>
28 : #include <vector>
29 :
30 : #include "commonutils.h"
31 : #include "cpl_conv.h"
32 : #include "cpl_error.h"
33 : #include "cpl_json_header.h"
34 : #include "cpl_minixml.h"
35 : #include "cpl_progress.h"
36 : #include "cpl_string.h"
37 : #include "cpl_vsi.h"
38 : #include "gdal.h"
39 : #include "gdal_alg.h"
40 : #include "gdal_priv.h"
41 : #include "gdal_rat.h"
42 : #include "ogr_api.h"
43 : #include "ogr_srs_api.h"
44 : #include "ogr_spatialref.h"
45 : #include "ogrlibjsonutils.h"
46 : #include "ogrgeojsongeometry.h"
47 : #include "ogrgeojsonwriter.h"
48 :
49 : using std::vector;
50 :
51 : /*! output format */
52 : typedef enum
53 : {
54 : /*! output in text format */ GDALINFO_FORMAT_TEXT = 0,
55 : /*! output in json format */ GDALINFO_FORMAT_JSON = 1
56 : } GDALInfoFormat;
57 :
58 : /************************************************************************/
59 : /* GDALInfoOptions */
60 : /************************************************************************/
61 :
62 : /** Options for use with GDALInfo(). GDALInfoOptions* must be allocated and
63 : * freed with GDALInfoOptionsNew() and GDALInfoOptionsFree() respectively.
64 : */
65 : struct GDALInfoOptions
66 : {
67 : /*! output format */
68 : GDALInfoFormat eFormat = GDALINFO_FORMAT_TEXT;
69 :
70 : bool bComputeMinMax = false;
71 :
72 : /*! report histogram information for all bands */
73 : bool bReportHistograms = false;
74 :
75 : /*! report a PROJ.4 string corresponding to the file's coordinate system */
76 : bool bReportProj4 = false;
77 :
78 : /*! read and display image statistics. Force computation if no statistics
79 : are stored in an image */
80 : bool bStats = false;
81 :
82 : /*! read and display image statistics. Force computation if no statistics
83 : are stored in an image. However, they may be computed based on
84 : overviews or a subset of all tiles. Useful if you are in a hurry and
85 : don't want precise stats. */
86 : bool bApproxStats = true;
87 :
88 : bool bSample = false;
89 :
90 : /*! force computation of the checksum for each band in the dataset */
91 : bool bComputeChecksum = false;
92 :
93 : /*! allow or suppress printing of nodata value */
94 : bool bShowNodata = true;
95 :
96 : /*! allow or suppress printing of mask information */
97 : bool bShowMask = true;
98 :
99 : /*! allow or suppress ground control points list printing. It may be useful
100 : for datasets with huge amount of GCPs, such as L1B AVHRR or HDF4 MODIS
101 : which contain thousands of them. */
102 : bool bShowGCPs = true;
103 :
104 : /*! allow or suppress metadata printing. Some datasets may contain a lot of
105 : metadata strings. */
106 : bool bShowMetadata = true;
107 :
108 : /*! allow or suppress printing of raster attribute table */
109 : bool bShowRAT = true;
110 :
111 : /*! allow or suppress printing of color table */
112 : bool bShowColorTable = true;
113 :
114 : /*! list all metadata domains available for the dataset */
115 : bool bListMDD = false;
116 :
117 : /*! display the file list or the first file of the file list */
118 : bool bShowFileList = true;
119 :
120 : /*! report metadata for the specified domains. "all" can be used to report
121 : metadata in all domains.
122 : */
123 : CPLStringList aosExtraMDDomains{};
124 :
125 : /*! WKT format used for SRS */
126 : std::string osWKTFormat = "WKT2";
127 :
128 : bool bStdoutOutput = false;
129 : };
130 :
131 : static int GDALInfoReportCorner(const GDALInfoOptions *psOptions,
132 : GDALDatasetH hDataset,
133 : OGRCoordinateTransformationH hTransform,
134 : const char *corner_name, double x, double y,
135 : bool bJson, json_object *poCornerCoordinates,
136 : json_object *poLongLatExtentCoordinates,
137 : CPLString &osStr);
138 :
139 : static void GDALInfoReportMetadata(const GDALInfoOptions *psOptions,
140 : GDALMajorObjectH hObject, bool bIsBand,
141 : bool bJson, json_object *poMetadata,
142 : CPLString &osStr);
143 :
144 : #ifndef Concat_defined
145 : #define Concat_defined
146 : static void Concat(CPLString &osRet, bool bStdoutOutput, const char *pszFormat,
147 : ...) CPL_PRINT_FUNC_FORMAT(3, 4);
148 :
149 2551 : static void Concat(CPLString &osRet, bool bStdoutOutput, const char *pszFormat,
150 : ...)
151 : {
152 : va_list args;
153 2551 : va_start(args, pszFormat);
154 :
155 2551 : if (bStdoutOutput)
156 : {
157 1706 : vfprintf(stdout, pszFormat, args);
158 : }
159 : else
160 : {
161 : try
162 : {
163 1690 : CPLString osTarget;
164 845 : osTarget.vPrintf(pszFormat, args);
165 :
166 845 : osRet += osTarget;
167 : }
168 0 : catch (const std::bad_alloc &)
169 : {
170 0 : CPLError(CE_Failure, CPLE_OutOfMemory, "Out of memory");
171 : }
172 : }
173 :
174 2551 : va_end(args);
175 2551 : }
176 : #endif
177 :
178 : /************************************************************************/
179 : /* gdal_json_object_new_double_or_str_for_non_finite() */
180 : /************************************************************************/
181 :
182 : static json_object *
183 124 : gdal_json_object_new_double_or_str_for_non_finite(double dfVal, int nPrecision)
184 : {
185 124 : if (std::isinf(dfVal))
186 0 : return json_object_new_string(dfVal < 0 ? "-Infinity" : "Infinity");
187 124 : else if (std::isnan(dfVal))
188 0 : return json_object_new_string("NaN");
189 : else
190 124 : return json_object_new_double_with_precision(dfVal, nPrecision);
191 : }
192 :
193 : /************************************************************************/
194 : /* gdal_json_object_new_double_significant_digits() */
195 : /************************************************************************/
196 :
197 : static json_object *
198 15 : gdal_json_object_new_double_significant_digits(double dfVal,
199 : int nSignificantDigits)
200 : {
201 15 : if (std::isinf(dfVal))
202 0 : return json_object_new_string(dfVal < 0 ? "-Infinity" : "Infinity");
203 15 : else if (std::isnan(dfVal))
204 0 : return json_object_new_string("NaN");
205 : else
206 15 : return json_object_new_double_with_significant_figures(
207 15 : dfVal, nSignificantDigits);
208 : }
209 :
210 : /************************************************************************/
211 : /* GDALWarpAppOptionsGetParser() */
212 : /************************************************************************/
213 :
214 : static std::unique_ptr<GDALArgumentParser>
215 123 : GDALInfoAppOptionsGetParser(GDALInfoOptions *psOptions,
216 : GDALInfoOptionsForBinary *psOptionsForBinary)
217 : {
218 : auto argParser = std::make_unique<GDALArgumentParser>(
219 123 : "gdalinfo", /* bForBinary=*/psOptionsForBinary != nullptr);
220 :
221 123 : argParser->add_description(_("Raster dataset information utility."));
222 :
223 123 : argParser->add_epilog(
224 123 : _("For more details, consult https://gdal.org/programs/gdalinfo.html"));
225 :
226 123 : argParser->add_argument("-json")
227 123 : .flag()
228 70 : .action([psOptions](const auto &)
229 123 : { psOptions->eFormat = GDALINFO_FORMAT_JSON; })
230 123 : .help(_("Display the output in json format."));
231 :
232 123 : argParser->add_argument("-mm")
233 123 : .store_into(psOptions->bComputeMinMax)
234 : .help(_("Force computation of the actual min/max values for each band "
235 123 : "in the dataset."));
236 :
237 : {
238 123 : auto &group = argParser->add_mutually_exclusive_group();
239 123 : group.add_argument("-stats")
240 123 : .store_into(psOptions->bStats)
241 : .help(_("Read and display image statistics computing exact values "
242 123 : "if required."));
243 :
244 123 : group.add_argument("-approx_stats")
245 123 : .store_into(psOptions->bApproxStats)
246 : .help(
247 : _("Read and display image statistics computing approximated "
248 123 : "values on overviews or a subset of all tiles if required."));
249 : }
250 :
251 123 : argParser->add_argument("-hist")
252 123 : .store_into(psOptions->bReportHistograms)
253 123 : .help(_("Report histogram information for all bands."));
254 :
255 123 : argParser->add_usage_newline();
256 :
257 : argParser->add_inverted_logic_flag(
258 : "-nogcp", &psOptions->bShowGCPs,
259 123 : _("Suppress ground control points list printing."));
260 :
261 : argParser->add_inverted_logic_flag("-nomd", &psOptions->bShowMetadata,
262 123 : _("Suppress metadata printing."));
263 :
264 : argParser->add_inverted_logic_flag(
265 : "-norat", &psOptions->bShowRAT,
266 123 : _("Suppress printing of raster attribute table."));
267 :
268 : argParser->add_inverted_logic_flag("-noct", &psOptions->bShowColorTable,
269 123 : _("Suppress printing of color table."));
270 :
271 : argParser->add_inverted_logic_flag("-nofl", &psOptions->bShowFileList,
272 123 : _("Suppress display of the file list."));
273 :
274 : argParser->add_inverted_logic_flag(
275 : "-nonodata", &psOptions->bShowNodata,
276 123 : _("Suppress nodata printing (implies -nomask)."));
277 :
278 : argParser->add_inverted_logic_flag("-nomask", &psOptions->bShowMask,
279 123 : _("Suppress mask printing."));
280 :
281 123 : argParser->add_usage_newline();
282 :
283 123 : argParser->add_argument("-checksum")
284 123 : .flag()
285 123 : .store_into(psOptions->bComputeChecksum)
286 : .help(_(
287 123 : "Force computation of the checksum for each band in the dataset."));
288 :
289 123 : argParser->add_argument("-listmdd")
290 123 : .flag()
291 123 : .store_into(psOptions->bListMDD)
292 123 : .help(_("List all metadata domains available for the dataset."));
293 :
294 123 : argParser->add_argument("-proj4")
295 123 : .flag()
296 123 : .store_into(psOptions->bReportProj4)
297 : .help(_("Report a PROJ.4 string corresponding to the file's coordinate "
298 123 : "system."));
299 :
300 123 : argParser->add_argument("-wkt_format")
301 246 : .metavar("<WKT1|WKT2|WKT2_2015|WKT2_2018|WKT2_2019>")
302 123 : .choices("WKT1", "WKT2", "WKT2_2015", "WKT2_2018", "WKT2_2019")
303 123 : .store_into(psOptions->osWKTFormat)
304 123 : .help(_("WKT format used for SRS."));
305 :
306 123 : if (psOptionsForBinary)
307 : {
308 59 : argParser->add_argument("-sd")
309 118 : .metavar("<n>")
310 59 : .store_into(psOptionsForBinary->nSubdataset)
311 : .help(_(
312 : "Use subdataset of specified index (starting at 1), instead of "
313 59 : "the source dataset itself."));
314 : }
315 :
316 123 : argParser->add_argument("-oo")
317 246 : .metavar("<NAME>=<VALUE>")
318 123 : .append()
319 : .action(
320 2 : [psOptionsForBinary](const std::string &s)
321 : {
322 1 : if (psOptionsForBinary)
323 1 : psOptionsForBinary->aosOpenOptions.AddString(s.c_str());
324 123 : })
325 123 : .help(_("Open option(s) for dataset."));
326 :
327 : argParser->add_input_format_argument(
328 : psOptionsForBinary ? &psOptionsForBinary->aosAllowedInputDrivers
329 123 : : nullptr);
330 :
331 123 : argParser->add_argument("-mdd")
332 246 : .metavar("<domain>|all")
333 : .action(
334 27 : [psOptions](const std::string &value)
335 : {
336 : psOptions->aosExtraMDDomains =
337 9 : CSLAddString(psOptions->aosExtraMDDomains, value.c_str());
338 123 : })
339 : .help(_("Report metadata for the specified domains. 'all' can be used "
340 123 : "to report metadata in all domains."));
341 :
342 : /* Not documented: used by gdalinfo_bin.cpp only */
343 123 : argParser->add_argument("-stdout").flag().hidden().store_into(
344 123 : psOptions->bStdoutOutput);
345 :
346 123 : if (psOptionsForBinary)
347 : {
348 59 : argParser->add_argument("dataset_name")
349 118 : .metavar("<dataset_name>")
350 59 : .store_into(psOptionsForBinary->osFilename)
351 59 : .help("Input dataset.");
352 : }
353 :
354 123 : return argParser;
355 : }
356 :
357 : /************************************************************************/
358 : /* GDALInfoAppGetParserUsage() */
359 : /************************************************************************/
360 :
361 0 : std::string GDALInfoAppGetParserUsage()
362 : {
363 : try
364 : {
365 0 : GDALInfoOptions sOptions;
366 0 : GDALInfoOptionsForBinary sOptionsForBinary;
367 : auto argParser =
368 0 : GDALInfoAppOptionsGetParser(&sOptions, &sOptionsForBinary);
369 0 : return argParser->usage();
370 : }
371 0 : catch (const std::exception &err)
372 : {
373 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unexpected exception: %s",
374 0 : err.what());
375 0 : return std::string();
376 : }
377 : }
378 :
379 : /************************************************************************/
380 : /* GDALInfo() */
381 : /************************************************************************/
382 :
383 : /**
384 : * Lists various information about a GDAL supported raster dataset.
385 : *
386 : * This is the equivalent of the <a href="/programs/gdalinfo.html">gdalinfo</a>
387 : * utility.
388 : *
389 : * GDALInfoOptions* must be allocated and freed with GDALInfoOptionsNew()
390 : * and GDALInfoOptionsFree() respectively.
391 : *
392 : * @param hDataset the dataset handle.
393 : * @param psOptions the options structure returned by GDALInfoOptionsNew() or
394 : * NULL.
395 : * @return string corresponding to the information about the raster dataset
396 : * (must be freed with CPLFree()), or NULL in case of error.
397 : *
398 : * @since GDAL 2.1
399 : */
400 :
401 118 : char *GDALInfo(GDALDatasetH hDataset, const GDALInfoOptions *psOptions)
402 : {
403 118 : if (hDataset == nullptr)
404 0 : return nullptr;
405 :
406 118 : GDALInfoOptions *psOptionsToFree = nullptr;
407 118 : if (psOptions == nullptr)
408 : {
409 0 : psOptionsToFree = GDALInfoOptionsNew(nullptr, nullptr);
410 0 : psOptions = psOptionsToFree;
411 : }
412 :
413 236 : CPLString osStr;
414 118 : json_object *poJsonObject = nullptr;
415 118 : json_object *poBands = nullptr;
416 118 : json_object *poMetadata = nullptr;
417 118 : json_object *poStac = nullptr;
418 118 : json_object *poStacRasterBands = nullptr;
419 118 : json_object *poStacEOBands = nullptr;
420 :
421 118 : const bool bJson = psOptions->eFormat == GDALINFO_FORMAT_JSON;
422 :
423 : /* -------------------------------------------------------------------- */
424 : /* Report general info. */
425 : /* -------------------------------------------------------------------- */
426 118 : GDALDriverH hDriver = GDALGetDatasetDriver(hDataset);
427 118 : if (bJson)
428 : {
429 : json_object *poDescription =
430 70 : json_object_new_string(GDALGetDescription(hDataset));
431 : json_object *poDriverShortName =
432 70 : json_object_new_string(GDALGetDriverShortName(hDriver));
433 : json_object *poDriverLongName =
434 70 : json_object_new_string(GDALGetDriverLongName(hDriver));
435 70 : poJsonObject = json_object_new_object();
436 70 : poBands = json_object_new_array();
437 70 : poMetadata = json_object_new_object();
438 70 : poStac = json_object_new_object();
439 70 : poStacRasterBands = json_object_new_array();
440 70 : poStacEOBands = json_object_new_array();
441 :
442 70 : json_object_object_add(poJsonObject, "description", poDescription);
443 70 : json_object_object_add(poJsonObject, "driverShortName",
444 : poDriverShortName);
445 70 : json_object_object_add(poJsonObject, "driverLongName",
446 : poDriverLongName);
447 : }
448 : else
449 : {
450 48 : Concat(osStr, psOptions->bStdoutOutput, "Driver: %s/%s\n",
451 : GDALGetDriverShortName(hDriver), GDALGetDriverLongName(hDriver));
452 : }
453 :
454 118 : if (psOptions->bShowFileList)
455 : {
456 : // The list of files of a raster FileGDB is not super useful and potentially
457 : // super long, so omit it, unless the -json mode is enabled
458 : char **papszFileList =
459 48 : (!bJson && EQUAL(GDALGetDriverShortName(hDriver), "OpenFileGDB"))
460 116 : ? nullptr
461 116 : : GDALGetFileList(hDataset);
462 :
463 116 : if (!papszFileList || *papszFileList == nullptr)
464 : {
465 24 : if (bJson)
466 : {
467 19 : json_object *poFiles = json_object_new_array();
468 19 : json_object_object_add(poJsonObject, "files", poFiles);
469 : }
470 : else
471 : {
472 5 : Concat(osStr, psOptions->bStdoutOutput,
473 : "Files: none associated\n");
474 24 : }
475 : }
476 : else
477 : {
478 92 : if (bJson)
479 : {
480 49 : json_object *poFiles = json_object_new_array();
481 :
482 116 : for (int i = 0; papszFileList[i] != nullptr; i++)
483 : {
484 : json_object *poFile =
485 67 : json_object_new_string(papszFileList[i]);
486 :
487 67 : json_object_array_add(poFiles, poFile);
488 : }
489 :
490 49 : json_object_object_add(poJsonObject, "files", poFiles);
491 : }
492 : else
493 : {
494 43 : Concat(osStr, psOptions->bStdoutOutput, "Files: %s\n",
495 : papszFileList[0]);
496 58 : for (int i = 1; papszFileList[i] != nullptr; i++)
497 15 : Concat(osStr, psOptions->bStdoutOutput, " %s\n",
498 15 : papszFileList[i]);
499 : }
500 : }
501 116 : CSLDestroy(papszFileList);
502 : }
503 :
504 118 : if (bJson)
505 : {
506 : {
507 70 : json_object *poSize = json_object_new_array();
508 : json_object *poSizeX =
509 70 : json_object_new_int(GDALGetRasterXSize(hDataset));
510 : json_object *poSizeY =
511 70 : json_object_new_int(GDALGetRasterYSize(hDataset));
512 :
513 : // size is X, Y ordered
514 70 : json_object_array_add(poSize, poSizeX);
515 70 : json_object_array_add(poSize, poSizeY);
516 :
517 70 : json_object_object_add(poJsonObject, "size", poSize);
518 : }
519 :
520 : {
521 70 : json_object *poStacSize = json_object_new_array();
522 : json_object *poSizeX =
523 70 : json_object_new_int(GDALGetRasterXSize(hDataset));
524 : json_object *poSizeY =
525 70 : json_object_new_int(GDALGetRasterYSize(hDataset));
526 :
527 : // ... but ... proj:shape is Y, X ordered.
528 70 : json_object_array_add(poStacSize, poSizeY);
529 70 : json_object_array_add(poStacSize, poSizeX);
530 :
531 70 : json_object_object_add(poStac, "proj:shape", poStacSize);
532 : }
533 : }
534 : else
535 : {
536 48 : Concat(osStr, psOptions->bStdoutOutput, "Size is %d, %d\n",
537 : GDALGetRasterXSize(hDataset), GDALGetRasterYSize(hDataset));
538 : }
539 :
540 236 : CPLString osWKTFormat("FORMAT=");
541 118 : osWKTFormat += psOptions->osWKTFormat;
542 118 : const char *const apszWKTOptions[] = {osWKTFormat.c_str(), "MULTILINE=YES",
543 118 : nullptr};
544 :
545 : /* -------------------------------------------------------------------- */
546 : /* Report projection. */
547 : /* -------------------------------------------------------------------- */
548 118 : auto hSRS = GDALGetSpatialRef(hDataset);
549 118 : if (hSRS != nullptr)
550 : {
551 101 : json_object *poCoordinateSystem = nullptr;
552 :
553 101 : if (bJson)
554 60 : poCoordinateSystem = json_object_new_object();
555 :
556 101 : char *pszPrettyWkt = nullptr;
557 :
558 101 : OSRExportToWktEx(hSRS, &pszPrettyWkt, apszWKTOptions);
559 :
560 101 : int nAxesCount = 0;
561 101 : const int *panAxes = OSRGetDataAxisToSRSAxisMapping(hSRS, &nAxesCount);
562 :
563 101 : const double dfCoordinateEpoch = OSRGetCoordinateEpoch(hSRS);
564 :
565 101 : if (bJson)
566 : {
567 60 : json_object *poWkt = json_object_new_string(pszPrettyWkt);
568 60 : if (psOptions->osWKTFormat == "WKT2")
569 : {
570 55 : json_object *poStacWkt = nullptr;
571 55 : json_object_deep_copy(poWkt, &poStacWkt, nullptr);
572 55 : json_object_object_add(poStac, "proj:wkt2", poStacWkt);
573 : }
574 60 : json_object_object_add(poCoordinateSystem, "wkt", poWkt);
575 :
576 60 : const char *pszAuthCode = OSRGetAuthorityCode(hSRS, nullptr);
577 60 : const char *pszAuthName = OSRGetAuthorityName(hSRS, nullptr);
578 60 : if (pszAuthCode && pszAuthName && EQUAL(pszAuthName, "EPSG"))
579 : {
580 43 : json_object *poEPSG = json_object_new_int64(atoi(pszAuthCode));
581 43 : json_object_object_add(poStac, "proj:epsg", poEPSG);
582 : }
583 : else
584 : {
585 : // Setting it to null is mandated by the
586 : // https://github.com/stac-extensions/projection#projepsg
587 : // when setting proj:projjson or proj:wkt2
588 17 : json_object_object_add(poStac, "proj:epsg", nullptr);
589 : }
590 : {
591 : // PROJJSON requires PROJ >= 6.2
592 : CPLErrorStateBackuper oCPLErrorHandlerPusher(
593 120 : CPLQuietErrorHandler);
594 60 : char *pszProjJson = nullptr;
595 : OGRErr result =
596 60 : OSRExportToPROJJSON(hSRS, &pszProjJson, nullptr);
597 60 : if (result == OGRERR_NONE)
598 : {
599 : json_object *poStacProjJson =
600 60 : json_tokener_parse(pszProjJson);
601 60 : json_object_object_add(poStac, "proj:projjson",
602 : poStacProjJson);
603 60 : CPLFree(pszProjJson);
604 : }
605 : }
606 :
607 60 : json_object *poAxisMapping = json_object_new_array();
608 182 : for (int i = 0; i < nAxesCount; i++)
609 : {
610 122 : json_object_array_add(poAxisMapping,
611 122 : json_object_new_int(panAxes[i]));
612 : }
613 60 : json_object_object_add(poCoordinateSystem,
614 : "dataAxisToSRSAxisMapping", poAxisMapping);
615 :
616 60 : if (dfCoordinateEpoch > 0)
617 : {
618 2 : json_object_object_add(
619 : poJsonObject, "coordinateEpoch",
620 : json_object_new_double(dfCoordinateEpoch));
621 : }
622 : }
623 : else
624 : {
625 41 : Concat(osStr, psOptions->bStdoutOutput,
626 : "Coordinate System is:\n%s\n", pszPrettyWkt);
627 :
628 41 : Concat(osStr, psOptions->bStdoutOutput,
629 : "Data axis to CRS axis mapping: ");
630 125 : for (int i = 0; i < nAxesCount; i++)
631 : {
632 84 : if (i > 0)
633 : {
634 43 : Concat(osStr, psOptions->bStdoutOutput, ",");
635 : }
636 84 : Concat(osStr, psOptions->bStdoutOutput, "%d", panAxes[i]);
637 : }
638 41 : Concat(osStr, psOptions->bStdoutOutput, "\n");
639 :
640 41 : if (dfCoordinateEpoch > 0)
641 : {
642 : std::string osCoordinateEpoch =
643 4 : CPLSPrintf("%f", dfCoordinateEpoch);
644 2 : const size_t nDotPos = osCoordinateEpoch.find('.');
645 2 : if (nDotPos != std::string::npos)
646 : {
647 22 : while (osCoordinateEpoch.size() > nDotPos + 2 &&
648 10 : osCoordinateEpoch.back() == '0')
649 10 : osCoordinateEpoch.pop_back();
650 : }
651 2 : Concat(osStr, psOptions->bStdoutOutput,
652 : "Coordinate epoch: %s\n", osCoordinateEpoch.c_str());
653 : }
654 : }
655 101 : CPLFree(pszPrettyWkt);
656 :
657 101 : if (psOptions->bReportProj4)
658 : {
659 2 : char *pszProj4 = nullptr;
660 2 : OSRExportToProj4(hSRS, &pszProj4);
661 :
662 2 : if (bJson)
663 : {
664 2 : json_object *proj4 = json_object_new_string(pszProj4);
665 2 : json_object_object_add(poCoordinateSystem, "proj4", proj4);
666 : }
667 : else
668 0 : Concat(osStr, psOptions->bStdoutOutput,
669 : "PROJ.4 string is:\n\'%s\'\n", pszProj4);
670 2 : CPLFree(pszProj4);
671 : }
672 :
673 101 : if (bJson)
674 60 : json_object_object_add(poJsonObject, "coordinateSystem",
675 : poCoordinateSystem);
676 : }
677 :
678 : /* -------------------------------------------------------------------- */
679 : /* Report Geotransform. */
680 : /* -------------------------------------------------------------------- */
681 118 : double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
682 118 : if (GDALGetGeoTransform(hDataset, adfGeoTransform) == CE_None)
683 : {
684 102 : if (bJson)
685 : {
686 59 : json_object *poGeoTransform = json_object_new_array();
687 : // Deep copy wasn't working on the array, for some reason, so we
688 : // build the geotransform STAC array at the same time.
689 59 : json_object *poStacGeoTransform = json_object_new_array();
690 :
691 413 : for (int i = 0; i < 6; i++)
692 : {
693 : json_object *poGeoTransformCoefficient =
694 354 : json_object_new_double_with_precision(adfGeoTransform[i],
695 : 16);
696 : json_object *poStacGeoTransformCoefficient =
697 354 : json_object_new_double_with_precision(adfGeoTransform[i],
698 : 16);
699 :
700 354 : json_object_array_add(poGeoTransform,
701 : poGeoTransformCoefficient);
702 354 : json_object_array_add(poStacGeoTransform,
703 : poStacGeoTransformCoefficient);
704 : }
705 :
706 59 : json_object_object_add(poJsonObject, "geoTransform",
707 : poGeoTransform);
708 59 : json_object_object_add(poStac, "proj:transform",
709 : poStacGeoTransform);
710 : }
711 : else
712 : {
713 43 : if (adfGeoTransform[2] == 0.0 && adfGeoTransform[4] == 0.0)
714 : {
715 41 : Concat(osStr, psOptions->bStdoutOutput,
716 : "Origin = (%.15f,%.15f)\n", adfGeoTransform[0],
717 : adfGeoTransform[3]);
718 :
719 41 : Concat(osStr, psOptions->bStdoutOutput,
720 : "Pixel Size = (%.15f,%.15f)\n", adfGeoTransform[1],
721 : adfGeoTransform[5]);
722 : }
723 : else
724 : {
725 2 : Concat(osStr, psOptions->bStdoutOutput,
726 : "GeoTransform =\n"
727 : " %.16g, %.16g, %.16g\n"
728 : " %.16g, %.16g, %.16g\n",
729 : adfGeoTransform[0], adfGeoTransform[1],
730 : adfGeoTransform[2], adfGeoTransform[3],
731 : adfGeoTransform[4], adfGeoTransform[5]);
732 : }
733 : }
734 : }
735 :
736 : /* -------------------------------------------------------------------- */
737 : /* Report GCPs. */
738 : /* -------------------------------------------------------------------- */
739 118 : if (psOptions->bShowGCPs && GDALGetGCPCount(hDataset) > 0)
740 : {
741 2 : json_object *const poGCPs = bJson ? json_object_new_object() : nullptr;
742 :
743 2 : hSRS = GDALGetGCPSpatialRef(hDataset);
744 2 : if (hSRS)
745 : {
746 2 : json_object *poGCPCoordinateSystem = nullptr;
747 :
748 2 : char *pszPrettyWkt = nullptr;
749 :
750 2 : int nAxesCount = 0;
751 : const int *panAxes =
752 2 : OSRGetDataAxisToSRSAxisMapping(hSRS, &nAxesCount);
753 :
754 2 : OSRExportToWktEx(hSRS, &pszPrettyWkt, apszWKTOptions);
755 :
756 2 : if (bJson)
757 : {
758 1 : json_object *poWkt = json_object_new_string(pszPrettyWkt);
759 1 : poGCPCoordinateSystem = json_object_new_object();
760 :
761 1 : json_object_object_add(poGCPCoordinateSystem, "wkt", poWkt);
762 :
763 1 : json_object *poAxisMapping = json_object_new_array();
764 3 : for (int i = 0; i < nAxesCount; i++)
765 : {
766 2 : json_object_array_add(poAxisMapping,
767 2 : json_object_new_int(panAxes[i]));
768 : }
769 1 : json_object_object_add(poGCPCoordinateSystem,
770 : "dataAxisToSRSAxisMapping",
771 : poAxisMapping);
772 : }
773 : else
774 : {
775 1 : Concat(osStr, psOptions->bStdoutOutput,
776 : "GCP Projection = \n%s\n", pszPrettyWkt);
777 :
778 1 : Concat(osStr, psOptions->bStdoutOutput,
779 : "Data axis to CRS axis mapping: ");
780 3 : for (int i = 0; i < nAxesCount; i++)
781 : {
782 2 : if (i > 0)
783 : {
784 1 : Concat(osStr, psOptions->bStdoutOutput, ",");
785 : }
786 2 : Concat(osStr, psOptions->bStdoutOutput, "%d", panAxes[i]);
787 : }
788 1 : Concat(osStr, psOptions->bStdoutOutput, "\n");
789 : }
790 2 : CPLFree(pszPrettyWkt);
791 :
792 2 : if (bJson)
793 1 : json_object_object_add(poGCPs, "coordinateSystem",
794 : poGCPCoordinateSystem);
795 : }
796 :
797 : json_object *const poGCPList =
798 2 : bJson ? json_object_new_array() : nullptr;
799 :
800 10 : for (int i = 0; i < GDALGetGCPCount(hDataset); i++)
801 : {
802 8 : const GDAL_GCP *psGCP = GDALGetGCPs(hDataset) + i;
803 8 : if (bJson)
804 : {
805 4 : json_object *poGCP = json_object_new_object();
806 4 : json_object *poId = json_object_new_string(psGCP->pszId);
807 4 : json_object *poInfo = json_object_new_string(psGCP->pszInfo);
808 8 : json_object *poPixel = json_object_new_double_with_precision(
809 4 : psGCP->dfGCPPixel, 15);
810 : json_object *poLine =
811 4 : json_object_new_double_with_precision(psGCP->dfGCPLine, 15);
812 : json_object *poX =
813 4 : json_object_new_double_with_precision(psGCP->dfGCPX, 15);
814 : json_object *poY =
815 4 : json_object_new_double_with_precision(psGCP->dfGCPY, 15);
816 : json_object *poZ =
817 4 : json_object_new_double_with_precision(psGCP->dfGCPZ, 15);
818 :
819 4 : json_object_object_add(poGCP, "id", poId);
820 4 : json_object_object_add(poGCP, "info", poInfo);
821 4 : json_object_object_add(poGCP, "pixel", poPixel);
822 4 : json_object_object_add(poGCP, "line", poLine);
823 4 : json_object_object_add(poGCP, "x", poX);
824 4 : json_object_object_add(poGCP, "y", poY);
825 4 : json_object_object_add(poGCP, "z", poZ);
826 4 : json_object_array_add(poGCPList, poGCP);
827 : }
828 : else
829 : {
830 4 : Concat(osStr, psOptions->bStdoutOutput,
831 : "GCP[%3d]: Id=%s, Info=%s\n"
832 : " (%.15g,%.15g) -> (%.15g,%.15g,%.15g)\n",
833 4 : i, psGCP->pszId, psGCP->pszInfo, psGCP->dfGCPPixel,
834 4 : psGCP->dfGCPLine, psGCP->dfGCPX, psGCP->dfGCPY,
835 4 : psGCP->dfGCPZ);
836 : }
837 : }
838 2 : if (bJson)
839 : {
840 1 : json_object_object_add(poGCPs, "gcpList", poGCPList);
841 1 : json_object_object_add(poJsonObject, "gcps", poGCPs);
842 : }
843 : }
844 :
845 : /* -------------------------------------------------------------------- */
846 : /* Report metadata. */
847 : /* -------------------------------------------------------------------- */
848 :
849 118 : GDALInfoReportMetadata(psOptions, hDataset, false, bJson, poMetadata,
850 : osStr);
851 118 : if (bJson)
852 : {
853 70 : if (psOptions->bShowMetadata)
854 67 : json_object_object_add(poJsonObject, "metadata", poMetadata);
855 : else
856 3 : json_object_put(poMetadata);
857 :
858 : // Include eo:cloud_cover in stac output
859 : const char *pszCloudCover =
860 70 : GDALGetMetadataItem(hDataset, "CLOUDCOVER", "IMAGERY");
861 70 : json_object *poValue = nullptr;
862 70 : if (pszCloudCover)
863 : {
864 1 : poValue = json_object_new_int(atoi(pszCloudCover));
865 1 : json_object_object_add(poStac, "eo:cloud_cover", poValue);
866 : }
867 : }
868 :
869 : /* -------------------------------------------------------------------- */
870 : /* Setup projected to lat/long transform if appropriate. */
871 : /* -------------------------------------------------------------------- */
872 118 : OGRSpatialReferenceH hProj = nullptr;
873 118 : if (GDALGetGeoTransform(hDataset, adfGeoTransform) == CE_None)
874 102 : hProj = GDALGetSpatialRef(hDataset);
875 :
876 118 : OGRCoordinateTransformationH hTransform = nullptr;
877 118 : bool bTransformToWGS84 = false;
878 :
879 118 : if (hProj)
880 : {
881 100 : OGRSpatialReferenceH hLatLong = nullptr;
882 :
883 100 : if (bJson)
884 : {
885 : // Check that it looks like Earth before trying to reproject to wgs84...
886 : // OSRGetSemiMajor() may raise an error on CRS like Engineering CRS
887 118 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
888 59 : OGRErr eErr = OGRERR_NONE;
889 117 : if (fabs(OSRGetSemiMajor(hProj, &eErr) - 6378137.0) < 10000.0 &&
890 58 : eErr == OGRERR_NONE)
891 : {
892 57 : bTransformToWGS84 = true;
893 57 : hLatLong = OSRNewSpatialReference(nullptr);
894 57 : OSRSetWellKnownGeogCS(hLatLong, "WGS84");
895 : }
896 : }
897 : else
898 : {
899 41 : hLatLong = OSRCloneGeogCS(hProj);
900 41 : if (hLatLong)
901 : {
902 : // Override GEOGCS|UNIT child to be sure to output as degrees
903 41 : OSRSetAngularUnits(hLatLong, SRS_UA_DEGREE,
904 : CPLAtof(SRS_UA_DEGREE_CONV));
905 : }
906 : }
907 :
908 100 : if (hLatLong != nullptr)
909 : {
910 98 : OSRSetAxisMappingStrategy(hLatLong, OAMS_TRADITIONAL_GIS_ORDER);
911 98 : CPLPushErrorHandler(CPLQuietErrorHandler);
912 98 : hTransform = OCTNewCoordinateTransformation(hProj, hLatLong);
913 98 : CPLPopErrorHandler();
914 :
915 98 : OSRDestroySpatialReference(hLatLong);
916 : }
917 : }
918 :
919 : /* -------------------------------------------------------------------- */
920 : /* Report corners. */
921 : /* -------------------------------------------------------------------- */
922 118 : if (bJson && GDALGetRasterXSize(hDataset))
923 : {
924 140 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
925 :
926 70 : json_object *poLinearRing = json_object_new_array();
927 70 : json_object *poCornerCoordinates = json_object_new_object();
928 70 : json_object *poLongLatExtent = json_object_new_object();
929 70 : json_object *poLongLatExtentType = json_object_new_string("Polygon");
930 70 : json_object *poLongLatExtentCoordinates = json_object_new_array();
931 :
932 70 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "upperLeft", 0.0,
933 : 0.0, bJson, poCornerCoordinates,
934 : poLongLatExtentCoordinates, osStr);
935 140 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "lowerLeft", 0.0,
936 70 : GDALGetRasterYSize(hDataset), bJson,
937 : poCornerCoordinates, poLongLatExtentCoordinates,
938 : osStr);
939 210 : GDALInfoReportCorner(
940 : psOptions, hDataset, hTransform, "lowerRight",
941 70 : GDALGetRasterXSize(hDataset), GDALGetRasterYSize(hDataset), bJson,
942 : poCornerCoordinates, poLongLatExtentCoordinates, osStr);
943 140 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "upperRight",
944 70 : GDALGetRasterXSize(hDataset), 0.0, bJson,
945 : poCornerCoordinates, poLongLatExtentCoordinates,
946 : osStr);
947 210 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "center",
948 70 : GDALGetRasterXSize(hDataset) / 2.0,
949 70 : GDALGetRasterYSize(hDataset) / 2.0, bJson,
950 : poCornerCoordinates, poLongLatExtentCoordinates,
951 : osStr);
952 70 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "upperLeft", 0.0,
953 : 0.0, bJson, poCornerCoordinates,
954 : poLongLatExtentCoordinates, osStr);
955 :
956 70 : json_object_object_add(poJsonObject, "cornerCoordinates",
957 : poCornerCoordinates);
958 70 : json_object_object_add(poLongLatExtent, "type", poLongLatExtentType);
959 70 : json_object_array_add(poLinearRing, poLongLatExtentCoordinates);
960 70 : json_object_object_add(poLongLatExtent, "coordinates", poLinearRing);
961 70 : json_object_object_add(poJsonObject,
962 : bTransformToWGS84 ? "wgs84Extent" : "extent",
963 : poLongLatExtent);
964 : }
965 48 : else if (GDALGetRasterXSize(hDataset))
966 : {
967 96 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
968 :
969 48 : Concat(osStr, psOptions->bStdoutOutput, "Corner Coordinates:\n");
970 48 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "Upper Left", 0.0,
971 : 0.0, bJson, nullptr, nullptr, osStr);
972 96 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "Lower Left", 0.0,
973 48 : GDALGetRasterYSize(hDataset), bJson, nullptr,
974 : nullptr, osStr);
975 96 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "Upper Right",
976 48 : GDALGetRasterXSize(hDataset), 0.0, bJson, nullptr,
977 : nullptr, osStr);
978 144 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "Lower Right",
979 48 : GDALGetRasterXSize(hDataset),
980 48 : GDALGetRasterYSize(hDataset), bJson, nullptr,
981 : nullptr, osStr);
982 144 : GDALInfoReportCorner(psOptions, hDataset, hTransform, "Center",
983 48 : GDALGetRasterXSize(hDataset) / 2.0,
984 48 : GDALGetRasterYSize(hDataset) / 2.0, bJson, nullptr,
985 : nullptr, osStr);
986 : }
987 :
988 118 : if (hTransform != nullptr)
989 : {
990 98 : OCTDestroyCoordinateTransformation(hTransform);
991 98 : hTransform = nullptr;
992 : }
993 :
994 : /* ==================================================================== */
995 : /* Loop over bands. */
996 : /* ==================================================================== */
997 265 : for (int iBand = 0; iBand < GDALGetRasterCount(hDataset); iBand++)
998 : {
999 147 : json_object *poBand = nullptr;
1000 147 : json_object *poBandMetadata = nullptr;
1001 147 : json_object *poStacRasterBand = nullptr;
1002 147 : json_object *poStacEOBand = nullptr;
1003 :
1004 147 : if (bJson)
1005 : {
1006 95 : poBand = json_object_new_object();
1007 95 : poBandMetadata = json_object_new_object();
1008 95 : poStacRasterBand = json_object_new_object();
1009 95 : poStacEOBand = json_object_new_object();
1010 : }
1011 :
1012 147 : GDALRasterBandH const hBand = GDALGetRasterBand(hDataset, iBand + 1);
1013 147 : const auto eDT = GDALGetRasterDataType(hBand);
1014 :
1015 147 : if (psOptions->bSample)
1016 : {
1017 0 : vector<float> ofSample(10000, 0);
1018 0 : float *const pafSample = &ofSample[0];
1019 : const int nCount =
1020 0 : GDALGetRandomRasterSample(hBand, 10000, pafSample);
1021 0 : if (!bJson)
1022 0 : Concat(osStr, psOptions->bStdoutOutput, "Got %d samples.\n",
1023 : nCount);
1024 : }
1025 :
1026 147 : int nBlockXSize = 0;
1027 147 : int nBlockYSize = 0;
1028 147 : GDALGetBlockSize(hBand, &nBlockXSize, &nBlockYSize);
1029 147 : if (bJson)
1030 : {
1031 95 : json_object *poBandNumber = json_object_new_int(iBand + 1);
1032 95 : json_object *poBlock = json_object_new_array();
1033 : json_object *poType =
1034 95 : json_object_new_string(GDALGetDataTypeName(eDT));
1035 : json_object *poColorInterp =
1036 95 : json_object_new_string(GDALGetColorInterpretationName(
1037 : GDALGetRasterColorInterpretation(hBand)));
1038 :
1039 95 : json_object_array_add(poBlock, json_object_new_int(nBlockXSize));
1040 95 : json_object_array_add(poBlock, json_object_new_int(nBlockYSize));
1041 95 : json_object_object_add(poBand, "band", poBandNumber);
1042 95 : json_object_object_add(poBand, "block", poBlock);
1043 95 : json_object_object_add(poBand, "type", poType);
1044 95 : json_object_object_add(poBand, "colorInterpretation",
1045 : poColorInterp);
1046 :
1047 95 : const char *stacDataType = nullptr;
1048 95 : switch (eDT)
1049 : {
1050 79 : case GDT_Byte:
1051 79 : stacDataType = "uint8";
1052 79 : break;
1053 0 : case GDT_Int8:
1054 0 : stacDataType = "int8";
1055 0 : break;
1056 2 : case GDT_UInt16:
1057 2 : stacDataType = "uint16";
1058 2 : break;
1059 0 : case GDT_Int16:
1060 0 : stacDataType = "int16";
1061 0 : break;
1062 0 : case GDT_UInt32:
1063 0 : stacDataType = "uint32";
1064 0 : break;
1065 1 : case GDT_Int32:
1066 1 : stacDataType = "int32";
1067 1 : break;
1068 0 : case GDT_UInt64:
1069 0 : stacDataType = "uint64";
1070 0 : break;
1071 0 : case GDT_Int64:
1072 0 : stacDataType = "int64";
1073 0 : break;
1074 0 : case GDT_Float16:
1075 0 : stacDataType = "float16";
1076 0 : break;
1077 11 : case GDT_Float32:
1078 11 : stacDataType = "float32";
1079 11 : break;
1080 2 : case GDT_Float64:
1081 2 : stacDataType = "float64";
1082 2 : break;
1083 0 : case GDT_CInt16:
1084 0 : stacDataType = "cint16";
1085 0 : break;
1086 0 : case GDT_CInt32:
1087 0 : stacDataType = "cint32";
1088 0 : break;
1089 0 : case GDT_CFloat16:
1090 0 : stacDataType = "cfloat16";
1091 0 : break;
1092 0 : case GDT_CFloat32:
1093 0 : stacDataType = "cfloat32";
1094 0 : break;
1095 0 : case GDT_CFloat64:
1096 0 : stacDataType = "cfloat64";
1097 0 : break;
1098 0 : case GDT_Unknown:
1099 : case GDT_TypeCount:
1100 0 : stacDataType = nullptr;
1101 : }
1102 95 : if (stacDataType)
1103 95 : json_object_object_add(poStacRasterBand, "data_type",
1104 : json_object_new_string(stacDataType));
1105 : }
1106 : else
1107 : {
1108 52 : Concat(osStr, psOptions->bStdoutOutput,
1109 : "Band %d Block=%dx%d Type=%s, ColorInterp=%s\n", iBand + 1,
1110 : nBlockXSize, nBlockYSize, GDALGetDataTypeName(eDT),
1111 : GDALGetColorInterpretationName(
1112 : GDALGetRasterColorInterpretation(hBand)));
1113 : }
1114 :
1115 147 : if (bJson)
1116 : {
1117 : json_object *poBandName =
1118 95 : json_object_new_string(CPLSPrintf("b%i", iBand + 1));
1119 95 : json_object_object_add(poStacEOBand, "name", poBandName);
1120 : }
1121 :
1122 147 : const char *pszBandDesc = GDALGetDescription(hBand);
1123 147 : if (pszBandDesc != nullptr && strlen(pszBandDesc) > 0)
1124 : {
1125 39 : if (bJson)
1126 : {
1127 33 : json_object_object_add(poBand, "description",
1128 : json_object_new_string(pszBandDesc));
1129 :
1130 33 : json_object_object_add(poStacEOBand, "description",
1131 : json_object_new_string(pszBandDesc));
1132 : }
1133 : else
1134 : {
1135 6 : Concat(osStr, psOptions->bStdoutOutput, " Description = %s\n",
1136 : pszBandDesc);
1137 : }
1138 : }
1139 : else
1140 : {
1141 108 : if (bJson)
1142 : {
1143 : json_object *poColorInterp =
1144 62 : json_object_new_string(GDALGetColorInterpretationName(
1145 : GDALGetRasterColorInterpretation(hBand)));
1146 62 : json_object_object_add(poStacEOBand, "description",
1147 : poColorInterp);
1148 : }
1149 : }
1150 :
1151 147 : if (bJson)
1152 : {
1153 95 : const char *pszCommonName = GDALGetSTACCommonNameFromColorInterp(
1154 : GDALGetRasterColorInterpretation(hBand));
1155 95 : if (pszCommonName)
1156 : {
1157 22 : json_object_object_add(poStacEOBand, "common_name",
1158 : json_object_new_string(pszCommonName));
1159 : }
1160 : }
1161 :
1162 : {
1163 147 : int bGotMin = FALSE;
1164 147 : int bGotMax = FALSE;
1165 147 : const double dfMin = GDALGetRasterMinimum(hBand, &bGotMin);
1166 147 : const double dfMax = GDALGetRasterMaximum(hBand, &bGotMax);
1167 147 : if (bGotMin || bGotMax || psOptions->bComputeMinMax)
1168 : {
1169 36 : if (!bJson)
1170 6 : Concat(osStr, psOptions->bStdoutOutput, " ");
1171 36 : if (bGotMin)
1172 : {
1173 33 : if (bJson)
1174 : {
1175 : json_object *poMin =
1176 28 : gdal_json_object_new_double_or_str_for_non_finite(
1177 : dfMin, 3);
1178 28 : json_object_object_add(poBand, "min", poMin);
1179 : }
1180 : else
1181 : {
1182 5 : Concat(osStr, psOptions->bStdoutOutput, "Min=%.3f ",
1183 : dfMin);
1184 : }
1185 : }
1186 36 : if (bGotMax)
1187 : {
1188 33 : if (bJson)
1189 : {
1190 : json_object *poMax =
1191 28 : gdal_json_object_new_double_or_str_for_non_finite(
1192 : dfMax, 3);
1193 28 : json_object_object_add(poBand, "max", poMax);
1194 : }
1195 : else
1196 : {
1197 5 : Concat(osStr, psOptions->bStdoutOutput, "Max=%.3f ",
1198 : dfMax);
1199 : }
1200 : }
1201 :
1202 36 : if (psOptions->bComputeMinMax)
1203 : {
1204 4 : CPLErrorReset();
1205 4 : double adfCMinMax[2] = {0.0, 0.0};
1206 4 : GDALComputeRasterMinMax(hBand, FALSE, adfCMinMax);
1207 4 : if (CPLGetLastErrorType() == CE_None)
1208 : {
1209 4 : if (bJson)
1210 : {
1211 : json_object *poComputedMin =
1212 2 : gdal_json_object_new_double_or_str_for_non_finite(
1213 : adfCMinMax[0], 3);
1214 : json_object *poComputedMax =
1215 2 : gdal_json_object_new_double_or_str_for_non_finite(
1216 : adfCMinMax[1], 3);
1217 2 : json_object_object_add(poBand, "computedMin",
1218 : poComputedMin);
1219 2 : json_object_object_add(poBand, "computedMax",
1220 : poComputedMax);
1221 : }
1222 : else
1223 : {
1224 2 : Concat(osStr, psOptions->bStdoutOutput,
1225 : " Computed Min/Max=%.3f,%.3f",
1226 : adfCMinMax[0], adfCMinMax[1]);
1227 : }
1228 : }
1229 : }
1230 36 : if (!bJson)
1231 6 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1232 : }
1233 : }
1234 :
1235 147 : double dfMinStat = 0.0;
1236 147 : double dfMaxStat = 0.0;
1237 147 : double dfMean = 0.0;
1238 147 : double dfStdDev = 0.0;
1239 294 : CPLErr eErr = GDALGetRasterStatistics(hBand, psOptions->bApproxStats,
1240 147 : psOptions->bStats, &dfMinStat,
1241 : &dfMaxStat, &dfMean, &dfStdDev);
1242 147 : if (eErr == CE_None)
1243 : {
1244 15 : if (bJson)
1245 : {
1246 8 : json_object *poStacStats = json_object_new_object();
1247 : json_object *poMinimum =
1248 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMinStat,
1249 : 3);
1250 8 : json_object_object_add(poBand, "minimum", poMinimum);
1251 : json_object *poStacMinimum =
1252 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMinStat,
1253 : 3);
1254 8 : json_object_object_add(poStacStats, "minimum", poStacMinimum);
1255 :
1256 : json_object *poMaximum =
1257 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMaxStat,
1258 : 3);
1259 8 : json_object_object_add(poBand, "maximum", poMaximum);
1260 : json_object *poStacMaximum =
1261 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMaxStat,
1262 : 3);
1263 8 : json_object_object_add(poStacStats, "maximum", poStacMaximum);
1264 :
1265 : json_object *poMean =
1266 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMean,
1267 : 3);
1268 8 : json_object_object_add(poBand, "mean", poMean);
1269 : json_object *poStacMean =
1270 8 : gdal_json_object_new_double_or_str_for_non_finite(dfMean,
1271 : 3);
1272 8 : json_object_object_add(poStacStats, "mean", poStacMean);
1273 :
1274 : json_object *poStdDev =
1275 8 : gdal_json_object_new_double_or_str_for_non_finite(dfStdDev,
1276 : 3);
1277 8 : json_object_object_add(poBand, "stdDev", poStdDev);
1278 : json_object *poStacStdDev =
1279 8 : gdal_json_object_new_double_or_str_for_non_finite(dfStdDev,
1280 : 3);
1281 8 : json_object_object_add(poStacStats, "stddev", poStacStdDev);
1282 :
1283 8 : json_object_object_add(poStacRasterBand, "stats", poStacStats);
1284 : }
1285 : else
1286 : {
1287 7 : Concat(osStr, psOptions->bStdoutOutput,
1288 : " Minimum=%.3f, Maximum=%.3f, Mean=%.3f, StdDev=%.3f\n",
1289 : dfMinStat, dfMaxStat, dfMean, dfStdDev);
1290 : }
1291 : }
1292 :
1293 147 : if (psOptions->bReportHistograms)
1294 : {
1295 5 : int nBucketCount = 0;
1296 5 : GUIntBig *panHistogram = nullptr;
1297 :
1298 5 : if (bJson)
1299 4 : eErr = GDALGetDefaultHistogramEx(
1300 : hBand, &dfMinStat, &dfMaxStat, &nBucketCount, &panHistogram,
1301 : TRUE, GDALDummyProgress, nullptr);
1302 : else
1303 1 : eErr = GDALGetDefaultHistogramEx(
1304 : hBand, &dfMinStat, &dfMaxStat, &nBucketCount, &panHistogram,
1305 : TRUE, GDALTermProgress, nullptr);
1306 5 : if (eErr == CE_None)
1307 : {
1308 5 : json_object *poHistogram = nullptr;
1309 5 : json_object *poBuckets = nullptr;
1310 :
1311 5 : if (bJson)
1312 : {
1313 4 : json_object *poCount = json_object_new_int(nBucketCount);
1314 4 : json_object *poMin = json_object_new_double(dfMinStat);
1315 4 : json_object *poMax = json_object_new_double(dfMaxStat);
1316 :
1317 4 : poBuckets = json_object_new_array();
1318 4 : poHistogram = json_object_new_object();
1319 4 : json_object_object_add(poHistogram, "count", poCount);
1320 4 : json_object_object_add(poHistogram, "min", poMin);
1321 4 : json_object_object_add(poHistogram, "max", poMax);
1322 : }
1323 : else
1324 : {
1325 1 : Concat(osStr, psOptions->bStdoutOutput,
1326 : " %d buckets from %g to %g:\n ", nBucketCount,
1327 : dfMinStat, dfMaxStat);
1328 : }
1329 :
1330 1285 : for (int iBucket = 0; iBucket < nBucketCount; iBucket++)
1331 : {
1332 1280 : if (bJson)
1333 : {
1334 : json_object *poBucket =
1335 1024 : json_object_new_int64(panHistogram[iBucket]);
1336 1024 : json_object_array_add(poBuckets, poBucket);
1337 : }
1338 : else
1339 256 : Concat(osStr, psOptions->bStdoutOutput,
1340 256 : CPL_FRMT_GUIB " ", panHistogram[iBucket]);
1341 : }
1342 5 : if (bJson)
1343 : {
1344 4 : json_object_object_add(poHistogram, "buckets", poBuckets);
1345 4 : json_object *poStacHistogram = nullptr;
1346 4 : json_object_deep_copy(poHistogram, &poStacHistogram,
1347 : nullptr);
1348 4 : json_object_object_add(poBand, "histogram", poHistogram);
1349 4 : json_object_object_add(poStacRasterBand, "histogram",
1350 : poStacHistogram);
1351 : }
1352 : else
1353 : {
1354 1 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1355 : }
1356 5 : CPLFree(panHistogram);
1357 : }
1358 : }
1359 :
1360 147 : if (psOptions->bComputeChecksum)
1361 : {
1362 : const int nBandChecksum =
1363 42 : GDALChecksumImage(hBand, 0, 0, GDALGetRasterXSize(hDataset),
1364 : GDALGetRasterYSize(hDataset));
1365 42 : if (bJson)
1366 : {
1367 32 : json_object *poChecksum = json_object_new_int(nBandChecksum);
1368 32 : json_object_object_add(poBand, "checksum", poChecksum);
1369 : }
1370 : else
1371 : {
1372 10 : Concat(osStr, psOptions->bStdoutOutput, " Checksum=%d\n",
1373 : nBandChecksum);
1374 : }
1375 : }
1376 :
1377 147 : int bGotNodata = FALSE;
1378 147 : if (!psOptions->bShowNodata)
1379 : {
1380 : // nothing to do
1381 : }
1382 145 : else if (eDT == GDT_Int64)
1383 : {
1384 : const auto nNoData =
1385 0 : GDALGetRasterNoDataValueAsInt64(hBand, &bGotNodata);
1386 0 : if (bGotNodata)
1387 : {
1388 0 : if (bJson)
1389 : {
1390 0 : json_object *poNoDataValue = json_object_new_int64(nNoData);
1391 0 : json_object *poStacNoDataValue = nullptr;
1392 0 : json_object_deep_copy(poNoDataValue, &poStacNoDataValue,
1393 : nullptr);
1394 0 : json_object_object_add(poStacRasterBand, "nodata",
1395 : poStacNoDataValue);
1396 0 : json_object_object_add(poBand, "noDataValue",
1397 : poNoDataValue);
1398 : }
1399 : else
1400 : {
1401 0 : Concat(osStr, psOptions->bStdoutOutput,
1402 : " NoData Value=" CPL_FRMT_GIB "\n",
1403 : static_cast<GIntBig>(nNoData));
1404 : }
1405 : }
1406 : }
1407 145 : else if (eDT == GDT_UInt64)
1408 : {
1409 : const auto nNoData =
1410 0 : GDALGetRasterNoDataValueAsUInt64(hBand, &bGotNodata);
1411 0 : if (bGotNodata)
1412 : {
1413 0 : if (bJson)
1414 : {
1415 0 : if (nNoData < static_cast<uint64_t>(
1416 0 : std::numeric_limits<int64_t>::max()))
1417 : {
1418 0 : json_object *poNoDataValue = json_object_new_int64(
1419 : static_cast<int64_t>(nNoData));
1420 0 : json_object *poStacNoDataValue = nullptr;
1421 0 : json_object_deep_copy(poNoDataValue, &poStacNoDataValue,
1422 : nullptr);
1423 0 : json_object_object_add(poStacRasterBand, "nodata",
1424 : poStacNoDataValue);
1425 0 : json_object_object_add(poBand, "noDataValue",
1426 : poNoDataValue);
1427 : }
1428 : else
1429 : {
1430 : // not pretty to serialize as a string but there's no
1431 : // way to serialize a uint64_t with libjson-c
1432 : json_object *poNoDataValue =
1433 0 : json_object_new_string(CPLSPrintf(
1434 : CPL_FRMT_GUIB, static_cast<GUIntBig>(nNoData)));
1435 0 : json_object_object_add(poBand, "noDataValue",
1436 : poNoDataValue);
1437 : }
1438 : }
1439 : else
1440 : {
1441 0 : Concat(osStr, psOptions->bStdoutOutput,
1442 : " NoData Value=" CPL_FRMT_GUIB "\n",
1443 : static_cast<GUIntBig>(nNoData));
1444 : }
1445 : }
1446 : }
1447 : else
1448 : {
1449 : const double dfNoData =
1450 145 : GDALGetRasterNoDataValue(hBand, &bGotNodata);
1451 145 : if (bGotNodata)
1452 : {
1453 24 : const bool bIsNoDataFloat =
1454 40 : eDT == GDT_Float32 &&
1455 16 : static_cast<float>(dfNoData) == dfNoData;
1456 : // Find the most compact decimal representation of the nodata
1457 : // value that can be used to exactly represent the binary value
1458 24 : int nSignificantDigits = bIsNoDataFloat ? 8 : 18;
1459 24 : char szNoData[64] = {0};
1460 252 : while (nSignificantDigits > 0)
1461 : {
1462 : char szCandidateNoData[64];
1463 : char szFormat[16];
1464 231 : snprintf(szFormat, sizeof(szFormat), "%%.%dg",
1465 : nSignificantDigits);
1466 231 : CPLsnprintf(szCandidateNoData, sizeof(szCandidateNoData),
1467 : szFormat, dfNoData);
1468 207 : if (szNoData[0] == '\0' ||
1469 112 : (bIsNoDataFloat &&
1470 112 : static_cast<float>(CPLAtof(szCandidateNoData)) ==
1471 438 : static_cast<float>(dfNoData)) ||
1472 95 : (!bIsNoDataFloat &&
1473 95 : CPLAtof(szCandidateNoData) == dfNoData))
1474 : {
1475 228 : strcpy(szNoData, szCandidateNoData);
1476 228 : nSignificantDigits--;
1477 : }
1478 : else
1479 : {
1480 3 : nSignificantDigits++;
1481 3 : break;
1482 : }
1483 : }
1484 :
1485 24 : if (bJson)
1486 : {
1487 : json_object *poNoDataValue =
1488 15 : gdal_json_object_new_double_significant_digits(
1489 : dfNoData, nSignificantDigits);
1490 15 : json_object *poStacNoDataValue = nullptr;
1491 15 : json_object_deep_copy(poNoDataValue, &poStacNoDataValue,
1492 : nullptr);
1493 15 : json_object_object_add(poStacRasterBand, "nodata",
1494 : poStacNoDataValue);
1495 15 : json_object_object_add(poBand, "noDataValue",
1496 : poNoDataValue);
1497 : }
1498 9 : else if (std::isnan(dfNoData))
1499 : {
1500 0 : Concat(osStr, psOptions->bStdoutOutput,
1501 : " NoData Value=nan\n");
1502 : }
1503 : else
1504 : {
1505 9 : Concat(osStr, psOptions->bStdoutOutput,
1506 : " NoData Value=%s\n", szNoData);
1507 : }
1508 : }
1509 : }
1510 :
1511 147 : if (GDALGetOverviewCount(hBand) > 0)
1512 : {
1513 9 : json_object *poOverviews = nullptr;
1514 :
1515 9 : if (bJson)
1516 8 : poOverviews = json_object_new_array();
1517 : else
1518 1 : Concat(osStr, psOptions->bStdoutOutput, " Overviews: ");
1519 :
1520 35 : for (int iOverview = 0; iOverview < GDALGetOverviewCount(hBand);
1521 : iOverview++)
1522 : {
1523 26 : if (!bJson)
1524 1 : if (iOverview != 0)
1525 0 : Concat(osStr, psOptions->bStdoutOutput, ", ");
1526 :
1527 26 : GDALRasterBandH hOverview = GDALGetOverview(hBand, iOverview);
1528 26 : if (hOverview != nullptr)
1529 : {
1530 26 : if (bJson)
1531 : {
1532 25 : json_object *poOverviewSize = json_object_new_array();
1533 25 : json_object *poOverviewSizeX = json_object_new_int(
1534 : GDALGetRasterBandXSize(hOverview));
1535 25 : json_object *poOverviewSizeY = json_object_new_int(
1536 : GDALGetRasterBandYSize(hOverview));
1537 :
1538 25 : json_object *poOverview = json_object_new_object();
1539 25 : json_object_array_add(poOverviewSize, poOverviewSizeX);
1540 25 : json_object_array_add(poOverviewSize, poOverviewSizeY);
1541 25 : json_object_object_add(poOverview, "size",
1542 : poOverviewSize);
1543 :
1544 25 : if (psOptions->bComputeChecksum)
1545 : {
1546 16 : const int nOverviewChecksum = GDALChecksumImage(
1547 : hOverview, 0, 0,
1548 : GDALGetRasterBandXSize(hOverview),
1549 : GDALGetRasterBandYSize(hOverview));
1550 : json_object *poOverviewChecksum =
1551 16 : json_object_new_int(nOverviewChecksum);
1552 16 : json_object_object_add(poOverview, "checksum",
1553 : poOverviewChecksum);
1554 : }
1555 25 : json_object_array_add(poOverviews, poOverview);
1556 : }
1557 : else
1558 : {
1559 1 : Concat(osStr, psOptions->bStdoutOutput, "%dx%d",
1560 : GDALGetRasterBandXSize(hOverview),
1561 : GDALGetRasterBandYSize(hOverview));
1562 : }
1563 :
1564 : const char *pszResampling =
1565 26 : GDALGetMetadataItem(hOverview, "RESAMPLING", "");
1566 :
1567 26 : if (pszResampling != nullptr && !bJson &&
1568 0 : STARTS_WITH_CI(pszResampling, "AVERAGE_BIT2"))
1569 0 : Concat(osStr, psOptions->bStdoutOutput, "*");
1570 : }
1571 : else
1572 : {
1573 0 : if (!bJson)
1574 0 : Concat(osStr, psOptions->bStdoutOutput, "(null)");
1575 : }
1576 : }
1577 9 : if (bJson)
1578 8 : json_object_object_add(poBand, "overviews", poOverviews);
1579 : else
1580 1 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1581 :
1582 9 : if (psOptions->bComputeChecksum && !bJson)
1583 : {
1584 0 : Concat(osStr, psOptions->bStdoutOutput,
1585 : " Overviews checksum: ");
1586 :
1587 0 : for (int iOverview = 0; iOverview < GDALGetOverviewCount(hBand);
1588 : iOverview++)
1589 : {
1590 : GDALRasterBandH hOverview;
1591 :
1592 0 : if (iOverview != 0)
1593 0 : Concat(osStr, psOptions->bStdoutOutput, ", ");
1594 :
1595 0 : hOverview = GDALGetOverview(hBand, iOverview);
1596 0 : if (hOverview)
1597 : {
1598 0 : Concat(osStr, psOptions->bStdoutOutput, "%d",
1599 : GDALChecksumImage(
1600 : hOverview, 0, 0,
1601 : GDALGetRasterBandXSize(hOverview),
1602 : GDALGetRasterBandYSize(hOverview)));
1603 : }
1604 : else
1605 : {
1606 0 : Concat(osStr, psOptions->bStdoutOutput, "(null)");
1607 : }
1608 : }
1609 0 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1610 : }
1611 : }
1612 :
1613 147 : if (GDALHasArbitraryOverviews(hBand) && !bJson)
1614 : {
1615 0 : Concat(osStr, psOptions->bStdoutOutput, " Overviews: arbitrary\n");
1616 : }
1617 :
1618 : const int nMaskFlags =
1619 147 : psOptions->bShowMask ? GDALGetMaskFlags(hBand) : GMF_ALL_VALID;
1620 147 : if ((nMaskFlags & (GMF_NODATA | GMF_ALL_VALID)) == 0 ||
1621 : nMaskFlags == (GMF_NODATA | GMF_PER_DATASET))
1622 : {
1623 21 : GDALRasterBandH hMaskBand = GDALGetMaskBand(hBand);
1624 21 : json_object *poMask = nullptr;
1625 21 : json_object *poFlags = nullptr;
1626 21 : json_object *poMaskOverviews = nullptr;
1627 :
1628 21 : if (bJson)
1629 : {
1630 17 : poMask = json_object_new_object();
1631 17 : poFlags = json_object_new_array();
1632 : }
1633 : else
1634 4 : Concat(osStr, psOptions->bStdoutOutput, " Mask Flags: ");
1635 21 : if (nMaskFlags & GMF_PER_DATASET)
1636 : {
1637 19 : if (bJson)
1638 : {
1639 16 : json_object *poFlag = json_object_new_string("PER_DATASET");
1640 16 : json_object_array_add(poFlags, poFlag);
1641 : }
1642 : else
1643 3 : Concat(osStr, psOptions->bStdoutOutput, "PER_DATASET ");
1644 : }
1645 21 : if (nMaskFlags & GMF_ALPHA)
1646 : {
1647 15 : if (bJson)
1648 : {
1649 15 : json_object *poFlag = json_object_new_string("ALPHA");
1650 15 : json_object_array_add(poFlags, poFlag);
1651 : }
1652 : else
1653 0 : Concat(osStr, psOptions->bStdoutOutput, "ALPHA ");
1654 : }
1655 21 : if (nMaskFlags & GMF_NODATA)
1656 : {
1657 3 : if (bJson)
1658 : {
1659 0 : json_object *poFlag = json_object_new_string("NODATA");
1660 0 : json_object_array_add(poFlags, poFlag);
1661 : }
1662 : else
1663 : {
1664 3 : Concat(osStr, psOptions->bStdoutOutput, "NODATA ");
1665 : }
1666 : }
1667 :
1668 21 : if (bJson)
1669 17 : json_object_object_add(poMask, "flags", poFlags);
1670 : else
1671 4 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1672 :
1673 21 : if (bJson)
1674 17 : poMaskOverviews = json_object_new_array();
1675 :
1676 21 : if (hMaskBand != nullptr && GDALGetOverviewCount(hMaskBand) > 0)
1677 : {
1678 0 : if (!bJson)
1679 0 : Concat(osStr, psOptions->bStdoutOutput,
1680 : " Overviews of mask band: ");
1681 :
1682 0 : for (int iOverview = 0;
1683 0 : iOverview < GDALGetOverviewCount(hMaskBand); iOverview++)
1684 : {
1685 : GDALRasterBandH hOverview =
1686 0 : GDALGetOverview(hMaskBand, iOverview);
1687 0 : if (!hOverview)
1688 0 : break;
1689 0 : json_object *poMaskOverview = nullptr;
1690 0 : json_object *poMaskOverviewSize = nullptr;
1691 :
1692 0 : if (bJson)
1693 : {
1694 0 : poMaskOverview = json_object_new_object();
1695 0 : poMaskOverviewSize = json_object_new_array();
1696 : }
1697 : else
1698 : {
1699 0 : if (iOverview != 0)
1700 0 : Concat(osStr, psOptions->bStdoutOutput, ", ");
1701 : }
1702 :
1703 0 : if (bJson)
1704 : {
1705 0 : json_object *poMaskOverviewSizeX = json_object_new_int(
1706 : GDALGetRasterBandXSize(hOverview));
1707 0 : json_object *poMaskOverviewSizeY = json_object_new_int(
1708 : GDALGetRasterBandYSize(hOverview));
1709 :
1710 0 : json_object_array_add(poMaskOverviewSize,
1711 : poMaskOverviewSizeX);
1712 0 : json_object_array_add(poMaskOverviewSize,
1713 : poMaskOverviewSizeY);
1714 0 : json_object_object_add(poMaskOverview, "size",
1715 : poMaskOverviewSize);
1716 0 : json_object_array_add(poMaskOverviews, poMaskOverview);
1717 : }
1718 : else
1719 : {
1720 0 : Concat(osStr, psOptions->bStdoutOutput, "%dx%d",
1721 : GDALGetRasterBandXSize(hOverview),
1722 : GDALGetRasterBandYSize(hOverview));
1723 : }
1724 : }
1725 0 : if (!bJson)
1726 0 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1727 : }
1728 21 : if (bJson)
1729 : {
1730 17 : json_object_object_add(poMask, "overviews", poMaskOverviews);
1731 17 : json_object_object_add(poBand, "mask", poMask);
1732 : }
1733 : }
1734 :
1735 147 : if (strlen(GDALGetRasterUnitType(hBand)) > 0)
1736 : {
1737 0 : if (bJson)
1738 : {
1739 : json_object *poUnit =
1740 0 : json_object_new_string(GDALGetRasterUnitType(hBand));
1741 0 : json_object *poStacUnit = nullptr;
1742 0 : json_object_deep_copy(poUnit, &poStacUnit, nullptr);
1743 0 : json_object_object_add(poStacRasterBand, "unit", poStacUnit);
1744 0 : json_object_object_add(poBand, "unit", poUnit);
1745 : }
1746 : else
1747 : {
1748 0 : Concat(osStr, psOptions->bStdoutOutput, " Unit Type: %s\n",
1749 : GDALGetRasterUnitType(hBand));
1750 : }
1751 : }
1752 :
1753 147 : if (GDALGetRasterCategoryNames(hBand) != nullptr)
1754 : {
1755 0 : char **papszCategories = GDALGetRasterCategoryNames(hBand);
1756 0 : json_object *poCategories = nullptr;
1757 :
1758 0 : if (bJson)
1759 0 : poCategories = json_object_new_array();
1760 : else
1761 0 : Concat(osStr, psOptions->bStdoutOutput, " Categories:\n");
1762 :
1763 0 : for (int i = 0; papszCategories[i] != nullptr; i++)
1764 : {
1765 0 : if (bJson)
1766 : {
1767 : json_object *poCategoryName =
1768 0 : json_object_new_string(papszCategories[i]);
1769 0 : json_object_array_add(poCategories, poCategoryName);
1770 : }
1771 : else
1772 0 : Concat(osStr, psOptions->bStdoutOutput, " %3d: %s\n", i,
1773 0 : papszCategories[i]);
1774 : }
1775 0 : if (bJson)
1776 0 : json_object_object_add(poBand, "categories", poCategories);
1777 : }
1778 :
1779 147 : int bSuccess = FALSE;
1780 294 : if (GDALGetRasterScale(hBand, &bSuccess) != 1.0 ||
1781 147 : GDALGetRasterOffset(hBand, &bSuccess) != 0.0)
1782 : {
1783 0 : if (bJson)
1784 : {
1785 0 : json_object *poOffset = json_object_new_double_with_precision(
1786 : GDALGetRasterOffset(hBand, &bSuccess), 15);
1787 0 : json_object *poScale = json_object_new_double_with_precision(
1788 : GDALGetRasterScale(hBand, &bSuccess), 15);
1789 0 : json_object *poStacScale = nullptr;
1790 0 : json_object *poStacOffset = nullptr;
1791 0 : json_object_deep_copy(poScale, &poStacScale, nullptr);
1792 0 : json_object_deep_copy(poOffset, &poStacOffset, nullptr);
1793 0 : json_object_object_add(poStacRasterBand, "scale", poStacScale);
1794 0 : json_object_object_add(poStacRasterBand, "offset",
1795 : poStacOffset);
1796 0 : json_object_object_add(poBand, "offset", poOffset);
1797 0 : json_object_object_add(poBand, "scale", poScale);
1798 : }
1799 : else
1800 : {
1801 0 : Concat(osStr, psOptions->bStdoutOutput,
1802 : " Offset: %.15g, Scale:%.15g\n",
1803 : GDALGetRasterOffset(hBand, &bSuccess),
1804 : GDALGetRasterScale(hBand, &bSuccess));
1805 : }
1806 : }
1807 :
1808 147 : GDALInfoReportMetadata(psOptions, hBand, true, bJson, poBandMetadata,
1809 : osStr);
1810 147 : if (bJson)
1811 : {
1812 95 : if (psOptions->bShowMetadata)
1813 92 : json_object_object_add(poBand, "metadata", poBandMetadata);
1814 : else
1815 3 : json_object_put(poBandMetadata);
1816 : }
1817 :
1818 : GDALColorTableH hTable;
1819 155 : if (GDALGetRasterColorInterpretation(hBand) == GCI_PaletteIndex &&
1820 8 : (hTable = GDALGetRasterColorTable(hBand)) != nullptr)
1821 : {
1822 8 : if (!bJson)
1823 2 : Concat(osStr, psOptions->bStdoutOutput,
1824 : " Color Table (%s with %d entries)\n",
1825 : GDALGetPaletteInterpretationName(
1826 : GDALGetPaletteInterpretation(hTable)),
1827 : GDALGetColorEntryCount(hTable));
1828 :
1829 8 : if (psOptions->bShowColorTable)
1830 : {
1831 6 : json_object *poEntries = nullptr;
1832 :
1833 6 : if (bJson)
1834 : {
1835 : json_object *poPalette =
1836 5 : json_object_new_string(GDALGetPaletteInterpretationName(
1837 : GDALGetPaletteInterpretation(hTable)));
1838 : json_object *poCount =
1839 5 : json_object_new_int(GDALGetColorEntryCount(hTable));
1840 :
1841 5 : json_object *poColorTable = json_object_new_object();
1842 :
1843 5 : json_object_object_add(poColorTable, "palette", poPalette);
1844 5 : json_object_object_add(poColorTable, "count", poCount);
1845 :
1846 5 : poEntries = json_object_new_array();
1847 5 : json_object_object_add(poColorTable, "entries", poEntries);
1848 5 : json_object_object_add(poBand, "colorTable", poColorTable);
1849 : }
1850 :
1851 46 : for (int i = 0; i < GDALGetColorEntryCount(hTable); i++)
1852 : {
1853 : GDALColorEntry sEntry;
1854 :
1855 40 : GDALGetColorEntryAsRGB(hTable, i, &sEntry);
1856 :
1857 40 : if (bJson)
1858 : {
1859 24 : json_object *poEntry = json_object_new_array();
1860 24 : json_object *poC1 = json_object_new_int(sEntry.c1);
1861 24 : json_object *poC2 = json_object_new_int(sEntry.c2);
1862 24 : json_object *poC3 = json_object_new_int(sEntry.c3);
1863 24 : json_object *poC4 = json_object_new_int(sEntry.c4);
1864 :
1865 24 : json_object_array_add(poEntry, poC1);
1866 24 : json_object_array_add(poEntry, poC2);
1867 24 : json_object_array_add(poEntry, poC3);
1868 24 : json_object_array_add(poEntry, poC4);
1869 24 : json_object_array_add(poEntries, poEntry);
1870 : }
1871 : else
1872 : {
1873 16 : Concat(osStr, psOptions->bStdoutOutput,
1874 16 : " %3d: %d,%d,%d,%d\n", i, sEntry.c1, sEntry.c2,
1875 16 : sEntry.c3, sEntry.c4);
1876 : }
1877 : }
1878 : }
1879 : }
1880 :
1881 147 : if (psOptions->bShowRAT && GDALGetDefaultRAT(hBand) != nullptr)
1882 : {
1883 4 : GDALRasterAttributeTableH hRAT = GDALGetDefaultRAT(hBand);
1884 :
1885 4 : if (bJson)
1886 : {
1887 : json_object *poRAT =
1888 3 : static_cast<json_object *>(GDALRATSerializeJSON(hRAT));
1889 3 : json_object_object_add(poJsonObject, "rat", poRAT);
1890 : }
1891 : else
1892 : {
1893 : CPLXMLNode *psTree =
1894 1 : static_cast<GDALRasterAttributeTable *>(hRAT)->Serialize();
1895 1 : char *pszXMLText = CPLSerializeXMLTree(psTree);
1896 1 : CPLDestroyXMLNode(psTree);
1897 1 : Concat(osStr, psOptions->bStdoutOutput, "%s\n", pszXMLText);
1898 1 : CPLFree(pszXMLText);
1899 : }
1900 : }
1901 147 : if (bJson)
1902 : {
1903 95 : json_object_array_add(poBands, poBand);
1904 95 : json_object_array_add(poStacRasterBands, poStacRasterBand);
1905 95 : json_object_array_add(poStacEOBands, poStacEOBand);
1906 : }
1907 : }
1908 :
1909 118 : if (bJson)
1910 : {
1911 70 : json_object_object_add(poJsonObject, "bands", poBands);
1912 70 : json_object_object_add(poStac, "raster:bands", poStacRasterBands);
1913 70 : json_object_object_add(poStac, "eo:bands", poStacEOBands);
1914 70 : json_object_object_add(poJsonObject, "stac", poStac);
1915 70 : Concat(osStr, psOptions->bStdoutOutput, "%s",
1916 : json_object_to_json_string_ext(
1917 : poJsonObject, JSON_C_TO_STRING_PRETTY
1918 : #ifdef JSON_C_TO_STRING_NOSLASHESCAPE
1919 : | JSON_C_TO_STRING_NOSLASHESCAPE
1920 : #endif
1921 : ));
1922 70 : json_object_put(poJsonObject);
1923 70 : Concat(osStr, psOptions->bStdoutOutput, "\n");
1924 : }
1925 :
1926 118 : if (psOptionsToFree != nullptr)
1927 0 : GDALInfoOptionsFree(psOptionsToFree);
1928 :
1929 118 : return VSI_STRDUP_VERBOSE(osStr);
1930 : }
1931 :
1932 : /************************************************************************/
1933 : /* GDALInfoReportCorner() */
1934 : /************************************************************************/
1935 :
1936 660 : static int GDALInfoReportCorner(const GDALInfoOptions *psOptions,
1937 : GDALDatasetH hDataset,
1938 : OGRCoordinateTransformationH hTransform,
1939 : const char *corner_name, double x, double y,
1940 : bool bJson, json_object *poCornerCoordinates,
1941 : json_object *poLongLatExtentCoordinates,
1942 : CPLString &osStr)
1943 :
1944 : {
1945 660 : if (!bJson)
1946 240 : Concat(osStr, psOptions->bStdoutOutput, "%-11s ", corner_name);
1947 :
1948 : /* -------------------------------------------------------------------- */
1949 : /* Transform the point into georeferenced coordinates. */
1950 : /* -------------------------------------------------------------------- */
1951 660 : double adfGeoTransform[6] = {0.0, 0.0, 0.0, 0.0, 0.0, 0.0};
1952 660 : double dfGeoX = 0.0;
1953 660 : double dfGeoY = 0.0;
1954 :
1955 660 : if (GDALGetGeoTransform(hDataset, adfGeoTransform) == CE_None)
1956 : {
1957 569 : dfGeoX = adfGeoTransform[0] + adfGeoTransform[1] * x +
1958 569 : adfGeoTransform[2] * y;
1959 569 : dfGeoY = adfGeoTransform[3] + adfGeoTransform[4] * x +
1960 569 : adfGeoTransform[5] * y;
1961 : }
1962 : else
1963 : {
1964 91 : if (bJson)
1965 : {
1966 66 : json_object *const poCorner = json_object_new_array();
1967 : json_object *const poX =
1968 66 : json_object_new_double_with_precision(x, 1);
1969 : json_object *const poY =
1970 66 : json_object_new_double_with_precision(y, 1);
1971 66 : json_object_array_add(poCorner, poX);
1972 66 : json_object_array_add(poCorner, poY);
1973 66 : json_object_object_add(poCornerCoordinates, corner_name, poCorner);
1974 : }
1975 : else
1976 : {
1977 25 : Concat(osStr, psOptions->bStdoutOutput, "(%7.1f,%7.1f)\n", x, y);
1978 : }
1979 91 : return FALSE;
1980 : }
1981 :
1982 : /* -------------------------------------------------------------------- */
1983 : /* Report the georeferenced coordinates. */
1984 : /* -------------------------------------------------------------------- */
1985 569 : if (std::abs(dfGeoX) < 181 && std::abs(dfGeoY) < 91)
1986 : {
1987 88 : if (bJson)
1988 : {
1989 48 : json_object *const poCorner = json_object_new_array();
1990 : json_object *const poX =
1991 48 : json_object_new_double_with_precision(dfGeoX, 7);
1992 : json_object *const poY =
1993 48 : json_object_new_double_with_precision(dfGeoY, 7);
1994 48 : json_object_array_add(poCorner, poX);
1995 48 : json_object_array_add(poCorner, poY);
1996 48 : json_object_object_add(poCornerCoordinates, corner_name, poCorner);
1997 : }
1998 : else
1999 : {
2000 40 : Concat(osStr, psOptions->bStdoutOutput, "(%12.7f,%12.7f) ", dfGeoX,
2001 : dfGeoY);
2002 : }
2003 : }
2004 : else
2005 : {
2006 481 : if (bJson)
2007 : {
2008 306 : json_object *const poCorner = json_object_new_array();
2009 : json_object *const poX =
2010 306 : json_object_new_double_with_precision(dfGeoX, 3);
2011 : json_object *const poY =
2012 306 : json_object_new_double_with_precision(dfGeoY, 3);
2013 306 : json_object_array_add(poCorner, poX);
2014 306 : json_object_array_add(poCorner, poY);
2015 306 : json_object_object_add(poCornerCoordinates, corner_name, poCorner);
2016 : }
2017 : else
2018 : {
2019 175 : Concat(osStr, psOptions->bStdoutOutput, "(%12.3f,%12.3f) ", dfGeoX,
2020 : dfGeoY);
2021 : }
2022 : }
2023 :
2024 : /* -------------------------------------------------------------------- */
2025 : /* Transform to latlong and report. */
2026 : /* -------------------------------------------------------------------- */
2027 569 : if (bJson)
2028 : {
2029 354 : double dfZ = 0.0;
2030 639 : if (hTransform != nullptr && !EQUAL(corner_name, "center") &&
2031 285 : OCTTransform(hTransform, 1, &dfGeoX, &dfGeoY, &dfZ))
2032 : {
2033 285 : json_object *const poCorner = json_object_new_array();
2034 : json_object *const poX =
2035 285 : json_object_new_double_with_precision(dfGeoX, 7);
2036 : json_object *const poY =
2037 285 : json_object_new_double_with_precision(dfGeoY, 7);
2038 285 : json_object_array_add(poCorner, poX);
2039 285 : json_object_array_add(poCorner, poY);
2040 285 : json_object_array_add(poLongLatExtentCoordinates, poCorner);
2041 : }
2042 : }
2043 : else
2044 : {
2045 215 : double dfZ = 0.0;
2046 420 : if (hTransform != nullptr &&
2047 205 : OCTTransform(hTransform, 1, &dfGeoX, &dfGeoY, &dfZ))
2048 : {
2049 205 : Concat(osStr, psOptions->bStdoutOutput, "(%s,",
2050 : GDALDecToDMS(dfGeoX, "Long", 2));
2051 205 : Concat(osStr, psOptions->bStdoutOutput, "%s)",
2052 : GDALDecToDMS(dfGeoY, "Lat", 2));
2053 : }
2054 215 : Concat(osStr, psOptions->bStdoutOutput, "\n");
2055 : }
2056 :
2057 569 : return TRUE;
2058 : }
2059 :
2060 : /************************************************************************/
2061 : /* GDALInfoPrintMetadata() */
2062 : /************************************************************************/
2063 1245 : static void GDALInfoPrintMetadata(const GDALInfoOptions *psOptions,
2064 : GDALMajorObjectH hObject,
2065 : const char *pszDomain,
2066 : const char *pszDisplayedname,
2067 : const char *pszIndent, int bJsonOutput,
2068 : json_object *poMetadata, CPLString &osStr)
2069 : {
2070 1245 : const bool bIsxml =
2071 1245 : pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "xml:");
2072 1245 : const bool bMDIsJson =
2073 1245 : pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "json:");
2074 :
2075 1245 : char **papszMetadata = GDALGetMetadata(hObject, pszDomain);
2076 1245 : if (papszMetadata != nullptr && *papszMetadata != nullptr)
2077 : {
2078 91 : json_object *poDomain = (bJsonOutput && !bIsxml && !bMDIsJson)
2079 254 : ? json_object_new_object()
2080 163 : : nullptr;
2081 :
2082 163 : if (!bJsonOutput)
2083 72 : Concat(osStr, psOptions->bStdoutOutput, "%s%s:\n", pszIndent,
2084 : pszDisplayedname);
2085 :
2086 163 : json_object *poValue = nullptr;
2087 :
2088 905 : for (int i = 0; papszMetadata[i] != nullptr; i++)
2089 : {
2090 745 : if (bJsonOutput)
2091 : {
2092 471 : if (bIsxml)
2093 : {
2094 2 : poValue = json_object_new_string(papszMetadata[i]);
2095 2 : break;
2096 : }
2097 469 : else if (bMDIsJson)
2098 : {
2099 1 : OGRJSonParse(papszMetadata[i], &poValue, true);
2100 1 : break;
2101 : }
2102 : else
2103 : {
2104 468 : char *pszKey = nullptr;
2105 : const char *pszValue =
2106 468 : CPLParseNameValue(papszMetadata[i], &pszKey);
2107 468 : if (pszKey)
2108 : {
2109 468 : poValue = json_object_new_string(pszValue);
2110 468 : json_object_object_add(poDomain, pszKey, poValue);
2111 468 : CPLFree(pszKey);
2112 : }
2113 : }
2114 : }
2115 : else
2116 : {
2117 274 : if (bIsxml || bMDIsJson)
2118 2 : Concat(osStr, psOptions->bStdoutOutput, "%s%s\n", pszIndent,
2119 2 : papszMetadata[i]);
2120 : else
2121 272 : Concat(osStr, psOptions->bStdoutOutput, "%s %s\n",
2122 272 : pszIndent, papszMetadata[i]);
2123 : }
2124 : }
2125 163 : if (bJsonOutput)
2126 : {
2127 91 : if (bIsxml || bMDIsJson)
2128 : {
2129 3 : json_object_object_add(poMetadata, pszDomain, poValue);
2130 : }
2131 : else
2132 : {
2133 88 : if (pszDomain == nullptr)
2134 52 : json_object_object_add(poMetadata, "", poDomain);
2135 : else
2136 36 : json_object_object_add(poMetadata, pszDomain, poDomain);
2137 : }
2138 : }
2139 : }
2140 1245 : }
2141 :
2142 : /************************************************************************/
2143 : /* GDALInfoReportMetadata() */
2144 : /************************************************************************/
2145 265 : static void GDALInfoReportMetadata(const GDALInfoOptions *psOptions,
2146 : GDALMajorObjectH hObject, bool bIsBand,
2147 : bool bJson, json_object *poMetadata,
2148 : CPLString &osStr)
2149 : {
2150 265 : const char *const pszIndent = bIsBand ? " " : "";
2151 :
2152 : /* -------------------------------------------------------------------- */
2153 : /* Report list of Metadata domains */
2154 : /* -------------------------------------------------------------------- */
2155 265 : if (psOptions->bListMDD)
2156 : {
2157 16 : const CPLStringList aosDomainList(GDALGetMetadataDomainList(hObject));
2158 8 : json_object *poMDD = nullptr;
2159 : json_object *const poListMDD =
2160 8 : bJson ? json_object_new_array() : nullptr;
2161 :
2162 8 : if (!aosDomainList.empty())
2163 : {
2164 5 : if (!bJson)
2165 1 : Concat(osStr, psOptions->bStdoutOutput, "%sMetadata domains:\n",
2166 : pszIndent);
2167 : }
2168 :
2169 23 : for (const char *pszDomain : aosDomainList)
2170 : {
2171 15 : if (EQUAL(pszDomain, ""))
2172 : {
2173 5 : if (bJson)
2174 4 : poMDD = json_object_new_string(pszDomain);
2175 : else
2176 1 : Concat(osStr, psOptions->bStdoutOutput, "%s (default)\n",
2177 : pszIndent);
2178 : }
2179 : else
2180 : {
2181 10 : if (bJson)
2182 7 : poMDD = json_object_new_string(pszDomain);
2183 : else
2184 3 : Concat(osStr, psOptions->bStdoutOutput, "%s %s\n",
2185 : pszIndent, pszDomain);
2186 : }
2187 15 : if (bJson)
2188 11 : json_object_array_add(poListMDD, poMDD);
2189 : }
2190 8 : if (bJson)
2191 6 : json_object_object_add(poMetadata, "metadataDomains", poListMDD);
2192 : }
2193 :
2194 265 : if (!psOptions->bShowMetadata)
2195 8 : return;
2196 :
2197 : /* -------------------------------------------------------------------- */
2198 : /* Report default Metadata domain. */
2199 : /* -------------------------------------------------------------------- */
2200 257 : GDALInfoPrintMetadata(psOptions, hObject, nullptr, "Metadata", pszIndent,
2201 : bJson, poMetadata, osStr);
2202 :
2203 : /* -------------------------------------------------------------------- */
2204 : /* Report extra Metadata domains */
2205 : /* -------------------------------------------------------------------- */
2206 257 : if (!psOptions->aosExtraMDDomains.empty())
2207 : {
2208 36 : CPLStringList aosExtraMDDomainsExpanded;
2209 :
2210 26 : if (EQUAL(psOptions->aosExtraMDDomains[0], "all") &&
2211 8 : psOptions->aosExtraMDDomains.Count() == 1)
2212 : {
2213 16 : const CPLStringList aosMDDList(GDALGetMetadataDomainList(hObject));
2214 23 : for (const char *pszDomain : aosMDDList)
2215 : {
2216 15 : if (!EQUAL(pszDomain, "") &&
2217 11 : !EQUAL(pszDomain, "IMAGE_STRUCTURE") &&
2218 8 : !EQUAL(pszDomain, "TILING_SCHEME") &&
2219 8 : !EQUAL(pszDomain, "SUBDATASETS") &&
2220 8 : !EQUAL(pszDomain, "GEOLOCATION") &&
2221 8 : !EQUAL(pszDomain, "RPC"))
2222 : {
2223 8 : aosExtraMDDomainsExpanded.AddString(pszDomain);
2224 : }
2225 : }
2226 : }
2227 : else
2228 : {
2229 10 : aosExtraMDDomainsExpanded = psOptions->aosExtraMDDomains;
2230 : }
2231 :
2232 36 : for (const char *pszDomain : aosExtraMDDomainsExpanded)
2233 : {
2234 18 : if (bJson)
2235 : {
2236 12 : GDALInfoPrintMetadata(psOptions, hObject, pszDomain, pszDomain,
2237 : pszIndent, bJson, poMetadata, osStr);
2238 : }
2239 : else
2240 : {
2241 : const std::string osDisplayedName =
2242 18 : std::string("Metadata (").append(pszDomain).append(")");
2243 :
2244 6 : GDALInfoPrintMetadata(psOptions, hObject, pszDomain,
2245 : osDisplayedName.c_str(), pszIndent, bJson,
2246 : poMetadata, osStr);
2247 : }
2248 : }
2249 : }
2250 :
2251 : /* -------------------------------------------------------------------- */
2252 : /* Report various named metadata domains. */
2253 : /* -------------------------------------------------------------------- */
2254 257 : GDALInfoPrintMetadata(psOptions, hObject, "IMAGE_STRUCTURE",
2255 : "Image Structure Metadata", pszIndent, bJson,
2256 : poMetadata, osStr);
2257 :
2258 257 : if (!bIsBand)
2259 : {
2260 114 : GDALInfoPrintMetadata(psOptions, hObject, "TILING_SCHEME",
2261 : "Tiling Scheme", pszIndent, bJson, poMetadata,
2262 : osStr);
2263 114 : GDALInfoPrintMetadata(psOptions, hObject, "SUBDATASETS", "Subdatasets",
2264 : pszIndent, bJson, poMetadata, osStr);
2265 114 : GDALInfoPrintMetadata(psOptions, hObject, "GEOLOCATION", "Geolocation",
2266 : pszIndent, bJson, poMetadata, osStr);
2267 114 : GDALInfoPrintMetadata(psOptions, hObject, "RPC", "RPC Metadata",
2268 : pszIndent, bJson, poMetadata, osStr);
2269 : }
2270 :
2271 257 : GDALInfoPrintMetadata(psOptions, hObject, "IMAGERY", "Imagery", pszIndent,
2272 : bJson, poMetadata, osStr);
2273 : }
2274 :
2275 : /************************************************************************/
2276 : /* GDALInfoOptionsNew() */
2277 : /************************************************************************/
2278 :
2279 : /**
2280 : * Allocates a GDALInfoOptions struct.
2281 : *
2282 : * @param papszArgv NULL terminated list of options (potentially including
2283 : * filename and open options too), or NULL. The accepted options are the ones of
2284 : * the <a href="/programs/gdalinfo.html">gdalinfo</a> utility.
2285 : * @param psOptionsForBinary (output) may be NULL (and should generally be
2286 : * NULL), otherwise (gdalinfo_bin.cpp use case) must be allocated with
2287 : * GDALInfoOptionsForBinaryNew() prior to this
2288 : * function. Will be filled with potentially present filename, open options,
2289 : * subdataset number...
2290 : * @return pointer to the allocated GDALInfoOptions struct. Must be freed with
2291 : * GDALInfoOptionsFree().
2292 : *
2293 : * @since GDAL 2.1
2294 : */
2295 :
2296 : GDALInfoOptions *
2297 123 : GDALInfoOptionsNew(char **papszArgv,
2298 : GDALInfoOptionsForBinary *psOptionsForBinary)
2299 : {
2300 246 : auto psOptions = std::make_unique<GDALInfoOptions>();
2301 :
2302 : /* -------------------------------------------------------------------- */
2303 : /* Parse arguments. */
2304 : /* -------------------------------------------------------------------- */
2305 :
2306 246 : CPLStringList aosArgv;
2307 :
2308 123 : if (papszArgv)
2309 : {
2310 110 : const int nArgc = CSLCount(papszArgv);
2311 403 : for (int i = 0; i < nArgc; i++)
2312 : {
2313 293 : aosArgv.AddString(papszArgv[i]);
2314 : }
2315 : }
2316 :
2317 : try
2318 : {
2319 : auto argParser =
2320 246 : GDALInfoAppOptionsGetParser(psOptions.get(), psOptionsForBinary);
2321 :
2322 123 : argParser->parse_args_without_binary_name(aosArgv.List());
2323 :
2324 123 : if (psOptions->bApproxStats)
2325 2 : psOptions->bStats = true;
2326 : }
2327 0 : catch (const std::exception &error)
2328 : {
2329 0 : CPLError(CE_Failure, CPLE_AppDefined, "%s", error.what());
2330 0 : return nullptr;
2331 : }
2332 :
2333 123 : if (!psOptions->bShowNodata)
2334 2 : psOptions->bShowMask = false;
2335 :
2336 123 : return psOptions.release();
2337 : }
2338 :
2339 : /************************************************************************/
2340 : /* GDALInfoOptionsFree() */
2341 : /************************************************************************/
2342 :
2343 : /**
2344 : * Frees the GDALInfoOptions struct.
2345 : *
2346 : * @param psOptions the options struct for GDALInfo().
2347 : *
2348 : * @since GDAL 2.1
2349 : */
2350 :
2351 64 : void GDALInfoOptionsFree(GDALInfoOptions *psOptions)
2352 : {
2353 64 : delete psOptions;
2354 64 : }
|