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