Coverage Report

Created: 2025-06-13 06:18

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