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