Coverage Report

Created: 2025-12-31 08:30

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