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

OSGeo / gdal / 13836648005

13 Mar 2025 02:09PM UTC coverage: 70.436% (-0.01%) from 70.446%
13836648005

push

github

web-flow
New Transform type: Homography (#11949)

Add new transform type, Homography.
Add functions to compute homography from a list of GCPs.
Add functions to serialize and deserialize a homography
Automatically select homography transfrom when there are 4 or 5 GCPs present.

Fixes #11940

231 of 274 new or added lines in 2 files covered. (84.31%)

16257 existing lines in 42 files now uncovered.

553736 of 786159 relevant lines covered (70.44%)

221595.72 hits per line

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

94.09
/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()
64,948✔
140
{
141
    SetMOFlags(GetMOFlags() | GMO_PAM_CLASS);
64,893✔
142
}
64,902✔
143

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

148
GDALPamDataset::~GDALPamDataset()
64,917✔
149

150
{
151
    if (IsMarkedSuppressOnClose())
64,942✔
152
    {
153
        if (psPam && psPam->pszPamFilename != nullptr)
382✔
154
            VSIUnlink(psPam->pszPamFilename);
10✔
155
    }
156
    else if (nPamFlags & GPF_DIRTY)
64,538✔
157
    {
158
        CPLDebug("GDALPamDataset", "In destructor with dirty metadata.");
527✔
159
        GDALPamDataset::TrySaveXML();
527✔
160
    }
161

162
    PamClear();
64,920✔
163
}
64,951✔
164

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

169
CPLErr GDALPamDataset::FlushCache(bool bAtClosing)
66,325✔
170

171
{
172
    CPLErr eErr = GDALDataset::FlushCache(bAtClosing);
66,325✔
173
    if (nPamFlags & GPF_DIRTY)
66,326✔
174
    {
175
        if (TrySaveXML() != CE_None)
5,909✔
176
            eErr = CE_Failure;
26✔
177
    }
178
    return eErr;
66,326✔
179
}
180

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

185
//! @cond Doxygen_Suppress
186
void GDALPamDataset::MarkPamDirty()
58,755✔
187
{
188
    if ((nPamFlags & GPF_DIRTY) == 0 &&
80,041✔
189
        CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLE_MARK_DIRTY", "YES")))
21,286✔
190
    {
191
        nPamFlags |= GPF_DIRTY;
21,266✔
192
    }
193
}
58,755✔
194

195
// @endcond
196

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

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

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

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

213
    /* -------------------------------------------------------------------- */
214
    /*      SRS                                                             */
215
    /* -------------------------------------------------------------------- */
216
    if (psPam->poSRS && !psPam->poSRS->IsEmpty())
1,456✔
217
    {
218
        char *pszWKT = nullptr;
289✔
219
        {
220
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
578✔
221
            if (psPam->poSRS->exportToWkt(&pszWKT) != OGRERR_NONE)
289✔
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);
289✔
231
        CPLFree(pszWKT);
289✔
232
        const auto &mapping = psPam->poSRS->GetDataAxisToSRSAxisMapping();
289✔
233
        CPLString osMapping;
578✔
234
        for (size_t i = 0; i < mapping.size(); ++i)
868✔
235
        {
236
            if (!osMapping.empty())
579✔
237
                osMapping += ",";
290✔
238
            osMapping += CPLSPrintf("%d", mapping[i]);
579✔
239
        }
240
        CPLAddXMLAttributeAndValue(psSRSNode, "dataAxisToSRSAxisMapping",
289✔
241
                                   osMapping.c_str());
242

243
        const double dfCoordinateEpoch = psPam->poSRS->GetCoordinateEpoch();
289✔
244
        if (dfCoordinateEpoch > 0)
289✔
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,456✔
261
    {
262
        CPLString oFmt;
678✔
263
        oFmt.Printf("%24.16e,%24.16e,%24.16e,%24.16e,%24.16e,%24.16e",
264
                    psPam->adfGeoTransform[0], psPam->adfGeoTransform[1],
678✔
265
                    psPam->adfGeoTransform[2], psPam->adfGeoTransform[3],
678✔
266
                    psPam->adfGeoTransform[4], psPam->adfGeoTransform[5]);
339✔
267
        CPLSetXMLValue(psDSTree, "GeoTransform", oFmt);
339✔
268
    }
269

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

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

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

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

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

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

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

312
        if (psBandTree != nullptr)
2,235✔
313
        {
314
            if (psLastChild == nullptr)
668✔
315
            {
316
                CPLAddXMLChild(psDSTree, psBandTree);
190✔
317
            }
318
            else
319
            {
320
                psLastChild->psNext = psBandTree;
478✔
321
            }
322
            psLastChild = psBandTree;
668✔
323
        }
324
    }
325

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

336
    return psDSTree;
1,456✔
337
}
338

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

343
void GDALPamDataset::PamInitialize()
600,376✔
344

345
{
346
#ifdef PAM_ENABLED
347
    const char *const pszPamDefault = "YES";
600,376✔
348
#else
349
    const char *const pszPamDefault = "NO";
350
#endif
351

352
    if (psPam)
600,376✔
353
        return;
557,009✔
354

355
    if (!CPLTestBool(CPLGetConfigOption("GDAL_PAM_ENABLED", pszPamDefault)))
43,367✔
356
    {
357
        CPLDebug("GDAL", "PAM is disabled");
14✔
358
        nPamFlags |= GPF_DISABLED;
14✔
359
    }
360

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

365
    psPam = new GDALDatasetPamInfo;
43,369✔
366
    for (int iBand = 0; iBand < GetRasterCount(); iBand++)
517,971✔
367
    {
368
        GDALRasterBand *poBand = GetRasterBand(iBand + 1);
474,601✔
369

370
        if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
474,603✔
371
            continue;
136✔
372

373
        cpl::down_cast<GDALPamRasterBand *>(poBand)->PamInitialize();
474,466✔
374
    }
375
}
376

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

381
void GDALPamDataset::PamClear()
64,937✔
382

383
{
384
    if (psPam)
64,937✔
385
    {
386
        CPLFree(psPam->pszPamFilename);
43,367✔
387
        if (psPam->poSRS)
43,367✔
388
            psPam->poSRS->Release();
580✔
389
        if (psPam->poGCP_SRS)
43,367✔
390
            psPam->poGCP_SRS->Release();
37✔
391

392
        delete psPam;
43,367✔
393
        psPam = nullptr;
43,366✔
394
    }
395
}
64,936✔
396

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

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

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

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

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

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

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

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

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

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

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

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

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

650
                    psPam->bHaveGeoTransform = TRUE;
1✔
651
                }
652
            }
653
        }
654
    }
655

656
    /* -------------------------------------------------------------------- */
657
    /*      Process bands.                                                  */
658
    /* -------------------------------------------------------------------- */
659
    for (const CPLXMLNode *psBandTree = psTree->psChild; psBandTree;
4,187✔
660
         psBandTree = psBandTree->psNext)
2,798✔
661
    {
662
        if (psBandTree->eType != CXT_Element ||
2,798✔
663
            !EQUAL(psBandTree->pszValue, "PAMRasterBand"))
2,794✔
664
            continue;
2,116✔
665

666
        const int nBand = atoi(CPLGetXMLValue(psBandTree, "band", "0"));
682✔
667

668
        if (nBand < 1 || nBand > GetRasterCount())
682✔
669
            continue;
41✔
670

671
        GDALRasterBand *poBand = GetRasterBand(nBand);
641✔
672

673
        if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
641✔
674
            continue;
×
675

676
        GDALPamRasterBand *poPamBand =
677
            cpl::down_cast<GDALPamRasterBand *>(GetRasterBand(nBand));
641✔
678

679
        poPamBand->XMLInit(psBandTree, pszUnused);
641✔
680
    }
681

682
    /* -------------------------------------------------------------------- */
683
    /*      Preserve Array information.                                     */
684
    /* -------------------------------------------------------------------- */
685
    for (const CPLXMLNode *psIter = psTree->psChild; psIter;
4,187✔
686
         psIter = psIter->psNext)
2,798✔
687
    {
688
        if (psIter->eType == CXT_Element &&
5,592✔
689
            (strcmp(psIter->pszValue, "Array") == 0 ||
5,576✔
690
             (psPam->osDerivedDatasetName.empty() &&
2,782✔
691
              strcmp(psIter->pszValue, "DerivedDataset") == 0)))
2,772✔
692
        {
693
            CPLXMLNode sArrayTmp = *psIter;
29✔
694
            sArrayTmp.psNext = nullptr;
29✔
695
            psPam->m_apoOtherNodes.emplace_back(
29✔
696
                CPLXMLTreeCloser(CPLCloneXMLTree(&sArrayTmp)));
29✔
697
        }
698
    }
699

700
    /* -------------------------------------------------------------------- */
701
    /*      Clear dirty flag.                                               */
702
    /* -------------------------------------------------------------------- */
703
    nPamFlags &= ~GPF_DIRTY;
1,389✔
704

705
    return CE_None;
1,389✔
706
}
707

708
/************************************************************************/
709
/*                        SetPhysicalFilename()                         */
710
/************************************************************************/
711

712
void GDALPamDataset::SetPhysicalFilename(const char *pszFilename)
3,962✔
713

714
{
715
    PamInitialize();
3,962✔
716

717
    if (psPam)
3,962✔
718
        psPam->osPhysicalFilename = pszFilename;
3,962✔
719
}
3,962✔
720

721
/************************************************************************/
722
/*                        GetPhysicalFilename()                         */
723
/************************************************************************/
724

725
const char *GDALPamDataset::GetPhysicalFilename()
142✔
726

727
{
728
    PamInitialize();
142✔
729

730
    if (psPam)
142✔
731
        return psPam->osPhysicalFilename;
142✔
732

733
    return "";
×
734
}
735

736
/************************************************************************/
737
/*                         SetSubdatasetName()                          */
738
/************************************************************************/
739

740
/* Mutually exclusive with SetDerivedDatasetName() */
741
void GDALPamDataset::SetSubdatasetName(const char *pszSubdataset)
703✔
742

743
{
744
    PamInitialize();
703✔
745

746
    if (psPam)
703✔
747
        psPam->osSubdatasetName = pszSubdataset;
703✔
748
}
703✔
749

750
/************************************************************************/
751
/*                        SetDerivedDatasetName()                        */
752
/************************************************************************/
753

754
/* Mutually exclusive with SetSubdatasetName() */
755
void GDALPamDataset::SetDerivedDatasetName(const char *pszDerivedDataset)
170✔
756

757
{
758
    PamInitialize();
170✔
759

760
    if (psPam)
170✔
761
        psPam->osDerivedDatasetName = pszDerivedDataset;
170✔
762
}
170✔
763

764
/************************************************************************/
765
/*                         GetSubdatasetName()                          */
766
/************************************************************************/
767

768
const char *GDALPamDataset::GetSubdatasetName()
3✔
769

770
{
771
    PamInitialize();
3✔
772

773
    if (psPam)
3✔
774
        return psPam->osSubdatasetName;
3✔
775

776
    return "";
×
777
}
778

779
/************************************************************************/
780
/*                          BuildPamFilename()                          */
781
/************************************************************************/
782

783
const char *GDALPamDataset::BuildPamFilename()
35,246✔
784

785
{
786
    if (psPam == nullptr)
35,246✔
787
        return nullptr;
×
788

789
    /* -------------------------------------------------------------------- */
790
    /*      What is the name of the physical file we are referencing?       */
791
    /*      We allow an override via the psPam->pszPhysicalFile item.       */
792
    /* -------------------------------------------------------------------- */
793
    if (psPam->pszPamFilename != nullptr)
35,246✔
794
        return psPam->pszPamFilename;
2,363✔
795

796
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
32,883✔
797

798
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
32,883✔
799
        pszPhysicalFile = GetDescription();
30,863✔
800

801
    if (strlen(pszPhysicalFile) == 0)
32,883✔
802
        return nullptr;
229✔
803

804
    /* -------------------------------------------------------------------- */
805
    /*      Try a proxy lookup, otherwise just add .aux.xml.                */
806
    /* -------------------------------------------------------------------- */
807
    const char *pszProxyPam = PamGetProxy(pszPhysicalFile);
32,654✔
808
    if (pszProxyPam != nullptr)
32,652✔
809
        psPam->pszPamFilename = CPLStrdup(pszProxyPam);
4✔
810
    else
811
    {
812
        if (!GDALCanFileAcceptSidecarFile(pszPhysicalFile))
32,648✔
813
            return nullptr;
107✔
814
        psPam->pszPamFilename =
65,085✔
815
            static_cast<char *>(CPLMalloc(strlen(pszPhysicalFile) + 10));
32,543✔
816
        strcpy(psPam->pszPamFilename, pszPhysicalFile);
32,542✔
817
        strcat(psPam->pszPamFilename, ".aux.xml");
32,542✔
818
    }
819

820
    return psPam->pszPamFilename;
32,546✔
821
}
822

823
/************************************************************************/
824
/*                   IsPamFilenameAPotentialSiblingFile()               */
825
/************************************************************************/
826

827
int GDALPamDataset::IsPamFilenameAPotentialSiblingFile()
27,695✔
828
{
829
    if (psPam == nullptr)
27,695✔
830
        return FALSE;
×
831

832
    /* -------------------------------------------------------------------- */
833
    /*      Determine if the PAM filename is a .aux.xml file next to the    */
834
    /*      physical file, or if it comes from the ProxyDB                  */
835
    /* -------------------------------------------------------------------- */
836
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
27,695✔
837

838
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
27,695✔
839
        pszPhysicalFile = GetDescription();
26,771✔
840

841
    size_t nLenPhysicalFile = strlen(pszPhysicalFile);
27,694✔
842
    int bIsSiblingPamFile =
27,694✔
843
        strncmp(psPam->pszPamFilename, pszPhysicalFile, nLenPhysicalFile) ==
27,694✔
844
            0 &&
55,381✔
845
        strcmp(psPam->pszPamFilename + nLenPhysicalFile, ".aux.xml") == 0;
27,687✔
846

847
    return bIsSiblingPamFile;
27,694✔
848
}
849

850
/************************************************************************/
851
/*                             TryLoadXML()                             */
852
/************************************************************************/
853

854
CPLErr GDALPamDataset::TryLoadXML(CSLConstList papszSiblingFiles)
32,793✔
855

856
{
857
    PamInitialize();
32,793✔
858

859
    if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
32,794✔
860
        return CE_None;
62✔
861

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

872
    /* -------------------------------------------------------------------- */
873
    /*      Try reading the file.                                           */
874
    /* -------------------------------------------------------------------- */
875
    if (!BuildPamFilename())
32,732✔
876
        return CE_None;
245✔
877

878
    /* -------------------------------------------------------------------- */
879
    /*      In case the PAM filename is a .aux.xml file next to the         */
880
    /*      physical file and we have a siblings list, then we can skip     */
881
    /*      stat'ing the filesystem.                                        */
882
    /* -------------------------------------------------------------------- */
883
    VSIStatBufL sStatBuf;
884
    CPLXMLNode *psTree = nullptr;
32,486✔
885

886
    if (papszSiblingFiles != nullptr && IsPamFilenameAPotentialSiblingFile() &&
57,155✔
887
        GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
24,669✔
888
    {
889
        const int iSibling = CSLFindString(
24,670✔
890
            papszSiblingFiles, CPLGetFilename(psPam->pszPamFilename));
24,669✔
891
        if (iSibling >= 0)
24,670✔
892
        {
893
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
439✔
894
            psTree = CPLParseXMLFile(psPam->pszPamFilename);
439✔
895
        }
896
    }
897
    else if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
7,818✔
898
                        VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
8,648✔
899
             VSI_ISREG(sStatBuf.st_mode))
830✔
900
    {
901
        CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
830✔
902
        psTree = CPLParseXMLFile(psPam->pszPamFilename);
830✔
903
    }
904

905
    /* -------------------------------------------------------------------- */
906
    /*      If we are looking for a subdataset, search for its subtree now. */
907
    /* -------------------------------------------------------------------- */
908
    if (psTree)
32,488✔
909
    {
910
        std::string osSubNode;
2,520✔
911
        std::string osSubNodeValue;
2,520✔
912
        if (!psPam->osSubdatasetName.empty())
1,260✔
913
        {
914
            osSubNode = "Subdataset";
161✔
915
            osSubNodeValue = psPam->osSubdatasetName;
161✔
916
        }
917
        else if (!psPam->osDerivedDatasetName.empty())
1,099✔
918
        {
919
            osSubNode = "DerivedDataset";
15✔
920
            osSubNodeValue = psPam->osDerivedDatasetName;
15✔
921
        }
922
        if (!osSubNode.empty())
1,260✔
923
        {
924
            CPLXMLNode *psSubTree = psTree->psChild;
176✔
925

926
            for (; psSubTree != nullptr; psSubTree = psSubTree->psNext)
318✔
927
            {
928
                if (psSubTree->eType != CXT_Element ||
382✔
929
                    !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
191✔
930
                    continue;
123✔
931

932
                if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
68✔
933
                           osSubNodeValue.c_str()))
934
                    continue;
19✔
935

936
                psSubTree = CPLGetXMLNode(psSubTree, "PAMDataset");
49✔
937
                break;
49✔
938
            }
939

940
            if (psSubTree != nullptr)
176✔
941
                psSubTree = CPLCloneXMLTree(psSubTree);
49✔
942

943
            CPLDestroyXMLNode(psTree);
176✔
944
            psTree = psSubTree;
176✔
945
        }
946
    }
947

948
    /* -------------------------------------------------------------------- */
949
    /*      If we fail, try .aux.                                           */
950
    /* -------------------------------------------------------------------- */
951
    if (psTree == nullptr)
32,488✔
952
        return TryLoadAux(papszSiblingFiles);
31,355✔
953

954
    /* -------------------------------------------------------------------- */
955
    /*      Initialize ourselves from this XML tree.                        */
956
    /* -------------------------------------------------------------------- */
957

958
    CPLString osVRTPath(CPLGetPathSafe(psPam->pszPamFilename));
1,133✔
959
    const CPLErr eErr = XMLInit(psTree, osVRTPath);
1,133✔
960

961
    CPLDestroyXMLNode(psTree);
1,133✔
962

963
    if (eErr != CE_None)
1,133✔
964
        PamClear();
×
965

966
    return eErr;
1,133✔
967
}
968

969
/************************************************************************/
970
/*                             TrySaveXML()                             */
971
/************************************************************************/
972

973
CPLErr GDALPamDataset::TrySaveXML()
6,482✔
974

975
{
976
    nPamFlags &= ~GPF_DIRTY;
6,482✔
977

978
    if (psPam == nullptr || (nPamFlags & GPF_NOSAVE) != 0 ||
6,482✔
979
        (nPamFlags & GPF_DISABLED) != 0)
1,427✔
980
        return CE_None;
5,070✔
981

982
    /* -------------------------------------------------------------------- */
983
    /*      Make sure we know the filename we want to store in.             */
984
    /* -------------------------------------------------------------------- */
985
    if (!BuildPamFilename())
1,412✔
986
        return CE_None;
91✔
987

988
    /* -------------------------------------------------------------------- */
989
    /*      Build the XML representation of the auxiliary metadata.          */
990
    /* -------------------------------------------------------------------- */
991
    CPLXMLNode *psTree = SerializeToXML(nullptr);
1,321✔
992

993
    if (psTree == nullptr)
1,321✔
994
    {
995
        /* If we have unset all metadata, we have to delete the PAM file */
996
        CPLPushErrorHandler(CPLQuietErrorHandler);
214✔
997
        VSIUnlink(psPam->pszPamFilename);
214✔
998
        CPLPopErrorHandler();
214✔
999
        return CE_None;
214✔
1000
    }
1001

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

1023
        VSIStatBufL sStatBuf;
1024
        if (VSIStatExL(psPam->pszPamFilename, &sStatBuf,
38✔
1025
                       VSI_STAT_EXISTS_FLAG | VSI_STAT_NATURE_FLAG) == 0 &&
42✔
1026
            VSI_ISREG(sStatBuf.st_mode))
4✔
1027
        {
1028
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4✔
1029
            psOldTree = CPLParseXMLFile(psPam->pszPamFilename);
4✔
1030
        }
1031

1032
        if (psOldTree == nullptr)
38✔
1033
            psOldTree = CPLCreateXMLNode(nullptr, CXT_Element, "PAMDataset");
34✔
1034

1035
        CPLXMLNode *psSubTree = psOldTree->psChild;
38✔
1036
        for (/* initialized above */; psSubTree != nullptr;
43✔
1037
             psSubTree = psSubTree->psNext)
5✔
1038
        {
1039
            if (psSubTree->eType != CXT_Element ||
10✔
1040
                !EQUAL(psSubTree->pszValue, osSubNode.c_str()))
5✔
1041
                continue;
1✔
1042

1043
            if (!EQUAL(CPLGetXMLValue(psSubTree, "name", ""),
4✔
1044
                       osSubNodeValue.c_str()))
1045
                continue;
4✔
1046

1047
            break;
×
1048
        }
1049

1050
        if (psSubTree == nullptr)
38✔
1051
        {
1052
            psSubTree =
1053
                CPLCreateXMLNode(psOldTree, CXT_Element, osSubNode.c_str());
38✔
1054
            CPLCreateXMLNode(CPLCreateXMLNode(psSubTree, CXT_Attribute, "name"),
38✔
1055
                             CXT_Text, osSubNodeValue.c_str());
1056
        }
1057

1058
        CPLXMLNode *psOldPamDataset = CPLGetXMLNode(psSubTree, "PAMDataset");
38✔
1059
        if (psOldPamDataset != nullptr)
38✔
1060
        {
1061
            CPLRemoveXMLChild(psSubTree, psOldPamDataset);
×
1062
            CPLDestroyXMLNode(psOldPamDataset);
×
1063
        }
1064

1065
        CPLAddXMLChild(psSubTree, psTree);
38✔
1066
        psTree = psOldTree;
38✔
1067
    }
1068

1069
    /* -------------------------------------------------------------------- */
1070
    /*      Preserve other information.                                     */
1071
    /* -------------------------------------------------------------------- */
1072
    for (const auto &poOtherNode : psPam->m_apoOtherNodes)
1,112✔
1073
    {
1074
        CPLAddXMLChild(psTree, CPLCloneXMLTree(poOtherNode.get()));
5✔
1075
    }
1076

1077
    /* -------------------------------------------------------------------- */
1078
    /*      Try saving the auxiliary metadata.                               */
1079
    /* -------------------------------------------------------------------- */
1080

1081
    CPLPushErrorHandler(CPLQuietErrorHandler);
1,107✔
1082
    const int bSaved = CPLSerializeXMLTreeToFile(psTree, psPam->pszPamFilename);
1,107✔
1083
    CPLPopErrorHandler();
1,107✔
1084

1085
    /* -------------------------------------------------------------------- */
1086
    /*      If it fails, check if we have a proxy directory for auxiliary    */
1087
    /*      metadata to be stored in, and try to save there.                */
1088
    /* -------------------------------------------------------------------- */
1089
    CPLErr eErr = CE_None;
1,107✔
1090

1091
    if (bSaved)
1,107✔
1092
        eErr = CE_None;
1,079✔
1093
    else
1094
    {
1095
        const char *pszBasename = GetDescription();
28✔
1096

1097
        if (psPam->osPhysicalFilename.length() > 0)
28✔
1098
            pszBasename = psPam->osPhysicalFilename;
10✔
1099

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

1119
    /* -------------------------------------------------------------------- */
1120
    /*      Cleanup                                                         */
1121
    /* -------------------------------------------------------------------- */
1122
    CPLDestroyXMLNode(psTree);
1,107✔
1123

1124
    return eErr;
1,107✔
1125
}
1126

1127
/************************************************************************/
1128
/*                             CloneInfo()                              */
1129
/************************************************************************/
1130

1131
CPLErr GDALPamDataset::CloneInfo(GDALDataset *poSrcDS, int nCloneFlags)
7,071✔
1132

1133
{
1134
    const int bOnlyIfMissing = nCloneFlags & GCIF_ONLY_IF_MISSING;
7,071✔
1135
    const int nSavedMOFlags = GetMOFlags();
7,071✔
1136

1137
    PamInitialize();
7,071✔
1138

1139
    /* -------------------------------------------------------------------- */
1140
    /*      Suppress NotImplemented error messages - mainly needed if PAM   */
1141
    /*      disabled.                                                       */
1142
    /* -------------------------------------------------------------------- */
1143
    SetMOFlags(nSavedMOFlags | GMO_IGNORE_UNIMPLEMENTED);
7,071✔
1144

1145
    /* -------------------------------------------------------------------- */
1146
    /*      GeoTransform                                                    */
1147
    /* -------------------------------------------------------------------- */
1148
    if (nCloneFlags & GCIF_GEOTRANSFORM)
7,071✔
1149
    {
1150
        double adfGeoTransform[6] = {0.0};
7,069✔
1151

1152
        if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None)
7,069✔
1153
        {
1154
            double adfOldGT[6] = {0.0};
2,166✔
1155

1156
            if (!bOnlyIfMissing || GetGeoTransform(adfOldGT) != CE_None)
2,166✔
1157
                SetGeoTransform(adfGeoTransform);
205✔
1158
        }
1159
    }
1160

1161
    /* -------------------------------------------------------------------- */
1162
    /*      Projection                                                      */
1163
    /* -------------------------------------------------------------------- */
1164
    if (nCloneFlags & GCIF_PROJECTION)
7,071✔
1165
    {
1166
        const auto poSRS = poSrcDS->GetSpatialRef();
7,069✔
1167

1168
        if (poSRS != nullptr)
7,069✔
1169
        {
1170
            if (!bOnlyIfMissing || GetSpatialRef() == nullptr)
1,923✔
1171
                SetSpatialRef(poSRS);
125✔
1172
        }
1173
    }
1174

1175
    /* -------------------------------------------------------------------- */
1176
    /*      GCPs                                                            */
1177
    /* -------------------------------------------------------------------- */
1178
    if (nCloneFlags & GCIF_GCPS)
7,071✔
1179
    {
1180
        if (poSrcDS->GetGCPCount() > 0)
7,071✔
1181
        {
1182
            if (!bOnlyIfMissing || GetGCPCount() == 0)
19✔
1183
            {
1184
                SetGCPs(poSrcDS->GetGCPCount(), poSrcDS->GetGCPs(),
2✔
1185
                        poSrcDS->GetGCPSpatialRef());
2✔
1186
            }
1187
        }
1188
    }
1189

1190
    /* -------------------------------------------------------------------- */
1191
    /*      Metadata                                                        */
1192
    /* -------------------------------------------------------------------- */
1193
    if (nCloneFlags & GCIF_METADATA)
7,071✔
1194
    {
1195
        for (const char *pszMDD : {"", "RPC", "json:ISIS3", "json:VICAR"})
12,305✔
1196
        {
1197
            auto papszSrcMD = poSrcDS->GetMetadata(pszMDD);
9,844✔
1198
            if (papszSrcMD != nullptr)
9,844✔
1199
            {
1200
                if (!bOnlyIfMissing ||
3,728✔
1201
                    CSLCount(GetMetadata(pszMDD)) != CSLCount(papszSrcMD))
1,864✔
1202
                {
1203
                    SetMetadata(papszSrcMD, pszMDD);
384✔
1204
                }
1205
            }
1206
        }
1207
    }
1208

1209
    /* -------------------------------------------------------------------- */
1210
    /*      Process bands.                                                  */
1211
    /* -------------------------------------------------------------------- */
1212
    if (nCloneFlags & GCIF_PROCESS_BANDS)
7,071✔
1213
    {
1214
        for (int iBand = 0; iBand < GetRasterCount(); iBand++)
25,745✔
1215
        {
1216
            GDALRasterBand *poBand = GetRasterBand(iBand + 1);
18,674✔
1217

1218
            if (poBand == nullptr || !(poBand->GetMOFlags() & GMO_PAM_CLASS))
18,674✔
1219
                continue;
×
1220

1221
            if (poSrcDS->GetRasterCount() >= iBand + 1)
18,674✔
1222
            {
1223
                cpl::down_cast<GDALPamRasterBand *>(poBand)->CloneInfo(
37,346✔
1224
                    poSrcDS->GetRasterBand(iBand + 1), nCloneFlags);
18,673✔
1225
            }
1226
            else
1227
                CPLDebug("GDALPamDataset",
1✔
1228
                         "Skipping CloneInfo for band not in source, "
1229
                         "this is a bit unusual!");
1230
        }
1231
    }
1232

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

1244
    /* -------------------------------------------------------------------- */
1245
    /*      Restore MO flags.                                               */
1246
    /* -------------------------------------------------------------------- */
1247
    SetMOFlags(nSavedMOFlags);
7,071✔
1248

1249
    return CE_None;
7,071✔
1250
}
1251

1252
//! @endcond
1253

1254
/************************************************************************/
1255
/*                            GetFileList()                             */
1256
/*                                                                      */
1257
/*      Add .aux.xml or .aux file into file list as appropriate.        */
1258
/************************************************************************/
1259

1260
char **GDALPamDataset::GetFileList()
4,130✔
1261

1262
{
1263
    char **papszFileList = GDALDataset::GetFileList();
4,130✔
1264

1265
    if (psPam && !psPam->osPhysicalFilename.empty() &&
4,042✔
1266
        GDALCanReliablyUseSiblingFileList(psPam->osPhysicalFilename.c_str()) &&
8,512✔
1267
        CSLFindString(papszFileList, psPam->osPhysicalFilename) == -1)
340✔
1268
    {
1269
        papszFileList =
1270
            CSLInsertString(papszFileList, 0, psPam->osPhysicalFilename);
12✔
1271
    }
1272

1273
    if (psPam && psPam->pszPamFilename)
4,130✔
1274
    {
1275
        int bAddPamFile = nPamFlags & GPF_DIRTY;
4,016✔
1276
        if (!bAddPamFile)
4,016✔
1277
        {
1278
            VSIStatBufL sStatBuf;
1279
            if (oOvManager.GetSiblingFiles() != nullptr &&
4,016✔
1280
                IsPamFilenameAPotentialSiblingFile() &&
7,034✔
1281
                GDALCanReliablyUseSiblingFileList(psPam->pszPamFilename))
3,018✔
1282
            {
1283
                bAddPamFile =
3,018✔
1284
                    CSLFindString(oOvManager.GetSiblingFiles(),
3,018✔
1285
                                  CPLGetFilename(psPam->pszPamFilename)) >= 0;
6,036✔
1286
            }
1287
            else
1288
            {
1289
                bAddPamFile = VSIStatExL(psPam->pszPamFilename, &sStatBuf,
998✔
1290
                                         VSI_STAT_EXISTS_FLAG) == 0;
998✔
1291
            }
1292
        }
1293
        if (bAddPamFile)
4,016✔
1294
        {
1295
            papszFileList = CSLAddString(papszFileList, psPam->pszPamFilename);
310✔
1296
        }
1297
    }
1298

1299
    if (psPam && !psPam->osAuxFilename.empty() &&
4,042✔
1300
        GDALCanReliablyUseSiblingFileList(psPam->osAuxFilename.c_str()) &&
8,174✔
1301
        CSLFindString(papszFileList, psPam->osAuxFilename) == -1)
2✔
1302
    {
1303
        papszFileList = CSLAddString(papszFileList, psPam->osAuxFilename);
1✔
1304
    }
1305
    return papszFileList;
4,130✔
1306
}
1307

1308
/************************************************************************/
1309
/*                          IBuildOverviews()                           */
1310
/************************************************************************/
1311

1312
//! @cond Doxygen_Suppress
1313
CPLErr GDALPamDataset::IBuildOverviews(
34✔
1314
    const char *pszResampling, int nOverviews, const int *panOverviewList,
1315
    int nListBands, const int *panBandList, GDALProgressFunc pfnProgress,
1316
    void *pProgressData, CSLConstList papszOptions)
1317

1318
{
1319
    /* -------------------------------------------------------------------- */
1320
    /*      Initialize PAM.                                                 */
1321
    /* -------------------------------------------------------------------- */
1322
    PamInitialize();
34✔
1323
    if (psPam == nullptr)
34✔
1324
        return GDALDataset::IBuildOverviews(
×
1325
            pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1326
            pfnProgress, pProgressData, papszOptions);
×
1327

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

1341
    return GDALDataset::IBuildOverviews(
25✔
1342
        pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
1343
        pfnProgress, pProgressData, papszOptions);
25✔
1344
}
1345

1346
//! @endcond
1347

1348
/************************************************************************/
1349
/*                           GetSpatialRef()                            */
1350
/************************************************************************/
1351

1352
const OGRSpatialReference *GDALPamDataset::GetSpatialRef() const
13,933✔
1353

1354
{
1355
    if (psPam && psPam->poSRS)
13,933✔
1356
        return psPam->poSRS;
92✔
1357

1358
    return GDALDataset::GetSpatialRef();
13,841✔
1359
}
1360

1361
/************************************************************************/
1362
/*                           SetSpatialRef()                            */
1363
/************************************************************************/
1364

1365
CPLErr GDALPamDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
384✔
1366

1367
{
1368
    PamInitialize();
384✔
1369

1370
    if (psPam == nullptr)
384✔
1371
        return GDALDataset::SetSpatialRef(poSRS);
×
1372

1373
    if (psPam->poSRS)
384✔
1374
        psPam->poSRS->Release();
6✔
1375
    psPam->poSRS = poSRS ? poSRS->Clone() : nullptr;
384✔
1376
    MarkPamDirty();
384✔
1377

1378
    return CE_None;
384✔
1379
}
1380

1381
/************************************************************************/
1382
/*                          GetGeoTransform()                           */
1383
/************************************************************************/
1384

1385
CPLErr GDALPamDataset::GetGeoTransform(double *padfTransform)
12,566✔
1386

1387
{
1388
    if (psPam && psPam->bHaveGeoTransform)
12,566✔
1389
    {
1390
        memcpy(padfTransform, psPam->adfGeoTransform.data(),
188✔
1391
               sizeof(psPam->adfGeoTransform));
1392
        return CE_None;
188✔
1393
    }
1394

1395
    return GDALDataset::GetGeoTransform(padfTransform);
12,378✔
1396
}
1397

1398
/************************************************************************/
1399
/*                          SetGeoTransform()                           */
1400
/************************************************************************/
1401

1402
CPLErr GDALPamDataset::SetGeoTransform(double *padfTransform)
382✔
1403

1404
{
1405
    PamInitialize();
382✔
1406

1407
    if (psPam)
382✔
1408
    {
1409
        MarkPamDirty();
382✔
1410
        psPam->bHaveGeoTransform = TRUE;
382✔
1411
        memcpy(psPam->adfGeoTransform.data(), padfTransform,
382✔
1412
               sizeof(psPam->adfGeoTransform));
1413
        return (CE_None);
382✔
1414
    }
1415

1416
    return GDALDataset::SetGeoTransform(padfTransform);
×
1417
}
1418

1419
/************************************************************************/
1420
/*                        DeleteGeoTransform()                          */
1421
/************************************************************************/
1422

1423
/** Remove geotransform from PAM.
1424
 *
1425
 * @since GDAL 3.4.1
1426
 */
1427
void GDALPamDataset::DeleteGeoTransform()
1,559✔
1428

1429
{
1430
    PamInitialize();
1,559✔
1431

1432
    if (psPam && psPam->bHaveGeoTransform)
1,559✔
1433
    {
1434
        MarkPamDirty();
3✔
1435
        psPam->bHaveGeoTransform = FALSE;
3✔
1436
    }
1437
}
1,559✔
1438

1439
/************************************************************************/
1440
/*                            GetGCPCount()                             */
1441
/************************************************************************/
1442

1443
int GDALPamDataset::GetGCPCount()
12,877✔
1444

1445
{
1446
    if (psPam && !psPam->asGCPs.empty())
12,877✔
1447
        return static_cast<int>(psPam->asGCPs.size());
45✔
1448

1449
    return GDALDataset::GetGCPCount();
12,831✔
1450
}
1451

1452
/************************************************************************/
1453
/*                          GetGCPSpatialRef()                          */
1454
/************************************************************************/
1455

1456
const OGRSpatialReference *GDALPamDataset::GetGCPSpatialRef() const
63✔
1457

1458
{
1459
    if (psPam && psPam->poGCP_SRS != nullptr)
63✔
1460
        return psPam->poGCP_SRS;
24✔
1461

1462
    return GDALDataset::GetGCPSpatialRef();
39✔
1463
}
1464

1465
/************************************************************************/
1466
/*                               GetGCPs()                              */
1467
/************************************************************************/
1468

1469
const GDAL_GCP *GDALPamDataset::GetGCPs()
37✔
1470

1471
{
1472
    if (psPam && !psPam->asGCPs.empty())
37✔
1473
        return gdal::GCP::c_ptr(psPam->asGCPs);
27✔
1474

1475
    return GDALDataset::GetGCPs();
10✔
1476
}
1477

1478
/************************************************************************/
1479
/*                              SetGCPs()                               */
1480
/************************************************************************/
1481

1482
CPLErr GDALPamDataset::SetGCPs(int nGCPCount, const GDAL_GCP *pasGCPList,
32✔
1483
                               const OGRSpatialReference *poGCP_SRS)
1484

1485
{
1486
    PamInitialize();
32✔
1487

1488
    if (psPam)
32✔
1489
    {
1490
        if (psPam->poGCP_SRS)
32✔
1491
            psPam->poGCP_SRS->Release();
1✔
1492
        psPam->poGCP_SRS = poGCP_SRS ? poGCP_SRS->Clone() : nullptr;
32✔
1493
        psPam->asGCPs = gdal::GCP::fromC(pasGCPList, nGCPCount);
32✔
1494
        MarkPamDirty();
32✔
1495

1496
        return CE_None;
32✔
1497
    }
1498

1499
    return GDALDataset::SetGCPs(nGCPCount, pasGCPList, poGCP_SRS);
×
1500
}
1501

1502
/************************************************************************/
1503
/*                            SetMetadata()                             */
1504
/************************************************************************/
1505

1506
CPLErr GDALPamDataset::SetMetadata(char **papszMetadata, const char *pszDomain)
3,544✔
1507

1508
{
1509
    PamInitialize();
3,544✔
1510

1511
    if (psPam)
3,544✔
1512
    {
1513
        psPam->bHasMetadata = TRUE;
3,544✔
1514
        MarkPamDirty();
3,544✔
1515
    }
1516

1517
    return GDALDataset::SetMetadata(papszMetadata, pszDomain);
3,544✔
1518
}
1519

1520
/************************************************************************/
1521
/*                          SetMetadataItem()                           */
1522
/************************************************************************/
1523

1524
CPLErr GDALPamDataset::SetMetadataItem(const char *pszName,
37,880✔
1525
                                       const char *pszValue,
1526
                                       const char *pszDomain)
1527

1528
{
1529
    PamInitialize();
37,880✔
1530

1531
    if (psPam)
37,881✔
1532
    {
1533
        psPam->bHasMetadata = TRUE;
37,881✔
1534
        MarkPamDirty();
37,881✔
1535
    }
1536

1537
    return GDALDataset::SetMetadataItem(pszName, pszValue, pszDomain);
37,881✔
1538
}
1539

1540
/************************************************************************/
1541
/*                          GetMetadataItem()                           */
1542
/************************************************************************/
1543

1544
const char *GDALPamDataset::GetMetadataItem(const char *pszName,
17,306✔
1545
                                            const char *pszDomain)
1546

1547
{
1548
    /* -------------------------------------------------------------------- */
1549
    /*      A request against the ProxyOverviewRequest is a special         */
1550
    /*      mechanism to request an overview filename be allocated in       */
1551
    /*      the proxy pool location.  The allocated name is saved as        */
1552
    /*      metadata as well as being returned.                             */
1553
    /* -------------------------------------------------------------------- */
1554
    if (pszDomain != nullptr && EQUAL(pszDomain, "ProxyOverviewRequest"))
17,306✔
1555
    {
1556
        CPLString osPrelimOvr = GetDescription();
8✔
1557
        osPrelimOvr += ":::OVR";
4✔
1558

1559
        const char *pszProxyOvrFilename = PamAllocateProxy(osPrelimOvr);
4✔
1560
        if (pszProxyOvrFilename == nullptr)
4✔
1561
            return nullptr;
3✔
1562

1563
        SetMetadataItem("OVERVIEW_FILE", pszProxyOvrFilename, "OVERVIEWS");
1✔
1564

1565
        return pszProxyOvrFilename;
1✔
1566
    }
1567

1568
    /* -------------------------------------------------------------------- */
1569
    /*      If the OVERVIEW_FILE metadata is requested, we intercept the    */
1570
    /*      request in order to replace ":::BASE:::" with the path to       */
1571
    /*      the physical file - if available.  This is primarily for the    */
1572
    /*      purpose of managing subdataset overview filenames as being      */
1573
    /*      relative to the physical file the subdataset comes              */
1574
    /*      from. (#3287).                                                  */
1575
    /* -------------------------------------------------------------------- */
1576
    else if (pszDomain != nullptr && EQUAL(pszDomain, "OVERVIEWS") &&
17,302✔
1577
             EQUAL(pszName, "OVERVIEW_FILE"))
5,493✔
1578
    {
1579
        if (m_osOverviewFile.empty())
5,493✔
1580
        {
1581
            const char *pszOverviewFile =
1582
                GDALDataset::GetMetadataItem(pszName, pszDomain);
5,484✔
1583

1584
            if (pszOverviewFile == nullptr ||
5,484✔
1585
                !STARTS_WITH_CI(pszOverviewFile, ":::BASE:::"))
17✔
1586
                return pszOverviewFile;
5,468✔
1587

1588
            std::string osPath;
16✔
1589

1590
            if (strlen(GetPhysicalFilename()) > 0)
16✔
1591
                osPath = CPLGetPathSafe(GetPhysicalFilename());
16✔
1592
            else
1593
                osPath = CPLGetPathSafe(GetDescription());
×
1594

1595
            m_osOverviewFile = CPLFormFilenameSafe(
32✔
1596
                osPath.c_str(), pszOverviewFile + 10, nullptr);
16✔
1597
        }
1598
        return m_osOverviewFile.c_str();
25✔
1599
    }
1600

1601
    /* -------------------------------------------------------------------- */
1602
    /*      Everything else is a pass through.                              */
1603
    /* -------------------------------------------------------------------- */
1604

1605
    return GDALDataset::GetMetadataItem(pszName, pszDomain);
11,809✔
1606
}
1607

1608
/************************************************************************/
1609
/*                            GetMetadata()                             */
1610
/************************************************************************/
1611

1612
char **GDALPamDataset::GetMetadata(const char *pszDomain)
13,989✔
1613

1614
{
1615
    // if( pszDomain == nullptr || !EQUAL(pszDomain,"ProxyOverviewRequest") )
1616
    return GDALDataset::GetMetadata(pszDomain);
13,989✔
1617
}
1618

1619
/************************************************************************/
1620
/*                             TryLoadAux()                             */
1621
/************************************************************************/
1622

1623
//! @cond Doxygen_Suppress
1624
CPLErr GDALPamDataset::TryLoadAux(CSLConstList papszSiblingFiles)
31,354✔
1625

1626
{
1627
    /* -------------------------------------------------------------------- */
1628
    /*      Initialize PAM.                                                 */
1629
    /* -------------------------------------------------------------------- */
1630
    PamInitialize();
31,354✔
1631

1632
    if (psPam == nullptr || (nPamFlags & GPF_DISABLED) != 0)
31,353✔
UNCOV
1633
        return CE_None;
×
1634

1635
    /* -------------------------------------------------------------------- */
1636
    /*      What is the name of the physical file we are referencing?       */
1637
    /*      We allow an override via the psPam->pszPhysicalFile item.       */
1638
    /* -------------------------------------------------------------------- */
1639
    const char *pszPhysicalFile = psPam->osPhysicalFilename;
31,353✔
1640

1641
    if (strlen(pszPhysicalFile) == 0 && GetDescription() != nullptr)
31,354✔
1642
        pszPhysicalFile = GetDescription();
29,978✔
1643

1644
    if (strlen(pszPhysicalFile) == 0)
31,353✔
1645
        return CE_None;
×
1646

1647
    if (papszSiblingFiles && GDALCanReliablyUseSiblingFileList(pszPhysicalFile))
31,353✔
1648
    {
1649
        CPLString osAuxFilename = CPLResetExtensionSafe(pszPhysicalFile, "aux");
24,234✔
1650
        int iSibling =
1651
            CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
24,234✔
1652
        if (iSibling < 0)
24,236✔
1653
        {
1654
            osAuxFilename = pszPhysicalFile;
24,229✔
1655
            osAuxFilename += ".aux";
24,229✔
1656
            iSibling =
1657
                CSLFindString(papszSiblingFiles, CPLGetFilename(osAuxFilename));
24,230✔
1658
            if (iSibling < 0)
24,230✔
1659
                return CE_None;
24,230✔
1660
        }
1661
    }
1662

1663
    /* -------------------------------------------------------------------- */
1664
    /*      Try to open .aux file.                                          */
1665
    /* -------------------------------------------------------------------- */
1666
    GDALDataset *poAuxDS =
1667
        GDALFindAssociatedAuxFile(pszPhysicalFile, GA_ReadOnly, this);
7,124✔
1668

1669
    if (poAuxDS == nullptr)
7,125✔
1670
        return CE_None;
7,116✔
1671

1672
    psPam->osAuxFilename = poAuxDS->GetDescription();
9✔
1673

1674
    /* -------------------------------------------------------------------- */
1675
    /*      Do we have an SRS on the aux file?                              */
1676
    /* -------------------------------------------------------------------- */
1677
    if (strlen(poAuxDS->GetProjectionRef()) > 0)
9✔
1678
        GDALPamDataset::SetProjection(poAuxDS->GetProjectionRef());
2✔
1679

1680
    /* -------------------------------------------------------------------- */
1681
    /*      Geotransform.                                                   */
1682
    /* -------------------------------------------------------------------- */
1683
    if (poAuxDS->GetGeoTransform(psPam->adfGeoTransform.data()) == CE_None)
9✔
1684
        psPam->bHaveGeoTransform = TRUE;
2✔
1685

1686
    /* -------------------------------------------------------------------- */
1687
    /*      GCPs                                                            */
1688
    /* -------------------------------------------------------------------- */
1689
    if (poAuxDS->GetGCPCount() > 0)
9✔
1690
    {
1691
        psPam->asGCPs =
×
1692
            gdal::GCP::fromC(poAuxDS->GetGCPs(), poAuxDS->GetGCPCount());
×
1693
    }
1694

1695
    /* -------------------------------------------------------------------- */
1696
    /*      Apply metadata. We likely ought to be merging this in rather    */
1697
    /*      than overwriting everything that was there.                     */
1698
    /* -------------------------------------------------------------------- */
1699
    char **papszMD = poAuxDS->GetMetadata();
9✔
1700
    if (CSLCount(papszMD) > 0)
9✔
1701
    {
1702
        char **papszMerged = CSLMerge(CSLDuplicate(GetMetadata()), papszMD);
×
1703
        GDALPamDataset::SetMetadata(papszMerged);
×
1704
        CSLDestroy(papszMerged);
×
1705
    }
1706

1707
    papszMD = poAuxDS->GetMetadata("XFORMS");
9✔
1708
    if (CSLCount(papszMD) > 0)
9✔
1709
    {
1710
        char **papszMerged =
1711
            CSLMerge(CSLDuplicate(GetMetadata("XFORMS")), papszMD);
×
1712
        GDALPamDataset::SetMetadata(papszMerged, "XFORMS");
×
1713
        CSLDestroy(papszMerged);
×
1714
    }
1715

1716
    /* ==================================================================== */
1717
    /*      Process bands.                                                  */
1718
    /* ==================================================================== */
1719
    for (int iBand = 0; iBand < poAuxDS->GetRasterCount(); iBand++)
22✔
1720
    {
1721
        if (iBand >= GetRasterCount())
13✔
1722
            break;
×
1723

1724
        GDALRasterBand *const poAuxBand = poAuxDS->GetRasterBand(iBand + 1);
13✔
1725
        GDALRasterBand *const poBand = GetRasterBand(iBand + 1);
13✔
1726

1727
        papszMD = poAuxBand->GetMetadata();
13✔
1728
        if (CSLCount(papszMD) > 0)
13✔
1729
        {
1730
            char **papszMerged =
1731
                CSLMerge(CSLDuplicate(poBand->GetMetadata()), papszMD);
13✔
1732
            poBand->SetMetadata(papszMerged);
13✔
1733
            CSLDestroy(papszMerged);
13✔
1734
        }
1735

1736
        if (strlen(poAuxBand->GetDescription()) > 0)
13✔
1737
            poBand->SetDescription(poAuxBand->GetDescription());
13✔
1738

1739
        if (poAuxBand->GetCategoryNames() != nullptr)
13✔
1740
            poBand->SetCategoryNames(poAuxBand->GetCategoryNames());
×
1741

1742
        if (poAuxBand->GetColorTable() != nullptr &&
13✔
1743
            poBand->GetColorTable() == nullptr)
×
1744
            poBand->SetColorTable(poAuxBand->GetColorTable());
×
1745

1746
        // histograms?
1747
        double dfMin = 0.0;
13✔
1748
        double dfMax = 0.0;
13✔
1749
        int nBuckets = 0;
13✔
1750
        GUIntBig *panHistogram = nullptr;
13✔
1751

1752
        if (poAuxBand->GetDefaultHistogram(&dfMin, &dfMax, &nBuckets,
26✔
1753
                                           &panHistogram, FALSE, nullptr,
1754
                                           nullptr) == CE_None)
13✔
1755
        {
1756
            poBand->SetDefaultHistogram(dfMin, dfMax, nBuckets, panHistogram);
8✔
1757
            CPLFree(panHistogram);
8✔
1758
        }
1759

1760
        // RAT
1761
        if (poAuxBand->GetDefaultRAT() != nullptr)
13✔
1762
            poBand->SetDefaultRAT(poAuxBand->GetDefaultRAT());
13✔
1763

1764
        // NoData
1765
        int bSuccess = FALSE;
13✔
1766
        const double dfNoDataValue = poAuxBand->GetNoDataValue(&bSuccess);
13✔
1767
        if (bSuccess)
13✔
1768
            poBand->SetNoDataValue(dfNoDataValue);
2✔
1769
    }
1770

1771
    GDALClose(poAuxDS);
9✔
1772

1773
    /* -------------------------------------------------------------------- */
1774
    /*      Mark PAM info as clean.                                         */
1775
    /* -------------------------------------------------------------------- */
1776
    nPamFlags &= ~GPF_DIRTY;
9✔
1777

1778
    return CE_Failure;
9✔
1779
}
1780

1781
//! @endcond
1782

1783
/************************************************************************/
1784
/*                          ClearStatistics()                           */
1785
/************************************************************************/
1786

1787
void GDALPamDataset::ClearStatistics()
2✔
1788
{
1789
    PamInitialize();
2✔
1790
    if (!psPam)
2✔
1791
        return;
×
1792
    for (int i = 1; i <= nBands; ++i)
3✔
1793
    {
1794
        bool bChanged = false;
1✔
1795
        GDALRasterBand *poBand = GetRasterBand(i);
1✔
1796
        CPLStringList aosNewMD;
2✔
1797
        for (const char *pszStr :
5✔
1798
             cpl::Iterate(static_cast<CSLConstList>(poBand->GetMetadata())))
11✔
1799
        {
1800
            if (STARTS_WITH_CI(pszStr, "STATISTICS_"))
5✔
1801
            {
1802
                MarkPamDirty();
5✔
1803
                bChanged = true;
5✔
1804
            }
1805
            else
1806
            {
1807
                aosNewMD.AddString(pszStr);
×
1808
            }
1809
        }
1810
        if (bChanged)
1✔
1811
        {
1812
            poBand->SetMetadata(aosNewMD.List());
1✔
1813
        }
1814
    }
1815

1816
    GDALDataset::ClearStatistics();
2✔
1817
}
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