Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL Core
4 : * Purpose: Implementation of GDALPamDataset, a dataset base class that
5 : * knows how to persist auxiliary metadata into a support XML file.
6 : * Author: Frank Warmerdam, warmerdam@pobox.com
7 : *
8 : ******************************************************************************
9 : * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
10 : * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
11 : *
12 : * SPDX-License-Identifier: MIT
13 : ****************************************************************************/
14 :
15 : #include "cpl_port.h"
16 : #include "gdal_pam.h"
17 :
18 : #include <cstddef>
19 : #include <cstdlib>
20 : #include <cstring>
21 : #include <string>
22 :
23 : #include "cpl_conv.h"
24 : #include "cpl_error.h"
25 : #include "cpl_minixml.h"
26 : #include "cpl_progress.h"
27 : #include "cpl_string.h"
28 : #include "cpl_vsi.h"
29 : #include "gdal.h"
30 : #include "gdal_priv.h"
31 : #include "ogr_core.h"
32 : #include "ogr_spatialref.h"
33 :
34 : /************************************************************************/
35 : /* GDALPamDataset() */
36 : /************************************************************************/
37 :
38 : /**
39 : * \class GDALPamDataset "gdal_pam.h"
40 : *
41 : * A subclass of GDALDataset which introduces the ability to save and
42 : * restore auxiliary information (coordinate system, gcps, metadata,
43 : * etc) not supported by a file format via an "auxiliary metadata" file
44 : * with the .aux.xml extension.
45 : *
46 : * <h3>Enabling PAM</h3>
47 : *
48 : * PAM support can be enabled (resp. disabled) in GDAL by setting the
49 : * GDAL_PAM_ENABLED configuration option (via CPLSetConfigOption(), or the
50 : * environment) to the value of YES (resp. NO). Note: The default value is
51 : * build dependent and defaults to YES in Windows and Unix builds. Warning:
52 : * For GDAL < 3.5, setting this option to OFF may have unwanted side-effects on
53 : * drivers that rely on PAM functionality.
54 : *
55 : * <h3>PAM Proxy Files</h3>
56 : *
57 : * In order to be able to record auxiliary information about files on
58 : * read-only media such as CDROMs or in directories where the user does not
59 : * have write permissions, it is possible to enable the "PAM Proxy Database".
60 : * When enabled the .aux.xml files are kept in a different directory, writable
61 : * by the user. Overviews will also be stored in the PAM proxy directory.
62 : *
63 : * To enable this, set the GDAL_PAM_PROXY_DIR configuration option to be
64 : * the name of the directory where the proxies should be kept. The configuration
65 : * option must be set *before* the first access to PAM, because its value is
66 : * cached for later access.
67 : *
68 : * <h3>Adding PAM to Drivers</h3>
69 : *
70 : * Drivers for physical file formats that wish to support persistent auxiliary
71 : * metadata in addition to that for the format itself should derive their
72 : * dataset class from GDALPamDataset instead of directly from GDALDataset.
73 : * The raster band classes should also be derived from GDALPamRasterBand.
74 : *
75 : * They should also call something like this near the end of the Open()
76 : * method:
77 : *
78 : * \code
79 : * poDS->SetDescription( poOpenInfo->pszFilename );
80 : * poDS->TryLoadXML();
81 : * \endcode
82 : *
83 : * The SetDescription() is necessary so that the dataset will have a valid
84 : * filename set as the description before TryLoadXML() is called. TryLoadXML()
85 : * will look for an .aux.xml file with the same basename as the dataset and
86 : * in the same directory. If found the contents will be loaded and kept
87 : * track of in the GDALPamDataset and GDALPamRasterBand objects. When a
88 : * call like GetProjectionRef() is not implemented by the format specific
89 : * class, it will fall through to the PAM implementation which will return
90 : * information if it was in the .aux.xml file.
91 : *
92 : * Drivers should also try to call the GDALPamDataset/GDALPamRasterBand
93 : * methods as a fallback if their implementation does not find information.
94 : * This allows using the .aux.xml for variations that can't be stored in
95 : * the format. For instance, the GeoTIFF driver GetProjectionRef() looks
96 : * like this:
97 : *
98 : * \code
99 : * if( EQUAL(pszProjection,"") )
100 : * return GDALPamDataset::GetProjectionRef();
101 : * else
102 : * return( pszProjection );
103 : * \endcode
104 : *
105 : * So if the geotiff header is missing, the .aux.xml file will be
106 : * consulted.
107 : *
108 : * Similarly, if SetProjection() were called with a coordinate system
109 : * not supported by GeoTIFF, the SetProjection() method should pass it on
110 : * to the GDALPamDataset::SetProjection() method after issuing a warning
111 : * that the information can't be represented within the file itself.
112 : *
113 : * Drivers for subdataset based formats will also need to declare the
114 : * name of the physical file they are related to, and the name of their
115 : * subdataset before calling TryLoadXML().
116 : *
117 : * \code
118 : * poDS->SetDescription( poOpenInfo->pszFilename );
119 : * poDS->SetPhysicalFilename( poDS->pszFilename );
120 : * poDS->SetSubdatasetName( osSubdatasetName );
121 : *
122 : * poDS->TryLoadXML();
123 : * \endcode
124 : *
125 : * In some situations where a derived dataset (e.g. used by
126 : * GDALMDArray::AsClassicDataset()) is linked to a physical file, the name of
127 : * the derived dataset is set with the SetDerivedSubdatasetName() method.
128 : *
129 : * \code
130 : * poDS->SetDescription( poOpenInfo->pszFilename );
131 : * poDS->SetPhysicalFilename( poDS->pszFilename );
132 : * poDS->SetDerivedDatasetName( osDerivedDatasetName );
133 : *
134 : * poDS->TryLoadXML();
135 : * \endcode
136 : */
137 : class GDALPamDataset;
138 :
139 62791 : GDALPamDataset::GDALPamDataset()
140 : {
141 62747 : SetMOFlags(GetMOFlags() | GMO_PAM_CLASS);
142 62664 : }
143 :
144 : /************************************************************************/
145 : /* ~GDALPamDataset() */
146 : /************************************************************************/
147 :
148 62776 : GDALPamDataset::~GDALPamDataset()
149 :
150 : {
151 62798 : if (IsMarkedSuppressOnClose())
152 : {
153 382 : if (psPam && psPam->pszPamFilename != nullptr)
154 10 : VSIUnlink(psPam->pszPamFilename);
155 : }
156 62390 : else if (nPamFlags & GPF_DIRTY)
157 : {
158 574 : CPLDebug("GDALPamDataset", "In destructor with dirty metadata.");
159 574 : GDALPamDataset::TrySaveXML();
160 : }
161 :
162 62772 : PamClear();
163 62793 : }
164 :
165 : /************************************************************************/
166 : /* FlushCache() */
167 : /************************************************************************/
168 :
169 65032 : CPLErr GDALPamDataset::FlushCache(bool bAtClosing)
170 :
171 : {
172 65032 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
173 65032 : if (nPamFlags & GPF_DIRTY)
174 : {
175 6054 : if (TrySaveXML() != CE_None)
176 28 : eErr = CE_Failure;
177 : }
178 65032 : return eErr;
179 : }
180 :
181 : /************************************************************************/
182 : /* MarkPamDirty() */
183 : /************************************************************************/
184 :
185 : //! @cond Doxygen_Suppress
186 59038 : void GDALPamDataset::MarkPamDirty()
187 : {
188 80309 : if ((nPamFlags & GPF_DIRTY) == 0 &&
189 21271 : CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLE_MARK_DIRTY", "YES")))
190 : {
191 21251 : nPamFlags |= GPF_DIRTY;
192 : }
193 59038 : }
194 :
195 : // @endcond
196 :
197 : /************************************************************************/
198 : /* SerializeToXML() */
199 : /************************************************************************/
200 :
201 : //! @cond Doxygen_Suppress
202 1623 : CPLXMLNode *GDALPamDataset::SerializeToXML(const char *pszUnused)
203 :
204 : {
205 1623 : if (psPam == nullptr)
206 0 : return nullptr;
207 :
208 : /* -------------------------------------------------------------------- */
209 : /* Setup root node and attributes. */
210 : /* -------------------------------------------------------------------- */
211 1623 : CPLXMLNode *psDSTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
212 :
213 : /* -------------------------------------------------------------------- */
214 : /* SRS */
215 : /* -------------------------------------------------------------------- */
216 1623 : if (psPam->poSRS && !psPam->poSRS->IsEmpty())
217 : {
218 456 : char *pszWKT = nullptr;
219 : {
220 912 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
221 456 : if (psPam->poSRS->exportToWkt(&pszWKT) != OGRERR_NONE)
222 : {
223 0 : CPLFree(pszWKT);
224 0 : pszWKT = nullptr;
225 0 : const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
226 0 : psPam->poSRS->exportToWkt(&pszWKT, apszOptions);
227 : }
228 : }
229 : CPLXMLNode *psSRSNode =
230 456 : CPLCreateXMLElementAndValue(psDSTree, "SRS", pszWKT);
231 456 : CPLFree(pszWKT);
232 456 : const auto &mapping = psPam->poSRS->GetDataAxisToSRSAxisMapping();
233 912 : CPLString osMapping;
234 1369 : for (size_t i = 0; i < mapping.size(); ++i)
235 : {
236 913 : if (!osMapping.empty())
237 457 : osMapping += ",";
238 913 : osMapping += CPLSPrintf("%d", mapping[i]);
239 : }
240 456 : CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
241 : osMapping.c_str());
242 :
243 456 : const double dfCoordinateEpoch = psPam->poSRS->GetCoordinateEpoch();
244 456 : if (dfCoordinateEpoch > 0)
245 : {
246 2 : std::string osCoordinateEpoch = CPLSPrintf("%f", dfCoordinateEpoch);
247 1 : if (osCoordinateEpoch.find('.') != std::string::npos)
248 : {
249 6 : while (osCoordinateEpoch.back() == '0')
250 5 : osCoordinateEpoch.resize(osCoordinateEpoch.size() - 1);
251 : }
252 1 : CPLAddXMLAttributeAndValue(psSRSNode, "coordinateEpoch",
253 : osCoordinateEpoch.c_str());
254 : }
255 : }
256 :
257 : /* -------------------------------------------------------------------- */
258 : /* GeoTransform. */
259 : /* -------------------------------------------------------------------- */
260 1623 : if (psPam->bHaveGeoTransform)
261 : {
262 812 : CPLString oFmt;
263 : oFmt.Printf("%24.16e,%24.16e,%24.16e,%24.16e,%24.16e,%24.16e",
264 812 : psPam->adfGeoTransform[0], psPam->adfGeoTransform[1],
265 812 : psPam->adfGeoTransform[2], psPam->adfGeoTransform[3],
266 406 : psPam->adfGeoTransform[4], psPam->adfGeoTransform[5]);
267 406 : CPLSetXMLValue(psDSTree, "GeoTransform", oFmt);
268 : }
269 :
270 : /* -------------------------------------------------------------------- */
271 : /* Metadata. */
272 : /* -------------------------------------------------------------------- */
273 1623 : if (psPam->bHasMetadata)
274 : {
275 1220 : CPLXMLNode *psMD = oMDMD.Serialize();
276 1220 : if (psMD != nullptr)
277 : {
278 1061 : CPLAddXMLChild(psDSTree, psMD);
279 : }
280 : }
281 :
282 : /* -------------------------------------------------------------------- */
283 : /* GCPs */
284 : /* -------------------------------------------------------------------- */
285 1623 : if (!psPam->asGCPs.empty())
286 : {
287 9 : GDALSerializeGCPListToXML(psDSTree, psPam->asGCPs, psPam->poGCP_SRS);
288 : }
289 :
290 : /* -------------------------------------------------------------------- */
291 : /* Process bands. */
292 : /* -------------------------------------------------------------------- */
293 :
294 : // Find last child
295 1623 : CPLXMLNode *psLastChild = psDSTree->psChild;
296 2636 : for (; psLastChild != nullptr && psLastChild->psNext;
297 1013 : psLastChild = psLastChild->psNext)
298 : {
299 : }
300 :
301 4219 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
302 : {
303 2596 : GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
304 :
305 2596 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
306 4 : continue;
307 :
308 : CPLXMLNode *const psBandTree =
309 2592 : cpl::down_cast<GDALPamRasterBand *>(poBand)->SerializeToXML(
310 2592 : pszUnused);
311 :
312 2592 : if (psBandTree != nullptr)
313 : {
314 759 : if (psLastChild == nullptr)
315 : {
316 188 : CPLAddXMLChild(psDSTree, psBandTree);
317 : }
318 : else
319 : {
320 571 : psLastChild->psNext = psBandTree;
321 : }
322 759 : psLastChild = psBandTree;
323 : }
324 : }
325 :
326 : /* -------------------------------------------------------------------- */
327 : /* We don't want to return anything if we had no metadata to */
328 : /* attach. */
329 : /* -------------------------------------------------------------------- */
330 1623 : if (psDSTree->psChild == nullptr)
331 : {
332 193 : CPLDestroyXMLNode(psDSTree);
333 193 : psDSTree = nullptr;
334 : }
335 :
336 1623 : return psDSTree;
337 : }
338 :
339 : /************************************************************************/
340 : /* PamInitialize() */
341 : /************************************************************************/
342 :
343 598321 : void GDALPamDataset::PamInitialize()
344 :
345 : {
346 : #ifdef PAM_ENABLED
347 598321 : const char *const pszPamDefault = "YES";
348 : #else
349 : const char *const pszPamDefault = "NO";
350 : #endif
351 :
352 598321 : if (psPam)
353 555616 : return;
354 :
355 42705 : if (!CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLED", pszPamDefault)))
356 : {
357 17 : CPLDebug("GDAL", "PAM is disabled");
358 17 : nPamFlags |= GPF_DISABLED;
359 : }
360 :
361 : /* ERO 2011/04/13 : GPF_AUXMODE seems to be unimplemented */
362 42705 : if (EQUAL(CPLGetConfigOption("GDAL_PAM_MODE", "PAM"), "AUX"))
363 0 : nPamFlags |= GPF_AUXMODE;
364 :
365 42705 : psPam = new GDALDatasetPamInfo;
366 516259 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
367 : {
368 473553 : GDALRasterBand *poBand = GetRasterBand(iBand + 1);
369 :
370 473554 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
371 136 : continue;
372 :
373 473418 : cpl::down_cast<GDALPamRasterBand *>(poBand)->PamInitialize();
374 : }
375 : }
376 :
377 : /************************************************************************/
378 : /* PamClear() */
379 : /************************************************************************/
380 :
381 62786 : void GDALPamDataset::PamClear()
382 :
383 : {
384 62786 : if (psPam)
385 : {
386 42704 : CPLFree(psPam->pszPamFilename);
387 42704 : if (psPam->poSRS)
388 835 : psPam->poSRS->Release();
389 42704 : if (psPam->poGCP_SRS)
390 37 : psPam->poGCP_SRS->Release();
391 :
392 42704 : delete psPam;
393 42704 : psPam = nullptr;
394 : }
395 62786 : }
396 :
397 : /************************************************************************/
398 : /* XMLInit() */
399 : /************************************************************************/
400 :
401 1465 : CPLErr GDALPamDataset::XMLInit(const CPLXMLNode *psTree, const char *pszUnused)
402 :
403 : {
404 : /* -------------------------------------------------------------------- */
405 : /* Check for an SRS node. */
406 : /* -------------------------------------------------------------------- */
407 1465 : if (const CPLXMLNode *psSRSNode = CPLGetXMLNode(psTree, "SRS"))
408 : {
409 436 : if (psPam->poSRS)
410 98 : psPam->poSRS->Release();
411 436 : psPam->poSRS = new OGRSpatialReference();
412 436 : psPam->poSRS->SetFromUserInput(
413 : CPLGetXMLValue(psSRSNode, nullptr, ""),
414 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS);
415 : const char *pszMapping =
416 436 : CPLGetXMLValue(psSRSNode, "dataAxisToSRSAxisMapping", nullptr);
417 436 : if (pszMapping)
418 : {
419 : char **papszTokens =
420 358 : CSLTokenizeStringComplex(pszMapping, ",", FALSE, FALSE);
421 716 : std::vector<int> anMapping;
422 1075 : for (int i = 0; papszTokens && papszTokens[i]; i++)
423 : {
424 717 : anMapping.push_back(atoi(papszTokens[i]));
425 : }
426 358 : CSLDestroy(papszTokens);
427 358 : psPam->poSRS->SetDataAxisToSRSAxisMapping(anMapping);
428 : }
429 : else
430 : {
431 78 : psPam->poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
432 : }
433 :
434 : const char *pszCoordinateEpoch =
435 436 : CPLGetXMLValue(psSRSNode, "coordinateEpoch", nullptr);
436 436 : if (pszCoordinateEpoch)
437 2 : psPam->poSRS->SetCoordinateEpoch(CPLAtof(pszCoordinateEpoch));
438 : }
439 :
440 : /* -------------------------------------------------------------------- */
441 : /* Check for a GeoTransform node. */
442 : /* -------------------------------------------------------------------- */
443 1465 : const char *pszGT = CPLGetXMLValue(psTree, "GeoTransform", "");
444 1465 : if (strlen(pszGT) > 0)
445 : {
446 : const CPLStringList aosTokens(
447 790 : CSLTokenizeStringComplex(pszGT, ",", FALSE, FALSE));
448 395 : if (aosTokens.size() != 6)
449 : {
450 0 : CPLError(CE_Warning, CPLE_AppDefined,
451 : "GeoTransform node does not have expected six values.");
452 : }
453 : else
454 : {
455 2765 : for (int iTA = 0; iTA < 6; iTA++)
456 2370 : psPam->adfGeoTransform[iTA] = CPLAtof(aosTokens[iTA]);
457 395 : psPam->bHaveGeoTransform = TRUE;
458 : }
459 : }
460 :
461 : /* -------------------------------------------------------------------- */
462 : /* Check for GCPs. */
463 : /* -------------------------------------------------------------------- */
464 1465 : if (const CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"))
465 : {
466 29 : if (psPam->poGCP_SRS)
467 0 : psPam->poGCP_SRS->Release();
468 29 : psPam->poGCP_SRS = nullptr;
469 :
470 : // Make sure any previous GCPs, perhaps from an .aux file, are cleared
471 : // if we have new ones.
472 29 : psPam->asGCPs.clear();
473 29 : GDALDeserializeGCPListFromXML(psGCPList, psPam->asGCPs,
474 29 : &(psPam->poGCP_SRS));
475 : }
476 :
477 : /* -------------------------------------------------------------------- */
478 : /* Apply any dataset level metadata. */
479 : /* -------------------------------------------------------------------- */
480 1465 : if (oMDMD.XMLInit(psTree, TRUE))
481 : {
482 1074 : psPam->bHasMetadata = TRUE;
483 : }
484 :
485 : /* -------------------------------------------------------------------- */
486 : /* Try loading ESRI xml encoded GeodataXform. */
487 : /* -------------------------------------------------------------------- */
488 : {
489 : // previously we only tried to load GeodataXform if we didn't already
490 : // encounter a valid SRS at this stage. But in some cases a PAMDataset
491 : // may have both a SRS child element AND a GeodataXform with a SpatialReference
492 : // child element. In this case we should prioritize the GeodataXform
493 : // over the root PAMDataset SRS node.
494 :
495 : // ArcGIS 9.3: GeodataXform as a root element
496 : const CPLXMLNode *psGeodataXform =
497 1465 : CPLGetXMLNode(psTree, "=GeodataXform");
498 2930 : CPLXMLTreeCloser oTreeValueAsXML(nullptr);
499 1465 : if (psGeodataXform != nullptr)
500 : {
501 : char *apszMD[2];
502 2 : apszMD[0] = CPLSerializeXMLTree(psGeodataXform);
503 2 : apszMD[1] = nullptr;
504 2 : oMDMD.SetMetadata(apszMD, "xml:ESRI");
505 2 : CPLFree(apszMD[0]);
506 : }
507 : else
508 : {
509 : // ArcGIS 10: GeodataXform as content of xml:ESRI metadata domain.
510 1463 : char **papszXML = oMDMD.GetMetadata("xml:ESRI");
511 1463 : if (CSLCount(papszXML) == 1)
512 : {
513 11 : oTreeValueAsXML.reset(CPLParseXMLString(papszXML[0]));
514 11 : if (oTreeValueAsXML)
515 : psGeodataXform =
516 11 : CPLGetXMLNode(oTreeValueAsXML.get(), "=GeodataXform");
517 : }
518 : }
519 :
520 1465 : if (psGeodataXform)
521 : {
522 : const char *pszESRI_WKT =
523 9 : CPLGetXMLValue(psGeodataXform, "SpatialReference.WKT", nullptr);
524 9 : if (pszESRI_WKT)
525 : {
526 9 : auto poSRS = std::make_unique<OGRSpatialReference>();
527 9 : poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
528 9 : if (poSRS->importFromWkt(pszESRI_WKT) != OGRERR_NONE)
529 : {
530 0 : poSRS.reset();
531 : }
532 9 : delete psPam->poSRS;
533 9 : psPam->poSRS = poSRS.release();
534 : }
535 :
536 : // Parse GCPs
537 : const CPLXMLNode *psSourceGCPS =
538 9 : CPLGetXMLNode(psGeodataXform, "SourceGCPs");
539 : const CPLXMLNode *psTargetGCPs =
540 9 : CPLGetXMLNode(psGeodataXform, "TargetGCPs");
541 : const CPLXMLNode *psCoeffX =
542 9 : CPLGetXMLNode(psGeodataXform, "CoeffX");
543 : const CPLXMLNode *psCoeffY =
544 9 : CPLGetXMLNode(psGeodataXform, "CoeffY");
545 9 : if (psSourceGCPS && psTargetGCPs && !psPam->bHaveGeoTransform)
546 : {
547 12 : std::vector<double> adfSource;
548 12 : std::vector<double> adfTarget;
549 6 : bool ySourceAllNegative = true;
550 80 : for (auto psIter = psSourceGCPS->psChild; psIter;
551 74 : psIter = psIter->psNext)
552 : {
553 74 : if (psIter->eType == CXT_Element &&
554 68 : strcmp(psIter->pszValue, "Double") == 0)
555 : {
556 68 : adfSource.push_back(
557 68 : CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
558 68 : if ((adfSource.size() % 2) == 0 && adfSource.back() > 0)
559 28 : ySourceAllNegative = false;
560 : }
561 : }
562 80 : for (auto psIter = psTargetGCPs->psChild; psIter;
563 74 : psIter = psIter->psNext)
564 : {
565 74 : if (psIter->eType == CXT_Element &&
566 68 : strcmp(psIter->pszValue, "Double") == 0)
567 : {
568 68 : adfTarget.push_back(
569 68 : CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
570 : }
571 : }
572 12 : if (!adfSource.empty() &&
573 12 : adfSource.size() == adfTarget.size() &&
574 6 : (adfSource.size() % 2) == 0)
575 : {
576 6 : std::vector<gdal::GCP> asGCPs;
577 40 : for (size_t i = 0; i + 1 < adfSource.size(); i += 2)
578 : {
579 : asGCPs.emplace_back("", "",
580 34 : /* pixel = */ adfSource[i],
581 : /* line = */
582 : ySourceAllNegative
583 96 : ? -adfSource[i + 1]
584 28 : : adfSource[i + 1],
585 34 : /* X = */ adfTarget[i],
586 68 : /* Y = */ adfTarget[i + 1]);
587 : }
588 6 : GDALPamDataset::SetGCPs(static_cast<int>(asGCPs.size()),
589 : gdal::GCP::c_ptr(asGCPs),
590 6 : psPam->poSRS);
591 6 : delete psPam->poSRS;
592 6 : psPam->poSRS = nullptr;
593 6 : }
594 : }
595 4 : else if (psCoeffX && psCoeffY && !psPam->bHaveGeoTransform &&
596 1 : EQUAL(
597 : CPLGetXMLValue(psGeodataXform, "PolynomialOrder", ""),
598 : "1"))
599 : {
600 2 : std::vector<double> adfCoeffX;
601 2 : std::vector<double> adfCoeffY;
602 5 : for (auto psIter = psCoeffX->psChild; psIter;
603 4 : psIter = psIter->psNext)
604 : {
605 4 : if (psIter->eType == CXT_Element &&
606 3 : strcmp(psIter->pszValue, "Double") == 0)
607 : {
608 3 : adfCoeffX.push_back(
609 3 : CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
610 : }
611 : }
612 5 : for (auto psIter = psCoeffY->psChild; psIter;
613 4 : psIter = psIter->psNext)
614 : {
615 4 : if (psIter->eType == CXT_Element &&
616 3 : strcmp(psIter->pszValue, "Double") == 0)
617 : {
618 3 : adfCoeffY.push_back(
619 3 : CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
620 : }
621 : }
622 1 : if (adfCoeffX.size() == 3 && adfCoeffY.size() == 3)
623 : {
624 1 : psPam->adfGeoTransform[0] = adfCoeffX[0];
625 1 : psPam->adfGeoTransform[1] = adfCoeffX[1];
626 : // Looking at the example of https://github.com/qgis/QGIS/issues/53125#issuecomment-1567650082
627 : // when comparing the .pgwx world file and .png.aux.xml file,
628 : // it appears that the sign of the coefficients for the line
629 : // terms must be negated (which is a bit in line with the
630 : // negation of dfGCPLine in the above GCP case)
631 1 : psPam->adfGeoTransform[2] = -adfCoeffX[2];
632 1 : psPam->adfGeoTransform[3] = adfCoeffY[0];
633 1 : psPam->adfGeoTransform[4] = adfCoeffY[1];
634 1 : psPam->adfGeoTransform[5] = -adfCoeffY[2];
635 :
636 : // Looking at the example of https://github.com/qgis/QGIS/issues/53125#issuecomment-1567650082
637 : // when comparing the .pgwx world file and .png.aux.xml file,
638 : // one can see that they have the same origin, so knowing
639 : // that world file uses a center-of-pixel convention,
640 : // correct from center of pixel to top left of pixel
641 2 : psPam->adfGeoTransform[0] -=
642 1 : 0.5 * psPam->adfGeoTransform[1];
643 2 : psPam->adfGeoTransform[0] -=
644 1 : 0.5 * psPam->adfGeoTransform[2];
645 2 : psPam->adfGeoTransform[3] -=
646 1 : 0.5 * psPam->adfGeoTransform[4];
647 2 : psPam->adfGeoTransform[3] -=
648 1 : 0.5 * psPam->adfGeoTransform[5];
649 :
650 1 : psPam->bHaveGeoTransform = TRUE;
651 : }
652 : }
653 : }
654 : }
655 :
656 : /* -------------------------------------------------------------------- */
657 : /* Process bands. */
658 : /* -------------------------------------------------------------------- */
659 4457 : for (const CPLXMLNode *psBandTree = psTree->psChild; psBandTree;
660 2992 : psBandTree = psBandTree->psNext)
661 : {
662 2992 : if (psBandTree->eType != CXT_Element ||
663 2988 : !EQUAL(psBandTree->pszValue, "PAMRasterBand"))
664 2265 : continue;
665 :
666 727 : const int nBand = atoi(CPLGetXMLValue(psBandTree, "band", "0"));
667 :
668 727 : if (nBand < 1 || nBand > GetRasterCount())
669 41 : continue;
670 :
671 686 : GDALRasterBand *poBand = GetRasterBand(nBand);
672 :
673 686 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
674 0 : continue;
675 :
676 : GDALPamRasterBand *poPamBand =
677 686 : cpl::down_cast<GDALPamRasterBand *>(GetRasterBand(nBand));
678 :
679 686 : poPamBand->XMLInit(psBandTree, pszUnused);
680 : }
681 :
682 : /* -------------------------------------------------------------------- */
683 : /* Preserve Array information. */
684 : /* -------------------------------------------------------------------- */
685 4457 : for (const CPLXMLNode *psIter = psTree->psChild; psIter;
686 2992 : psIter = psIter->psNext)
687 : {
688 5980 : if (psIter->eType == CXT_Element &&
689 5964 : (strcmp(psIter->pszValue, "Array") == 0 ||
690 2976 : (psPam->osDerivedDatasetName.empty() &&
691 2966 : strcmp(psIter->pszValue, "DerivedDataset") == 0)))
692 : {
693 29 : CPLXMLNode sArrayTmp = *psIter;
694 29 : sArrayTmp.psNext = nullptr;
695 29 : psPam->m_apoOtherNodes.emplace_back(
696 29 : CPLXMLTreeCloser(CPLCloneXMLTree(&sArrayTmp)));
697 : }
698 : }
699 :
700 : /* -------------------------------------------------------------------- */
701 : /* Clear dirty flag. */
702 : /* -------------------------------------------------------------------- */
703 1465 : nPamFlags &= ~GPF_DIRTY;
704 :
705 1465 : return CE_None;
706 : }
707 :
708 : /************************************************************************/
709 : /* SetPhysicalFilename() */
710 : /************************************************************************/
711 :
712 4004 : void GDALPamDataset::SetPhysicalFilename(const char *pszFilename)
713 :
714 : {
715 4004 : PamInitialize();
716 :
717 4004 : if (psPam)
718 4004 : psPam->osPhysicalFilename = pszFilename;
719 4004 : }
720 :
721 : /************************************************************************/
722 : /* GetPhysicalFilename() */
723 : /************************************************************************/
724 :
725 160 : const char *GDALPamDataset::GetPhysicalFilename()
726 :
727 : {
728 160 : PamInitialize();
729 :
730 160 : if (psPam)
731 160 : return psPam->osPhysicalFilename;
732 :
733 0 : return "";
734 : }
735 :
736 : /************************************************************************/
737 : /* SetSubdatasetName() */
738 : /************************************************************************/
739 :
740 : /* Mutually exclusive with SetDerivedDatasetName() */
741 744 : void GDALPamDataset::SetSubdatasetName(const char *pszSubdataset)
742 :
743 : {
744 744 : PamInitialize();
745 :
746 744 : if (psPam)
747 744 : psPam->osSubdatasetName = pszSubdataset;
748 744 : }
749 :
750 : /************************************************************************/
751 : /* SetDerivedDatasetName() */
752 : /************************************************************************/
753 :
754 : /* Mutually exclusive with SetSubdatasetName() */
755 165 : void GDALPamDataset::SetDerivedDatasetName(const char *pszDerivedDataset)
756 :
757 : {
758 165 : PamInitialize();
759 :
760 165 : if (psPam)
761 165 : psPam->osDerivedDatasetName = pszDerivedDataset;
762 165 : }
763 :
764 : /************************************************************************/
765 : /* GetSubdatasetName() */
766 : /************************************************************************/
767 :
768 3 : const char *GDALPamDataset::GetSubdatasetName()
769 :
770 : {
771 3 : PamInitialize();
772 :
773 3 : if (psPam)
774 3 : return psPam->osSubdatasetName;
775 :
776 0 : return "";
777 : }
778 :
779 : /************************************************************************/
780 : /* BuildPamFilename() */
781 : /************************************************************************/
782 :
783 34777 : const char *GDALPamDataset::BuildPamFilename()
784 :
785 : {
786 34777 : if (psPam == nullptr)
787 0 : return nullptr;
788 :
789 : /* -------------------------------------------------------------------- */
790 : /* What is the name of the physical file we are referencing? */
791 : /* We allow an override via the psPam->pszPhysicalFile item. */
792 : /* -------------------------------------------------------------------- */
793 34777 : if (psPam->pszPamFilename != nullptr)
794 2506 : return psPam->pszPamFilename;
795 :
796 32271 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
797 :
798 32271 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
799 30220 : pszPhysicalFile = GetDescription();
800 :
801 32271 : if (strlen(pszPhysicalFile) == 0)
802 229 : return nullptr;
803 :
804 : /* -------------------------------------------------------------------- */
805 : /* Try a proxy lookup, otherwise just add .aux.xml. */
806 : /* -------------------------------------------------------------------- */
807 32042 : const char *pszProxyPam = PamGetProxy(pszPhysicalFile);
808 32041 : if (pszProxyPam != nullptr)
809 4 : psPam->pszPamFilename = CPLStrdup(pszProxyPam);
810 : else
811 : {
812 32037 : if (!GDALCanFileAcceptSidecarFile(pszPhysicalFile))
813 104 : return nullptr;
814 63868 : psPam->pszPamFilename =
815 31934 : static_cast<char *>(CPLMalloc(strlen(pszPhysicalFile) + 10));
816 31934 : strcpy(psPam->pszPamFilename, pszPhysicalFile);
817 31934 : strcat(psPam->pszPamFilename, ".aux.xml");
818 : }
819 :
820 31938 : return psPam->pszPamFilename;
821 : }
822 :
823 : /************************************************************************/
824 : /* IsPamFilenameAPotentialSiblingFile() */
825 : /************************************************************************/
826 :
827 26796 : int GDALPamDataset::IsPamFilenameAPotentialSiblingFile()
828 : {
829 26796 : if (psPam == nullptr)
830 0 : return FALSE;
831 :
832 : /* -------------------------------------------------------------------- */
833 : /* Determine if the PAM filename is a .aux.xml file next to the */
834 : /* physical file, or if it comes from the ProxyDB */
835 : /* -------------------------------------------------------------------- */
836 26796 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
837 :
838 26795 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
839 25874 : pszPhysicalFile = GetDescription();
840 :
841 26796 : size_t nLenPhysicalFile = strlen(pszPhysicalFile);
842 26796 : int bIsSiblingPamFile =
843 26796 : strncmp(psPam->pszPamFilename, pszPhysicalFile, nLenPhysicalFile) ==
844 53584 : 0 &&
845 26788 : strcmp(psPam->pszPamFilename + nLenPhysicalFile, ".aux.xml") == 0;
846 :
847 26796 : return bIsSiblingPamFile;
848 : }
849 :
850 : /************************************************************************/
851 : /* TryLoadXML() */
852 : /************************************************************************/
853 :
854 32165 : CPLErr GDALPamDataset::TryLoadXML(CSLConstList papszSiblingFiles)
855 :
856 : {
857 32165 : PamInitialize();
858 :
859 32165 : if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
860 64 : return CE_None;
861 :
862 : /* -------------------------------------------------------------------- */
863 : /* Clear dirty flag. Generally when we get to this point is */
864 : /* from a call at the end of the Open() method, and some calls */
865 : /* may have already marked the PAM info as dirty (for instance */
866 : /* setting metadata), but really everything to this point is */
867 : /* reproducible, and so the PAM info should not really be */
868 : /* thought of as dirty. */
869 : /* -------------------------------------------------------------------- */
870 32101 : nPamFlags &= ~GPF_DIRTY;
871 :
872 : /* -------------------------------------------------------------------- */
873 : /* Try reading the file. */
874 : /* -------------------------------------------------------------------- */
875 32101 : if (!BuildPamFilename())
876 242 : return CE_None;
877 :
878 : /* -------------------------------------------------------------------- */
879 : /* In case the PAM filename is a .aux.xml file next to the */
880 : /* physical file and we have a siblings list, then we can skip */
881 : /* stat'ing the filesystem. */
882 : /* -------------------------------------------------------------------- */
883 : VSIStatBufL sStatBuf;
884 31859 : CPLXMLNode *psTree = nullptr;
885 :
886 55644 : if (papszSiblingFiles != nullptr && IsPamFilenameAPotentialSiblingFile() &&
887 23786 : GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
888 : {
889 23786 : const int iSibling = CSLFindString(
890 23785 : papszSiblingFiles, CPLGetFilename(psPam->pszPamFilename));
891 23786 : if (iSibling >= 0)
892 : {
893 423 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
894 423 : psTree = CPLParseXMLFile(psPam->pszPamFilename);
895 : }
896 : }
897 8073 : else if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
898 8997 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
899 924 : VSI_ISREG(sStatBuf.st_mode))
900 : {
901 924 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
902 924 : psTree = CPLParseXMLFile(psPam->pszPamFilename);
903 : }
904 :
905 : /* -------------------------------------------------------------------- */
906 : /* If we are looking for a subdataset, search for its subtree now. */
907 : /* -------------------------------------------------------------------- */
908 31859 : if (psTree)
909 : {
910 2676 : std::string osSubNode;
911 2676 : std::string osSubNodeValue;
912 1338 : if (!psPam->osSubdatasetName.empty())
913 : {
914 161 : osSubNode = "Subdataset";
915 161 : osSubNodeValue = psPam->osSubdatasetName;
916 : }
917 1177 : else if (!psPam->osDerivedDatasetName.empty())
918 : {
919 15 : osSubNode = "DerivedDataset";
920 15 : osSubNodeValue = psPam->osDerivedDatasetName;
921 : }
922 1338 : if (!osSubNode.empty())
923 : {
924 176 : CPLXMLNode *psSubTree = psTree->psChild;
925 :
926 318 : for (; psSubTree != nullptr; psSubTree = psSubTree->psNext)
927 : {
928 382 : if (psSubTree->eType != CXT_Element ||
929 191 : !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
930 123 : continue;
931 :
932 68 : if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
933 : osSubNodeValue.c_str()))
934 19 : continue;
935 :
936 49 : psSubTree = CPLGetXMLNode(psSubTree, "PAMDataset");
937 49 : break;
938 : }
939 :
940 176 : if (psSubTree != nullptr)
941 49 : psSubTree = CPLCloneXMLTree(psSubTree);
942 :
943 176 : CPLDestroyXMLNode(psTree);
944 176 : psTree = psSubTree;
945 : }
946 : }
947 :
948 : /* -------------------------------------------------------------------- */
949 : /* If we fail, try .aux. */
950 : /* -------------------------------------------------------------------- */
951 31859 : if (psTree == nullptr)
952 30648 : return TryLoadAux(papszSiblingFiles);
953 :
954 : /* -------------------------------------------------------------------- */
955 : /* Initialize ourselves from this XML tree. */
956 : /* -------------------------------------------------------------------- */
957 :
958 1211 : CPLString osVRTPath(CPLGetPath(psPam->pszPamFilename));
959 1211 : const CPLErr eErr = XMLInit(psTree, osVRTPath);
960 :
961 1211 : CPLDestroyXMLNode(psTree);
962 :
963 1211 : if (eErr != CE_None)
964 0 : PamClear();
965 :
966 1211 : return eErr;
967 : }
968 :
969 : /************************************************************************/
970 : /* TrySaveXML() */
971 : /************************************************************************/
972 :
973 6674 : CPLErr GDALPamDataset::TrySaveXML()
974 :
975 : {
976 6674 : nPamFlags &= ~GPF_DIRTY;
977 :
978 6674 : if (psPam == nullptr || (nPamFlags & GPF_NOSAVE) != 0 ||
979 1595 : (nPamFlags & GPF_DISABLED) != 0)
980 5094 : return CE_None;
981 :
982 : /* -------------------------------------------------------------------- */
983 : /* Make sure we know the filename we want to store in. */
984 : /* -------------------------------------------------------------------- */
985 1580 : if (!BuildPamFilename())
986 91 : return CE_None;
987 :
988 : /* -------------------------------------------------------------------- */
989 : /* Build the XML representation of the auxiliary metadata. */
990 : /* -------------------------------------------------------------------- */
991 1489 : CPLXMLNode *psTree = SerializeToXML(nullptr);
992 :
993 1489 : if (psTree == nullptr)
994 : {
995 : /* If we have unset all metadata, we have to delete the PAM file */
996 193 : CPLPushErrorHandler(CPLQuietErrorHandler);
997 193 : VSIUnlink(psPam->pszPamFilename);
998 193 : CPLPopErrorHandler();
999 193 : return CE_None;
1000 : }
1001 :
1002 : /* -------------------------------------------------------------------- */
1003 : /* If we are working with a subdataset, we need to integrate */
1004 : /* the subdataset tree within the whole existing pam tree, */
1005 : /* after removing any old version of the same subdataset. */
1006 : /* -------------------------------------------------------------------- */
1007 2592 : std::string osSubNode;
1008 1296 : std::string osSubNodeValue;
1009 1296 : if (!psPam->osSubdatasetName.empty())
1010 : {
1011 30 : osSubNode = "Subdataset";
1012 30 : osSubNodeValue = psPam->osSubdatasetName;
1013 : }
1014 1266 : else if (!psPam->osDerivedDatasetName.empty())
1015 : {
1016 7 : osSubNode = "DerivedDataset";
1017 7 : osSubNodeValue = psPam->osDerivedDatasetName;
1018 : }
1019 1296 : if (!osSubNode.empty())
1020 : {
1021 37 : CPLXMLNode *psOldTree = nullptr;
1022 :
1023 : VSIStatBufL sStatBuf;
1024 37 : if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
1025 41 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
1026 4 : VSI_ISREG(sStatBuf.st_mode))
1027 : {
1028 4 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1029 4 : psOldTree = CPLParseXMLFile(psPam->pszPamFilename);
1030 : }
1031 :
1032 37 : if (psOldTree == nullptr)
1033 33 : psOldTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
1034 :
1035 37 : CPLXMLNode *psSubTree = psOldTree->psChild;
1036 42 : for (/* initialized above */; psSubTree != nullptr;
1037 5 : psSubTree = psSubTree->psNext)
1038 : {
1039 10 : if (psSubTree->eType != CXT_Element ||
1040 5 : !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
1041 1 : continue;
1042 :
1043 4 : if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
1044 : osSubNodeValue.c_str()))
1045 4 : continue;
1046 :
1047 0 : break;
1048 : }
1049 :
1050 37 : if (psSubTree == nullptr)
1051 : {
1052 : psSubTree =
1053 37 : CPLCreateXMLNode(psOldTree, CXT_Element, osSubNode.c_str());
1054 37 : CPLCreateXMLNode(CPLCreateXMLNode(psSubTree, CXT_Attribute, "name"),
1055 : CXT_Text, osSubNodeValue.c_str());
1056 : }
1057 :
1058 37 : CPLXMLNode *psOldPamDataset = CPLGetXMLNode(psSubTree, "PAMDataset");
1059 37 : if (psOldPamDataset != nullptr)
1060 : {
1061 0 : CPLRemoveXMLChild(psSubTree, psOldPamDataset);
1062 0 : CPLDestroyXMLNode(psOldPamDataset);
1063 : }
1064 :
1065 37 : CPLAddXMLChild(psSubTree, psTree);
1066 37 : psTree = psOldTree;
1067 : }
1068 :
1069 : /* -------------------------------------------------------------------- */
1070 : /* Preserve other information. */
1071 : /* -------------------------------------------------------------------- */
1072 1301 : for (const auto &poOtherNode : psPam->m_apoOtherNodes)
1073 : {
1074 5 : CPLAddXMLChild(psTree, CPLCloneXMLTree(poOtherNode.get()));
1075 : }
1076 :
1077 : /* -------------------------------------------------------------------- */
1078 : /* Try saving the auxiliary metadata. */
1079 : /* -------------------------------------------------------------------- */
1080 :
1081 1296 : CPLPushErrorHandler(CPLQuietErrorHandler);
1082 1296 : const int bSaved = CPLSerializeXMLTreeToFile(psTree, psPam->pszPamFilename);
1083 1296 : CPLPopErrorHandler();
1084 :
1085 : /* -------------------------------------------------------------------- */
1086 : /* If it fails, check if we have a proxy directory for auxiliary */
1087 : /* metadata to be stored in, and try to save there. */
1088 : /* -------------------------------------------------------------------- */
1089 1296 : CPLErr eErr = CE_None;
1090 :
1091 1296 : if (bSaved)
1092 1266 : eErr = CE_None;
1093 : else
1094 : {
1095 30 : const char *pszBasename = GetDescription();
1096 :
1097 30 : if (psPam->osPhysicalFilename.length() > 0)
1098 10 : pszBasename = psPam->osPhysicalFilename;
1099 :
1100 30 : const char *pszNewPam = nullptr;
1101 60 : if (PamGetProxy(pszBasename) == nullptr &&
1102 30 : ((pszNewPam = PamAllocateProxy(pszBasename)) != nullptr))
1103 : {
1104 1 : CPLErrorReset();
1105 1 : CPLFree(psPam->pszPamFilename);
1106 1 : psPam->pszPamFilename = CPLStrdup(pszNewPam);
1107 1 : eErr = TrySaveXML();
1108 : }
1109 : /* No way we can save into a /vsicurl resource */
1110 29 : else if (!STARTS_WITH(psPam->pszPamFilename, "/vsicurl"))
1111 : {
1112 29 : CPLError(CE_Warning, CPLE_AppDefined,
1113 : "Unable to save auxiliary information in %s.",
1114 29 : psPam->pszPamFilename);
1115 29 : eErr = CE_Warning;
1116 : }
1117 : }
1118 :
1119 : /* -------------------------------------------------------------------- */
1120 : /* Cleanup */
1121 : /* -------------------------------------------------------------------- */
1122 1296 : CPLDestroyXMLNode(psTree);
1123 :
1124 1296 : return eErr;
1125 : }
1126 :
1127 : /************************************************************************/
1128 : /* CloneInfo() */
1129 : /************************************************************************/
1130 :
1131 7091 : CPLErr GDALPamDataset::CloneInfo(GDALDataset *poSrcDS, int nCloneFlags)
1132 :
1133 : {
1134 7091 : const int bOnlyIfMissing = nCloneFlags & GCIF_ONLY_IF_MISSING;
1135 7091 : const int nSavedMOFlags = GetMOFlags();
1136 :
1137 7091 : PamInitialize();
1138 :
1139 : /* -------------------------------------------------------------------- */
1140 : /* Suppress NotImplemented error messages - mainly needed if PAM */
1141 : /* disabled. */
1142 : /* -------------------------------------------------------------------- */
1143 7091 : SetMOFlags(nSavedMOFlags | GMO_IGNORE_UNIMPLEMENTED);
1144 :
1145 : /* -------------------------------------------------------------------- */
1146 : /* GeoTransform */
1147 : /* -------------------------------------------------------------------- */
1148 7091 : if (nCloneFlags & GCIF_GEOTRANSFORM)
1149 : {
1150 7089 : double adfGeoTransform[6] = {0.0};
1151 :
1152 7089 : if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
1153 : {
1154 2180 : double adfOldGT[6] = {0.0};
1155 :
1156 2180 : if (!bOnlyIfMissing || GetGeoTransform(adfOldGT) != CE_None)
1157 226 : SetGeoTransform(adfGeoTransform);
1158 : }
1159 : }
1160 :
1161 : /* -------------------------------------------------------------------- */
1162 : /* Projection */
1163 : /* -------------------------------------------------------------------- */
1164 7091 : if (nCloneFlags & GCIF_PROJECTION)
1165 : {
1166 7089 : const auto poSRS = poSrcDS->GetSpatialRef();
1167 :
1168 7089 : if (poSRS != nullptr)
1169 : {
1170 1944 : if (!bOnlyIfMissing || GetSpatialRef() == nullptr)
1171 177 : SetSpatialRef(poSRS);
1172 : }
1173 : }
1174 :
1175 : /* -------------------------------------------------------------------- */
1176 : /* GCPs */
1177 : /* -------------------------------------------------------------------- */
1178 7091 : if (nCloneFlags & GCIF_GCPS)
1179 : {
1180 7091 : if (poSrcDS->GetGCPCount() > 0)
1181 : {
1182 19 : if (!bOnlyIfMissing || GetGCPCount() == 0)
1183 : {
1184 2 : SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(),
1185 2 : poSrcDS->GetGCPSpatialRef());
1186 : }
1187 : }
1188 : }
1189 :
1190 : /* -------------------------------------------------------------------- */
1191 : /* Metadata */
1192 : /* -------------------------------------------------------------------- */
1193 7091 : if (nCloneFlags & GCIF_METADATA)
1194 : {
1195 12475 : for (const char *pszMDD : {"", "RPC", "json:ISIS3", "json:VICAR"})
1196 : {
1197 9980 : auto papszSrcMD = poSrcDS->GetMetadata(pszMDD);
1198 9980 : if (papszSrcMD != nullptr)
1199 : {
1200 3806 : if (!bOnlyIfMissing ||
1201 1903 : CSLCount(GetMetadata(pszMDD)) != CSLCount(papszSrcMD))
1202 : {
1203 447 : SetMetadata(papszSrcMD, pszMDD);
1204 : }
1205 : }
1206 : }
1207 : }
1208 :
1209 : /* -------------------------------------------------------------------- */
1210 : /* Process bands. */
1211 : /* -------------------------------------------------------------------- */
1212 7091 : if (nCloneFlags & GCIF_PROCESS_BANDS)
1213 : {
1214 25768 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
1215 : {
1216 18677 : GDALRasterBand *poBand = GetRasterBand(iBand + 1);
1217 :
1218 18677 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
1219 0 : continue;
1220 :
1221 18677 : if (poSrcDS->GetRasterCount() >= iBand + 1)
1222 : {
1223 37352 : cpl::down_cast<GDALPamRasterBand *>(poBand)->CloneInfo(
1224 18676 : poSrcDS->GetRasterBand(iBand + 1), nCloneFlags);
1225 : }
1226 : else
1227 1 : CPLDebug("GDALPamDataset",
1228 : "Skipping CloneInfo for band not in source, "
1229 : "this is a bit unusual!");
1230 : }
1231 : }
1232 :
1233 : /* -------------------------------------------------------------------- */
1234 : /* Copy masks. These are really copied at a lower level using */
1235 : /* GDALDefaultOverviews, for formats with no native mask */
1236 : /* support but this is a convenient central point to put this */
1237 : /* for most drivers. */
1238 : /* -------------------------------------------------------------------- */
1239 7091 : if (nCloneFlags & GCIF_MASK)
1240 : {
1241 5182 : GDALDriver::DefaultCopyMasks(poSrcDS, this, FALSE);
1242 : }
1243 :
1244 : /* -------------------------------------------------------------------- */
1245 : /* Restore MO flags. */
1246 : /* -------------------------------------------------------------------- */
1247 7091 : SetMOFlags(nSavedMOFlags);
1248 :
1249 7091 : return CE_None;
1250 : }
1251 :
1252 : //! @endcond
1253 :
1254 : /************************************************************************/
1255 : /* GetFileList() */
1256 : /* */
1257 : /* Add .aux.xml or .aux file into file list as appropriate. */
1258 : /************************************************************************/
1259 :
1260 4156 : char **GDALPamDataset::GetFileList()
1261 :
1262 : {
1263 4156 : char **papszFileList = GDALDataset::GetFileList();
1264 :
1265 4068 : if (psPam && !psPam->osPhysicalFilename.empty() &&
1266 8563 : GDALCanReliablyUseSiblingFileList(psPam->osPhysicalFilename.c_str()) &&
1267 339 : CSLFindString(papszFileList, psPam->osPhysicalFilename) == -1)
1268 : {
1269 : papszFileList =
1270 11 : CSLInsertString(papszFileList, 0, psPam->osPhysicalFilename);
1271 : }
1272 :
1273 4156 : if (psPam && psPam->pszPamFilename)
1274 : {
1275 4043 : int bAddPamFile = nPamFlags & GPF_DIRTY;
1276 4043 : if (!bAddPamFile)
1277 : {
1278 : VSIStatBufL sStatBuf;
1279 4043 : if (oOvManager.GetSiblingFiles() != nullptr &&
1280 7046 : IsPamFilenameAPotentialSiblingFile() &&
1281 3003 : GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
1282 : {
1283 3003 : bAddPamFile =
1284 3003 : CSLFindString(oOvManager.GetSiblingFiles(),
1285 6006 : CPLGetFilename(psPam->pszPamFilename)) >= 0;
1286 : }
1287 : else
1288 : {
1289 1040 : bAddPamFile = VSIStatExL(psPam->pszPamFilename, &sStatBuf,
1290 1040 : VSI_STAT_EXISTS_FLAG) == 0;
1291 : }
1292 : }
1293 4043 : if (bAddPamFile)
1294 : {
1295 316 : papszFileList = CSLAddString(papszFileList, psPam->pszPamFilename);
1296 : }
1297 : }
1298 :
1299 4068 : if (psPam && !psPam->osAuxFilename.empty() &&
1300 8226 : GDALCanReliablyUseSiblingFileList(psPam->osAuxFilename.c_str()) &&
1301 2 : CSLFindString(papszFileList, psPam->osAuxFilename) == -1)
1302 : {
1303 1 : papszFileList = CSLAddString(papszFileList, psPam->osAuxFilename);
1304 : }
1305 4156 : return papszFileList;
1306 : }
1307 :
1308 : /************************************************************************/
1309 : /* IBuildOverviews() */
1310 : /************************************************************************/
1311 :
1312 : //! @cond Doxygen_Suppress
1313 34 : CPLErr GDALPamDataset::IBuildOverviews(
1314 : const char *pszResampling, int nOverviews, const int *panOverviewList,
1315 : int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
1316 : void *pProgressData, CSLConstList papszOptions)
1317 :
1318 : {
1319 : /* -------------------------------------------------------------------- */
1320 : /* Initialize PAM. */
1321 : /* -------------------------------------------------------------------- */
1322 34 : PamInitialize();
1323 34 : if (psPam == nullptr)
1324 0 : return GDALDataset::IBuildOverviews(
1325 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1326 0 : pfnProgress, pProgressData, papszOptions);
1327 :
1328 : /* -------------------------------------------------------------------- */
1329 : /* If we appear to have subdatasets and to have a physical */
1330 : /* filename, use that physical filename to derive a name for a */
1331 : /* new overview file. */
1332 : /* -------------------------------------------------------------------- */
1333 34 : if (oOvManager.IsInitialized() && psPam->osPhysicalFilename.length() != 0)
1334 : {
1335 9 : return oOvManager.BuildOverviewsSubDataset(
1336 9 : psPam->osPhysicalFilename, pszResampling, nOverviews,
1337 : panOverviewList, nListBands, panBandList, pfnProgress,
1338 9 : pProgressData, papszOptions);
1339 : }
1340 :
1341 25 : return GDALDataset::IBuildOverviews(
1342 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1343 25 : pfnProgress, pProgressData, papszOptions);
1344 : }
1345 :
1346 : //! @endcond
1347 :
1348 : /************************************************************************/
1349 : /* GetSpatialRef() */
1350 : /************************************************************************/
1351 :
1352 13610 : const OGRSpatialReference *GDALPamDataset::GetSpatialRef() const
1353 :
1354 : {
1355 13610 : if (psPam && psPam->poSRS)
1356 95 : return psPam->poSRS;
1357 :
1358 13515 : return GDALDataset::GetSpatialRef();
1359 : }
1360 :
1361 : /************************************************************************/
1362 : /* SetSpatialRef() */
1363 : /************************************************************************/
1364 :
1365 553 : CPLErr GDALPamDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1366 :
1367 : {
1368 553 : PamInitialize();
1369 :
1370 553 : if (psPam == nullptr)
1371 0 : return GDALDataset::SetSpatialRef(poSRS);
1372 :
1373 553 : if (psPam->poSRS)
1374 6 : psPam->poSRS->Release();
1375 553 : psPam->poSRS = poSRS ? poSRS->Clone() : nullptr;
1376 553 : MarkPamDirty();
1377 :
1378 553 : return CE_None;
1379 : }
1380 :
1381 : /************************************************************************/
1382 : /* GetGeoTransform() */
1383 : /************************************************************************/
1384 :
1385 12274 : CPLErr GDALPamDataset::GetGeoTransform(double *padfTransform)
1386 :
1387 : {
1388 12274 : if (psPam && psPam->bHaveGeoTransform)
1389 : {
1390 209 : memcpy(padfTransform, psPam->adfGeoTransform.data(),
1391 : sizeof(psPam->adfGeoTransform));
1392 209 : return CE_None;
1393 : }
1394 :
1395 12065 : return GDALDataset::GetGeoTransform(padfTransform);
1396 : }
1397 :
1398 : /************************************************************************/
1399 : /* SetGeoTransform() */
1400 : /************************************************************************/
1401 :
1402 449 : CPLErr GDALPamDataset::SetGeoTransform(double *padfTransform)
1403 :
1404 : {
1405 449 : PamInitialize();
1406 :
1407 449 : if (psPam)
1408 : {
1409 449 : MarkPamDirty();
1410 449 : psPam->bHaveGeoTransform = TRUE;
1411 449 : memcpy(psPam->adfGeoTransform.data(), padfTransform,
1412 : sizeof(psPam->adfGeoTransform));
1413 449 : return (CE_None);
1414 : }
1415 :
1416 0 : return GDALDataset::SetGeoTransform(padfTransform);
1417 : }
1418 :
1419 : /************************************************************************/
1420 : /* DeleteGeoTransform() */
1421 : /************************************************************************/
1422 :
1423 : /** Remove geotransform from PAM.
1424 : *
1425 : * @since GDAL 3.4.1
1426 : */
1427 1512 : void GDALPamDataset::DeleteGeoTransform()
1428 :
1429 : {
1430 1512 : PamInitialize();
1431 :
1432 1512 : if (psPam && psPam->bHaveGeoTransform)
1433 : {
1434 3 : MarkPamDirty();
1435 3 : psPam->bHaveGeoTransform = FALSE;
1436 : }
1437 1512 : }
1438 :
1439 : /************************************************************************/
1440 : /* GetGCPCount() */
1441 : /************************************************************************/
1442 :
1443 12489 : int GDALPamDataset::GetGCPCount()
1444 :
1445 : {
1446 12489 : if (psPam && !psPam->asGCPs.empty())
1447 45 : return static_cast<int>(psPam->asGCPs.size());
1448 :
1449 12443 : return GDALDataset::GetGCPCount();
1450 : }
1451 :
1452 : /************************************************************************/
1453 : /* GetGCPSpatialRef() */
1454 : /************************************************************************/
1455 :
1456 63 : const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const
1457 :
1458 : {
1459 63 : if (psPam && psPam->poGCP_SRS != nullptr)
1460 24 : return psPam->poGCP_SRS;
1461 :
1462 39 : return GDALDataset::GetGCPSpatialRef();
1463 : }
1464 :
1465 : /************************************************************************/
1466 : /* GetGCPs() */
1467 : /************************************************************************/
1468 :
1469 37 : const GDAL_GCP *GDALPamDataset::GetGCPs()
1470 :
1471 : {
1472 37 : if (psPam && !psPam->asGCPs.empty())
1473 27 : return gdal::GCP::c_ptr(psPam->asGCPs);
1474 :
1475 10 : return GDALDataset::GetGCPs();
1476 : }
1477 :
1478 : /************************************************************************/
1479 : /* SetGCPs() */
1480 : /************************************************************************/
1481 :
1482 31 : CPLErr GDALPamDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
1483 : const OGRSpatialReference *poGCP_SRS)
1484 :
1485 : {
1486 31 : PamInitialize();
1487 :
1488 31 : if (psPam)
1489 : {
1490 31 : if (psPam->poGCP_SRS)
1491 1 : psPam->poGCP_SRS->Release();
1492 31 : psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr;
1493 31 : psPam->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
1494 31 : MarkPamDirty();
1495 :
1496 31 : return CE_None;
1497 : }
1498 :
1499 0 : return GDALDataset::SetGCPs(nGCPCount, pasGCPList, poGCP_SRS);
1500 : }
1501 :
1502 : /************************************************************************/
1503 : /* SetMetadata() */
1504 : /************************************************************************/
1505 :
1506 3659 : CPLErr GDALPamDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
1507 :
1508 : {
1509 3659 : PamInitialize();
1510 :
1511 3659 : if (psPam)
1512 : {
1513 3659 : psPam->bHasMetadata = TRUE;
1514 3659 : MarkPamDirty();
1515 : }
1516 :
1517 3659 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1518 : }
1519 :
1520 : /************************************************************************/
1521 : /* SetMetadataItem() */
1522 : /************************************************************************/
1523 :
1524 37858 : CPLErr GDALPamDataset::SetMetadataItem(const char *pszName,
1525 : const char *pszValue,
1526 : const char *pszDomain)
1527 :
1528 : {
1529 37858 : PamInitialize();
1530 :
1531 37858 : if (psPam)
1532 : {
1533 37858 : psPam->bHasMetadata = TRUE;
1534 37858 : MarkPamDirty();
1535 : }
1536 :
1537 37858 : return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
1538 : }
1539 :
1540 : /************************************************************************/
1541 : /* GetMetadataItem() */
1542 : /************************************************************************/
1543 :
1544 17051 : const char *GDALPamDataset::GetMetadataItem(const char *pszName,
1545 : const char *pszDomain)
1546 :
1547 : {
1548 : /* -------------------------------------------------------------------- */
1549 : /* A request against the ProxyOverviewRequest is a special */
1550 : /* mechanism to request an overview filename be allocated in */
1551 : /* the proxy pool location. The allocated name is saved as */
1552 : /* metadata as well as being returned. */
1553 : /* -------------------------------------------------------------------- */
1554 17051 : if (pszDomain != nullptr && EQUAL(pszDomain, "ProxyOverviewRequest"))
1555 : {
1556 8 : CPLString osPrelimOvr = GetDescription();
1557 4 : osPrelimOvr += ":::OVR";
1558 :
1559 4 : const char *pszProxyOvrFilename = PamAllocateProxy(osPrelimOvr);
1560 4 : if (pszProxyOvrFilename == nullptr)
1561 3 : return nullptr;
1562 :
1563 1 : SetMetadataItem("OVERVIEW_FILE", pszProxyOvrFilename, "OVERVIEWS");
1564 :
1565 1 : return pszProxyOvrFilename;
1566 : }
1567 :
1568 : /* -------------------------------------------------------------------- */
1569 : /* If the OVERVIEW_FILE metadata is requested, we intercept the */
1570 : /* request in order to replace ":::BASE:::" with the path to */
1571 : /* the physical file - if available. This is primarily for the */
1572 : /* purpose of managing subdataset overview filenames as being */
1573 : /* relative to the physical file the subdataset comes */
1574 : /* from. (#3287). */
1575 : /* -------------------------------------------------------------------- */
1576 17047 : else if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") &&
1577 5542 : EQUAL(pszName, "OVERVIEW_FILE"))
1578 : {
1579 : const char *pszOverviewFile =
1580 5542 : GDALDataset::GetMetadataItem(pszName, pszDomain);
1581 :
1582 5542 : if (pszOverviewFile == nullptr ||
1583 26 : !STARTS_WITH_CI(pszOverviewFile, ":::BASE:::"))
1584 5517 : return pszOverviewFile;
1585 :
1586 50 : CPLString osPath;
1587 :
1588 25 : if (strlen(GetPhysicalFilename()) > 0)
1589 25 : osPath = CPLGetPath(GetPhysicalFilename());
1590 : else
1591 0 : osPath = CPLGetPath(GetDescription());
1592 :
1593 25 : return CPLFormFilename(osPath, pszOverviewFile + 10, nullptr);
1594 : }
1595 :
1596 : /* -------------------------------------------------------------------- */
1597 : /* Everything else is a pass through. */
1598 : /* -------------------------------------------------------------------- */
1599 :
1600 11505 : return GDALDataset::GetMetadataItem(pszName, pszDomain);
1601 : }
1602 :
1603 : /************************************************************************/
1604 : /* GetMetadata() */
1605 : /************************************************************************/
1606 :
1607 13894 : char **GDALPamDataset::GetMetadata(const char *pszDomain)
1608 :
1609 : {
1610 : // if( pszDomain == nullptr || !EQUAL(pszDomain,"ProxyOverviewRequest") )
1611 13894 : return GDALDataset::GetMetadata(pszDomain);
1612 : }
1613 :
1614 : /************************************************************************/
1615 : /* TryLoadAux() */
1616 : /************************************************************************/
1617 :
1618 : //! @cond Doxygen_Suppress
1619 30648 : CPLErr GDALPamDataset::TryLoadAux(CSLConstList papszSiblingFiles)
1620 :
1621 : {
1622 : /* -------------------------------------------------------------------- */
1623 : /* Initialize PAM. */
1624 : /* -------------------------------------------------------------------- */
1625 30648 : PamInitialize();
1626 :
1627 30648 : if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
1628 0 : return CE_None;
1629 :
1630 : /* -------------------------------------------------------------------- */
1631 : /* What is the name of the physical file we are referencing? */
1632 : /* We allow an override via the psPam->pszPhysicalFile item. */
1633 : /* -------------------------------------------------------------------- */
1634 30648 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
1635 :
1636 30648 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
1637 29237 : pszPhysicalFile = GetDescription();
1638 :
1639 30648 : if (strlen(pszPhysicalFile) == 0)
1640 0 : return CE_None;
1641 :
1642 30648 : if (papszSiblingFiles && GDALCanReliablyUseSiblingFileList(pszPhysicalFile))
1643 : {
1644 23368 : CPLString osAuxFilename = CPLResetExtension(pszPhysicalFile, "aux");
1645 : int iSibling =
1646 23368 : CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
1647 23368 : if (iSibling < 0)
1648 : {
1649 23363 : osAuxFilename = pszPhysicalFile;
1650 23363 : osAuxFilename += ".aux";
1651 : iSibling =
1652 23363 : CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
1653 23363 : if (iSibling < 0)
1654 23363 : return CE_None;
1655 : }
1656 : }
1657 :
1658 : /* -------------------------------------------------------------------- */
1659 : /* Try to open .aux file. */
1660 : /* -------------------------------------------------------------------- */
1661 : GDALDataset *poAuxDS =
1662 7285 : GDALFindAssociatedAuxFile(pszPhysicalFile, GA_ReadOnly, this);
1663 :
1664 7285 : if (poAuxDS == nullptr)
1665 7277 : return CE_None;
1666 :
1667 8 : psPam->osAuxFilename = poAuxDS->GetDescription();
1668 :
1669 : /* -------------------------------------------------------------------- */
1670 : /* Do we have an SRS on the aux file? */
1671 : /* -------------------------------------------------------------------- */
1672 8 : if (strlen(poAuxDS->GetProjectionRef()) > 0)
1673 1 : GDALPamDataset::SetProjection(poAuxDS->GetProjectionRef());
1674 :
1675 : /* -------------------------------------------------------------------- */
1676 : /* Geotransform. */
1677 : /* -------------------------------------------------------------------- */
1678 8 : if (poAuxDS->GetGeoTransform(psPam->adfGeoTransform.data()) == CE_None)
1679 1 : psPam->bHaveGeoTransform = TRUE;
1680 :
1681 : /* -------------------------------------------------------------------- */
1682 : /* GCPs */
1683 : /* -------------------------------------------------------------------- */
1684 8 : if (poAuxDS->GetGCPCount() > 0)
1685 : {
1686 0 : psPam->asGCPs =
1687 0 : gdal::GCP::fromC(poAuxDS->GetGCPs(), poAuxDS->GetGCPCount());
1688 : }
1689 :
1690 : /* -------------------------------------------------------------------- */
1691 : /* Apply metadata. We likely ought to be merging this in rather */
1692 : /* than overwriting everything that was there. */
1693 : /* -------------------------------------------------------------------- */
1694 8 : char **papszMD = poAuxDS->GetMetadata();
1695 8 : if (CSLCount(papszMD) > 0)
1696 : {
1697 0 : char **papszMerged = CSLMerge(CSLDuplicate(GetMetadata()), papszMD);
1698 0 : GDALPamDataset::SetMetadata(papszMerged);
1699 0 : CSLDestroy(papszMerged);
1700 : }
1701 :
1702 8 : papszMD = poAuxDS->GetMetadata("XFORMS");
1703 8 : if (CSLCount(papszMD) > 0)
1704 : {
1705 : char **papszMerged =
1706 0 : CSLMerge(CSLDuplicate(GetMetadata("XFORMS")), papszMD);
1707 0 : GDALPamDataset::SetMetadata(papszMerged, "XFORMS");
1708 0 : CSLDestroy(papszMerged);
1709 : }
1710 :
1711 : /* ==================================================================== */
1712 : /* Process bands. */
1713 : /* ==================================================================== */
1714 18 : for (int iBand = 0; iBand < poAuxDS->GetRasterCount(); iBand++)
1715 : {
1716 10 : if (iBand >= GetRasterCount())
1717 0 : break;
1718 :
1719 10 : GDALRasterBand *const poAuxBand = poAuxDS->GetRasterBand(iBand + 1);
1720 10 : GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
1721 :
1722 10 : papszMD = poAuxBand->GetMetadata();
1723 10 : if (CSLCount(papszMD) > 0)
1724 : {
1725 : char **papszMerged =
1726 10 : CSLMerge(CSLDuplicate(poBand->GetMetadata()), papszMD);
1727 10 : poBand->SetMetadata(papszMerged);
1728 10 : CSLDestroy(papszMerged);
1729 : }
1730 :
1731 10 : if (strlen(poAuxBand->GetDescription()) > 0)
1732 10 : poBand->SetDescription(poAuxBand->GetDescription());
1733 :
1734 10 : if (poAuxBand->GetCategoryNames() != nullptr)
1735 0 : poBand->SetCategoryNames(poAuxBand->GetCategoryNames());
1736 :
1737 10 : if (poAuxBand->GetColorTable() != nullptr &&
1738 0 : poBand->GetColorTable() == nullptr)
1739 0 : poBand->SetColorTable(poAuxBand->GetColorTable());
1740 :
1741 : // histograms?
1742 10 : double dfMin = 0.0;
1743 10 : double dfMax = 0.0;
1744 10 : int nBuckets = 0;
1745 10 : GUIntBig *panHistogram = nullptr;
1746 :
1747 20 : if (poAuxBand->GetDefaultHistogram(&dfMin, &dfMax, &nBuckets,
1748 : &panHistogram, FALSE, nullptr,
1749 10 : nullptr) == CE_None)
1750 : {
1751 5 : poBand->SetDefaultHistogram(dfMin, dfMax, nBuckets, panHistogram);
1752 5 : CPLFree(panHistogram);
1753 : }
1754 :
1755 : // RAT
1756 10 : if (poAuxBand->GetDefaultRAT() != nullptr)
1757 10 : poBand->SetDefaultRAT(poAuxBand->GetDefaultRAT());
1758 :
1759 : // NoData
1760 10 : int bSuccess = FALSE;
1761 10 : const double dfNoDataValue = poAuxBand->GetNoDataValue(&bSuccess);
1762 10 : if (bSuccess)
1763 2 : poBand->SetNoDataValue(dfNoDataValue);
1764 : }
1765 :
1766 8 : GDALClose(poAuxDS);
1767 :
1768 : /* -------------------------------------------------------------------- */
1769 : /* Mark PAM info as clean. */
1770 : /* -------------------------------------------------------------------- */
1771 8 : nPamFlags &= ~GPF_DIRTY;
1772 :
1773 8 : return CE_Failure;
1774 : }
1775 :
1776 : //! @endcond
1777 :
1778 : /************************************************************************/
1779 : /* ClearStatistics() */
1780 : /************************************************************************/
1781 :
1782 2 : void GDALPamDataset::ClearStatistics()
1783 : {
1784 2 : PamInitialize();
1785 2 : if (!psPam)
1786 0 : return;
1787 3 : for (int i = 1; i <= nBands; ++i)
1788 : {
1789 1 : bool bChanged = false;
1790 1 : GDALRasterBand *poBand = GetRasterBand(i);
1791 2 : CPLStringList aosNewMD;
1792 5 : for (const char *pszStr :
1793 11 : cpl::Iterate(static_cast<CSLConstList>(poBand->GetMetadata())))
1794 : {
1795 5 : if (STARTS_WITH_CI(pszStr, "STATISTICS_"))
1796 : {
1797 5 : MarkPamDirty();
1798 5 : bChanged = true;
1799 : }
1800 : else
1801 : {
1802 0 : aosNewMD.AddString(pszStr);
1803 : }
1804 : }
1805 1 : if (bChanged)
1806 : {
1807 1 : poBand->SetMetadata(aosNewMD.List());
1808 : }
1809 : }
1810 :
1811 2 : GDALDataset::ClearStatistics();
1812 : }
|