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

Generated by: LCOV version 1.14