LCOV - code coverage report
Current view: top level - gcore - gdalpamdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 650 691 94.1 %
Date: 2024-11-21 22:18:42 Functions: 35 36 97.2 %

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

Generated by: LCOV version 1.14