Coverage Report

Created: 2026-11-12 08:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pds/pds4dataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PDS 4 Driver; Planetary Data System Format
4
 * Purpose:  Implementation of PDS4Dataset
5
 * Author:   Even Rouault, even.rouault at spatialys.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2017, Hobu Inc
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_vsi_error.h"
14
#include "gdal_proxy.h"
15
#include "gdal_frmts.h"
16
#include "rawdataset.h"
17
#include "vrtdataset.h"
18
#include "ogrsf_frmts.h"
19
#include "ogr_spatialref.h"
20
#include "gdal_priv_templates.hpp"
21
#include "ogreditablelayer.h"
22
#include "pds4dataset.h"
23
#include "pdsdrivercore.h"
24
25
#ifdef EMBED_RESOURCE_FILES
26
#include "embedded_resources.h"
27
#endif
28
29
#include <cstdlib>
30
#include <vector>
31
#include <algorithm>
32
#include <optional>
33
34
0
#define TIFF_GEOTIFF_STRING "TIFF 6.0"
35
0
#define BIGTIFF_GEOTIFF_STRING "TIFF 6.0"
36
#define PREEXISTING_BINARY_FILE                                                \
37
0
    "Binary file pre-existing PDS4 label. This comment is used by GDAL to "    \
38
0
    "avoid deleting the binary file when the label is deleted. Keep it to "    \
39
0
    "preserve this behavior."
40
41
440
#define CURRENT_CART_VERSION "1O00_1970"
42
43
/************************************************************************/
44
/*                   PDS4BrowseImageProxyRasterBand()                   */
45
/************************************************************************/
46
47
PDS4BrowseImageProxyRasterBand::PDS4BrowseImageProxyRasterBand(
48
    GDALRasterBand *poBaseBandIn)
49
0
    : m_poBaseBand(poBaseBandIn)
50
0
{
51
0
    eDataType = m_poBaseBand->GetRasterDataType();
52
0
    m_poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
53
0
}
54
55
/************************************************************************/
56
/*                      RefUnderlyingRasterBand()                       */
57
/************************************************************************/
58
59
GDALRasterBand *PDS4BrowseImageProxyRasterBand::RefUnderlyingRasterBand(
60
    bool /*bForceOpen*/) const
61
0
{
62
0
    return m_poBaseBand;
63
0
}
64
65
/************************************************************************/
66
/*                       PDS4WrapperRasterBand()                        */
67
/************************************************************************/
68
69
PDS4WrapperRasterBand::PDS4WrapperRasterBand(GDALRasterBand *poBaseBandIn)
70
0
    : m_poBaseBand(poBaseBandIn)
71
0
{
72
0
    eDataType = m_poBaseBand->GetRasterDataType();
73
0
    m_poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
74
0
}
75
76
/************************************************************************/
77
/*                            SetMaskBand()                             */
78
/************************************************************************/
79
80
void PDS4WrapperRasterBand::SetMaskBand(
81
    std::unique_ptr<GDALRasterBand> poMaskBand)
82
0
{
83
0
    poMask.reset(std::move(poMaskBand));
84
0
    nMaskFlags = 0;
85
0
}
86
87
/************************************************************************/
88
/*                             GetOffset()                              */
89
/************************************************************************/
90
91
double PDS4WrapperRasterBand::GetOffset(int *pbSuccess)
92
0
{
93
0
    if (pbSuccess)
94
0
        *pbSuccess = m_bHasOffset;
95
0
    return m_dfOffset;
96
0
}
97
98
/************************************************************************/
99
/*                              GetScale()                              */
100
/************************************************************************/
101
102
double PDS4WrapperRasterBand::GetScale(int *pbSuccess)
103
0
{
104
0
    if (pbSuccess)
105
0
        *pbSuccess = m_bHasScale;
106
0
    return m_dfScale;
107
0
}
108
109
/************************************************************************/
110
/*                             SetOffset()                              */
111
/************************************************************************/
112
113
CPLErr PDS4WrapperRasterBand::SetOffset(double dfNewOffset)
114
0
{
115
0
    m_dfOffset = dfNewOffset;
116
0
    m_bHasOffset = true;
117
118
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
119
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
120
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetOffset(dfNewOffset);
121
122
0
    return CE_None;
123
0
}
124
125
/************************************************************************/
126
/*                              SetScale()                              */
127
/************************************************************************/
128
129
CPLErr PDS4WrapperRasterBand::SetScale(double dfNewScale)
130
0
{
131
0
    m_dfScale = dfNewScale;
132
0
    m_bHasScale = true;
133
134
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
135
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
136
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetScale(dfNewScale);
137
138
0
    return CE_None;
139
0
}
140
141
/************************************************************************/
142
/*                           GetNoDataValue()                           */
143
/************************************************************************/
144
145
double PDS4WrapperRasterBand::GetNoDataValue(int *pbSuccess)
146
0
{
147
0
    if (m_bHasNoDataInt64)
148
0
    {
149
0
        if (pbSuccess)
150
0
            *pbSuccess = true;
151
0
        return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
152
0
    }
153
154
0
    if (m_bHasNoDataUInt64)
155
0
    {
156
0
        if (pbSuccess)
157
0
            *pbSuccess = true;
158
0
        return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
159
0
    }
160
161
0
    if (pbSuccess)
162
0
        *pbSuccess = m_bHasNoData;
163
0
    return m_dfNoData;
164
0
}
165
166
/************************************************************************/
167
/*                           SetNoDataValue()                           */
168
/************************************************************************/
169
170
CPLErr PDS4WrapperRasterBand::SetNoDataValue(double dfNewNoData)
171
0
{
172
0
    m_dfNoData = dfNewNoData;
173
0
    m_bHasNoData = true;
174
175
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
176
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
177
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValue(
178
0
            dfNewNoData);
179
180
0
    return CE_None;
181
0
}
182
183
/************************************************************************/
184
/*                       GetNoDataValueAsInt64()                        */
185
/************************************************************************/
186
187
int64_t PDS4WrapperRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
188
0
{
189
0
    if (pbSuccess)
190
0
        *pbSuccess = m_bHasNoDataInt64;
191
0
    return m_nNoDataInt64;
192
0
}
193
194
/************************************************************************/
195
/*                       SetNoDataValueAsInt64()                        */
196
/************************************************************************/
197
198
CPLErr PDS4WrapperRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
199
0
{
200
0
    m_nNoDataInt64 = nNewNoData;
201
0
    m_bHasNoDataInt64 = true;
202
203
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
204
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
205
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
206
0
            nNewNoData);
207
208
0
    return CE_None;
209
0
}
210
211
/************************************************************************/
212
/*                       GetNoDataValueAsUInt64()                       */
213
/************************************************************************/
214
215
uint64_t PDS4WrapperRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
216
0
{
217
0
    if (pbSuccess)
218
0
        *pbSuccess = m_bHasNoDataUInt64;
219
0
    return m_nNoDataUInt64;
220
0
}
221
222
/************************************************************************/
223
/*                       SetNoDataValueAsUInt64()                       */
224
/************************************************************************/
225
226
CPLErr PDS4WrapperRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
227
0
{
228
0
    m_nNoDataUInt64 = nNewNoData;
229
0
    m_bHasNoDataUInt64 = true;
230
231
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
232
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
233
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
234
0
            nNewNoData);
235
236
0
    return CE_None;
237
0
}
238
239
/************************************************************************/
240
/*                                Fill()                                */
241
/************************************************************************/
242
243
CPLErr PDS4WrapperRasterBand::Fill(double dfRealValue, double dfImaginaryValue)
244
0
{
245
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
246
0
    if (poGDS->m_bMustInitImageFile)
247
0
    {
248
0
        if (!poGDS->InitImageFile())
249
0
            return CE_Failure;
250
0
    }
251
0
    return GDALProxyRasterBand::Fill(dfRealValue, dfImaginaryValue);
252
0
}
253
254
/************************************************************************/
255
/*                            IWriteBlock()                             */
256
/************************************************************************/
257
258
CPLErr PDS4WrapperRasterBand::IWriteBlock(int nXBlock, int nYBlock,
259
                                          void *pImage)
260
261
0
{
262
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
263
0
    if (poGDS->m_bMustInitImageFile)
264
0
    {
265
0
        if (!poGDS->InitImageFile())
266
0
            return CE_Failure;
267
0
    }
268
0
    return GDALProxyRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
269
0
}
270
271
/************************************************************************/
272
/*                             IRasterIO()                              */
273
/************************************************************************/
274
275
CPLErr PDS4WrapperRasterBand::IRasterIO(
276
    GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
277
    void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
278
    GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
279
280
0
{
281
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
282
0
    if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
283
0
    {
284
0
        if (!poGDS->InitImageFile())
285
0
            return CE_Failure;
286
0
    }
287
0
    return GDALProxyRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
288
0
                                          pData, nBufXSize, nBufYSize, eBufType,
289
0
                                          nPixelSpace, nLineSpace, psExtraArg);
290
0
}
291
292
/************************************************************************/
293
/*                         PDS4RawRasterBand()                          */
294
/************************************************************************/
295
296
PDS4RawRasterBand::PDS4RawRasterBand(GDALDataset *l_poDS, int l_nBand,
297
                                     VSILFILE *l_fpRaw,
298
                                     vsi_l_offset l_nImgOffset,
299
                                     int l_nPixelOffset, int l_nLineOffset,
300
                                     GDALDataType l_eDataType,
301
                                     RawRasterBand::ByteOrder eByteOrderIn)
302
1.63k
    : RawRasterBand(l_poDS, l_nBand, l_fpRaw, l_nImgOffset, l_nPixelOffset,
303
1.63k
                    l_nLineOffset, l_eDataType, eByteOrderIn,
304
1.63k
                    RawRasterBand::OwnFP::NO)
305
1.63k
{
306
1.63k
}
307
308
/************************************************************************/
309
/*                            SetMaskBand()                             */
310
/************************************************************************/
311
312
void PDS4RawRasterBand::SetMaskBand(std::unique_ptr<GDALRasterBand> poMaskBand)
313
0
{
314
0
    poMask.reset(std::move(poMaskBand));
315
0
    nMaskFlags = 0;
316
0
}
317
318
/************************************************************************/
319
/*                             GetOffset()                              */
320
/************************************************************************/
321
322
double PDS4RawRasterBand::GetOffset(int *pbSuccess)
323
16
{
324
16
    if (pbSuccess)
325
16
        *pbSuccess = m_bHasOffset;
326
16
    return m_dfOffset;
327
16
}
328
329
/************************************************************************/
330
/*                              GetScale()                              */
331
/************************************************************************/
332
333
double PDS4RawRasterBand::GetScale(int *pbSuccess)
334
16
{
335
16
    if (pbSuccess)
336
16
        *pbSuccess = m_bHasScale;
337
16
    return m_dfScale;
338
16
}
339
340
/************************************************************************/
341
/*                             SetOffset()                              */
342
/************************************************************************/
343
344
CPLErr PDS4RawRasterBand::SetOffset(double dfNewOffset)
345
804
{
346
804
    m_dfOffset = dfNewOffset;
347
804
    m_bHasOffset = true;
348
804
    return CE_None;
349
804
}
350
351
/************************************************************************/
352
/*                              SetScale()                              */
353
/************************************************************************/
354
355
CPLErr PDS4RawRasterBand::SetScale(double dfNewScale)
356
1.40k
{
357
1.40k
    m_dfScale = dfNewScale;
358
1.40k
    m_bHasScale = true;
359
1.40k
    return CE_None;
360
1.40k
}
361
362
/************************************************************************/
363
/*                           GetNoDataValue()                           */
364
/************************************************************************/
365
366
double PDS4RawRasterBand::GetNoDataValue(int *pbSuccess)
367
16
{
368
16
    if (m_bHasNoDataInt64)
369
0
    {
370
0
        if (pbSuccess)
371
0
            *pbSuccess = true;
372
0
        return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
373
0
    }
374
375
16
    if (m_bHasNoDataUInt64)
376
0
    {
377
0
        if (pbSuccess)
378
0
            *pbSuccess = true;
379
0
        return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
380
0
    }
381
382
16
    if (pbSuccess)
383
16
        *pbSuccess = m_bHasNoData;
384
16
    return m_dfNoData;
385
16
}
386
387
/************************************************************************/
388
/*                           SetNoDataValue()                           */
389
/************************************************************************/
390
391
CPLErr PDS4RawRasterBand::SetNoDataValue(double dfNewNoData)
392
32
{
393
32
    m_dfNoData = dfNewNoData;
394
32
    m_bHasNoData = true;
395
32
    return CE_None;
396
32
}
397
398
/************************************************************************/
399
/*                       GetNoDataValueAsInt64()                        */
400
/************************************************************************/
401
402
int64_t PDS4RawRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
403
0
{
404
0
    if (pbSuccess)
405
0
        *pbSuccess = m_bHasNoDataInt64;
406
0
    return m_nNoDataInt64;
407
0
}
408
409
/************************************************************************/
410
/*                       SetNoDataValueAsInt64()                        */
411
/************************************************************************/
412
413
CPLErr PDS4RawRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
414
0
{
415
0
    m_nNoDataInt64 = nNewNoData;
416
0
    m_bHasNoDataInt64 = true;
417
418
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
419
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
420
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
421
0
            nNewNoData);
422
423
0
    return CE_None;
424
0
}
425
426
/************************************************************************/
427
/*                       GetNoDataValueAsUInt64()                       */
428
/************************************************************************/
429
430
uint64_t PDS4RawRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
431
0
{
432
0
    if (pbSuccess)
433
0
        *pbSuccess = m_bHasNoDataUInt64;
434
0
    return m_nNoDataUInt64;
435
0
}
436
437
/************************************************************************/
438
/*                       SetNoDataValueAsUInt64()                       */
439
/************************************************************************/
440
441
CPLErr PDS4RawRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
442
0
{
443
0
    m_nNoDataUInt64 = nNewNoData;
444
0
    m_bHasNoDataUInt64 = true;
445
446
0
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
447
0
    if (poGDS->m_poExternalDS && eAccess == GA_Update)
448
0
        poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
449
0
            nNewNoData);
450
451
0
    return CE_None;
452
0
}
453
454
/************************************************************************/
455
/*                             IReadBlock()                             */
456
/************************************************************************/
457
458
CPLErr PDS4RawRasterBand::IWriteBlock(int nXBlock, int nYBlock, void *pImage)
459
460
7
{
461
7
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
462
7
    if (poGDS->m_bMustInitImageFile)
463
0
    {
464
0
        if (!poGDS->InitImageFile())
465
0
            return CE_Failure;
466
0
    }
467
468
7
    return RawRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
469
7
}
470
471
/************************************************************************/
472
/*                             IRasterIO()                              */
473
/************************************************************************/
474
475
CPLErr PDS4RawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
476
                                    int nXSize, int nYSize, void *pData,
477
                                    int nBufXSize, int nBufYSize,
478
                                    GDALDataType eBufType, GSpacing nPixelSpace,
479
                                    GSpacing nLineSpace,
480
                                    GDALRasterIOExtraArg *psExtraArg)
481
482
1.46k
{
483
1.46k
    PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
484
1.46k
    if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
485
0
    {
486
0
        if (!poGDS->InitImageFile())
487
0
            return CE_Failure;
488
0
    }
489
490
1.46k
    return RawRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
491
1.46k
                                    pData, nBufXSize, nBufYSize, eBufType,
492
1.46k
                                    nPixelSpace, nLineSpace, psExtraArg);
493
1.46k
}
494
495
/************************************************************************/
496
/*                            PDS4MaskBand()                            */
497
/************************************************************************/
498
499
PDS4MaskBand::PDS4MaskBand(GDALRasterBand *poBaseBand,
500
                           const std::vector<double> &adfConstants)
501
0
    : m_poBaseBand(poBaseBand), m_pBuffer(nullptr), m_adfConstants(adfConstants)
502
0
{
503
0
    eDataType = GDT_UInt8;
504
0
    poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
505
0
    nRasterXSize = poBaseBand->GetXSize();
506
0
    nRasterYSize = poBaseBand->GetYSize();
507
0
}
508
509
/************************************************************************/
510
/*                           ~PDS4MaskBand()                            */
511
/************************************************************************/
512
513
PDS4MaskBand::~PDS4MaskBand()
514
0
{
515
0
    VSIFree(m_pBuffer);
516
0
}
517
518
/************************************************************************/
519
/*                              FillMask()                              */
520
/************************************************************************/
521
522
template <class T>
523
static void FillMask(void *pvBuffer, GByte *pabyDst, int nReqXSize,
524
                     int nReqYSize, int nBlockXSize,
525
                     const std::vector<double> &adfConstants)
526
0
{
527
0
    const T *pSrc = static_cast<T *>(pvBuffer);
528
0
    std::vector<T> aConstants;
529
0
    for (size_t i = 0; i < adfConstants.size(); i++)
530
0
    {
531
0
        T cst;
532
0
        GDALCopyWord(adfConstants[i], cst);
533
0
        aConstants.push_back(cst);
534
0
    }
535
536
0
    for (int y = 0; y < nReqYSize; y++)
537
0
    {
538
0
        for (int x = 0; x < nReqXSize; x++)
539
0
        {
540
0
            const T nSrc = pSrc[y * nBlockXSize + x];
541
0
            if (std::find(aConstants.begin(), aConstants.end(), nSrc) !=
542
0
                aConstants.end())
543
0
            {
544
0
                pabyDst[y * nBlockXSize + x] = 0;
545
0
            }
546
0
            else
547
0
            {
548
0
                pabyDst[y * nBlockXSize + x] = 255;
549
0
            }
550
0
        }
551
0
    }
552
0
}
Unexecuted instantiation: pds4dataset.cpp:void FillMask<unsigned char>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<signed char>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<unsigned short>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<short>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<unsigned int>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<int>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<unsigned long>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<long>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<float>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
Unexecuted instantiation: pds4dataset.cpp:void FillMask<double>(void*, unsigned char*, int, int, int, std::__1::vector<double, std::__1::allocator<double> > const&)
553
554
/************************************************************************/
555
/*                             IReadBlock()                             */
556
/************************************************************************/
557
558
CPLErr PDS4MaskBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
559
560
0
{
561
0
    const GDALDataType eSrcDT = m_poBaseBand->GetRasterDataType();
562
0
    const int nSrcDTSize = GDALGetDataTypeSizeBytes(eSrcDT);
563
0
    if (m_pBuffer == nullptr)
564
0
    {
565
0
        m_pBuffer = VSI_MALLOC3_VERBOSE(nBlockXSize, nBlockYSize, nSrcDTSize);
566
0
        if (m_pBuffer == nullptr)
567
0
            return CE_Failure;
568
0
    }
569
570
0
    const int nXOff = nBlockXOff * nBlockXSize;
571
0
    const int nReqXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
572
0
    const int nYOff = nBlockYOff * nBlockYSize;
573
0
    const int nReqYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
574
575
0
    if (m_poBaseBand->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
576
0
                               m_pBuffer, nReqXSize, nReqYSize, eSrcDT,
577
0
                               nSrcDTSize,
578
0
                               static_cast<GSpacing>(nSrcDTSize) * nBlockXSize,
579
0
                               nullptr) != CE_None)
580
0
    {
581
0
        return CE_Failure;
582
0
    }
583
584
0
    GByte *pabyDst = static_cast<GByte *>(pImage);
585
0
    if (eSrcDT == GDT_UInt8)
586
0
    {
587
0
        FillMask<GByte>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
588
0
                        m_adfConstants);
589
0
    }
590
0
    else if (eSrcDT == GDT_Int8)
591
0
    {
592
0
        FillMask<GInt8>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
593
0
                        m_adfConstants);
594
0
    }
595
0
    else if (eSrcDT == GDT_UInt16)
596
0
    {
597
0
        FillMask<GUInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
598
0
                          m_adfConstants);
599
0
    }
600
0
    else if (eSrcDT == GDT_Int16)
601
0
    {
602
0
        FillMask<GInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
603
0
                         m_adfConstants);
604
0
    }
605
0
    else if (eSrcDT == GDT_UInt32)
606
0
    {
607
0
        FillMask<GUInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
608
0
                          m_adfConstants);
609
0
    }
610
0
    else if (eSrcDT == GDT_Int32)
611
0
    {
612
0
        FillMask<GInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
613
0
                         m_adfConstants);
614
0
    }
615
0
    else if (eSrcDT == GDT_UInt64)
616
0
    {
617
0
        FillMask<uint64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize,
618
0
                           nBlockXSize, m_adfConstants);
619
0
    }
620
0
    else if (eSrcDT == GDT_Int64)
621
0
    {
622
0
        FillMask<int64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
623
0
                          m_adfConstants);
624
0
    }
625
0
    else if (eSrcDT == GDT_Float32)
626
0
    {
627
0
        FillMask<float>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
628
0
                        m_adfConstants);
629
0
    }
630
0
    else if (eSrcDT == GDT_Float64)
631
0
    {
632
0
        FillMask<double>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
633
0
                         m_adfConstants);
634
0
    }
635
636
0
    return CE_None;
637
0
}
638
639
/************************************************************************/
640
/*                            PDS4Dataset()                             */
641
/************************************************************************/
642
643
PDS4Dataset::PDS4Dataset()
644
1.18k
{
645
1.18k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
646
1.18k
}
647
648
/************************************************************************/
649
/*                            ~PDS4Dataset()                            */
650
/************************************************************************/
651
652
PDS4Dataset::~PDS4Dataset()
653
1.18k
{
654
1.18k
    PDS4Dataset::Close();
655
1.18k
}
656
657
/************************************************************************/
658
/*                               Close()                                */
659
/************************************************************************/
660
661
CPLErr PDS4Dataset::Close(GDALProgressFunc, void *)
662
1.95k
{
663
1.95k
    CPLErr eErr = CE_None;
664
1.95k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
665
1.18k
    {
666
1.18k
        if (m_bMustInitImageFile)
667
0
        {
668
0
            if (!InitImageFile())
669
0
                eErr = CE_Failure;
670
0
        }
671
672
1.18k
        if (PDS4Dataset::FlushCache(true) != CE_None)
673
0
            eErr = CE_Failure;
674
675
1.18k
        if (m_bCreateHeader || m_bDirtyHeader)
676
440
            WriteHeader();
677
1.18k
        if (m_fpImage)
678
16
            VSIFCloseL(m_fpImage);
679
1.18k
        CSLDestroy(m_papszCreationOptions);
680
1.18k
        PDS4Dataset::CloseDependentDatasets();
681
682
1.18k
        if (GDALPamDataset::Close() != CE_None)
683
0
            eErr = CE_Failure;
684
1.18k
    }
685
1.95k
    return eErr;
686
1.95k
}
687
688
/************************************************************************/
689
/*                         GetRawBinaryLayout()                         */
690
/************************************************************************/
691
692
bool PDS4Dataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
693
0
{
694
0
    if (!RawDataset::GetRawBinaryLayout(sLayout))
695
0
        return false;
696
0
    sLayout.osRawFilename = m_osImageFilename;
697
0
    return true;
698
0
}
699
700
/************************************************************************/
701
/*                       CloseDependentDatasets()                       */
702
/************************************************************************/
703
704
int PDS4Dataset::CloseDependentDatasets()
705
1.18k
{
706
1.18k
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
707
708
1.18k
    if (m_poExternalDS)
709
0
    {
710
0
        bHasDroppedRef = FALSE;
711
0
        delete m_poExternalDS;
712
0
        m_poExternalDS = nullptr;
713
714
0
        for (int iBand = 0; iBand < nBands; iBand++)
715
0
        {
716
0
            delete papoBands[iBand];
717
0
            papoBands[iBand] = nullptr;
718
0
        }
719
0
        nBands = 0;
720
0
    }
721
722
1.18k
    return bHasDroppedRef;
723
1.18k
}
724
725
/************************************************************************/
726
/*                           GetSpatialRef()                            */
727
/************************************************************************/
728
729
const OGRSpatialReference *PDS4Dataset::GetSpatialRef() const
730
2
{
731
2
    if (!m_oSRS.IsEmpty())
732
0
        return &m_oSRS;
733
2
    return GDALPamDataset::GetSpatialRef();
734
2
}
735
736
/************************************************************************/
737
/*                           SetSpatialRef()                            */
738
/************************************************************************/
739
740
CPLErr PDS4Dataset::SetSpatialRef(const OGRSpatialReference *poSRS)
741
742
10
{
743
10
    if (eAccess == GA_ReadOnly)
744
0
        return CE_Failure;
745
10
    m_oSRS.Clear();
746
10
    if (poSRS)
747
10
        m_oSRS = *poSRS;
748
10
    if (m_poExternalDS)
749
0
        m_poExternalDS->SetSpatialRef(poSRS);
750
10
    return CE_None;
751
10
}
752
753
/************************************************************************/
754
/*                          GetGeoTransform()                           */
755
/************************************************************************/
756
757
CPLErr PDS4Dataset::GetGeoTransform(GDALGeoTransform &gt) const
758
759
2
{
760
2
    if (m_bGotTransform)
761
2
    {
762
2
        gt = m_gt;
763
2
        return CE_None;
764
2
    }
765
766
0
    return GDALPamDataset::GetGeoTransform(gt);
767
2
}
768
769
/************************************************************************/
770
/*                          SetGeoTransform()                           */
771
/************************************************************************/
772
773
CPLErr PDS4Dataset::SetGeoTransform(const GDALGeoTransform &gt)
774
775
11
{
776
11
    if (!((gt.xscale > 0.0 && gt.xrot == 0.0 && gt.yrot == 0.0 &&
777
10
           gt.yscale < 0.0) ||
778
8
          (gt.xscale == 0.0 && gt.xrot > 0.0 && gt.yrot > 0.0 &&
779
0
           gt.yscale == 0.0)))
780
8
    {
781
8
        CPLError(CE_Failure, CPLE_NotSupported,
782
8
                 "Only north-up geotransform or map_projection_rotation=90 "
783
8
                 "supported");
784
8
        return CE_Failure;
785
8
    }
786
3
    m_gt = gt;
787
3
    m_bGotTransform = true;
788
3
    if (m_poExternalDS)
789
0
        m_poExternalDS->SetGeoTransform(m_gt);
790
3
    return CE_None;
791
11
}
792
793
/************************************************************************/
794
/*                            SetMetadata()                             */
795
/************************************************************************/
796
797
CPLErr PDS4Dataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
798
0
{
799
0
    if (m_bUseSrcLabel && eAccess == GA_Update && pszDomain != nullptr &&
800
0
        EQUAL(pszDomain, "xml:PDS4"))
801
0
    {
802
0
        if (papszMD != nullptr && papszMD[0] != nullptr)
803
0
        {
804
0
            m_osXMLPDS4 = papszMD[0];
805
0
        }
806
0
        return CE_None;
807
0
    }
808
0
    return GDALPamDataset::SetMetadata(papszMD, pszDomain);
809
0
}
810
811
/************************************************************************/
812
/*                            GetFileList()                             */
813
/************************************************************************/
814
815
char **PDS4Dataset::GetFileList()
816
364
{
817
364
    char **papszFileList = GDALPamDataset::GetFileList();
818
364
    if (!m_osXMLFilename.empty() &&
819
364
        CSLFindString(papszFileList, m_osXMLFilename) < 0)
820
0
    {
821
0
        papszFileList = CSLAddString(papszFileList, m_osXMLFilename);
822
0
    }
823
364
    if (!m_osImageFilename.empty())
824
0
    {
825
0
        papszFileList = CSLAddString(papszFileList, m_osImageFilename);
826
0
    }
827
364
    for (const auto &poLayer : m_apoLayers)
828
2.50k
    {
829
2.50k
        auto papszTemp = poLayer->GetFileList();
830
2.50k
        papszFileList = CSLInsertStrings(papszFileList, -1, papszTemp);
831
2.50k
        CSLDestroy(papszTemp);
832
2.50k
    }
833
364
    return papszFileList;
834
364
}
835
836
/************************************************************************/
837
/*                           GetLinearValue()                           */
838
/************************************************************************/
839
840
static const struct
841
{
842
    const char *pszUnit;
843
    double dfToMeter;
844
} apsLinearUnits[] = {
845
    {"AU", 149597870700.0}, {"Angstrom", 1e-10}, {"cm", 1e-2}, {"km", 1e3},
846
    {"micrometer", 1e-6},   {"mm", 1e-3},        {"nm", 1e-9}};
847
848
static double GetLinearValue(const CPLXMLNode *psParent,
849
                             const char *pszElementName)
850
555
{
851
555
    const CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
852
555
    if (psNode == nullptr)
853
0
        return 0.0;
854
555
    double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
855
555
    const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
856
555
    if (pszUnit && !EQUAL(pszUnit, "m"))
857
8
    {
858
8
        bool bFound = false;
859
40
        for (size_t i = 0; i < CPL_ARRAYSIZE(apsLinearUnits); i++)
860
38
        {
861
38
            if (EQUAL(pszUnit, apsLinearUnits[i].pszUnit))
862
6
            {
863
6
                dfVal *= apsLinearUnits[i].dfToMeter;
864
6
                bFound = true;
865
6
                break;
866
6
            }
867
38
        }
868
8
        if (!bFound)
869
2
        {
870
2
            CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
871
2
                     pszUnit, pszElementName);
872
2
        }
873
8
    }
874
555
    return dfVal;
875
555
}
876
877
/************************************************************************/
878
/*                         GetResolutionValue()                         */
879
/************************************************************************/
880
881
static const struct
882
{
883
    const char *pszUnit;
884
    double dfToMeter;
885
} apsResolutionUnits[] = {
886
    {"km/pixel", 1e3},
887
    {"mm/pixel", 1e-3},
888
};
889
890
static double GetResolutionValue(CPLXMLNode *psParent,
891
                                 const char *pszElementName)
892
12
{
893
12
    CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
894
12
    if (psNode == nullptr)
895
0
        return 0.0;
896
12
    double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
897
12
    const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
898
12
    if (pszUnit && !EQUAL(pszUnit, "m/pixel"))
899
6
    {
900
6
        bool bFound = false;
901
6
        for (size_t i = 0; i < CPL_ARRAYSIZE(apsResolutionUnits); i++)
902
6
        {
903
6
            if (EQUAL(pszUnit, apsResolutionUnits[i].pszUnit))
904
6
            {
905
6
                dfVal *= apsResolutionUnits[i].dfToMeter;
906
6
                bFound = true;
907
6
                break;
908
6
            }
909
6
        }
910
6
        if (!bFound)
911
0
        {
912
0
            CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
913
0
                     pszUnit, pszElementName);
914
0
        }
915
6
    }
916
12
    return dfVal;
917
12
}
918
919
/************************************************************************/
920
/*                          GetAngularValue()                           */
921
/************************************************************************/
922
923
static const struct
924
{
925
    const char *pszUnit;
926
    double dfToDeg;
927
} apsAngularUnits[] = {{"arcmin", 1. / 60.},
928
                       {"arcsec", 1. / 3600},
929
                       {"hr", 15.0},
930
                       {"mrad", 180.0 / M_PI / 1000.},
931
                       {"rad", 180.0 / M_PI}};
932
933
static double GetAngularValue(CPLXMLNode *psParent, const char *pszElementName,
934
                              bool *pbGotVal = nullptr)
935
810
{
936
810
    CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
937
810
    if (psNode == nullptr)
938
314
    {
939
314
        if (pbGotVal)
940
152
            *pbGotVal = false;
941
314
        return 0.0;
942
314
    }
943
496
    double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
944
496
    const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
945
496
    if (pszUnit && !EQUAL(pszUnit, "deg"))
946
0
    {
947
0
        bool bFound = false;
948
0
        for (size_t i = 0; i < CPL_ARRAYSIZE(apsAngularUnits); i++)
949
0
        {
950
0
            if (EQUAL(pszUnit, apsAngularUnits[i].pszUnit))
951
0
            {
952
0
                dfVal *= apsAngularUnits[i].dfToDeg;
953
0
                bFound = true;
954
0
                break;
955
0
            }
956
0
        }
957
0
        if (!bFound)
958
0
        {
959
0
            CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
960
0
                     pszUnit, pszElementName);
961
0
        }
962
0
    }
963
496
    if (pbGotVal)
964
334
        *pbGotVal = true;
965
496
    return dfVal;
966
810
}
967
968
/************************************************************************/
969
/*                         ReadGeoreferencing()                         */
970
/************************************************************************/
971
972
// See https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1G00_1950.xsd, (GDAL 3.4)
973
//     https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1D00_1933.xsd,
974
//     https://raw.githubusercontent.com/nasa-pds-data-dictionaries/ldd-cart/master/build/1.B.0.0/PDS4_CART_1B00.xsd,
975
//     https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.xsd
976
// and the corresponding .sch files
977
void PDS4Dataset::ReadGeoreferencing(CPLXMLNode *psProduct)
978
695
{
979
695
    CPLXMLNode *psCart = CPLGetXMLNode(
980
695
        psProduct, "Observation_Area.Discipline_Area.Cartography");
981
695
    if (psCart == nullptr)
982
514
    {
983
514
        CPLDebug("PDS4",
984
514
                 "Did not find Observation_Area.Discipline_Area.Cartography");
985
514
        return;
986
514
    }
987
988
    // Bounding box: informative only
989
181
    CPLXMLNode *psBounding =
990
181
        CPLGetXMLNode(psCart, "Spatial_Domain.Bounding_Coordinates");
991
181
    if (psBounding)
992
181
    {
993
181
        const char *pszWest =
994
181
            CPLGetXMLValue(psBounding, "west_bounding_coordinate", nullptr);
995
181
        const char *pszEast =
996
181
            CPLGetXMLValue(psBounding, "east_bounding_coordinate", nullptr);
997
181
        const char *pszNorth =
998
181
            CPLGetXMLValue(psBounding, "north_bounding_coordinate", nullptr);
999
181
        const char *pszSouth =
1000
181
            CPLGetXMLValue(psBounding, "south_bounding_coordinate", nullptr);
1001
181
        if (pszWest)
1002
181
            CPLDebug("PDS4", "West: %s", pszWest);
1003
181
        if (pszEast)
1004
181
            CPLDebug("PDS4", "East: %s", pszEast);
1005
181
        if (pszNorth)
1006
181
            CPLDebug("PDS4", "North: %s", pszNorth);
1007
181
        if (pszSouth)
1008
181
            CPLDebug("PDS4", "South: %s", pszSouth);
1009
181
    }
1010
1011
181
    CPLXMLNode *psSR =
1012
181
        CPLGetXMLNode(psCart, "Spatial_Reference_Information.Horizontal_"
1013
181
                              "Coordinate_System_Definition");
1014
181
    if (psSR == nullptr)
1015
0
    {
1016
0
        CPLDebug("PDS4", "Did not find Spatial_Reference_Information."
1017
0
                         "Horizontal_Coordinate_System_Definition");
1018
0
        return;
1019
0
    }
1020
1021
181
    double dfLongitudeMultiplier = 1;
1022
181
    const CPLXMLNode *psGeodeticModel = CPLGetXMLNode(psSR, "Geodetic_Model");
1023
181
    if (psGeodeticModel != nullptr)
1024
181
    {
1025
181
        if (EQUAL(CPLGetXMLValue(psGeodeticModel, "longitude_direction", ""),
1026
181
                  "Positive West"))
1027
0
        {
1028
0
            dfLongitudeMultiplier = -1;
1029
0
        }
1030
181
    }
1031
1032
181
    OGRSpatialReference oSRS;
1033
181
    CPLXMLNode *psGridCoordinateSystem =
1034
181
        CPLGetXMLNode(psSR, "Planar.Grid_Coordinate_System");
1035
181
    CPLXMLNode *psMapProjection = CPLGetXMLNode(psSR, "Planar.Map_Projection");
1036
181
    CPLString osProjName;
1037
181
    double dfCenterLon = 0.0;
1038
181
    double dfCenterLat = 0.0;
1039
181
    double dfStdParallel1 = 0.0;
1040
181
    double dfStdParallel2 = 0.0;
1041
181
    double dfScale = 1.0;
1042
181
    double dfMapProjectionRotation = 0.0;
1043
181
    if (psGridCoordinateSystem != nullptr)
1044
0
    {
1045
0
        osProjName = CPLGetXMLValue(psGridCoordinateSystem,
1046
0
                                    "grid_coordinate_system_name", "");
1047
0
        if (!osProjName.empty())
1048
0
        {
1049
0
            if (osProjName == "Universal Transverse Mercator")
1050
0
            {
1051
0
                CPLXMLNode *psUTMZoneNumber = CPLGetXMLNode(
1052
0
                    psGridCoordinateSystem,
1053
0
                    "Universal_Transverse_Mercator.utm_zone_number");
1054
0
                if (psUTMZoneNumber)
1055
0
                {
1056
0
                    int nZone =
1057
0
                        atoi(CPLGetXMLValue(psUTMZoneNumber, nullptr, ""));
1058
0
                    oSRS.SetUTM(std::abs(nZone), nZone >= 0);
1059
0
                }
1060
0
            }
1061
0
            else if (osProjName == "Universal Polar Stereographic")
1062
0
            {
1063
0
                CPLXMLNode *psProjParamNode = CPLGetXMLNode(
1064
0
                    psGridCoordinateSystem,
1065
0
                    "Universal_Polar_Stereographic.Polar_Stereographic");
1066
0
                if (psProjParamNode)
1067
0
                {
1068
0
                    dfCenterLon =
1069
0
                        GetAngularValue(psProjParamNode,
1070
0
                                        "longitude_of_central_meridian") *
1071
0
                        dfLongitudeMultiplier;
1072
0
                    dfCenterLat = GetAngularValue(
1073
0
                        psProjParamNode, "latitude_of_projection_origin");
1074
0
                    dfScale = CPLAtof(CPLGetXMLValue(
1075
0
                        psProjParamNode, "scale_factor_at_projection_origin",
1076
0
                        "1"));
1077
0
                    oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1078
0
                }
1079
0
            }
1080
0
            else
1081
0
            {
1082
0
                CPLError(CE_Warning, CPLE_NotSupported,
1083
0
                         "grid_coordinate_system_name = %s not supported",
1084
0
                         osProjName.c_str());
1085
0
            }
1086
0
        }
1087
0
    }
1088
181
    else if (psMapProjection != nullptr)
1089
174
    {
1090
174
        osProjName = CPLGetXMLValue(psMapProjection, "map_projection_name", "");
1091
174
        if (!osProjName.empty())
1092
163
        {
1093
163
            CPLXMLNode *psProjParamNode = CPLGetXMLNode(
1094
163
                psMapProjection,
1095
163
                CPLString(osProjName).replaceAll(' ', '_').c_str());
1096
163
            if (psProjParamNode == nullptr &&
1097
                // typo in https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.sch
1098
1
                EQUAL(osProjName, "Orothographic"))
1099
0
            {
1100
0
                psProjParamNode =
1101
0
                    CPLGetXMLNode(psMapProjection, "Orthographic");
1102
0
            }
1103
163
            bool bGotStdParallel1 = false;
1104
163
            bool bGotStdParallel2 = false;
1105
163
            bool bGotScale = false;
1106
163
            if (psProjParamNode)
1107
162
            {
1108
162
                bool bGotCenterLon = false;
1109
162
                dfCenterLon = GetAngularValue(psProjParamNode,
1110
162
                                              "longitude_of_central_meridian",
1111
162
                                              &bGotCenterLon) *
1112
162
                              dfLongitudeMultiplier;
1113
162
                if (!bGotCenterLon)
1114
0
                {
1115
0
                    dfCenterLon =
1116
0
                        GetAngularValue(psProjParamNode,
1117
0
                                        "straight_vertical_longitude_from_pole",
1118
0
                                        &bGotCenterLon) *
1119
0
                        dfLongitudeMultiplier;
1120
0
                }
1121
162
                dfCenterLat = GetAngularValue(psProjParamNode,
1122
162
                                              "latitude_of_projection_origin");
1123
162
                dfStdParallel1 = GetAngularValue(
1124
162
                    psProjParamNode, "standard_parallel_1", &bGotStdParallel1);
1125
162
                dfStdParallel2 = GetAngularValue(
1126
162
                    psProjParamNode, "standard_parallel_2", &bGotStdParallel2);
1127
162
                const char *pszScaleParam =
1128
162
                    (osProjName == "Transverse Mercator")
1129
162
                        ? "scale_factor_at_central_meridian"
1130
162
                        : "scale_factor_at_projection_origin";
1131
162
                const char *pszScaleVal =
1132
162
                    CPLGetXMLValue(psProjParamNode, pszScaleParam, nullptr);
1133
162
                bGotScale = pszScaleVal != nullptr;
1134
162
                dfScale = (pszScaleVal) ? CPLAtof(pszScaleVal) : 1.0;
1135
1136
162
                dfMapProjectionRotation =
1137
162
                    GetAngularValue(psProjParamNode, "map_projection_rotation");
1138
162
            }
1139
1140
163
            CPLXMLNode *psObliqueAzimuth =
1141
163
                CPLGetXMLNode(psProjParamNode, "Oblique_Line_Azimuth");
1142
163
            CPLXMLNode *psObliquePoint =
1143
163
                CPLGetXMLNode(psProjParamNode, "Oblique_Line_Point");
1144
1145
163
            if (EQUAL(osProjName, "Equirectangular"))
1146
0
            {
1147
0
                oSRS.SetEquirectangular2(dfCenterLat, dfCenterLon,
1148
0
                                         dfStdParallel1, 0, 0);
1149
0
            }
1150
163
            else if (EQUAL(osProjName, "Lambert Conformal Conic"))
1151
87
            {
1152
87
                if (bGotScale)
1153
2
                {
1154
2
                    if ((bGotStdParallel1 && dfStdParallel1 != dfCenterLat) ||
1155
2
                        (bGotStdParallel2 && dfStdParallel2 != dfCenterLat))
1156
0
                    {
1157
0
                        CPLError(
1158
0
                            CE_Warning, CPLE_AppDefined,
1159
0
                            "Ignoring standard_parallel_1 and/or "
1160
0
                            "standard_parallel_2 with LCC_1SP formulation");
1161
0
                    }
1162
2
                    oSRS.SetLCC1SP(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1163
2
                }
1164
85
                else
1165
85
                {
1166
85
                    oSRS.SetLCC(dfStdParallel1, dfStdParallel2, dfCenterLat,
1167
85
                                dfCenterLon, 0, 0);
1168
85
                }
1169
87
            }
1170
76
            else if (EQUAL(osProjName, "Mercator"))
1171
0
            {
1172
0
                if (bGotScale)
1173
0
                {
1174
0
                    oSRS.SetMercator(dfCenterLat,  // should be 0 normally
1175
0
                                     dfCenterLon, dfScale, 0.0, 0.0);
1176
0
                }
1177
0
                else
1178
0
                {
1179
0
                    oSRS.SetMercator2SP(dfStdParallel1,
1180
0
                                        dfCenterLat,  // should be 0 normally
1181
0
                                        dfCenterLon, 0.0, 0.0);
1182
0
                }
1183
0
            }
1184
76
            else if (EQUAL(osProjName, "Orthographic"))
1185
0
            {
1186
0
                oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0.0, 0.0);
1187
0
            }
1188
76
            else if (EQUAL(osProjName, "Oblique Mercator") &&
1189
0
                     (psObliqueAzimuth != nullptr || psObliquePoint != nullptr))
1190
0
            {
1191
0
                if (psObliqueAzimuth)
1192
0
                {
1193
                    // Not sure of this
1194
0
                    dfCenterLon = CPLAtof(
1195
0
                        CPLGetXMLValue(psObliqueAzimuth,
1196
0
                                       "azimuth_measure_point_longitude", "0"));
1197
1198
0
                    double dfAzimuth = CPLAtof(CPLGetXMLValue(
1199
0
                        psObliqueAzimuth, "azimuthal_angle", "0"));
1200
0
                    oSRS.SetProjection(
1201
0
                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER);
1202
0
                    oSRS.SetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,
1203
0
                                         dfCenterLat);
1204
0
                    oSRS.SetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,
1205
0
                                         dfCenterLon);
1206
0
                    oSRS.SetNormProjParm(SRS_PP_AZIMUTH, dfAzimuth);
1207
                    // SetNormProjParm( SRS_PP_RECTIFIED_GRID_ANGLE,
1208
                    // dfRectToSkew );
1209
0
                    oSRS.SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
1210
0
                    oSRS.SetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
1211
0
                    oSRS.SetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
1212
0
                }
1213
0
                else
1214
0
                {
1215
0
                    double dfLat1 = 0.0;
1216
0
                    double dfLong1 = 0.0;
1217
0
                    double dfLat2 = 0.0;
1218
0
                    double dfLong2 = 0.0;
1219
0
                    CPLXMLNode *psPoint = CPLGetXMLNode(
1220
0
                        psObliquePoint, "Oblique_Line_Point_Group");
1221
0
                    if (psPoint)
1222
0
                    {
1223
0
                        dfLat1 = CPLAtof(CPLGetXMLValue(
1224
0
                            psPoint, "oblique_line_latitude", "0.0"));
1225
0
                        dfLong1 = CPLAtof(CPLGetXMLValue(
1226
0
                            psPoint, "oblique_line_longitude", "0.0"));
1227
0
                        psPoint = psPoint->psNext;
1228
0
                        if (psPoint && psPoint->eType == CXT_Element &&
1229
0
                            EQUAL(psPoint->pszValue,
1230
0
                                  "Oblique_Line_Point_Group"))
1231
0
                        {
1232
0
                            dfLat2 = CPLAtof(CPLGetXMLValue(
1233
0
                                psPoint, "oblique_line_latitude", "0.0"));
1234
0
                            dfLong2 = CPLAtof(CPLGetXMLValue(
1235
0
                                psPoint, "oblique_line_longitude", "0.0"));
1236
0
                        }
1237
0
                    }
1238
0
                    oSRS.SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
1239
0
                                    dfLong2, dfScale, 0.0, 0.0);
1240
0
                }
1241
0
            }
1242
76
            else if (EQUAL(osProjName, "Polar Stereographic"))
1243
0
            {
1244
0
                oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1245
0
            }
1246
76
            else if (EQUAL(osProjName, "Polyconic"))
1247
0
            {
1248
0
                oSRS.SetPolyconic(dfCenterLat, dfCenterLon, 0, 0);
1249
0
            }
1250
76
            else if (EQUAL(osProjName, "Sinusoidal"))
1251
0
            {
1252
0
                oSRS.SetSinusoidal(dfCenterLon, 0, 0);
1253
0
            }
1254
76
            else if (EQUAL(osProjName, "Transverse Mercator"))
1255
75
            {
1256
75
                oSRS.SetTM(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1257
75
            }
1258
1259
            // Below values are valid map_projection_name according to
1260
            // the schematron but they don't have a dedicated element to
1261
            // hold the projection parameter. Assumed the schema is extended
1262
            // similarly to the existing for a few obvious ones
1263
1
            else if (EQUAL(osProjName, "Albers Conical Equal Area"))
1264
0
            {
1265
0
                oSRS.SetACEA(dfStdParallel1, dfStdParallel2, dfCenterLat,
1266
0
                             dfCenterLon, 0.0, 0.0);
1267
0
            }
1268
1
            else if (EQUAL(osProjName, "Azimuthal Equidistant"))
1269
0
            {
1270
0
                oSRS.SetAE(dfCenterLat, dfCenterLon, 0, 0);
1271
0
            }
1272
1
            else if (EQUAL(osProjName, "Equidistant Conic"))
1273
0
            {
1274
0
                oSRS.SetEC(dfStdParallel1, dfStdParallel2, dfCenterLat,
1275
0
                           dfCenterLon, 0.0, 0.0);
1276
0
            }
1277
            // Unhandled: General Vertical Near-sided Projection
1278
1
            else if (EQUAL(osProjName, "Gnomonic"))
1279
0
            {
1280
0
                oSRS.SetGnomonic(dfCenterLat, dfCenterLon, 0, 0);
1281
0
            }
1282
1
            else if (EQUAL(osProjName, "Lambert Azimuthal Equal Area"))
1283
0
            {
1284
0
                oSRS.SetLAEA(dfCenterLat, dfCenterLon, 0, 0);
1285
0
            }
1286
1
            else if (EQUAL(osProjName, "Miller Cylindrical"))
1287
0
            {
1288
0
                oSRS.SetMC(dfCenterLat, dfCenterLon, 0, 0);
1289
0
            }
1290
1
            else if (EQUAL(osProjName, "Orothographic")  // typo
1291
1
                     || EQUAL(osProjName, "Orthographic"))
1292
0
            {
1293
0
                osProjName = "Orthographic";
1294
0
                oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0, 0);
1295
0
            }
1296
1
            else if (EQUAL(osProjName, "Robinson"))
1297
0
            {
1298
0
                oSRS.SetRobinson(dfCenterLon, 0, 0);
1299
0
            }
1300
            // Unhandled: Space Oblique Mercator
1301
1
            else if (EQUAL(osProjName, "Stereographic"))
1302
0
            {
1303
0
                oSRS.SetStereographic(dfCenterLat, dfCenterLon, dfScale, 0, 0);
1304
0
            }
1305
1
            else if (EQUAL(osProjName, "van der Grinten"))
1306
0
            {
1307
0
                oSRS.SetVDG(dfCenterLon, 0, 0);
1308
0
            }
1309
1
            else if (EQUAL(osProjName, "Oblique Cylindrical"))
1310
0
            {
1311
0
                const double poleLatitude = GetAngularValue(
1312
0
                    psProjParamNode, "oblique_proj_pole_latitude");
1313
0
                const double poleLongitude =
1314
0
                    GetAngularValue(psProjParamNode,
1315
0
                                    "oblique_proj_pole_longitude") *
1316
0
                    dfLongitudeMultiplier;
1317
0
                const double poleRotation = GetAngularValue(
1318
0
                    psProjParamNode, "oblique_proj_pole_rotation");
1319
1320
0
                CPLString oProj4String;
1321
                // Cf isis3dataset.cpp comments for ObliqueCylindrical
1322
0
                oProj4String.Printf("+proj=ob_tran +o_proj=eqc +o_lon_p=%.17g "
1323
0
                                    "+o_lat_p=%.17g +lon_0=%.17g",
1324
0
                                    -poleRotation, 180 - poleLatitude,
1325
0
                                    poleLongitude);
1326
0
                oSRS.SetFromUserInput(oProj4String);
1327
0
            }
1328
1
            else
1329
1
            {
1330
1
                CPLError(CE_Warning, CPLE_NotSupported,
1331
1
                         "map_projection_name = %s not supported",
1332
1
                         osProjName.c_str());
1333
1
            }
1334
163
        }
1335
174
    }
1336
7
    else
1337
7
    {
1338
7
        CPLXMLNode *psGeographic = CPLGetXMLNode(psSR, "Geographic");
1339
7
        if (GetLayerCount() && psGeographic)
1340
4
        {
1341
            // do nothing
1342
4
        }
1343
3
        else
1344
3
        {
1345
3
            CPLError(CE_Warning, CPLE_AppDefined,
1346
3
                     "Planar.Map_Projection not found");
1347
3
        }
1348
7
    }
1349
1350
181
    if (oSRS.IsProjected())
1351
162
    {
1352
162
        oSRS.SetLinearUnits("Metre", 1.0);
1353
162
    }
1354
1355
181
    if (psGeodeticModel != nullptr)
1356
181
    {
1357
181
        const char *pszLatitudeType =
1358
181
            CPLGetXMLValue(psGeodeticModel, "latitude_type", "");
1359
181
        bool bIsOgraphic = EQUAL(pszLatitudeType, "Planetographic");
1360
1361
181
        const bool bUseLDD1930RadiusNames =
1362
181
            CPLGetXMLNode(psGeodeticModel, "a_axis_radius") != nullptr;
1363
1364
        // Before PDS CART schema pre-1.B.10.0 (pre LDD version 1.9.3.0),
1365
        // the confusing semi_major_radius, semi_minor_radius and polar_radius
1366
        // were used but did not follow the recommended
1367
        // FGDC names. Using both "semi" and "radius" in the same keyword,
1368
        // which both mean half, does not make sense.
1369
181
        const char *pszAAxis =
1370
181
            bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius";
1371
181
        const char *pszBAxis =
1372
181
            bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius";
1373
181
        const char *pszCAxis =
1374
181
            bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius";
1375
1376
181
        const double dfSemiMajor = GetLinearValue(psGeodeticModel, pszAAxis);
1377
1378
        // a_axis_radius and b_axis_radius should be the same in most cases
1379
        // unless a triaxial body is being defined. This should be extremely
1380
        // rare (and not used) since the IAU generally defines a best-fit sphere
1381
        // for triaxial bodies: https://astrogeology.usgs.gov/groups/IAU-WGCCRE
1382
181
        const double dfBValue = GetLinearValue(psGeodeticModel, pszBAxis);
1383
181
        if (dfSemiMajor != dfBValue)
1384
0
        {
1385
0
            CPLError(CE_Warning, CPLE_AppDefined,
1386
0
                     "%s = %f m, different from "
1387
0
                     "%s = %f, will be ignored",
1388
0
                     pszBAxis, dfBValue, pszAAxis, dfSemiMajor);
1389
0
        }
1390
1391
181
        const double dfPolarRadius = GetLinearValue(psGeodeticModel, pszCAxis);
1392
        // Use the polar_radius as the actual semi minor
1393
181
        const double dfSemiMinor = dfPolarRadius;
1394
1395
        // Compulsory
1396
181
        const char *pszTargetName = CPLGetXMLValue(
1397
181
            psProduct, "Observation_Area.Target_Identification.name",
1398
181
            "unknown");
1399
1400
181
        if (oSRS.IsProjected())
1401
162
        {
1402
162
            CPLString osProjTargetName = osProjName + " " + pszTargetName;
1403
162
            oSRS.SetProjCS(osProjTargetName);
1404
162
        }
1405
1406
181
        CPLString osGeogName = CPLString("GCS_") + pszTargetName;
1407
1408
181
        CPLString osSphereName =
1409
181
            CPLGetXMLValue(psGeodeticModel, "spheroid_name", pszTargetName);
1410
181
        CPLString osDatumName = "D_" + osSphereName;
1411
1412
        // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
1413
181
        double dfInvFlattening = 0;
1414
181
        if ((dfSemiMajor - dfSemiMinor) >= 0.00000001)
1415
181
        {
1416
181
            dfInvFlattening = dfSemiMajor / (dfSemiMajor - dfSemiMinor);
1417
181
        }
1418
1419
        //(if stereographic with center lat ==90) or (polar stereographic )
1420
181
        if ((EQUAL(osProjName, "STEREOGRAPHIC") && fabs(dfCenterLat) == 90) ||
1421
181
            (EQUAL(osProjName, "POLAR STEREOGRAPHIC")))
1422
0
        {
1423
0
            if (bIsOgraphic)
1424
0
            {
1425
0
                oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1426
0
                               dfSemiMajor, dfInvFlattening,
1427
0
                               "Reference_Meridian", 0.0);
1428
0
            }
1429
0
            else
1430
0
            {
1431
0
                osSphereName += "_polarRadius";
1432
0
                oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1433
0
                               dfPolarRadius, 0.0, "Reference_Meridian", 0.0);
1434
0
            }
1435
0
        }
1436
181
        else if ((EQUAL(osProjName, "EQUIRECTANGULAR")) ||
1437
181
                 (EQUAL(osProjName, "ORTHOGRAPHIC")) ||
1438
181
                 (EQUAL(osProjName, "STEREOGRAPHIC")) ||
1439
181
                 (EQUAL(osProjName, "SINUSOIDAL")))
1440
0
        {
1441
0
            oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName, dfSemiMajor,
1442
0
                           0.0, "Reference_Meridian", 0.0);
1443
0
        }
1444
181
        else
1445
181
        {
1446
181
            if (bIsOgraphic)
1447
0
            {
1448
0
                oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1449
0
                               dfSemiMajor, dfInvFlattening,
1450
0
                               "Reference_Meridian", 0.0);
1451
0
            }
1452
181
            else
1453
181
            {
1454
181
                oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
1455
181
                               dfSemiMajor, 0.0, "Reference_Meridian", 0.0);
1456
181
            }
1457
181
        }
1458
181
    }
1459
1460
181
    CPLXMLNode *psPCI =
1461
181
        CPLGetXMLNode(psSR, "Planar.Planar_Coordinate_Information");
1462
181
    CPLXMLNode *psGT = CPLGetXMLNode(psSR, "Planar.Geo_Transformation");
1463
181
    if (psPCI && psGT)
1464
6
    {
1465
6
        const char *pszPCIEncoding =
1466
6
            CPLGetXMLValue(psPCI, "planar_coordinate_encoding_method", "");
1467
6
        CPLXMLNode *psCR = CPLGetXMLNode(psPCI, "Coordinate_Representation");
1468
6
        if (!EQUAL(pszPCIEncoding, "Coordinate Pair"))
1469
0
        {
1470
0
            CPLError(CE_Warning, CPLE_NotSupported,
1471
0
                     "planar_coordinate_encoding_method = %s not supported",
1472
0
                     pszPCIEncoding);
1473
0
        }
1474
6
        else if (psCR != nullptr)
1475
6
        {
1476
6
            double dfXRes = GetResolutionValue(psCR, "pixel_resolution_x");
1477
6
            double dfYRes = GetResolutionValue(psCR, "pixel_resolution_y");
1478
6
            double dfULX = GetLinearValue(psGT, "upperleft_corner_x");
1479
6
            double dfULY = GetLinearValue(psGT, "upperleft_corner_y");
1480
1481
            // The PDS4 specification is not really clear about the
1482
            // origin convention, but it appears from
1483
            // https://github.com/OSGeo/gdal/issues/735 that it matches GDAL
1484
            // top-left corner of top-left pixel
1485
6
            m_gt.xorig = dfULX;
1486
6
            m_gt.xscale = dfXRes;
1487
6
            m_gt.xrot = 0.0;
1488
6
            m_gt.yorig = dfULY;
1489
6
            m_gt.yrot = 0.0;
1490
6
            m_gt.yscale = -dfYRes;
1491
6
            m_bGotTransform = true;
1492
1493
6
            if (dfMapProjectionRotation != 0)
1494
0
            {
1495
0
                const double sin_rot =
1496
0
                    dfMapProjectionRotation == 90
1497
0
                        ? 1.0
1498
0
                        : sin(dfMapProjectionRotation / 180 * M_PI);
1499
0
                const double cos_rot =
1500
0
                    dfMapProjectionRotation == 90
1501
0
                        ? 0.0
1502
0
                        : cos(dfMapProjectionRotation / 180 * M_PI);
1503
0
                const double gt_1 = cos_rot * m_gt.xscale - sin_rot * m_gt.yrot;
1504
0
                const double gt_2 = cos_rot * m_gt.xrot - sin_rot * m_gt.yscale;
1505
0
                const double gt_0 = cos_rot * m_gt.xorig - sin_rot * m_gt.yorig;
1506
0
                const double gt_4 = sin_rot * m_gt.xscale + cos_rot * m_gt.yrot;
1507
0
                const double gt_5 = sin_rot * m_gt.xrot + cos_rot * m_gt.yscale;
1508
0
                const double gt_3 = sin_rot * m_gt.xorig + cos_rot * m_gt.yorig;
1509
0
                m_gt.xscale = gt_1;
1510
0
                m_gt.xrot = gt_2;
1511
0
                m_gt.xorig = gt_0;
1512
0
                m_gt.yrot = gt_4;
1513
0
                m_gt.yscale = gt_5;
1514
0
                m_gt.yorig = gt_3;
1515
0
            }
1516
6
        }
1517
6
    }
1518
1519
181
    if (!oSRS.IsEmpty())
1520
181
    {
1521
181
        if (GetRasterCount())
1522
0
        {
1523
0
            m_oSRS = std::move(oSRS);
1524
0
        }
1525
181
        else if (GetLayerCount())
1526
160
        {
1527
160
            for (auto &poLayer : m_apoLayers)
1528
880
            {
1529
880
                if (poLayer->GetGeomType() != wkbNone)
1530
382
                {
1531
382
                    auto poSRSClone = oSRS.Clone();
1532
382
                    poLayer->SetSpatialRef(poSRSClone);
1533
382
                    poSRSClone->Release();
1534
382
                }
1535
880
            }
1536
160
        }
1537
181
    }
1538
181
}
1539
1540
/************************************************************************/
1541
/*                              GetLayer()                              */
1542
/************************************************************************/
1543
1544
const OGRLayer *PDS4Dataset::GetLayer(int nIndex) const
1545
55.9k
{
1546
55.9k
    if (nIndex < 0 || nIndex >= GetLayerCount())
1547
0
        return nullptr;
1548
55.9k
    return dynamic_cast<const OGRLayer *>(m_apoLayers[nIndex].get());
1549
55.9k
}
1550
1551
/************************************************************************/
1552
/*                         FixupTableFilename()                         */
1553
/************************************************************************/
1554
1555
static std::string FixupTableFilename(const std::string &osFilename)
1556
5.54k
{
1557
5.54k
    VSIStatBufL sStat;
1558
5.54k
    if (VSIStatL(osFilename.c_str(), &sStat) == 0)
1559
5.12k
    {
1560
5.12k
        return osFilename;
1561
5.12k
    }
1562
418
    const std::string osExt = CPLGetExtensionSafe(osFilename.c_str());
1563
418
    if (!osExt.empty())
1564
418
    {
1565
418
        std::string osTry(osFilename);
1566
418
        if (osExt[0] >= 'a' && osExt[0] <= 'z')
1567
418
        {
1568
418
            osTry = CPLResetExtensionSafe(osFilename.c_str(),
1569
418
                                          CPLString(osExt).toupper());
1570
418
        }
1571
0
        else
1572
0
        {
1573
0
            osTry = CPLResetExtensionSafe(osFilename.c_str(),
1574
0
                                          CPLString(osExt).tolower());
1575
0
        }
1576
418
        if (VSIStatL(osTry.c_str(), &sStat) == 0)
1577
0
        {
1578
0
            return osTry;
1579
0
        }
1580
418
    }
1581
418
    return osFilename;
1582
418
}
1583
1584
/************************************************************************/
1585
/*                         OpenTableCharacter()                         */
1586
/************************************************************************/
1587
1588
bool PDS4Dataset::OpenTableCharacter(const char *pszFilename,
1589
                                     const CPLXMLNode *psTable)
1590
1
{
1591
1
    if (CPLHasPathTraversal(pszFilename))
1592
0
    {
1593
0
        CPLError(CE_Failure, CPLE_NotSupported,
1594
0
                 "OpenTableCharacter(): path traversal detected: %s",
1595
0
                 pszFilename);
1596
0
        return false;
1597
0
    }
1598
1
    std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1599
1
    if (cpl::starts_with(osLayerName,
1600
1
                         CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1601
0
        osLayerName = osLayerName.substr(
1602
0
            CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1603
1604
1
    const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1605
1
        CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1606
1
    auto poLayer = std::make_unique<PDS4TableCharacter>(
1607
1
        this, osLayerName.c_str(), osFullFilename.c_str(),
1608
1
        eAccess == GA_Update);
1609
1
    if (!poLayer->ReadTableDef(psTable))
1610
1
    {
1611
1
        return false;
1612
1
    }
1613
0
    if (eAccess == GA_Update)
1614
0
    {
1615
0
        m_apoLayers.push_back(
1616
0
            std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1617
0
    }
1618
0
    else
1619
0
    {
1620
0
        m_apoLayers.push_back(std::move(poLayer));
1621
0
    }
1622
0
    return true;
1623
1
}
1624
1625
/************************************************************************/
1626
/*                          OpenTableBinary()                           */
1627
/************************************************************************/
1628
1629
bool PDS4Dataset::OpenTableBinary(const char *pszFilename,
1630
                                  const CPLXMLNode *psTable)
1631
1
{
1632
1
    if (CPLHasPathTraversal(pszFilename))
1633
0
    {
1634
0
        CPLError(CE_Failure, CPLE_NotSupported,
1635
0
                 "OpenTableBinary(): path traversal detected: %s", pszFilename);
1636
0
        return false;
1637
0
    }
1638
1639
1
    std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1640
1
    if (cpl::starts_with(osLayerName,
1641
1
                         CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1642
0
        osLayerName = osLayerName.substr(
1643
0
            CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1644
1645
1
    const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1646
1
        CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1647
1
    auto poLayer = std::make_unique<PDS4TableBinary>(this, osLayerName.c_str(),
1648
1
                                                     osFullFilename.c_str(),
1649
1
                                                     eAccess == GA_Update);
1650
1
    if (!poLayer->ReadTableDef(psTable))
1651
1
    {
1652
1
        return false;
1653
1
    }
1654
0
    if (eAccess == GA_Update)
1655
0
    {
1656
0
        m_apoLayers.push_back(
1657
0
            std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1658
0
    }
1659
0
    else
1660
0
    {
1661
0
        m_apoLayers.push_back(std::move(poLayer));
1662
0
    }
1663
0
    return true;
1664
1
}
1665
1666
/************************************************************************/
1667
/*                         OpenTableDelimited()                         */
1668
/************************************************************************/
1669
1670
bool PDS4Dataset::OpenTableDelimited(const char *pszFilename,
1671
                                     const CPLXMLNode *psTable)
1672
5.54k
{
1673
5.54k
    if (CPLHasPathTraversal(pszFilename))
1674
0
    {
1675
0
        CPLError(CE_Failure, CPLE_NotSupported,
1676
0
                 "OpenTableDelimited(): path traversal detected: %s",
1677
0
                 pszFilename);
1678
0
        return false;
1679
0
    }
1680
1681
5.54k
    std::string osLayerName(CPLGetBasenameSafe(pszFilename));
1682
5.54k
    if (cpl::starts_with(osLayerName,
1683
5.54k
                         CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
1684
5.33k
        osLayerName = osLayerName.substr(
1685
5.33k
            CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
1686
1687
5.54k
    const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
1688
5.54k
        CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
1689
5.54k
    auto poLayer = std::make_unique<PDS4DelimitedTable>(
1690
5.54k
        this, osLayerName.c_str(), osFullFilename.c_str(),
1691
5.54k
        eAccess == GA_Update);
1692
5.54k
    if (!poLayer->ReadTableDef(psTable))
1693
530
    {
1694
530
        return false;
1695
530
    }
1696
5.01k
    if (eAccess == GA_Update)
1697
0
    {
1698
0
        m_apoLayers.push_back(
1699
0
            std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
1700
0
    }
1701
5.01k
    else
1702
5.01k
    {
1703
5.01k
        m_apoLayers.push_back(std::move(poLayer));
1704
5.01k
    }
1705
5.01k
    return true;
1706
5.54k
}
1707
1708
/************************************************************************/
1709
/*                          ConstantToDouble()                          */
1710
/************************************************************************/
1711
1712
static std::optional<double> ConstantToDouble(const char *pszItem,
1713
                                              const char *pszVal)
1714
54
{
1715
54
    if (STARTS_WITH(pszVal, "0x"))
1716
23
    {
1717
23
        if (strlen(pszVal) == strlen("0x") + 2 * sizeof(float))
1718
19
        {
1719
19
            char *endptr = nullptr;
1720
19
            const uint32_t nVal =
1721
19
                static_cast<uint32_t>(std::strtoull(pszVal, &endptr, 0));
1722
19
            if (endptr == pszVal + strlen(pszVal))
1723
19
            {
1724
19
                float fVal;
1725
19
                memcpy(&fVal, &nVal, sizeof(nVal));
1726
19
                return fVal;
1727
19
            }
1728
19
        }
1729
4
        else if (strlen(pszVal) == strlen("0x") + 2 * sizeof(double))
1730
4
        {
1731
4
            char *endptr = nullptr;
1732
4
            const uint64_t nVal =
1733
4
                static_cast<uint64_t>(std::strtoull(pszVal, &endptr, 0));
1734
4
            if (endptr == pszVal + strlen(pszVal))
1735
4
            {
1736
4
                double dfVal;
1737
4
                memcpy(&dfVal, &nVal, sizeof(nVal));
1738
4
                return dfVal;
1739
4
            }
1740
4
        }
1741
0
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for '%s': '%s'",
1742
0
                 pszItem, pszVal);
1743
0
        return std::nullopt;
1744
23
    }
1745
31
    else
1746
31
    {
1747
31
        char *endptr = nullptr;
1748
31
        double dfVal = std::strtod(pszVal, &endptr);
1749
31
        if (endptr == pszVal + strlen(pszVal))
1750
31
        {
1751
31
            return dfVal;
1752
31
        }
1753
0
        else
1754
0
        {
1755
0
            CPLError(CE_Failure, CPLE_AppDefined,
1756
0
                     "Invalid value for '%s': '%s'", pszItem, pszVal);
1757
0
            return std::nullopt;
1758
0
        }
1759
31
    }
1760
54
}
1761
1762
/************************************************************************/
1763
/*                             OpenBrowse()                             */
1764
/************************************************************************/
1765
1766
/* static */ std::unique_ptr<PDS4Dataset>
1767
PDS4Dataset::OpenBrowse(GDALOpenInfo *poOpenInfo, const CPLXMLNode *psProduct)
1768
4
{
1769
4
    const CPLXMLNode *psFileAreaBrowse =
1770
4
        CPLGetXMLNode(psProduct, "File_Area_Browse");
1771
4
    if (!psFileAreaBrowse)
1772
0
        return nullptr;
1773
4
    const char *pszFilename =
1774
4
        CPLGetXMLValue(psFileAreaBrowse, "File.file_name", nullptr);
1775
4
    if (!pszFilename)
1776
0
        return nullptr;
1777
4
    if (CPLHasPathTraversal(pszFilename))
1778
0
    {
1779
0
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
1780
0
                 pszFilename);
1781
0
        return nullptr;
1782
0
    }
1783
4
    const char *pszFormat = CPLGetXMLValue(
1784
4
        psFileAreaBrowse, "Encoded_Image.encoding_standard_id", "");
1785
4
    const char *const apszAllowedDrivers[] = {EQUAL(pszFormat, "TIFF") ? "GTiff"
1786
4
                                              : EQUAL(pszFormat, "PNG")
1787
2
                                                  ? "PNG"
1788
2
                                                  : nullptr,
1789
4
                                              nullptr};
1790
4
    if (apszAllowedDrivers[0] == nullptr)
1791
0
    {
1792
0
        CPLError(CE_Failure, CPLE_NotSupported,
1793
0
                 "Unhandled encoding_standard_id=%s", pszFormat);
1794
0
        return nullptr;
1795
0
    }
1796
4
    const std::string osImageFullFilename = CPLFormFilenameSafe(
1797
4
        CPLGetPathSafe(poOpenInfo->pszFilename).c_str(), pszFilename, nullptr);
1798
4
    auto poExternalDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
1799
4
        osImageFullFilename.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR,
1800
4
        apszAllowedDrivers));
1801
4
    if (!poExternalDS)
1802
4
        return nullptr;
1803
1804
0
    auto poDS = std::make_unique<PDS4Dataset>();
1805
0
    poDS->SetDescription(poOpenInfo->pszFilename);
1806
0
    poDS->m_poExternalDS = poExternalDS.release();
1807
0
    poDS->nRasterXSize = poDS->m_poExternalDS->GetRasterXSize();
1808
0
    poDS->nRasterYSize = poDS->m_poExternalDS->GetRasterYSize();
1809
0
    poDS->eAccess = GA_ReadOnly;
1810
0
    poDS->m_osImageFilename = osImageFullFilename;
1811
1812
0
    for (int i = 0; i < poDS->m_poExternalDS->GetRasterCount(); i++)
1813
0
    {
1814
0
        auto poBand = std::make_unique<PDS4BrowseImageProxyRasterBand>(
1815
0
            poDS->m_poExternalDS->GetRasterBand(i + 1));
1816
0
        poDS->SetBand(i + 1, std::move(poBand));
1817
0
    }
1818
1819
0
    if (poDS->m_poExternalDS->GetGeoTransform(poDS->m_gt) == CE_None)
1820
0
    {
1821
0
        poDS->m_bGotTransform = true;
1822
0
    }
1823
0
    const auto poSRS = poDS->m_poExternalDS->GetSpatialRef();
1824
0
    if (poSRS)
1825
0
        poDS->m_oSRS = *poSRS;
1826
1827
0
    for (const char *pszDomain : {GDAL_MDD_DEFAULT, GDAL_MDD_IMAGE_STRUCTURE})
1828
0
    {
1829
0
        poDS->GDALDataset::SetMetadata(
1830
0
            poDS->m_poExternalDS->GetMetadata(pszDomain), pszDomain);
1831
0
    }
1832
1833
    // Expose XML content in xml:PDS4 metadata domain
1834
0
    GByte *pabyRet = nullptr;
1835
0
    CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, poOpenInfo->pszFilename, &pabyRet,
1836
0
                                     nullptr, 10 * 1024 * 1024));
1837
0
    if (pabyRet)
1838
0
    {
1839
0
        char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
1840
0
        poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
1841
0
    }
1842
0
    VSIFree(pabyRet);
1843
1844
    /*--------------------------------------------------------------------------*/
1845
    /*  Initialize any PAM information */
1846
    /*--------------------------------------------------------------------------*/
1847
0
    poDS->SetDescription(poOpenInfo->pszFilename);
1848
0
    poDS->TryLoadXML();
1849
1850
0
    return poDS;
1851
4
}
1852
1853
/************************************************************************/
1854
/*                                Open()                                */
1855
/************************************************************************/
1856
1857
// See https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.xsd
1858
// and https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.sch
1859
std::unique_ptr<PDS4Dataset> PDS4Dataset::OpenInternal(GDALOpenInfo *poOpenInfo)
1860
1.46k
{
1861
1.46k
    if (!PDS4DriverIdentify(poOpenInfo))
1862
0
        return nullptr;
1863
1864
1.46k
    CPLString osXMLFilename(poOpenInfo->pszFilename);
1865
1.46k
    int nFAOIdxLookup = -1;
1866
1.46k
    int nArrayIdxLookup = -1;
1867
1.46k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:"))
1868
0
    {
1869
0
        char **papszTokens =
1870
0
            CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
1871
0
        int nCount = CSLCount(papszTokens);
1872
0
        if (nCount == 5 && strlen(papszTokens[1]) == 1 &&
1873
0
            (papszTokens[2][0] == '\\' || papszTokens[2][0] == '/'))
1874
0
        {
1875
0
            osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1876
0
            nFAOIdxLookup = atoi(papszTokens[3]);
1877
0
            nArrayIdxLookup = atoi(papszTokens[4]);
1878
0
        }
1879
0
        else if (nCount == 5 && (EQUAL(papszTokens[1], "/vsicurl/http") ||
1880
0
                                 EQUAL(papszTokens[1], "/vsicurl/https")))
1881
0
        {
1882
0
            osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
1883
0
            nFAOIdxLookup = atoi(papszTokens[3]);
1884
0
            nArrayIdxLookup = atoi(papszTokens[4]);
1885
0
        }
1886
0
        else if (nCount == 4)
1887
0
        {
1888
0
            osXMLFilename = papszTokens[1];
1889
0
            nFAOIdxLookup = atoi(papszTokens[2]);
1890
0
            nArrayIdxLookup = atoi(papszTokens[3]);
1891
0
        }
1892
0
        else
1893
0
        {
1894
0
            CPLError(CE_Failure, CPLE_AppDefined,
1895
0
                     "Invalid syntax for PDS4 subdataset name");
1896
0
            CSLDestroy(papszTokens);
1897
0
            return nullptr;
1898
0
        }
1899
0
        CSLDestroy(papszTokens);
1900
0
    }
1901
1902
1.46k
    CPLXMLTreeCloser oCloser(CPLParseXMLFile(osXMLFilename));
1903
1.46k
    CPLXMLNode *psRoot = oCloser.get();
1904
1.46k
    CPLStripXMLNamespace(psRoot, nullptr, TRUE);
1905
1906
1.46k
    GDALAccess eAccess = STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:")
1907
1.46k
                             ? GA_ReadOnly
1908
1.46k
                             : poOpenInfo->eAccess;
1909
1910
1.46k
    CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
1911
1.46k
    if (psProduct == nullptr)
1912
719
    {
1913
719
        eAccess = GA_ReadOnly;
1914
719
        psProduct = CPLGetXMLNode(psRoot, "=Product_Ancillary");
1915
719
        if (psProduct == nullptr)
1916
719
        {
1917
719
            psProduct = CPLGetXMLNode(psRoot, "=Product_Collection");
1918
719
        }
1919
719
        if (psProduct == nullptr)
1920
718
        {
1921
718
            psProduct = CPLGetXMLNode(psRoot, "=Product_Browse");
1922
718
            if (psProduct)
1923
4
                return OpenBrowse(poOpenInfo, psProduct);
1924
718
        }
1925
719
    }
1926
1.46k
    if (psProduct == nullptr)
1927
714
    {
1928
714
        return nullptr;
1929
714
    }
1930
1931
    // Test case:
1932
    // https://starbase.jpl.nasa.gov/pds4/1700/dph_example_products/test_Images_DisplaySettings/TestPattern_Image/TestPattern.xml
1933
747
    const char *pszVertDir = CPLGetXMLValue(
1934
747
        psProduct,
1935
747
        "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
1936
747
        "vertical_display_direction",
1937
747
        "");
1938
747
    const bool bBottomToTop = EQUAL(pszVertDir, "Bottom to Top");
1939
1940
747
    const char *pszHorizDir = CPLGetXMLValue(
1941
747
        psProduct,
1942
747
        "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
1943
747
        "horizontal_display_direction",
1944
747
        "");
1945
747
    const bool bRightToLeft = EQUAL(pszHorizDir, "Right to Left");
1946
1947
747
    auto poDS = std::make_unique<PDS4Dataset>();
1948
747
    poDS->m_osXMLFilename = osXMLFilename;
1949
747
    poDS->eAccess = eAccess;
1950
747
    poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
1951
1952
747
    CPLStringList aosSubdatasets;
1953
747
    int nFAOIdx = 0;
1954
12.2k
    for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
1955
11.5k
         psIter = psIter->psNext)
1956
11.5k
    {
1957
11.5k
        if (psIter->eType != CXT_Element ||
1958
7.07k
            (strcmp(psIter->pszValue, "File_Area_Observational") != 0 &&
1959
1.49k
             strcmp(psIter->pszValue, "File_Area_Ancillary") != 0 &&
1960
1.49k
             strcmp(psIter->pszValue, "File_Area_Inventory") != 0))
1961
5.96k
        {
1962
5.96k
            continue;
1963
5.96k
        }
1964
1965
5.57k
        nFAOIdx++;
1966
5.57k
        CPLXMLNode *psFile = CPLGetXMLNode(psIter, "File");
1967
5.57k
        if (psFile == nullptr)
1968
0
        {
1969
0
            continue;
1970
0
        }
1971
5.57k
        const char *pszFilename = CPLGetXMLValue(psFile, "file_name", nullptr);
1972
5.57k
        if (pszFilename == nullptr)
1973
0
        {
1974
0
            continue;
1975
0
        }
1976
1977
11.1k
        for (CPLXMLNode *psSubIter = psFile->psChild; psSubIter != nullptr;
1978
5.58k
             psSubIter = psSubIter->psNext)
1979
5.58k
        {
1980
5.58k
            if (psSubIter->eType == CXT_Comment &&
1981
0
                EQUAL(psSubIter->pszValue, PREEXISTING_BINARY_FILE))
1982
0
            {
1983
0
                poDS->m_bCreatedFromExistingBinaryFile = true;
1984
0
            }
1985
5.58k
        }
1986
1987
5.57k
        int nArrayIdx = 0;
1988
5.57k
        for (CPLXMLNode *psSubIter = psIter->psChild;
1989
16.7k
             (nFAOIdxLookup < 0 || nFAOIdxLookup == nFAOIdx) &&
1990
16.7k
             psSubIter != nullptr;
1991
11.1k
             psSubIter = psSubIter->psNext)
1992
11.1k
        {
1993
11.1k
            if (psSubIter->eType != CXT_Element)
1994
0
            {
1995
0
                continue;
1996
0
            }
1997
11.1k
            int nDIM = 0;
1998
11.1k
            if (STARTS_WITH(psSubIter->pszValue, "Array_1D"))
1999
0
            {
2000
0
                nDIM = 1;
2001
0
            }
2002
11.1k
            else if (STARTS_WITH(psSubIter->pszValue, "Array_2D"))
2003
2
            {
2004
2
                nDIM = 2;
2005
2
            }
2006
11.1k
            else if (STARTS_WITH(psSubIter->pszValue, "Array_3D"))
2007
33
            {
2008
33
                nDIM = 3;
2009
33
            }
2010
11.1k
            else if (strcmp(psSubIter->pszValue, "Array") == 0)
2011
0
            {
2012
0
                nDIM = atoi(CPLGetXMLValue(psSubIter, "axes", "0"));
2013
0
            }
2014
11.1k
            else if (strcmp(psSubIter->pszValue, "Table_Character") == 0)
2015
1
            {
2016
1
                poDS->OpenTableCharacter(pszFilename, psSubIter);
2017
1
                continue;
2018
1
            }
2019
11.1k
            else if (strcmp(psSubIter->pszValue, "Table_Binary") == 0)
2020
1
            {
2021
1
                poDS->OpenTableBinary(pszFilename, psSubIter);
2022
1
                continue;
2023
1
            }
2024
11.1k
            else if (strcmp(psSubIter->pszValue, "Table_Delimited") == 0 ||
2025
5.58k
                     strcmp(psSubIter->pszValue, "Inventory") == 0)
2026
5.54k
            {
2027
5.54k
                poDS->OpenTableDelimited(pszFilename, psSubIter);
2028
5.54k
                continue;
2029
5.54k
            }
2030
5.62k
            if (nDIM == 0)
2031
5.58k
            {
2032
5.58k
                continue;
2033
5.58k
            }
2034
35
            if (!(nDIM >= 1 && nDIM <= 3))
2035
0
            {
2036
0
                CPLError(CE_Warning, CPLE_NotSupported,
2037
0
                         "Array with %d dimensions not supported", nDIM);
2038
0
                continue;
2039
0
            }
2040
2041
35
            nArrayIdx++;
2042
            // Does it match a selected subdataset ?
2043
35
            if (nArrayIdxLookup > 0 && nArrayIdx != nArrayIdxLookup)
2044
0
            {
2045
0
                continue;
2046
0
            }
2047
2048
35
            const char *pszArrayName =
2049
35
                CPLGetXMLValue(psSubIter, "name", nullptr);
2050
35
            const char *pszArrayId =
2051
35
                CPLGetXMLValue(psSubIter, "local_identifier", nullptr);
2052
35
            vsi_l_offset nOffset = static_cast<vsi_l_offset>(
2053
35
                CPLAtoGIntBig(CPLGetXMLValue(psSubIter, "offset", "0")));
2054
2055
35
            const char *pszAxisIndexOrder =
2056
35
                CPLGetXMLValue(psSubIter, "axis_index_order", "");
2057
35
            if (!EQUAL(pszAxisIndexOrder, "Last Index Fastest"))
2058
1
            {
2059
1
                CPLError(CE_Warning, CPLE_NotSupported,
2060
1
                         "axis_index_order = '%s' unhandled",
2061
1
                         pszAxisIndexOrder);
2062
1
                continue;
2063
1
            }
2064
2065
            // Figure out data type
2066
34
            const char *pszDataType =
2067
34
                CPLGetXMLValue(psSubIter, "Element_Array.data_type", "");
2068
34
            GDALDataType eDT = GDT_UInt8;
2069
34
            bool bLSBOrder = strstr(pszDataType, "LSB") != nullptr;
2070
2071
            // ComplexLSB16', 'ComplexLSB8', 'ComplexMSB16', 'ComplexMSB8',
2072
            // 'IEEE754LSBDouble', 'IEEE754LSBSingle', 'IEEE754MSBDouble',
2073
            // 'IEEE754MSBSingle', 'SignedBitString', 'SignedByte',
2074
            // 'SignedLSB2', 'SignedLSB4', 'SignedLSB8', 'SignedMSB2',
2075
            // 'SignedMSB4', 'SignedMSB8', 'UnsignedBitString', 'UnsignedByte',
2076
            // 'UnsignedLSB2', 'UnsignedLSB4', 'UnsignedLSB8', 'UnsignedMSB2',
2077
            // 'UnsignedMSB4', 'UnsignedMSB8'
2078
34
            if (EQUAL(pszDataType, "ComplexLSB16") ||
2079
34
                EQUAL(pszDataType, "ComplexMSB16"))
2080
0
            {
2081
0
                eDT = GDT_CFloat64;
2082
0
            }
2083
34
            else if (EQUAL(pszDataType, "ComplexLSB8") ||
2084
34
                     EQUAL(pszDataType, "ComplexMSB8"))
2085
0
            {
2086
0
                eDT = GDT_CFloat32;
2087
0
            }
2088
34
            else if (EQUAL(pszDataType, "IEEE754LSBDouble") ||
2089
32
                     EQUAL(pszDataType, "IEEE754MSBDouble"))
2090
2
            {
2091
2
                eDT = GDT_Float64;
2092
2
            }
2093
32
            else if (EQUAL(pszDataType, "IEEE754LSBSingle") ||
2094
25
                     EQUAL(pszDataType, "IEEE754MSBSingle"))
2095
7
            {
2096
7
                eDT = GDT_Float32;
2097
7
            }
2098
            // SignedBitString unhandled
2099
25
            else if (EQUAL(pszDataType, "SignedByte"))
2100
0
            {
2101
0
                eDT = GDT_Int8;
2102
0
            }
2103
25
            else if (EQUAL(pszDataType, "SignedLSB2") ||
2104
25
                     EQUAL(pszDataType, "SignedMSB2"))
2105
0
            {
2106
0
                eDT = GDT_Int16;
2107
0
            }
2108
25
            else if (EQUAL(pszDataType, "SignedLSB4") ||
2109
25
                     EQUAL(pszDataType, "SignedMSB4"))
2110
0
            {
2111
0
                eDT = GDT_Int32;
2112
0
            }
2113
25
            else if (EQUAL(pszDataType, "SignedLSB8") ||
2114
25
                     EQUAL(pszDataType, "SignedMSB8"))
2115
0
            {
2116
0
                eDT = GDT_Int64;
2117
0
            }
2118
25
            else if (EQUAL(pszDataType, "UnsignedByte"))
2119
23
            {
2120
23
                eDT = GDT_UInt8;
2121
23
            }
2122
2
            else if (EQUAL(pszDataType, "UnsignedLSB2") ||
2123
2
                     EQUAL(pszDataType, "UnsignedMSB2"))
2124
0
            {
2125
0
                eDT = GDT_UInt16;
2126
0
            }
2127
2
            else if (EQUAL(pszDataType, "UnsignedLSB4") ||
2128
2
                     EQUAL(pszDataType, "UnsignedMSB4"))
2129
0
            {
2130
0
                eDT = GDT_UInt32;
2131
0
            }
2132
2
            else if (EQUAL(pszDataType, "UnsignedLSB8") ||
2133
2
                     EQUAL(pszDataType, "UnsignedMSB8"))
2134
0
            {
2135
0
                eDT = GDT_UInt64;
2136
0
            }
2137
2
            else
2138
2
            {
2139
2
                CPLDebug("PDS4", "data_type = '%s' unhandled", pszDataType);
2140
2
                continue;
2141
2
            }
2142
2143
32
            poDS->m_osUnits =
2144
32
                CPLGetXMLValue(psSubIter, "Element_Array.unit", "");
2145
2146
32
            double dfValueOffset = CPLAtof(
2147
32
                CPLGetXMLValue(psSubIter, "Element_Array.value_offset", "0"));
2148
32
            double dfValueScale = CPLAtof(
2149
32
                CPLGetXMLValue(psSubIter, "Element_Array.scaling_factor", "1"));
2150
2151
            // Parse Axis_Array elements
2152
32
            int l_nBands = 1;
2153
32
            int nLines = 0;
2154
32
            int nSamples = 0;
2155
32
            std::vector<int> anElements;
2156
32
            std::vector<std::string> axisNames;
2157
32
            std::vector<char> dimSemantics;
2158
32
            anElements.resize(nDIM);
2159
32
            axisNames.resize(nDIM);
2160
32
            dimSemantics.resize(nDIM);
2161
32
            int iBandIdx = -1;
2162
32
            int iLineIdx = -1;
2163
32
            int iSampleIdx = -1;
2164
32
            int nAxisOKCount = 0;
2165
32
            for (CPLXMLNode *psAxisIter = psSubIter->psChild;
2166
303
                 psAxisIter != nullptr; psAxisIter = psAxisIter->psNext)
2167
271
            {
2168
271
                if (psAxisIter->eType != CXT_Element ||
2169
271
                    strcmp(psAxisIter->pszValue, "Axis_Array") != 0)
2170
177
                {
2171
177
                    continue;
2172
177
                }
2173
94
                const char *pszAxisName =
2174
94
                    CPLGetXMLValue(psAxisIter, "axis_name", nullptr);
2175
94
                const char *pszElements =
2176
94
                    CPLGetXMLValue(psAxisIter, "elements", nullptr);
2177
94
                const char *pszSequenceNumber =
2178
94
                    CPLGetXMLValue(psAxisIter, "sequence_number", nullptr);
2179
94
                if (pszAxisName == nullptr || pszElements == nullptr ||
2180
94
                    pszSequenceNumber == nullptr)
2181
0
                {
2182
0
                    continue;
2183
0
                }
2184
94
                int nSeqNumber = atoi(pszSequenceNumber);
2185
94
                if (nSeqNumber < 1 || nSeqNumber > nDIM)
2186
1
                {
2187
1
                    CPLError(CE_Warning, CPLE_AppDefined,
2188
1
                             "Invalid sequence_number = %s", pszSequenceNumber);
2189
1
                    continue;
2190
1
                }
2191
93
                int nElements = atoi(pszElements);
2192
93
                if (nElements <= 0)
2193
0
                {
2194
0
                    CPLError(CE_Warning, CPLE_AppDefined,
2195
0
                             "Invalid elements = %s", pszElements);
2196
0
                    continue;
2197
0
                }
2198
2199
93
                const int nIdx = nSeqNumber - 1;
2200
93
                if (STARTS_WITH_CI(pszAxisName, "Line"))
2201
32
                {
2202
32
                    if (iLineIdx < 0)
2203
32
                        iLineIdx = nIdx;
2204
0
                    else
2205
0
                    {
2206
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2207
0
                                 "Several axis with Line identifier");
2208
0
                        break;
2209
0
                    }
2210
32
                }
2211
61
                else if (STARTS_WITH_CI(pszAxisName, "Sample"))
2212
31
                {
2213
31
                    if (iSampleIdx < 0)
2214
31
                        iSampleIdx = nIdx;
2215
0
                    else
2216
0
                    {
2217
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2218
0
                                 "Several axis with Sample identifier");
2219
0
                        break;
2220
0
                    }
2221
31
                }
2222
30
                else if (STARTS_WITH_CI(pszAxisName, "Band"))
2223
30
                {
2224
30
                    if (iBandIdx < 0)
2225
30
                        iBandIdx = nIdx;
2226
0
                    else
2227
0
                    {
2228
0
                        CPLError(CE_Warning, CPLE_AppDefined,
2229
0
                                 "Several axis with Band identifier");
2230
0
                        break;
2231
0
                    }
2232
30
                }
2233
2234
93
                anElements[nIdx] = nElements;
2235
93
                axisNames[nIdx] = pszAxisName;
2236
2237
93
                ++nAxisOKCount;
2238
93
            }
2239
2240
32
            if (nAxisOKCount != nDIM)
2241
1
            {
2242
1
                CPLError(CE_Warning, CPLE_AppDefined,
2243
1
                         "Found only %d Axis_Array elements. %d expected",
2244
1
                         nAxisOKCount, nDIM);
2245
1
                continue;
2246
1
            }
2247
2248
31
            if (nDIM == 1)
2249
0
            {
2250
0
                dimSemantics[0] = 'S';
2251
0
                nLines = 1;
2252
0
                nSamples = anElements[0];
2253
0
            }
2254
31
            else if (nDIM == 2)
2255
2
            {
2256
2
                if (iLineIdx < 0 || iSampleIdx < 0)
2257
0
                {
2258
0
                    CPLDebug("PDS4", "Assume that axis %s is Line",
2259
0
                             axisNames[0].c_str());
2260
0
                    CPLDebug("PDS4", "Assume that axis %s is Sample",
2261
0
                             axisNames[1].c_str());
2262
0
                    iLineIdx = 0;
2263
0
                    iSampleIdx = 1;
2264
0
                }
2265
2
                CPLAssert(iLineIdx >= 0 && iLineIdx < 2);
2266
2
                CPLAssert(iSampleIdx >= 0 && iSampleIdx < 2);
2267
2
                dimSemantics[iLineIdx] = 'L';
2268
2
                dimSemantics[iSampleIdx] = 'S';
2269
2
                nLines = anElements[iLineIdx];
2270
2
                nSamples = anElements[iSampleIdx];
2271
2
            }
2272
29
            else /* if (nDim == 3) */
2273
29
            {
2274
29
                if (iLineIdx < 0 || iSampleIdx < 0)
2275
0
                {
2276
0
                    CPLDebug("PDS4", "Assume that axis %s is Band",
2277
0
                             axisNames[0].c_str());
2278
0
                    CPLDebug("PDS4", "Assume that axis %s is Line",
2279
0
                             axisNames[1].c_str());
2280
0
                    CPLDebug("PDS4", "Assume that axis %s is Sample",
2281
0
                             axisNames[2].c_str());
2282
0
                    iBandIdx = 0;
2283
0
                    iLineIdx = 1;
2284
0
                    iSampleIdx = 2;
2285
0
                }
2286
29
                else if (iBandIdx < 0)
2287
0
                {
2288
0
                    CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
2289
0
                    CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
2290
0
                    bool abUsedIndices[3] = {false, false, false};
2291
0
                    abUsedIndices[iLineIdx] = true;
2292
0
                    abUsedIndices[iSampleIdx] = true;
2293
0
                    for (int i = 0; i < 3; ++i)
2294
0
                    {
2295
0
                        if (!abUsedIndices[i])
2296
0
                            iBandIdx = i;
2297
0
                    }
2298
0
                }
2299
29
                CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
2300
29
                CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
2301
29
                CPLAssert(iBandIdx >= 0 && iSampleIdx < 3);
2302
29
                dimSemantics[iBandIdx] = 'B';
2303
29
                dimSemantics[iLineIdx] = 'L';
2304
29
                dimSemantics[iSampleIdx] = 'S';
2305
29
                l_nBands = anElements[iBandIdx];
2306
29
                nLines = anElements[iLineIdx];
2307
29
                nSamples = anElements[iSampleIdx];
2308
29
            }
2309
2310
31
            if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
2311
31
                !GDALCheckBandCount(l_nBands, FALSE))
2312
0
            {
2313
0
                continue;
2314
0
            }
2315
2316
            // Compute pixel, line and band spacing
2317
31
            vsi_l_offset nSpacing = GDALGetDataTypeSizeBytes(eDT);
2318
31
            int nPixelOffset = 0;
2319
31
            int nLineOffset = 0;
2320
31
            vsi_l_offset nBandOffset = 0;
2321
31
            int nCountPreviousDim = 1;
2322
122
            for (int i = nDIM - 1; i >= 0; i--)
2323
91
            {
2324
91
                if (dimSemantics[i] == 'S')
2325
31
                {
2326
31
                    if (nCountPreviousDim > 0 &&
2327
31
                        nSpacing > static_cast<vsi_l_offset>(INT_MAX /
2328
31
                                                             nCountPreviousDim))
2329
0
                    {
2330
0
                        CPLError(CE_Failure, CPLE_NotSupported,
2331
0
                                 "Integer overflow");
2332
0
                        return nullptr;
2333
0
                    }
2334
31
                    nPixelOffset =
2335
31
                        static_cast<int>(nSpacing * nCountPreviousDim);
2336
31
                    nSpacing = nPixelOffset;
2337
31
                }
2338
60
                else if (dimSemantics[i] == 'L')
2339
31
                {
2340
31
                    if (nCountPreviousDim > 0 &&
2341
30
                        nSpacing > static_cast<vsi_l_offset>(INT_MAX /
2342
30
                                                             nCountPreviousDim))
2343
0
                    {
2344
0
                        CPLError(CE_Failure, CPLE_NotSupported,
2345
0
                                 "Integer overflow");
2346
0
                        return nullptr;
2347
0
                    }
2348
31
                    nLineOffset =
2349
31
                        static_cast<int>(nSpacing * nCountPreviousDim);
2350
31
                    nSpacing = nLineOffset;
2351
31
                }
2352
29
                else
2353
29
                {
2354
29
                    nBandOffset = nSpacing * nCountPreviousDim;
2355
29
                    nSpacing = nBandOffset;
2356
29
                }
2357
91
                nCountPreviousDim = anElements[i];
2358
91
            }
2359
2360
            // Retrieve no data value
2361
31
            bool bNoDataSet = false;
2362
31
            double dfNoData = 0.0;
2363
31
            bool bNoDataSetInt64 = false;
2364
31
            int64_t nNoDataInt64 = 0;
2365
31
            bool bNoDataSetUInt64 = false;
2366
31
            int64_t nNoDataUInt64 = 0;
2367
31
            std::vector<double> adfConstants;
2368
31
            CPLXMLNode *psSC = CPLGetXMLNode(psSubIter, "Special_Constants");
2369
31
            if (psSC)
2370
17
            {
2371
17
                const auto GetNoDataFromConstant =
2372
17
                    [psSC, eDT, &bNoDataSetInt64, &nNoDataInt64,
2373
17
                     &bNoDataSetUInt64, &nNoDataUInt64, &bNoDataSet,
2374
17
                     &dfNoData](const char *pszConstantName)
2375
17
                {
2376
17
                    if (const char *pszVal =
2377
17
                            CPLGetXMLValue(psSC, pszConstantName, nullptr))
2378
17
                    {
2379
17
                        if (eDT == GDT_Int64)
2380
0
                        {
2381
0
                            bNoDataSetInt64 = true;
2382
0
                            nNoDataInt64 = std::strtoll(pszVal, nullptr, 10);
2383
0
                        }
2384
17
                        else if (eDT == GDT_UInt64)
2385
0
                        {
2386
0
                            bNoDataSetUInt64 = true;
2387
0
                            nNoDataUInt64 = std::strtoull(pszVal, nullptr, 10);
2388
0
                        }
2389
17
                        else
2390
17
                        {
2391
17
                            auto val =
2392
17
                                ConstantToDouble(pszConstantName, pszVal);
2393
17
                            if (val)
2394
17
                            {
2395
17
                                bNoDataSet = true;
2396
17
                                dfNoData = *val;
2397
17
                            }
2398
17
                        }
2399
17
                        return true;
2400
17
                    }
2401
0
                    return false;
2402
17
                };
2403
2404
17
                if (!GetNoDataFromConstant("missing_constant"))
2405
0
                {
2406
                    // For example in https://d34uoeqxvp7znu.cloudfront.net/data/l2/200908/20090811/m3g20090811t012112_l2.xml
2407
0
                    GetNoDataFromConstant("invalid_constant");
2408
0
                }
2409
2410
17
                const char *const apszConstantNames[] = {
2411
17
                    "saturated_constant",
2412
17
                    "missing_constant",
2413
17
                    "error_constant",
2414
17
                    "invalid_constant",
2415
17
                    "unknown_constant",
2416
17
                    "not_applicable_constant",
2417
17
                    "high_instrument_saturation",
2418
17
                    "high_representation_saturation",
2419
17
                    "low_instrument_saturation",
2420
17
                    "low_representation_saturation"};
2421
17
                for (const char *pszItem : apszConstantNames)
2422
170
                {
2423
170
                    if (const char *pszConstant =
2424
170
                            CPLGetXMLValue(psSC, pszItem, nullptr))
2425
37
                    {
2426
37
                        auto val = ConstantToDouble(pszItem, pszConstant);
2427
37
                        if (val)
2428
37
                        {
2429
37
                            adfConstants.push_back(*val);
2430
37
                        }
2431
37
                    }
2432
170
                }
2433
17
            }
2434
2435
            // Add subdatasets
2436
31
            const int nSDSIdx = 1 + aosSubdatasets.size() / 2;
2437
31
            aosSubdatasets.SetNameValue(
2438
31
                CPLSPrintf("SUBDATASET_%d_NAME", nSDSIdx),
2439
31
                CPLSPrintf("PDS4:%s:%d:%d", osXMLFilename.c_str(), nFAOIdx,
2440
31
                           nArrayIdx));
2441
31
            aosSubdatasets.SetNameValue(
2442
31
                CPLSPrintf("SUBDATASET_%d_DESC", nSDSIdx),
2443
31
                CPLSPrintf("Image file %s, array %s", pszFilename,
2444
31
                           pszArrayName ? pszArrayName
2445
31
                           : pszArrayId ? pszArrayId
2446
31
                                        : CPLSPrintf("%d", nArrayIdx)));
2447
2448
31
            if (poDS->nBands != 0)
2449
0
                continue;
2450
2451
31
            const std::string osImageFullFilename = CPLFormFilenameSafe(
2452
31
                CPLGetPathSafe(osXMLFilename.c_str()).c_str(), pszFilename,
2453
31
                nullptr);
2454
31
            if (CPLHasPathTraversal(pszFilename))
2455
0
            {
2456
0
                CPLError(CE_Failure, CPLE_NotSupported,
2457
0
                         "Path traversal detected in %s", pszFilename);
2458
0
                return nullptr;
2459
0
            }
2460
31
            VSILFILE *fp = VSIFOpenExL(
2461
31
                osImageFullFilename.c_str(),
2462
31
                (poOpenInfo->eAccess == GA_Update) ? "rb+" : "rb", true);
2463
31
            if (fp == nullptr)
2464
31
            {
2465
31
                CPLError(CE_Warning, CPLE_FileIO, "Cannot open %s: %s",
2466
31
                         osImageFullFilename.c_str(), VSIGetLastErrorMsg());
2467
31
                continue;
2468
31
            }
2469
0
            poDS->nRasterXSize = nSamples;
2470
0
            poDS->nRasterYSize = nLines;
2471
0
            poDS->m_osImageFilename = osImageFullFilename;
2472
0
            poDS->m_fpImage = fp;
2473
0
            poDS->m_bIsLSB = bLSBOrder;
2474
2475
0
            if (l_nBands > 1 && dimSemantics[0] == 'B' &&
2476
0
                dimSemantics[1] == 'L' && dimSemantics[2] == 'S')
2477
0
            {
2478
0
                poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
2479
0
                                                   GDAL_MDD_IMAGE_STRUCTURE);
2480
0
            }
2481
0
            if (l_nBands > 1 && dimSemantics[0] == 'L' &&
2482
0
                dimSemantics[1] == 'S' && dimSemantics[2] == 'B')
2483
0
            {
2484
0
                poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
2485
0
                                                   GDAL_MDD_IMAGE_STRUCTURE);
2486
0
            }
2487
2488
0
            CPLXMLNode *psOS = CPLGetXMLNode(psSubIter, "Object_Statistics");
2489
0
            const char *pszMin = nullptr;
2490
0
            const char *pszMax = nullptr;
2491
0
            const char *pszMean = nullptr;
2492
0
            const char *pszStdDev = nullptr;
2493
0
            if (psOS)
2494
0
            {
2495
0
                pszMin = CPLGetXMLValue(psOS, "minimum", nullptr);
2496
0
                pszMax = CPLGetXMLValue(psOS, "maximum", nullptr);
2497
0
                pszMean = CPLGetXMLValue(psOS, "mean", nullptr);
2498
0
                pszStdDev = CPLGetXMLValue(psOS, "standard_deviation", nullptr);
2499
0
            }
2500
2501
0
            for (int i = 0; i < l_nBands; i++)
2502
0
            {
2503
0
                vsi_l_offset nThisBandOffset = nOffset + nBandOffset * i;
2504
0
                if (bBottomToTop)
2505
0
                {
2506
0
                    nThisBandOffset +=
2507
0
                        static_cast<vsi_l_offset>(nLines - 1) * nLineOffset;
2508
0
                }
2509
0
                if (bRightToLeft)
2510
0
                {
2511
0
                    nThisBandOffset +=
2512
0
                        static_cast<vsi_l_offset>(nSamples - 1) * nPixelOffset;
2513
0
                }
2514
0
                auto poBand = std::make_unique<PDS4RawRasterBand>(
2515
0
                    poDS.get(), i + 1, poDS->m_fpImage, nThisBandOffset,
2516
0
                    bRightToLeft ? -nPixelOffset : nPixelOffset,
2517
0
                    bBottomToTop ? -nLineOffset : nLineOffset, eDT,
2518
0
                    bLSBOrder ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
2519
0
                              : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
2520
0
                if (!poBand->IsValid())
2521
0
                    return nullptr;
2522
0
                if (bNoDataSet)
2523
0
                {
2524
0
                    poBand->SetNoDataValue(dfNoData);
2525
0
                }
2526
0
                else if (bNoDataSetInt64)
2527
0
                {
2528
0
                    poBand->SetNoDataValueAsInt64(nNoDataInt64);
2529
0
                }
2530
0
                else if (bNoDataSetUInt64)
2531
0
                {
2532
0
                    poBand->SetNoDataValueAsUInt64(nNoDataUInt64);
2533
0
                }
2534
0
                poBand->SetOffset(dfValueOffset);
2535
0
                poBand->SetScale(dfValueScale);
2536
2537
0
                if (l_nBands == 1)
2538
0
                {
2539
0
                    if (pszMin)
2540
0
                    {
2541
0
                        poBand->GDALRasterBand::SetMetadataItem(
2542
0
                            "STATISTICS_MINIMUM", pszMin);
2543
0
                    }
2544
0
                    if (pszMax)
2545
0
                    {
2546
0
                        poBand->GDALRasterBand::SetMetadataItem(
2547
0
                            "STATISTICS_MAXIMUM", pszMax);
2548
0
                    }
2549
0
                    if (pszMean)
2550
0
                    {
2551
0
                        poBand->GDALRasterBand::SetMetadataItem(
2552
0
                            "STATISTICS_MEAN", pszMean);
2553
0
                    }
2554
0
                    if (pszStdDev)
2555
0
                    {
2556
0
                        poBand->GDALRasterBand::SetMetadataItem(
2557
0
                            "STATISTICS_STDDEV", pszStdDev);
2558
0
                    }
2559
0
                }
2560
2561
                // Only instantiate explicit mask band if we have at least one
2562
                // special constant (that is not the missing_constant,
2563
                // already exposed as nodata value)
2564
0
                if (!GDALDataTypeIsComplex(eDT) &&
2565
0
                    (CPLTestBool(CPLGetConfigOption("PDS4_FORCE_MASK", "NO")) ||
2566
0
                     adfConstants.size() >= 2 ||
2567
0
                     (adfConstants.size() == 1 && !bNoDataSet)))
2568
0
                {
2569
0
                    poBand->SetMaskBand(std::make_unique<PDS4MaskBand>(
2570
0
                        poBand.get(), adfConstants));
2571
0
                }
2572
2573
0
                poDS->SetBand(i + 1, std::move(poBand));
2574
0
            }
2575
0
        }
2576
5.57k
    }
2577
2578
747
    if (nFAOIdxLookup < 0 && aosSubdatasets.size() > 2)
2579
6
    {
2580
6
        poDS->GDALDataset::SetMetadata(aosSubdatasets.List(),
2581
6
                                       GDAL_MDD_SUBDATASETS);
2582
6
    }
2583
741
    else if (poDS->nBands == 0 &&
2584
741
             (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
2585
10
             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
2586
10
    {
2587
10
        return nullptr;
2588
10
    }
2589
731
    else if (poDS->m_apoLayers.empty() &&
2590
73
             (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
2591
42
             (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
2592
42
    {
2593
42
        return nullptr;
2594
42
    }
2595
2596
    // Expose XML content in xml:PDS4 metadata domain
2597
695
    GByte *pabyRet = nullptr;
2598
695
    CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osXMLFilename, &pabyRet, nullptr,
2599
695
                                     10 * 1024 * 1024));
2600
695
    if (pabyRet)
2601
695
    {
2602
695
        char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
2603
695
        poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
2604
695
    }
2605
695
    VSIFree(pabyRet);
2606
2607
    /*--------------------------------------------------------------------------*/
2608
    /*  Parse georeferencing info */
2609
    /*--------------------------------------------------------------------------*/
2610
695
    poDS->ReadGeoreferencing(psProduct);
2611
2612
    /*--------------------------------------------------------------------------*/
2613
    /*  Check for overviews */
2614
    /*--------------------------------------------------------------------------*/
2615
695
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
2616
2617
    /*--------------------------------------------------------------------------*/
2618
    /*  Initialize any PAM information */
2619
    /*--------------------------------------------------------------------------*/
2620
695
    poDS->SetDescription(poOpenInfo->pszFilename);
2621
695
    poDS->TryLoadXML();
2622
2623
695
    return poDS;
2624
747
}
2625
2626
/************************************************************************/
2627
/*                          IsCARTVersionGTE()                          */
2628
/************************************************************************/
2629
2630
// Returns true is pszCur >= pszRef
2631
// Must be things like 1900, 1B00, 1D00_1933 ...
2632
static bool IsCARTVersionGTE(const char *pszCur, const char *pszRef)
2633
841
{
2634
841
    return strcmp(pszCur, pszRef) >= 0;
2635
841
}
2636
2637
/************************************************************************/
2638
/*                        WriteGeoreferencing()                         */
2639
/************************************************************************/
2640
2641
void PDS4Dataset::WriteGeoreferencing(CPLXMLNode *psCart,
2642
                                      const char *pszCARTVersion)
2643
143
{
2644
143
    bool bHasBoundingBox = false;
2645
143
    double adfX[4] = {0};
2646
143
    double adfY[4] = {0};
2647
143
    CPLString osPrefix;
2648
143
    const char *pszColon = strchr(psCart->pszValue, ':');
2649
143
    if (pszColon)
2650
143
        osPrefix.assign(psCart->pszValue, pszColon - psCart->pszValue + 1);
2651
2652
143
    if (m_bGotTransform)
2653
2
    {
2654
2
        bHasBoundingBox = true;
2655
2656
        // upper left
2657
2
        adfX[0] = m_gt.xorig;
2658
2
        adfY[0] = m_gt.yorig;
2659
2660
        // upper right
2661
2
        adfX[1] = m_gt.xorig + m_gt.xscale * nRasterXSize;
2662
2
        adfY[1] = m_gt.yorig;
2663
2664
        // lower left
2665
2
        adfX[2] = m_gt.xorig;
2666
2
        adfY[2] = m_gt.yorig + m_gt.yscale * nRasterYSize;
2667
2668
        // lower right
2669
2
        adfX[3] = m_gt.xorig + m_gt.xscale * nRasterXSize;
2670
2
        adfY[3] = m_gt.yorig + m_gt.yscale * nRasterYSize;
2671
2
    }
2672
141
    else
2673
141
    {
2674
141
        OGRLayer *poLayer = GetLayer(0);
2675
141
        OGREnvelope sEnvelope;
2676
141
        if (poLayer->GetExtent(&sEnvelope) == OGRERR_NONE)
2677
36
        {
2678
36
            bHasBoundingBox = true;
2679
2680
36
            adfX[0] = sEnvelope.MinX;
2681
36
            adfY[0] = sEnvelope.MaxY;
2682
2683
36
            adfX[1] = sEnvelope.MaxX;
2684
36
            adfY[1] = sEnvelope.MaxY;
2685
2686
36
            adfX[2] = sEnvelope.MinX;
2687
36
            adfY[2] = sEnvelope.MinY;
2688
2689
36
            adfX[3] = sEnvelope.MaxX;
2690
36
            adfY[3] = sEnvelope.MinY;
2691
36
        }
2692
141
    }
2693
2694
143
    if (bHasBoundingBox && !m_oSRS.IsGeographic())
2695
33
    {
2696
33
        bHasBoundingBox = false;
2697
33
        OGRSpatialReference *poSRSLongLat = m_oSRS.CloneGeogCS();
2698
33
        if (poSRSLongLat)
2699
33
        {
2700
33
            poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
2701
33
            OGRCoordinateTransformation *poCT =
2702
33
                OGRCreateCoordinateTransformation(&m_oSRS, poSRSLongLat);
2703
33
            if (poCT)
2704
33
            {
2705
33
                if (poCT->Transform(4, adfX, adfY))
2706
20
                {
2707
20
                    bHasBoundingBox = true;
2708
20
                }
2709
33
                delete poCT;
2710
33
            }
2711
33
            delete poSRSLongLat;
2712
33
        }
2713
33
    }
2714
2715
143
    if (!bHasBoundingBox)
2716
118
    {
2717
        // Write dummy values
2718
118
        adfX[0] = -180.0;
2719
118
        adfY[0] = 90.0;
2720
118
        adfX[1] = 180.0;
2721
118
        adfY[1] = 90.0;
2722
118
        adfX[2] = -180.0;
2723
118
        adfY[2] = -90.0;
2724
118
        adfX[3] = 180.0;
2725
118
        adfY[3] = -90.0;
2726
118
    }
2727
2728
143
    const char *pszLongitudeDirection = CSLFetchNameValueDef(
2729
143
        m_papszCreationOptions, "LONGITUDE_DIRECTION", "Positive East");
2730
143
    const double dfLongitudeMultiplier =
2731
143
        EQUAL(pszLongitudeDirection, "Positive West") ? -1 : 1;
2732
143
    const auto FixLong = [dfLongitudeMultiplier](double dfLon)
2733
339
    { return dfLon * dfLongitudeMultiplier; };
2734
2735
    // Note: starting with CART 1900, Spatial_Domain is actually optional
2736
143
    CPLXMLNode *psSD = CPLCreateXMLNode(psCart, CXT_Element,
2737
143
                                        (osPrefix + "Spatial_Domain").c_str());
2738
143
    CPLXMLNode *psBC = CPLCreateXMLNode(
2739
143
        psSD, CXT_Element, (osPrefix + "Bounding_Coordinates").c_str());
2740
2741
143
    const char *pszBoundingDegrees =
2742
143
        CSLFetchNameValue(m_papszCreationOptions, "BOUNDING_DEGREES");
2743
143
    double dfWest = FixLong(
2744
143
        std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3])));
2745
143
    double dfEast = FixLong(
2746
143
        std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3])));
2747
143
    double dfNorth =
2748
143
        std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
2749
143
    double dfSouth =
2750
143
        std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
2751
143
    if (pszBoundingDegrees)
2752
0
    {
2753
0
        char **papszTokens = CSLTokenizeString2(pszBoundingDegrees, ",", 0);
2754
0
        if (CSLCount(papszTokens) == 4)
2755
0
        {
2756
0
            dfWest = CPLAtof(papszTokens[0]);
2757
0
            dfSouth = CPLAtof(papszTokens[1]);
2758
0
            dfEast = CPLAtof(papszTokens[2]);
2759
0
            dfNorth = CPLAtof(papszTokens[3]);
2760
0
        }
2761
0
        CSLDestroy(papszTokens);
2762
0
    }
2763
2764
143
    CPLAddXMLAttributeAndValue(
2765
143
        CPLCreateXMLElementAndValue(
2766
143
            psBC, (osPrefix + "west_bounding_coordinate").c_str(),
2767
143
            CPLSPrintf("%.17g", dfWest)),
2768
143
        "unit", "deg");
2769
143
    CPLAddXMLAttributeAndValue(
2770
143
        CPLCreateXMLElementAndValue(
2771
143
            psBC, (osPrefix + "east_bounding_coordinate").c_str(),
2772
143
            CPLSPrintf("%.17g", dfEast)),
2773
143
        "unit", "deg");
2774
143
    CPLAddXMLAttributeAndValue(
2775
143
        CPLCreateXMLElementAndValue(
2776
143
            psBC, (osPrefix + "north_bounding_coordinate").c_str(),
2777
143
            CPLSPrintf("%.17g", dfNorth)),
2778
143
        "unit", "deg");
2779
143
    CPLAddXMLAttributeAndValue(
2780
143
        CPLCreateXMLElementAndValue(
2781
143
            psBC, (osPrefix + "south_bounding_coordinate").c_str(),
2782
143
            CPLSPrintf("%.17g", dfSouth)),
2783
143
        "unit", "deg");
2784
2785
143
    CPLXMLNode *psSRI =
2786
143
        CPLCreateXMLNode(psCart, CXT_Element,
2787
143
                         (osPrefix + "Spatial_Reference_Information").c_str());
2788
143
    CPLXMLNode *psHCSD = CPLCreateXMLNode(
2789
143
        psSRI, CXT_Element,
2790
143
        (osPrefix + "Horizontal_Coordinate_System_Definition").c_str());
2791
2792
143
    double dfUnrotatedULX = m_gt.xorig;
2793
143
    double dfUnrotatedULY = m_gt.yorig;
2794
143
    double dfUnrotatedResX = m_gt.xscale;
2795
143
    double dfUnrotatedResY = m_gt.yscale;
2796
143
    double dfMapProjectionRotation = 0.0;
2797
143
    if (m_gt.xscale == 0.0 && m_gt.xrot > 0.0 && m_gt.yrot > 0.0 &&
2798
0
        m_gt.yscale == 0.0)
2799
0
    {
2800
0
        dfUnrotatedULX = m_gt.yorig;
2801
0
        dfUnrotatedULY = -m_gt.xorig;
2802
0
        dfUnrotatedResX = m_gt.yrot;
2803
0
        dfUnrotatedResY = -m_gt.xrot;
2804
0
        dfMapProjectionRotation = 90.0;
2805
0
    }
2806
2807
143
    if (GetRasterCount() || m_oSRS.IsProjected())
2808
138
    {
2809
138
        CPLXMLNode *psPlanar = CPLCreateXMLNode(psHCSD, CXT_Element,
2810
138
                                                (osPrefix + "Planar").c_str());
2811
138
        CPLXMLNode *psMP = CPLCreateXMLNode(
2812
138
            psPlanar, CXT_Element, (osPrefix + "Map_Projection").c_str());
2813
138
        const char *pszProjection = m_oSRS.GetAttrValue("PROJECTION");
2814
138
        CPLString pszPDS4ProjectionName = "";
2815
138
        typedef std::pair<const char *, double> ProjParam;
2816
138
        std::vector<ProjParam> aoProjParams;
2817
2818
138
        const bool bUse_CART_1933_Or_Later =
2819
138
            IsCARTVersionGTE(pszCARTVersion, "1D00_1933");
2820
2821
138
        const bool bUse_CART_1950_Or_Later =
2822
138
            IsCARTVersionGTE(pszCARTVersion, "1G00_1950");
2823
2824
138
        const bool bUse_CART_1970_Or_Later =
2825
138
            IsCARTVersionGTE(pszCARTVersion, "1O00_1970");
2826
2827
138
        if (pszProjection == nullptr)
2828
0
        {
2829
0
            pszPDS4ProjectionName = "Equirectangular";
2830
0
            if (bUse_CART_1933_Or_Later)
2831
0
            {
2832
0
                aoProjParams.push_back(
2833
0
                    ProjParam("latitude_of_projection_origin", 0.0));
2834
0
                aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
2835
0
                aoProjParams.push_back(
2836
0
                    ProjParam("longitude_of_central_meridian", 0.0));
2837
0
            }
2838
0
            else
2839
0
            {
2840
0
                aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
2841
0
                aoProjParams.push_back(
2842
0
                    ProjParam("longitude_of_central_meridian", 0.0));
2843
0
                aoProjParams.push_back(
2844
0
                    ProjParam("latitude_of_projection_origin", 0.0));
2845
0
            }
2846
0
        }
2847
2848
138
        else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
2849
0
        {
2850
0
            pszPDS4ProjectionName = "Equirectangular";
2851
0
            if (bUse_CART_1933_Or_Later)
2852
0
            {
2853
0
                aoProjParams.push_back(ProjParam(
2854
0
                    "latitude_of_projection_origin",
2855
0
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2856
0
                aoProjParams.push_back(ProjParam(
2857
0
                    "standard_parallel_1",
2858
0
                    m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
2859
0
                aoProjParams.push_back(
2860
0
                    ProjParam("longitude_of_central_meridian",
2861
0
                              FixLong(m_oSRS.GetNormProjParm(
2862
0
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2863
0
            }
2864
0
            else
2865
0
            {
2866
0
                aoProjParams.push_back(ProjParam(
2867
0
                    "standard_parallel_1",
2868
0
                    m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
2869
0
                aoProjParams.push_back(
2870
0
                    ProjParam("longitude_of_central_meridian",
2871
0
                              FixLong(m_oSRS.GetNormProjParm(
2872
0
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2873
0
                aoProjParams.push_back(ProjParam(
2874
0
                    "latitude_of_projection_origin",
2875
0
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2876
0
            }
2877
0
        }
2878
2879
138
        else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
2880
1
        {
2881
1
            pszPDS4ProjectionName = "Lambert Conformal Conic";
2882
1
            if (bUse_CART_1933_Or_Later)
2883
1
            {
2884
1
                if (bUse_CART_1970_Or_Later)
2885
1
                {
2886
                    // Note: in EPSG (and PROJ "metadata" part), there is
2887
                    // no standard_parallel_1 parameter for LCC_1SP. But
2888
                    // for historical reason we do map the latitude of origin
2889
                    // to +lat_1 (in addition to +lat_0). So do the same here.
2890
1
                    aoProjParams.push_back(
2891
1
                        ProjParam("standard_parallel_1",
2892
1
                                  m_oSRS.GetNormProjParm(
2893
1
                                      SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2894
1
                }
2895
1
                aoProjParams.push_back(
2896
1
                    ProjParam("longitude_of_central_meridian",
2897
1
                              FixLong(m_oSRS.GetNormProjParm(
2898
1
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2899
1
                aoProjParams.push_back(ProjParam(
2900
1
                    "latitude_of_projection_origin",
2901
1
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2902
1
                aoProjParams.push_back(ProjParam(
2903
1
                    "scale_factor_at_projection_origin",
2904
1
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2905
1
            }
2906
0
            else
2907
0
            {
2908
0
                aoProjParams.push_back(ProjParam(
2909
0
                    "scale_factor_at_projection_origin",
2910
0
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2911
0
                aoProjParams.push_back(
2912
0
                    ProjParam("longitude_of_central_meridian",
2913
0
                              FixLong(m_oSRS.GetNormProjParm(
2914
0
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2915
0
                aoProjParams.push_back(ProjParam(
2916
0
                    "latitude_of_projection_origin",
2917
0
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2918
0
            }
2919
1
        }
2920
2921
137
        else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
2922
52
        {
2923
52
            pszPDS4ProjectionName = "Lambert Conformal Conic";
2924
52
            aoProjParams.push_back(ProjParam(
2925
52
                "standard_parallel_1",
2926
52
                m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
2927
52
            aoProjParams.push_back(ProjParam(
2928
52
                "standard_parallel_2",
2929
52
                m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0)));
2930
52
            aoProjParams.push_back(ProjParam(
2931
52
                "longitude_of_central_meridian",
2932
52
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2933
52
            aoProjParams.push_back(ProjParam(
2934
52
                "latitude_of_projection_origin",
2935
52
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2936
52
        }
2937
2938
85
        else if (EQUAL(pszProjection,
2939
85
                       SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
2940
0
        {
2941
0
            pszPDS4ProjectionName = "Oblique Mercator";
2942
            // Proj params defined later
2943
0
        }
2944
2945
85
        else if (EQUAL(pszProjection,
2946
85
                       SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
2947
0
        {
2948
0
            pszPDS4ProjectionName = "Oblique Mercator";
2949
            // Proj params defined later
2950
0
        }
2951
2952
85
        else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
2953
0
        {
2954
0
            pszPDS4ProjectionName = "Polar Stereographic";
2955
0
            if (bUse_CART_1950_Or_Later)
2956
0
            {
2957
0
                aoProjParams.push_back(
2958
0
                    ProjParam("longitude_of_central_meridian",
2959
0
                              FixLong(m_oSRS.GetNormProjParm(
2960
0
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2961
0
                aoProjParams.push_back(ProjParam(
2962
0
                    "latitude_of_projection_origin",
2963
0
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2964
0
                aoProjParams.push_back(ProjParam(
2965
0
                    "scale_factor_at_projection_origin",
2966
0
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2967
0
            }
2968
0
            else
2969
0
            {
2970
0
                aoProjParams.push_back(
2971
0
                    ProjParam(bUse_CART_1933_Or_Later
2972
0
                                  ? "longitude_of_central_meridian"
2973
0
                                  : "straight_vertical_longitude_from_pole",
2974
0
                              FixLong(m_oSRS.GetNormProjParm(
2975
0
                                  SRS_PP_CENTRAL_MERIDIAN, 0.0))));
2976
0
                aoProjParams.push_back(ProjParam(
2977
0
                    "scale_factor_at_projection_origin",
2978
0
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
2979
0
                aoProjParams.push_back(ProjParam(
2980
0
                    "latitude_of_projection_origin",
2981
0
                    m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2982
0
            }
2983
0
        }
2984
2985
85
        else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
2986
0
        {
2987
0
            pszPDS4ProjectionName = "Polyconic";
2988
0
            aoProjParams.push_back(ProjParam(
2989
0
                "longitude_of_central_meridian",
2990
0
                m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
2991
0
            aoProjParams.push_back(ProjParam(
2992
0
                "latitude_of_projection_origin",
2993
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
2994
0
        }
2995
85
        else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
2996
0
        {
2997
0
            pszPDS4ProjectionName = "Sinusoidal";
2998
0
            aoProjParams.push_back(ProjParam(
2999
0
                "longitude_of_central_meridian",
3000
0
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3001
0
            aoProjParams.push_back(ProjParam(
3002
0
                "latitude_of_projection_origin",
3003
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3004
0
        }
3005
3006
85
        else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
3007
78
        {
3008
78
            pszPDS4ProjectionName = "Transverse Mercator";
3009
78
            aoProjParams.push_back(
3010
78
                ProjParam("scale_factor_at_central_meridian",
3011
78
                          m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
3012
78
            aoProjParams.push_back(ProjParam(
3013
78
                "longitude_of_central_meridian",
3014
78
                m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
3015
78
            aoProjParams.push_back(ProjParam(
3016
78
                "latitude_of_projection_origin",
3017
78
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3018
78
        }
3019
7
        else if (EQUAL(pszProjection, SRS_PT_ORTHOGRAPHIC))
3020
0
        {
3021
0
            pszPDS4ProjectionName = "Orthographic";
3022
0
            aoProjParams.push_back(ProjParam(
3023
0
                "longitude_of_central_meridian",
3024
0
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3025
0
            aoProjParams.push_back(ProjParam(
3026
0
                "latitude_of_projection_origin",
3027
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3028
0
        }
3029
3030
7
        else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
3031
0
        {
3032
0
            pszPDS4ProjectionName = "Mercator";
3033
0
            aoProjParams.push_back(ProjParam(
3034
0
                "longitude_of_central_meridian",
3035
0
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3036
0
            aoProjParams.push_back(ProjParam(
3037
0
                "latitude_of_projection_origin",
3038
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3039
0
            aoProjParams.push_back(
3040
0
                ProjParam("scale_factor_at_projection_origin",
3041
0
                          m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
3042
0
        }
3043
3044
7
        else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
3045
0
        {
3046
0
            pszPDS4ProjectionName = "Mercator";
3047
0
            aoProjParams.push_back(ProjParam(
3048
0
                "standard_parallel_1",
3049
0
                m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
3050
0
            aoProjParams.push_back(ProjParam(
3051
0
                "longitude_of_central_meridian",
3052
0
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3053
0
            aoProjParams.push_back(ProjParam(
3054
0
                "latitude_of_projection_origin",
3055
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3056
0
        }
3057
3058
7
        else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
3059
0
        {
3060
0
            pszPDS4ProjectionName = "Lambert Azimuthal Equal Area";
3061
0
            aoProjParams.push_back(ProjParam(
3062
0
                "longitude_of_central_meridian",
3063
0
                FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
3064
0
            aoProjParams.push_back(ProjParam(
3065
0
                "latitude_of_projection_origin",
3066
0
                m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
3067
0
        }
3068
3069
7
        else if (EQUAL(pszProjection, "custom_proj4"))
3070
0
        {
3071
0
            const char *pszProj4 =
3072
0
                m_oSRS.GetExtension("PROJCS", "PROJ4", nullptr);
3073
0
            if (pszProj4 && strstr(pszProj4, "+proj=ob_tran") &&
3074
0
                strstr(pszProj4, "+o_proj=eqc"))
3075
0
            {
3076
0
                pszPDS4ProjectionName = "Oblique Cylindrical";
3077
0
                const auto FetchParam =
3078
0
                    [](const char *pszProj4Str, const char *pszKey)
3079
0
                {
3080
0
                    CPLString needle;
3081
0
                    needle.Printf("+%s=", pszKey);
3082
0
                    const char *pszVal = strstr(pszProj4Str, needle.c_str());
3083
0
                    if (pszVal)
3084
0
                        return CPLAtof(pszVal + needle.size());
3085
0
                    return 0.0;
3086
0
                };
3087
3088
0
                double dfLonP = FetchParam(pszProj4, "o_lon_p");
3089
0
                double dfLatP = FetchParam(pszProj4, "o_lat_p");
3090
0
                double dfLon0 = FetchParam(pszProj4, "lon_0");
3091
0
                double dfPoleRotation = -dfLonP;
3092
0
                double dfPoleLatitude = 180 - dfLatP;
3093
0
                double dfPoleLongitude = dfLon0;
3094
3095
0
                aoProjParams.push_back(ProjParam("map_projection_rotation",
3096
0
                                                 dfMapProjectionRotation));
3097
0
                aoProjParams.push_back(
3098
0
                    ProjParam("oblique_proj_pole_latitude", dfPoleLatitude));
3099
0
                aoProjParams.push_back(ProjParam("oblique_proj_pole_longitude",
3100
0
                                                 FixLong(dfPoleLongitude)));
3101
0
                aoProjParams.push_back(
3102
0
                    ProjParam("oblique_proj_pole_rotation", dfPoleRotation));
3103
0
            }
3104
0
            else
3105
0
            {
3106
0
                CPLError(CE_Warning, CPLE_NotSupported,
3107
0
                         "Projection %s not supported", pszProjection);
3108
0
            }
3109
0
        }
3110
7
        else
3111
7
        {
3112
7
            CPLError(CE_Warning, CPLE_NotSupported,
3113
7
                     "Projection %s not supported", pszProjection);
3114
7
        }
3115
138
        CPLCreateXMLElementAndValue(psMP,
3116
138
                                    (osPrefix + "map_projection_name").c_str(),
3117
138
                                    pszPDS4ProjectionName);
3118
138
        CPLXMLNode *psProj = CPLCreateXMLNode(
3119
138
            psMP, CXT_Element,
3120
138
            CPLString(osPrefix + pszPDS4ProjectionName).replaceAll(' ', '_'));
3121
584
        for (size_t i = 0; i < aoProjParams.size(); i++)
3122
446
        {
3123
446
            CPLXMLNode *psParam = CPLCreateXMLElementAndValue(
3124
446
                psProj, (osPrefix + aoProjParams[i].first).c_str(),
3125
446
                CPLSPrintf("%.17g", aoProjParams[i].second));
3126
446
            if (!STARTS_WITH(aoProjParams[i].first, "scale_factor"))
3127
367
            {
3128
367
                CPLAddXMLAttributeAndValue(psParam, "unit", "deg");
3129
367
            }
3130
446
        }
3131
3132
138
        if (pszProjection &&
3133
138
            EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
3134
0
        {
3135
0
            CPLXMLNode *psOLA =
3136
0
                CPLCreateXMLNode(nullptr, CXT_Element,
3137
0
                                 (osPrefix + "Oblique_Line_Azimuth").c_str());
3138
0
            CPLAddXMLAttributeAndValue(
3139
0
                CPLCreateXMLElementAndValue(
3140
0
                    psOLA, (osPrefix + "azimuthal_angle").c_str(),
3141
0
                    CPLSPrintf("%.17g",
3142
0
                               m_oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0))),
3143
0
                "unit", "deg");
3144
0
            ;
3145
            // Not completely sure of this
3146
0
            CPLAddXMLAttributeAndValue(
3147
0
                CPLCreateXMLElementAndValue(
3148
0
                    psOLA,
3149
0
                    (osPrefix + "azimuth_measure_point_longitude").c_str(),
3150
0
                    CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
3151
0
                                            SRS_PP_CENTRAL_MERIDIAN, 0.0)))),
3152
0
                "unit", "deg");
3153
3154
0
            if (bUse_CART_1933_Or_Later)
3155
0
            {
3156
0
                CPLAddXMLChild(psProj, psOLA);
3157
3158
0
                CPLAddXMLAttributeAndValue(
3159
0
                    CPLCreateXMLElementAndValue(
3160
0
                        psProj,
3161
0
                        (osPrefix + "longitude_of_central_meridian").c_str(),
3162
0
                        "0"),
3163
0
                    "unit", "deg");
3164
3165
0
                const double dfScaleFactor =
3166
0
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
3167
0
                if (dfScaleFactor != 1.0)
3168
0
                {
3169
0
                    CPLError(CE_Warning, CPLE_NotSupported,
3170
0
                             "Scale factor on initial support = %.17g cannot "
3171
0
                             "be encoded in PDS4",
3172
0
                             dfScaleFactor);
3173
0
                }
3174
0
            }
3175
0
            else
3176
0
            {
3177
0
                CPLCreateXMLElementAndValue(
3178
0
                    psProj,
3179
0
                    (osPrefix + "scale_factor_at_projection_origin").c_str(),
3180
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3181
0
                                            SRS_PP_SCALE_FACTOR, 0.0)));
3182
3183
0
                CPLAddXMLChild(psProj, psOLA);
3184
0
            }
3185
3186
0
            CPLAddXMLAttributeAndValue(
3187
0
                CPLCreateXMLElementAndValue(
3188
0
                    psProj,
3189
0
                    (osPrefix + "latitude_of_projection_origin").c_str(),
3190
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3191
0
                                            SRS_PP_LATITUDE_OF_ORIGIN, 0.0))),
3192
0
                "unit", "deg");
3193
0
        }
3194
138
        else if (pszProjection &&
3195
138
                 EQUAL(pszProjection,
3196
138
                       SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
3197
0
        {
3198
0
            if (bUse_CART_1933_Or_Later)
3199
0
            {
3200
0
                const double dfScaleFactor =
3201
0
                    m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
3202
0
                if (dfScaleFactor != 1.0)
3203
0
                {
3204
0
                    CPLError(CE_Warning, CPLE_NotSupported,
3205
0
                             "Scale factor on initial support = %.17g cannot "
3206
0
                             "be encoded in PDS4",
3207
0
                             dfScaleFactor);
3208
0
                }
3209
0
            }
3210
0
            else
3211
0
            {
3212
0
                CPLCreateXMLElementAndValue(
3213
0
                    psProj,
3214
0
                    (osPrefix + "scale_factor_at_projection_origin").c_str(),
3215
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3216
0
                                            SRS_PP_SCALE_FACTOR, 0.0)));
3217
0
            }
3218
3219
0
            CPLXMLNode *psOLP = CPLCreateXMLNode(
3220
0
                psProj, CXT_Element, (osPrefix + "Oblique_Line_Point").c_str());
3221
0
            CPLXMLNode *psOLPG1 = CPLCreateXMLNode(
3222
0
                psOLP, CXT_Element,
3223
0
                (osPrefix + "Oblique_Line_Point_Group").c_str());
3224
0
            CPLAddXMLAttributeAndValue(
3225
0
                CPLCreateXMLElementAndValue(
3226
0
                    psOLPG1, (osPrefix + "oblique_line_latitude").c_str(),
3227
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3228
0
                                            SRS_PP_LATITUDE_OF_POINT_1, 0.0))),
3229
0
                "unit", "deg");
3230
0
            CPLAddXMLAttributeAndValue(
3231
0
                CPLCreateXMLElementAndValue(
3232
0
                    psOLPG1, (osPrefix + "oblique_line_longitude").c_str(),
3233
0
                    CPLSPrintf("%.17g",
3234
0
                               FixLong(m_oSRS.GetNormProjParm(
3235
0
                                   SRS_PP_LONGITUDE_OF_POINT_1, 0.0)))),
3236
0
                "unit", "deg");
3237
0
            CPLXMLNode *psOLPG2 = CPLCreateXMLNode(
3238
0
                psOLP, CXT_Element,
3239
0
                (osPrefix + "Oblique_Line_Point_Group").c_str());
3240
0
            CPLAddXMLAttributeAndValue(
3241
0
                CPLCreateXMLElementAndValue(
3242
0
                    psOLPG2, (osPrefix + "oblique_line_latitude").c_str(),
3243
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3244
0
                                            SRS_PP_LATITUDE_OF_POINT_2, 0.0))),
3245
0
                "unit", "deg");
3246
0
            CPLAddXMLAttributeAndValue(
3247
0
                CPLCreateXMLElementAndValue(
3248
0
                    psOLPG2, (osPrefix + "oblique_line_longitude").c_str(),
3249
0
                    CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
3250
0
                                            SRS_PP_LONGITUDE_OF_POINT_2, 0.0))),
3251
0
                "unit", "deg");
3252
3253
0
            if (bUse_CART_1933_Or_Later)
3254
0
            {
3255
0
                CPLAddXMLAttributeAndValue(
3256
0
                    CPLCreateXMLElementAndValue(
3257
0
                        psProj,
3258
0
                        (osPrefix + "longitude_of_central_meridian").c_str(),
3259
0
                        "0"),
3260
0
                    "unit", "deg");
3261
0
            }
3262
3263
0
            CPLAddXMLAttributeAndValue(
3264
0
                CPLCreateXMLElementAndValue(
3265
0
                    psProj,
3266
0
                    (osPrefix + "latitude_of_projection_origin").c_str(),
3267
0
                    CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
3268
0
                                            SRS_PP_LATITUDE_OF_ORIGIN, 0.0)))),
3269
0
                "unit", "deg");
3270
0
        }
3271
3272
138
        CPLXMLNode *psCR = nullptr;
3273
138
        if (m_bGotTransform || !IsCARTVersionGTE(pszCARTVersion, "1B00"))
3274
2
        {
3275
2
            CPLXMLNode *psPCI = CPLCreateXMLNode(
3276
2
                psPlanar, CXT_Element,
3277
2
                (osPrefix + "Planar_Coordinate_Information").c_str());
3278
2
            CPLCreateXMLElementAndValue(
3279
2
                psPCI, (osPrefix + "planar_coordinate_encoding_method").c_str(),
3280
2
                "Coordinate Pair");
3281
2
            psCR = CPLCreateXMLNode(
3282
2
                psPCI, CXT_Element,
3283
2
                (osPrefix + "Coordinate_Representation").c_str());
3284
2
        }
3285
138
        const double dfLinearUnits = m_oSRS.GetLinearUnits();
3286
138
        const double dfDegToMeter = m_oSRS.GetSemiMajor() * M_PI / 180.0;
3287
3288
138
        if (psCR == nullptr)
3289
136
        {
3290
            // do nothing
3291
136
        }
3292
2
        else if (!m_bGotTransform)
3293
0
        {
3294
0
            CPLAddXMLAttributeAndValue(
3295
0
                CPLCreateXMLElementAndValue(
3296
0
                    psCR, (osPrefix + "pixel_resolution_x").c_str(), "0"),
3297
0
                "unit", "m/pixel");
3298
0
            CPLAddXMLAttributeAndValue(
3299
0
                CPLCreateXMLElementAndValue(
3300
0
                    psCR, (osPrefix + "pixel_resolution_y").c_str(), "0"),
3301
0
                "unit", "m/pixel");
3302
0
            CPLAddXMLAttributeAndValue(
3303
0
                CPLCreateXMLElementAndValue(
3304
0
                    psCR, (osPrefix + "pixel_scale_x").c_str(), "0"),
3305
0
                "unit", "pixel/deg");
3306
0
            CPLAddXMLAttributeAndValue(
3307
0
                CPLCreateXMLElementAndValue(
3308
0
                    psCR, (osPrefix + "pixel_scale_y").c_str(), "0"),
3309
0
                "unit", "pixel/deg");
3310
0
        }
3311
2
        else if (m_oSRS.IsGeographic())
3312
1
        {
3313
1
            CPLAddXMLAttributeAndValue(
3314
1
                CPLCreateXMLElementAndValue(
3315
1
                    psCR, (osPrefix + "pixel_resolution_x").c_str(),
3316
1
                    CPLSPrintf("%.17g", dfUnrotatedResX * dfDegToMeter)),
3317
1
                "unit", "m/pixel");
3318
1
            CPLAddXMLAttributeAndValue(
3319
1
                CPLCreateXMLElementAndValue(
3320
1
                    psCR, (osPrefix + "pixel_resolution_y").c_str(),
3321
1
                    CPLSPrintf("%.17g", -dfUnrotatedResY * dfDegToMeter)),
3322
1
                "unit", "m/pixel");
3323
1
            CPLAddXMLAttributeAndValue(
3324
1
                CPLCreateXMLElementAndValue(
3325
1
                    psCR, (osPrefix + "pixel_scale_x").c_str(),
3326
1
                    CPLSPrintf("%.17g", 1.0 / (dfUnrotatedResX))),
3327
1
                "unit", "pixel/deg");
3328
1
            CPLAddXMLAttributeAndValue(
3329
1
                CPLCreateXMLElementAndValue(
3330
1
                    psCR, (osPrefix + "pixel_scale_y").c_str(),
3331
1
                    CPLSPrintf("%.17g", 1.0 / (-dfUnrotatedResY))),
3332
1
                "unit", "pixel/deg");
3333
1
        }
3334
1
        else if (m_oSRS.IsProjected())
3335
1
        {
3336
1
            CPLAddXMLAttributeAndValue(
3337
1
                CPLCreateXMLElementAndValue(
3338
1
                    psCR, (osPrefix + "pixel_resolution_x").c_str(),
3339
1
                    CPLSPrintf("%.17g", dfUnrotatedResX * dfLinearUnits)),
3340
1
                "unit", "m/pixel");
3341
1
            CPLAddXMLAttributeAndValue(
3342
1
                CPLCreateXMLElementAndValue(
3343
1
                    psCR, (osPrefix + "pixel_resolution_y").c_str(),
3344
1
                    CPLSPrintf("%.17g", -dfUnrotatedResY * dfLinearUnits)),
3345
1
                "unit", "m/pixel");
3346
1
            CPLAddXMLAttributeAndValue(
3347
1
                CPLCreateXMLElementAndValue(
3348
1
                    psCR, (osPrefix + "pixel_scale_x").c_str(),
3349
1
                    CPLSPrintf("%.17g", dfDegToMeter /
3350
1
                                            (dfUnrotatedResX * dfLinearUnits))),
3351
1
                "unit", "pixel/deg");
3352
1
            CPLAddXMLAttributeAndValue(
3353
1
                CPLCreateXMLElementAndValue(
3354
1
                    psCR, (osPrefix + "pixel_scale_y").c_str(),
3355
1
                    CPLSPrintf("%.17g", dfDegToMeter / (-dfUnrotatedResY *
3356
1
                                                        dfLinearUnits))),
3357
1
                "unit", "pixel/deg");
3358
1
        }
3359
3360
138
        if (m_bGotTransform)
3361
2
        {
3362
2
            CPLXMLNode *psGT =
3363
2
                CPLCreateXMLNode(psPlanar, CXT_Element,
3364
2
                                 (osPrefix + "Geo_Transformation").c_str());
3365
2
            const double dfFalseEasting =
3366
2
                m_oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
3367
2
            const double dfFalseNorthing =
3368
2
                m_oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
3369
2
            const double dfULX = -dfFalseEasting + dfUnrotatedULX;
3370
2
            const double dfULY = -dfFalseNorthing + dfUnrotatedULY;
3371
2
            if (m_oSRS.IsGeographic())
3372
1
            {
3373
1
                CPLAddXMLAttributeAndValue(
3374
1
                    CPLCreateXMLElementAndValue(
3375
1
                        psGT, (osPrefix + "upperleft_corner_x").c_str(),
3376
1
                        CPLSPrintf("%.17g", dfULX * dfDegToMeter)),
3377
1
                    "unit", "m");
3378
1
                CPLAddXMLAttributeAndValue(
3379
1
                    CPLCreateXMLElementAndValue(
3380
1
                        psGT, (osPrefix + "upperleft_corner_y").c_str(),
3381
1
                        CPLSPrintf("%.17g", dfULY * dfDegToMeter)),
3382
1
                    "unit", "m");
3383
1
            }
3384
1
            else if (m_oSRS.IsProjected())
3385
1
            {
3386
1
                CPLAddXMLAttributeAndValue(
3387
1
                    CPLCreateXMLElementAndValue(
3388
1
                        psGT, (osPrefix + "upperleft_corner_x").c_str(),
3389
1
                        CPLSPrintf("%.17g", dfULX * dfLinearUnits)),
3390
1
                    "unit", "m");
3391
1
                CPLAddXMLAttributeAndValue(
3392
1
                    CPLCreateXMLElementAndValue(
3393
1
                        psGT, (osPrefix + "upperleft_corner_y").c_str(),
3394
1
                        CPLSPrintf("%.17g", dfULY * dfLinearUnits)),
3395
1
                    "unit", "m");
3396
1
            }
3397
2
        }
3398
138
    }
3399
5
    else
3400
5
    {
3401
5
        CPLXMLNode *psGeographic = CPLCreateXMLNode(
3402
5
            psHCSD, CXT_Element, (osPrefix + "Geographic").c_str());
3403
5
        if (!IsCARTVersionGTE(pszCARTVersion, "1B00"))
3404
0
        {
3405
0
            CPLAddXMLAttributeAndValue(
3406
0
                CPLCreateXMLElementAndValue(
3407
0
                    psGeographic, (osPrefix + "latitude_resolution").c_str(),
3408
0
                    "0"),
3409
0
                "unit", "deg");
3410
0
            CPLAddXMLAttributeAndValue(
3411
0
                CPLCreateXMLElementAndValue(
3412
0
                    psGeographic, (osPrefix + "longitude_resolution").c_str(),
3413
0
                    "0"),
3414
0
                "unit", "deg");
3415
0
        }
3416
5
    }
3417
3418
143
    CPLXMLNode *psGM = CPLCreateXMLNode(psHCSD, CXT_Element,
3419
143
                                        (osPrefix + "Geodetic_Model").c_str());
3420
143
    const char *pszLatitudeType = CSLFetchNameValueDef(
3421
143
        m_papszCreationOptions, "LATITUDE_TYPE", "Planetocentric");
3422
    // Fix case
3423
143
    if (EQUAL(pszLatitudeType, "Planetocentric"))
3424
143
        pszLatitudeType = "Planetocentric";
3425
0
    else if (EQUAL(pszLatitudeType, "Planetographic"))
3426
0
        pszLatitudeType = "Planetographic";
3427
143
    CPLCreateXMLElementAndValue(psGM, (osPrefix + "latitude_type").c_str(),
3428
143
                                pszLatitudeType);
3429
3430
143
    const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
3431
143
    if (pszDatum && STARTS_WITH(pszDatum, "D_"))
3432
0
    {
3433
0
        CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
3434
0
                                    pszDatum + 2);
3435
0
    }
3436
143
    else if (pszDatum)
3437
143
    {
3438
143
        CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
3439
143
                                    pszDatum);
3440
143
    }
3441
3442
143
    double dfSemiMajor = m_oSRS.GetSemiMajor();
3443
143
    double dfSemiMinor = m_oSRS.GetSemiMinor();
3444
143
    const char *pszRadii = CSLFetchNameValue(m_papszCreationOptions, "RADII");
3445
143
    if (pszRadii)
3446
0
    {
3447
0
        char **papszTokens = CSLTokenizeString2(pszRadii, " ,", 0);
3448
0
        if (CSLCount(papszTokens) == 2)
3449
0
        {
3450
0
            dfSemiMajor = CPLAtof(papszTokens[0]);
3451
0
            dfSemiMinor = CPLAtof(papszTokens[1]);
3452
0
        }
3453
0
        CSLDestroy(papszTokens);
3454
0
    }
3455
3456
143
    const bool bUseLDD1930RadiusNames =
3457
143
        IsCARTVersionGTE(pszCARTVersion, "1B10_1930");
3458
3459
143
    CPLAddXMLAttributeAndValue(
3460
143
        CPLCreateXMLElementAndValue(
3461
143
            psGM,
3462
143
            (osPrefix +
3463
143
             (bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius"))
3464
143
                .c_str(),
3465
143
            CPLSPrintf("%.17g", dfSemiMajor)),
3466
143
        "unit", "m");
3467
    // No, this is not a bug. The PDS4  b_axis_radius/semi_minor_radius is the
3468
    // minor radius on the equatorial plane. Which in WKT doesn't really exist,
3469
    // so reuse the WKT semi major
3470
143
    CPLAddXMLAttributeAndValue(
3471
143
        CPLCreateXMLElementAndValue(
3472
143
            psGM,
3473
143
            (osPrefix +
3474
143
             (bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius"))
3475
143
                .c_str(),
3476
143
            CPLSPrintf("%.17g", dfSemiMajor)),
3477
143
        "unit", "m");
3478
143
    CPLAddXMLAttributeAndValue(
3479
143
        CPLCreateXMLElementAndValue(
3480
143
            psGM,
3481
143
            (osPrefix +
3482
143
             (bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius"))
3483
143
                .c_str(),
3484
143
            CPLSPrintf("%.17g", dfSemiMinor)),
3485
143
        "unit", "m");
3486
3487
    // Fix case
3488
143
    if (EQUAL(pszLongitudeDirection, "Positive East"))
3489
143
        pszLongitudeDirection = "Positive East";
3490
0
    else if (EQUAL(pszLongitudeDirection, "Positive West"))
3491
0
        pszLongitudeDirection = "Positive West";
3492
143
    CPLCreateXMLElementAndValue(psGM,
3493
143
                                (osPrefix + "longitude_direction").c_str(),
3494
143
                                pszLongitudeDirection);
3495
143
}
3496
3497
/************************************************************************/
3498
/*                        SubstituteVariables()                         */
3499
/************************************************************************/
3500
3501
void PDS4Dataset::SubstituteVariables(CPLXMLNode *psNode, char **papszDict)
3502
34.3k
{
3503
34.3k
    if (psNode->eType == CXT_Text && psNode->pszValue &&
3504
13.6k
        strstr(psNode->pszValue, "${"))
3505
3.52k
    {
3506
3.52k
        CPLString osVal(psNode->pszValue);
3507
3508
3.52k
        if (strstr(psNode->pszValue, "${TITLE}") != nullptr &&
3509
440
            CSLFetchNameValue(papszDict, "VAR_TITLE") == nullptr)
3510
440
        {
3511
440
            const CPLString osTitle(CPLGetFilename(GetDescription()));
3512
440
            CPLError(CE_Warning, CPLE_AppDefined,
3513
440
                     "VAR_TITLE not defined. Using %s by default",
3514
440
                     osTitle.c_str());
3515
440
            osVal.replaceAll("${TITLE}", osTitle);
3516
440
        }
3517
3518
6.96k
        for (char **papszIter = papszDict; papszIter && *papszIter; papszIter++)
3519
3.44k
        {
3520
3.44k
            if (STARTS_WITH_CI(*papszIter, "VAR_"))
3521
2.41k
            {
3522
2.41k
                char *pszKey = nullptr;
3523
2.41k
                const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
3524
2.41k
                if (pszKey && pszValue)
3525
2.41k
                {
3526
2.41k
                    const char *pszVarName = pszKey + strlen("VAR_");
3527
2.41k
                    osVal.replaceAll(
3528
2.41k
                        (CPLString("${") + pszVarName + "}").c_str(), pszValue);
3529
2.41k
                    osVal.replaceAll(
3530
2.41k
                        CPLString(CPLString("${") + pszVarName + "}")
3531
2.41k
                            .tolower()
3532
2.41k
                            .c_str(),
3533
2.41k
                        CPLString(pszValue).tolower());
3534
2.41k
                    CPLFree(pszKey);
3535
2.41k
                }
3536
2.41k
            }
3537
3.44k
        }
3538
3.52k
        if (osVal.find("${") != std::string::npos)
3539
2.62k
        {
3540
2.62k
            CPLError(CE_Warning, CPLE_AppDefined, "%s could not be substituted",
3541
2.62k
                     osVal.c_str());
3542
2.62k
        }
3543
3.52k
        CPLFree(psNode->pszValue);
3544
3.52k
        psNode->pszValue = CPLStrdup(osVal);
3545
3.52k
    }
3546
3547
68.2k
    for (CPLXMLNode *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
3548
33.8k
    {
3549
33.8k
        SubstituteVariables(psIter, papszDict);
3550
33.8k
    }
3551
34.3k
}
3552
3553
/************************************************************************/
3554
/*                           InitImageFile()                            */
3555
/************************************************************************/
3556
3557
bool PDS4Dataset::InitImageFile()
3558
0
{
3559
0
    m_bMustInitImageFile = false;
3560
3561
0
    int bHasNoData = FALSE;
3562
0
    double dfNoData = 0;
3563
0
    int bHasNoDataAsInt64 = FALSE;
3564
0
    int64_t nNoDataInt64 = 0;
3565
0
    int bHasNoDataAsUInt64 = FALSE;
3566
0
    uint64_t nNoDataUInt64 = 0;
3567
0
    const GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
3568
0
    if (eDT == GDT_Int64)
3569
0
    {
3570
0
        nNoDataInt64 =
3571
0
            GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoDataAsInt64);
3572
0
        if (!bHasNoDataAsInt64)
3573
0
            nNoDataInt64 = 0;
3574
0
    }
3575
0
    else if (eDT == GDT_UInt64)
3576
0
    {
3577
0
        nNoDataUInt64 =
3578
0
            GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoDataAsUInt64);
3579
0
        if (!bHasNoDataAsUInt64)
3580
0
            nNoDataUInt64 = 0;
3581
0
    }
3582
0
    else
3583
0
    {
3584
0
        dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
3585
0
        if (!bHasNoData)
3586
0
            dfNoData = 0;
3587
0
    }
3588
3589
0
    if (m_poExternalDS)
3590
0
    {
3591
0
        int nBlockXSize, nBlockYSize;
3592
0
        GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
3593
0
        const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
3594
0
        const int nBlockSizeBytes = nBlockXSize * nBlockYSize * nDTSize;
3595
0
        const int l_nBlocksPerColumn = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
3596
3597
0
        if (nBands == 1 || EQUAL(m_osInterleave, "BSQ"))
3598
0
        {
3599
            // We need to make sure that blocks are written in the right order
3600
0
            for (int i = 0; i < nBands; i++)
3601
0
            {
3602
0
                if (eDT == GDT_Int64)
3603
0
                {
3604
0
                    if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
3605
0
                            GF_Write, 0, 0, nRasterXSize, nRasterYSize,
3606
0
                            &nNoDataInt64, 1, 1, eDT, 0, 0, nullptr) != CE_None)
3607
0
                    {
3608
0
                        return false;
3609
0
                    }
3610
0
                }
3611
0
                else if (eDT == GDT_UInt64)
3612
0
                {
3613
0
                    if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
3614
0
                            GF_Write, 0, 0, nRasterXSize, nRasterYSize,
3615
0
                            &nNoDataUInt64, 1, 1, eDT, 0, 0,
3616
0
                            nullptr) != CE_None)
3617
0
                    {
3618
0
                        return false;
3619
0
                    }
3620
0
                }
3621
0
                else
3622
0
                {
3623
0
                    if (m_poExternalDS->GetRasterBand(i + 1)->Fill(dfNoData) !=
3624
0
                        CE_None)
3625
0
                    {
3626
0
                        return false;
3627
0
                    }
3628
0
                }
3629
0
            }
3630
0
            m_poExternalDS->FlushCache(false);
3631
3632
            // Check that blocks are effectively written in expected order.
3633
0
            GIntBig nLastOffset = 0;
3634
0
            for (int i = 0; i < nBands; i++)
3635
0
            {
3636
0
                for (int y = 0; y < l_nBlocksPerColumn; y++)
3637
0
                {
3638
0
                    const char *pszBlockOffset =
3639
0
                        m_poExternalDS->GetRasterBand(i + 1)->GetMetadataItem(
3640
0
                            CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
3641
0
                    if (pszBlockOffset)
3642
0
                    {
3643
0
                        GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
3644
0
                        if (i != 0 || y != 0)
3645
0
                        {
3646
0
                            if (nOffset != nLastOffset + nBlockSizeBytes)
3647
0
                            {
3648
0
                                CPLError(CE_Warning, CPLE_AppDefined,
3649
0
                                         "Block %d,%d band %d not at expected "
3650
0
                                         "offset",
3651
0
                                         0, y, i + 1);
3652
0
                                return false;
3653
0
                            }
3654
0
                        }
3655
0
                        nLastOffset = nOffset;
3656
0
                    }
3657
0
                    else
3658
0
                    {
3659
0
                        CPLError(CE_Warning, CPLE_AppDefined,
3660
0
                                 "Block %d,%d band %d not at expected "
3661
0
                                 "offset",
3662
0
                                 0, y, i + 1);
3663
0
                        return false;
3664
0
                    }
3665
0
                }
3666
0
            }
3667
0
        }
3668
0
        else
3669
0
        {
3670
0
            void *pBlockData = VSI_MALLOC_VERBOSE(nBlockSizeBytes);
3671
0
            if (pBlockData == nullptr)
3672
0
                return false;
3673
0
            if (eDT == GDT_Int64)
3674
0
            {
3675
0
                GDALCopyWords(&nNoDataInt64, eDT, 0, pBlockData, eDT, nDTSize,
3676
0
                              nBlockXSize * nBlockYSize);
3677
0
            }
3678
0
            else if (eDT == GDT_UInt64)
3679
0
            {
3680
0
                GDALCopyWords(&nNoDataUInt64, eDT, 0, pBlockData, eDT, nDTSize,
3681
0
                              nBlockXSize * nBlockYSize);
3682
0
            }
3683
0
            else
3684
0
            {
3685
0
                GDALCopyWords(&dfNoData, GDT_Float64, 0, pBlockData, eDT,
3686
0
                              nDTSize, nBlockXSize * nBlockYSize);
3687
0
            }
3688
0
            for (int y = 0; y < l_nBlocksPerColumn; y++)
3689
0
            {
3690
0
                for (int i = 0; i < nBands; i++)
3691
0
                {
3692
0
                    if (m_poExternalDS->GetRasterBand(i + 1)->WriteBlock(
3693
0
                            0, y, pBlockData) != CE_None)
3694
0
                    {
3695
0
                        VSIFree(pBlockData);
3696
0
                        return false;
3697
0
                    }
3698
0
                }
3699
0
            }
3700
0
            VSIFree(pBlockData);
3701
0
            m_poExternalDS->FlushCache(false);
3702
3703
            // Check that blocks are effectively written in expected order.
3704
0
            GIntBig nLastOffset = 0;
3705
0
            for (int y = 0; y < l_nBlocksPerColumn; y++)
3706
0
            {
3707
0
                const char *pszBlockOffset =
3708
0
                    m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
3709
0
                        CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
3710
0
                if (pszBlockOffset)
3711
0
                {
3712
0
                    GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
3713
0
                    if (y != 0)
3714
0
                    {
3715
0
                        if (nOffset !=
3716
0
                            nLastOffset +
3717
0
                                static_cast<GIntBig>(nBlockSizeBytes) * nBands)
3718
0
                        {
3719
0
                            CPLError(CE_Warning, CPLE_AppDefined,
3720
0
                                     "Block %d,%d not at expected "
3721
0
                                     "offset",
3722
0
                                     0, y);
3723
0
                            return false;
3724
0
                        }
3725
0
                    }
3726
0
                    nLastOffset = nOffset;
3727
0
                }
3728
0
                else
3729
0
                {
3730
0
                    CPLError(CE_Warning, CPLE_AppDefined,
3731
0
                             "Block %d,%d not at expected "
3732
0
                             "offset",
3733
0
                             0, y);
3734
0
                    return false;
3735
0
                }
3736
0
            }
3737
0
        }
3738
3739
0
        return true;
3740
0
    }
3741
3742
0
    const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
3743
0
    const vsi_l_offset nFileSize = static_cast<vsi_l_offset>(nRasterXSize) *
3744
0
                                   nRasterYSize * nBands * nDTSize;
3745
0
    if ((eDT == GDT_Int64 && (nNoDataInt64 == 0 || !bHasNoDataAsInt64)) ||
3746
0
        (eDT == GDT_UInt64 && (nNoDataUInt64 == 0 || !bHasNoDataAsUInt64)) ||
3747
0
        (eDT != GDT_Int64 && eDT != GDT_UInt64 &&
3748
0
         (dfNoData == 0 || !bHasNoData)))
3749
0
    {
3750
0
        if (VSIFTruncateL(m_fpImage, nFileSize) != 0)
3751
0
        {
3752
0
            CPLError(CE_Failure, CPLE_FileIO,
3753
0
                     "Cannot create file of size " CPL_FRMT_GUIB " bytes",
3754
0
                     nFileSize);
3755
0
            return false;
3756
0
        }
3757
0
    }
3758
0
    else
3759
0
    {
3760
0
        size_t nLineSize = static_cast<size_t>(nRasterXSize) * nDTSize;
3761
0
        void *pData = VSI_MALLOC_VERBOSE(nLineSize);
3762
0
        if (pData == nullptr)
3763
0
            return false;
3764
0
        if (eDT == GDT_Int64)
3765
0
        {
3766
0
            GDALCopyWords(&nNoDataInt64, eDT, 0, pData, eDT, nDTSize,
3767
0
                          nRasterXSize);
3768
0
        }
3769
0
        else if (eDT == GDT_UInt64)
3770
0
        {
3771
0
            GDALCopyWords(&nNoDataUInt64, eDT, 0, pData, eDT, nDTSize,
3772
0
                          nRasterXSize);
3773
0
        }
3774
0
        else
3775
0
        {
3776
0
            GDALCopyWords(&dfNoData, GDT_Float64, 0, pData, eDT, nDTSize,
3777
0
                          nRasterXSize);
3778
0
        }
3779
#ifdef CPL_MSB
3780
        if (GDALDataTypeIsComplex(eDT))
3781
        {
3782
            GDALSwapWords(pData, nDTSize / 2, nRasterXSize * 2, nDTSize / 2);
3783
        }
3784
        else
3785
        {
3786
            GDALSwapWords(pData, nDTSize, nRasterXSize, nDTSize);
3787
        }
3788
#endif
3789
0
        for (vsi_l_offset i = 0;
3790
0
             i < static_cast<vsi_l_offset>(nRasterYSize) * nBands; i++)
3791
0
        {
3792
0
            size_t nBytesWritten = VSIFWriteL(pData, 1, nLineSize, m_fpImage);
3793
0
            if (nBytesWritten != nLineSize)
3794
0
            {
3795
0
                CPLError(CE_Failure, CPLE_FileIO,
3796
0
                         "Cannot create file of size " CPL_FRMT_GUIB " bytes",
3797
0
                         nFileSize);
3798
0
                VSIFree(pData);
3799
0
                return false;
3800
0
            }
3801
0
        }
3802
0
        VSIFree(pData);
3803
0
    }
3804
0
    return true;
3805
0
}
3806
3807
/************************************************************************/
3808
/*                        GetSpecialConstants()                         */
3809
/************************************************************************/
3810
3811
static CPLXMLNode *GetSpecialConstants(const CPLString &osPrefix,
3812
                                       CPLXMLNode *psFileAreaObservational)
3813
0
{
3814
0
    for (CPLXMLNode *psIter = psFileAreaObservational->psChild; psIter;
3815
0
         psIter = psIter->psNext)
3816
0
    {
3817
0
        if (psIter->eType == CXT_Element &&
3818
0
            STARTS_WITH(psIter->pszValue, (osPrefix + "Array").c_str()))
3819
0
        {
3820
0
            CPLXMLNode *psSC =
3821
0
                CPLGetXMLNode(psIter, (osPrefix + "Special_Constants").c_str());
3822
0
            if (psSC)
3823
0
            {
3824
0
                CPLXMLNode *psNext = psSC->psNext;
3825
0
                psSC->psNext = nullptr;
3826
0
                CPLXMLNode *psRet = CPLCloneXMLTree(psSC);
3827
0
                psSC->psNext = psNext;
3828
0
                return psRet;
3829
0
            }
3830
0
        }
3831
0
    }
3832
0
    return nullptr;
3833
0
}
3834
3835
/************************************************************************/
3836
/*                       WriteHeaderAppendCase()                        */
3837
/************************************************************************/
3838
3839
void PDS4Dataset::WriteHeaderAppendCase()
3840
0
{
3841
0
    CPLXMLTreeCloser oCloser(CPLParseXMLFile(GetDescription()));
3842
0
    CPLXMLNode *psRoot = oCloser.get();
3843
0
    if (psRoot == nullptr)
3844
0
        return;
3845
0
    CPLString osPrefix;
3846
0
    CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
3847
0
    if (psProduct == nullptr)
3848
0
    {
3849
0
        psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
3850
0
        if (psProduct)
3851
0
            osPrefix = "pds:";
3852
0
    }
3853
0
    if (psProduct == nullptr)
3854
0
    {
3855
0
        CPLError(CE_Failure, CPLE_AppDefined,
3856
0
                 "Cannot find Product_Observational element");
3857
0
        return;
3858
0
    }
3859
0
    CPLXMLNode *psFAO = CPLGetXMLNode(
3860
0
        psProduct, (osPrefix + "File_Area_Observational").c_str());
3861
0
    if (psFAO == nullptr)
3862
0
    {
3863
0
        CPLError(CE_Failure, CPLE_AppDefined,
3864
0
                 "Cannot find File_Area_Observational element");
3865
0
        return;
3866
0
    }
3867
3868
0
    WriteArray(osPrefix, psFAO, nullptr, nullptr);
3869
3870
0
    CPLSerializeXMLTreeToFile(psRoot, GetDescription());
3871
0
}
3872
3873
/************************************************************************/
3874
/*                             WriteArray()                             */
3875
/************************************************************************/
3876
3877
void PDS4Dataset::WriteArray(const CPLString &osPrefix, CPLXMLNode *psFAO,
3878
                             const char *pszLocalIdentifierDefault,
3879
                             CPLXMLNode *psTemplateSpecialConstants)
3880
16
{
3881
16
    const char *pszArrayType = CSLFetchNameValueDef(
3882
16
        m_papszCreationOptions, "ARRAY_TYPE", "Array_3D_Image");
3883
16
    const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
3884
16
    CPLXMLNode *psArray =
3885
16
        CPLCreateXMLNode(psFAO, CXT_Element, (osPrefix + pszArrayType).c_str());
3886
3887
16
    const char *pszLocalIdentifier = CSLFetchNameValueDef(
3888
16
        m_papszCreationOptions, "ARRAY_IDENTIFIER", pszLocalIdentifierDefault);
3889
16
    if (pszLocalIdentifier)
3890
16
    {
3891
16
        CPLCreateXMLElementAndValue(psArray,
3892
16
                                    (osPrefix + "local_identifier").c_str(),
3893
16
                                    pszLocalIdentifier);
3894
16
    }
3895
3896
16
    GUIntBig nOffset = m_nBaseOffset;
3897
16
    if (m_poExternalDS)
3898
0
    {
3899
0
        const char *pszOffset =
3900
0
            m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
3901
0
                "BLOCK_OFFSET_0_0", "TIFF");
3902
0
        if (pszOffset)
3903
0
            nOffset = CPLAtoGIntBig(pszOffset);
3904
0
    }
3905
16
    CPLAddXMLAttributeAndValue(
3906
16
        CPLCreateXMLElementAndValue(psArray, (osPrefix + "offset").c_str(),
3907
16
                                    CPLSPrintf(CPL_FRMT_GUIB, nOffset)),
3908
16
        "unit", "byte");
3909
16
    CPLCreateXMLElementAndValue(psArray, (osPrefix + "axes").c_str(),
3910
16
                                (bIsArray2D) ? "2" : "3");
3911
16
    CPLCreateXMLElementAndValue(
3912
16
        psArray, (osPrefix + "axis_index_order").c_str(), "Last Index Fastest");
3913
16
    CPLXMLNode *psElementArray = CPLCreateXMLNode(
3914
16
        psArray, CXT_Element, (osPrefix + "Element_Array").c_str());
3915
16
    GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
3916
16
    const char *pszDataType =
3917
16
        (eDT == GDT_UInt8)    ? "UnsignedByte"
3918
16
        : (eDT == GDT_Int8)   ? "SignedByte"
3919
10
        : (eDT == GDT_UInt16) ? "UnsignedLSB2"
3920
9
        : (eDT == GDT_Int16)  ? (m_bIsLSB ? "SignedLSB2" : "SignedMSB2")
3921
9
        : (eDT == GDT_UInt32) ? (m_bIsLSB ? "UnsignedLSB4" : "UnsignedMSB4")
3922
2
        : (eDT == GDT_Int32)  ? (m_bIsLSB ? "SignedLSB4" : "SignedMSB4")
3923
2
        : (eDT == GDT_UInt64) ? (m_bIsLSB ? "UnsignedLSB8" : "UnsignedMSB8")
3924
2
        : (eDT == GDT_Int64)  ? (m_bIsLSB ? "SignedLSB8" : "SignedMSB8")
3925
2
        : (eDT == GDT_Float32)
3926
2
            ? (m_bIsLSB ? "IEEE754LSBSingle" : "IEEE754MSBSingle")
3927
2
        : (eDT == GDT_Float64)
3928
2
            ? (m_bIsLSB ? "IEEE754LSBDouble" : "IEEE754MSBDouble")
3929
2
        : (eDT == GDT_CFloat32) ? (m_bIsLSB ? "ComplexLSB8" : "ComplexMSB8")
3930
0
        : (eDT == GDT_CFloat64) ? (m_bIsLSB ? "ComplexLSB16" : "ComplexMSB16")
3931
0
                                : "should not happen";
3932
16
    CPLCreateXMLElementAndValue(psElementArray,
3933
16
                                (osPrefix + "data_type").c_str(), pszDataType);
3934
3935
16
    const char *pszUnits = GetRasterBand(1)->GetUnitType();
3936
16
    const char *pszUnitsCO = CSLFetchNameValue(m_papszCreationOptions, "UNIT");
3937
16
    if (pszUnitsCO)
3938
0
    {
3939
0
        pszUnits = pszUnitsCO;
3940
0
    }
3941
16
    if (pszUnits && pszUnits[0] != 0)
3942
0
    {
3943
0
        CPLCreateXMLElementAndValue(psElementArray, (osPrefix + "unit").c_str(),
3944
0
                                    pszUnits);
3945
0
    }
3946
3947
16
    int bHasScale = FALSE;
3948
16
    double dfScale = GetRasterBand(1)->GetScale(&bHasScale);
3949
16
    if (bHasScale && dfScale != 1.0)
3950
7
    {
3951
7
        CPLCreateXMLElementAndValue(psElementArray,
3952
7
                                    (osPrefix + "scaling_factor").c_str(),
3953
7
                                    CPLSPrintf("%.17g", dfScale));
3954
7
    }
3955
3956
16
    int bHasOffset = FALSE;
3957
16
    double dfOffset = GetRasterBand(1)->GetOffset(&bHasOffset);
3958
16
    if (bHasOffset && dfOffset != 1.0)
3959
7
    {
3960
7
        CPLCreateXMLElementAndValue(psElementArray,
3961
7
                                    (osPrefix + "value_offset").c_str(),
3962
7
                                    CPLSPrintf("%.17g", dfOffset));
3963
7
    }
3964
3965
    // Axis definitions
3966
16
    {
3967
16
        CPLXMLNode *psAxis = CPLCreateXMLNode(
3968
16
            psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
3969
16
        CPLCreateXMLElementAndValue(
3970
16
            psAxis, (osPrefix + "axis_name").c_str(),
3971
16
            EQUAL(m_osInterleave, "BSQ")
3972
16
                ? "Band"
3973
16
                :
3974
                /* EQUAL(m_osInterleave, "BIL") ? "Line" : */
3975
16
                "Line");
3976
16
        CPLCreateXMLElementAndValue(
3977
16
            psAxis, (osPrefix + "elements").c_str(),
3978
16
            CPLSPrintf("%d",
3979
16
                       EQUAL(m_osInterleave, "BSQ")
3980
16
                           ? nBands
3981
16
                           :
3982
                           /* EQUAL(m_osInterleave, "BIL") ? nRasterYSize : */
3983
16
                           nRasterYSize));
3984
16
        CPLCreateXMLElementAndValue(
3985
16
            psAxis, (osPrefix + "sequence_number").c_str(), "1");
3986
16
    }
3987
16
    {
3988
16
        CPLXMLNode *psAxis = CPLCreateXMLNode(
3989
16
            psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
3990
16
        CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
3991
16
                                    EQUAL(m_osInterleave, "BSQ")   ? "Line"
3992
16
                                    : EQUAL(m_osInterleave, "BIL") ? "Band"
3993
4
                                                                   : "Sample");
3994
16
        CPLCreateXMLElementAndValue(
3995
16
            psAxis, (osPrefix + "elements").c_str(),
3996
16
            CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterYSize
3997
16
                             : EQUAL(m_osInterleave, "BIL") ? nBands
3998
4
                                                            : nRasterXSize));
3999
16
        CPLCreateXMLElementAndValue(
4000
16
            psAxis, (osPrefix + "sequence_number").c_str(), "2");
4001
16
    }
4002
16
    if (!bIsArray2D)
4003
16
    {
4004
16
        CPLXMLNode *psAxis = CPLCreateXMLNode(
4005
16
            psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
4006
16
        CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
4007
16
                                    EQUAL(m_osInterleave, "BSQ")   ? "Sample"
4008
16
                                    : EQUAL(m_osInterleave, "BIL") ? "Sample"
4009
4
                                                                   : "Band");
4010
16
        CPLCreateXMLElementAndValue(
4011
16
            psAxis, (osPrefix + "elements").c_str(),
4012
16
            CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterXSize
4013
16
                             : EQUAL(m_osInterleave, "BIL") ? nRasterXSize
4014
4
                                                            : nBands));
4015
16
        CPLCreateXMLElementAndValue(
4016
16
            psAxis, (osPrefix + "sequence_number").c_str(), "3");
4017
16
    }
4018
4019
16
    int bHasNoData = FALSE;
4020
16
    int64_t nNoDataInt64 = 0;
4021
16
    uint64_t nNoDataUInt64 = 0;
4022
16
    double dfNoData = 0;
4023
16
    if (eDT == GDT_Int64)
4024
0
        nNoDataInt64 = GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoData);
4025
16
    else if (eDT == GDT_UInt64)
4026
0
        nNoDataUInt64 = GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoData);
4027
16
    else
4028
16
        dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
4029
4030
16
    std::string osNoData;
4031
16
    if (bHasNoData)
4032
2
    {
4033
2
        if (eDT == GDT_Int64)
4034
0
            osNoData = std::to_string(nNoDataInt64);
4035
2
        else if (eDT == GDT_UInt64)
4036
0
            osNoData = std::to_string(nNoDataUInt64);
4037
2
        else if (GDALDataTypeIsInteger(GetRasterBand(1)->GetRasterDataType()))
4038
1
            osNoData = std::to_string(static_cast<int64_t>(dfNoData));
4039
1
        else if (eDT == GDT_Float32)
4040
0
        {
4041
0
            uint32_t nVal;
4042
0
            float fVal = static_cast<float>(dfNoData);
4043
0
            memcpy(&nVal, &fVal, sizeof(nVal));
4044
0
            osNoData = CPLSPrintf("0x%08X", nVal);
4045
0
        }
4046
1
        else
4047
1
        {
4048
1
            uint64_t nVal;
4049
1
            memcpy(&nVal, &dfNoData, sizeof(nVal));
4050
1
            osNoData =
4051
1
                CPLSPrintf("0x%16llX", static_cast<unsigned long long>(nVal));
4052
1
        }
4053
2
    }
4054
4055
16
    if (psTemplateSpecialConstants)
4056
0
    {
4057
0
        CPLAddXMLChild(psArray, psTemplateSpecialConstants);
4058
0
        if (bHasNoData)
4059
0
        {
4060
0
            CPLXMLNode *psMC =
4061
0
                CPLGetXMLNode(psTemplateSpecialConstants,
4062
0
                              (osPrefix + "missing_constant").c_str());
4063
0
            if (psMC != nullptr)
4064
0
            {
4065
0
                if (psMC->psChild && psMC->psChild->eType == CXT_Text)
4066
0
                {
4067
0
                    CPLFree(psMC->psChild->pszValue);
4068
0
                    psMC->psChild->pszValue = CPLStrdup(osNoData.c_str());
4069
0
                }
4070
0
            }
4071
0
            else
4072
0
            {
4073
0
                CPLXMLNode *psSaturatedConstant =
4074
0
                    CPLGetXMLNode(psTemplateSpecialConstants,
4075
0
                                  (osPrefix + "saturated_constant").c_str());
4076
0
                psMC = CPLCreateXMLElementAndValue(
4077
0
                    nullptr, (osPrefix + "missing_constant").c_str(),
4078
0
                    osNoData.c_str());
4079
0
                CPLXMLNode *psNext;
4080
0
                if (psSaturatedConstant)
4081
0
                {
4082
0
                    psNext = psSaturatedConstant->psNext;
4083
0
                    psSaturatedConstant->psNext = psMC;
4084
0
                }
4085
0
                else
4086
0
                {
4087
0
                    psNext = psTemplateSpecialConstants->psChild;
4088
0
                    psTemplateSpecialConstants->psChild = psMC;
4089
0
                }
4090
0
                psMC->psNext = psNext;
4091
0
            }
4092
0
        }
4093
0
    }
4094
16
    else if (bHasNoData)
4095
2
    {
4096
2
        CPLXMLNode *psSC = CPLCreateXMLNode(
4097
2
            psArray, CXT_Element, (osPrefix + "Special_Constants").c_str());
4098
2
        CPLCreateXMLElementAndValue(
4099
2
            psSC, (osPrefix + "missing_constant").c_str(), osNoData.c_str());
4100
2
    }
4101
16
}
4102
4103
/************************************************************************/
4104
/*                         WriteVectorLayers()                          */
4105
/************************************************************************/
4106
4107
void PDS4Dataset::WriteVectorLayers(CPLXMLNode *psProduct)
4108
440
{
4109
440
    CPLString osPrefix;
4110
440
    if (STARTS_WITH(psProduct->pszValue, "pds:"))
4111
0
        osPrefix = "pds:";
4112
4113
440
    for (auto &poLayer : m_apoLayers)
4114
2.96k
    {
4115
2.96k
        if (!poLayer->IsDirtyHeader())
4116
0
            continue;
4117
4118
2.96k
        if (poLayer->GetFeatureCount(false) == 0)
4119
762
        {
4120
762
            CPLError(CE_Warning, CPLE_AppDefined,
4121
762
                     "Writing header for layer %s which has 0 features. "
4122
762
                     "This is not legal in PDS4",
4123
762
                     poLayer->GetName());
4124
762
        }
4125
4126
2.96k
        if (poLayer->GetRawFieldCount() == 0)
4127
65
        {
4128
65
            CPLError(CE_Warning, CPLE_AppDefined,
4129
65
                     "Writing header for layer %s which has 0 fields. "
4130
65
                     "This is not legal in PDS4",
4131
65
                     poLayer->GetName());
4132
65
        }
4133
4134
2.96k
        const std::string osRelativePath(
4135
2.96k
            CPLExtractRelativePath(CPLGetPathSafe(m_osXMLFilename).c_str(),
4136
2.96k
                                   poLayer->GetFileName(), nullptr));
4137
4138
2.96k
        bool bFound = false;
4139
46.4k
        for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
4140
43.4k
             psIter = psIter->psNext)
4141
43.4k
        {
4142
43.4k
            if (psIter->eType == CXT_Element &&
4143
25.6k
                strcmp(psIter->pszValue,
4144
25.6k
                       (osPrefix + "File_Area_Observational").c_str()) == 0)
4145
19.7k
            {
4146
19.7k
                const char *pszFilename = CPLGetXMLValue(
4147
19.7k
                    psIter,
4148
19.7k
                    (osPrefix + "File." + osPrefix + "file_name").c_str(), "");
4149
19.7k
                if (strcmp(pszFilename, osRelativePath.c_str()) == 0)
4150
0
                {
4151
0
                    poLayer->RefreshFileAreaObservational(psIter);
4152
0
                    bFound = true;
4153
0
                    break;
4154
0
                }
4155
19.7k
            }
4156
43.4k
        }
4157
2.96k
        if (!bFound)
4158
2.96k
        {
4159
2.96k
            CPLXMLNode *psFAO = CPLCreateXMLNode(
4160
2.96k
                psProduct, CXT_Element,
4161
2.96k
                (osPrefix + "File_Area_Observational").c_str());
4162
2.96k
            CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
4163
2.96k
                                                  (osPrefix + "File").c_str());
4164
2.96k
            CPLCreateXMLElementAndValue(psFile,
4165
2.96k
                                        (osPrefix + "file_name").c_str(),
4166
2.96k
                                        osRelativePath.c_str());
4167
2.96k
            poLayer->RefreshFileAreaObservational(psFAO);
4168
2.96k
        }
4169
2.96k
    }
4170
440
}
4171
4172
/************************************************************************/
4173
/*                            CreateHeader()                            */
4174
/************************************************************************/
4175
4176
void PDS4Dataset::CreateHeader(CPLXMLNode *psProduct,
4177
                               const char *pszCARTVersion)
4178
440
{
4179
440
    CPLString osPrefix;
4180
440
    if (STARTS_WITH(psProduct->pszValue, "pds:"))
4181
0
        osPrefix = "pds:";
4182
4183
440
    if (m_oSRS.IsEmpty() && GetLayerCount() >= 1)
4184
416
    {
4185
416
        if (const auto poSRS = GetLayer(0)->GetSpatialRef())
4186
163
            m_oSRS = *poSRS;
4187
416
    }
4188
4189
440
    if (!m_oSRS.IsEmpty() &&
4190
151
        CSLFetchNameValue(m_papszCreationOptions, "VAR_TARGET") == nullptr)
4191
151
    {
4192
151
        const char *pszTarget = nullptr;
4193
151
        if (fabs(m_oSRS.GetSemiMajor() - 6378137) < 0.001 * 6378137)
4194
151
        {
4195
151
            pszTarget = "Earth";
4196
151
            m_papszCreationOptions = CSLSetNameValue(
4197
151
                m_papszCreationOptions, "VAR_TARGET_TYPE", "Planet");
4198
151
        }
4199
0
        else
4200
0
        {
4201
0
            const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
4202
0
            if (pszDatum && STARTS_WITH(pszDatum, "D_"))
4203
0
            {
4204
0
                pszTarget = pszDatum + 2;
4205
0
            }
4206
0
            else if (pszDatum)
4207
0
            {
4208
0
                pszTarget = pszDatum;
4209
0
            }
4210
0
        }
4211
151
        if (pszTarget)
4212
151
        {
4213
151
            m_papszCreationOptions = CSLSetNameValue(m_papszCreationOptions,
4214
151
                                                     "VAR_TARGET", pszTarget);
4215
151
        }
4216
151
    }
4217
440
    SubstituteVariables(psProduct, m_papszCreationOptions);
4218
4219
    // Remove <Discipline_Area>/<disp:Display_Settings> if there is no raster
4220
440
    if (GetRasterCount() == 0)
4221
424
    {
4222
424
        CPLXMLNode *psDisciplineArea =
4223
424
            CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4224
424
                                      osPrefix + "Discipline_Area")
4225
424
                                         .c_str());
4226
424
        if (psDisciplineArea)
4227
424
        {
4228
424
            CPLXMLNode *psDisplaySettings =
4229
424
                CPLGetXMLNode(psDisciplineArea, "disp:Display_Settings");
4230
424
            if (psDisplaySettings)
4231
424
            {
4232
424
                CPLRemoveXMLChild(psDisciplineArea, psDisplaySettings);
4233
424
                CPLDestroyXMLNode(psDisplaySettings);
4234
424
            }
4235
424
        }
4236
424
    }
4237
4238
    // Depending on the version of the DISP schema, Local_Internal_Reference
4239
    // may be in the disp: namespace or the default one.
4240
440
    const auto GetLocalIdentifierReferenceFromDisciplineArea =
4241
440
        [](const CPLXMLNode *psDisciplineArea, const char *pszDefault)
4242
440
    {
4243
159
        return CPLGetXMLValue(
4244
159
            psDisciplineArea,
4245
159
            "disp:Display_Settings.Local_Internal_Reference."
4246
159
            "local_identifier_reference",
4247
159
            CPLGetXMLValue(
4248
159
                psDisciplineArea,
4249
159
                "disp:Display_Settings.disp:Local_Internal_Reference."
4250
159
                "local_identifier_reference",
4251
159
                pszDefault));
4252
159
    };
4253
4254
440
    if (GetRasterCount() || !m_oSRS.IsEmpty())
4255
157
    {
4256
157
        CPLXMLNode *psDisciplineArea =
4257
157
            CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4258
157
                                      osPrefix + "Discipline_Area")
4259
157
                                         .c_str());
4260
157
        if (GetRasterCount() && !(m_bGotTransform && !m_oSRS.IsEmpty()))
4261
14
        {
4262
            // if we have no georeferencing, strip any existing georeferencing
4263
            // from the template
4264
14
            if (psDisciplineArea)
4265
14
            {
4266
14
                CPLXMLNode *psCart =
4267
14
                    CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
4268
14
                if (psCart == nullptr)
4269
14
                    psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
4270
14
                if (psCart)
4271
0
                {
4272
0
                    CPLRemoveXMLChild(psDisciplineArea, psCart);
4273
0
                    CPLDestroyXMLNode(psCart);
4274
0
                }
4275
4276
14
                if (CPLGetXMLNode(psDisciplineArea,
4277
14
                                  "sp:Spectral_Characteristics"))
4278
0
                {
4279
0
                    const char *pszArrayType =
4280
0
                        CSLFetchNameValue(m_papszCreationOptions, "ARRAY_TYPE");
4281
                    // The schematron PDS4_SP_1100.sch requires that
4282
                    // sp:local_identifier_reference is used by
4283
                    // Array_[2D|3D]_Spectrum/pds:local_identifier
4284
0
                    if (pszArrayType == nullptr)
4285
0
                    {
4286
0
                        m_papszCreationOptions =
4287
0
                            CSLSetNameValue(m_papszCreationOptions,
4288
0
                                            "ARRAY_TYPE", "Array_3D_Spectrum");
4289
0
                    }
4290
0
                    else if (!EQUAL(pszArrayType, "Array_2D_Spectrum") &&
4291
0
                             !EQUAL(pszArrayType, "Array_3D_Spectrum"))
4292
0
                    {
4293
0
                        CPLError(CE_Warning, CPLE_AppDefined,
4294
0
                                 "PDS4_SP_xxxx.sch schematron requires the "
4295
0
                                 "use of ARRAY_TYPE=Array_2D_Spectrum or "
4296
0
                                 "Array_3D_Spectrum");
4297
0
                    }
4298
0
                }
4299
14
            }
4300
14
        }
4301
143
        else
4302
143
        {
4303
143
            if (psDisciplineArea == nullptr)
4304
0
            {
4305
0
                CPLXMLNode *psTI = CPLGetXMLNode(
4306
0
                    psProduct, (osPrefix + "Observation_Area." + osPrefix +
4307
0
                                "Target_Identification")
4308
0
                                   .c_str());
4309
0
                if (psTI == nullptr)
4310
0
                {
4311
0
                    CPLError(CE_Failure, CPLE_AppDefined,
4312
0
                             "Cannot find Target_Identification element in "
4313
0
                             "template");
4314
0
                    return;
4315
0
                }
4316
0
                psDisciplineArea =
4317
0
                    CPLCreateXMLNode(nullptr, CXT_Element,
4318
0
                                     (osPrefix + "Discipline_Area").c_str());
4319
0
                if (psTI->psNext)
4320
0
                    psDisciplineArea->psNext = psTI->psNext;
4321
0
                psTI->psNext = psDisciplineArea;
4322
0
            }
4323
143
            CPLXMLNode *psCart =
4324
143
                CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
4325
143
            if (psCart == nullptr)
4326
143
                psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
4327
143
            if (psCart == nullptr)
4328
143
            {
4329
143
                psCart = CPLCreateXMLNode(psDisciplineArea, CXT_Element,
4330
143
                                          "cart:Cartography");
4331
143
                if (CPLGetXMLNode(psProduct, "xmlns:cart") == nullptr)
4332
0
                {
4333
0
                    CPLXMLNode *psNS =
4334
0
                        CPLCreateXMLNode(nullptr, CXT_Attribute, "xmlns:cart");
4335
0
                    CPLCreateXMLNode(psNS, CXT_Text,
4336
0
                                     "http://pds.nasa.gov/pds4/cart/v1");
4337
0
                    CPLAddXMLChild(psProduct, psNS);
4338
0
                    CPLXMLNode *psSchemaLoc =
4339
0
                        CPLGetXMLNode(psProduct, "xsi:schemaLocation");
4340
0
                    if (psSchemaLoc != nullptr &&
4341
0
                        psSchemaLoc->psChild != nullptr &&
4342
0
                        psSchemaLoc->psChild->pszValue != nullptr)
4343
0
                    {
4344
0
                        CPLString osCartSchema;
4345
0
                        if (strstr(psSchemaLoc->psChild->pszValue,
4346
0
                                   "PDS4_PDS_1800.xsd"))
4347
0
                        {
4348
                            // GDAL 2.4
4349
0
                            osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4350
0
                                           "PDS4_CART_1700.xsd";
4351
0
                            pszCARTVersion = "1700";
4352
0
                        }
4353
0
                        else if (strstr(psSchemaLoc->psChild->pszValue,
4354
0
                                        "PDS4_PDS_1B00.xsd"))
4355
0
                        {
4356
                            // GDAL 3.0
4357
0
                            osCartSchema =
4358
0
                                "https://raw.githubusercontent.com/"
4359
0
                                "nasa-pds-data-dictionaries/ldd-cart/master/"
4360
0
                                "build/1.B.0.0/PDS4_CART_1B00.xsd";
4361
0
                            pszCARTVersion = "1B00";
4362
0
                        }
4363
0
                        else if (strstr(psSchemaLoc->psChild->pszValue,
4364
0
                                        "PDS4_PDS_1D00.xsd"))
4365
0
                        {
4366
                            // GDAL 3.1
4367
0
                            osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4368
0
                                           "PDS4_CART_1D00_1933.xsd";
4369
0
                            pszCARTVersion = "1D00_1933";
4370
0
                        }
4371
0
                        else if (strstr(psSchemaLoc->psChild->pszValue,
4372
0
                                        "PDS4_PDS_1G00_1950.xsd"))
4373
0
                        {
4374
                            // GDAL 3.4
4375
0
                            osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
4376
0
                                           "PDS4_CART_1G00_1950.xsd";
4377
0
                            pszCARTVersion = "1G00_1950";
4378
0
                        }
4379
0
                        else
4380
0
                        {
4381
                            // GDAL 3.12
4382
0
                            osCartSchema =
4383
0
                                "https://pds.nasa.gov/pds4/cart/v1/"
4384
0
                                "PDS4_CART_" CURRENT_CART_VERSION ".xsd";
4385
0
                            pszCARTVersion = CURRENT_CART_VERSION;
4386
0
                        }
4387
0
                        CPLString osNewVal(psSchemaLoc->psChild->pszValue);
4388
0
                        osNewVal +=
4389
0
                            " http://pds.nasa.gov/pds4/cart/v1 " + osCartSchema;
4390
0
                        CPLFree(psSchemaLoc->psChild->pszValue);
4391
0
                        psSchemaLoc->psChild->pszValue = CPLStrdup(osNewVal);
4392
0
                    }
4393
0
                }
4394
143
            }
4395
0
            else
4396
0
            {
4397
0
                if (psCart->psChild)
4398
0
                {
4399
0
                    CPLDestroyXMLNode(psCart->psChild);
4400
0
                    psCart->psChild = nullptr;
4401
0
                }
4402
0
            }
4403
4404
143
            if (IsCARTVersionGTE(pszCARTVersion, "1900"))
4405
143
            {
4406
143
                const char *pszLocalIdentifier =
4407
143
                    GetLocalIdentifierReferenceFromDisciplineArea(
4408
143
                        psDisciplineArea,
4409
143
                        GetRasterCount() == 0 && GetLayerCount() > 0
4410
143
                            ? GetLayer(0)->GetName()
4411
143
                            : "image");
4412
143
                CPLXMLNode *psLIR = CPLCreateXMLNode(
4413
143
                    psCart, CXT_Element,
4414
143
                    (osPrefix + "Local_Internal_Reference").c_str());
4415
143
                CPLCreateXMLElementAndValue(
4416
143
                    psLIR, (osPrefix + "local_identifier_reference").c_str(),
4417
143
                    pszLocalIdentifier);
4418
143
                CPLCreateXMLElementAndValue(
4419
143
                    psLIR, (osPrefix + "local_reference_type").c_str(),
4420
143
                    "cartography_parameters_to_image_object");
4421
143
            }
4422
4423
143
            WriteGeoreferencing(psCart, pszCARTVersion);
4424
143
        }
4425
4426
157
        const char *pszVertDir = CSLFetchNameValue(
4427
157
            m_papszCreationOptions, "VAR_VERTICAL_DISPLAY_DIRECTION");
4428
157
        if (pszVertDir)
4429
0
        {
4430
0
            CPLXMLNode *psVertDirNode =
4431
0
                CPLGetXMLNode(psDisciplineArea,
4432
0
                              "disp:Display_Settings.disp:Display_Direction."
4433
0
                              "disp:vertical_display_direction");
4434
0
            if (psVertDirNode == nullptr)
4435
0
            {
4436
0
                CPLError(
4437
0
                    CE_Warning, CPLE_AppDefined,
4438
0
                    "PDS4 template lacks a disp:vertical_display_direction "
4439
0
                    "element where to write %s",
4440
0
                    pszVertDir);
4441
0
            }
4442
0
            else
4443
0
            {
4444
0
                CPLDestroyXMLNode(psVertDirNode->psChild);
4445
0
                psVertDirNode->psChild =
4446
0
                    CPLCreateXMLNode(nullptr, CXT_Text, pszVertDir);
4447
0
            }
4448
0
        }
4449
157
    }
4450
283
    else
4451
283
    {
4452
        // Remove Observation_Area.Discipline_Area if it contains only
4453
        // <disp:Display_Settings> or is empty
4454
283
        CPLXMLNode *psObservationArea =
4455
283
            CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area").c_str());
4456
283
        if (psObservationArea)
4457
283
        {
4458
283
            CPLXMLNode *psDisciplineArea = CPLGetXMLNode(
4459
283
                psObservationArea, (osPrefix + "Discipline_Area").c_str());
4460
283
            if (psDisciplineArea &&
4461
283
                (psDisciplineArea->psChild == nullptr ||
4462
0
                 (psDisciplineArea->psChild->eType == CXT_Element &&
4463
0
                  psDisciplineArea->psChild->psNext == nullptr &&
4464
0
                  strcmp(psDisciplineArea->psChild->pszValue,
4465
0
                         "disp:Display_Settings") == 0)))
4466
283
            {
4467
283
                CPLRemoveXMLChild(psObservationArea, psDisciplineArea);
4468
283
                CPLDestroyXMLNode(psDisciplineArea);
4469
283
            }
4470
283
        }
4471
283
    }
4472
4473
440
    if (m_bStripFileAreaObservationalFromTemplate)
4474
440
    {
4475
440
        m_bStripFileAreaObservationalFromTemplate = false;
4476
440
        CPLXMLNode *psObservationArea = nullptr;
4477
440
        CPLXMLNode *psPrev = nullptr;
4478
440
        CPLXMLNode *psTemplateSpecialConstants = nullptr;
4479
3.96k
        for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;)
4480
3.52k
        {
4481
3.52k
            if (psIter->eType == CXT_Element &&
4482
880
                psIter->pszValue == osPrefix + "Observation_Area")
4483
440
            {
4484
440
                psObservationArea = psIter;
4485
440
                psPrev = psIter;
4486
440
                psIter = psIter->psNext;
4487
440
            }
4488
3.08k
            else if (psIter->eType == CXT_Element &&
4489
440
                     (psIter->pszValue ==
4490
440
                          osPrefix + "File_Area_Observational" ||
4491
440
                      psIter->pszValue ==
4492
440
                          osPrefix + "File_Area_Observational_Supplemental"))
4493
0
            {
4494
0
                if (psIter->pszValue == osPrefix + "File_Area_Observational")
4495
0
                {
4496
0
                    psTemplateSpecialConstants =
4497
0
                        GetSpecialConstants(osPrefix, psIter);
4498
0
                }
4499
0
                if (psPrev)
4500
0
                    psPrev->psNext = psIter->psNext;
4501
0
                else
4502
0
                {
4503
0
                    CPLAssert(psProduct->psChild == psIter);
4504
0
                    psProduct->psChild = psIter->psNext;
4505
0
                }
4506
0
                CPLXMLNode *psNext = psIter->psNext;
4507
0
                psIter->psNext = nullptr;
4508
0
                CPLDestroyXMLNode(psIter);
4509
0
                psIter = psNext;
4510
0
            }
4511
3.08k
            else
4512
3.08k
            {
4513
3.08k
                psPrev = psIter;
4514
3.08k
                psIter = psIter->psNext;
4515
3.08k
            }
4516
3.52k
        }
4517
440
        if (psObservationArea == nullptr)
4518
0
        {
4519
0
            CPLError(CE_Failure, CPLE_AppDefined,
4520
0
                     "Cannot find Observation_Area in template");
4521
0
            CPLDestroyXMLNode(psTemplateSpecialConstants);
4522
0
            return;
4523
0
        }
4524
4525
440
        if (GetRasterCount())
4526
16
        {
4527
16
            CPLXMLNode *psFAOPrev = psObservationArea;
4528
16
            while (psFAOPrev->psNext != nullptr &&
4529
0
                   psFAOPrev->psNext->eType == CXT_Comment)
4530
0
            {
4531
0
                psFAOPrev = psFAOPrev->psNext;
4532
0
            }
4533
16
            if (psFAOPrev->psNext != nullptr)
4534
0
            {
4535
                // There may be an optional Reference_List element between
4536
                // Observation_Area and File_Area_Observational
4537
0
                if (!(psFAOPrev->psNext->eType == CXT_Element &&
4538
0
                      psFAOPrev->psNext->pszValue ==
4539
0
                          osPrefix + "Reference_List"))
4540
0
                {
4541
0
                    CPLError(CE_Failure, CPLE_AppDefined,
4542
0
                             "Unexpected content found after Observation_Area "
4543
0
                             "in template");
4544
0
                    CPLDestroyXMLNode(psTemplateSpecialConstants);
4545
0
                    return;
4546
0
                }
4547
0
                psFAOPrev = psFAOPrev->psNext;
4548
0
                while (psFAOPrev->psNext != nullptr &&
4549
0
                       psFAOPrev->psNext->eType == CXT_Comment)
4550
0
                {
4551
0
                    psFAOPrev = psFAOPrev->psNext;
4552
0
                }
4553
0
                if (psFAOPrev->psNext != nullptr)
4554
0
                {
4555
0
                    CPLError(CE_Failure, CPLE_AppDefined,
4556
0
                             "Unexpected content found after Reference_List in "
4557
0
                             "template");
4558
0
                    CPLDestroyXMLNode(psTemplateSpecialConstants);
4559
0
                    return;
4560
0
                }
4561
0
            }
4562
4563
16
            CPLXMLNode *psFAO = CPLCreateXMLNode(
4564
16
                nullptr, CXT_Element,
4565
16
                (osPrefix + "File_Area_Observational").c_str());
4566
16
            psFAOPrev->psNext = psFAO;
4567
4568
16
            CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
4569
16
                                                  (osPrefix + "File").c_str());
4570
16
            CPLCreateXMLElementAndValue(psFile,
4571
16
                                        (osPrefix + "file_name").c_str(),
4572
16
                                        CPLGetFilename(m_osImageFilename));
4573
16
            if (m_bCreatedFromExistingBinaryFile)
4574
0
            {
4575
0
                CPLCreateXMLNode(psFile, CXT_Comment, PREEXISTING_BINARY_FILE);
4576
0
            }
4577
16
            CPLXMLNode *psDisciplineArea =
4578
16
                CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
4579
16
                                          osPrefix + "Discipline_Area")
4580
16
                                             .c_str());
4581
16
            const char *pszLocalIdentifier =
4582
16
                GetLocalIdentifierReferenceFromDisciplineArea(psDisciplineArea,
4583
16
                                                              "image");
4584
4585
16
            if (m_poExternalDS && m_poExternalDS->GetDriver() &&
4586
0
                EQUAL(m_poExternalDS->GetDriver()->GetDescription(), "GTiff"))
4587
0
            {
4588
0
                VSILFILE *fpTemp =
4589
0
                    VSIFOpenL(m_poExternalDS->GetDescription(), "rb");
4590
0
                if (fpTemp)
4591
0
                {
4592
0
                    GByte abySignature[4] = {0};
4593
0
                    VSIFReadL(abySignature, 1, 4, fpTemp);
4594
0
                    VSIFCloseL(fpTemp);
4595
0
                    const bool bBigTIFF =
4596
0
                        abySignature[2] == 43 || abySignature[3] == 43;
4597
0
                    m_osHeaderParsingStandard =
4598
0
                        bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
4599
0
                    const char *pszOffset =
4600
0
                        m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
4601
0
                            "BLOCK_OFFSET_0_0", "TIFF");
4602
0
                    if (pszOffset)
4603
0
                        m_nBaseOffset = CPLAtoGIntBig(pszOffset);
4604
0
                }
4605
0
            }
4606
4607
16
            if (!m_osHeaderParsingStandard.empty() && m_nBaseOffset > 0)
4608
0
            {
4609
0
                CPLXMLNode *psHeader = CPLCreateXMLNode(
4610
0
                    psFAO, CXT_Element, (osPrefix + "Header").c_str());
4611
0
                CPLAddXMLAttributeAndValue(
4612
0
                    CPLCreateXMLElementAndValue(
4613
0
                        psHeader, (osPrefix + "offset").c_str(), "0"),
4614
0
                    "unit", "byte");
4615
0
                CPLAddXMLAttributeAndValue(
4616
0
                    CPLCreateXMLElementAndValue(
4617
0
                        psHeader, (osPrefix + "object_length").c_str(),
4618
0
                        CPLSPrintf(CPL_FRMT_GUIB,
4619
0
                                   static_cast<GUIntBig>(m_nBaseOffset))),
4620
0
                    "unit", "byte");
4621
0
                CPLCreateXMLElementAndValue(
4622
0
                    psHeader, (osPrefix + "parsing_standard_id").c_str(),
4623
0
                    m_osHeaderParsingStandard.c_str());
4624
0
                if (m_osHeaderParsingStandard == TIFF_GEOTIFF_STRING)
4625
0
                {
4626
0
                    CPLCreateXMLElementAndValue(
4627
0
                        psHeader, (osPrefix + "description").c_str(),
4628
0
                        "TIFF/GeoTIFF header. The TIFF/GeoTIFF format is used "
4629
0
                        "throughout the geospatial and science communities "
4630
0
                        "to share geographic image data. ");
4631
0
                }
4632
0
                else if (m_osHeaderParsingStandard == BIGTIFF_GEOTIFF_STRING)
4633
0
                {
4634
0
                    CPLCreateXMLElementAndValue(
4635
0
                        psHeader, (osPrefix + "description").c_str(),
4636
0
                        "BigTIFF/GeoTIFF header. The BigTIFF/GeoTIFF format is "
4637
0
                        "used "
4638
0
                        "throughout the geospatial and science communities "
4639
0
                        "to share geographic image data. ");
4640
0
                }
4641
0
            }
4642
4643
16
            WriteArray(osPrefix, psFAO, pszLocalIdentifier,
4644
16
                       psTemplateSpecialConstants);
4645
16
        }
4646
440
    }
4647
440
}
4648
4649
/************************************************************************/
4650
/*                            WriteHeader()                             */
4651
/************************************************************************/
4652
4653
void PDS4Dataset::WriteHeader()
4654
440
{
4655
440
    const bool bAppend =
4656
440
        CPLFetchBool(m_papszCreationOptions, "APPEND_SUBDATASET", false);
4657
440
    if (bAppend)
4658
0
    {
4659
0
        WriteHeaderAppendCase();
4660
0
        return;
4661
0
    }
4662
4663
440
    CPLXMLNode *psRoot;
4664
440
    if (m_bCreateHeader)
4665
440
    {
4666
440
        CPLString osTemplateFilename =
4667
440
            CSLFetchNameValueDef(m_papszCreationOptions, "TEMPLATE", "");
4668
440
        if (!osTemplateFilename.empty())
4669
0
        {
4670
0
            if (STARTS_WITH(osTemplateFilename, "http://") ||
4671
0
                STARTS_WITH(osTemplateFilename, "https://"))
4672
0
            {
4673
0
                osTemplateFilename = "/vsicurl_streaming/" + osTemplateFilename;
4674
0
            }
4675
0
            psRoot = CPLParseXMLFile(osTemplateFilename);
4676
0
        }
4677
440
        else if (!m_osXMLPDS4.empty())
4678
0
            psRoot = CPLParseXMLString(m_osXMLPDS4);
4679
440
        else
4680
440
        {
4681
440
#ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
4682
440
#ifdef EMBED_RESOURCE_FILES
4683
440
            CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
4684
440
#endif
4685
440
            const char *pszDefaultTemplateFilename =
4686
440
                CPLFindFile("gdal", "pds4_template.xml");
4687
440
            if (pszDefaultTemplateFilename)
4688
0
            {
4689
0
                psRoot = CPLParseXMLFile(pszDefaultTemplateFilename);
4690
0
            }
4691
440
            else
4692
440
#endif
4693
440
            {
4694
440
#ifdef EMBED_RESOURCE_FILES
4695
440
                static const bool bOnce [[maybe_unused]] = []()
4696
440
                {
4697
2
                    CPLDebug("PDS4", "Using embedded pds4_template.xml");
4698
2
                    return true;
4699
2
                }();
4700
440
                psRoot = CPLParseXMLString(PDS4GetEmbeddedTemplate());
4701
#else
4702
                CPLError(CE_Failure, CPLE_AppDefined,
4703
                         "Cannot find pds4_template.xml and TEMPLATE "
4704
                         "creation option not specified");
4705
                return;
4706
#endif
4707
440
            }
4708
440
        }
4709
440
    }
4710
0
    else
4711
0
    {
4712
0
        psRoot = CPLParseXMLFile(m_osXMLFilename);
4713
0
    }
4714
440
    CPLXMLTreeCloser oCloser(psRoot);
4715
440
    psRoot = oCloser.get();
4716
440
    if (psRoot == nullptr)
4717
0
        return;
4718
440
    CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
4719
440
    if (psProduct == nullptr)
4720
0
    {
4721
0
        psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
4722
0
    }
4723
440
    if (psProduct == nullptr)
4724
0
    {
4725
0
        CPLError(CE_Failure, CPLE_AppDefined,
4726
0
                 "Cannot find Product_Observational element in template");
4727
0
        return;
4728
0
    }
4729
4730
440
    if (m_bCreateHeader)
4731
440
    {
4732
440
        CPLString osCARTVersion(CURRENT_CART_VERSION);
4733
440
        char *pszXML = CPLSerializeXMLTree(psRoot);
4734
440
        if (pszXML)
4735
440
        {
4736
440
            const char *pszIter = pszXML;
4737
880
            while (true)
4738
880
            {
4739
880
                const char *pszCartSchema = strstr(pszIter, "PDS4_CART_");
4740
880
                if (pszCartSchema)
4741
880
                {
4742
880
                    const char *pszXSDExtension = strstr(pszCartSchema, ".xsd");
4743
880
                    if (pszXSDExtension &&
4744
880
                        pszXSDExtension - pszCartSchema <= 20)
4745
440
                    {
4746
440
                        osCARTVersion = pszCartSchema + strlen("PDS4_CART_");
4747
440
                        osCARTVersion.resize(pszXSDExtension - pszCartSchema -
4748
440
                                             strlen("PDS4_CART_"));
4749
440
                        break;
4750
440
                    }
4751
440
                    else
4752
440
                    {
4753
440
                        pszIter = pszCartSchema + 1;
4754
440
                    }
4755
880
                }
4756
0
                else
4757
0
                {
4758
0
                    break;
4759
0
                }
4760
880
            }
4761
4762
440
            CPLFree(pszXML);
4763
440
        }
4764
4765
440
        CreateHeader(psProduct, osCARTVersion.c_str());
4766
440
    }
4767
4768
440
    WriteVectorLayers(psProduct);
4769
4770
440
    CPLSerializeXMLTreeToFile(psRoot, GetDescription());
4771
440
}
4772
4773
/************************************************************************/
4774
/*                            ICreateLayer()                            */
4775
/************************************************************************/
4776
4777
OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName,
4778
                                    const OGRGeomFieldDefn *poGeomFieldDefn,
4779
                                    CSLConstList papszOptions)
4780
2.98k
{
4781
2.98k
    const char *pszTableType =
4782
2.98k
        CSLFetchNameValueDef(papszOptions, "TABLE_TYPE", "DELIMITED");
4783
2.98k
    if (!EQUAL(pszTableType, "CHARACTER") && !EQUAL(pszTableType, "BINARY") &&
4784
2.98k
        !EQUAL(pszTableType, "DELIMITED"))
4785
0
    {
4786
0
        return nullptr;
4787
0
    }
4788
4789
2.98k
    const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
4790
2.98k
    const auto poSpatialRef =
4791
2.98k
        poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
4792
4793
2.98k
    const char *pszExt = EQUAL(pszTableType, "CHARACTER") ? "dat"
4794
2.98k
                         : EQUAL(pszTableType, "BINARY")  ? "bin"
4795
2.98k
                                                          : "csv";
4796
2.98k
    std::string osBasename(pszName);
4797
2.98k
    for (char &ch : osBasename)
4798
58.0k
    {
4799
58.0k
        if (!isalnum(static_cast<unsigned char>(ch)) &&
4800
23.4k
            static_cast<unsigned>(ch) <= 127)
4801
6.12k
            ch = '_';
4802
58.0k
    }
4803
4804
2.98k
    CPLString osFullFilename(CPLFormFilenameSafe(
4805
2.98k
        CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(),
4806
2.98k
        CPLGetBasenameSafe(m_osXMLFilename.c_str()).c_str(), nullptr));
4807
2.98k
    osFullFilename += '_';
4808
2.98k
    osFullFilename += osBasename.c_str();
4809
2.98k
    osFullFilename += '.';
4810
2.98k
    osFullFilename += pszExt;
4811
2.98k
    VSIStatBufL sStat;
4812
2.98k
    if (VSIStatL(osFullFilename, &sStat) == 0)
4813
18
    {
4814
18
        CPLError(CE_Failure, CPLE_AppDefined,
4815
18
                 "%s already exists. Please delete it before, or "
4816
18
                 "rename the layer",
4817
18
                 osFullFilename.c_str());
4818
18
        return nullptr;
4819
18
    }
4820
4821
2.96k
    if (EQUAL(pszTableType, "DELIMITED"))
4822
2.96k
    {
4823
2.96k
        auto poLayer = std::make_unique<PDS4DelimitedTable>(
4824
2.96k
            this, pszName, osFullFilename, true);
4825
2.96k
        if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
4826
2.96k
                                         papszOptions))
4827
0
        {
4828
0
            return nullptr;
4829
0
        }
4830
2.96k
        m_apoLayers.push_back(
4831
2.96k
            std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
4832
2.96k
    }
4833
0
    else
4834
0
    {
4835
0
        std::unique_ptr<PDS4FixedWidthTable> poLayer;
4836
0
        if (EQUAL(pszTableType, "CHARACTER"))
4837
0
            poLayer = std::make_unique<PDS4TableCharacter>(
4838
0
                this, pszName, osFullFilename, true);
4839
0
        else
4840
0
            poLayer = std::make_unique<PDS4TableBinary>(this, pszName,
4841
0
                                                        osFullFilename, true);
4842
0
        if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
4843
0
                                         papszOptions))
4844
0
        {
4845
0
            return nullptr;
4846
0
        }
4847
0
        m_apoLayers.push_back(
4848
0
            std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
4849
0
    }
4850
2.96k
    return dynamic_cast<OGRLayer *>(m_apoLayers.back().get());
4851
2.96k
}
4852
4853
/************************************************************************/
4854
/*                           TestCapability()                           */
4855
/************************************************************************/
4856
4857
bool PDS4Dataset::TestCapability(const char *pszCap) const
4858
6.64k
{
4859
6.64k
    if (EQUAL(pszCap, ODsCCreateLayer))
4860
2.98k
        return eAccess == GA_Update;
4861
3.66k
    else if (EQUAL(pszCap, ODsCZGeometries))
4862
0
        return TRUE;
4863
3.66k
    else
4864
3.66k
        return FALSE;
4865
6.64k
}
4866
4867
/************************************************************************/
4868
/*                               Create()                               */
4869
/************************************************************************/
4870
4871
GDALDataset *PDS4Dataset::Create(const char *pszFilename, int nXSize,
4872
                                 int nYSize, int nBandsIn, GDALDataType eType,
4873
                                 CSLConstList papszOptions)
4874
424
{
4875
424
    return CreateInternal(pszFilename, nullptr, nXSize, nYSize, nBandsIn, eType,
4876
424
                          papszOptions)
4877
424
        .release();
4878
424
}
4879
4880
/************************************************************************/
4881
/*                           CreateInternal()                           */
4882
/************************************************************************/
4883
4884
std::unique_ptr<PDS4Dataset> PDS4Dataset::CreateInternal(
4885
    const char *pszFilename, GDALDataset *poSrcDS, int nXSize, int nYSize,
4886
    int nBandsIn, GDALDataType eType, const char *const *papszOptionsIn)
4887
440
{
4888
440
    CPLStringList aosOptions(papszOptionsIn);
4889
4890
440
    if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eType == GDT_Unknown)
4891
424
    {
4892
        // Vector file creation
4893
424
        auto poDS = std::make_unique<PDS4Dataset>();
4894
424
        poDS->SetDescription(pszFilename);
4895
424
        poDS->nRasterXSize = 0;
4896
424
        poDS->nRasterYSize = 0;
4897
424
        poDS->eAccess = GA_Update;
4898
424
        poDS->m_osXMLFilename = pszFilename;
4899
424
        poDS->m_bCreateHeader = true;
4900
424
        poDS->m_bStripFileAreaObservationalFromTemplate = true;
4901
424
        poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
4902
424
        poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
4903
424
        return poDS;
4904
424
    }
4905
4906
16
    if (nXSize == 0)
4907
0
        return nullptr;
4908
4909
16
    if (!(eType == GDT_UInt8 || eType == GDT_Int8 || eType == GDT_Int16 ||
4910
2
          eType == GDT_UInt16 || eType == GDT_Int32 || eType == GDT_UInt32 ||
4911
2
          eType == GDT_Int64 || eType == GDT_UInt64 || eType == GDT_Float32 ||
4912
2
          eType == GDT_Float64 || eType == GDT_CFloat32 ||
4913
0
          eType == GDT_CFloat64))
4914
0
    {
4915
0
        CPLError(
4916
0
            CE_Failure, CPLE_NotSupported,
4917
0
            "The PDS4 driver does not supporting creating files of type %s.",
4918
0
            GDALGetDataTypeName(eType));
4919
0
        return nullptr;
4920
0
    }
4921
4922
16
    if (nBandsIn == 0)
4923
0
    {
4924
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid number of bands");
4925
0
        return nullptr;
4926
0
    }
4927
4928
16
    const char *pszArrayType =
4929
16
        aosOptions.FetchNameValueDef("ARRAY_TYPE", "Array_3D_Image");
4930
16
    const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
4931
16
    if (nBandsIn > 1 && bIsArray2D)
4932
0
    {
4933
0
        CPLError(CE_Failure, CPLE_NotSupported,
4934
0
                 "ARRAY_TYPE=%s is not supported for a multi-band raster",
4935
0
                 pszArrayType);
4936
0
        return nullptr;
4937
0
    }
4938
4939
    /* -------------------------------------------------------------------- */
4940
    /*      Compute pixel, line and band offsets                            */
4941
    /* -------------------------------------------------------------------- */
4942
16
    const int nItemSize = GDALGetDataTypeSizeBytes(eType);
4943
16
    int nLineOffset, nPixelOffset;
4944
16
    vsi_l_offset nBandOffset;
4945
4946
16
    const char *pszInterleave =
4947
16
        aosOptions.FetchNameValueDef(GDALMD_INTERLEAVE, "BSQ");
4948
16
    if (bIsArray2D)
4949
0
        pszInterleave = "BIP";
4950
4951
16
    if (EQUAL(pszInterleave, "BIP"))
4952
4
    {
4953
4
        nPixelOffset = nItemSize * nBandsIn;
4954
4
        if (nPixelOffset > INT_MAX / nBandsIn)
4955
0
        {
4956
0
            return nullptr;
4957
0
        }
4958
4
        nLineOffset = nPixelOffset * nXSize;
4959
4
        nBandOffset = nItemSize;
4960
4
    }
4961
12
    else if (EQUAL(pszInterleave, "BSQ"))
4962
12
    {
4963
12
        nPixelOffset = nItemSize;
4964
12
        if (nPixelOffset > INT_MAX / nXSize)
4965
0
        {
4966
0
            return nullptr;
4967
0
        }
4968
12
        nLineOffset = nPixelOffset * nXSize;
4969
12
        nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nYSize;
4970
12
    }
4971
0
    else if (EQUAL(pszInterleave, "BIL"))
4972
0
    {
4973
0
        nPixelOffset = nItemSize;
4974
0
        if (nPixelOffset > INT_MAX / nBandsIn ||
4975
0
            nPixelOffset * nBandsIn > INT_MAX / nXSize)
4976
0
        {
4977
0
            return nullptr;
4978
0
        }
4979
0
        nLineOffset = nItemSize * nBandsIn * nXSize;
4980
0
        nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nXSize;
4981
0
    }
4982
0
    else
4983
0
    {
4984
0
        CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for INTERLEAVE");
4985
0
        return nullptr;
4986
0
    }
4987
4988
16
    const char *pszImageFormat =
4989
16
        aosOptions.FetchNameValueDef("IMAGE_FORMAT", "RAW");
4990
16
    const char *pszImageExtension = aosOptions.FetchNameValueDef(
4991
16
        "IMAGE_EXTENSION", EQUAL(pszImageFormat, "RAW") ? "img" : "tif");
4992
16
    CPLString osImageFilename(aosOptions.FetchNameValueDef(
4993
16
        "IMAGE_FILENAME",
4994
16
        CPLResetExtensionSafe(pszFilename, pszImageExtension).c_str()));
4995
4996
16
    const bool bAppend = aosOptions.FetchBool("APPEND_SUBDATASET", false);
4997
16
    if (bAppend)
4998
0
    {
4999
0
        GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5000
0
        auto poExistingPDS4 = OpenInternal(&oOpenInfo);
5001
0
        if (!poExistingPDS4)
5002
0
        {
5003
0
            return nullptr;
5004
0
        }
5005
0
        osImageFilename = poExistingPDS4->m_osImageFilename;
5006
0
        poExistingPDS4.reset();
5007
5008
0
        auto poImageDS = std::unique_ptr<GDALDataset>(
5009
0
            GDALDataset::Open(osImageFilename, GDAL_OF_RASTER));
5010
0
        if (poImageDS && poImageDS->GetDriver() &&
5011
0
            EQUAL(poImageDS->GetDriver()->GetDescription(), "GTiff"))
5012
0
        {
5013
0
            pszImageFormat = "GEOTIFF";
5014
0
        }
5015
0
    }
5016
5017
16
    GDALDataset *poExternalDS = nullptr;
5018
16
    VSILFILE *fpImage = nullptr;
5019
16
    vsi_l_offset nBaseOffset = 0;
5020
16
    bool bIsLSB = true;
5021
16
    CPLString osHeaderParsingStandard;
5022
16
    const bool bCreateLabelOnly =
5023
16
        aosOptions.FetchBool("CREATE_LABEL_ONLY", false);
5024
16
    if (bCreateLabelOnly)
5025
0
    {
5026
0
        if (poSrcDS == nullptr)
5027
0
        {
5028
0
            CPLError(
5029
0
                CE_Failure, CPLE_AppDefined,
5030
0
                "CREATE_LABEL_ONLY is only compatible with CreateCopy() mode");
5031
0
            return nullptr;
5032
0
        }
5033
0
        RawBinaryLayout sLayout;
5034
0
        if (!poSrcDS->GetRawBinaryLayout(sLayout))
5035
0
        {
5036
0
            CPLError(CE_Failure, CPLE_AppDefined,
5037
0
                     "Source dataset is not compatible with raw binary format");
5038
0
            return nullptr;
5039
0
        }
5040
0
        if ((nBandsIn > 1 &&
5041
0
             sLayout.eInterleaving == RawBinaryLayout::Interleaving::UNKNOWN) ||
5042
0
            (nBandsIn == 1 &&
5043
0
             !(sLayout.nPixelOffset == nItemSize &&
5044
0
               sLayout.nLineOffset == sLayout.nPixelOffset * nXSize)))
5045
0
        {
5046
0
            CPLError(CE_Failure, CPLE_AppDefined,
5047
0
                     "Source dataset has an interleaving not handled in PDS4");
5048
0
            return nullptr;
5049
0
        }
5050
0
        fpImage = VSIFOpenL(sLayout.osRawFilename.c_str(), "rb");
5051
0
        if (fpImage == nullptr)
5052
0
        {
5053
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot open raw image %s",
5054
0
                     sLayout.osRawFilename.c_str());
5055
0
            return nullptr;
5056
0
        }
5057
0
        osImageFilename = sLayout.osRawFilename;
5058
0
        if (nBandsIn == 1 ||
5059
0
            sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIP)
5060
0
            pszInterleave = "BIP";
5061
0
        else if (sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIL)
5062
0
            pszInterleave = "BIL";
5063
0
        else
5064
0
            pszInterleave = "BSQ";
5065
0
        nBaseOffset = sLayout.nImageOffset;
5066
0
        nPixelOffset = static_cast<int>(sLayout.nPixelOffset);
5067
0
        nLineOffset = static_cast<int>(sLayout.nLineOffset);
5068
0
        nBandOffset = static_cast<vsi_l_offset>(sLayout.nBandOffset);
5069
0
        bIsLSB = sLayout.bLittleEndianOrder;
5070
0
        auto poSrcDriver = poSrcDS->GetDriver();
5071
0
        if (poSrcDriver)
5072
0
        {
5073
0
            auto pszDriverName = poSrcDriver->GetDescription();
5074
0
            if (EQUAL(pszDriverName, "GTiff"))
5075
0
            {
5076
0
                GByte abySignature[4] = {0};
5077
0
                VSIFReadL(abySignature, 1, 4, fpImage);
5078
0
                const bool bBigTIFF =
5079
0
                    abySignature[2] == 43 || abySignature[3] == 43;
5080
0
                osHeaderParsingStandard =
5081
0
                    bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
5082
0
            }
5083
0
            else if (EQUAL(pszDriverName, "ISIS3"))
5084
0
            {
5085
0
                osHeaderParsingStandard = "ISIS3";
5086
0
            }
5087
0
            else if (EQUAL(pszDriverName, "VICAR"))
5088
0
            {
5089
0
                osHeaderParsingStandard = "VICAR2";
5090
0
            }
5091
0
            else if (EQUAL(pszDriverName, "PDS"))
5092
0
            {
5093
0
                osHeaderParsingStandard = "PDS3";
5094
0
            }
5095
0
            else if (EQUAL(pszDriverName, "FITS"))
5096
0
            {
5097
0
                osHeaderParsingStandard = "FITS 3.0";
5098
0
                aosOptions.SetNameValue("VAR_VERTICAL_DISPLAY_DIRECTION",
5099
0
                                        "Bottom to Top");
5100
0
            }
5101
0
        }
5102
0
    }
5103
16
    else if (EQUAL(pszImageFormat, "GEOTIFF"))
5104
0
    {
5105
0
        if (EQUAL(pszInterleave, "BIL"))
5106
0
        {
5107
0
            if (aosOptions.FetchBool("@INTERLEAVE_ADDED_AUTOMATICALLY", false))
5108
0
            {
5109
0
                pszInterleave = "BSQ";
5110
0
            }
5111
0
            else
5112
0
            {
5113
0
                CPLError(CE_Failure, CPLE_AppDefined,
5114
0
                         "INTERLEAVE=BIL not supported for GeoTIFF in PDS4");
5115
0
                return nullptr;
5116
0
            }
5117
0
        }
5118
0
        GDALDriver *poDrv =
5119
0
            static_cast<GDALDriver *>(GDALGetDriverByName("GTiff"));
5120
0
        if (poDrv == nullptr)
5121
0
        {
5122
0
            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GTiff driver");
5123
0
            return nullptr;
5124
0
        }
5125
0
        char **papszGTiffOptions = nullptr;
5126
#ifdef notdef
5127
        // In practice I can't see which option we can really use
5128
        const char *pszGTiffOptions =
5129
            CSLFetchNameValueDef(papszOptions, "GEOTIFF_OPTIONS", "");
5130
        char **papszTokens = CSLTokenizeString2(pszGTiffOptions, ",", 0);
5131
        if (CPLFetchBool(papszTokens, "TILED", false))
5132
        {
5133
            CSLDestroy(papszTokens);
5134
            CPLError(CE_Failure, CPLE_AppDefined,
5135
                     "Tiled GeoTIFF is not supported for PDS4");
5136
            return NULL;
5137
        }
5138
        if (!EQUAL(CSLFetchNameValueDef(papszTokens, "COMPRESS", "NONE"),
5139
                   "NONE"))
5140
        {
5141
            CSLDestroy(papszTokens);
5142
            CPLError(CE_Failure, CPLE_AppDefined,
5143
                     "Compressed GeoTIFF is not supported for PDS4");
5144
            return NULL;
5145
        }
5146
        papszGTiffOptions =
5147
            CSLSetNameValue(papszGTiffOptions, "ENDIANNESS", "LITTLE");
5148
        for (int i = 0; papszTokens[i] != NULL; i++)
5149
        {
5150
            papszGTiffOptions = CSLAddString(papszGTiffOptions, papszTokens[i]);
5151
        }
5152
        CSLDestroy(papszTokens);
5153
#endif
5154
5155
0
        papszGTiffOptions =
5156
0
            CSLSetNameValue(papszGTiffOptions, GDALMD_INTERLEAVE,
5157
0
                            EQUAL(pszInterleave, "BSQ") ? "BAND" : "PIXEL");
5158
        // Will make sure that our blocks at nodata are not optimized
5159
        // away but indeed well written
5160
0
        papszGTiffOptions = CSLSetNameValue(
5161
0
            papszGTiffOptions, "@WRITE_EMPTY_TILES_SYNCHRONOUSLY", "YES");
5162
0
        if (nBandsIn > 1 && EQUAL(pszInterleave, "BSQ"))
5163
0
        {
5164
0
            papszGTiffOptions =
5165
0
                CSLSetNameValue(papszGTiffOptions, "BLOCKYSIZE", "1");
5166
0
        }
5167
5168
0
        if (bAppend)
5169
0
        {
5170
0
            papszGTiffOptions =
5171
0
                CSLAddString(papszGTiffOptions, "APPEND_SUBDATASET=YES");
5172
0
        }
5173
5174
0
        poExternalDS = poDrv->Create(osImageFilename, nXSize, nYSize, nBandsIn,
5175
0
                                     eType, papszGTiffOptions);
5176
0
        CSLDestroy(papszGTiffOptions);
5177
0
        if (poExternalDS == nullptr)
5178
0
        {
5179
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
5180
0
                     osImageFilename.c_str());
5181
0
            return nullptr;
5182
0
        }
5183
0
    }
5184
16
    else
5185
16
    {
5186
16
        fpImage = VSIFOpenL(
5187
16
            osImageFilename,
5188
16
            bAppend                                                 ? "rb+"
5189
16
            : VSISupportsRandomWrite(osImageFilename.c_str(), true) ? "wb+"
5190
16
                                                                    : "wb");
5191
16
        if (fpImage == nullptr)
5192
0
        {
5193
0
            CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
5194
0
                     osImageFilename.c_str());
5195
0
            return nullptr;
5196
0
        }
5197
16
        if (bAppend)
5198
0
        {
5199
0
            VSIFSeekL(fpImage, 0, SEEK_END);
5200
0
            nBaseOffset = VSIFTellL(fpImage);
5201
0
        }
5202
16
    }
5203
5204
16
    auto poDS = std::make_unique<PDS4Dataset>();
5205
16
    poDS->SetDescription(pszFilename);
5206
16
    poDS->m_bMustInitImageFile = true;
5207
16
    poDS->m_fpImage = fpImage;
5208
16
    poDS->m_nBaseOffset = nBaseOffset;
5209
16
    poDS->m_poExternalDS = poExternalDS;
5210
16
    poDS->nRasterXSize = nXSize;
5211
16
    poDS->nRasterYSize = nYSize;
5212
16
    poDS->eAccess = GA_Update;
5213
16
    poDS->m_osImageFilename = std::move(osImageFilename);
5214
16
    poDS->m_bCreateHeader = true;
5215
16
    poDS->m_bStripFileAreaObservationalFromTemplate = true;
5216
16
    poDS->m_osInterleave = pszInterleave;
5217
16
    poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
5218
16
    poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
5219
16
    poDS->m_bIsLSB = bIsLSB;
5220
16
    poDS->m_osHeaderParsingStandard = std::move(osHeaderParsingStandard);
5221
16
    poDS->m_bCreatedFromExistingBinaryFile = bCreateLabelOnly;
5222
5223
16
    if (EQUAL(pszInterleave, "BIP"))
5224
4
    {
5225
4
        poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
5226
4
                                           GDAL_MDD_IMAGE_STRUCTURE);
5227
4
    }
5228
12
    else if (EQUAL(pszInterleave, "BSQ"))
5229
12
    {
5230
12
        poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
5231
12
                                           GDAL_MDD_IMAGE_STRUCTURE);
5232
12
    }
5233
5234
1.64k
    for (int i = 0; i < nBandsIn; i++)
5235
1.63k
    {
5236
1.63k
        if (poDS->m_poExternalDS != nullptr)
5237
0
        {
5238
0
            auto poBand = std::make_unique<PDS4WrapperRasterBand>(
5239
0
                poDS->m_poExternalDS->GetRasterBand(i + 1));
5240
0
            poDS->SetBand(i + 1, std::move(poBand));
5241
0
        }
5242
1.63k
        else
5243
1.63k
        {
5244
1.63k
            auto poBand = std::make_unique<PDS4RawRasterBand>(
5245
1.63k
                poDS.get(), i + 1, poDS->m_fpImage,
5246
1.63k
                poDS->m_nBaseOffset + nBandOffset * i, nPixelOffset,
5247
1.63k
                nLineOffset, eType,
5248
1.63k
                bIsLSB ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
5249
1.63k
                       : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
5250
1.63k
            poDS->SetBand(i + 1, std::move(poBand));
5251
1.63k
        }
5252
1.63k
    }
5253
5254
16
    return poDS;
5255
16
}
5256
5257
/************************************************************************/
5258
/*                      PDS4GetUnderlyingDataset()                      */
5259
/************************************************************************/
5260
5261
static GDALDataset *PDS4GetUnderlyingDataset(GDALDataset *poSrcDS)
5262
16
{
5263
16
    if (poSrcDS->GetDriver() != nullptr &&
5264
16
        poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
5265
16
    {
5266
16
        VRTDataset *poVRTDS = cpl::down_cast<VRTDataset *>(poSrcDS);
5267
16
        poSrcDS = poVRTDS->GetSingleSimpleSource();
5268
16
    }
5269
5270
16
    return poSrcDS;
5271
16
}
5272
5273
/************************************************************************/
5274
/*                             CreateCopy()                             */
5275
/************************************************************************/
5276
5277
GDALDataset *PDS4Dataset::CreateCopy(const char *pszFilename,
5278
                                     GDALDataset *poSrcDS, int bStrict,
5279
                                     CSLConstList papszOptions,
5280
                                     GDALProgressFunc pfnProgress,
5281
                                     void *pProgressData)
5282
16
{
5283
16
    const char *pszImageFormat =
5284
16
        CSLFetchNameValueDef(papszOptions, "IMAGE_FORMAT", "RAW");
5285
16
    GDALDataset *poSrcUnderlyingDS = PDS4GetUnderlyingDataset(poSrcDS);
5286
16
    if (poSrcUnderlyingDS == nullptr)
5287
16
        poSrcUnderlyingDS = poSrcDS;
5288
16
    if (EQUAL(pszImageFormat, "GEOTIFF") &&
5289
0
        strcmp(poSrcUnderlyingDS->GetDescription(),
5290
0
               CSLFetchNameValueDef(
5291
0
                   papszOptions, "IMAGE_FILENAME",
5292
0
                   CPLResetExtensionSafe(pszFilename, "tif").c_str())) == 0)
5293
0
    {
5294
0
        CPLError(CE_Failure, CPLE_NotSupported,
5295
0
                 "Output file has same name as input file");
5296
0
        return nullptr;
5297
0
    }
5298
16
    if (poSrcDS->GetRasterCount() == 0)
5299
0
    {
5300
0
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
5301
0
        return nullptr;
5302
0
    }
5303
5304
16
    const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
5305
16
    if (bAppend)
5306
0
    {
5307
0
        GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5308
0
        auto poExistingDS = OpenInternal(&oOpenInfo);
5309
0
        if (poExistingDS)
5310
0
        {
5311
0
            GDALGeoTransform existingGT;
5312
0
            const bool bExistingHasGT =
5313
0
                poExistingDS->GetGeoTransform(existingGT) == CE_None;
5314
0
            GDALGeoTransform gt;
5315
0
            const bool bSrcHasGT = poSrcDS->GetGeoTransform(gt) == CE_None;
5316
5317
0
            CPLString osExistingProj4;
5318
0
            if (const auto poExistingSRS = poExistingDS->GetSpatialRef())
5319
0
            {
5320
0
                char *pszExistingProj4 = nullptr;
5321
0
                poExistingSRS->exportToProj4(&pszExistingProj4);
5322
0
                if (pszExistingProj4)
5323
0
                    osExistingProj4 = pszExistingProj4;
5324
0
                CPLFree(pszExistingProj4);
5325
0
            }
5326
0
            CPLString osSrcProj4;
5327
0
            if (const auto poSrcSRS = poSrcDS->GetSpatialRef())
5328
0
            {
5329
0
                char *pszSrcProj4 = nullptr;
5330
0
                poSrcSRS->exportToProj4(&pszSrcProj4);
5331
0
                if (pszSrcProj4)
5332
0
                    osSrcProj4 = pszSrcProj4;
5333
0
                CPLFree(pszSrcProj4);
5334
0
            }
5335
5336
0
            poExistingDS.reset();
5337
5338
0
            const auto maxRelErrorGT =
5339
0
                [](const GDALGeoTransform &gt1, const GDALGeoTransform &gt2)
5340
0
            {
5341
0
                double maxRelError = 0.0;
5342
0
                for (int i = 0; i < 6; i++)
5343
0
                {
5344
0
                    if (gt1[i] == 0.0)
5345
0
                    {
5346
0
                        maxRelError = std::max(maxRelError, std::abs(gt2[i]));
5347
0
                    }
5348
0
                    else
5349
0
                    {
5350
0
                        maxRelError =
5351
0
                            std::max(maxRelError, std::abs(gt2[i] - gt1[i]) /
5352
0
                                                      std::abs(gt1[i]));
5353
0
                    }
5354
0
                }
5355
0
                return maxRelError;
5356
0
            };
5357
5358
0
            if ((bExistingHasGT && !bSrcHasGT) ||
5359
0
                (!bExistingHasGT && bSrcHasGT) ||
5360
0
                (bExistingHasGT && bSrcHasGT &&
5361
0
                 maxRelErrorGT(existingGT, gt) > 1e-10))
5362
0
            {
5363
0
                CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
5364
0
                         "Appending to a dataset with a different "
5365
0
                         "geotransform is not supported");
5366
0
                if (bStrict)
5367
0
                    return nullptr;
5368
0
            }
5369
            // Do proj string comparison, as it is unlikely that
5370
            // OGRSpatialReference::IsSame() will lead to identical reasons due
5371
            // to PDS changing CRS names, etc...
5372
0
            if (osExistingProj4 != osSrcProj4)
5373
0
            {
5374
0
                CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
5375
0
                         "Appending to a dataset with a different "
5376
0
                         "coordinate reference system is not supported");
5377
0
                if (bStrict)
5378
0
                    return nullptr;
5379
0
            }
5380
0
        }
5381
0
    }
5382
5383
16
    const int nXSize = poSrcDS->GetRasterXSize();
5384
16
    const int nYSize = poSrcDS->GetRasterYSize();
5385
16
    const int nBands = poSrcDS->GetRasterCount();
5386
16
    GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
5387
16
    auto poDS = CreateInternal(pszFilename, poSrcDS, nXSize, nYSize, nBands,
5388
16
                               eType, papszOptions);
5389
16
    if (poDS == nullptr)
5390
0
        return nullptr;
5391
5392
16
    GDALGeoTransform gt;
5393
16
    if (poSrcDS->GetGeoTransform(gt) == CE_None && gt != GDALGeoTransform())
5394
11
    {
5395
11
        poDS->SetGeoTransform(gt);
5396
11
    }
5397
5398
16
    if (poSrcDS->GetProjectionRef() != nullptr &&
5399
16
        strlen(poSrcDS->GetProjectionRef()) > 0)
5400
11
    {
5401
11
        poDS->SetProjection(poSrcDS->GetProjectionRef());
5402
11
    }
5403
5404
1.64k
    for (int i = 1; i <= nBands; i++)
5405
1.63k
    {
5406
1.63k
        int bHasNoData = false;
5407
5408
1.63k
        if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_Int64)
5409
0
        {
5410
0
            const auto nNoData =
5411
0
                poSrcDS->GetRasterBand(i)->GetNoDataValueAsInt64(&bHasNoData);
5412
0
            if (bHasNoData)
5413
0
                poDS->GetRasterBand(i)->SetNoDataValueAsInt64(nNoData);
5414
0
        }
5415
1.63k
        else if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_UInt64)
5416
0
        {
5417
0
            const auto nNoData =
5418
0
                poSrcDS->GetRasterBand(i)->GetNoDataValueAsUInt64(&bHasNoData);
5419
0
            if (bHasNoData)
5420
0
                poDS->GetRasterBand(i)->SetNoDataValueAsUInt64(nNoData);
5421
0
        }
5422
1.63k
        else
5423
1.63k
        {
5424
1.63k
            const double dfNoData =
5425
1.63k
                poSrcDS->GetRasterBand(i)->GetNoDataValue(&bHasNoData);
5426
1.63k
            if (bHasNoData)
5427
32
                poDS->GetRasterBand(i)->SetNoDataValue(dfNoData);
5428
1.63k
        }
5429
5430
1.63k
        const double dfOffset = poSrcDS->GetRasterBand(i)->GetOffset();
5431
1.63k
        if (dfOffset != 0.0)
5432
804
            poDS->GetRasterBand(i)->SetOffset(dfOffset);
5433
5434
1.63k
        const double dfScale = poSrcDS->GetRasterBand(i)->GetScale();
5435
1.63k
        if (dfScale != 1.0)
5436
1.40k
            poDS->GetRasterBand(i)->SetScale(dfScale);
5437
5438
1.63k
        poDS->GetRasterBand(i)->SetUnitType(
5439
1.63k
            poSrcDS->GetRasterBand(i)->GetUnitType());
5440
1.63k
    }
5441
5442
16
    if (poDS->m_bUseSrcLabel)
5443
16
    {
5444
16
        CSLConstList papszMD_PDS4 = poSrcDS->GetMetadata("xml:PDS4");
5445
16
        if (papszMD_PDS4 != nullptr)
5446
0
        {
5447
0
            poDS->SetMetadata(papszMD_PDS4, "xml:PDS4");
5448
0
        }
5449
16
    }
5450
5451
16
    if (poDS->m_poExternalDS == nullptr)
5452
16
    {
5453
        // We don't need to initialize the imagery as we are going to copy it
5454
        // completely
5455
16
        poDS->m_bMustInitImageFile = false;
5456
16
    }
5457
5458
16
    if (!CPLFetchBool(papszOptions, "CREATE_LABEL_ONLY", false))
5459
16
    {
5460
16
        CPLErr eErr = GDALDatasetCopyWholeRaster(poSrcDS, poDS.get(), nullptr,
5461
16
                                                 pfnProgress, pProgressData);
5462
16
        poDS->FlushCache(false);
5463
16
        if (eErr != CE_None)
5464
6
        {
5465
6
            return nullptr;
5466
6
        }
5467
5468
10
        if (CPLFetchBool(papszOptions, "PROPAGATE_SRC_METADATA", true))
5469
10
        {
5470
10
            CSLConstList papszISIS3MD = poSrcDS->GetMetadata("json:ISIS3");
5471
10
            if (papszISIS3MD)
5472
0
            {
5473
0
                poDS->SetMetadata(papszISIS3MD, "json:ISIS3");
5474
5475
0
                if (poDS->m_poExternalDS)
5476
0
                    poDS->m_poExternalDS->SetMetadata(papszISIS3MD,
5477
0
                                                      "json:ISIS3");
5478
0
            }
5479
10
        }
5480
10
    }
5481
5482
10
    return poDS.release();
5483
16
}
5484
5485
/************************************************************************/
5486
/*                               Delete()                               */
5487
/************************************************************************/
5488
5489
CPLErr PDS4Dataset::Delete(const char *pszFilename)
5490
5491
360
{
5492
    /* -------------------------------------------------------------------- */
5493
    /*      Collect file list.                                              */
5494
    /* -------------------------------------------------------------------- */
5495
360
    GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
5496
360
    auto poDS = PDS4Dataset::OpenInternal(&oOpenInfo);
5497
360
    if (poDS == nullptr)
5498
0
    {
5499
0
        if (CPLGetLastErrorNo() == 0)
5500
0
            CPLError(CE_Failure, CPLE_OpenFailed,
5501
0
                     "Unable to open %s to obtain file list.", pszFilename);
5502
5503
0
        return CE_Failure;
5504
0
    }
5505
5506
360
    char **papszFileList = poDS->GetFileList();
5507
360
    CPLString osImageFilename = poDS->m_osImageFilename;
5508
360
    bool bCreatedFromExistingBinaryFile =
5509
360
        poDS->m_bCreatedFromExistingBinaryFile;
5510
5511
360
    poDS.reset();
5512
5513
360
    if (CSLCount(papszFileList) == 0)
5514
0
    {
5515
0
        CPLError(CE_Failure, CPLE_NotSupported,
5516
0
                 "Unable to determine files associated with %s, "
5517
0
                 "delete fails.",
5518
0
                 pszFilename);
5519
0
        CSLDestroy(papszFileList);
5520
0
        return CE_Failure;
5521
0
    }
5522
5523
    /* -------------------------------------------------------------------- */
5524
    /*      Delete all files.                                               */
5525
    /* -------------------------------------------------------------------- */
5526
360
    CPLErr eErr = CE_None;
5527
5.73k
    for (int i = 0; papszFileList[i] != nullptr; ++i)
5528
5.37k
    {
5529
5.37k
        if (bCreatedFromExistingBinaryFile &&
5530
0
            EQUAL(papszFileList[i], osImageFilename))
5531
0
        {
5532
0
            continue;
5533
0
        }
5534
5.37k
        if (VSIUnlink(papszFileList[i]) != 0)
5535
0
        {
5536
0
            CPLError(CE_Failure, CPLE_AppDefined, "Deleting %s failed:\n%s",
5537
0
                     papszFileList[i], VSIStrerror(errno));
5538
0
            eErr = CE_Failure;
5539
0
        }
5540
5.37k
    }
5541
5542
360
    CSLDestroy(papszFileList);
5543
5544
360
    return eErr;
5545
360
}
5546
5547
/************************************************************************/
5548
/*                         GDALRegister_PDS4()                          */
5549
/************************************************************************/
5550
5551
void GDALRegister_PDS4()
5552
5553
24
{
5554
24
    if (GDALGetDriverByName(PDS4_DRIVER_NAME) != nullptr)
5555
0
        return;
5556
5557
24
    GDALDriver *poDriver = new GDALDriver();
5558
24
    PDS4DriverSetCommonMetadata(poDriver);
5559
5560
24
    poDriver->pfnOpen = PDS4Dataset::Open;
5561
24
    poDriver->pfnCreate = PDS4Dataset::Create;
5562
24
    poDriver->pfnCreateCopy = PDS4Dataset::CreateCopy;
5563
24
    poDriver->pfnDelete = PDS4Dataset::Delete;
5564
5565
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
5566
24
}