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 72922 : GDALPamDataset::GDALPamDataset()
140 : {
141 72764 : SetMOFlags(GetMOFlags() | GMO_PAM_CLASS);
142 72691 : }
143 :
144 : /************************************************************************/
145 : /* ~GDALPamDataset() */
146 : /************************************************************************/
147 :
148 72820 : GDALPamDataset::~GDALPamDataset()
149 :
150 : {
151 72855 : GDALPamDataset::Close();
152 :
153 72828 : PamClear();
154 72928 : }
155 :
156 : /************************************************************************/
157 : /* Close() */
158 : /************************************************************************/
159 :
160 130366 : CPLErr GDALPamDataset::Close()
161 : {
162 130366 : CPLErr eErr = CE_None;
163 130366 : if (nOpenFlags != OPEN_FLAGS_CLOSED)
164 : {
165 72925 : if (IsMarkedSuppressOnClose())
166 : {
167 482 : if (psPam && psPam->pszPamFilename != nullptr)
168 19 : VSIUnlink(psPam->pszPamFilename);
169 : }
170 72444 : else if (nPamFlags & GPF_DIRTY)
171 : {
172 1137 : CPLDebug("GDALPamDataset", "In Close() with dirty metadata.");
173 1137 : eErr = GDALPamDataset::TrySaveXML();
174 : }
175 :
176 72926 : if (GDALDataset::Close() != CE_None)
177 0 : eErr = CE_Failure;
178 : }
179 130359 : return eErr;
180 : }
181 :
182 : /************************************************************************/
183 : /* FlushCache() */
184 : /************************************************************************/
185 :
186 76627 : CPLErr GDALPamDataset::FlushCache(bool bAtClosing)
187 :
188 : {
189 76627 : CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
190 76626 : if (nPamFlags & GPF_DIRTY)
191 : {
192 6665 : if (TrySaveXML() != CE_None)
193 26 : eErr = CE_Failure;
194 : }
195 76626 : return eErr;
196 : }
197 :
198 : /************************************************************************/
199 : /* MarkPamDirty() */
200 : /************************************************************************/
201 :
202 : //! @cond Doxygen_Suppress
203 66657 : void GDALPamDataset::MarkPamDirty()
204 : {
205 89458 : if ((nPamFlags & GPF_DIRTY) == 0 &&
206 22797 : CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLE_MARK_DIRTY", "YES")))
207 : {
208 22767 : nPamFlags |= GPF_DIRTY;
209 : }
210 66661 : }
211 :
212 : // @endcond
213 :
214 : /************************************************************************/
215 : /* SerializeToXML() */
216 : /************************************************************************/
217 :
218 : //! @cond Doxygen_Suppress
219 1554 : CPLXMLNode *GDALPamDataset::SerializeToXML(const char *pszUnused)
220 :
221 : {
222 1554 : if (psPam == nullptr)
223 0 : return nullptr;
224 :
225 : /* -------------------------------------------------------------------- */
226 : /* Setup root node and attributes. */
227 : /* -------------------------------------------------------------------- */
228 1554 : CPLXMLNode *psDSTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
229 :
230 : /* -------------------------------------------------------------------- */
231 : /* SRS */
232 : /* -------------------------------------------------------------------- */
233 1554 : if (psPam->poSRS && !psPam->poSRS->IsEmpty())
234 : {
235 328 : char *pszWKT = nullptr;
236 : {
237 656 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
238 328 : 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 328 : CPLCreateXMLElementAndValue(psDSTree, "SRS", pszWKT);
248 328 : CPLFree(pszWKT);
249 328 : const auto &mapping = psPam->poSRS->GetDataAxisToSRSAxisMapping();
250 656 : CPLString osMapping;
251 985 : for (size_t i = 0; i < mapping.size(); ++i)
252 : {
253 657 : if (!osMapping.empty())
254 329 : osMapping += ",";
255 657 : osMapping += CPLSPrintf("%d", mapping[i]);
256 : }
257 328 : CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
258 : osMapping.c_str());
259 :
260 328 : const double dfCoordinateEpoch = psPam->poSRS->GetCoordinateEpoch();
261 328 : 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 1554 : if (psPam->bHaveGeoTransform)
278 : {
279 686 : CPLString oFmt;
280 : oFmt.Printf("%24.16e,%24.16e,%24.16e,%24.16e,%24.16e,%24.16e",
281 1372 : psPam->gt[0], psPam->gt[1], psPam->gt[2], psPam->gt[3],
282 343 : psPam->gt[4], psPam->gt[5]);
283 343 : CPLSetXMLValue(psDSTree, "GeoTransform", oFmt);
284 : }
285 :
286 : /* -------------------------------------------------------------------- */
287 : /* Metadata. */
288 : /* -------------------------------------------------------------------- */
289 1554 : if (psPam->bHasMetadata)
290 : {
291 1208 : CPLXMLNode *psMD = oMDMD.Serialize();
292 1208 : if (psMD != nullptr)
293 : {
294 1017 : CPLAddXMLChild(psDSTree, psMD);
295 : }
296 : }
297 :
298 : /* -------------------------------------------------------------------- */
299 : /* GCPs */
300 : /* -------------------------------------------------------------------- */
301 1554 : 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 1554 : CPLXMLNode *psLastChild = psDSTree->psChild;
312 2465 : for (; psLastChild != nullptr && psLastChild->psNext;
313 911 : psLastChild = psLastChild->psNext)
314 : {
315 : }
316 :
317 3891 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
318 : {
319 2337 : GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
320 :
321 2337 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
322 5 : continue;
323 :
324 : CPLXMLNode *const psBandTree =
325 2332 : cpl::down_cast<GDALPamRasterBand *>(poBand)->SerializeToXML(
326 2332 : pszUnused);
327 :
328 2332 : if (psBandTree != nullptr)
329 : {
330 672 : if (psLastChild == nullptr)
331 : {
332 178 : CPLAddXMLChild(psDSTree, psBandTree);
333 : }
334 : else
335 : {
336 494 : psLastChild->psNext = psBandTree;
337 : }
338 672 : psLastChild = psBandTree;
339 : }
340 : }
341 :
342 : /* -------------------------------------------------------------------- */
343 : /* We don't want to return anything if we had no metadata to */
344 : /* attach. */
345 : /* -------------------------------------------------------------------- */
346 1554 : if (psDSTree->psChild == nullptr)
347 : {
348 239 : CPLDestroyXMLNode(psDSTree);
349 239 : psDSTree = nullptr;
350 : }
351 :
352 1554 : return psDSTree;
353 : }
354 :
355 : /************************************************************************/
356 : /* PamInitialize() */
357 : /************************************************************************/
358 :
359 788704 : void GDALPamDataset::PamInitialize()
360 :
361 : {
362 : #ifdef PAM_ENABLED
363 788704 : const char *const pszPamDefault = "YES";
364 : #else
365 : const char *const pszPamDefault = "NO";
366 : #endif
367 :
368 788704 : if (psPam)
369 740278 : return;
370 :
371 48426 : if (!CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLED", pszPamDefault)))
372 : {
373 1225 : CPLDebugOnce("GDAL", "PAM is disabled");
374 1225 : nPamFlags |= GPF_DISABLED;
375 : }
376 :
377 : /* ERO 2011/04/13 : GPF_AUXMODE seems to be unimplemented */
378 48415 : if (EQUAL(CPLGetConfigOption("GDAL_PAM_MODE", "PAM"), "AUX"))
379 0 : nPamFlags |= GPF_AUXMODE;
380 :
381 48415 : psPam = new GDALDatasetPamInfo;
382 695016 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
383 : {
384 646601 : GDALRasterBand *poBand = GetRasterBand(iBand + 1);
385 :
386 646611 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
387 138 : continue;
388 :
389 646465 : cpl::down_cast<GDALPamRasterBand *>(poBand)->PamInitialize();
390 : }
391 : }
392 :
393 : /************************************************************************/
394 : /* PamClear() */
395 : /************************************************************************/
396 :
397 72901 : void GDALPamDataset::PamClear()
398 :
399 : {
400 72901 : if (psPam)
401 : {
402 48408 : CPLFree(psPam->pszPamFilename);
403 48407 : if (psPam->poSRS)
404 645 : psPam->poSRS->Release();
405 48407 : if (psPam->poGCP_SRS)
406 36 : psPam->poGCP_SRS->Release();
407 :
408 48407 : delete psPam;
409 48403 : psPam = nullptr;
410 : }
411 72896 : }
412 :
413 : /************************************************************************/
414 : /* XMLInit() */
415 : /************************************************************************/
416 :
417 1396 : CPLErr GDALPamDataset::XMLInit(const CPLXMLNode *psTree, const char *pszUnused)
418 :
419 : {
420 : /* -------------------------------------------------------------------- */
421 : /* Check for an SRS node. */
422 : /* -------------------------------------------------------------------- */
423 1396 : if (const CPLXMLNode *psSRSNode = CPLGetXMLNode(psTree, "SRS"))
424 : {
425 369 : if (psPam->poSRS)
426 99 : psPam->poSRS->Release();
427 369 : psPam->poSRS = new OGRSpatialReference();
428 369 : psPam->poSRS->SetFromUserInput(
429 : CPLGetXMLValue(psSRSNode, nullptr, ""),
430 : OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS);
431 : const char *pszMapping =
432 369 : CPLGetXMLValue(psSRSNode, "dataAxisToSRSAxisMapping", nullptr);
433 369 : if (pszMapping)
434 : {
435 : char **papszTokens =
436 291 : CSLTokenizeStringComplex(pszMapping, ",", FALSE, FALSE);
437 582 : std::vector<int> anMapping;
438 874 : for (int i = 0; papszTokens && papszTokens[i]; i++)
439 : {
440 583 : anMapping.push_back(atoi(papszTokens[i]));
441 : }
442 291 : CSLDestroy(papszTokens);
443 291 : psPam->poSRS->SetDataAxisToSRSAxisMapping(anMapping);
444 : }
445 : else
446 : {
447 78 : psPam->poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
448 : }
449 :
450 : const char *pszCoordinateEpoch =
451 369 : CPLGetXMLValue(psSRSNode, "coordinateEpoch", nullptr);
452 369 : if (pszCoordinateEpoch)
453 2 : psPam->poSRS->SetCoordinateEpoch(CPLAtof(pszCoordinateEpoch));
454 : }
455 :
456 : /* -------------------------------------------------------------------- */
457 : /* Check for a GeoTransform node. */
458 : /* -------------------------------------------------------------------- */
459 1396 : const char *pszGT = CPLGetXMLValue(psTree, "GeoTransform", "");
460 1396 : if (strlen(pszGT) > 0)
461 : {
462 : const CPLStringList aosTokens(
463 726 : CSLTokenizeStringComplex(pszGT, ",", FALSE, FALSE));
464 363 : 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 2541 : for (int iTA = 0; iTA < 6; iTA++)
472 2178 : psPam->gt[iTA] = CPLAtof(aosTokens[iTA]);
473 363 : psPam->bHaveGeoTransform = TRUE;
474 : }
475 : }
476 :
477 : /* -------------------------------------------------------------------- */
478 : /* Check for GCPs. */
479 : /* -------------------------------------------------------------------- */
480 1396 : 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 1396 : if (oMDMD.XMLInit(psTree, TRUE))
497 : {
498 1058 : 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 1396 : CPLGetXMLNode(psTree, "=GeodataXform");
514 2792 : CPLXMLTreeCloser oTreeValueAsXML(nullptr);
515 1396 : 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 1394 : char **papszXML = oMDMD.GetMetadata("xml:ESRI");
527 1394 : 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 1396 : 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[0] = adfCoeffX[0];
641 1 : psPam->gt[1] = 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[2] = -adfCoeffX[2];
648 1 : psPam->gt[3] = adfCoeffY[0];
649 1 : psPam->gt[4] = adfCoeffY[1];
650 1 : psPam->gt[5] = -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[0] -= 0.5 * psPam->gt[1];
658 1 : psPam->gt[0] -= 0.5 * psPam->gt[2];
659 1 : psPam->gt[3] -= 0.5 * psPam->gt[4];
660 1 : psPam->gt[3] -= 0.5 * psPam->gt[5];
661 :
662 1 : psPam->bHaveGeoTransform = TRUE;
663 : }
664 : }
665 : }
666 : }
667 :
668 : /* -------------------------------------------------------------------- */
669 : /* Process bands. */
670 : /* -------------------------------------------------------------------- */
671 4223 : for (const CPLXMLNode *psBandTree = psTree->psChild; psBandTree;
672 2827 : psBandTree = psBandTree->psNext)
673 : {
674 2827 : if (psBandTree->eType != CXT_Element ||
675 2823 : !EQUAL(psBandTree->pszValue, "PAMRasterBand"))
676 2187 : continue;
677 :
678 640 : const int nBand = atoi(CPLGetXMLValue(psBandTree, "band", "0"));
679 :
680 640 : if (nBand < 1 || nBand > GetRasterCount())
681 41 : continue;
682 :
683 599 : GDALRasterBand *poBand = GetRasterBand(nBand);
684 :
685 599 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
686 0 : continue;
687 :
688 : GDALPamRasterBand *poPamBand =
689 599 : cpl::down_cast<GDALPamRasterBand *>(GetRasterBand(nBand));
690 :
691 599 : poPamBand->XMLInit(psBandTree, pszUnused);
692 : }
693 :
694 : /* -------------------------------------------------------------------- */
695 : /* Preserve Array information. */
696 : /* -------------------------------------------------------------------- */
697 4223 : for (const CPLXMLNode *psIter = psTree->psChild; psIter;
698 2827 : psIter = psIter->psNext)
699 : {
700 5650 : if (psIter->eType == CXT_Element &&
701 5607 : (strcmp(psIter->pszValue, "Array") == 0 ||
702 2784 : (psPam->osDerivedDatasetName.empty() &&
703 2774 : strcmp(psIter->pszValue, "DerivedDataset") == 0)))
704 : {
705 56 : CPLXMLNode sArrayTmp = *psIter;
706 56 : sArrayTmp.psNext = nullptr;
707 56 : psPam->m_apoOtherNodes.emplace_back(
708 56 : CPLXMLTreeCloser(CPLCloneXMLTree(&sArrayTmp)));
709 : }
710 : }
711 :
712 : /* -------------------------------------------------------------------- */
713 : /* Clear dirty flag. */
714 : /* -------------------------------------------------------------------- */
715 1396 : nPamFlags &= ~GPF_DIRTY;
716 :
717 1396 : return CE_None;
718 : }
719 :
720 : /************************************************************************/
721 : /* SetPhysicalFilename() */
722 : /************************************************************************/
723 :
724 4005 : void GDALPamDataset::SetPhysicalFilename(const char *pszFilename)
725 :
726 : {
727 4005 : PamInitialize();
728 :
729 4005 : if (psPam)
730 4005 : psPam->osPhysicalFilename = pszFilename;
731 4005 : }
732 :
733 : /************************************************************************/
734 : /* GetPhysicalFilename() */
735 : /************************************************************************/
736 :
737 144 : const char *GDALPamDataset::GetPhysicalFilename()
738 :
739 : {
740 144 : PamInitialize();
741 :
742 144 : if (psPam)
743 144 : return psPam->osPhysicalFilename;
744 :
745 0 : return "";
746 : }
747 :
748 : /************************************************************************/
749 : /* SetSubdatasetName() */
750 : /************************************************************************/
751 :
752 : /* Mutually exclusive with SetDerivedDatasetName() */
753 700 : void GDALPamDataset::SetSubdatasetName(const char *pszSubdataset)
754 :
755 : {
756 700 : PamInitialize();
757 :
758 700 : if (psPam)
759 700 : psPam->osSubdatasetName = pszSubdataset;
760 700 : }
761 :
762 : /************************************************************************/
763 : /* SetDerivedDatasetName() */
764 : /************************************************************************/
765 :
766 : /* Mutually exclusive with SetSubdatasetName() */
767 183 : void GDALPamDataset::SetDerivedDatasetName(const char *pszDerivedDataset)
768 :
769 : {
770 183 : PamInitialize();
771 :
772 183 : if (psPam)
773 183 : psPam->osDerivedDatasetName = pszDerivedDataset;
774 183 : }
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 38825 : const char *GDALPamDataset::BuildPamFilename()
796 :
797 : {
798 38825 : 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 38825 : if (psPam->pszPamFilename != nullptr)
806 2455 : return psPam->pszPamFilename;
807 :
808 36370 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
809 :
810 36350 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
811 34310 : pszPhysicalFile = GetDescription();
812 :
813 36360 : if (strlen(pszPhysicalFile) == 0)
814 227 : return nullptr;
815 :
816 : /* -------------------------------------------------------------------- */
817 : /* Try a proxy lookup, otherwise just add .aux.xml. */
818 : /* -------------------------------------------------------------------- */
819 36133 : const char *pszProxyPam = PamGetProxy(pszPhysicalFile);
820 36123 : if (pszProxyPam != nullptr)
821 4 : psPam->pszPamFilename = CPLStrdup(pszProxyPam);
822 : else
823 : {
824 36119 : if (!GDALCanFileAcceptSidecarFile(pszPhysicalFile))
825 107 : return nullptr;
826 72025 : psPam->pszPamFilename =
827 36003 : static_cast<char *>(CPLMalloc(strlen(pszPhysicalFile) + 10));
828 36022 : strcpy(psPam->pszPamFilename, pszPhysicalFile);
829 36022 : strcat(psPam->pszPamFilename, ".aux.xml");
830 : }
831 :
832 36026 : return psPam->pszPamFilename;
833 : }
834 :
835 : /************************************************************************/
836 : /* IsPamFilenameAPotentialSiblingFile() */
837 : /************************************************************************/
838 :
839 30997 : int GDALPamDataset::IsPamFilenameAPotentialSiblingFile()
840 : {
841 30997 : 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 30997 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
849 :
850 30975 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
851 30046 : pszPhysicalFile = GetDescription();
852 :
853 30990 : size_t nLenPhysicalFile = strlen(pszPhysicalFile);
854 30990 : int bIsSiblingPamFile =
855 30990 : strncmp(psPam->pszPamFilename, pszPhysicalFile, nLenPhysicalFile) ==
856 61964 : 0 &&
857 30974 : strcmp(psPam->pszPamFilename + nLenPhysicalFile, ".aux.xml") == 0;
858 :
859 30990 : return bIsSiblingPamFile;
860 : }
861 :
862 : /************************************************************************/
863 : /* TryLoadXML() */
864 : /************************************************************************/
865 :
866 37470 : CPLErr GDALPamDataset::TryLoadXML(CSLConstList papszSiblingFiles)
867 :
868 : {
869 37470 : PamInitialize();
870 :
871 37450 : if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
872 1254 : 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 36196 : nPamFlags &= ~GPF_DIRTY;
883 :
884 : /* -------------------------------------------------------------------- */
885 : /* Try reading the file. */
886 : /* -------------------------------------------------------------------- */
887 36196 : if (!BuildPamFilename())
888 245 : 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 35952 : CPLXMLNode *psTree = nullptr;
897 :
898 63886 : if (papszSiblingFiles != nullptr && IsPamFilenameAPotentialSiblingFile() &&
899 27921 : GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
900 : {
901 27950 : const int iSibling = CSLFindString(
902 27918 : papszSiblingFiles, CPLGetFilename(psPam->pszPamFilename));
903 27924 : if (iSibling >= 0)
904 : {
905 433 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
906 433 : psTree = CPLParseXMLFile(psPam->pszPamFilename);
907 : }
908 : }
909 8051 : else if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
910 8868 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
911 845 : VSI_ISREG(sStatBuf.st_mode))
912 : {
913 845 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
914 845 : psTree = CPLParseXMLFile(psPam->pszPamFilename);
915 : }
916 :
917 : /* -------------------------------------------------------------------- */
918 : /* If we are looking for a subdataset, search for its subtree now. */
919 : /* -------------------------------------------------------------------- */
920 35947 : if (psTree)
921 : {
922 2538 : std::string osSubNode;
923 2538 : std::string osSubNodeValue;
924 1269 : if (!psPam->osSubdatasetName.empty())
925 : {
926 161 : osSubNode = "Subdataset";
927 161 : osSubNodeValue = psPam->osSubdatasetName;
928 : }
929 1108 : else if (!psPam->osDerivedDatasetName.empty())
930 : {
931 17 : osSubNode = "DerivedDataset";
932 17 : osSubNodeValue = psPam->osDerivedDatasetName;
933 : }
934 1269 : if (!osSubNode.empty())
935 : {
936 178 : CPLXMLNode *psSubTree = psTree->psChild;
937 :
938 326 : for (; psSubTree != nullptr; psSubTree = psSubTree->psNext)
939 : {
940 394 : if (psSubTree->eType != CXT_Element ||
941 197 : !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
942 129 : continue;
943 :
944 68 : if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
945 : osSubNodeValue.c_str()))
946 19 : continue;
947 :
948 49 : psSubTree = CPLGetXMLNode(psSubTree, "PAMDataset");
949 49 : break;
950 : }
951 :
952 178 : if (psSubTree != nullptr)
953 49 : psSubTree = CPLCloneXMLTree(psSubTree);
954 :
955 178 : CPLDestroyXMLNode(psTree);
956 178 : psTree = psSubTree;
957 : }
958 : }
959 :
960 : /* -------------------------------------------------------------------- */
961 : /* If we fail, try .aux. */
962 : /* -------------------------------------------------------------------- */
963 35969 : if (psTree == nullptr)
964 34829 : return TryLoadAux(papszSiblingFiles);
965 :
966 : /* -------------------------------------------------------------------- */
967 : /* Initialize ourselves from this XML tree. */
968 : /* -------------------------------------------------------------------- */
969 :
970 1140 : CPLString osVRTPath(CPLGetPathSafe(psPam->pszPamFilename));
971 1140 : const CPLErr eErr = XMLInit(psTree, osVRTPath);
972 :
973 1140 : CPLDestroyXMLNode(psTree);
974 :
975 1140 : if (eErr != CE_None)
976 0 : PamClear();
977 :
978 1140 : return eErr;
979 : }
980 :
981 : /************************************************************************/
982 : /* TrySaveXML() */
983 : /************************************************************************/
984 :
985 7848 : CPLErr GDALPamDataset::TrySaveXML()
986 :
987 : {
988 7848 : nPamFlags &= ~GPF_DIRTY;
989 :
990 7848 : if (psPam == nullptr || (nPamFlags & GPF_NOSAVE) != 0 ||
991 2717 : (nPamFlags & GPF_DISABLED) != 0)
992 6340 : return CE_None;
993 :
994 : /* -------------------------------------------------------------------- */
995 : /* Make sure we know the filename we want to store in. */
996 : /* -------------------------------------------------------------------- */
997 1508 : if (!BuildPamFilename())
998 89 : return CE_None;
999 :
1000 : /* -------------------------------------------------------------------- */
1001 : /* Build the XML representation of the auxiliary metadata. */
1002 : /* -------------------------------------------------------------------- */
1003 1419 : CPLXMLNode *psTree = SerializeToXML(nullptr);
1004 :
1005 1419 : if (psTree == nullptr)
1006 : {
1007 : /* If we have unset all metadata, we have to delete the PAM file */
1008 239 : CPLPushErrorHandler(CPLQuietErrorHandler);
1009 239 : VSIUnlink(psPam->pszPamFilename);
1010 239 : CPLPopErrorHandler();
1011 239 : 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 2360 : std::string osSubNode;
1020 1180 : std::string osSubNodeValue;
1021 1180 : if (!psPam->osSubdatasetName.empty())
1022 : {
1023 32 : osSubNode = "Subdataset";
1024 32 : osSubNodeValue = psPam->osSubdatasetName;
1025 : }
1026 1148 : else if (!psPam->osDerivedDatasetName.empty())
1027 : {
1028 8 : osSubNode = "DerivedDataset";
1029 8 : osSubNodeValue = psPam->osDerivedDatasetName;
1030 : }
1031 1180 : if (!osSubNode.empty())
1032 : {
1033 40 : CPLXMLNode *psOldTree = nullptr;
1034 :
1035 : VSIStatBufL sStatBuf;
1036 40 : if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
1037 47 : VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
1038 7 : VSI_ISREG(sStatBuf.st_mode))
1039 : {
1040 7 : CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
1041 7 : psOldTree = CPLParseXMLFile(psPam->pszPamFilename);
1042 : }
1043 :
1044 40 : if (psOldTree == nullptr)
1045 33 : psOldTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
1046 :
1047 40 : CPLXMLNode *psSubTree = psOldTree->psChild;
1048 45 : for (/* initialized above */; psSubTree != nullptr;
1049 5 : psSubTree = psSubTree->psNext)
1050 : {
1051 16 : if (psSubTree->eType != CXT_Element ||
1052 8 : !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
1053 1 : continue;
1054 :
1055 7 : if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
1056 : osSubNodeValue.c_str()))
1057 4 : continue;
1058 :
1059 3 : break;
1060 : }
1061 :
1062 40 : if (psSubTree == nullptr)
1063 : {
1064 : psSubTree =
1065 37 : CPLCreateXMLNode(psOldTree, CXT_Element, osSubNode.c_str());
1066 37 : CPLCreateXMLNode(CPLCreateXMLNode(psSubTree, CXT_Attribute, "name"),
1067 : CXT_Text, osSubNodeValue.c_str());
1068 : }
1069 :
1070 40 : CPLXMLNode *psOldPamDataset = CPLGetXMLNode(psSubTree, "PAMDataset");
1071 40 : if (psOldPamDataset != nullptr)
1072 : {
1073 3 : CPLRemoveXMLChild(psSubTree, psOldPamDataset);
1074 3 : CPLDestroyXMLNode(psOldPamDataset);
1075 : }
1076 :
1077 40 : CPLAddXMLChild(psSubTree, psTree);
1078 40 : psTree = psOldTree;
1079 : }
1080 :
1081 : /* -------------------------------------------------------------------- */
1082 : /* Preserve other information. */
1083 : /* -------------------------------------------------------------------- */
1084 1185 : 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 1180 : CPLPushErrorHandler(CPLQuietErrorHandler);
1094 1180 : const int bSaved = CPLSerializeXMLTreeToFile(psTree, psPam->pszPamFilename);
1095 1180 : 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 1180 : CPLErr eErr = CE_None;
1102 :
1103 1180 : if (bSaved)
1104 1152 : 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 1180 : CPLDestroyXMLNode(psTree);
1135 :
1136 1180 : return eErr;
1137 : }
1138 :
1139 : /************************************************************************/
1140 : /* CloneInfo() */
1141 : /************************************************************************/
1142 :
1143 7265 : CPLErr GDALPamDataset::CloneInfo(GDALDataset *poSrcDS, int nCloneFlags)
1144 :
1145 : {
1146 7265 : const int bOnlyIfMissing = nCloneFlags & GCIF_ONLY_IF_MISSING;
1147 7265 : const int nSavedMOFlags = GetMOFlags();
1148 :
1149 7265 : PamInitialize();
1150 :
1151 : /* -------------------------------------------------------------------- */
1152 : /* Suppress NotImplemented error messages - mainly needed if PAM */
1153 : /* disabled. */
1154 : /* -------------------------------------------------------------------- */
1155 7265 : SetMOFlags(nSavedMOFlags | GMO_IGNORE_UNIMPLEMENTED);
1156 :
1157 : /* -------------------------------------------------------------------- */
1158 : /* GeoTransform */
1159 : /* -------------------------------------------------------------------- */
1160 7265 : if (nCloneFlags & GCIF_GEOTRANSFORM)
1161 : {
1162 7263 : GDALGeoTransform gt;
1163 :
1164 7263 : if (poSrcDS->GetGeoTransform(gt) == CE_None)
1165 : {
1166 2343 : GDALGeoTransform oldGT;
1167 :
1168 2343 : if (!bOnlyIfMissing || GetGeoTransform(oldGT) != CE_None)
1169 220 : SetGeoTransform(gt);
1170 : }
1171 : }
1172 :
1173 : /* -------------------------------------------------------------------- */
1174 : /* Projection */
1175 : /* -------------------------------------------------------------------- */
1176 7265 : if (nCloneFlags & GCIF_PROJECTION)
1177 : {
1178 7263 : const auto poSRS = poSrcDS->GetSpatialRef();
1179 :
1180 7263 : if (poSRS != nullptr)
1181 : {
1182 2081 : if (!bOnlyIfMissing || GetSpatialRef() == nullptr)
1183 163 : SetSpatialRef(poSRS);
1184 : }
1185 : }
1186 :
1187 : /* -------------------------------------------------------------------- */
1188 : /* GCPs */
1189 : /* -------------------------------------------------------------------- */
1190 7265 : if (nCloneFlags & GCIF_GCPS)
1191 : {
1192 7265 : if (poSrcDS->GetGCPCount() > 0)
1193 : {
1194 20 : 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 7265 : if (nCloneFlags & GCIF_METADATA)
1206 : {
1207 13165 : for (const char *pszMDD : {"", "RPC", "json:ISIS3", "json:VICAR"})
1208 : {
1209 10532 : auto papszSrcMD = poSrcDS->GetMetadata(pszMDD);
1210 10532 : if (papszSrcMD != nullptr)
1211 : {
1212 3944 : if (!bOnlyIfMissing ||
1213 1972 : CSLCount(GetMetadata(pszMDD)) != CSLCount(papszSrcMD))
1214 : {
1215 405 : SetMetadata(papszSrcMD, pszMDD);
1216 : }
1217 : }
1218 : }
1219 : }
1220 :
1221 : /* -------------------------------------------------------------------- */
1222 : /* Process bands. */
1223 : /* -------------------------------------------------------------------- */
1224 7265 : if (nCloneFlags & GCIF_PROCESS_BANDS)
1225 : {
1226 26208 : for (int iBand = 0; iBand < GetRasterCount(); iBand++)
1227 : {
1228 18943 : GDALRasterBand *poBand = GetRasterBand(iBand + 1);
1229 :
1230 18943 : if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
1231 0 : continue;
1232 :
1233 18943 : if (poSrcDS->GetRasterCount() >= iBand + 1)
1234 : {
1235 37884 : cpl::down_cast<GDALPamRasterBand *>(poBand)->CloneInfo(
1236 18942 : poSrcDS->GetRasterBand(iBand + 1), nCloneFlags);
1237 : }
1238 : else
1239 1 : CPLDebug("GDALPamDataset",
1240 : "Skipping CloneInfo for band not in source, "
1241 : "this is a bit unusual!");
1242 : }
1243 : }
1244 :
1245 : /* -------------------------------------------------------------------- */
1246 : /* Copy masks. These are really copied at a lower level using */
1247 : /* GDALDefaultOverviews, for formats with no native mask */
1248 : /* support but this is a convenient central point to put this */
1249 : /* for most drivers. */
1250 : /* -------------------------------------------------------------------- */
1251 7265 : if (nCloneFlags & GCIF_MASK)
1252 : {
1253 5173 : GDALDriver::DefaultCopyMasks(poSrcDS, this, FALSE);
1254 : }
1255 :
1256 : /* -------------------------------------------------------------------- */
1257 : /* Restore MO flags. */
1258 : /* -------------------------------------------------------------------- */
1259 7265 : SetMOFlags(nSavedMOFlags);
1260 :
1261 7265 : return CE_None;
1262 : }
1263 :
1264 : //! @endcond
1265 :
1266 : /************************************************************************/
1267 : /* GetFileList() */
1268 : /* */
1269 : /* Add .aux.xml or .aux file into file list as appropriate. */
1270 : /************************************************************************/
1271 :
1272 4122 : char **GDALPamDataset::GetFileList()
1273 :
1274 : {
1275 4122 : char **papszFileList = GDALDataset::GetFileList();
1276 :
1277 4032 : if (psPam && !psPam->osPhysicalFilename.empty() &&
1278 8496 : GDALCanReliablyUseSiblingFileList(psPam->osPhysicalFilename.c_str()) &&
1279 343 : CSLFindString(papszFileList, psPam->osPhysicalFilename) == -1)
1280 : {
1281 : papszFileList =
1282 13 : CSLInsertString(papszFileList, 0, psPam->osPhysicalFilename);
1283 : }
1284 :
1285 4121 : if (psPam && psPam->pszPamFilename)
1286 : {
1287 3997 : int bAddPamFile = nPamFlags & GPF_DIRTY;
1288 3997 : if (!bAddPamFile)
1289 : {
1290 : VSIStatBufL sStatBuf;
1291 3997 : if (oOvManager.GetSiblingFiles() != nullptr &&
1292 7039 : IsPamFilenameAPotentialSiblingFile() &&
1293 3042 : GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
1294 : {
1295 3043 : bAddPamFile =
1296 3042 : CSLFindString(oOvManager.GetSiblingFiles(),
1297 6085 : CPLGetFilename(psPam->pszPamFilename)) >= 0;
1298 : }
1299 : else
1300 : {
1301 954 : bAddPamFile = VSIStatExL(psPam->pszPamFilename, &sStatBuf,
1302 954 : VSI_STAT_EXISTS_FLAG) == 0;
1303 : }
1304 : }
1305 3997 : if (bAddPamFile)
1306 : {
1307 302 : papszFileList = CSLAddString(papszFileList, psPam->pszPamFilename);
1308 : }
1309 : }
1310 :
1311 4031 : if (psPam && !psPam->osAuxFilename.empty() &&
1312 8154 : GDALCanReliablyUseSiblingFileList(psPam->osAuxFilename.c_str()) &&
1313 1 : CSLFindString(papszFileList, psPam->osAuxFilename) == -1)
1314 : {
1315 1 : papszFileList = CSLAddString(papszFileList, psPam->osAuxFilename);
1316 : }
1317 4121 : return papszFileList;
1318 : }
1319 :
1320 : /************************************************************************/
1321 : /* IBuildOverviews() */
1322 : /************************************************************************/
1323 :
1324 : //! @cond Doxygen_Suppress
1325 35 : CPLErr GDALPamDataset::IBuildOverviews(
1326 : const char *pszResampling, int nOverviews, const int *panOverviewList,
1327 : int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
1328 : void *pProgressData, CSLConstList papszOptions)
1329 :
1330 : {
1331 : /* -------------------------------------------------------------------- */
1332 : /* Initialize PAM. */
1333 : /* -------------------------------------------------------------------- */
1334 35 : PamInitialize();
1335 35 : if (psPam == nullptr)
1336 0 : return GDALDataset::IBuildOverviews(
1337 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1338 0 : pfnProgress, pProgressData, papszOptions);
1339 :
1340 : /* -------------------------------------------------------------------- */
1341 : /* If we appear to have subdatasets and to have a physical */
1342 : /* filename, use that physical filename to derive a name for a */
1343 : /* new overview file. */
1344 : /* -------------------------------------------------------------------- */
1345 35 : if (oOvManager.IsInitialized() && psPam->osPhysicalFilename.length() != 0)
1346 : {
1347 9 : return oOvManager.BuildOverviewsSubDataset(
1348 9 : psPam->osPhysicalFilename, pszResampling, nOverviews,
1349 : panOverviewList, nListBands, panBandList, pfnProgress,
1350 9 : pProgressData, papszOptions);
1351 : }
1352 :
1353 26 : return GDALDataset::IBuildOverviews(
1354 : pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1355 26 : pfnProgress, pProgressData, papszOptions);
1356 : }
1357 :
1358 : //! @endcond
1359 :
1360 : /************************************************************************/
1361 : /* GetSpatialRef() */
1362 : /************************************************************************/
1363 :
1364 17204 : const OGRSpatialReference *GDALPamDataset::GetSpatialRef() const
1365 :
1366 : {
1367 17204 : if (psPam && psPam->poSRS)
1368 95 : return psPam->poSRS;
1369 :
1370 17109 : return GDALDataset::GetSpatialRef();
1371 : }
1372 :
1373 : /************************************************************************/
1374 : /* GetSpatialRefRasterOnly() */
1375 : /************************************************************************/
1376 :
1377 342 : const OGRSpatialReference *GDALPamDataset::GetSpatialRefRasterOnly() const
1378 :
1379 : {
1380 342 : if (psPam && psPam->poSRS)
1381 2 : return psPam->poSRS;
1382 :
1383 340 : return GDALDataset::GetSpatialRefRasterOnly();
1384 : }
1385 :
1386 : /************************************************************************/
1387 : /* SetSpatialRef() */
1388 : /************************************************************************/
1389 :
1390 432 : CPLErr GDALPamDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
1391 :
1392 : {
1393 432 : PamInitialize();
1394 :
1395 432 : if (psPam == nullptr)
1396 0 : return GDALDataset::SetSpatialRef(poSRS);
1397 :
1398 432 : if (psPam->poSRS)
1399 6 : psPam->poSRS->Release();
1400 432 : psPam->poSRS = poSRS ? poSRS->Clone() : nullptr;
1401 432 : MarkPamDirty();
1402 :
1403 432 : return CE_None;
1404 : }
1405 :
1406 : /************************************************************************/
1407 : /* GetGeoTransform() */
1408 : /************************************************************************/
1409 :
1410 15798 : CPLErr GDALPamDataset::GetGeoTransform(GDALGeoTransform >) const
1411 :
1412 : {
1413 15798 : if (psPam && psPam->bHaveGeoTransform)
1414 : {
1415 191 : gt = psPam->gt;
1416 191 : return CE_None;
1417 : }
1418 :
1419 15607 : return GDALDataset::GetGeoTransform(gt);
1420 : }
1421 :
1422 : /************************************************************************/
1423 : /* SetGeoTransform() */
1424 : /************************************************************************/
1425 :
1426 395 : CPLErr GDALPamDataset::SetGeoTransform(const GDALGeoTransform >)
1427 :
1428 : {
1429 395 : PamInitialize();
1430 :
1431 395 : if (psPam)
1432 : {
1433 395 : MarkPamDirty();
1434 395 : psPam->bHaveGeoTransform = true;
1435 395 : psPam->gt = gt;
1436 395 : return (CE_None);
1437 : }
1438 :
1439 0 : return GDALDataset::SetGeoTransform(gt);
1440 : }
1441 :
1442 : /************************************************************************/
1443 : /* DeleteGeoTransform() */
1444 : /************************************************************************/
1445 :
1446 : /** Remove geotransform from PAM.
1447 : *
1448 : * @since GDAL 3.4.1
1449 : */
1450 1681 : void GDALPamDataset::DeleteGeoTransform()
1451 :
1452 : {
1453 1681 : PamInitialize();
1454 :
1455 1681 : if (psPam && psPam->bHaveGeoTransform)
1456 : {
1457 3 : MarkPamDirty();
1458 3 : psPam->bHaveGeoTransform = FALSE;
1459 : }
1460 1681 : }
1461 :
1462 : /************************************************************************/
1463 : /* GetGCPCount() */
1464 : /************************************************************************/
1465 :
1466 16113 : int GDALPamDataset::GetGCPCount()
1467 :
1468 : {
1469 16113 : if (psPam && !psPam->asGCPs.empty())
1470 45 : return static_cast<int>(psPam->asGCPs.size());
1471 :
1472 16067 : return GDALDataset::GetGCPCount();
1473 : }
1474 :
1475 : /************************************************************************/
1476 : /* GetGCPSpatialRef() */
1477 : /************************************************************************/
1478 :
1479 63 : const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const
1480 :
1481 : {
1482 63 : if (psPam && psPam->poGCP_SRS != nullptr)
1483 24 : return psPam->poGCP_SRS;
1484 :
1485 39 : return GDALDataset::GetGCPSpatialRef();
1486 : }
1487 :
1488 : /************************************************************************/
1489 : /* GetGCPs() */
1490 : /************************************************************************/
1491 :
1492 37 : const GDAL_GCP *GDALPamDataset::GetGCPs()
1493 :
1494 : {
1495 37 : if (psPam && !psPam->asGCPs.empty())
1496 27 : return gdal::GCP::c_ptr(psPam->asGCPs);
1497 :
1498 10 : return GDALDataset::GetGCPs();
1499 : }
1500 :
1501 : /************************************************************************/
1502 : /* SetGCPs() */
1503 : /************************************************************************/
1504 :
1505 32 : CPLErr GDALPamDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
1506 : const OGRSpatialReference *poGCP_SRS)
1507 :
1508 : {
1509 32 : PamInitialize();
1510 :
1511 32 : if (psPam)
1512 : {
1513 32 : if (psPam->poGCP_SRS)
1514 1 : psPam->poGCP_SRS->Release();
1515 32 : psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr;
1516 32 : psPam->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
1517 32 : MarkPamDirty();
1518 :
1519 32 : return CE_None;
1520 : }
1521 :
1522 0 : return GDALDataset::SetGCPs(nGCPCount, pasGCPList, poGCP_SRS);
1523 : }
1524 :
1525 : /************************************************************************/
1526 : /* SetMetadata() */
1527 : /************************************************************************/
1528 :
1529 3653 : CPLErr GDALPamDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
1530 :
1531 : {
1532 3653 : PamInitialize();
1533 :
1534 3653 : if (psPam)
1535 : {
1536 3653 : psPam->bHasMetadata = TRUE;
1537 3653 : MarkPamDirty();
1538 : }
1539 :
1540 3653 : return GDALDataset::SetMetadata(papszMetadata, pszDomain);
1541 : }
1542 :
1543 : /************************************************************************/
1544 : /* SetMetadataItem() */
1545 : /************************************************************************/
1546 :
1547 45397 : CPLErr GDALPamDataset::SetMetadataItem(const char *pszName,
1548 : const char *pszValue,
1549 : const char *pszDomain)
1550 :
1551 : {
1552 45397 : PamInitialize();
1553 :
1554 45397 : if (psPam)
1555 : {
1556 45397 : psPam->bHasMetadata = TRUE;
1557 45397 : MarkPamDirty();
1558 : }
1559 :
1560 45396 : return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
1561 : }
1562 :
1563 : /************************************************************************/
1564 : /* GetMetadataItem() */
1565 : /************************************************************************/
1566 :
1567 19893 : const char *GDALPamDataset::GetMetadataItem(const char *pszName,
1568 : const char *pszDomain)
1569 :
1570 : {
1571 : /* -------------------------------------------------------------------- */
1572 : /* A request against the ProxyOverviewRequest is a special */
1573 : /* mechanism to request an overview filename be allocated in */
1574 : /* the proxy pool location. The allocated name is saved as */
1575 : /* metadata as well as being returned. */
1576 : /* -------------------------------------------------------------------- */
1577 19893 : if (pszDomain != nullptr && EQUAL(pszDomain, "ProxyOverviewRequest"))
1578 : {
1579 8 : CPLString osPrelimOvr = GetDescription();
1580 4 : osPrelimOvr += ":::OVR";
1581 :
1582 4 : const char *pszProxyOvrFilename = PamAllocateProxy(osPrelimOvr);
1583 4 : if (pszProxyOvrFilename == nullptr)
1584 3 : return nullptr;
1585 :
1586 1 : SetMetadataItem("OVERVIEW_FILE", pszProxyOvrFilename, "OVERVIEWS");
1587 :
1588 1 : return pszProxyOvrFilename;
1589 : }
1590 :
1591 : /* -------------------------------------------------------------------- */
1592 : /* If the OVERVIEW_FILE metadata is requested, we intercept the */
1593 : /* request in order to replace ":::BASE:::" with the path to */
1594 : /* the physical file - if available. This is primarily for the */
1595 : /* purpose of managing subdataset overview filenames as being */
1596 : /* relative to the physical file the subdataset comes */
1597 : /* from. (#3287). */
1598 : /* -------------------------------------------------------------------- */
1599 19889 : else if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") &&
1600 5521 : EQUAL(pszName, "OVERVIEW_FILE"))
1601 : {
1602 5521 : if (m_osOverviewFile.empty())
1603 : {
1604 : const char *pszOverviewFile =
1605 5512 : GDALDataset::GetMetadataItem(pszName, pszDomain);
1606 :
1607 5512 : if (pszOverviewFile == nullptr ||
1608 17 : !STARTS_WITH_CI(pszOverviewFile, ":::BASE:::"))
1609 5496 : return pszOverviewFile;
1610 :
1611 16 : std::string osPath;
1612 :
1613 16 : if (strlen(GetPhysicalFilename()) > 0)
1614 16 : osPath = CPLGetPathSafe(GetPhysicalFilename());
1615 : else
1616 0 : osPath = CPLGetPathSafe(GetDescription());
1617 :
1618 32 : m_osOverviewFile = CPLFormFilenameSafe(
1619 16 : osPath.c_str(), pszOverviewFile + 10, nullptr);
1620 : }
1621 25 : return m_osOverviewFile.c_str();
1622 : }
1623 :
1624 : /* -------------------------------------------------------------------- */
1625 : /* Everything else is a pass through. */
1626 : /* -------------------------------------------------------------------- */
1627 :
1628 14368 : return GDALDataset::GetMetadataItem(pszName, pszDomain);
1629 : }
1630 :
1631 : /************************************************************************/
1632 : /* GetMetadata() */
1633 : /************************************************************************/
1634 :
1635 14451 : char **GDALPamDataset::GetMetadata(const char *pszDomain)
1636 :
1637 : {
1638 : // if( pszDomain == nullptr || !EQUAL(pszDomain,"ProxyOverviewRequest") )
1639 14451 : return GDALDataset::GetMetadata(pszDomain);
1640 : }
1641 :
1642 : /************************************************************************/
1643 : /* TryLoadAux() */
1644 : /************************************************************************/
1645 :
1646 : //! @cond Doxygen_Suppress
1647 34831 : CPLErr GDALPamDataset::TryLoadAux(CSLConstList papszSiblingFiles)
1648 :
1649 : {
1650 : /* -------------------------------------------------------------------- */
1651 : /* Initialize PAM. */
1652 : /* -------------------------------------------------------------------- */
1653 34831 : PamInitialize();
1654 :
1655 34817 : if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
1656 14 : return CE_None;
1657 :
1658 : /* -------------------------------------------------------------------- */
1659 : /* What is the name of the physical file we are referencing? */
1660 : /* We allow an override via the psPam->pszPhysicalFile item. */
1661 : /* -------------------------------------------------------------------- */
1662 34803 : const char *pszPhysicalFile = psPam->osPhysicalFilename;
1663 :
1664 34803 : if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
1665 33417 : pszPhysicalFile = GetDescription();
1666 :
1667 34815 : if (strlen(pszPhysicalFile) == 0)
1668 0 : return CE_None;
1669 :
1670 34815 : if (papszSiblingFiles && GDALCanReliablyUseSiblingFileList(pszPhysicalFile))
1671 : {
1672 27490 : CPLString osAuxFilename = CPLResetExtensionSafe(pszPhysicalFile, "aux");
1673 : int iSibling =
1674 27518 : CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
1675 27515 : if (iSibling < 0)
1676 : {
1677 27502 : osAuxFilename = pszPhysicalFile;
1678 27507 : osAuxFilename += ".aux";
1679 : iSibling =
1680 27479 : CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
1681 27512 : if (iSibling < 0)
1682 27506 : return CE_None;
1683 : }
1684 : }
1685 :
1686 : /* -------------------------------------------------------------------- */
1687 : /* Try to open .aux file. */
1688 : /* -------------------------------------------------------------------- */
1689 : GDALDataset *poAuxDS =
1690 7341 : GDALFindAssociatedAuxFile(pszPhysicalFile, GA_ReadOnly, this);
1691 :
1692 7317 : if (poAuxDS == nullptr)
1693 7309 : return CE_None;
1694 :
1695 8 : psPam->osAuxFilename = poAuxDS->GetDescription();
1696 :
1697 : /* -------------------------------------------------------------------- */
1698 : /* Do we have an SRS on the aux file? */
1699 : /* -------------------------------------------------------------------- */
1700 8 : if (strlen(poAuxDS->GetProjectionRef()) > 0)
1701 2 : GDALPamDataset::SetProjection(poAuxDS->GetProjectionRef());
1702 :
1703 : /* -------------------------------------------------------------------- */
1704 : /* Geotransform. */
1705 : /* -------------------------------------------------------------------- */
1706 8 : if (poAuxDS->GetGeoTransform(psPam->gt) == CE_None)
1707 2 : psPam->bHaveGeoTransform = TRUE;
1708 :
1709 : /* -------------------------------------------------------------------- */
1710 : /* GCPs */
1711 : /* -------------------------------------------------------------------- */
1712 8 : if (poAuxDS->GetGCPCount() > 0)
1713 : {
1714 0 : psPam->asGCPs =
1715 0 : gdal::GCP::fromC(poAuxDS->GetGCPs(), poAuxDS->GetGCPCount());
1716 : }
1717 :
1718 : /* -------------------------------------------------------------------- */
1719 : /* Apply metadata. We likely ought to be merging this in rather */
1720 : /* than overwriting everything that was there. */
1721 : /* -------------------------------------------------------------------- */
1722 8 : char **papszMD = poAuxDS->GetMetadata();
1723 8 : if (CSLCount(papszMD) > 0)
1724 : {
1725 0 : char **papszMerged = CSLMerge(CSLDuplicate(GetMetadata()), papszMD);
1726 0 : GDALPamDataset::SetMetadata(papszMerged);
1727 0 : CSLDestroy(papszMerged);
1728 : }
1729 :
1730 8 : papszMD = poAuxDS->GetMetadata("XFORMS");
1731 8 : if (CSLCount(papszMD) > 0)
1732 : {
1733 : char **papszMerged =
1734 0 : CSLMerge(CSLDuplicate(GetMetadata("XFORMS")), papszMD);
1735 0 : GDALPamDataset::SetMetadata(papszMerged, "XFORMS");
1736 0 : CSLDestroy(papszMerged);
1737 : }
1738 :
1739 : /* ==================================================================== */
1740 : /* Process bands. */
1741 : /* ==================================================================== */
1742 20 : for (int iBand = 0; iBand < poAuxDS->GetRasterCount(); iBand++)
1743 : {
1744 12 : if (iBand >= GetRasterCount())
1745 0 : break;
1746 :
1747 12 : GDALRasterBand *const poAuxBand = poAuxDS->GetRasterBand(iBand + 1);
1748 12 : GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
1749 :
1750 12 : papszMD = poAuxBand->GetMetadata();
1751 12 : if (CSLCount(papszMD) > 0)
1752 : {
1753 : char **papszMerged =
1754 12 : CSLMerge(CSLDuplicate(poBand->GetMetadata()), papszMD);
1755 12 : poBand->SetMetadata(papszMerged);
1756 12 : CSLDestroy(papszMerged);
1757 : }
1758 :
1759 12 : if (strlen(poAuxBand->GetDescription()) > 0)
1760 12 : poBand->SetDescription(poAuxBand->GetDescription());
1761 :
1762 12 : if (poAuxBand->GetCategoryNames() != nullptr)
1763 0 : poBand->SetCategoryNames(poAuxBand->GetCategoryNames());
1764 :
1765 12 : if (poAuxBand->GetColorTable() != nullptr &&
1766 0 : poBand->GetColorTable() == nullptr)
1767 0 : poBand->SetColorTable(poAuxBand->GetColorTable());
1768 :
1769 : // histograms?
1770 12 : double dfMin = 0.0;
1771 12 : double dfMax = 0.0;
1772 12 : int nBuckets = 0;
1773 12 : GUIntBig *panHistogram = nullptr;
1774 :
1775 24 : if (poAuxBand->GetDefaultHistogram(&dfMin, &dfMax, &nBuckets,
1776 : &panHistogram, FALSE, nullptr,
1777 12 : nullptr) == CE_None)
1778 : {
1779 8 : poBand->SetDefaultHistogram(dfMin, dfMax, nBuckets, panHistogram);
1780 8 : CPLFree(panHistogram);
1781 : }
1782 :
1783 : // RAT
1784 12 : if (poAuxBand->GetDefaultRAT() != nullptr)
1785 12 : poBand->SetDefaultRAT(poAuxBand->GetDefaultRAT());
1786 :
1787 : // NoData
1788 12 : int bSuccess = FALSE;
1789 12 : const double dfNoDataValue = poAuxBand->GetNoDataValue(&bSuccess);
1790 12 : if (bSuccess)
1791 2 : poBand->SetNoDataValue(dfNoDataValue);
1792 : }
1793 :
1794 8 : GDALClose(poAuxDS);
1795 :
1796 : /* -------------------------------------------------------------------- */
1797 : /* Mark PAM info as clean. */
1798 : /* -------------------------------------------------------------------- */
1799 8 : nPamFlags &= ~GPF_DIRTY;
1800 :
1801 8 : return CE_Failure;
1802 : }
1803 :
1804 : //! @endcond
1805 :
1806 : /************************************************************************/
1807 : /* ClearStatistics() */
1808 : /************************************************************************/
1809 :
1810 5 : void GDALPamDataset::ClearStatistics()
1811 : {
1812 5 : PamInitialize();
1813 5 : if (!psPam)
1814 0 : return;
1815 12 : for (int i = 1; i <= nBands; ++i)
1816 : {
1817 7 : bool bChanged = false;
1818 7 : GDALRasterBand *poBand = GetRasterBand(i);
1819 14 : CPLStringList aosNewMD;
1820 5 : for (const char *pszStr :
1821 17 : cpl::Iterate(static_cast<CSLConstList>(poBand->GetMetadata())))
1822 : {
1823 5 : if (STARTS_WITH_CI(pszStr, "STATISTICS_"))
1824 : {
1825 5 : MarkPamDirty();
1826 5 : bChanged = true;
1827 : }
1828 : else
1829 : {
1830 0 : aosNewMD.AddString(pszStr);
1831 : }
1832 : }
1833 7 : if (bChanged)
1834 : {
1835 1 : poBand->SetMetadata(aosNewMD.List());
1836 : }
1837 : }
1838 :
1839 5 : GDALDataset::ClearStatistics();
1840 : }
|