LCOV - code coverage report
Current view: top level - gcore - gdalpamdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 648 689 94.0 %
Date: 2025-07-09 17:50:03 Functions: 35 36 97.2 %

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

Generated by: LCOV version 1.14