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