• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

OSGeo / gdal / 16038479760

03 Jul 2025 12:12AM UTC coverage: 71.106% (-0.004%) from 71.11%
16038479760

Pull #12692

github

web-flow
Merge efeee3602 into b5d2a80d4
Pull Request #12692: C/C++/Python band algebra: add gdal.abs(), sqrt(), log(), log10() and pow()

80 of 87 new or added lines in 3 files covered. (91.95%)

8848 existing lines in 54 files now uncovered.

574863 of 808463 relevant lines covered (71.11%)

255001.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.05
/gcore/gdalpamdataset.cpp
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()
71,730✔
140
{
141
    SetMOFlags(GetMOFlags() | GMO_PAM_CLASS);
71,720✔
142
}
71,720✔
143

144
/************************************************************************/
145
/*                          ~GDALPamDataset()                           */
146
/************************************************************************/
147

148
GDALPamDataset::~GDALPamDataset()
71,725✔
149

150
{
151
    if (IsMarkedSuppressOnClose())
71,727✔
152
    {
153
        if (psPam && psPam->pszPamFilename != nullptr)
466✔
154
            VSIUnlink(psPam->pszPamFilename);
16✔
155
    }
156
    else if (nPamFlags & GPF_DIRTY)
71,259✔
157
    {
158
        CPLDebug("GDALPamDataset", "In destructor with dirty metadata.");
533✔
159
        GDALPamDataset::TrySaveXML();
533✔
160
    }
161

162
    PamClear();
71,725✔
163
}
71,730✔
164

165
/************************************************************************/
166
/*                             FlushCache()                             */
167
/************************************************************************/
168

169
CPLErr GDALPamDataset::FlushCache(bool bAtClosing)
75,522✔
170

171
{
172
    CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
75,522✔
173
    if (nPamFlags & GPF_DIRTY)
75,522✔
174
    {
175
        if (TrySaveXML() != CE_None)
6,446✔
176
            eErr = CE_Failure;
26✔
177
    }
178
    return eErr;
75,522✔
179
}
180

181
/************************************************************************/
182
/*                            MarkPamDirty()                            */
183
/************************************************************************/
184

185
//! @cond Doxygen_Suppress
186
void GDALPamDataset::MarkPamDirty()
59,694✔
187
{
188
    if ((nPamFlags & GPF_DIRTY) == 0 &&
81,639✔
189
        CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLE_MARK_DIRTY", "YES")))
21,944✔
190
    {
191
        nPamFlags |= GPF_DIRTY;
21,905✔
192
    }
193
}
59,695✔
194

195
// @endcond
196

197
/************************************************************************/
198
/*                           SerializeToXML()                           */
199
/************************************************************************/
200

201
//! @cond Doxygen_Suppress
202
CPLXMLNode *GDALPamDataset::SerializeToXML(const char *pszUnused)
1,516✔
203

204
{
205
    if (psPam == nullptr)
1,516✔
206
        return nullptr;
×
207

208
    /* -------------------------------------------------------------------- */
209
    /*      Setup root node and attributes.                                 */
210
    /* -------------------------------------------------------------------- */
211
    CPLXMLNode *psDSTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
1,516✔
212

213
    /* -------------------------------------------------------------------- */
214
    /*      SRS                                                             */
215
    /* -------------------------------------------------------------------- */
216
    if (psPam->poSRS && !psPam->poSRS->IsEmpty())
1,516✔
217
    {
218
        char *pszWKT = nullptr;
314✔
219
        {
220
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
628✔
221
            if (psPam->poSRS->exportToWkt(&pszWKT) != OGRERR_NONE)
314✔
222
            {
223
                CPLFree(pszWKT);
×
224
                pszWKT = nullptr;
×
225
                const char *const apszOptions[] = {"FORMAT=WKT2", nullptr};
×
226
                psPam->poSRS->exportToWkt(&pszWKT, apszOptions);
×
227
            }
228
        }
229
        CPLXMLNode *psSRSNode =
230
            CPLCreateXMLElementAndValue(psDSTree, "SRS", pszWKT);
314✔
231
        CPLFree(pszWKT);
314✔
232
        const auto &mapping = psPam->poSRS->GetDataAxisToSRSAxisMapping();
314✔
233
        CPLString osMapping;
628✔
234
        for (size_t i = 0; i < mapping.size(); ++i)
943✔
235
        {
236
            if (!osMapping.empty())
629✔
237
                osMapping += ",";
315✔
238
            osMapping += CPLSPrintf("%d", mapping[i]);
629✔
239
        }
240
        CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
314✔
241
                                   osMapping.c_str());
242

243
        const double dfCoordinateEpoch = psPam->poSRS->GetCoordinateEpoch();
314✔
244
        if (dfCoordinateEpoch > 0)
314✔
245
        {
246
            std::string osCoordinateEpoch = CPLSPrintf("%f", dfCoordinateEpoch);
2✔
247
            if (osCoordinateEpoch.find('.') != std::string::npos)
1✔
248
            {
249
                while (osCoordinateEpoch.back() == '0')
6✔
250
                    osCoordinateEpoch.resize(osCoordinateEpoch.size() - 1);
5✔
251
            }
252
            CPLAddXMLAttributeAndValue(psSRSNode, "coordinateEpoch",
1✔
253
                                       osCoordinateEpoch.c_str());
254
        }
255
    }
256

257
    /* -------------------------------------------------------------------- */
258
    /*      GeoTransform.                                                   */
259
    /* -------------------------------------------------------------------- */
260
    if (psPam->bHaveGeoTransform)
1,516✔
261
    {
262
        CPLString oFmt;
682✔
263
        oFmt.Printf("%24.16e,%24.16e,%24.16e,%24.16e,%24.16e,%24.16e",
264
                    psPam->gt[0], psPam->gt[1], psPam->gt[2], psPam->gt[3],
1,364✔
265
                    psPam->gt[4], psPam->gt[5]);
341✔
266
        CPLSetXMLValue(psDSTree, "GeoTransform", oFmt);
341✔
267
    }
268

269
    /* -------------------------------------------------------------------- */
270
    /*      Metadata.                                                       */
271
    /* -------------------------------------------------------------------- */
272
    if (psPam->bHasMetadata)
1,516✔
273
    {
274
        CPLXMLNode *psMD = oMDMD.Serialize();
1,153✔
275
        if (psMD != nullptr)
1,153✔
276
        {
277
            CPLAddXMLChild(psDSTree, psMD);
962✔
278
        }
279
    }
280

281
    /* -------------------------------------------------------------------- */
282
    /*      GCPs                                                            */
283
    /* -------------------------------------------------------------------- */
284
    if (!psPam->asGCPs.empty())
1,516✔
285
    {
286
        GDALSerializeGCPListToXML(psDSTree, psPam->asGCPs, psPam->poGCP_SRS);
9✔
287
    }
288

289
    /* -------------------------------------------------------------------- */
290
    /*      Process bands.                                                  */
291
    /* -------------------------------------------------------------------- */
292

293
    // Find last child
294
    CPLXMLNode *psLastChild = psDSTree->psChild;
1,516✔
295
    for (; psLastChild != nullptr && psLastChild->psNext;
2,387✔
296
         psLastChild = psLastChild->psNext)
871✔
297
    {
298
    }
299

300
    for (int iBand = 0; iBand < GetRasterCount(); iBand++)
3,811✔
301
    {
302
        GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
2,295✔
303

304
        if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
2,295✔
305
            continue;
4✔
306

307
        CPLXMLNode *const psBandTree =
308
            cpl::down_cast<GDALPamRasterBand *>(poBand)->SerializeToXML(
2,291✔
309
                pszUnused);
2,291✔
310

311
        if (psBandTree != nullptr)
2,291✔
312
        {
313
            if (psLastChild == nullptr)
679✔
314
            {
315
                CPLAddXMLChild(psDSTree, psBandTree);
193✔
316
            }
317
            else
318
            {
319
                psLastChild->psNext = psBandTree;
486✔
320
            }
321
            psLastChild = psBandTree;
679✔
322
        }
323
    }
324

325
    /* -------------------------------------------------------------------- */
326
    /*      We don't want to return anything if we had no metadata to       */
327
    /*      attach.                                                         */
328
    /* -------------------------------------------------------------------- */
329
    if (psDSTree->psChild == nullptr)
1,516✔
330
    {
331
        CPLDestroyXMLNode(psDSTree);
239✔
332
        psDSTree = nullptr;
239✔
333
    }
334

335
    return psDSTree;
1,516✔
336
}
337

338
/************************************************************************/
339
/*                           PamInitialize()                            */
340
/************************************************************************/
341

342
void GDALPamDataset::PamInitialize()
778,341✔
343

344
{
345
#ifdef PAM_ENABLED
346
    const char *const pszPamDefault = "YES";
778,341✔
347
#else
348
    const char *const pszPamDefault = "NO";
349
#endif
350

351
    if (psPam)
778,341✔
352
        return;
731,019✔
353

354
    if (!CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLED", pszPamDefault)))
47,322✔
355
    {
356
        CPLDebugOnce("GDAL", "PAM is disabled");
512✔
357
        nPamFlags |= GPF_DISABLED;
512✔
358
    }
359

360
    /* ERO 2011/04/13 : GPF_AUXMODE seems to be unimplemented */
361
    if (EQUAL(CPLGetConfigOption("GDAL_PAM_MODE", "PAM"), "AUX"))
47,322✔
362
        nPamFlags |= GPF_AUXMODE;
×
363

364
    psPam = new GDALDatasetPamInfo;
47,322✔
365
    for (int iBand = 0; iBand < GetRasterCount(); iBand++)
691,838✔
366
    {
367
        GDALRasterBand *poBand = GetRasterBand(iBand + 1);
644,515✔
368

369
        if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
644,514✔
370
            continue;
137✔
371

372
        cpl::down_cast<GDALPamRasterBand *>(poBand)->PamInitialize();
644,378✔
373
    }
374
}
375

376
/************************************************************************/
377
/*                              PamClear()                              */
378
/************************************************************************/
379

380
void GDALPamDataset::PamClear()
71,724✔
381

382
{
383
    if (psPam)
71,724✔
384
    {
385
        CPLFree(psPam->pszPamFilename);
47,320✔
386
        if (psPam->poSRS)
47,322✔
387
            psPam->poSRS->Release();
629✔
388
        if (psPam->poGCP_SRS)
47,322✔
389
            psPam->poGCP_SRS->Release();
36✔
390

391
        delete psPam;
47,322✔
392
        psPam = nullptr;
47,321✔
393
    }
394
}
71,725✔
395

396
/************************************************************************/
397
/*                              XMLInit()                               */
398
/************************************************************************/
399

400
CPLErr GDALPamDataset::XMLInit(const CPLXMLNode *psTree, const char *pszUnused)
1,406✔
401

402
{
403
    /* -------------------------------------------------------------------- */
404
    /*      Check for an SRS node.                                          */
405
    /* -------------------------------------------------------------------- */
406
    if (const CPLXMLNode *psSRSNode = CPLGetXMLNode(psTree, "SRS"))
1,406✔
407
    {
408
        if (psPam->poSRS)
367✔
409
            psPam->poSRS->Release();
99✔
410
        psPam->poSRS = new OGRSpatialReference();
367✔
411
        psPam->poSRS->SetFromUserInput(
367✔
412
            CPLGetXMLValue(psSRSNode, nullptr, ""),
413
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS);
414
        const char *pszMapping =
415
            CPLGetXMLValue(psSRSNode, "dataAxisToSRSAxisMapping", nullptr);
367✔
416
        if (pszMapping)
367✔
417
        {
418
            char **papszTokens =
419
                CSLTokenizeStringComplex(pszMapping, ",", FALSE, FALSE);
289✔
420
            std::vector<int> anMapping;
578✔
421
            for (int i = 0; papszTokens && papszTokens[i]; i++)
868✔
422
            {
423
                anMapping.push_back(atoi(papszTokens[i]));
579✔
424
            }
425
            CSLDestroy(papszTokens);
289✔
426
            psPam->poSRS->SetDataAxisToSRSAxisMapping(anMapping);
289✔
427
        }
428
        else
429
        {
430
            psPam->poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
78✔
431
        }
432

433
        const char *pszCoordinateEpoch =
434
            CPLGetXMLValue(psSRSNode, "coordinateEpoch", nullptr);
367✔
435
        if (pszCoordinateEpoch)
367✔
436
            psPam->poSRS->SetCoordinateEpoch(CPLAtof(pszCoordinateEpoch));
2✔
437
    }
438

439
    /* -------------------------------------------------------------------- */
440
    /*      Check for a GeoTransform node.                                  */
441
    /* -------------------------------------------------------------------- */
442
    const char *pszGT = CPLGetXMLValue(psTree, "GeoTransform", "");
1,406✔
443
    if (strlen(pszGT) > 0)
1,406✔
444
    {
445
        const CPLStringList aosTokens(
446
            CSLTokenizeStringComplex(pszGT, ",", FALSE, FALSE));
722✔
447
        if (aosTokens.size() != 6)
361✔
448
        {
449
            CPLError(CE_Warning, CPLE_AppDefined,
×
450
                     "GeoTransform node does not have expected six values.");
451
        }
452
        else
453
        {
454
            for (int iTA = 0; iTA < 6; iTA++)
2,527✔
455
                psPam->gt[iTA] = CPLAtof(aosTokens[iTA]);
2,166✔
456
            psPam->bHaveGeoTransform = TRUE;
361✔
457
        }
458
    }
459

460
    /* -------------------------------------------------------------------- */
461
    /*      Check for GCPs.                                                 */
462
    /* -------------------------------------------------------------------- */
463
    if (const CPLXMLNode *psGCPList = CPLGetXMLNode(psTree, "GCPList"))
1,406✔
464
    {
465
        if (psPam->poGCP_SRS)
28✔
466
            psPam->poGCP_SRS->Release();
×
467
        psPam->poGCP_SRS = nullptr;
28✔
468

469
        // Make sure any previous GCPs, perhaps from an .aux file, are cleared
470
        // if we have new ones.
471
        psPam->asGCPs.clear();
28✔
472
        GDALDeserializeGCPListFromXML(psGCPList, psPam->asGCPs,
28✔
473
                                      &(psPam->poGCP_SRS));
28✔
474
    }
475

476
    /* -------------------------------------------------------------------- */
477
    /*      Apply any dataset level metadata.                               */
478
    /* -------------------------------------------------------------------- */
479
    if (oMDMD.XMLInit(psTree, TRUE))
1,406✔
480
    {
481
        psPam->bHasMetadata = TRUE;
1,051✔
482
    }
483

484
    /* -------------------------------------------------------------------- */
485
    /*      Try loading ESRI xml encoded GeodataXform.                      */
486
    /* -------------------------------------------------------------------- */
487
    {
488
        // previously we only tried to load GeodataXform if we didn't already
489
        // encounter a valid SRS at this stage. But in some cases a PAMDataset
490
        // may have both a SRS child element AND a GeodataXform with a SpatialReference
491
        // child element. In this case we should prioritize the GeodataXform
492
        // over the root PAMDataset SRS node.
493

494
        // ArcGIS 9.3: GeodataXform as a root element
495
        const CPLXMLNode *psGeodataXform =
496
            CPLGetXMLNode(psTree, "=GeodataXform");
1,406✔
497
        CPLXMLTreeCloser oTreeValueAsXML(nullptr);
2,812✔
498
        if (psGeodataXform != nullptr)
1,406✔
499
        {
500
            char *apszMD[2];
501
            apszMD[0] = CPLSerializeXMLTree(psGeodataXform);
2✔
502
            apszMD[1] = nullptr;
2✔
503
            oMDMD.SetMetadata(apszMD, "xml:ESRI");
2✔
504
            CPLFree(apszMD[0]);
2✔
505
        }
506
        else
507
        {
508
            // ArcGIS 10: GeodataXform as content of xml:ESRI metadata domain.
509
            char **papszXML = oMDMD.GetMetadata("xml:ESRI");
1,404✔
510
            if (CSLCount(papszXML) == 1)
1,404✔
511
            {
512
                oTreeValueAsXML.reset(CPLParseXMLString(papszXML[0]));
11✔
513
                if (oTreeValueAsXML)
11✔
514
                    psGeodataXform =
515
                        CPLGetXMLNode(oTreeValueAsXML.get(), "=GeodataXform");
11✔
516
            }
517
        }
518

519
        if (psGeodataXform)
1,406✔
520
        {
521
            const char *pszESRI_WKT =
522
                CPLGetXMLValue(psGeodataXform, "SpatialReference.WKT", nullptr);
9✔
523
            if (pszESRI_WKT)
9✔
524
            {
525
                auto poSRS = std::make_unique<OGRSpatialReference>();
9✔
526
                poSRS->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
9✔
527
                if (poSRS->importFromWkt(pszESRI_WKT) != OGRERR_NONE)
9✔
528
                {
529
                    poSRS.reset();
×
530
                }
531
                delete psPam->poSRS;
9✔
532
                psPam->poSRS = poSRS.release();
9✔
533
            }
534

535
            // Parse GCPs
536
            const CPLXMLNode *psSourceGCPS =
537
                CPLGetXMLNode(psGeodataXform, "SourceGCPs");
9✔
538
            const CPLXMLNode *psTargetGCPs =
539
                CPLGetXMLNode(psGeodataXform, "TargetGCPs");
9✔
540
            const CPLXMLNode *psCoeffX =
541
                CPLGetXMLNode(psGeodataXform, "CoeffX");
9✔
542
            const CPLXMLNode *psCoeffY =
543
                CPLGetXMLNode(psGeodataXform, "CoeffY");
9✔
544
            if (psSourceGCPS && psTargetGCPs && !psPam->bHaveGeoTransform)
9✔
545
            {
546
                std::vector<double> adfSource;
12✔
547
                std::vector<double> adfTarget;
12✔
548
                bool ySourceAllNegative = true;
6✔
549
                for (auto psIter = psSourceGCPS->psChild; psIter;
80✔
550
                     psIter = psIter->psNext)
74✔
551
                {
552
                    if (psIter->eType == CXT_Element &&
74✔
553
                        strcmp(psIter->pszValue, "Double") == 0)
68✔
554
                    {
555
                        adfSource.push_back(
68✔
556
                            CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
68✔
557
                        if ((adfSource.size() % 2) == 0 && adfSource.back() > 0)
68✔
558
                            ySourceAllNegative = false;
28✔
559
                    }
560
                }
561
                for (auto psIter = psTargetGCPs->psChild; psIter;
80✔
562
                     psIter = psIter->psNext)
74✔
563
                {
564
                    if (psIter->eType == CXT_Element &&
74✔
565
                        strcmp(psIter->pszValue, "Double") == 0)
68✔
566
                    {
567
                        adfTarget.push_back(
68✔
568
                            CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
68✔
569
                    }
570
                }
571
                if (!adfSource.empty() &&
12✔
572
                    adfSource.size() == adfTarget.size() &&
12✔
573
                    (adfSource.size() % 2) == 0)
6✔
574
                {
575
                    std::vector<gdal::GCP> asGCPs;
6✔
576
                    for (size_t i = 0; i + 1 < adfSource.size(); i += 2)
40✔
577
                    {
578
                        asGCPs.emplace_back("", "",
579
                                            /* pixel = */ adfSource[i],
34✔
580
                                            /* line = */
581
                                            ySourceAllNegative
582
                                                ? -adfSource[i + 1]
96✔
583
                                                : adfSource[i + 1],
28✔
584
                                            /* X = */ adfTarget[i],
34✔
585
                                            /* Y = */ adfTarget[i + 1]);
68✔
586
                    }
587
                    GDALPamDataset::SetGCPs(static_cast<int>(asGCPs.size()),
6✔
588
                                            gdal::GCP::c_ptr(asGCPs),
589
                                            psPam->poSRS);
6✔
590
                    delete psPam->poSRS;
6✔
591
                    psPam->poSRS = nullptr;
6✔
592
                }
6✔
593
            }
594
            else if (psCoeffX && psCoeffY && !psPam->bHaveGeoTransform &&
4✔
595
                     EQUAL(
1✔
596
                         CPLGetXMLValue(psGeodataXform, "PolynomialOrder", ""),
597
                         "1"))
598
            {
599
                std::vector<double> adfCoeffX;
2✔
600
                std::vector<double> adfCoeffY;
2✔
601
                for (auto psIter = psCoeffX->psChild; psIter;
5✔
602
                     psIter = psIter->psNext)
4✔
603
                {
604
                    if (psIter->eType == CXT_Element &&
4✔
605
                        strcmp(psIter->pszValue, "Double") == 0)
3✔
606
                    {
607
                        adfCoeffX.push_back(
3✔
608
                            CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
3✔
609
                    }
610
                }
611
                for (auto psIter = psCoeffY->psChild; psIter;
5✔
612
                     psIter = psIter->psNext)
4✔
613
                {
614
                    if (psIter->eType == CXT_Element &&
4✔
615
                        strcmp(psIter->pszValue, "Double") == 0)
3✔
616
                    {
617
                        adfCoeffY.push_back(
3✔
618
                            CPLAtof(CPLGetXMLValue(psIter, nullptr, "0")));
3✔
619
                    }
620
                }
621
                if (adfCoeffX.size() == 3 && adfCoeffY.size() == 3)
1✔
622
                {
623
                    psPam->gt[0] = adfCoeffX[0];
1✔
624
                    psPam->gt[1] = adfCoeffX[1];
1✔
625
                    // Looking at the example of https://github.com/qgis/QGIS/issues/53125#issuecomment-1567650082
626
                    // when comparing the .pgwx world file and .png.aux.xml file,
627
                    // it appears that the sign of the coefficients for the line
628
                    // terms must be negated (which is a bit in line with the
629
                    // negation of dfGCPLine in the above GCP case)
630
                    psPam->gt[2] = -adfCoeffX[2];
1✔
631
                    psPam->gt[3] = adfCoeffY[0];
1✔
632
                    psPam->gt[4] = adfCoeffY[1];
1✔
633
                    psPam->gt[5] = -adfCoeffY[2];
1✔
634

635
                    // Looking at the example of https://github.com/qgis/QGIS/issues/53125#issuecomment-1567650082
636
                    // when comparing the .pgwx world file and .png.aux.xml file,
637
                    // one can see that they have the same origin, so knowing
638
                    // that world file uses a center-of-pixel convention,
639
                    // correct from center of pixel to top left of pixel
640
                    psPam->gt[0] -= 0.5 * psPam->gt[1];
1✔
641
                    psPam->gt[0] -= 0.5 * psPam->gt[2];
1✔
642
                    psPam->gt[3] -= 0.5 * psPam->gt[4];
1✔
643
                    psPam->gt[3] -= 0.5 * psPam->gt[5];
1✔
644

645
                    psPam->bHaveGeoTransform = TRUE;
1✔
646
                }
647
            }
648
        }
649
    }
650

651
    /* -------------------------------------------------------------------- */
652
    /*      Process bands.                                                  */
653
    /* -------------------------------------------------------------------- */
654
    for (const CPLXMLNode *psBandTree = psTree->psChild; psBandTree;
4,232✔
655
         psBandTree = psBandTree->psNext)
2,826✔
656
    {
657
        if (psBandTree->eType != CXT_Element ||
2,826✔
658
            !EQUAL(psBandTree->pszValue, "PAMRasterBand"))
2,822✔
659
            continue;
2,168✔
660

661
        const int nBand = atoi(CPLGetXMLValue(psBandTree, "band", "0"));
658✔
662

663
        if (nBand < 1 || nBand > GetRasterCount())
658✔
664
            continue;
41✔
665

666
        GDALRasterBand *poBand = GetRasterBand(nBand);
617✔
667

668
        if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
617✔
669
            continue;
×
670

671
        GDALPamRasterBand *poPamBand =
672
            cpl::down_cast<GDALPamRasterBand *>(GetRasterBand(nBand));
617✔
673

674
        poPamBand->XMLInit(psBandTree, pszUnused);
617✔
675
    }
676

677
    /* -------------------------------------------------------------------- */
678
    /*      Preserve Array information.                                     */
679
    /* -------------------------------------------------------------------- */
680
    for (const CPLXMLNode *psIter = psTree->psChild; psIter;
4,232✔
681
         psIter = psIter->psNext)
2,826✔
682
    {
683
        if (psIter->eType == CXT_Element &&
5,648✔
684
            (strcmp(psIter->pszValue, "Array") == 0 ||
5,608✔
685
             (psPam->osDerivedDatasetName.empty() &&
2,786✔
686
              strcmp(psIter->pszValue, "DerivedDataset") == 0)))
2,776✔
687
        {
688
            CPLXMLNode sArrayTmp = *psIter;
53✔
689
            sArrayTmp.psNext = nullptr;
53✔
690
            psPam->m_apoOtherNodes.emplace_back(
53✔
691
                CPLXMLTreeCloser(CPLCloneXMLTree(&sArrayTmp)));
53✔
692
        }
693
    }
694

695
    /* -------------------------------------------------------------------- */
696
    /*      Clear dirty flag.                                               */
697
    /* -------------------------------------------------------------------- */
698
    nPamFlags &= ~GPF_DIRTY;
1,406✔
699

700
    return CE_None;
1,406✔
701
}
702

703
/************************************************************************/
704
/*                        SetPhysicalFilename()                         */
705
/************************************************************************/
706

707
void GDALPamDataset::SetPhysicalFilename(const char *pszFilename)
4,003✔
708

709
{
710
    PamInitialize();
4,003✔
711

712
    if (psPam)
4,003✔
713
        psPam->osPhysicalFilename = pszFilename;
4,003✔
714
}
4,003✔
715

716
/************************************************************************/
717
/*                        GetPhysicalFilename()                         */
718
/************************************************************************/
719

720
const char *GDALPamDataset::GetPhysicalFilename()
144✔
721

722
{
723
    PamInitialize();
144✔
724

725
    if (psPam)
144✔
726
        return psPam->osPhysicalFilename;
144✔
727

728
    return "";
×
729
}
730

731
/************************************************************************/
732
/*                         SetSubdatasetName()                          */
733
/************************************************************************/
734

735
/* Mutually exclusive with SetDerivedDatasetName() */
736
void GDALPamDataset::SetSubdatasetName(const char *pszSubdataset)
710✔
737

738
{
739
    PamInitialize();
710✔
740

741
    if (psPam)
710✔
742
        psPam->osSubdatasetName = pszSubdataset;
710✔
743
}
710✔
744

745
/************************************************************************/
746
/*                        SetDerivedDatasetName()                        */
747
/************************************************************************/
748

749
/* Mutually exclusive with SetSubdatasetName() */
750
void GDALPamDataset::SetDerivedDatasetName(const char *pszDerivedDataset)
181✔
751

752
{
753
    PamInitialize();
181✔
754

755
    if (psPam)
181✔
756
        psPam->osDerivedDatasetName = pszDerivedDataset;
181✔
757
}
181✔
758

759
/************************************************************************/
760
/*                         GetSubdatasetName()                          */
761
/************************************************************************/
762

763
const char *GDALPamDataset::GetSubdatasetName()
3✔
764

765
{
766
    PamInitialize();
3✔
767

768
    if (psPam)
3✔
769
        return psPam->osSubdatasetName;
3✔
770

771
    return "";
×
772
}
773

774
/************************************************************************/
775
/*                          BuildPamFilename()                          */
776
/************************************************************************/
777

778
const char *GDALPamDataset::BuildPamFilename()
38,537✔
779

780
{
781
    if (psPam == nullptr)
38,537✔
782
        return nullptr;
×
783

784
    /* -------------------------------------------------------------------- */
785
    /*      What is the name of the physical file we are referencing?       */
786
    /*      We allow an override via the psPam->pszPhysicalFile item.       */
787
    /* -------------------------------------------------------------------- */
788
    if (psPam->pszPamFilename != nullptr)
38,537✔
789
        return psPam->pszPamFilename;
2,411✔
790

791
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
36,126✔
792

793
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
36,125✔
794
        pszPhysicalFile = GetDescription();
34,083✔
795

796
    if (strlen(pszPhysicalFile) == 0)
36,126✔
797
        return nullptr;
229✔
798

799
    /* -------------------------------------------------------------------- */
800
    /*      Try a proxy lookup, otherwise just add .aux.xml.                */
801
    /* -------------------------------------------------------------------- */
802
    const char *pszProxyPam = PamGetProxy(pszPhysicalFile);
35,897✔
803
    if (pszProxyPam != nullptr)
35,896✔
804
        psPam->pszPamFilename = CPLStrdup(pszProxyPam);
4✔
805
    else
806
    {
807
        if (!GDALCanFileAcceptSidecarFile(pszPhysicalFile))
35,892✔
808
            return nullptr;
107✔
809
        psPam->pszPamFilename =
71,571✔
810
            static_cast<char *>(CPLMalloc(strlen(pszPhysicalFile) + 10));
35,786✔
811
        strcpy(psPam->pszPamFilename, pszPhysicalFile);
35,785✔
812
        strcat(psPam->pszPamFilename, ".aux.xml");
35,785✔
813
    }
814

815
    return psPam->pszPamFilename;
35,789✔
816
}
817

818
/************************************************************************/
819
/*                   IsPamFilenameAPotentialSiblingFile()               */
820
/************************************************************************/
821

822
int GDALPamDataset::IsPamFilenameAPotentialSiblingFile()
30,759✔
823
{
824
    if (psPam == nullptr)
30,759✔
825
        return FALSE;
×
826

827
    /* -------------------------------------------------------------------- */
828
    /*      Determine if the PAM filename is a .aux.xml file next to the    */
829
    /*      physical file, or if it comes from the ProxyDB                  */
830
    /* -------------------------------------------------------------------- */
831
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
30,759✔
832

833
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
30,759✔
834
        pszPhysicalFile = GetDescription();
29,834✔
835

836
    size_t nLenPhysicalFile = strlen(pszPhysicalFile);
30,759✔
837
    int bIsSiblingPamFile =
30,759✔
838
        strncmp(psPam->pszPamFilename, pszPhysicalFile, nLenPhysicalFile) ==
30,759✔
839
            0 &&
61,512✔
840
        strcmp(psPam->pszPamFilename + nLenPhysicalFile, ".aux.xml") == 0;
30,753✔
841

842
    return bIsSiblingPamFile;
30,759✔
843
}
844

845
/************************************************************************/
846
/*                             TryLoadXML()                             */
847
/************************************************************************/
848

849
CPLErr GDALPamDataset::TryLoadXML(CSLConstList papszSiblingFiles)
36,503✔
850

851
{
852
    PamInitialize();
36,503✔
853

854
    if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
36,502✔
855
        return CE_None;
539✔
856

857
    /* -------------------------------------------------------------------- */
858
    /*      Clear dirty flag.  Generally when we get to this point is       */
859
    /*      from a call at the end of the Open() method, and some calls     */
860
    /*      may have already marked the PAM info as dirty (for instance     */
861
    /*      setting metadata), but really everything to this point is       */
862
    /*      reproducible, and so the PAM info should not really be          */
863
    /*      thought of as dirty.                                            */
864
    /* -------------------------------------------------------------------- */
865
    nPamFlags &= ~GPF_DIRTY;
35,963✔
866

867
    /* -------------------------------------------------------------------- */
868
    /*      Try reading the file.                                           */
869
    /* -------------------------------------------------------------------- */
870
    if (!BuildPamFilename())
35,963✔
871
        return CE_None;
245✔
872

873
    /* -------------------------------------------------------------------- */
874
    /*      In case the PAM filename is a .aux.xml file next to the         */
875
    /*      physical file and we have a siblings list, then we can skip     */
876
    /*      stat'ing the filesystem.                                        */
877
    /* -------------------------------------------------------------------- */
878
    VSIStatBufL sStatBuf;
879
    CPLXMLNode *psTree = nullptr;
35,717✔
880

881
    if (papszSiblingFiles != nullptr && IsPamFilenameAPotentialSiblingFile() &&
63,434✔
882
        GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
27,718✔
883
    {
884
        const int iSibling = CSLFindString(
27,718✔
885
            papszSiblingFiles, CPLGetFilename(psPam->pszPamFilename));
27,717✔
886
        if (iSibling >= 0)
27,718✔
887
        {
888
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
433✔
889
            psTree = CPLParseXMLFile(psPam->pszPamFilename);
433✔
890
        }
891
    }
892
    else if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
8,001✔
893
                        VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
8,855✔
894
             VSI_ISREG(sStatBuf.st_mode))
855✔
895
    {
896
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
855✔
897
        psTree = CPLParseXMLFile(psPam->pszPamFilename);
855✔
898
    }
899

900
    /* -------------------------------------------------------------------- */
901
    /*      If we are looking for a subdataset, search for its subtree now. */
902
    /* -------------------------------------------------------------------- */
903
    if (psTree)
35,718✔
904
    {
905
        std::string osSubNode;
2,558✔
906
        std::string osSubNodeValue;
2,558✔
907
        if (!psPam->osSubdatasetName.empty())
1,279✔
908
        {
909
            osSubNode = "Subdataset";
161✔
910
            osSubNodeValue = psPam->osSubdatasetName;
161✔
911
        }
912
        else if (!psPam->osDerivedDatasetName.empty())
1,118✔
913
        {
914
            osSubNode = "DerivedDataset";
17✔
915
            osSubNodeValue = psPam->osDerivedDatasetName;
17✔
916
        }
917
        if (!osSubNode.empty())
1,279✔
918
        {
919
            CPLXMLNode *psSubTree = psTree->psChild;
178✔
920

921
            for (; psSubTree != nullptr; psSubTree = psSubTree->psNext)
326✔
922
            {
923
                if (psSubTree->eType != CXT_Element ||
394✔
924
                    !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
197✔
925
                    continue;
129✔
926

927
                if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
68✔
928
                           osSubNodeValue.c_str()))
929
                    continue;
19✔
930

931
                psSubTree = CPLGetXMLNode(psSubTree, "PAMDataset");
49✔
932
                break;
49✔
933
            }
934

935
            if (psSubTree != nullptr)
178✔
936
                psSubTree = CPLCloneXMLTree(psSubTree);
49✔
937

938
            CPLDestroyXMLNode(psTree);
178✔
939
            psTree = psSubTree;
178✔
940
        }
941
    }
942

943
    /* -------------------------------------------------------------------- */
944
    /*      If we fail, try .aux.                                           */
945
    /* -------------------------------------------------------------------- */
946
    if (psTree == nullptr)
35,718✔
947
        return TryLoadAux(papszSiblingFiles);
34,568✔
948

949
    /* -------------------------------------------------------------------- */
950
    /*      Initialize ourselves from this XML tree.                        */
951
    /* -------------------------------------------------------------------- */
952

953
    CPLString osVRTPath(CPLGetPathSafe(psPam->pszPamFilename));
1,150✔
954
    const CPLErr eErr = XMLInit(psTree, osVRTPath);
1,150✔
955

956
    CPLDestroyXMLNode(psTree);
1,150✔
957

958
    if (eErr != CE_None)
1,150✔
959
        PamClear();
×
960

961
    return eErr;
1,150✔
962
}
963

964
/************************************************************************/
965
/*                             TrySaveXML()                             */
966
/************************************************************************/
967

968
CPLErr GDALPamDataset::TrySaveXML()
7,025✔
969

970
{
971
    nPamFlags &= ~GPF_DIRTY;
7,025✔
972

973
    if (psPam == nullptr || (nPamFlags & GPF_NOSAVE) != 0 ||
7,025✔
974
        (nPamFlags & GPF_DISABLED) != 0)
1,965✔
975
        return CE_None;
5,553✔
976

977
    /* -------------------------------------------------------------------- */
978
    /*      Make sure we know the filename we want to store in.             */
979
    /* -------------------------------------------------------------------- */
980
    if (!BuildPamFilename())
1,472✔
981
        return CE_None;
91✔
982

983
    /* -------------------------------------------------------------------- */
984
    /*      Build the XML representation of the auxiliary metadata.          */
985
    /* -------------------------------------------------------------------- */
986
    CPLXMLNode *psTree = SerializeToXML(nullptr);
1,381✔
987

988
    if (psTree == nullptr)
1,381✔
989
    {
990
        /* If we have unset all metadata, we have to delete the PAM file */
991
        CPLPushErrorHandler(CPLQuietErrorHandler);
239✔
992
        VSIUnlink(psPam->pszPamFilename);
239✔
993
        CPLPopErrorHandler();
239✔
994
        return CE_None;
239✔
995
    }
996

997
    /* -------------------------------------------------------------------- */
998
    /*      If we are working with a subdataset, we need to integrate       */
999
    /*      the subdataset tree within the whole existing pam tree,         */
1000
    /*      after removing any old version of the same subdataset.          */
1001
    /* -------------------------------------------------------------------- */
1002
    std::string osSubNode;
2,284✔
1003
    std::string osSubNodeValue;
1,142✔
1004
    if (!psPam->osSubdatasetName.empty())
1,142✔
1005
    {
1006
        osSubNode = "Subdataset";
30✔
1007
        osSubNodeValue = psPam->osSubdatasetName;
30✔
1008
    }
1009
    else if (!psPam->osDerivedDatasetName.empty())
1,112✔
1010
    {
1011
        osSubNode = "DerivedDataset";
8✔
1012
        osSubNodeValue = psPam->osDerivedDatasetName;
8✔
1013
    }
1014
    if (!osSubNode.empty())
1,142✔
1015
    {
1016
        CPLXMLNode *psOldTree = nullptr;
38✔
1017

1018
        VSIStatBufL sStatBuf;
1019
        if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
38✔
1020
                       VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
42✔
1021
            VSI_ISREG(sStatBuf.st_mode))
4✔
1022
        {
1023
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4✔
1024
            psOldTree = CPLParseXMLFile(psPam->pszPamFilename);
4✔
1025
        }
1026

1027
        if (psOldTree == nullptr)
38✔
1028
            psOldTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
34✔
1029

1030
        CPLXMLNode *psSubTree = psOldTree->psChild;
38✔
1031
        for (/* initialized above */; psSubTree != nullptr;
43✔
1032
             psSubTree = psSubTree->psNext)
5✔
1033
        {
1034
            if (psSubTree->eType != CXT_Element ||
10✔
1035
                !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
5✔
1036
                continue;
1✔
1037

1038
            if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
4✔
1039
                       osSubNodeValue.c_str()))
1040
                continue;
4✔
1041

1042
            break;
×
1043
        }
1044

1045
        if (psSubTree == nullptr)
38✔
1046
        {
1047
            psSubTree =
1048
                CPLCreateXMLNode(psOldTree, CXT_Element, osSubNode.c_str());
38✔
1049
            CPLCreateXMLNode(CPLCreateXMLNode(psSubTree, CXT_Attribute, "name"),
38✔
1050
                             CXT_Text, osSubNodeValue.c_str());
1051
        }
1052

1053
        CPLXMLNode *psOldPamDataset = CPLGetXMLNode(psSubTree, "PAMDataset");
38✔
1054
        if (psOldPamDataset != nullptr)
38✔
1055
        {
1056
            CPLRemoveXMLChild(psSubTree, psOldPamDataset);
×
1057
            CPLDestroyXMLNode(psOldPamDataset);
×
1058
        }
1059

1060
        CPLAddXMLChild(psSubTree, psTree);
38✔
1061
        psTree = psOldTree;
38✔
1062
    }
1063

1064
    /* -------------------------------------------------------------------- */
1065
    /*      Preserve other information.                                     */
1066
    /* -------------------------------------------------------------------- */
1067
    for (const auto &poOtherNode : psPam->m_apoOtherNodes)
1,147✔
1068
    {
1069
        CPLAddXMLChild(psTree, CPLCloneXMLTree(poOtherNode.get()));
5✔
1070
    }
1071

1072
    /* -------------------------------------------------------------------- */
1073
    /*      Try saving the auxiliary metadata.                               */
1074
    /* -------------------------------------------------------------------- */
1075

1076
    CPLPushErrorHandler(CPLQuietErrorHandler);
1,142✔
1077
    const int bSaved = CPLSerializeXMLTreeToFile(psTree, psPam->pszPamFilename);
1,142✔
1078
    CPLPopErrorHandler();
1,142✔
1079

1080
    /* -------------------------------------------------------------------- */
1081
    /*      If it fails, check if we have a proxy directory for auxiliary    */
1082
    /*      metadata to be stored in, and try to save there.                */
1083
    /* -------------------------------------------------------------------- */
1084
    CPLErr eErr = CE_None;
1,142✔
1085

1086
    if (bSaved)
1,142✔
1087
        eErr = CE_None;
1,114✔
1088
    else
1089
    {
1090
        const char *pszBasename = GetDescription();
28✔
1091

1092
        if (psPam->osPhysicalFilename.length() > 0)
28✔
1093
            pszBasename = psPam->osPhysicalFilename;
10✔
1094

1095
        const char *pszNewPam = nullptr;
28✔
1096
        if (PamGetProxy(pszBasename) == nullptr &&
56✔
1097
            ((pszNewPam = PamAllocateProxy(pszBasename)) != nullptr))
28✔
1098
        {
1099
            CPLErrorReset();
1✔
1100
            CPLFree(psPam->pszPamFilename);
1✔
1101
            psPam->pszPamFilename = CPLStrdup(pszNewPam);
1✔
1102
            eErr = TrySaveXML();
1✔
1103
        }
1104
        /* No way we can save into a /vsicurl resource */
1105
        else if (!STARTS_WITH(psPam->pszPamFilename, "/vsicurl"))
27✔
1106
        {
1107
            CPLError(CE_Warning, CPLE_AppDefined,
27✔
1108
                     "Unable to save auxiliary information in %s.",
1109
                     psPam->pszPamFilename);
27✔
1110
            eErr = CE_Warning;
27✔
1111
        }
1112
    }
1113

1114
    /* -------------------------------------------------------------------- */
1115
    /*      Cleanup                                                         */
1116
    /* -------------------------------------------------------------------- */
1117
    CPLDestroyXMLNode(psTree);
1,142✔
1118

1119
    return eErr;
1,142✔
1120
}
1121

1122
/************************************************************************/
1123
/*                             CloneInfo()                              */
1124
/************************************************************************/
1125

1126
CPLErr GDALPamDataset::CloneInfo(GDALDataset *poSrcDS, int nCloneFlags)
7,208✔
1127

1128
{
1129
    const int bOnlyIfMissing = nCloneFlags & GCIF_ONLY_IF_MISSING;
7,208✔
1130
    const int nSavedMOFlags = GetMOFlags();
7,208✔
1131

1132
    PamInitialize();
7,208✔
1133

1134
    /* -------------------------------------------------------------------- */
1135
    /*      Suppress NotImplemented error messages - mainly needed if PAM   */
1136
    /*      disabled.                                                       */
1137
    /* -------------------------------------------------------------------- */
1138
    SetMOFlags(nSavedMOFlags | GMO_IGNORE_UNIMPLEMENTED);
7,208✔
1139

1140
    /* -------------------------------------------------------------------- */
1141
    /*      GeoTransform                                                    */
1142
    /* -------------------------------------------------------------------- */
1143
    if (nCloneFlags & GCIF_GEOTRANSFORM)
7,208✔
1144
    {
1145
        GDALGeoTransform gt;
7,206✔
1146

1147
        if (poSrcDS->GetGeoTransform(gt) == CE_None)
7,206✔
1148
        {
1149
            GDALGeoTransform oldGT;
2,298✔
1150

1151
            if (!bOnlyIfMissing || GetGeoTransform(oldGT) != CE_None)
2,298✔
1152
                SetGeoTransform(gt);
216✔
1153
        }
1154
    }
1155

1156
    /* -------------------------------------------------------------------- */
1157
    /*      Projection                                                      */
1158
    /* -------------------------------------------------------------------- */
1159
    if (nCloneFlags & GCIF_PROJECTION)
7,208✔
1160
    {
1161
        const auto poSRS = poSrcDS->GetSpatialRef();
7,206✔
1162

1163
        if (poSRS != nullptr)
7,206✔
1164
        {
1165
            if (!bOnlyIfMissing || GetSpatialRef() == nullptr)
2,037✔
1166
                SetSpatialRef(poSRS);
147✔
1167
        }
1168
    }
1169

1170
    /* -------------------------------------------------------------------- */
1171
    /*      GCPs                                                            */
1172
    /* -------------------------------------------------------------------- */
1173
    if (nCloneFlags & GCIF_GCPS)
7,208✔
1174
    {
1175
        if (poSrcDS->GetGCPCount() > 0)
7,208✔
1176
        {
1177
            if (!bOnlyIfMissing || GetGCPCount() == 0)
20✔
1178
            {
1179
                SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(),
3✔
1180
                        poSrcDS->GetGCPSpatialRef());
3✔
1181
            }
1182
        }
1183
    }
1184

1185
    /* -------------------------------------------------------------------- */
1186
    /*      Metadata                                                        */
1187
    /* -------------------------------------------------------------------- */
1188
    if (nCloneFlags & GCIF_METADATA)
7,208✔
1189
    {
1190
        for (const char *pszMDD : {"", "RPC", "json:ISIS3", "json:VICAR"})
12,925✔
1191
        {
1192
            auto papszSrcMD = poSrcDS->GetMetadata(pszMDD);
10,340✔
1193
            if (papszSrcMD != nullptr)
10,340✔
1194
            {
1195
                if (!bOnlyIfMissing ||
3,860✔
1196
                    CSLCount(GetMetadata(pszMDD)) != CSLCount(papszSrcMD))
1,930✔
1197
                {
1198
                    SetMetadata(papszSrcMD, pszMDD);
396✔
1199
                }
1200
            }
1201
        }
1202
    }
1203

1204
    /* -------------------------------------------------------------------- */
1205
    /*      Process bands.                                                  */
1206
    /* -------------------------------------------------------------------- */
1207
    if (nCloneFlags & GCIF_PROCESS_BANDS)
7,208✔
1208
    {
1209
        for (int iBand = 0; iBand < GetRasterCount(); iBand++)
26,074✔
1210
        {
1211
            GDALRasterBand *poBand = GetRasterBand(iBand + 1);
18,866✔
1212

1213
            if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
18,866✔
1214
                continue;
×
1215

1216
            if (poSrcDS->GetRasterCount() >= iBand + 1)
18,866✔
1217
            {
1218
                cpl::down_cast<GDALPamRasterBand *>(poBand)->CloneInfo(
37,730✔
1219
                    poSrcDS->GetRasterBand(iBand + 1), nCloneFlags);
18,865✔
1220
            }
1221
            else
1222
                CPLDebug("GDALPamDataset",
1✔
1223
                         "Skipping CloneInfo for band not in source, "
1224
                         "this is a bit unusual!");
1225
        }
1226
    }
1227

1228
    /* -------------------------------------------------------------------- */
1229
    /*      Copy masks.  These are really copied at a lower level using     */
1230
    /*      GDALDefaultOverviews, for formats with no native mask           */
1231
    /*      support but this is a convenient central point to put this      */
1232
    /*      for most drivers.                                               */
1233
    /* -------------------------------------------------------------------- */
1234
    if (nCloneFlags & GCIF_MASK)
7,208✔
1235
    {
1236
        GDALDriver::DefaultCopyMasks(poSrcDS, this, FALSE);
5,149✔
1237
    }
1238

1239
    /* -------------------------------------------------------------------- */
1240
    /*      Restore MO flags.                                               */
1241
    /* -------------------------------------------------------------------- */
1242
    SetMOFlags(nSavedMOFlags);
7,208✔
1243

1244
    return CE_None;
7,208✔
1245
}
1246

1247
//! @endcond
1248

1249
/************************************************************************/
1250
/*                            GetFileList()                             */
1251
/*                                                                      */
1252
/*      Add .aux.xml or .aux file into file list as appropriate.        */
1253
/************************************************************************/
1254

1255
char **GDALPamDataset::GetFileList()
4,141✔
1256

1257
{
1258
    char **papszFileList = GDALDataset::GetFileList();
4,141✔
1259

1260
    if (psPam && !psPam->osPhysicalFilename.empty() &&
4,052✔
1261
        GDALCanReliablyUseSiblingFileList(psPam->osPhysicalFilename.c_str()) &&
8,533✔
1262
        CSLFindString(papszFileList, psPam->osPhysicalFilename) == -1)
340✔
1263
    {
1264
        papszFileList =
1265
            CSLInsertString(papszFileList, 0, psPam->osPhysicalFilename);
12✔
1266
    }
1267

1268
    if (psPam && psPam->pszPamFilename)
4,141✔
1269
    {
1270
        int bAddPamFile = nPamFlags & GPF_DIRTY;
4,026✔
1271
        if (!bAddPamFile)
4,026✔
1272
        {
1273
            VSIStatBufL sStatBuf;
1274
            if (oOvManager.GetSiblingFiles() != nullptr &&
4,026✔
1275
                IsPamFilenameAPotentialSiblingFile() &&
7,061✔
1276
                GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
3,035✔
1277
            {
1278
                bAddPamFile =
3,035✔
1279
                    CSLFindString(oOvManager.GetSiblingFiles(),
3,035✔
1280
                                  CPLGetFilename(psPam->pszPamFilename)) >= 0;
6,070✔
1281
            }
1282
            else
1283
            {
1284
                bAddPamFile = VSIStatExL(psPam->pszPamFilename, &sStatBuf,
991✔
1285
                                         VSI_STAT_EXISTS_FLAG) == 0;
991✔
1286
            }
1287
        }
1288
        if (bAddPamFile)
4,026✔
1289
        {
1290
            papszFileList = CSLAddString(papszFileList, psPam->pszPamFilename);
304✔
1291
        }
1292
    }
1293

1294
    if (psPam && !psPam->osAuxFilename.empty() &&
4,052✔
1295
        GDALCanReliablyUseSiblingFileList(psPam->osAuxFilename.c_str()) &&
8,194✔
1296
        CSLFindString(papszFileList, psPam->osAuxFilename) == -1)
1✔
1297
    {
1298
        papszFileList = CSLAddString(papszFileList, psPam->osAuxFilename);
1✔
1299
    }
1300
    return papszFileList;
4,141✔
1301
}
1302

1303
/************************************************************************/
1304
/*                          IBuildOverviews()                           */
1305
/************************************************************************/
1306

1307
//! @cond Doxygen_Suppress
1308
CPLErr GDALPamDataset::IBuildOverviews(
35✔
1309
    const char *pszResampling, int nOverviews, const int *panOverviewList,
1310
    int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
1311
    void *pProgressData, CSLConstList papszOptions)
1312

1313
{
1314
    /* -------------------------------------------------------------------- */
1315
    /*      Initialize PAM.                                                 */
1316
    /* -------------------------------------------------------------------- */
1317
    PamInitialize();
35✔
1318
    if (psPam == nullptr)
35✔
1319
        return GDALDataset::IBuildOverviews(
×
1320
            pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1321
            pfnProgress, pProgressData, papszOptions);
×
1322

1323
    /* -------------------------------------------------------------------- */
1324
    /*      If we appear to have subdatasets and to have a physical         */
1325
    /*      filename, use that physical filename to derive a name for a     */
1326
    /*      new overview file.                                              */
1327
    /* -------------------------------------------------------------------- */
1328
    if (oOvManager.IsInitialized() && psPam->osPhysicalFilename.length() != 0)
35✔
1329
    {
1330
        return oOvManager.BuildOverviewsSubDataset(
9✔
1331
            psPam->osPhysicalFilename, pszResampling, nOverviews,
9✔
1332
            panOverviewList, nListBands, panBandList, pfnProgress,
1333
            pProgressData, papszOptions);
9✔
1334
    }
1335

1336
    return GDALDataset::IBuildOverviews(
26✔
1337
        pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1338
        pfnProgress, pProgressData, papszOptions);
26✔
1339
}
1340

1341
//! @endcond
1342

1343
/************************************************************************/
1344
/*                           GetSpatialRef()                            */
1345
/************************************************************************/
1346

1347
const OGRSpatialReference *GDALPamDataset::GetSpatialRef() const
17,007✔
1348

1349
{
1350
    if (psPam && psPam->poSRS)
17,007✔
1351
        return psPam->poSRS;
92✔
1352

1353
    return GDALDataset::GetSpatialRef();
16,915✔
1354
}
1355

1356
/************************************************************************/
1357
/*                           SetSpatialRef()                            */
1358
/************************************************************************/
1359

1360
CPLErr GDALPamDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
418✔
1361

1362
{
1363
    PamInitialize();
418✔
1364

1365
    if (psPam == nullptr)
418✔
1366
        return GDALDataset::SetSpatialRef(poSRS);
×
1367

1368
    if (psPam->poSRS)
418✔
1369
        psPam->poSRS->Release();
6✔
1370
    psPam->poSRS = poSRS ? poSRS->Clone() : nullptr;
418✔
1371
    MarkPamDirty();
418✔
1372

1373
    return CE_None;
418✔
1374
}
1375

1376
/************************************************************************/
1377
/*                          GetGeoTransform()                           */
1378
/************************************************************************/
1379

1380
CPLErr GDALPamDataset::GetGeoTransform(GDALGeoTransform &gt) const
15,600✔
1381

1382
{
1383
    if (psPam && psPam->bHaveGeoTransform)
15,600✔
1384
    {
1385
        gt = psPam->gt;
188✔
1386
        return CE_None;
188✔
1387
    }
1388

1389
    return GDALDataset::GetGeoTransform(gt);
15,412✔
1390
}
1391

1392
/************************************************************************/
1393
/*                          SetGeoTransform()                           */
1394
/************************************************************************/
1395

1396
CPLErr GDALPamDataset::SetGeoTransform(const GDALGeoTransform &gt)
393✔
1397

1398
{
1399
    PamInitialize();
393✔
1400

1401
    if (psPam)
393✔
1402
    {
1403
        MarkPamDirty();
393✔
1404
        psPam->bHaveGeoTransform = true;
393✔
1405
        psPam->gt = gt;
393✔
1406
        return (CE_None);
393✔
1407
    }
1408

1409
    return GDALDataset::SetGeoTransform(gt);
×
1410
}
1411

1412
/************************************************************************/
1413
/*                        DeleteGeoTransform()                          */
1414
/************************************************************************/
1415

1416
/** Remove geotransform from PAM.
1417
 *
1418
 * @since GDAL 3.4.1
1419
 */
1420
void GDALPamDataset::DeleteGeoTransform()
1,670✔
1421

1422
{
1423
    PamInitialize();
1,670✔
1424

1425
    if (psPam && psPam->bHaveGeoTransform)
1,670✔
1426
    {
1427
        MarkPamDirty();
3✔
1428
        psPam->bHaveGeoTransform = FALSE;
3✔
1429
    }
1430
}
1,670✔
1431

1432
/************************************************************************/
1433
/*                            GetGCPCount()                             */
1434
/************************************************************************/
1435

1436
int GDALPamDataset::GetGCPCount()
15,890✔
1437

1438
{
1439
    if (psPam && !psPam->asGCPs.empty())
15,890✔
1440
        return static_cast<int>(psPam->asGCPs.size());
45✔
1441

1442
    return GDALDataset::GetGCPCount();
15,843✔
1443
}
1444

1445
/************************************************************************/
1446
/*                          GetGCPSpatialRef()                          */
1447
/************************************************************************/
1448

1449
const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const
63✔
1450

1451
{
1452
    if (psPam && psPam->poGCP_SRS != nullptr)
63✔
1453
        return psPam->poGCP_SRS;
24✔
1454

1455
    return GDALDataset::GetGCPSpatialRef();
39✔
1456
}
1457

1458
/************************************************************************/
1459
/*                               GetGCPs()                              */
1460
/************************************************************************/
1461

1462
const GDAL_GCP *GDALPamDataset::GetGCPs()
37✔
1463

1464
{
1465
    if (psPam && !psPam->asGCPs.empty())
37✔
1466
        return gdal::GCP::c_ptr(psPam->asGCPs);
27✔
1467

1468
    return GDALDataset::GetGCPs();
10✔
1469
}
1470

1471
/************************************************************************/
1472
/*                              SetGCPs()                               */
1473
/************************************************************************/
1474

1475
CPLErr GDALPamDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
32✔
1476
                               const OGRSpatialReference *poGCP_SRS)
1477

1478
{
1479
    PamInitialize();
32✔
1480

1481
    if (psPam)
32✔
1482
    {
1483
        if (psPam->poGCP_SRS)
32✔
1484
            psPam->poGCP_SRS->Release();
1✔
1485
        psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr;
32✔
1486
        psPam->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
32✔
1487
        MarkPamDirty();
32✔
1488

1489
        return CE_None;
32✔
1490
    }
1491

1492
    return GDALDataset::SetGCPs(nGCPCount, pasGCPList, poGCP_SRS);
×
1493
}
1494

1495
/************************************************************************/
1496
/*                            SetMetadata()                             */
1497
/************************************************************************/
1498

1499
CPLErr GDALPamDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
3,619✔
1500

1501
{
1502
    PamInitialize();
3,619✔
1503

1504
    if (psPam)
3,619✔
1505
    {
1506
        psPam->bHasMetadata = TRUE;
3,619✔
1507
        MarkPamDirty();
3,619✔
1508
    }
1509

1510
    return GDALDataset::SetMetadata(papszMetadata, pszDomain);
3,619✔
1511
}
1512

1513
/************************************************************************/
1514
/*                          SetMetadataItem()                           */
1515
/************************************************************************/
1516

1517
CPLErr GDALPamDataset::SetMetadataItem(const char *pszName,
38,544✔
1518
                                       const char *pszValue,
1519
                                       const char *pszDomain)
1520

1521
{
1522
    PamInitialize();
38,544✔
1523

1524
    if (psPam)
38,544✔
1525
    {
1526
        psPam->bHasMetadata = TRUE;
38,544✔
1527
        MarkPamDirty();
38,544✔
1528
    }
1529

1530
    return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
38,544✔
1531
}
1532

1533
/************************************************************************/
1534
/*                          GetMetadataItem()                           */
1535
/************************************************************************/
1536

1537
const char *GDALPamDataset::GetMetadataItem(const char *pszName,
19,754✔
1538
                                            const char *pszDomain)
1539

1540
{
1541
    /* -------------------------------------------------------------------- */
1542
    /*      A request against the ProxyOverviewRequest is a special         */
1543
    /*      mechanism to request an overview filename be allocated in       */
1544
    /*      the proxy pool location.  The allocated name is saved as        */
1545
    /*      metadata as well as being returned.                             */
1546
    /* -------------------------------------------------------------------- */
1547
    if (pszDomain != nullptr && EQUAL(pszDomain, "ProxyOverviewRequest"))
19,754✔
1548
    {
1549
        CPLString osPrelimOvr = GetDescription();
8✔
1550
        osPrelimOvr += ":::OVR";
4✔
1551

1552
        const char *pszProxyOvrFilename = PamAllocateProxy(osPrelimOvr);
4✔
1553
        if (pszProxyOvrFilename == nullptr)
4✔
1554
            return nullptr;
3✔
1555

1556
        SetMetadataItem("OVERVIEW_FILE", pszProxyOvrFilename, "OVERVIEWS");
1✔
1557

1558
        return pszProxyOvrFilename;
1✔
1559
    }
1560

1561
    /* -------------------------------------------------------------------- */
1562
    /*      If the OVERVIEW_FILE metadata is requested, we intercept the    */
1563
    /*      request in order to replace ":::BASE:::" with the path to       */
1564
    /*      the physical file - if available.  This is primarily for the    */
1565
    /*      purpose of managing subdataset overview filenames as being      */
1566
    /*      relative to the physical file the subdataset comes              */
1567
    /*      from. (#3287).                                                  */
1568
    /* -------------------------------------------------------------------- */
1569
    else if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") &&
19,750✔
1570
             EQUAL(pszName, "OVERVIEW_FILE"))
5,512✔
1571
    {
1572
        if (m_osOverviewFile.empty())
5,512✔
1573
        {
1574
            const char *pszOverviewFile =
1575
                GDALDataset::GetMetadataItem(pszName, pszDomain);
5,503✔
1576

1577
            if (pszOverviewFile == nullptr ||
5,503✔
1578
                !STARTS_WITH_CI(pszOverviewFile, ":::BASE:::"))
17✔
1579
                return pszOverviewFile;
5,487✔
1580

1581
            std::string osPath;
16✔
1582

1583
            if (strlen(GetPhysicalFilename()) > 0)
16✔
1584
                osPath = CPLGetPathSafe(GetPhysicalFilename());
16✔
1585
            else
1586
                osPath = CPLGetPathSafe(GetDescription());
×
1587

1588
            m_osOverviewFile = CPLFormFilenameSafe(
32✔
1589
                osPath.c_str(), pszOverviewFile + 10, nullptr);
16✔
1590
        }
1591
        return m_osOverviewFile.c_str();
25✔
1592
    }
1593

1594
    /* -------------------------------------------------------------------- */
1595
    /*      Everything else is a pass through.                              */
1596
    /* -------------------------------------------------------------------- */
1597

1598
    return GDALDataset::GetMetadataItem(pszName, pszDomain);
14,238✔
1599
}
1600

1601
/************************************************************************/
1602
/*                            GetMetadata()                             */
1603
/************************************************************************/
1604

1605
char **GDALPamDataset::GetMetadata(const char *pszDomain)
14,382✔
1606

1607
{
1608
    // if( pszDomain == nullptr || !EQUAL(pszDomain,"ProxyOverviewRequest") )
1609
    return GDALDataset::GetMetadata(pszDomain);
14,382✔
1610
}
1611

1612
/************************************************************************/
1613
/*                             TryLoadAux()                             */
1614
/************************************************************************/
1615

1616
//! @cond Doxygen_Suppress
1617
CPLErr GDALPamDataset::TryLoadAux(CSLConstList papszSiblingFiles)
34,568✔
1618

1619
{
1620
    /* -------------------------------------------------------------------- */
1621
    /*      Initialize PAM.                                                 */
1622
    /* -------------------------------------------------------------------- */
1623
    PamInitialize();
34,568✔
1624

1625
    if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
34,568✔
UNCOV
1626
        return CE_None;
×
1627

1628
    /* -------------------------------------------------------------------- */
1629
    /*      What is the name of the physical file we are referencing?       */
1630
    /*      We allow an override via the psPam->pszPhysicalFile item.       */
1631
    /* -------------------------------------------------------------------- */
1632
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
34,568✔
1633

1634
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
34,568✔
1635
        pszPhysicalFile = GetDescription();
33,171✔
1636

1637
    if (strlen(pszPhysicalFile) == 0)
34,567✔
1638
        return CE_None;
×
1639

1640
    if (papszSiblingFiles && GDALCanReliablyUseSiblingFileList(pszPhysicalFile))
34,567✔
1641
    {
1642
        CPLString osAuxFilename = CPLResetExtensionSafe(pszPhysicalFile, "aux");
27,289✔
1643
        int iSibling =
1644
            CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
27,290✔
1645
        if (iSibling < 0)
27,290✔
1646
        {
1647
            osAuxFilename = pszPhysicalFile;
27,284✔
1648
            osAuxFilename += ".aux";
27,282✔
1649
            iSibling =
1650
                CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
27,282✔
1651
            if (iSibling < 0)
27,284✔
1652
                return CE_None;
27,284✔
1653
        }
1654
    }
1655

1656
    /* -------------------------------------------------------------------- */
1657
    /*      Try to open .aux file.                                          */
1658
    /* -------------------------------------------------------------------- */
1659
    GDALDataset *poAuxDS =
1660
        GDALFindAssociatedAuxFile(pszPhysicalFile, GA_ReadOnly, this);
7,284✔
1661

1662
    if (poAuxDS == nullptr)
7,284✔
1663
        return CE_None;
7,276✔
1664

1665
    psPam->osAuxFilename = poAuxDS->GetDescription();
8✔
1666

1667
    /* -------------------------------------------------------------------- */
1668
    /*      Do we have an SRS on the aux file?                              */
1669
    /* -------------------------------------------------------------------- */
1670
    if (strlen(poAuxDS->GetProjectionRef()) > 0)
8✔
1671
        GDALPamDataset::SetProjection(poAuxDS->GetProjectionRef());
2✔
1672

1673
    /* -------------------------------------------------------------------- */
1674
    /*      Geotransform.                                                   */
1675
    /* -------------------------------------------------------------------- */
1676
    if (poAuxDS->GetGeoTransform(psPam->gt) == CE_None)
8✔
1677
        psPam->bHaveGeoTransform = TRUE;
2✔
1678

1679
    /* -------------------------------------------------------------------- */
1680
    /*      GCPs                                                            */
1681
    /* -------------------------------------------------------------------- */
1682
    if (poAuxDS->GetGCPCount() > 0)
8✔
1683
    {
1684
        psPam->asGCPs =
×
1685
            gdal::GCP::fromC(poAuxDS->GetGCPs(), poAuxDS->GetGCPCount());
×
1686
    }
1687

1688
    /* -------------------------------------------------------------------- */
1689
    /*      Apply metadata. We likely ought to be merging this in rather    */
1690
    /*      than overwriting everything that was there.                     */
1691
    /* -------------------------------------------------------------------- */
1692
    char **papszMD = poAuxDS->GetMetadata();
8✔
1693
    if (CSLCount(papszMD) > 0)
8✔
1694
    {
1695
        char **papszMerged = CSLMerge(CSLDuplicate(GetMetadata()), papszMD);
×
1696
        GDALPamDataset::SetMetadata(papszMerged);
×
1697
        CSLDestroy(papszMerged);
×
1698
    }
1699

1700
    papszMD = poAuxDS->GetMetadata("XFORMS");
8✔
1701
    if (CSLCount(papszMD) > 0)
8✔
1702
    {
1703
        char **papszMerged =
1704
            CSLMerge(CSLDuplicate(GetMetadata("XFORMS")), papszMD);
×
1705
        GDALPamDataset::SetMetadata(papszMerged, "XFORMS");
×
1706
        CSLDestroy(papszMerged);
×
1707
    }
1708

1709
    /* ==================================================================== */
1710
    /*      Process bands.                                                  */
1711
    /* ==================================================================== */
1712
    for (int iBand = 0; iBand < poAuxDS->GetRasterCount(); iBand++)
20✔
1713
    {
1714
        if (iBand >= GetRasterCount())
12✔
1715
            break;
×
1716

1717
        GDALRasterBand *const poAuxBand = poAuxDS->GetRasterBand(iBand + 1);
12✔
1718
        GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
12✔
1719

1720
        papszMD = poAuxBand->GetMetadata();
12✔
1721
        if (CSLCount(papszMD) > 0)
12✔
1722
        {
1723
            char **papszMerged =
1724
                CSLMerge(CSLDuplicate(poBand->GetMetadata()), papszMD);
12✔
1725
            poBand->SetMetadata(papszMerged);
12✔
1726
            CSLDestroy(papszMerged);
12✔
1727
        }
1728

1729
        if (strlen(poAuxBand->GetDescription()) > 0)
12✔
1730
            poBand->SetDescription(poAuxBand->GetDescription());
12✔
1731

1732
        if (poAuxBand->GetCategoryNames() != nullptr)
12✔
1733
            poBand->SetCategoryNames(poAuxBand->GetCategoryNames());
×
1734

1735
        if (poAuxBand->GetColorTable() != nullptr &&
12✔
1736
            poBand->GetColorTable() == nullptr)
×
1737
            poBand->SetColorTable(poAuxBand->GetColorTable());
×
1738

1739
        // histograms?
1740
        double dfMin = 0.0;
12✔
1741
        double dfMax = 0.0;
12✔
1742
        int nBuckets = 0;
12✔
1743
        GUIntBig *panHistogram = nullptr;
12✔
1744

1745
        if (poAuxBand->GetDefaultHistogram(&dfMin, &dfMax, &nBuckets,
24✔
1746
                                           &panHistogram, FALSE, nullptr,
1747
                                           nullptr) == CE_None)
12✔
1748
        {
1749
            poBand->SetDefaultHistogram(dfMin, dfMax, nBuckets, panHistogram);
8✔
1750
            CPLFree(panHistogram);
8✔
1751
        }
1752

1753
        // RAT
1754
        if (poAuxBand->GetDefaultRAT() != nullptr)
12✔
1755
            poBand->SetDefaultRAT(poAuxBand->GetDefaultRAT());
12✔
1756

1757
        // NoData
1758
        int bSuccess = FALSE;
12✔
1759
        const double dfNoDataValue = poAuxBand->GetNoDataValue(&bSuccess);
12✔
1760
        if (bSuccess)
12✔
1761
            poBand->SetNoDataValue(dfNoDataValue);
2✔
1762
    }
1763

1764
    GDALClose(poAuxDS);
8✔
1765

1766
    /* -------------------------------------------------------------------- */
1767
    /*      Mark PAM info as clean.                                         */
1768
    /* -------------------------------------------------------------------- */
1769
    nPamFlags &= ~GPF_DIRTY;
8✔
1770

1771
    return CE_Failure;
8✔
1772
}
1773

1774
//! @endcond
1775

1776
/************************************************************************/
1777
/*                          ClearStatistics()                           */
1778
/************************************************************************/
1779

1780
void GDALPamDataset::ClearStatistics()
5✔
1781
{
1782
    PamInitialize();
5✔
1783
    if (!psPam)
5✔
1784
        return;
×
1785
    for (int i = 1; i <= nBands; ++i)
12✔
1786
    {
1787
        bool bChanged = false;
7✔
1788
        GDALRasterBand *poBand = GetRasterBand(i);
7✔
1789
        CPLStringList aosNewMD;
14✔
1790
        for (const char *pszStr :
5✔
1791
             cpl::Iterate(static_cast<CSLConstList>(poBand->GetMetadata())))
17✔
1792
        {
1793
            if (STARTS_WITH_CI(pszStr, "STATISTICS_"))
5✔
1794
            {
1795
                MarkPamDirty();
5✔
1796
                bChanged = true;
5✔
1797
            }
1798
            else
1799
            {
1800
                aosNewMD.AddString(pszStr);
×
1801
            }
1802
        }
1803
        if (bChanged)
7✔
1804
        {
1805
            poBand->SetMetadata(aosNewMD.List());
1✔
1806
        }
1807
    }
1808

1809
    GDALDataset::ClearStatistics();
5✔
1810
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc