LCOV - code coverage report
Current view: top level - gcore - gdalpamdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 662 700 94.6 %
Date: 2025-09-10 17:48:50 Functions: 37 38 97.4 %

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

Generated by: LCOV version 1.14