Coverage Report

Created: 2026-03-30 09:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/rs2/rs2dataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Polarimetric Workstation
4
 * Purpose:  Radarsat 2 - XML Products (product.xml) driver
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2004, Frank Warmerdam <warmerdam@pobox.com>
9
 * Copyright (c) 2009-2013, Even Rouault <even dot rouault at spatialys.com>
10
 * Copyright (c) 2020, Defence Research and Development Canada (DRDC) Ottawa Research Centre
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "cpl_minixml.h"
16
#include "gdal_frmts.h"
17
#include "gdal_pam.h"
18
#include "gdal_driver.h"
19
#include "gdal_drivermanager.h"
20
#include "gdal_openinfo.h"
21
#include "gdal_cpp_functions.h"
22
#include "ogr_spatialref.h"
23
24
#include <algorithm>
25
26
typedef enum eCalibration_t
27
{
28
    Sigma0 = 0,
29
    Gamma,
30
    Beta0,
31
    Uncalib,
32
    None
33
} eCalibration;
34
35
/*** Function to test for valid LUT files ***/
36
static bool IsValidXMLFile(const char *pszPath, const char *pszLut)
37
0
{
38
0
    if (CPLHasPathTraversal(pszLut))
39
0
    {
40
0
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
41
0
                 pszLut);
42
0
        return false;
43
0
    }
44
    /* Return true for valid xml file, false otherwise */
45
0
    char *pszLutFile =
46
0
        VSIStrdup(CPLFormFilenameSafe(pszPath, pszLut, nullptr).c_str());
47
48
0
    CPLXMLTreeCloser psLut(CPLParseXMLFile(pszLutFile));
49
50
0
    CPLFree(pszLutFile);
51
52
0
    return psLut.get() != nullptr;
53
0
}
54
55
// Check that the referenced dataset for each band has the
56
// correct data type and returns whether a 2 band I+Q dataset should
57
// be mapped onto a single complex band.
58
// Returns BANDERROR for error, STRAIGHT for 1:1 mapping, TWOBANDCOMPLEX for 2
59
// bands -> 1 complex band
60
typedef enum
61
{
62
    BANDERROR,
63
    STRAIGHT,
64
    TWOBANDCOMPLEX
65
} BandMapping;
66
67
static BandMapping GetBandFileMapping(GDALDataType eDataType,
68
                                      GDALDataset *poBandDS)
69
0
{
70
71
0
    GDALRasterBand *poBand1 = poBandDS->GetRasterBand(1);
72
0
    GDALDataType eBandDataType1 = poBand1->GetRasterDataType();
73
74
    // if there is one band and it has the same datatype, the band file gets
75
    // passed straight through
76
0
    if (poBandDS->GetRasterCount() == 1 && eDataType == eBandDataType1)
77
0
        return STRAIGHT;
78
79
    // if the band file has 2 bands, they should represent I+Q
80
    // and be a compatible data type
81
0
    if (poBandDS->GetRasterCount() == 2 && GDALDataTypeIsComplex(eDataType))
82
0
    {
83
0
        GDALRasterBand *band2 = poBandDS->GetRasterBand(2);
84
85
0
        if (eBandDataType1 != band2->GetRasterDataType())
86
0
            return BANDERROR;  // both bands must be same datatype
87
88
        // check compatible types - there are 4 complex types in GDAL
89
0
        if ((eDataType == GDT_CInt16 && eBandDataType1 == GDT_Int16) ||
90
0
            (eDataType == GDT_CInt32 && eBandDataType1 == GDT_Int32) ||
91
0
            (eDataType == GDT_CFloat32 && eBandDataType1 == GDT_Float32) ||
92
0
            (eDataType == GDT_CFloat64 && eBandDataType1 == GDT_Float64))
93
0
            return TWOBANDCOMPLEX;
94
0
    }
95
0
    return BANDERROR;  // don't accept any other combinations
96
0
}
97
98
/************************************************************************/
99
/* ==================================================================== */
100
/*                               RS2Dataset                             */
101
/* ==================================================================== */
102
/************************************************************************/
103
104
class RS2Dataset final : public GDALPamDataset
105
{
106
    CPLXMLNode *psProduct;
107
108
    int nGCPCount;
109
    GDAL_GCP *pasGCPList;
110
    OGRSpatialReference m_oSRS{};
111
    OGRSpatialReference m_oGCPSRS{};
112
    char **papszSubDatasets;
113
    GDALGeoTransform m_gt{};
114
    bool bHaveGeoTransform;
115
116
    char **papszExtraFiles;
117
118
    CPL_DISALLOW_COPY_ASSIGN(RS2Dataset)
119
120
  protected:
121
    int CloseDependentDatasets() override;
122
123
  public:
124
    RS2Dataset();
125
    ~RS2Dataset() override;
126
127
    int GetGCPCount() override;
128
    const OGRSpatialReference *GetGCPSpatialRef() const override;
129
    const GDAL_GCP *GetGCPs() override;
130
131
    const OGRSpatialReference *GetSpatialRef() const override;
132
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
133
134
    char **GetMetadataDomainList() override;
135
    CSLConstList GetMetadata(const char *pszDomain = "") override;
136
    char **GetFileList(void) override;
137
138
    static GDALDataset *Open(GDALOpenInfo *);
139
    static int Identify(GDALOpenInfo *);
140
141
    CPLXMLNode *GetProduct()
142
0
    {
143
0
        return psProduct;
144
0
    }
145
};
146
147
/************************************************************************/
148
/* ==================================================================== */
149
/*                    RS2RasterBand                           */
150
/* ==================================================================== */
151
/************************************************************************/
152
153
class RS2RasterBand final : public GDALPamRasterBand
154
{
155
    std::unique_ptr<GDALDataset> poBandDS{};
156
157
    // 2 bands representing I+Q -> one complex band
158
    // otherwise poBandDS is passed straight through
159
    bool bIsTwoBandComplex = false;
160
161
    CPL_DISALLOW_COPY_ASSIGN(RS2RasterBand)
162
163
  public:
164
    RS2RasterBand(RS2Dataset *poDSIn, GDALDataType eDataTypeIn,
165
                  const char *pszPole, std::unique_ptr<GDALDataset> poBandDSIn,
166
                  bool bTwoBandComplex = false);
167
168
    CPLErr IReadBlock(int, int, void *) override;
169
170
    static GDALDataset *Open(GDALOpenInfo *);
171
};
172
173
/************************************************************************/
174
/*                            RS2RasterBand                             */
175
/************************************************************************/
176
177
RS2RasterBand::RS2RasterBand(RS2Dataset *poDSIn, GDALDataType eDataTypeIn,
178
                             const char *pszPole,
179
                             std::unique_ptr<GDALDataset> poBandDSIn,
180
                             bool bTwoBandComplex)
181
0
    : poBandDS(std::move(poBandDSIn))
182
0
{
183
0
    poDS = poDSIn;
184
185
0
    GDALRasterBand *poSrcBand = poBandDS->GetRasterBand(1);
186
187
0
    poSrcBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
188
189
0
    eDataType = eDataTypeIn;
190
191
0
    bIsTwoBandComplex = bTwoBandComplex;
192
193
0
    if (*pszPole != '\0')
194
0
        SetMetadataItem("POLARIMETRIC_INTERP", pszPole);
195
0
}
196
197
/************************************************************************/
198
/*                             IReadBlock()                             */
199
/************************************************************************/
200
201
CPLErr RS2RasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
202
203
0
{
204
    /* -------------------------------------------------------------------- */
205
    /*      If the last strip is partial, we need to avoid                  */
206
    /*      over-requesting.  We also need to initialize the extra part     */
207
    /*      of the block to zero.                                           */
208
    /* -------------------------------------------------------------------- */
209
0
    const int nYOff = nBlockYOff * nBlockYSize;
210
0
    const int nRequestYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
211
212
    /*-------------------------------------------------------------------- */
213
    /*      If the input imagery is tiled, also need to avoid over-        */
214
    /*      requesting in the X-direction.                                 */
215
    /* ------------------------------------------------------------------- */
216
0
    const int nXOff = nBlockXOff * nBlockXSize;
217
0
    const int nRequestXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
218
219
0
    if (nRequestXSize < nBlockXSize || nRequestYSize < nBlockYSize)
220
0
    {
221
0
        memset(pImage, 0,
222
0
               static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
223
0
                   nBlockXSize * nBlockYSize);
224
0
    }
225
226
0
    if (eDataType == GDT_CInt16 && poBandDS->GetRasterCount() == 2)
227
0
        return poBandDS->RasterIO(GF_Read, nXOff, nYOff, nRequestXSize,
228
0
                                  nRequestYSize, pImage, nRequestXSize,
229
0
                                  nRequestYSize, GDT_Int16, 2, nullptr, 4,
230
0
                                  nBlockXSize * 4, 2, nullptr);
231
232
    /* -------------------------------------------------------------------- */
233
    /*      File has one sample marked as sample format void, a 32bits.     */
234
    /* -------------------------------------------------------------------- */
235
0
    else if (eDataType == GDT_CInt16 && poBandDS->GetRasterCount() == 1)
236
0
    {
237
0
        CPLErr eErr = poBandDS->RasterIO(GF_Read, nXOff, nYOff, nRequestXSize,
238
0
                                         nRequestYSize, pImage, nRequestXSize,
239
0
                                         nRequestYSize, GDT_UInt32, 1, nullptr,
240
0
                                         4, nBlockXSize * 4, 0, nullptr);
241
242
0
#ifdef CPL_LSB
243
        /* First, undo the 32bit swap. */
244
0
        GDALSwapWords(pImage, 4, nBlockXSize * nBlockYSize, 4);
245
246
        /* Then apply 16 bit swap. */
247
0
        GDALSwapWords(pImage, 2, nBlockXSize * nBlockYSize * 2, 2);
248
0
#endif
249
250
0
        return eErr;
251
0
    }
252
253
    /* -------------------------------------------------------------------- */
254
    /*      The 16bit case is straight forward.  The underlying file        */
255
    /*      looks like a 16bit unsigned data too.                           */
256
    /* -------------------------------------------------------------------- */
257
0
    else if (eDataType == GDT_UInt16)
258
0
        return poBandDS->RasterIO(GF_Read, nXOff, nYOff, nRequestXSize,
259
0
                                  nRequestYSize, pImage, nRequestXSize,
260
0
                                  nRequestYSize, GDT_UInt16, 1, nullptr, 2,
261
0
                                  nBlockXSize * 2, 0, nullptr);
262
0
    else if (eDataType == GDT_UInt8)
263
        /* Ticket #2104: Support for ScanSAR products */
264
0
        return poBandDS->RasterIO(GF_Read, nXOff, nYOff, nRequestXSize,
265
0
                                  nRequestYSize, pImage, nRequestXSize,
266
0
                                  nRequestYSize, GDT_UInt8, 1, nullptr, 1,
267
0
                                  nBlockXSize, 0, nullptr);
268
269
0
    CPLAssert(false);
270
0
    return CE_Failure;
271
0
}
272
273
/************************************************************************/
274
/* ==================================================================== */
275
/*                         RS2CalibRasterBand                           */
276
/* ==================================================================== */
277
/************************************************************************/
278
/* Returns data that has been calibrated to sigma nought, gamma         */
279
/* or beta nought.                                                      */
280
/************************************************************************/
281
282
class RS2CalibRasterBand final : public GDALPamRasterBand
283
{
284
  private:
285
    // eCalibration m_eCalib;
286
    std::unique_ptr<GDALDataset> m_poBandDataset{};
287
    GDALDataType m_eType; /* data type of data being ingested */
288
    float *m_nfTable;
289
    int m_nTableSize;
290
    float m_nfOffset;
291
    char *m_pszLUTFile;
292
293
    CPL_DISALLOW_COPY_ASSIGN(RS2CalibRasterBand)
294
295
    void ReadLUT();
296
297
  public:
298
    RS2CalibRasterBand(RS2Dataset *poDataset, const char *pszPolarization,
299
                       GDALDataType eType,
300
                       std::unique_ptr<GDALDataset> poBandDataset,
301
                       eCalibration eCalib, const char *pszLUT);
302
    ~RS2CalibRasterBand() override;
303
304
    CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
305
};
306
307
/************************************************************************/
308
/*                              ReadLUT()                               */
309
/************************************************************************/
310
/* Read the provided LUT in to m_ndTable                                */
311
/************************************************************************/
312
void RS2CalibRasterBand::ReadLUT()
313
0
{
314
0
    CPLXMLNode *psLUT = CPLParseXMLFile(m_pszLUTFile);
315
316
0
    this->m_nfOffset = static_cast<float>(
317
0
        CPLAtof(CPLGetXMLValue(psLUT, "=lut.offset", "0.0")));
318
319
0
    char **papszLUTList = CSLTokenizeString2(
320
0
        CPLGetXMLValue(psLUT, "=lut.gains", ""), " ", CSLT_HONOURSTRINGS);
321
322
0
    m_nTableSize = CSLCount(papszLUTList);
323
324
0
    m_nfTable = static_cast<float *>(CPLMalloc(sizeof(float) * m_nTableSize));
325
326
0
    for (int i = 0; i < m_nTableSize; i++)
327
0
    {
328
0
        m_nfTable[i] = static_cast<float>(CPLAtof(papszLUTList[i]));
329
0
    }
330
331
0
    CPLDestroyXMLNode(psLUT);
332
333
0
    CSLDestroy(papszLUTList);
334
0
}
335
336
/************************************************************************/
337
/*                         RS2CalibRasterBand()                         */
338
/************************************************************************/
339
340
RS2CalibRasterBand::RS2CalibRasterBand(
341
    RS2Dataset *poDataset, const char *pszPolarization, GDALDataType eType,
342
    std::unique_ptr<GDALDataset> poBandDataset, eCalibration /* eCalib */,
343
    const char *pszLUT)
344
    :  // m_eCalib(eCalib),
345
0
      m_poBandDataset(std::move(poBandDataset)), m_eType(eType),
346
0
      m_nfTable(nullptr), m_nTableSize(0), m_nfOffset(0),
347
0
      m_pszLUTFile(VSIStrdup(pszLUT))
348
0
{
349
0
    poDS = poDataset;
350
351
0
    if (*pszPolarization != '\0')
352
0
    {
353
0
        SetMetadataItem("POLARIMETRIC_INTERP", pszPolarization);
354
0
    }
355
356
0
    if (eType == GDT_CInt16)
357
0
        eDataType = GDT_CFloat32;
358
0
    else
359
0
        eDataType = GDT_Float32;
360
361
0
    GDALRasterBand *poRasterBand = m_poBandDataset->GetRasterBand(1);
362
0
    poRasterBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
363
364
0
    ReadLUT();
365
0
}
366
367
/************************************************************************/
368
/*                        ~RS2CalibRasterBand()                         */
369
/************************************************************************/
370
371
RS2CalibRasterBand::~RS2CalibRasterBand()
372
0
{
373
0
    CPLFree(m_nfTable);
374
0
    CPLFree(m_pszLUTFile);
375
0
}
376
377
/************************************************************************/
378
/*                             IReadBlock()                             */
379
/************************************************************************/
380
381
CPLErr RS2CalibRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
382
                                      void *pImage)
383
0
{
384
    /* -------------------------------------------------------------------- */
385
    /*      If the last strip is partial, we need to avoid                  */
386
    /*      over-requesting.  We also need to initialize the extra part     */
387
    /*      of the block to zero.                                           */
388
    /* -------------------------------------------------------------------- */
389
0
    int nRequestYSize;
390
0
    if ((nBlockYOff + 1) * nBlockYSize > nRasterYSize)
391
0
    {
392
0
        nRequestYSize = nRasterYSize - nBlockYOff * nBlockYSize;
393
0
        memset(pImage, 0,
394
0
               static_cast<size_t>(GDALGetDataTypeSizeBytes(eDataType)) *
395
0
                   nBlockXSize * nBlockYSize);
396
0
    }
397
0
    else
398
0
    {
399
0
        nRequestYSize = nBlockYSize;
400
0
    }
401
402
0
    CPLErr eErr;
403
0
    if (m_eType == GDT_CInt16)
404
0
    {
405
        /* read in complex values */
406
0
        GInt16 *pnImageTmp = static_cast<GInt16 *>(
407
0
            VSI_MALLOC3_VERBOSE(2 * sizeof(int16_t), nBlockXSize, nBlockYSize));
408
0
        if (!pnImageTmp)
409
0
            return CE_Failure;
410
0
        if (m_poBandDataset->GetRasterCount() == 2)
411
0
        {
412
0
            eErr = m_poBandDataset->RasterIO(
413
0
                GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
414
0
                nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize,
415
0
                nRequestYSize, GDT_Int16, 2, nullptr, 4, nBlockXSize * 4, 2,
416
0
                nullptr);
417
0
        }
418
0
        else
419
0
        {
420
0
            eErr = m_poBandDataset->RasterIO(
421
0
                GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
422
0
                nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize,
423
0
                nRequestYSize, GDT_UInt32, 1, nullptr, 4, nBlockXSize * 4, 0,
424
0
                nullptr);
425
426
0
#ifdef CPL_LSB
427
            /* First, undo the 32bit swap. */
428
0
            GDALSwapWords(pImage, 4, nBlockXSize * nBlockYSize, 4);
429
430
            /* Then apply 16 bit swap. */
431
0
            GDALSwapWords(pImage, 2, nBlockXSize * nBlockYSize * 2, 2);
432
0
#endif
433
0
        }
434
435
        /* calibrate the complex values */
436
0
        for (int i = 0; i < nBlockYSize; i++)
437
0
        {
438
0
            for (int j = 0; j < nBlockXSize; j++)
439
0
            {
440
                /* calculate pixel offset in memory*/
441
0
                int nPixOff = (2 * (i * nBlockXSize)) + (j * 2);
442
443
0
                static_cast<float *>(pImage)[nPixOff] =
444
0
                    static_cast<float>(pnImageTmp[nPixOff]) /
445
0
                    (m_nfTable[nBlockXOff + j]);
446
0
                static_cast<float *>(pImage)[nPixOff + 1] =
447
0
                    static_cast<float>(pnImageTmp[nPixOff + 1]) /
448
0
                    (m_nfTable[nBlockXOff + j]);
449
0
            }
450
0
        }
451
0
        CPLFree(pnImageTmp);
452
0
    }
453
    // If the underlying file is NITF CFloat32
454
0
    else if (eDataType == GDT_CFloat32 &&
455
0
             m_poBandDataset->GetRasterCount() == 1)
456
0
    {
457
0
        eErr = m_poBandDataset->RasterIO(
458
0
            GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
459
0
            nBlockXSize, nRequestYSize, pImage, nBlockXSize, nRequestYSize,
460
0
            GDT_CFloat32, 1, nullptr, 2 * static_cast<int>(sizeof(float)),
461
0
            nBlockXSize * 2 * static_cast<GSpacing>(sizeof(float)), 0, nullptr);
462
463
        /* calibrate the complex values */
464
0
        for (int i = 0; i < nBlockYSize; i++)
465
0
        {
466
0
            for (int j = 0; j < nBlockXSize; j++)
467
0
            {
468
                /* calculate pixel offset in memory*/
469
0
                const int nPixOff = 2 * (i * nBlockXSize + j);
470
471
0
                static_cast<float *>(pImage)[nPixOff] /=
472
0
                    m_nfTable[nBlockXOff * nBlockXSize + j];
473
0
                static_cast<float *>(pImage)[nPixOff + 1] /=
474
0
                    m_nfTable[nBlockXOff * nBlockXSize + j];
475
0
            }
476
0
        }
477
0
    }
478
0
    else if (m_eType == GDT_UInt16)
479
0
    {
480
        /* read in detected values */
481
0
        GUInt16 *pnImageTmp = static_cast<GUInt16 *>(
482
0
            VSI_MALLOC3_VERBOSE(sizeof(uint16_t), nBlockXSize, nBlockYSize));
483
0
        if (!pnImageTmp)
484
0
            return CE_Failure;
485
0
        eErr = m_poBandDataset->RasterIO(
486
0
            GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
487
0
            nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize, nRequestYSize,
488
0
            GDT_UInt16, 1, nullptr, 2, nBlockXSize * 2, 0, nullptr);
489
490
        /* iterate over detected values */
491
0
        for (int i = 0; i < nBlockYSize; i++)
492
0
        {
493
0
            for (int j = 0; j < nBlockXSize; j++)
494
0
            {
495
0
                int nPixOff = (i * nBlockXSize) + j;
496
497
0
                static_cast<float *>(pImage)[nPixOff] =
498
0
                    ((static_cast<float>(pnImageTmp[nPixOff]) *
499
0
                      static_cast<float>(pnImageTmp[nPixOff])) +
500
0
                     m_nfOffset) /
501
0
                    m_nfTable[nBlockXOff + j];
502
0
            }
503
0
        }
504
0
        CPLFree(pnImageTmp);
505
0
    } /* Ticket #2104: Support for ScanSAR products */
506
0
    else if (m_eType == GDT_UInt8)
507
0
    {
508
0
        GByte *pnImageTmp =
509
0
            static_cast<GByte *>(VSI_MALLOC2_VERBOSE(nBlockXSize, nBlockYSize));
510
0
        if (!pnImageTmp)
511
0
            return CE_Failure;
512
0
        eErr = m_poBandDataset->RasterIO(
513
0
            GF_Read, nBlockXOff * nBlockXSize, nBlockYOff * nBlockYSize,
514
0
            nBlockXSize, nRequestYSize, pnImageTmp, nBlockXSize, nRequestYSize,
515
0
            GDT_UInt8, 1, nullptr, 1, 1, 0, nullptr);
516
517
        /* iterate over detected values */
518
0
        for (int i = 0; i < nBlockYSize; i++)
519
0
        {
520
0
            for (int j = 0; j < nBlockXSize; j++)
521
0
            {
522
0
                int nPixOff = (i * nBlockXSize) + j;
523
524
0
                static_cast<float *>(pImage)[nPixOff] =
525
0
                    ((pnImageTmp[nPixOff] * pnImageTmp[nPixOff]) + m_nfOffset) /
526
0
                    m_nfTable[nBlockXOff + j];
527
0
            }
528
0
        }
529
0
        CPLFree(pnImageTmp);
530
0
    }
531
0
    else
532
0
    {
533
0
        CPLAssert(false);
534
0
        return CE_Failure;
535
0
    }
536
0
    return eErr;
537
0
}
538
539
/************************************************************************/
540
/* ==================================================================== */
541
/*                              RS2Dataset                              */
542
/* ==================================================================== */
543
/************************************************************************/
544
545
/************************************************************************/
546
/*                             RS2Dataset()                             */
547
/************************************************************************/
548
549
RS2Dataset::RS2Dataset()
550
0
    : psProduct(nullptr), nGCPCount(0), pasGCPList(nullptr),
551
0
      papszSubDatasets(nullptr), bHaveGeoTransform(FALSE),
552
0
      papszExtraFiles(nullptr)
553
0
{
554
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
555
0
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
556
0
}
557
558
/************************************************************************/
559
/*                            ~RS2Dataset()                             */
560
/************************************************************************/
561
562
RS2Dataset::~RS2Dataset()
563
564
0
{
565
0
    RS2Dataset::FlushCache(true);
566
567
0
    CPLDestroyXMLNode(psProduct);
568
569
0
    if (nGCPCount > 0)
570
0
    {
571
0
        GDALDeinitGCPs(nGCPCount, pasGCPList);
572
0
        CPLFree(pasGCPList);
573
0
    }
574
575
0
    RS2Dataset::CloseDependentDatasets();
576
577
0
    CSLDestroy(papszSubDatasets);
578
0
    CSLDestroy(papszExtraFiles);
579
0
}
580
581
/************************************************************************/
582
/*                       CloseDependentDatasets()                       */
583
/************************************************************************/
584
585
int RS2Dataset::CloseDependentDatasets()
586
0
{
587
0
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
588
589
0
    if (nBands != 0)
590
0
        bHasDroppedRef = TRUE;
591
592
0
    for (int iBand = 0; iBand < nBands; iBand++)
593
0
    {
594
0
        delete papoBands[iBand];
595
0
    }
596
0
    nBands = 0;
597
598
0
    return bHasDroppedRef;
599
0
}
600
601
/************************************************************************/
602
/*                            GetFileList()                             */
603
/************************************************************************/
604
605
char **RS2Dataset::GetFileList()
606
607
0
{
608
0
    char **papszFileList = GDALPamDataset::GetFileList();
609
610
0
    papszFileList = CSLInsertStrings(papszFileList, -1, papszExtraFiles);
611
612
0
    return papszFileList;
613
0
}
614
615
/************************************************************************/
616
/*                              Identify()                              */
617
/************************************************************************/
618
619
int RS2Dataset::Identify(GDALOpenInfo *poOpenInfo)
620
515k
{
621
    /* Check for the case where we're trying to read the calibrated data: */
622
515k
    if (STARTS_WITH_CI(poOpenInfo->pszFilename, "RADARSAT_2_CALIB:"))
623
0
    {
624
0
        return TRUE;
625
0
    }
626
627
    /* Check for directory access when there is a product.xml file in the
628
       directory. */
629
515k
    if (poOpenInfo->bIsDirectory)
630
3.70k
    {
631
3.70k
        const CPLString osMDFilename = CPLFormCIFilenameSafe(
632
3.70k
            poOpenInfo->pszFilename, "product.xml", nullptr);
633
634
3.70k
        GDALOpenInfo oOpenInfo(osMDFilename.c_str(), GA_ReadOnly);
635
3.70k
        return Identify(&oOpenInfo);
636
3.70k
    }
637
638
    /* otherwise, do our normal stuff */
639
511k
    if (strlen(poOpenInfo->pszFilename) < 11 ||
640
493k
        !EQUAL(poOpenInfo->pszFilename + strlen(poOpenInfo->pszFilename) - 11,
641
511k
               "product.xml"))
642
507k
        return FALSE;
643
644
4.07k
    if (poOpenInfo->nHeaderBytes < 100)
645
4.04k
        return FALSE;
646
647
27
    if (strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
648
27
               "/rs2") == nullptr ||
649
0
        strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
650
0
               "<product") == nullptr)
651
27
        return FALSE;
652
653
0
    return TRUE;
654
27
}
655
656
/************************************************************************/
657
/*                                Open()                                */
658
/************************************************************************/
659
660
GDALDataset *RS2Dataset::Open(GDALOpenInfo *poOpenInfo)
661
662
0
{
663
    /* -------------------------------------------------------------------- */
664
    /*      Is this a RADARSAT-2 Product.xml definition?                   */
665
    /* -------------------------------------------------------------------- */
666
0
    if (!RS2Dataset::Identify(poOpenInfo))
667
0
    {
668
0
        return nullptr;
669
0
    }
670
671
    /* -------------------------------------------------------------------- */
672
    /*        Get subdataset information, if relevant */
673
    /* -------------------------------------------------------------------- */
674
0
    const char *pszFilename = poOpenInfo->pszFilename;
675
0
    eCalibration eCalib = None;
676
677
0
    if (STARTS_WITH_CI(pszFilename, "RADARSAT_2_CALIB:"))
678
0
    {
679
0
        pszFilename += 17;
680
681
0
        if (STARTS_WITH_CI(pszFilename, "BETA0"))
682
0
            eCalib = Beta0;
683
0
        else if (STARTS_WITH_CI(pszFilename, "SIGMA0"))
684
0
            eCalib = Sigma0;
685
0
        else if (STARTS_WITH_CI(pszFilename, "GAMMA"))
686
0
            eCalib = Gamma;
687
0
        else if (STARTS_WITH_CI(pszFilename, "UNCALIB"))
688
0
            eCalib = Uncalib;
689
0
        else
690
0
            eCalib = None;
691
692
        /* advance the pointer to the actual filename */
693
0
        while (*pszFilename != '\0' && *pszFilename != ':')
694
0
            pszFilename++;
695
696
0
        if (*pszFilename == ':')
697
0
            pszFilename++;
698
699
        // need to redo the directory check:
700
        // the GDALOpenInfo check would have failed because of the calibration
701
        // string on the filename
702
0
        VSIStatBufL sStat;
703
0
        if (VSIStatL(pszFilename, &sStat) == 0)
704
0
            poOpenInfo->bIsDirectory = VSI_ISDIR(sStat.st_mode);
705
0
    }
706
707
0
    CPLString osMDFilename;
708
0
    if (poOpenInfo->bIsDirectory)
709
0
    {
710
0
        osMDFilename =
711
0
            CPLFormCIFilenameSafe(pszFilename, "product.xml", nullptr);
712
0
    }
713
0
    else
714
0
        osMDFilename = pszFilename;
715
716
    /* -------------------------------------------------------------------- */
717
    /*      Ingest the Product.xml file.                                    */
718
    /* -------------------------------------------------------------------- */
719
0
    CPLXMLNode *psProduct = CPLParseXMLFile(osMDFilename);
720
0
    if (psProduct == nullptr)
721
0
        return nullptr;
722
723
    /* -------------------------------------------------------------------- */
724
    /*      Confirm the requested access is supported.                      */
725
    /* -------------------------------------------------------------------- */
726
0
    if (poOpenInfo->eAccess == GA_Update)
727
0
    {
728
0
        CPLDestroyXMLNode(psProduct);
729
0
        ReportUpdateNotSupportedByDriver("RS2");
730
0
        return nullptr;
731
0
    }
732
733
0
    CPLXMLNode *psImageAttributes =
734
0
        CPLGetXMLNode(psProduct, "=product.imageAttributes");
735
0
    if (psImageAttributes == nullptr)
736
0
    {
737
0
        CPLDestroyXMLNode(psProduct);
738
0
        CPLError(CE_Failure, CPLE_OpenFailed,
739
0
                 "Failed to find <imageAttributes> in document.");
740
0
        return nullptr;
741
0
    }
742
743
0
    CPLXMLNode *psImageGenerationParameters =
744
0
        CPLGetXMLNode(psProduct, "=product.imageGenerationParameters");
745
0
    if (psImageGenerationParameters == nullptr)
746
0
    {
747
0
        CPLDestroyXMLNode(psProduct);
748
0
        CPLError(CE_Failure, CPLE_OpenFailed,
749
0
                 "Failed to find <imageGenerationParameters> in document.");
750
0
        return nullptr;
751
0
    }
752
753
    /* -------------------------------------------------------------------- */
754
    /*      Create the dataset.                                             */
755
    /* -------------------------------------------------------------------- */
756
0
    auto poDS = std::make_unique<RS2Dataset>();
757
758
0
    poDS->psProduct = psProduct;
759
760
    /* -------------------------------------------------------------------- */
761
    /*      Get overall image information.                                  */
762
    /* -------------------------------------------------------------------- */
763
0
    poDS->nRasterXSize = atoi(CPLGetXMLValue(
764
0
        psImageAttributes, "rasterAttributes.numberOfSamplesPerLine", "-1"));
765
0
    poDS->nRasterYSize = atoi(CPLGetXMLValue(
766
0
        psImageAttributes, "rasterAttributes.numberofLines", "-1"));
767
0
    if (poDS->nRasterXSize <= 1 || poDS->nRasterYSize <= 1)
768
0
    {
769
0
        CPLError(
770
0
            CE_Failure, CPLE_OpenFailed,
771
0
            "Non-sane raster dimensions provided in product.xml. If this is "
772
0
            "a valid RADARSAT-2 scene, please contact your data provider for "
773
0
            "a corrected dataset.");
774
0
        return nullptr;
775
0
    }
776
777
    /* -------------------------------------------------------------------- */
778
    /*      Check product type, as to determine if there are LUTs for       */
779
    /*      calibration purposes.                                           */
780
    /* -------------------------------------------------------------------- */
781
782
0
    const char *pszProductType =
783
0
        CPLGetXMLValue(psImageGenerationParameters,
784
0
                       "generalProcessingInformation.productType", "UNK");
785
786
0
    poDS->SetMetadataItem("PRODUCT_TYPE", pszProductType);
787
788
    /* the following cases can be assumed to have no LUTs, as per
789
     * RN-RP-51-2713, but also common sense
790
     */
791
0
    bool bCanCalib = false;
792
0
    if (!(STARTS_WITH_CI(pszProductType, "UNK") ||
793
0
          STARTS_WITH_CI(pszProductType, "SSG") ||
794
0
          STARTS_WITH_CI(pszProductType, "SPG")))
795
0
    {
796
0
        bCanCalib = true;
797
0
    }
798
799
    /* -------------------------------------------------------------------- */
800
    /*      Get dataType (so we can recognise complex data), and the        */
801
    /*      bitsPerSample.                                                  */
802
    /* -------------------------------------------------------------------- */
803
0
    const char *pszDataType =
804
0
        CPLGetXMLValue(psImageAttributes, "rasterAttributes.dataType", "");
805
0
    const int nBitsPerSample = atoi(CPLGetXMLValue(
806
0
        psImageAttributes, "rasterAttributes.bitsPerSample", ""));
807
808
0
    GDALDataType eDataType;
809
0
    if (nBitsPerSample == 16 && EQUAL(pszDataType, "Complex"))
810
0
        eDataType = GDT_CInt16;
811
0
    else if (nBitsPerSample == 32 &&
812
0
             EQUAL(pszDataType,
813
0
                   "Complex"))  // NITF datasets can come in this configuration
814
0
        eDataType = GDT_CFloat32;
815
0
    else if (nBitsPerSample == 16 && STARTS_WITH_CI(pszDataType, "Mag"))
816
0
        eDataType = GDT_UInt16;
817
0
    else if (nBitsPerSample == 8 && STARTS_WITH_CI(pszDataType, "Mag"))
818
0
        eDataType = GDT_UInt8;
819
0
    else
820
0
    {
821
0
        CPLError(
822
0
            CE_Failure, CPLE_AppDefined,
823
0
            "dataType=%s, bitsPerSample=%d: not a supported configuration.",
824
0
            pszDataType, nBitsPerSample);
825
0
        return nullptr;
826
0
    }
827
828
    /* while we're at it, extract the pixel spacing information */
829
0
    const char *pszPixelSpacing = CPLGetXMLValue(
830
0
        psImageAttributes, "rasterAttributes.sampledPixelSpacing", "UNK");
831
0
    poDS->SetMetadataItem("PIXEL_SPACING", pszPixelSpacing);
832
833
0
    const char *pszLineSpacing = CPLGetXMLValue(
834
0
        psImageAttributes, "rasterAttributes.sampledLineSpacing", "UNK");
835
0
    poDS->SetMetadataItem("LINE_SPACING", pszLineSpacing);
836
837
    /* -------------------------------------------------------------------- */
838
    /*      Open each of the data files as a complex band.                  */
839
    /* -------------------------------------------------------------------- */
840
0
    CPLString osBeta0LUT;
841
0
    CPLString osGammaLUT;
842
0
    CPLString osSigma0LUT;
843
844
0
    const std::string osPath = CPLGetPathSafe(osMDFilename);
845
846
0
    CPLXMLNode *psNode = psImageAttributes->psChild;
847
0
    for (; psNode != nullptr; psNode = psNode->psNext)
848
0
    {
849
0
        if (psNode->eType != CXT_Element ||
850
0
            !(EQUAL(psNode->pszValue, "fullResolutionImageData") ||
851
0
              EQUAL(psNode->pszValue, "lookupTable")))
852
0
            continue;
853
854
0
        if (EQUAL(psNode->pszValue, "lookupTable") && bCanCalib)
855
0
        {
856
            /* Determine which incidence angle correction this is */
857
0
            const char *pszLUTType =
858
0
                CPLGetXMLValue(psNode, "incidenceAngleCorrection", "");
859
0
            const char *pszLUTFile = CPLGetXMLValue(psNode, "", "");
860
0
            if (CPLHasPathTraversal(pszLUTFile))
861
0
            {
862
0
                CPLError(CE_Failure, CPLE_NotSupported,
863
0
                         "Path traversal detected in %s", pszLUTFile);
864
0
                return nullptr;
865
0
            }
866
0
            CPLString osLUTFilePath =
867
0
                CPLFormFilenameSafe(osPath.c_str(), pszLUTFile, nullptr);
868
869
0
            if (EQUAL(pszLUTType, ""))
870
0
                continue;
871
0
            else if (EQUAL(pszLUTType, "Beta Nought") &&
872
0
                     IsValidXMLFile(osPath.c_str(), pszLUTFile))
873
0
            {
874
0
                poDS->papszExtraFiles =
875
0
                    CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
876
877
0
                osBeta0LUT = pszLUTFile;
878
0
                poDS->SetMetadataItem("BETA_NOUGHT_LUT", pszLUTFile);
879
880
0
                std::string osDSName("RADARSAT_2_CALIB:BETA0:");
881
0
                osDSName += osMDFilename;
882
0
                poDS->papszSubDatasets =
883
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_3_NAME",
884
0
                                    osDSName.c_str());
885
0
                poDS->papszSubDatasets =
886
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_3_DESC",
887
0
                                    "Beta Nought calibrated");
888
0
            }
889
0
            else if (EQUAL(pszLUTType, "Sigma Nought") &&
890
0
                     IsValidXMLFile(osPath.c_str(), pszLUTFile))
891
0
            {
892
0
                poDS->papszExtraFiles =
893
0
                    CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
894
895
0
                osSigma0LUT = pszLUTFile;
896
0
                poDS->SetMetadataItem("SIGMA_NOUGHT_LUT", pszLUTFile);
897
898
0
                std::string osDSName("RADARSAT_2_CALIB:SIGMA0:");
899
0
                osDSName += osMDFilename;
900
0
                poDS->papszSubDatasets =
901
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_2_NAME",
902
0
                                    osDSName.c_str());
903
0
                poDS->papszSubDatasets =
904
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_2_DESC",
905
0
                                    "Sigma Nought calibrated");
906
0
            }
907
0
            else if (EQUAL(pszLUTType, "Gamma") &&
908
0
                     IsValidXMLFile(osPath.c_str(), pszLUTFile))
909
0
            {
910
0
                poDS->papszExtraFiles =
911
0
                    CSLAddString(poDS->papszExtraFiles, osLUTFilePath);
912
913
0
                osGammaLUT = pszLUTFile;
914
0
                poDS->SetMetadataItem("GAMMA_LUT", pszLUTFile);
915
916
0
                std::string osDSName("RADARSAT_2_CALIB:GAMMA:");
917
0
                osDSName += osMDFilename;
918
0
                poDS->papszSubDatasets =
919
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_4_NAME",
920
0
                                    osDSName.c_str());
921
0
                poDS->papszSubDatasets =
922
0
                    CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_4_DESC",
923
0
                                    "Gamma calibrated");
924
0
            }
925
0
            continue;
926
0
        }
927
928
        /* --------------------------------------------------------------------
929
         */
930
        /*      Fetch filename. */
931
        /* --------------------------------------------------------------------
932
         */
933
0
        const char *pszBasename = CPLGetXMLValue(psNode, "", "");
934
0
        if (*pszBasename == '\0')
935
0
            continue;
936
0
        std::string osPathImage = osPath;
937
0
        std::string osBasename = pszBasename;
938
0
        if (STARTS_WITH(osBasename.c_str(), "../") ||
939
0
            STARTS_WITH(osBasename.c_str(), "..\\"))
940
0
        {
941
0
            osPathImage = CPLGetPathSafe(osPath.c_str());
942
0
            osBasename = osBasename.substr(strlen("../"));
943
0
        }
944
0
        if (CPLHasPathTraversal(osBasename.c_str()))
945
0
        {
946
0
            CPLError(CE_Failure, CPLE_NotSupported,
947
0
                     "Path traversal detected in %s", osBasename.c_str());
948
0
            return nullptr;
949
0
        }
950
        /* --------------------------------------------------------------------
951
         */
952
        /*      Form full filename (path of product.xml + basename). */
953
        /* --------------------------------------------------------------------
954
         */
955
0
        const std::string osFullname = CPLFormFilenameSafe(
956
0
            osPathImage.c_str(), osBasename.c_str(), nullptr);
957
958
        /* --------------------------------------------------------------------
959
         */
960
        /*      Try and open the file. */
961
        /* --------------------------------------------------------------------
962
         */
963
0
        auto poBandDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
964
0
            osFullname.c_str(), GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
965
0
        if (poBandDS == nullptr)
966
0
        {
967
0
            continue;
968
0
        }
969
0
        if (poBandDS->GetRasterCount() == 0)
970
0
        {
971
0
            continue;
972
0
        }
973
974
        /* Some CFloat32 NITF files have nBitsPerSample incorrectly reported */
975
        /* as 16, and get misinterpreted as CInt16.  Check the underlying NITF
976
         */
977
        /* and override if this is the case. */
978
0
        if (poBandDS->GetRasterBand(1)->GetRasterDataType() == GDT_CFloat32)
979
0
            eDataType = GDT_CFloat32;
980
981
0
        BandMapping b = GetBandFileMapping(eDataType, poBandDS.get());
982
0
        const bool twoBandComplex = b == TWOBANDCOMPLEX;
983
984
0
        poDS->papszExtraFiles =
985
0
            CSLAddString(poDS->papszExtraFiles, osFullname.c_str());
986
987
        /* --------------------------------------------------------------------
988
         */
989
        /*      Create the band. */
990
        /* --------------------------------------------------------------------
991
         */
992
0
        if (eCalib == None || eCalib == Uncalib)
993
0
        {
994
0
            auto poBand = std::make_unique<RS2RasterBand>(
995
0
                poDS.get(), eDataType, CPLGetXMLValue(psNode, "pole", ""),
996
0
                std::move(poBandDS), twoBandComplex);
997
998
0
            poDS->SetBand(poDS->GetRasterCount() + 1, std::move(poBand));
999
0
        }
1000
0
        else
1001
0
        {
1002
0
            const char *pszLUT = nullptr;
1003
0
            switch (eCalib)
1004
0
            {
1005
0
                case Sigma0:
1006
0
                    pszLUT = osSigma0LUT;
1007
0
                    break;
1008
0
                case Beta0:
1009
0
                    pszLUT = osBeta0LUT;
1010
0
                    break;
1011
0
                case Gamma:
1012
0
                    pszLUT = osGammaLUT;
1013
0
                    break;
1014
0
                default:
1015
                    /* we should bomb gracefully... */
1016
0
                    pszLUT = osSigma0LUT;
1017
0
            }
1018
0
            auto poBand = std::make_unique<RS2CalibRasterBand>(
1019
0
                poDS.get(), CPLGetXMLValue(psNode, "pole", ""), eDataType,
1020
0
                std::move(poBandDS), eCalib,
1021
0
                CPLFormFilenameSafe(osPath.c_str(), pszLUT, nullptr).c_str());
1022
0
            poDS->SetBand(poDS->GetRasterCount() + 1, std::move(poBand));
1023
0
        }
1024
0
    }
1025
1026
0
    if (poDS->papszSubDatasets != nullptr && eCalib == None)
1027
0
    {
1028
0
        std::string osDSName("RADARSAT_2_CALIB:UNCALIB:");
1029
0
        osDSName += osMDFilename;
1030
0
        poDS->papszSubDatasets = CSLSetNameValue(
1031
0
            poDS->papszSubDatasets, "SUBDATASET_1_NAME", osDSName.c_str());
1032
0
        poDS->papszSubDatasets =
1033
0
            CSLSetNameValue(poDS->papszSubDatasets, "SUBDATASET_1_DESC",
1034
0
                            "Uncalibrated digital numbers");
1035
0
    }
1036
0
    else if (poDS->papszSubDatasets != nullptr)
1037
0
    {
1038
0
        CSLDestroy(poDS->papszSubDatasets);
1039
0
        poDS->papszSubDatasets = nullptr;
1040
0
    }
1041
1042
    /* -------------------------------------------------------------------- */
1043
    /*      Set the appropriate MATRIX_REPRESENTATION.                      */
1044
    /* -------------------------------------------------------------------- */
1045
1046
0
    if (poDS->GetRasterCount() == 4 &&
1047
0
        (eDataType == GDT_CInt16 || eDataType == GDT_CFloat32))
1048
0
    {
1049
0
        poDS->SetMetadataItem("MATRIX_REPRESENTATION", "SCATTERING");
1050
0
    }
1051
1052
    /* -------------------------------------------------------------------- */
1053
    /*      Collect a few useful metadata items                             */
1054
    /* -------------------------------------------------------------------- */
1055
1056
0
    CPLXMLNode *psSourceAttrs =
1057
0
        CPLGetXMLNode(psProduct, "=product.sourceAttributes");
1058
0
    const char *pszItem = CPLGetXMLValue(psSourceAttrs, "satellite", "");
1059
0
    poDS->SetMetadataItem("SATELLITE_IDENTIFIER", pszItem);
1060
1061
0
    pszItem = CPLGetXMLValue(psSourceAttrs, "sensor", "");
1062
0
    poDS->SetMetadataItem("SENSOR_IDENTIFIER", pszItem);
1063
1064
0
    if (psSourceAttrs != nullptr)
1065
0
    {
1066
        /* Get beam mode mnemonic */
1067
0
        pszItem = CPLGetXMLValue(psSourceAttrs, "beamModeMnemonic", "UNK");
1068
0
        poDS->SetMetadataItem("BEAM_MODE", pszItem);
1069
0
        pszItem = CPLGetXMLValue(psSourceAttrs, "rawDataStartTime", "UNK");
1070
0
        poDS->SetMetadataItem("ACQUISITION_START_TIME", pszItem);
1071
1072
0
        pszItem =
1073
0
            CPLGetXMLValue(psSourceAttrs, "inputDatasetFacilityId", "UNK");
1074
0
        poDS->SetMetadataItem("FACILITY_IDENTIFIER", pszItem);
1075
1076
0
        pszItem = CPLGetXMLValue(
1077
0
            psSourceAttrs, "orbitAndAttitude.orbitInformation.passDirection",
1078
0
            "UNK");
1079
0
        poDS->SetMetadataItem("ORBIT_DIRECTION", pszItem);
1080
0
        pszItem = CPLGetXMLValue(
1081
0
            psSourceAttrs, "orbitAndAttitude.orbitInformation.orbitDataSource",
1082
0
            "UNK");
1083
0
        poDS->SetMetadataItem("ORBIT_DATA_SOURCE", pszItem);
1084
0
        pszItem = CPLGetXMLValue(
1085
0
            psSourceAttrs, "orbitAndAttitude.orbitInformation.orbitDataFile",
1086
0
            "UNK");
1087
0
        poDS->SetMetadataItem("ORBIT_DATA_FILE", pszItem);
1088
0
    }
1089
1090
0
    CPLXMLNode *psSarProcessingInformation =
1091
0
        CPLGetXMLNode(psProduct, "=product.imageGenerationParameters");
1092
1093
0
    if (psSarProcessingInformation != nullptr)
1094
0
    {
1095
        /* Get incidence angle information */
1096
0
        pszItem = CPLGetXMLValue(
1097
0
            psSarProcessingInformation,
1098
0
            "sarProcessingInformation.incidenceAngleNearRange", "UNK");
1099
0
        poDS->SetMetadataItem("NEAR_RANGE_INCIDENCE_ANGLE", pszItem);
1100
1101
0
        pszItem = CPLGetXMLValue(
1102
0
            psSarProcessingInformation,
1103
0
            "sarProcessingInformation.incidenceAngleFarRange", "UNK");
1104
0
        poDS->SetMetadataItem("FAR_RANGE_INCIDENCE_ANGLE", pszItem);
1105
1106
0
        pszItem = CPLGetXMLValue(psSarProcessingInformation,
1107
0
                                 "sarProcessingInformation.slantRangeNearEdge",
1108
0
                                 "UNK");
1109
0
        poDS->SetMetadataItem("SLANT_RANGE_NEAR_EDGE", pszItem);
1110
1111
0
        pszItem = CPLGetXMLValue(
1112
0
            psSarProcessingInformation,
1113
0
            "sarProcessingInformation.zeroDopplerTimeFirstLine", "UNK");
1114
0
        poDS->SetMetadataItem("FIRST_LINE_TIME", pszItem);
1115
1116
0
        pszItem = CPLGetXMLValue(
1117
0
            psSarProcessingInformation,
1118
0
            "sarProcessingInformation.zeroDopplerTimeLastLine", "UNK");
1119
0
        poDS->SetMetadataItem("LAST_LINE_TIME", pszItem);
1120
1121
0
        pszItem =
1122
0
            CPLGetXMLValue(psSarProcessingInformation,
1123
0
                           "generalProcessingInformation.productType", "UNK");
1124
0
        poDS->SetMetadataItem("PRODUCT_TYPE", pszItem);
1125
1126
0
        pszItem = CPLGetXMLValue(
1127
0
            psSarProcessingInformation,
1128
0
            "generalProcessingInformation.processingFacility", "UNK");
1129
0
        poDS->SetMetadataItem("PROCESSING_FACILITY", pszItem);
1130
1131
0
        pszItem = CPLGetXMLValue(psSarProcessingInformation,
1132
0
                                 "generalProcessingInformation.processingTime",
1133
0
                                 "UNK");
1134
0
        poDS->SetMetadataItem("PROCESSING_TIME", pszItem);
1135
0
    }
1136
1137
    /*--------------------------------------------------------------------- */
1138
    /*      Collect Map projection/Geotransform information, if present     */
1139
    /* -------------------------------------------------------------------- */
1140
0
    CPLXMLNode *psMapProjection =
1141
0
        CPLGetXMLNode(psImageAttributes, "geographicInformation.mapProjection");
1142
1143
0
    if (psMapProjection != nullptr)
1144
0
    {
1145
0
        CPLXMLNode *psPos =
1146
0
            CPLGetXMLNode(psMapProjection, "positioningInformation");
1147
1148
0
        pszItem =
1149
0
            CPLGetXMLValue(psMapProjection, "mapProjectionDescriptor", "UNK");
1150
0
        poDS->SetMetadataItem("MAP_PROJECTION_DESCRIPTOR", pszItem);
1151
1152
0
        pszItem =
1153
0
            CPLGetXMLValue(psMapProjection, "mapProjectionOrientation", "UNK");
1154
0
        poDS->SetMetadataItem("MAP_PROJECTION_ORIENTATION", pszItem);
1155
1156
0
        pszItem = CPLGetXMLValue(psMapProjection, "resamplingKernel", "UNK");
1157
0
        poDS->SetMetadataItem("RESAMPLING_KERNEL", pszItem);
1158
1159
0
        pszItem = CPLGetXMLValue(psMapProjection, "satelliteHeading", "UNK");
1160
0
        poDS->SetMetadataItem("SATELLITE_HEADING", pszItem);
1161
1162
0
        if (psPos != nullptr)
1163
0
        {
1164
0
            const double tl_x = CPLStrtod(
1165
0
                CPLGetXMLValue(psPos, "upperLeftCorner.mapCoordinate.easting",
1166
0
                               "0.0"),
1167
0
                nullptr);
1168
0
            const double tl_y = CPLStrtod(
1169
0
                CPLGetXMLValue(psPos, "upperLeftCorner.mapCoordinate.northing",
1170
0
                               "0.0"),
1171
0
                nullptr);
1172
0
            const double bl_x = CPLStrtod(
1173
0
                CPLGetXMLValue(psPos, "lowerLeftCorner.mapCoordinate.easting",
1174
0
                               "0.0"),
1175
0
                nullptr);
1176
0
            const double bl_y = CPLStrtod(
1177
0
                CPLGetXMLValue(psPos, "lowerLeftCorner.mapCoordinate.northing",
1178
0
                               "0.0"),
1179
0
                nullptr);
1180
0
            const double tr_x = CPLStrtod(
1181
0
                CPLGetXMLValue(psPos, "upperRightCorner.mapCoordinate.easting",
1182
0
                               "0.0"),
1183
0
                nullptr);
1184
0
            const double tr_y = CPLStrtod(
1185
0
                CPLGetXMLValue(psPos, "upperRightCorner.mapCoordinate.northing",
1186
0
                               "0.0"),
1187
0
                nullptr);
1188
0
            poDS->m_gt.xscale = (tr_x - tl_x) / (poDS->nRasterXSize - 1);
1189
0
            poDS->m_gt.yrot = (tr_y - tl_y) / (poDS->nRasterXSize - 1);
1190
0
            poDS->m_gt.xrot = (bl_x - tl_x) / (poDS->nRasterYSize - 1);
1191
0
            poDS->m_gt.yscale = (bl_y - tl_y) / (poDS->nRasterYSize - 1);
1192
0
            poDS->m_gt.xorig =
1193
0
                (tl_x - 0.5 * poDS->m_gt.xscale - 0.5 * poDS->m_gt.xrot);
1194
0
            poDS->m_gt.yorig =
1195
0
                (tl_y - 0.5 * poDS->m_gt.yrot - 0.5 * poDS->m_gt.yscale);
1196
1197
            /* Use bottom right pixel to test geotransform */
1198
0
            const double br_x = CPLStrtod(
1199
0
                CPLGetXMLValue(psPos, "lowerRightCorner.mapCoordinate.easting",
1200
0
                               "0.0"),
1201
0
                nullptr);
1202
0
            const double br_y = CPLStrtod(
1203
0
                CPLGetXMLValue(psPos, "lowerRightCorner.mapCoordinate.northing",
1204
0
                               "0.0"),
1205
0
                nullptr);
1206
0
            const double testx =
1207
0
                poDS->m_gt.xorig +
1208
0
                poDS->m_gt.xscale * (poDS->nRasterXSize - 0.5) +
1209
0
                poDS->m_gt.xrot * (poDS->nRasterYSize - 0.5);
1210
0
            const double testy = poDS->m_gt.yorig +
1211
0
                                 poDS->m_gt.yrot * (poDS->nRasterXSize - 0.5) +
1212
0
                                 poDS->m_gt.yscale * (poDS->nRasterYSize - 0.5);
1213
1214
            /* Give 1/4 pixel numerical error leeway in calculating location
1215
               based on affine transform */
1216
0
            if ((fabs(testx - br_x) >
1217
0
                 fabs(0.25 * (poDS->m_gt.xscale + poDS->m_gt.xrot))) ||
1218
0
                (fabs(testy - br_y) >
1219
0
                 fabs(0.25 * (poDS->m_gt.yrot + poDS->m_gt.yscale))))
1220
0
            {
1221
0
                CPLError(CE_Warning, CPLE_AppDefined,
1222
0
                         "Unexpected error in calculating affine transform: "
1223
0
                         "corner coordinates inconsistent.");
1224
0
            }
1225
0
            else
1226
0
            {
1227
0
                poDS->bHaveGeoTransform = TRUE;
1228
0
            }
1229
0
        }
1230
0
    }
1231
1232
    /* -------------------------------------------------------------------- */
1233
    /*      Collect Projection String Information                           */
1234
    /* -------------------------------------------------------------------- */
1235
0
    CPLXMLNode *psEllipsoid =
1236
0
        CPLGetXMLNode(psImageAttributes,
1237
0
                      "geographicInformation.referenceEllipsoidParameters");
1238
1239
0
    if (psEllipsoid != nullptr)
1240
0
    {
1241
0
        OGRSpatialReference oLL, oPrj;
1242
0
        oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1243
0
        oPrj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1244
1245
0
        const char *pszGeodeticTerrainHeight =
1246
0
            CPLGetXMLValue(psEllipsoid, "geodeticTerrainHeight", "UNK");
1247
0
        poDS->SetMetadataItem("GEODETIC_TERRAIN_HEIGHT",
1248
0
                              pszGeodeticTerrainHeight);
1249
1250
0
        const char *pszEllipsoidName =
1251
0
            CPLGetXMLValue(psEllipsoid, "ellipsoidName", "");
1252
0
        double minor_axis =
1253
0
            CPLAtof(CPLGetXMLValue(psEllipsoid, "semiMinorAxis", "0.0"));
1254
0
        double major_axis =
1255
0
            CPLAtof(CPLGetXMLValue(psEllipsoid, "semiMajorAxis", "0.0"));
1256
1257
0
        if (EQUAL(pszEllipsoidName, "") || (minor_axis == 0.0) ||
1258
0
            (major_axis == 0.0))
1259
0
        {
1260
0
            CPLError(CE_Warning, CPLE_AppDefined,
1261
0
                     "Warning- incomplete"
1262
0
                     " ellipsoid information.  Using wgs-84 parameters.");
1263
0
            oLL.SetWellKnownGeogCS("WGS84");
1264
0
            oPrj.SetWellKnownGeogCS("WGS84");
1265
0
        }
1266
0
        else if (EQUAL(pszEllipsoidName, "WGS84") ||
1267
0
                 EQUAL(pszEllipsoidName, "WGS 1984"))
1268
0
        {
1269
0
            oLL.SetWellKnownGeogCS("WGS84");
1270
0
            oPrj.SetWellKnownGeogCS("WGS84");
1271
0
        }
1272
0
        else
1273
0
        {
1274
0
            const double inv_flattening =
1275
0
                major_axis / (major_axis - minor_axis);
1276
0
            oLL.SetGeogCS("", "", pszEllipsoidName, major_axis, inv_flattening);
1277
0
            oPrj.SetGeogCS("", "", pszEllipsoidName, major_axis,
1278
0
                           inv_flattening);
1279
0
        }
1280
1281
0
        if (psMapProjection != nullptr)
1282
0
        {
1283
0
            const char *pszProj =
1284
0
                CPLGetXMLValue(psMapProjection, "mapProjectionDescriptor", "");
1285
0
            bool bUseProjInfo = false;
1286
1287
0
            CPLXMLNode *psUtmParams =
1288
0
                CPLGetXMLNode(psMapProjection, "utmProjectionParameters");
1289
1290
0
            CPLXMLNode *psNspParams =
1291
0
                CPLGetXMLNode(psMapProjection, "nspProjectionParameters");
1292
1293
0
            if ((psUtmParams != nullptr) && poDS->bHaveGeoTransform)
1294
0
            {
1295
                /* double origEasting, origNorthing; */
1296
0
                bool bNorth = true;
1297
1298
0
                const int utmZone =
1299
0
                    atoi(CPLGetXMLValue(psUtmParams, "utmZone", ""));
1300
0
                const char *pszHemisphere =
1301
0
                    CPLGetXMLValue(psUtmParams, "hemisphere", "");
1302
#if 0
1303
                origEasting = CPLStrtod(CPLGetXMLValue(
1304
                    psUtmParams, "mapOriginFalseEasting", "0.0" ), nullptr);
1305
                origNorthing = CPLStrtod(CPLGetXMLValue(
1306
                    psUtmParams, "mapOriginFalseNorthing", "0.0" ), nullptr);
1307
#endif
1308
0
                if (STARTS_WITH_CI(pszHemisphere, "southern"))
1309
0
                    bNorth = FALSE;
1310
1311
0
                if (STARTS_WITH_CI(pszProj, "UTM"))
1312
0
                {
1313
0
                    oPrj.SetUTM(utmZone, bNorth);
1314
0
                    bUseProjInfo = true;
1315
0
                }
1316
0
            }
1317
0
            else if ((psNspParams != nullptr) && poDS->bHaveGeoTransform)
1318
0
            {
1319
0
                const double origEasting = CPLStrtod(
1320
0
                    CPLGetXMLValue(psNspParams, "mapOriginFalseEasting", "0.0"),
1321
0
                    nullptr);
1322
0
                const double origNorthing =
1323
0
                    CPLStrtod(CPLGetXMLValue(psNspParams,
1324
0
                                             "mapOriginFalseNorthing", "0.0"),
1325
0
                              nullptr);
1326
0
                const double copLong = CPLStrtod(
1327
0
                    CPLGetXMLValue(psNspParams, "centerOfProjectionLongitude",
1328
0
                                   "0.0"),
1329
0
                    nullptr);
1330
0
                const double copLat = CPLStrtod(
1331
0
                    CPLGetXMLValue(psNspParams, "centerOfProjectionLatitude",
1332
0
                                   "0.0"),
1333
0
                    nullptr);
1334
0
                const double sP1 = CPLStrtod(
1335
0
                    CPLGetXMLValue(psNspParams, "standardParallels1", "0.0"),
1336
0
                    nullptr);
1337
0
                const double sP2 = CPLStrtod(
1338
0
                    CPLGetXMLValue(psNspParams, "standardParallels2", "0.0"),
1339
0
                    nullptr);
1340
1341
0
                if (STARTS_WITH_CI(pszProj, "ARC"))
1342
0
                {
1343
                    /* Albers Conical Equal Area */
1344
0
                    oPrj.SetACEA(sP1, sP2, copLat, copLong, origEasting,
1345
0
                                 origNorthing);
1346
0
                    bUseProjInfo = true;
1347
0
                }
1348
0
                else if (STARTS_WITH_CI(pszProj, "LCC"))
1349
0
                {
1350
                    /* Lambert Conformal Conic */
1351
0
                    oPrj.SetLCC(sP1, sP2, copLat, copLong, origEasting,
1352
0
                                origNorthing);
1353
0
                    bUseProjInfo = true;
1354
0
                }
1355
0
                else if (STARTS_WITH_CI(pszProj, "STPL"))
1356
0
                {
1357
                    /* State Plate
1358
                       ASSUMPTIONS: "zone" in product.xml matches USGS
1359
                       definition as expected by ogr spatial reference; NAD83
1360
                       zones (versus NAD27) are assumed. */
1361
1362
0
                    const int nSPZone =
1363
0
                        atoi(CPLGetXMLValue(psNspParams, "zone", "1"));
1364
1365
0
                    oPrj.SetStatePlane(nSPZone, TRUE, nullptr, 0.0);
1366
0
                    bUseProjInfo = true;
1367
0
                }
1368
0
            }
1369
1370
0
            if (bUseProjInfo)
1371
0
            {
1372
0
                poDS->m_oSRS = std::move(oPrj);
1373
0
            }
1374
0
            else
1375
0
            {
1376
0
                CPLError(CE_Warning, CPLE_AppDefined,
1377
0
                         "Unable to interpret "
1378
0
                         "projection information; check mapProjection info in "
1379
0
                         "product.xml!");
1380
0
            }
1381
0
        }
1382
1383
0
        poDS->m_oGCPSRS = std::move(oLL);
1384
0
    }
1385
1386
    /* -------------------------------------------------------------------- */
1387
    /*      Collect GCPs.                                                   */
1388
    /* -------------------------------------------------------------------- */
1389
0
    CPLXMLNode *psGeoGrid = CPLGetXMLNode(
1390
0
        psImageAttributes, "geographicInformation.geolocationGrid");
1391
1392
0
    if (psGeoGrid != nullptr)
1393
0
    {
1394
        /* count GCPs */
1395
0
        poDS->nGCPCount = 0;
1396
1397
0
        for (psNode = psGeoGrid->psChild; psNode != nullptr;
1398
0
             psNode = psNode->psNext)
1399
0
        {
1400
0
            if (EQUAL(psNode->pszValue, "imageTiePoint"))
1401
0
                poDS->nGCPCount++;
1402
0
        }
1403
1404
0
        poDS->pasGCPList = static_cast<GDAL_GCP *>(
1405
0
            CPLCalloc(sizeof(GDAL_GCP), poDS->nGCPCount));
1406
1407
0
        poDS->nGCPCount = 0;
1408
1409
0
        for (psNode = psGeoGrid->psChild; psNode != nullptr;
1410
0
             psNode = psNode->psNext)
1411
0
        {
1412
0
            GDAL_GCP *psGCP = poDS->pasGCPList + poDS->nGCPCount;
1413
1414
0
            if (!EQUAL(psNode->pszValue, "imageTiePoint"))
1415
0
                continue;
1416
1417
0
            poDS->nGCPCount++;
1418
1419
0
            char szID[32];
1420
0
            snprintf(szID, sizeof(szID), "%d", poDS->nGCPCount);
1421
0
            psGCP->pszId = CPLStrdup(szID);
1422
0
            psGCP->pszInfo = CPLStrdup("");
1423
0
            psGCP->dfGCPPixel =
1424
0
                CPLAtof(CPLGetXMLValue(psNode, "imageCoordinate.pixel", "0")) +
1425
0
                0.5;
1426
0
            psGCP->dfGCPLine =
1427
0
                CPLAtof(CPLGetXMLValue(psNode, "imageCoordinate.line", "0")) +
1428
0
                0.5;
1429
0
            psGCP->dfGCPX = CPLAtof(
1430
0
                CPLGetXMLValue(psNode, "geodeticCoordinate.longitude", ""));
1431
0
            psGCP->dfGCPY = CPLAtof(
1432
0
                CPLGetXMLValue(psNode, "geodeticCoordinate.latitude", ""));
1433
0
            psGCP->dfGCPZ = CPLAtof(
1434
0
                CPLGetXMLValue(psNode, "geodeticCoordinate.height", ""));
1435
0
        }
1436
0
    }
1437
1438
    /* -------------------------------------------------------------------- */
1439
    /*      Collect RPC.                                                   */
1440
    /* -------------------------------------------------------------------- */
1441
0
    CPLXMLNode *psRationalFunctions = CPLGetXMLNode(
1442
0
        psImageAttributes, "geographicInformation.rationalFunctions");
1443
0
    if (psRationalFunctions != nullptr)
1444
0
    {
1445
0
        char **papszRPC = nullptr;
1446
0
        static const char *const apszXMLToGDALMapping[] = {
1447
0
            "biasError",
1448
0
            "ERR_BIAS",
1449
0
            "randomError",
1450
0
            "ERR_RAND",
1451
            //"lineFitQuality", "????",
1452
            //"pixelFitQuality", "????",
1453
0
            "lineOffset",
1454
0
            "LINE_OFF",
1455
0
            "pixelOffset",
1456
0
            "SAMP_OFF",
1457
0
            "latitudeOffset",
1458
0
            "LAT_OFF",
1459
0
            "longitudeOffset",
1460
0
            "LONG_OFF",
1461
0
            "heightOffset",
1462
0
            "HEIGHT_OFF",
1463
0
            "lineScale",
1464
0
            "LINE_SCALE",
1465
0
            "pixelScale",
1466
0
            "SAMP_SCALE",
1467
0
            "latitudeScale",
1468
0
            "LAT_SCALE",
1469
0
            "longitudeScale",
1470
0
            "LONG_SCALE",
1471
0
            "heightScale",
1472
0
            "HEIGHT_SCALE",
1473
0
            "lineNumeratorCoefficients",
1474
0
            "LINE_NUM_COEFF",
1475
0
            "lineDenominatorCoefficients",
1476
0
            "LINE_DEN_COEFF",
1477
0
            "pixelNumeratorCoefficients",
1478
0
            "SAMP_NUM_COEFF",
1479
0
            "pixelDenominatorCoefficients",
1480
0
            "SAMP_DEN_COEFF",
1481
0
        };
1482
0
        for (size_t i = 0; i < CPL_ARRAYSIZE(apszXMLToGDALMapping); i += 2)
1483
0
        {
1484
0
            const char *pszValue = CPLGetXMLValue(
1485
0
                psRationalFunctions, apszXMLToGDALMapping[i], nullptr);
1486
0
            if (pszValue)
1487
0
                papszRPC = CSLSetNameValue(
1488
0
                    papszRPC, apszXMLToGDALMapping[i + 1], pszValue);
1489
0
        }
1490
0
        poDS->GDALDataset::SetMetadata(papszRPC, "RPC");
1491
0
        CSLDestroy(papszRPC);
1492
0
    }
1493
1494
    /* -------------------------------------------------------------------- */
1495
    /*      Initialize any PAM information.                                 */
1496
    /* -------------------------------------------------------------------- */
1497
0
    CPLString osDescription;
1498
1499
0
    switch (eCalib)
1500
0
    {
1501
0
        case Sigma0:
1502
0
            osDescription.Printf("RADARSAT_2_CALIB:SIGMA0:%s",
1503
0
                                 osMDFilename.c_str());
1504
0
            break;
1505
0
        case Beta0:
1506
0
            osDescription.Printf("RADARSAT_2_CALIB:BETA0:%s",
1507
0
                                 osMDFilename.c_str());
1508
0
            break;
1509
0
        case Gamma:
1510
0
            osDescription.Printf("RADARSAT_2_CALIB:GAMMA0:%s",
1511
0
                                 osMDFilename.c_str());
1512
0
            break;
1513
0
        case Uncalib:
1514
0
            osDescription.Printf("RADARSAT_2_CALIB:UNCALIB:%s",
1515
0
                                 osMDFilename.c_str());
1516
0
            break;
1517
0
        default:
1518
0
            osDescription = osMDFilename;
1519
0
    }
1520
1521
0
    if (eCalib != None)
1522
0
        poDS->papszExtraFiles =
1523
0
            CSLAddString(poDS->papszExtraFiles, osMDFilename);
1524
1525
    /* -------------------------------------------------------------------- */
1526
    /*      Initialize any PAM information.                                 */
1527
    /* -------------------------------------------------------------------- */
1528
0
    poDS->SetDescription(osDescription);
1529
1530
0
    poDS->SetPhysicalFilename(osMDFilename);
1531
0
    poDS->SetSubdatasetName(osDescription);
1532
1533
0
    poDS->TryLoadXML();
1534
1535
    /* -------------------------------------------------------------------- */
1536
    /*      Check for overviews.                                            */
1537
    /* -------------------------------------------------------------------- */
1538
0
    poDS->oOvManager.Initialize(poDS.get(), ":::VIRTUAL:::");
1539
1540
0
    return poDS.release();
1541
0
}
1542
1543
/************************************************************************/
1544
/*                            GetGCPCount()                             */
1545
/************************************************************************/
1546
1547
int RS2Dataset::GetGCPCount()
1548
1549
0
{
1550
0
    return nGCPCount;
1551
0
}
1552
1553
/************************************************************************/
1554
/*                          GetGCPSpatialRef()                          */
1555
/************************************************************************/
1556
1557
const OGRSpatialReference *RS2Dataset::GetGCPSpatialRef() const
1558
1559
0
{
1560
0
    return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
1561
0
}
1562
1563
/************************************************************************/
1564
/*                              GetGCPs()                               */
1565
/************************************************************************/
1566
1567
const GDAL_GCP *RS2Dataset::GetGCPs()
1568
1569
0
{
1570
0
    return pasGCPList;
1571
0
}
1572
1573
/************************************************************************/
1574
/*                           GetSpatialRef()                            */
1575
/************************************************************************/
1576
1577
const OGRSpatialReference *RS2Dataset::GetSpatialRef() const
1578
1579
0
{
1580
0
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1581
0
}
1582
1583
/************************************************************************/
1584
/*                          GetGeoTransform()                           */
1585
/************************************************************************/
1586
1587
CPLErr RS2Dataset::GetGeoTransform(GDALGeoTransform &gt) const
1588
1589
0
{
1590
0
    gt = m_gt;
1591
1592
0
    if (bHaveGeoTransform)
1593
0
        return CE_None;
1594
1595
0
    return CE_Failure;
1596
0
}
1597
1598
/************************************************************************/
1599
/*                       GetMetadataDomainList()                        */
1600
/************************************************************************/
1601
1602
char **RS2Dataset::GetMetadataDomainList()
1603
0
{
1604
0
    return BuildMetadataDomainList(GDALDataset::GetMetadataDomainList(), TRUE,
1605
0
                                   "SUBDATASETS", nullptr);
1606
0
}
1607
1608
/************************************************************************/
1609
/*                            GetMetadata()                             */
1610
/************************************************************************/
1611
1612
CSLConstList RS2Dataset::GetMetadata(const char *pszDomain)
1613
1614
0
{
1615
0
    if (pszDomain != nullptr && STARTS_WITH_CI(pszDomain, "SUBDATASETS") &&
1616
0
        papszSubDatasets != nullptr)
1617
0
        return papszSubDatasets;
1618
1619
0
    return GDALDataset::GetMetadata(pszDomain);
1620
0
}
1621
1622
/************************************************************************/
1623
/*                          GDALRegister_RS2()                          */
1624
/************************************************************************/
1625
1626
void GDALRegister_RS2()
1627
1628
22
{
1629
22
    if (GDALGetDriverByName("RS2") != nullptr)
1630
0
        return;
1631
1632
22
    GDALDriver *poDriver = new GDALDriver();
1633
1634
22
    poDriver->SetDescription("RS2");
1635
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1636
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "RadarSat 2 XML Product");
1637
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/rs2.html");
1638
22
    poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1639
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1640
1641
22
    poDriver->pfnOpen = RS2Dataset::Open;
1642
22
    poDriver->pfnIdentify = RS2Dataset::Identify;
1643
1644
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1645
22
}