Coverage Report

Created: 2025-06-09 08:44

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