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