Coverage Report

Created: 2025-11-15 08:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/pds/pdsdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  PDS Driver; Planetary Data System Format
4
 * Purpose:  Implementation of PDSDataset
5
 * Author:   Trent Hare (thare at usgs.gov),
6
 *           Robert Soricone (rsoricone at usgs.gov)
7
 *
8
 * NOTE: Original code authored by Trent and Robert and placed in the public
9
 * domain as per US government policy.  I have (within my rights) appropriated
10
 * it and placed it under the following license.  This is not intended to
11
 * diminish Trent and Roberts contribution.
12
 ******************************************************************************
13
 * Copyright (c) 2007, Frank Warmerdam <warmerdam at pobox.com>
14
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
15
 *
16
 * SPDX-License-Identifier: MIT
17
 ****************************************************************************/
18
19
// Set up PDS NULL values
20
constexpr int PDS_NULL1 = 0;
21
constexpr int PDS_NULL2 = -32768;
22
// #define NULL3 -0.3402822655089E+39
23
// Same as ESRI_GRID_FLOAT_NO_DATA
24
// #define NULL3 -340282346638528859811704183484516925440.0
25
constexpr double PDS_NULL3 = -3.4028226550889044521e+38;
26
27
#include "cpl_string.h"
28
#include "gdal_frmts.h"
29
#include "gdal_proxy.h"
30
#include "nasakeywordhandler.h"
31
#include "ogr_spatialref.h"
32
#include "rawdataset.h"
33
#include "cpl_safemaths.hpp"
34
#include "vicardataset.h"
35
#include "pdsdrivercore.h"
36
37
#include <array>
38
39
enum PDSLayout
40
{
41
    PDS_BSQ,
42
    PDS_BIP,
43
    PDS_BIL
44
};
45
46
/************************************************************************/
47
/* ==================================================================== */
48
/*                             PDSDataset                               */
49
/* ==================================================================== */
50
/************************************************************************/
51
52
class PDSDataset final : public RawDataset
53
{
54
    VSILFILE *fpImage{};  // image data file.
55
    GDALDataset *poCompressedDS{};
56
57
    NASAKeywordHandler oKeywords{};
58
59
    bool bGotTransform{};
60
    GDALGeoTransform m_gt{};
61
62
    OGRSpatialReference m_oSRS{};
63
64
    CPLString osTempResult{};
65
66
    CPLString osExternalCube{};
67
    CPLString m_osImageFilename{};
68
69
    CPLStringList m_aosPDSMD{};
70
71
    void ParseSRS();
72
    int ParseCompressedImage();
73
    int ParseImage(const CPLString &osPrefix,
74
                   const CPLString &osFilenamePrefix);
75
    static CPLString CleanString(const CPLString &osInput);
76
77
    const char *GetKeyword(const std::string &osPath,
78
                           const char *pszDefault = "");
79
    const char *GetKeywordSub(const std::string &osPath, int iSubscript,
80
                              const char *pszDefault = "");
81
    const char *GetKeywordUnit(const char *pszPath, int iSubscript,
82
                               const char *pszDefault = "");
83
84
    CPL_DISALLOW_COPY_ASSIGN(PDSDataset)
85
86
  protected:
87
    int CloseDependentDatasets() override;
88
89
    CPLErr Close() override;
90
91
  public:
92
    PDSDataset();
93
    ~PDSDataset() override;
94
95
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
96
    const OGRSpatialReference *GetSpatialRef() const override;
97
98
    char **GetFileList(void) override;
99
100
    CPLErr IBuildOverviews(const char *, int, const int *, int, const int *,
101
                           GDALProgressFunc, void *,
102
                           CSLConstList papszOptions) override;
103
104
    CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
105
                     GDALDataType, int, BANDMAP_TYPE, GSpacing nPixelSpace,
106
                     GSpacing nLineSpace, GSpacing nBandSpace,
107
                     GDALRasterIOExtraArg *psExtraArg) override;
108
109
    bool GetRawBinaryLayout(GDALDataset::RawBinaryLayout &) override;
110
111
    char **GetMetadataDomainList() override;
112
    char **GetMetadata(const char *pszDomain = "") override;
113
114
    static GDALDataset *Open(GDALOpenInfo *);
115
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
116
                               int nBands, GDALDataType eType,
117
                               char **papszParamList);
118
};
119
120
/************************************************************************/
121
/*                            PDSDataset()                            */
122
/************************************************************************/
123
124
PDSDataset::PDSDataset()
125
3.62k
{
126
3.62k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
127
3.62k
}
128
129
/************************************************************************/
130
/*                            ~PDSDataset()                            */
131
/************************************************************************/
132
133
PDSDataset::~PDSDataset()
134
135
3.62k
{
136
3.62k
    PDSDataset::Close();
137
3.62k
}
138
139
/************************************************************************/
140
/*                              Close()                                 */
141
/************************************************************************/
142
143
CPLErr PDSDataset::Close()
144
3.63k
{
145
3.63k
    CPLErr eErr = CE_None;
146
3.63k
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
147
3.62k
    {
148
3.62k
        if (PDSDataset::FlushCache(true) != CE_None)
149
0
            eErr = CE_Failure;
150
3.62k
        if (fpImage)
151
13
        {
152
13
            if (VSIFCloseL(fpImage) != 0)
153
0
                eErr = CE_Failure;
154
13
        }
155
156
3.62k
        PDSDataset::CloseDependentDatasets();
157
3.62k
        if (GDALPamDataset::Close() != CE_None)
158
0
            eErr = CE_Failure;
159
3.62k
    }
160
3.63k
    return eErr;
161
3.63k
}
162
163
/************************************************************************/
164
/*                        CloseDependentDatasets()                      */
165
/************************************************************************/
166
167
int PDSDataset::CloseDependentDatasets()
168
3.62k
{
169
3.62k
    int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
170
171
3.62k
    if (poCompressedDS)
172
0
    {
173
0
        bHasDroppedRef = FALSE;
174
0
        delete poCompressedDS;
175
0
        poCompressedDS = nullptr;
176
0
    }
177
178
3.63k
    for (int iBand = 0; iBand < nBands; iBand++)
179
14
    {
180
14
        delete papoBands[iBand];
181
14
    }
182
3.62k
    nBands = 0;
183
184
3.62k
    return bHasDroppedRef;
185
3.62k
}
186
187
/************************************************************************/
188
/*                            GetFileList()                             */
189
/************************************************************************/
190
191
char **PDSDataset::GetFileList()
192
193
26
{
194
26
    char **papszFileList = RawDataset::GetFileList();
195
196
26
    if (poCompressedDS != nullptr)
197
0
    {
198
0
        char **papszCFileList = poCompressedDS->GetFileList();
199
200
0
        papszFileList = CSLInsertStrings(papszFileList, -1, papszCFileList);
201
0
        CSLDestroy(papszCFileList);
202
0
    }
203
204
26
    if (!osExternalCube.empty())
205
0
    {
206
0
        papszFileList = CSLAddString(papszFileList, osExternalCube);
207
0
    }
208
209
26
    return papszFileList;
210
26
}
211
212
/************************************************************************/
213
/*                          IBuildOverviews()                           */
214
/************************************************************************/
215
216
CPLErr PDSDataset::IBuildOverviews(const char *pszResampling, int nOverviews,
217
                                   const int *panOverviewList, int nListBands,
218
                                   const int *panBandList,
219
                                   GDALProgressFunc pfnProgress,
220
                                   void *pProgressData,
221
                                   CSLConstList papszOptions)
222
0
{
223
0
    if (poCompressedDS != nullptr)
224
0
        return poCompressedDS->BuildOverviews(
225
0
            pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
226
0
            pfnProgress, pProgressData, papszOptions);
227
228
0
    return RawDataset::IBuildOverviews(
229
0
        pszResampling, nOverviews, panOverviewList, nListBands, panBandList,
230
0
        pfnProgress, pProgressData, papszOptions);
231
0
}
232
233
/************************************************************************/
234
/*                             IRasterIO()                              */
235
/************************************************************************/
236
237
CPLErr PDSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
238
                             int nXSize, int nYSize, void *pData, int nBufXSize,
239
                             int nBufYSize, GDALDataType eBufType,
240
                             int nBandCount, BANDMAP_TYPE panBandMap,
241
                             GSpacing nPixelSpace, GSpacing nLineSpace,
242
                             GSpacing nBandSpace,
243
                             GDALRasterIOExtraArg *psExtraArg)
244
245
0
{
246
0
    if (poCompressedDS != nullptr)
247
0
        return poCompressedDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
248
0
                                        pData, nBufXSize, nBufYSize, eBufType,
249
0
                                        nBandCount, panBandMap, nPixelSpace,
250
0
                                        nLineSpace, nBandSpace, psExtraArg);
251
252
0
    return RawDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
253
0
                                 nBufXSize, nBufYSize, eBufType, nBandCount,
254
0
                                 panBandMap, nPixelSpace, nLineSpace,
255
0
                                 nBandSpace, psExtraArg);
256
0
}
257
258
/************************************************************************/
259
/*                         GetSpatialRef()                              */
260
/************************************************************************/
261
262
const OGRSpatialReference *PDSDataset::GetSpatialRef() const
263
13
{
264
13
    if (!m_oSRS.IsEmpty())
265
8
        return &m_oSRS;
266
5
    return GDALPamDataset::GetSpatialRef();
267
13
}
268
269
/************************************************************************/
270
/*                          GetGeoTransform()                           */
271
/************************************************************************/
272
273
CPLErr PDSDataset::GetGeoTransform(GDALGeoTransform &gt) const
274
275
13
{
276
13
    if (bGotTransform)
277
8
    {
278
8
        gt = m_gt;
279
8
        return CE_None;
280
8
    }
281
282
5
    return GDALPamDataset::GetGeoTransform(gt);
283
13
}
284
285
/************************************************************************/
286
/*                              ParseSRS()                              */
287
/************************************************************************/
288
289
void PDSDataset::ParseSRS()
290
291
13
{
292
13
    const char *pszFilename = GetDescription();
293
294
13
    CPLString osPrefix;
295
13
    if (strlen(GetKeyword("IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE")) == 0 &&
296
5
        strlen(GetKeyword(
297
5
            "UNCOMPRESSED_FILE.IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE")) != 0)
298
0
        osPrefix = "UNCOMPRESSED_FILE.";
299
300
    /* ==================================================================== */
301
    /*      Get the geotransform.                                           */
302
    /* ==================================================================== */
303
    /***********   Grab Cellsize ************/
304
    // example:
305
    // MAP_SCALE   = 14.818 <KM/PIXEL>
306
    // added search for unit (only checks for CM, KM - defaults to Meters)
307
    // Georef parameters
308
13
    double dfXDim = 1.0;
309
13
    double dfYDim = 1.0;
310
311
13
    const char *value = GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_SCALE");
312
13
    if (strlen(value) > 0)
313
8
    {
314
8
        dfXDim = CPLAtof(value);
315
8
        dfYDim = CPLAtof(value) * -1;
316
317
8
        CPLString osKey(osPrefix + "IMAGE_MAP_PROJECTION.MAP_SCALE");
318
8
        CPLString unit = GetKeywordUnit(osKey, 2);  // KM
319
        // value = GetKeywordUnit("IMAGE_MAP_PROJECTION.MAP_SCALE",3); //PIXEL
320
8
        if ((EQUAL(unit, "M")) || (EQUAL(unit, "METER")) ||
321
8
            (EQUAL(unit, "METERS")))
322
0
        {
323
            // do nothing
324
0
        }
325
8
        else if (EQUAL(unit, "CM"))
326
0
        {
327
            // convert from cm to m
328
0
            dfXDim = dfXDim / 100.0;
329
0
            dfYDim = dfYDim / 100.0;
330
0
        }
331
8
        else
332
8
        {
333
            // defaults to convert km to m
334
8
            dfXDim = dfXDim * 1000.0;
335
8
            dfYDim = dfYDim * 1000.0;
336
8
        }
337
8
    }
338
339
    /* -------------------------------------------------------------------- */
340
    /*      Calculate upper left corner of pixel in meters from the         */
341
    /*      upper  left center pixel sample/line offsets.  It doesn't       */
342
    /*      mean the defaults will work for every PDS image, as these       */
343
    /*      values are used inconsistently.  Thus we have included          */
344
    /*      conversion options to allow the user to override the            */
345
    /*      documented PDS3 default. Jan. 2011, for known mapping issues    */
346
    /*      see GDAL PDS page or mapping within ISIS3 source (USGS)         */
347
    /*      $ISIS3DATA/base/translations/pdsProjectionLineSampToXY.def      */
348
    /* -------------------------------------------------------------------- */
349
350
    // defaults should be correct for what is documented in the PDS3 standard
351
352
    // https://trac.osgeo.org/gdal/ticket/5941 has the history of the default
353
    /* value of PDS_SampleProjOffset_Shift and PDS_LineProjOffset_Shift */
354
    // coverity[tainted_data]
355
13
    double dfSampleOffset_Shift =
356
13
        CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Shift", "0.5"));
357
358
    // coverity[tainted_data]
359
13
    const double dfLineOffset_Shift =
360
13
        CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Shift", "0.5"));
361
362
    // coverity[tainted_data]
363
13
    const double dfSampleOffset_Mult =
364
13
        CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Mult", "-1.0"));
365
366
    // coverity[tainted_data]
367
13
    const double dfLineOffset_Mult =
368
13
        CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Mult", "1.0"));
369
370
    /***********   Grab LINE_PROJECTION_OFFSET ************/
371
13
    double dfULYMap = 0.5;
372
373
13
    value =
374
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.LINE_PROJECTION_OFFSET");
375
13
    if (strlen(value) > 0)
376
8
    {
377
8
        const double yulcenter = CPLAtof(value);
378
8
        dfULYMap =
379
8
            ((yulcenter + dfLineOffset_Shift) * -dfYDim * dfLineOffset_Mult);
380
        // notice dfYDim is negative here which is why it is again negated here
381
8
    }
382
    /***********   Grab SAMPLE_PROJECTION_OFFSET ************/
383
13
    double dfULXMap = 0.5;
384
385
13
    value =
386
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.SAMPLE_PROJECTION_OFFSET");
387
13
    if (strlen(value) > 0)
388
7
    {
389
7
        const double xulcenter = CPLAtof(value);
390
7
        dfULXMap =
391
7
            ((xulcenter + dfSampleOffset_Shift) * dfXDim * dfSampleOffset_Mult);
392
7
    }
393
394
    /* ==================================================================== */
395
    /*      Get the coordinate system.                                      */
396
    /* ==================================================================== */
397
398
    /***********  Grab TARGET_NAME  ************/
399
    /**** This is the planets name i.e. MARS ***/
400
13
    const CPLString target_name = CleanString(GetKeyword("TARGET_NAME"));
401
402
    /**********   Grab MAP_PROJECTION_TYPE *****/
403
13
    const CPLString map_proj_name = CleanString(
404
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE"));
405
406
    /******  Grab semi_major & convert to KM ******/
407
13
    const double semi_major =
408
13
        CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.A_AXIS_RADIUS")) *
409
13
        1000.0;
410
411
    /******  Grab semi-minor & convert to KM ******/
412
13
    const double semi_minor =
413
13
        CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.C_AXIS_RADIUS")) *
414
13
        1000.0;
415
416
    /***********   Grab CENTER_LAT ************/
417
13
    const double center_lat =
418
13
        CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LATITUDE"));
419
420
    /***********   Grab CENTER_LON ************/
421
13
    const double center_lon =
422
13
        CPLAtof(GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.CENTER_LONGITUDE"));
423
424
    /**********   Grab 1st std parallel *******/
425
13
    const double first_std_parallel = CPLAtof(
426
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.FIRST_STANDARD_PARALLEL"));
427
428
    /**********   Grab 2nd std parallel *******/
429
13
    const double second_std_parallel = CPLAtof(
430
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.SECOND_STANDARD_PARALLEL"));
431
432
    /*** grab  PROJECTION_LATITUDE_TYPE = "PLANETOCENTRIC" ****/
433
    // Need to further study how ocentric/ographic will effect the gdal library.
434
    // So far we will use this fact to define a sphere or ellipse for some
435
    // projections Frank - may need to talk this over
436
13
    char bIsGeographic = TRUE;
437
13
    value =
438
13
        GetKeyword(osPrefix + "IMAGE_MAP_PROJECTION.COORDINATE_SYSTEM_NAME");
439
13
    if (EQUAL(value, "PLANETOCENTRIC"))
440
3
        bIsGeographic = FALSE;
441
442
13
    const double dfLongitudeMulFactor =
443
13
        EQUAL(GetKeyword("IMAGE_MAP_PROJECTION.POSITIVE_LONGITUDE_DIRECTION",
444
13
                         "EAST"),
445
13
              "EAST")
446
13
            ? 1
447
13
            : -1;
448
449
    /**   Set oSRS projection and parameters --- all PDS supported types added
450
    if apparently supported in oSRS "AITOFF",  ** Not supported in GDAL??
451
          "ALBERS",
452
          "BONNE",
453
          "BRIESEMEISTER",   ** Not supported in GDAL??
454
          "CYLINDRICAL EQUAL AREA",
455
          "EQUIDISTANT",
456
          "EQUIRECTANGULAR",
457
          "GNOMONIC",
458
          "HAMMER",    ** Not supported in GDAL??
459
          "HENDU",     ** Not supported in GDAL??
460
          "LAMBERT AZIMUTHAL EQUAL AREA",
461
          "LAMBERT CONFORMAL",
462
          "MERCATOR",
463
          "MOLLWEIDE",
464
          "OBLIQUE CYLINDRICAL",
465
          "ORTHOGRAPHIC",
466
          "SIMPLE CYLINDRICAL",
467
          "SINUSOIDAL",
468
          "STEREOGRAPHIC",
469
          "TRANSVERSE MERCATOR",
470
          "VAN DER GRINTEN",     ** Not supported in GDAL??
471
          "WERNER"     ** Not supported in GDAL??
472
    **/
473
13
    CPLDebug("PDS", "using projection %s\n\n", map_proj_name.c_str());
474
475
13
    bool bProjectionSet = true;
476
13
    OGRSpatialReference oSRS;
477
13
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
478
479
13
    if ((EQUAL(map_proj_name, "EQUIRECTANGULAR")) ||
480
13
        (EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
481
11
        (EQUAL(map_proj_name, "EQUIDISTANT")))
482
2
    {
483
2
        oSRS.SetEquirectangular2(0.0, center_lon, center_lat, 0, 0);
484
2
    }
485
11
    else if (EQUAL(map_proj_name, "ORTHOGRAPHIC"))
486
0
    {
487
0
        oSRS.SetOrthographic(center_lat, center_lon, 0, 0);
488
0
    }
489
11
    else if (EQUAL(map_proj_name, "SINUSOIDAL"))
490
1
    {
491
1
        oSRS.SetSinusoidal(center_lon, 0, 0);
492
1
    }
493
10
    else if (EQUAL(map_proj_name, "MERCATOR"))
494
3
    {
495
3
        if (center_lat == 0.0 && first_std_parallel != 0.0)
496
3
        {
497
3
            oSRS.SetMercator2SP(first_std_parallel, center_lat, center_lon, 0,
498
3
                                0);
499
3
        }
500
0
        else
501
0
        {
502
0
            oSRS.SetMercator(center_lat, center_lon, 1, 0, 0);
503
0
        }
504
3
    }
505
7
    else if (EQUAL(map_proj_name, "STEREOGRAPHIC"))
506
0
    {
507
0
        if ((fabs(center_lat) - 90) < 0.0000001)
508
0
        {
509
0
            oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
510
0
        }
511
0
        else
512
0
            oSRS.SetStereographic(center_lat, center_lon, 1, 0, 0);
513
0
    }
514
7
    else if (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC"))
515
0
    {
516
0
        oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
517
0
    }
518
7
    else if (EQUAL(map_proj_name, "TRANSVERSE_MERCATOR"))
519
0
    {
520
0
        oSRS.SetTM(center_lat, center_lon, 1, 0, 0);
521
0
    }
522
7
    else if (EQUAL(map_proj_name, "LAMBERT_CONFORMAL_CONIC"))
523
0
    {
524
0
        oSRS.SetLCC(first_std_parallel, second_std_parallel, center_lat,
525
0
                    center_lon, 0, 0);
526
0
    }
527
7
    else if (EQUAL(map_proj_name, "LAMBERT_AZIMUTHAL_EQUAL_AREA"))
528
0
    {
529
0
        oSRS.SetLAEA(center_lat, center_lon, 0, 0);
530
0
    }
531
7
    else if (EQUAL(map_proj_name, "CYLINDRICAL_EQUAL_AREA"))
532
0
    {
533
0
        oSRS.SetCEA(first_std_parallel, center_lon, 0, 0);
534
0
    }
535
7
    else if (EQUAL(map_proj_name, "MOLLWEIDE"))
536
0
    {
537
0
        oSRS.SetMollweide(center_lon, 0, 0);
538
0
    }
539
7
    else if (EQUAL(map_proj_name, "ALBERS"))
540
0
    {
541
0
        oSRS.SetACEA(first_std_parallel, second_std_parallel, center_lat,
542
0
                     center_lon, 0, 0);
543
0
    }
544
7
    else if (EQUAL(map_proj_name, "BONNE"))
545
0
    {
546
0
        oSRS.SetBonne(first_std_parallel, center_lon, 0, 0);
547
0
    }
548
7
    else if (EQUAL(map_proj_name, "GNOMONIC"))
549
0
    {
550
0
        oSRS.SetGnomonic(center_lat, center_lon, 0, 0);
551
0
    }
552
7
    else if (EQUAL(map_proj_name, "OBLIQUE_CYLINDRICAL"))
553
2
    {
554
2
        const double poleLatitude = CPLAtof(GetKeyword(
555
2
            osPrefix + "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_LATITUDE"));
556
2
        const double poleLongitude =
557
2
            CPLAtof(GetKeyword(
558
2
                osPrefix +
559
2
                "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_LONGITUDE")) *
560
2
            dfLongitudeMulFactor;
561
2
        const double poleRotation = CPLAtof(GetKeyword(
562
2
            osPrefix + "IMAGE_MAP_PROJECTION.OBLIQUE_PROJ_POLE_ROTATION"));
563
2
        CPLString oProj4String;
564
        // ISIS3 rotated pole doesn't use the same conventions than PROJ ob_tran
565
        // Compare the sign difference in
566
        // https://github.com/USGS-Astrogeology/ISIS3/blob/3.8.0/isis/src/base/objs/ObliqueCylindrical/ObliqueCylindrical.cpp#L244
567
        // and
568
        // https://github.com/OSGeo/PROJ/blob/6.2/src/projections/ob_tran.cpp#L34
569
        // They can be compensated by modifying the poleLatitude to
570
        // 180-poleLatitude There's also a sign difference for the poleRotation
571
        // parameter The existence of those different conventions is
572
        // acknowledged in
573
        // https://pds-imaging.jpl.nasa.gov/documentation/Cassini_BIDRSIS.PDF in
574
        // the middle of page 10
575
2
        oProj4String.Printf("+proj=ob_tran +o_proj=eqc +o_lon_p=%.17g "
576
2
                            "+o_lat_p=%.17g +lon_0=%.17g",
577
2
                            -poleRotation, 180 - poleLatitude, poleLongitude);
578
2
        oSRS.SetFromUserInput(oProj4String);
579
2
    }
580
5
    else
581
5
    {
582
5
        CPLDebug("PDS", "Dataset projection %s is not supported. Continuing...",
583
5
                 map_proj_name.c_str());
584
5
        bProjectionSet = false;
585
5
    }
586
587
13
    if (bProjectionSet)
588
8
    {
589
        // Create projection name, i.e. MERCATOR MARS and set as ProjCS keyword
590
8
        CPLString proj_target_name = map_proj_name + " " + target_name;
591
8
        oSRS.SetProjCS(proj_target_name);  // set ProjCS keyword
592
593
        // The geographic/geocentric name will be the same basic name as the
594
        // body name 'GCS' = Geographic/Geocentric Coordinate System
595
8
        const CPLString geog_name = "GCS_" + target_name;
596
597
        // The datum and sphere names will be the same basic name aas the planet
598
8
        const CPLString datum_name = "D_" + target_name;
599
600
8
        CPLString sphere_name = std::move(target_name);
601
602
        // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
603
8
        double iflattening;
604
8
        if ((semi_major - semi_minor) < 0.0000001)
605
6
            iflattening = 0;
606
2
        else
607
2
            iflattening = semi_major / (semi_major - semi_minor);
608
609
        // Set the body size but take into consideration which proj is being
610
        // used to help w/ compatibility Notice that most PDS projections are
611
        // spherical based on the fact that ISIS/PICS are spherical Set the body
612
        // size but take into consideration which proj is being used to help w/
613
        // proj4 compatibility The use of a Sphere, polar radius or ellipse here
614
        // is based on how ISIS does it internally
615
8
        if (((EQUAL(map_proj_name, "STEREOGRAPHIC") &&
616
0
              (fabs(center_lat) == 90))) ||
617
8
            (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC")))
618
0
        {
619
0
            if (bIsGeographic)
620
0
            {
621
                // Geograpraphic, so set an ellipse
622
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
623
0
                               iflattening, "Reference_Meridian", 0.0);
624
0
            }
625
0
            else
626
0
            {
627
                // Geocentric, so force a sphere using the semi-minor axis. I
628
                // hope...
629
0
                sphere_name += "_polarRadius";
630
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_minor,
631
0
                               0.0, "Reference_Meridian", 0.0);
632
0
            }
633
0
        }
634
8
        else if ((EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
635
6
                 (EQUAL(map_proj_name, "EQUIDISTANT")) ||
636
6
                 (EQUAL(map_proj_name, "ORTHOGRAPHIC")) ||
637
6
                 (EQUAL(map_proj_name, "STEREOGRAPHIC")) ||
638
6
                 (EQUAL(map_proj_name, "SINUSOIDAL")))
639
3
        {
640
            // isis uses the spherical equation for these projections so force a
641
            // sphere
642
3
            oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
643
3
                           "Reference_Meridian", 0.0);
644
3
        }
645
5
        else if (EQUAL(map_proj_name, "EQUIRECTANGULAR"))
646
0
        {
647
            // isis uses local radius as a sphere, which is pre-calculated in
648
            // the PDS label as the semi-major
649
0
            sphere_name += "_localRadius";
650
0
            oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
651
0
                           "Reference_Meridian", 0.0);
652
0
        }
653
5
        else
654
5
        {
655
            // All other projections: Mercator, Transverse Mercator, Lambert
656
            // Conformal, etc. Geographic, so set an ellipse
657
5
            if (bIsGeographic)
658
2
            {
659
2
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
660
2
                               iflattening, "Reference_Meridian", 0.0);
661
2
            }
662
3
            else
663
3
            {
664
                // Geocentric, so force a sphere. I hope...
665
3
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
666
3
                               0.0, "Reference_Meridian", 0.0);
667
3
            }
668
5
        }
669
670
        // translate back into a projection string.
671
8
        m_oSRS = std::move(oSRS);
672
8
    }
673
674
    /* ==================================================================== */
675
    /*      Check for a .prj and world file to override the georeferencing. */
676
    /* ==================================================================== */
677
13
    {
678
13
        const CPLString osPath = CPLGetPathSafe(pszFilename);
679
13
        const CPLString osName = CPLGetBasenameSafe(pszFilename);
680
13
        const std::string osPrjFile =
681
13
            CPLFormCIFilenameSafe(osPath, osName, "prj");
682
683
13
        VSILFILE *fp = VSIFOpenL(osPrjFile.c_str(), "r");
684
13
        if (fp != nullptr)
685
0
        {
686
0
            VSIFCloseL(fp);
687
688
0
            char **papszLines = CSLLoad(osPrjFile.c_str());
689
690
0
            m_oSRS.importFromESRI(papszLines);
691
0
            CSLDestroy(papszLines);
692
0
        }
693
13
    }
694
695
13
    if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
696
8
    {
697
8
        bGotTransform = TRUE;
698
8
        m_gt[0] = dfULXMap;
699
8
        m_gt[1] = dfXDim;
700
8
        m_gt[2] = 0.0;
701
8
        m_gt[3] = dfULYMap;
702
8
        m_gt[4] = 0.0;
703
8
        m_gt[5] = dfYDim;
704
705
8
        const double rotation = CPLAtof(GetKeyword(
706
8
            osPrefix + "IMAGE_MAP_PROJECTION.MAP_PROJECTION_ROTATION"));
707
8
        if (rotation != 0)
708
2
        {
709
2
            const double sin_rot =
710
2
                rotation == 90 ? 1.0 : sin(rotation / 180 * M_PI);
711
2
            const double cos_rot =
712
2
                rotation == 90 ? 0.0 : cos(rotation / 180 * M_PI);
713
2
            const double gt_1 = cos_rot * m_gt[1] - sin_rot * m_gt[4];
714
2
            const double gt_2 = cos_rot * m_gt[2] - sin_rot * m_gt[5];
715
2
            const double gt_0 = cos_rot * m_gt[0] - sin_rot * m_gt[3];
716
2
            const double gt_4 = sin_rot * m_gt[1] + cos_rot * m_gt[4];
717
2
            const double gt_5 = sin_rot * m_gt[2] + cos_rot * m_gt[5];
718
2
            const double gt_3 = sin_rot * m_gt[0] + cos_rot * m_gt[3];
719
2
            m_gt[1] = gt_1;
720
2
            m_gt[2] = gt_2;
721
2
            m_gt[0] = gt_0;
722
2
            m_gt[4] = gt_4;
723
2
            m_gt[5] = gt_5;
724
2
            m_gt[3] = gt_3;
725
2
        }
726
8
    }
727
728
13
    if (!bGotTransform)
729
5
        bGotTransform = GDALReadWorldFile(pszFilename, "psw", m_gt.data());
730
731
13
    if (!bGotTransform)
732
5
        bGotTransform = GDALReadWorldFile(pszFilename, "wld", m_gt.data());
733
13
}
734
735
/************************************************************************/
736
/*                        GetRawBinaryLayout()                          */
737
/************************************************************************/
738
739
bool PDSDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
740
0
{
741
0
    if (!RawDataset::GetRawBinaryLayout(sLayout))
742
0
        return false;
743
0
    sLayout.osRawFilename = m_osImageFilename;
744
0
    return true;
745
0
}
746
747
/************************************************************************/
748
/*                        PDSConvertFromHex()                           */
749
/************************************************************************/
750
751
static GUInt32 PDSConvertFromHex(const char *pszVal)
752
3
{
753
3
    if (!STARTS_WITH_CI(pszVal, "16#"))
754
0
        return 0;
755
756
3
    pszVal += 3;
757
3
    GUInt32 nVal = 0;
758
27
    while (*pszVal != '#' && *pszVal != '\0')
759
24
    {
760
24
        nVal <<= 4;
761
24
        if (*pszVal >= '0' && *pszVal <= '9')
762
3
            nVal += *pszVal - '0';
763
21
        else if (*pszVal >= 'A' && *pszVal <= 'F')
764
21
            nVal += *pszVal - 'A' + 10;
765
0
        else
766
0
            return 0;
767
24
        pszVal++;
768
24
    }
769
770
3
    return nVal;
771
3
}
772
773
/************************************************************************/
774
/*                             ParseImage()                             */
775
/************************************************************************/
776
777
int PDSDataset::ParseImage(const CPLString &osPrefix,
778
                           const CPLString &osFilenamePrefix)
779
813
{
780
    /* ------------------------------------------------------------------- */
781
    /*      We assume the user is pointing to the label (i.e. .lbl) file.  */
782
    /* ------------------------------------------------------------------- */
783
    // IMAGE can be inline or detached and point to an image name
784
    // ^IMAGE = 3
785
    // ^IMAGE             = "GLOBAL_ALBEDO_8PPD.IMG"
786
    // ^IMAGE             = "MEGT90N000CB.IMG"
787
    // ^IMAGE             = ("FOO.IMG",1)       -- start at record 1 (1 based)
788
    // ^IMAGE             = ("FOO.IMG")         -- start at record 1 equiv of
789
    // ("FOO.IMG",1) ^IMAGE             = ("FOO.IMG", 5 <BYTES>) -- start at
790
    // byte 5 (the fifth byte in the file) ^IMAGE             = 10851 <BYTES>
791
    // ^SPECTRAL_QUBE = 5  for multi-band images
792
    // ^QUBE = 5  for multi-band images
793
794
813
    CPLString osImageKeyword = "IMAGE";
795
813
    CPLString osQube = GetKeyword(osPrefix + "^" + osImageKeyword, "");
796
813
    m_osImageFilename = GetDescription();
797
798
813
    if (EQUAL(osQube, ""))
799
788
    {
800
788
        osImageKeyword = "SPECTRAL_QUBE";
801
788
        osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
802
788
    }
803
804
813
    if (EQUAL(osQube, ""))
805
788
    {
806
788
        osImageKeyword = "QUBE";
807
788
        osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
808
788
    }
809
810
813
    const int nQube = atoi(osQube);
811
813
    int nDetachedOffset = 0;
812
813
    bool bDetachedOffsetInBytes = false;
813
814
813
    if (!osQube.empty() && osQube[0] == '(')
815
9
    {
816
9
        osQube = "\"";
817
9
        osQube += GetKeywordSub(osPrefix + "^" + osImageKeyword, 1);
818
9
        osQube += "\"";
819
9
        nDetachedOffset =
820
9
            atoi(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2, "1"));
821
9
        if (nDetachedOffset >= 1)
822
9
            nDetachedOffset -= 1;
823
824
        // If this is not explicitly in bytes, then it is assumed to be in
825
        // records, and we need to translate to bytes.
826
9
        if (strstr(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2),
827
9
                   "<BYTES>") != nullptr)
828
2
            bDetachedOffsetInBytes = true;
829
9
    }
830
831
813
    if (!osQube.empty() && osQube[0] == '"')
832
12
    {
833
12
        const CPLString osFilename = CleanString(osQube);
834
12
        if (CPLHasPathTraversal(osFilename.c_str()))
835
0
        {
836
0
            CPLError(CE_Failure, CPLE_NotSupported,
837
0
                     "Path traversal detected in %s", osFilename.c_str());
838
0
            return false;
839
0
        }
840
12
        if (!osFilenamePrefix.empty())
841
0
        {
842
0
            m_osImageFilename = osFilenamePrefix + osFilename;
843
0
        }
844
12
        else
845
12
        {
846
12
            CPLString osTPath = CPLGetPathSafe(GetDescription());
847
12
            m_osImageFilename =
848
12
                CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
849
12
            osExternalCube = m_osImageFilename;
850
12
        }
851
12
    }
852
853
    /* -------------------------------------------------------------------- */
854
    /*      Checks to see if this is raw PDS image not compressed image     */
855
    /*      so ENCODING_TYPE either does not exist or it equals "N/A".      */
856
    /*      or "DCT_DECOMPRESSED".                                          */
857
    /*      Compressed types will not be supported in this routine          */
858
    /* -------------------------------------------------------------------- */
859
860
813
    const CPLString osEncodingType =
861
813
        CleanString(GetKeyword(osPrefix + "IMAGE.ENCODING_TYPE", "N/A"));
862
813
    if (!EQUAL(osEncodingType, "N/A") &&
863
0
        !EQUAL(osEncodingType, "DCT_DECOMPRESSED"))
864
0
    {
865
0
        CPLError(CE_Failure, CPLE_OpenFailed,
866
0
                 "*** PDS image file has an ENCODING_TYPE parameter:\n"
867
0
                 "*** GDAL PDS driver does not support compressed image types\n"
868
0
                 "found: (%s)\n\n",
869
0
                 osEncodingType.c_str());
870
0
        return FALSE;
871
0
    }
872
    /**************** end ENCODING_TYPE check ***********************/
873
874
    /***********   Grab layout type (BSQ, BIP, BIL) ************/
875
    //  AXIS_NAME = (SAMPLE,LINE,BAND)
876
    /***********   Grab samples lines band        **************/
877
    /** if AXIS_NAME = "" then Bands=1 and Sample and Lines   **/
878
    /** are there own keywords  "LINES" and "LINE_SAMPLES"    **/
879
    /** if not NULL then CORE_ITEMS keyword i.e. (234,322,2)  **/
880
    /***********************************************************/
881
813
    int eLayout = PDS_BSQ;  // default to band seq.
882
813
    int nRows, nCols, l_nBands = 1;
883
884
813
    CPLString value = GetKeyword(osPrefix + osImageKeyword + ".AXIS_NAME", "");
885
813
    if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
886
0
    {
887
0
        eLayout = PDS_BSQ;
888
0
        nCols =
889
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
890
0
        nRows =
891
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
892
0
        l_nBands =
893
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
894
0
    }
895
813
    else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
896
0
    {
897
0
        eLayout = PDS_BIP;
898
0
        l_nBands =
899
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
900
0
        nRows =
901
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
902
0
        nCols =
903
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
904
0
    }
905
813
    else if (EQUAL(value, "(SAMPLE,BAND,LINE)"))
906
0
    {
907
0
        eLayout = PDS_BIL;
908
0
        nCols =
909
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
910
0
        l_nBands =
911
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
912
0
        nRows =
913
0
            atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
914
0
    }
915
813
    else if (EQUAL(value, ""))
916
813
    {
917
813
        eLayout = PDS_BSQ;
918
813
        nCols =
919
813
            atoi(GetKeyword(osPrefix + osImageKeyword + ".LINE_SAMPLES", ""));
920
813
        nRows = atoi(GetKeyword(osPrefix + osImageKeyword + ".LINES", ""));
921
813
        l_nBands = atoi(GetKeyword(osPrefix + osImageKeyword + ".BANDS", "1"));
922
813
    }
923
0
    else
924
0
    {
925
0
        CPLError(CE_Failure, CPLE_OpenFailed,
926
0
                 "%s layout not supported. Abort\n\n", value.c_str());
927
0
        return FALSE;
928
0
    }
929
930
813
    CPLString osBAND_STORAGE_TYPE =
931
813
        GetKeyword(osPrefix + "IMAGE.BAND_STORAGE_TYPE", "");
932
813
    if (EQUAL(osBAND_STORAGE_TYPE, "BAND_SEQUENTIAL"))
933
10
    {
934
10
        eLayout = PDS_BSQ;
935
10
    }
936
803
    else if (EQUAL(osBAND_STORAGE_TYPE, "PIXEL_INTERLEAVED"))
937
0
    {
938
0
        eLayout = PDS_BIP;
939
0
    }
940
803
    else if (EQUAL(osBAND_STORAGE_TYPE, "LINE_INTERLEAVED"))
941
1
    {
942
1
        eLayout = PDS_BIL;
943
1
    }
944
802
    else if (!osBAND_STORAGE_TYPE.empty())
945
1
    {
946
1
        CPLDebug("PDS", "Unhandled BAND_STORAGE_TYPE = %s",
947
1
                 osBAND_STORAGE_TYPE.c_str());
948
1
    }
949
950
    /***********   Grab Qube record bytes  **********/
951
813
    int record_bytes = atoi(GetKeyword(osPrefix + "IMAGE.RECORD_BYTES"));
952
813
    if (record_bytes == 0)
953
812
        record_bytes = atoi(GetKeyword(osPrefix + "RECORD_BYTES"));
954
955
    // this can happen with "record_type = undefined".
956
813
    if (record_bytes < 0)
957
0
        return FALSE;
958
813
    if (record_bytes == 0)
959
788
        record_bytes = 1;
960
961
813
    int nSkipBytes = 0;
962
813
    try
963
813
    {
964
813
        if (nQube > 0)
965
13
        {
966
13
            if (osQube.find("<BYTES>") != CPLString::npos)
967
0
                nSkipBytes = (CPLSM(nQube) - CPLSM(1)).v();
968
13
            else
969
13
                nSkipBytes = (CPLSM(nQube - 1) * CPLSM(record_bytes)).v();
970
13
        }
971
800
        else if (nDetachedOffset > 0)
972
3
        {
973
3
            if (bDetachedOffsetInBytes)
974
2
                nSkipBytes = nDetachedOffset;
975
1
            else
976
1
            {
977
1
                nSkipBytes = (CPLSM(nDetachedOffset) * CPLSM(record_bytes)).v();
978
1
            }
979
3
        }
980
797
        else
981
797
            nSkipBytes = 0;
982
813
    }
983
813
    catch (const CPLSafeIntOverflow &)
984
813
    {
985
0
        return FALSE;
986
0
    }
987
988
813
    const int nLinePrefixBytes =
989
813
        atoi(GetKeyword(osPrefix + "IMAGE.LINE_PREFIX_BYTES", ""));
990
813
    if (nLinePrefixBytes < 0)
991
0
        return false;
992
813
    nSkipBytes += nLinePrefixBytes;
993
994
    /***********   Grab SAMPLE_TYPE *****************/
995
    /** if keyword not found leave as "M" or "MSB" **/
996
997
813
    CPLString osST = GetKeyword(osPrefix + "IMAGE.SAMPLE_TYPE");
998
813
    if (osST.size() >= 2 && osST[0] == '"' && osST.back() == '"')
999
6
        osST = osST.substr(1, osST.size() - 2);
1000
1001
813
    char chByteOrder = 'M';  // default to MSB
1002
813
    if ((EQUAL(osST, "LSB_INTEGER")) || (EQUAL(osST, "LSB")) ||  // just in case
1003
812
        (EQUAL(osST, "LSB_UNSIGNED_INTEGER")) ||
1004
811
        (EQUAL(osST, "LSB_SIGNED_INTEGER")) ||
1005
811
        (EQUAL(osST, "UNSIGNED_INTEGER")) || (EQUAL(osST, "VAX_REAL")) ||
1006
799
        (EQUAL(osST, "VAX_INTEGER")) ||
1007
799
        (EQUAL(osST, "PC_INTEGER")) ||  // just in case
1008
799
        (EQUAL(osST, "PC_REAL")))
1009
19
    {
1010
19
        chByteOrder = 'I';
1011
19
    }
1012
1013
    /**** Grab format type - pds supports 1,2,4,8,16,32,64 (in theory) **/
1014
    /**** I have only seen 8, 16, 32 (float) in released datasets      **/
1015
813
    GDALDataType eDataType = GDT_Byte;
1016
813
    int nSuffixItems = 0;
1017
813
    int nSuffixLines = 0;
1018
813
    int nSuffixBytes = 4;  // Default as per PDS specification
1019
813
    double dfNoData = 0.0;
1020
813
    double dfScale = 1.0;
1021
813
    double dfOffset = 0.0;
1022
813
    const char *pszUnit = nullptr;
1023
813
    const char *pszDesc = nullptr;
1024
1025
813
    CPLString osSB = GetKeyword(osPrefix + "IMAGE.SAMPLE_BITS", "");
1026
813
    if (!osSB.empty())
1027
27
    {
1028
27
        const int itype = atoi(osSB);
1029
27
        switch (itype)
1030
27
        {
1031
14
            case 8:
1032
14
                eDataType = GDT_Byte;
1033
14
                dfNoData = PDS_NULL1;
1034
14
                break;
1035
8
            case 16:
1036
8
                if (strstr(osST, "UNSIGNED") != nullptr)
1037
7
                {
1038
7
                    dfNoData = PDS_NULL1;
1039
7
                    eDataType = GDT_UInt16;
1040
7
                }
1041
1
                else
1042
1
                {
1043
1
                    eDataType = GDT_Int16;
1044
1
                    dfNoData = PDS_NULL2;
1045
1
                }
1046
8
                break;
1047
4
            case 32:
1048
4
                eDataType = GDT_Float32;
1049
4
                dfNoData = PDS_NULL3;
1050
4
                break;
1051
0
            case 64:
1052
0
                eDataType = GDT_Float64;
1053
0
                dfNoData = PDS_NULL3;
1054
0
                break;
1055
1
            default:
1056
1
                CPLError(CE_Failure, CPLE_AppDefined,
1057
1
                         "Sample_bits of %d is not supported in this gdal PDS "
1058
1
                         "reader.",
1059
1
                         itype);
1060
1
                return FALSE;
1061
27
        }
1062
1063
26
        dfOffset = CPLAtofM(GetKeyword(osPrefix + "IMAGE.OFFSET", "0.0"));
1064
26
        dfScale =
1065
26
            CPLAtofM(GetKeyword(osPrefix + "IMAGE.SCALING_FACTOR", "1.0"));
1066
26
    }
1067
786
    else /* No IMAGE object, search for the QUBE. */
1068
786
    {
1069
786
        osSB = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_ITEM_BYTES", "");
1070
786
        const int itype = atoi(osSB);
1071
786
        switch (itype)
1072
786
        {
1073
0
            case 1:
1074
0
                eDataType = GDT_Byte;
1075
0
                break;
1076
0
            case 2:
1077
0
                if (strstr(osST, "UNSIGNED") != nullptr)
1078
0
                    eDataType = GDT_UInt16;
1079
0
                else
1080
0
                    eDataType = GDT_Int16;
1081
0
                break;
1082
0
            case 4:
1083
0
                eDataType = GDT_Float32;
1084
0
                break;
1085
786
            default:
1086
786
                CPLError(CE_Failure, CPLE_AppDefined,
1087
786
                         "CORE_ITEM_BYTES of %d is not supported in this gdal "
1088
786
                         "PDS reader.",
1089
786
                         itype);
1090
786
                return FALSE;
1091
786
        }
1092
1093
        /* Parse suffix dimensions if defined. */
1094
0
        value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", "");
1095
0
        if (!value.empty())
1096
0
        {
1097
0
            value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_BYTES", "");
1098
0
            if (!value.empty())
1099
0
                nSuffixBytes = atoi(value);
1100
1101
0
            nSuffixItems =
1102
0
                atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 1));
1103
0
            nSuffixLines =
1104
0
                atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 2));
1105
0
        }
1106
1107
0
        value = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NULL", "");
1108
0
        if (!value.empty())
1109
0
            dfNoData = CPLAtofM(value);
1110
1111
0
        dfOffset =
1112
0
            CPLAtofM(GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_BASE", "0.0"));
1113
0
        dfScale = CPLAtofM(
1114
0
            GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_MULTIPLIER", "1.0"));
1115
0
        pszUnit = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_UNIT", nullptr);
1116
0
        pszDesc = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NAME", nullptr);
1117
0
    }
1118
1119
    /* -------------------------------------------------------------------- */
1120
    /*      Is there a specific nodata value in the file? Either the        */
1121
    /*      MISSING or MISSING_CONSTANT keywords are nodata.                */
1122
    /* -------------------------------------------------------------------- */
1123
1124
26
    const char *pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING", nullptr);
1125
26
    if (pszMissing == nullptr)
1126
25
        pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING_CONSTANT", nullptr);
1127
1128
26
    if (pszMissing != nullptr)
1129
9
    {
1130
9
        if (*pszMissing == '"')
1131
3
            pszMissing++;
1132
1133
        /* For example : MISSING_CONSTANT             = "16#FF7FFFFB#" */
1134
9
        if (STARTS_WITH_CI(pszMissing, "16#") &&
1135
3
            strlen(pszMissing) >= 3 + 8 + 1 && pszMissing[3 + 8] == '#' &&
1136
3
            (eDataType == GDT_Float32 || eDataType == GDT_Float64))
1137
3
        {
1138
3
            GUInt32 nVal = PDSConvertFromHex(pszMissing);
1139
3
            float fVal;
1140
3
            memcpy(&fVal, &nVal, 4);
1141
3
            dfNoData = fVal;
1142
3
        }
1143
6
        else
1144
6
            dfNoData = CPLAtofM(pszMissing);
1145
9
    }
1146
1147
    /* -------------------------------------------------------------------- */
1148
    /*      Did we get the required keywords?  If not we return with        */
1149
    /*      this never having been considered to be a match. This isn't     */
1150
    /*      an error!                                                       */
1151
    /* -------------------------------------------------------------------- */
1152
26
    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
1153
23
        !GDALCheckBandCount(l_nBands, false))
1154
3
    {
1155
3
        return FALSE;
1156
3
    }
1157
1158
    /* -------------------------------------------------------------------- */
1159
    /*      Capture some information from the file that is of interest.     */
1160
    /* -------------------------------------------------------------------- */
1161
23
    nRasterXSize = nCols;
1162
23
    nRasterYSize = nRows;
1163
1164
    /* -------------------------------------------------------------------- */
1165
    /*      Open target binary file.                                        */
1166
    /* -------------------------------------------------------------------- */
1167
1168
23
    if (eAccess == GA_ReadOnly)
1169
23
    {
1170
23
        fpImage = VSIFOpenL(m_osImageFilename, "rb");
1171
23
        if (fpImage == nullptr)
1172
10
        {
1173
10
            CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n%s",
1174
10
                     m_osImageFilename.c_str(), VSIStrerror(errno));
1175
10
            return FALSE;
1176
10
        }
1177
23
    }
1178
0
    else
1179
0
    {
1180
0
        fpImage = VSIFOpenL(m_osImageFilename, "r+b");
1181
0
        if (fpImage == nullptr)
1182
0
        {
1183
0
            CPLError(CE_Failure, CPLE_OpenFailed,
1184
0
                     "Failed to open %s with write permission.\n%s",
1185
0
                     m_osImageFilename.c_str(), VSIStrerror(errno));
1186
0
            return FALSE;
1187
0
        }
1188
0
    }
1189
1190
    /* -------------------------------------------------------------------- */
1191
    /*      Compute the line offset.                                        */
1192
    /* -------------------------------------------------------------------- */
1193
13
    const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
1194
1195
    // Needed for N1349177584_2.LBL from
1196
    // https://trac.osgeo.org/gdal/attachment/ticket/3355/PDS-TestFiles.zip
1197
13
    int nLineOffset = nLinePrefixBytes;
1198
1199
13
    int nPixelOffset;
1200
13
    vsi_l_offset nBandOffset;
1201
1202
78
    const auto CPLSM64 = [](int x) { return CPLSM(static_cast<int64_t>(x)); };
1203
1204
13
    try
1205
13
    {
1206
13
        if (eLayout == PDS_BIP)
1207
0
        {
1208
0
            nPixelOffset = (CPLSM(nItemSize) * CPLSM(l_nBands)).v();
1209
0
            nBandOffset = nItemSize;
1210
0
            nLineOffset =
1211
0
                (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1212
0
        }
1213
13
        else if (eLayout == PDS_BSQ)
1214
13
        {
1215
13
            nPixelOffset = nItemSize;
1216
13
            nLineOffset =
1217
13
                (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
1218
13
            nBandOffset = static_cast<vsi_l_offset>(
1219
13
                (CPLSM64(nLineOffset) * CPLSM64(nRows) +
1220
13
                 CPLSM64(nSuffixLines) *
1221
13
                     (CPLSM64(nCols) + CPLSM64(nSuffixItems)) *
1222
13
                     CPLSM64(nSuffixBytes))
1223
13
                    .v());
1224
13
        }
1225
0
        else /* assume BIL */
1226
0
        {
1227
0
            nPixelOffset = nItemSize;
1228
0
            nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols)).v();
1229
0
            nLineOffset =
1230
0
                (CPLSM(nLineOffset) +
1231
0
                 CPLSM(static_cast<int>(nBandOffset)) * CPLSM(l_nBands))
1232
0
                    .v();
1233
0
        }
1234
13
    }
1235
13
    catch (const CPLSafeIntOverflow &)
1236
13
    {
1237
0
        CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow");
1238
0
        return FALSE;
1239
0
    }
1240
1241
    /* -------------------------------------------------------------------- */
1242
    /*      Create band information objects.                                */
1243
    /* -------------------------------------------------------------------- */
1244
27
    for (int i = 0; i < l_nBands; i++)
1245
14
    {
1246
14
        auto poBand = RawRasterBand::Create(
1247
14
            this, i + 1, fpImage,
1248
14
            nSkipBytes + static_cast<vsi_l_offset>(nBandOffset) * i,
1249
14
            nPixelOffset, nLineOffset, eDataType,
1250
14
            chByteOrder == 'I' || chByteOrder == 'L'
1251
14
                ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
1252
14
                : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
1253
14
            RawRasterBand::OwnFP::NO);
1254
14
        if (!poBand)
1255
0
            return FALSE;
1256
1257
14
        if (l_nBands == 1)
1258
12
        {
1259
12
            const char *pszMin =
1260
12
                GetKeyword(osPrefix + "IMAGE.MINIMUM", nullptr);
1261
12
            const char *pszMax =
1262
12
                GetKeyword(osPrefix + "IMAGE.MAXIMUM", nullptr);
1263
12
            const char *pszMean = GetKeyword(osPrefix + "IMAGE.MEAN", nullptr);
1264
12
            const char *pszStdDev =
1265
12
                GetKeyword(osPrefix + "IMAGE.STANDARD_DEVIATION", nullptr);
1266
12
            if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
1267
0
                pszStdDev != nullptr)
1268
0
            {
1269
0
                poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
1270
0
                                      CPLAtofM(pszMean), CPLAtofM(pszStdDev));
1271
0
            }
1272
12
        }
1273
1274
14
        poBand->SetNoDataValue(dfNoData);
1275
1276
        // Set offset/scale values at the PAM level.
1277
14
        poBand->SetOffset(dfOffset);
1278
14
        poBand->SetScale(dfScale);
1279
14
        if (pszUnit)
1280
0
            poBand->SetUnitType(pszUnit);
1281
14
        if (pszDesc)
1282
0
            poBand->SetDescription(pszDesc);
1283
1284
14
        SetBand(i + 1, std::move(poBand));
1285
14
    }
1286
1287
13
    return TRUE;
1288
13
}
1289
1290
/************************************************************************/
1291
/* ==================================================================== */
1292
/*                         PDSWrapperRasterBand                         */
1293
/*                                                                      */
1294
/*      proxy for the jp2 or other compressed bands.                    */
1295
/* ==================================================================== */
1296
/************************************************************************/
1297
class PDSWrapperRasterBand final : public GDALProxyRasterBand
1298
{
1299
    GDALRasterBand *poBaseBand{};
1300
1301
    CPL_DISALLOW_COPY_ASSIGN(PDSWrapperRasterBand)
1302
1303
  protected:
1304
    virtual GDALRasterBand *
1305
    RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
1306
1307
  public:
1308
    explicit PDSWrapperRasterBand(GDALRasterBand *poBaseBandIn)
1309
0
    {
1310
0
        this->poBaseBand = poBaseBandIn;
1311
0
        eDataType = poBaseBand->GetRasterDataType();
1312
0
        poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
1313
0
    }
1314
};
1315
1316
GDALRasterBand *
1317
PDSWrapperRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
1318
0
{
1319
0
    return poBaseBand;
1320
0
}
1321
1322
/************************************************************************/
1323
/*                       ParseCompressedImage()                         */
1324
/************************************************************************/
1325
1326
int PDSDataset::ParseCompressedImage()
1327
1328
4
{
1329
4
    const CPLString osFileName =
1330
4
        CleanString(GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1331
4
    if (CPLHasPathTraversal(osFileName.c_str()))
1332
0
    {
1333
0
        CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
1334
0
                 osFileName.c_str());
1335
0
        return false;
1336
0
    }
1337
1338
4
    const CPLString osPath = CPLGetPathSafe(GetDescription());
1339
4
    const CPLString osFullFileName =
1340
4
        CPLFormFilenameSafe(osPath, osFileName, nullptr);
1341
1342
4
    poCompressedDS =
1343
4
        GDALDataset::FromHandle(GDALOpen(osFullFileName, GA_ReadOnly));
1344
1345
4
    if (poCompressedDS == nullptr)
1346
4
        return FALSE;
1347
1348
0
    nRasterXSize = poCompressedDS->GetRasterXSize();
1349
0
    nRasterYSize = poCompressedDS->GetRasterYSize();
1350
1351
0
    for (int iBand = 0; iBand < poCompressedDS->GetRasterCount(); iBand++)
1352
0
    {
1353
0
        SetBand(iBand + 1, new PDSWrapperRasterBand(
1354
0
                               poCompressedDS->GetRasterBand(iBand + 1)));
1355
0
    }
1356
1357
0
    return TRUE;
1358
4
}
1359
1360
/************************************************************************/
1361
/*                                Open()                                */
1362
/************************************************************************/
1363
1364
GDALDataset *PDSDataset::Open(GDALOpenInfo *poOpenInfo)
1365
4.23k
{
1366
4.23k
    if (!PDSDriverIdentify(poOpenInfo))
1367
0
        return nullptr;
1368
1369
4.23k
    const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
1370
4.23k
    if (strstr(pszHdr, "PDS_VERSION_ID") != nullptr &&
1371
1.41k
        strstr(pszHdr, "PDS3") == nullptr)
1372
610
    {
1373
610
        CPLError(
1374
610
            CE_Failure, CPLE_OpenFailed,
1375
610
            "It appears this is an older PDS image type.  Only PDS_VERSION_ID "
1376
610
            "= PDS3 are currently supported by this gdal PDS reader.");
1377
610
        return nullptr;
1378
610
    }
1379
1380
    /* -------------------------------------------------------------------- */
1381
    /*      Parse the keyword header.  Sometimes there is stuff             */
1382
    /*      before the PDS_VERSION_ID, which we want to ignore.             */
1383
    /* -------------------------------------------------------------------- */
1384
3.62k
    VSILFILE *fpQube = poOpenInfo->fpL;
1385
3.62k
    poOpenInfo->fpL = nullptr;
1386
1387
3.62k
    PDSDataset *poDS = new PDSDataset();
1388
3.62k
    poDS->SetDescription(poOpenInfo->pszFilename);
1389
3.62k
    poDS->eAccess = poOpenInfo->eAccess;
1390
1391
3.62k
    const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
1392
3.62k
    int nOffset = 0;
1393
3.62k
    if (pszPDSVersionID)
1394
809
        nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
1395
1396
3.62k
    if (!poDS->oKeywords.Ingest(fpQube, nOffset))
1397
2.80k
    {
1398
2.80k
        delete poDS;
1399
2.80k
        VSIFCloseL(fpQube);
1400
2.80k
        return nullptr;
1401
2.80k
    }
1402
817
    poDS->m_aosPDSMD.InsertString(
1403
817
        0, poDS->oKeywords.GetJsonObject()
1404
817
               .Format(CPLJSONObject::PrettyFormat::Pretty)
1405
817
               .c_str());
1406
817
    VSIFCloseL(fpQube);
1407
1408
    /* -------------------------------------------------------------------- */
1409
    /*      Is this a compressed image with COMPRESSED_FILE subdomain?      */
1410
    /*                                                                      */
1411
    /*      The corresponding parse operations will read keywords,          */
1412
    /*      establish bands and raster size.                                */
1413
    /* -------------------------------------------------------------------- */
1414
817
    CPLString osEncodingType =
1415
817
        poDS->GetKeyword("COMPRESSED_FILE.ENCODING_TYPE", "");
1416
1417
817
    CPLString osCompressedFilename =
1418
817
        CleanString(poDS->GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
1419
1420
817
    const char *pszImageName =
1421
817
        poDS->GetKeyword("UNCOMPRESSED_FILE.IMAGE.NAME", "");
1422
817
    CPLString osUncompressedFilename =
1423
817
        CleanString(!EQUAL(pszImageName, "")
1424
817
                        ? pszImageName
1425
817
                        : poDS->GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", ""));
1426
1427
817
    VSIStatBufL sStat;
1428
817
    CPLString osFilenamePrefix;
1429
1430
817
    if (EQUAL(osEncodingType, "ZIP") && !osCompressedFilename.empty() &&
1431
9
        !osUncompressedFilename.empty())
1432
6
    {
1433
6
        const CPLString osPath = CPLGetPathSafe(poDS->GetDescription());
1434
6
        osCompressedFilename =
1435
6
            CPLFormFilenameSafe(osPath, osCompressedFilename, nullptr);
1436
6
        osUncompressedFilename =
1437
6
            CPLFormFilenameSafe(osPath, osUncompressedFilename, nullptr);
1438
6
        if (VSIStatExL(osCompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) ==
1439
6
                0 &&
1440
0
            VSIStatExL(osUncompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) !=
1441
0
                0)
1442
0
        {
1443
0
            osFilenamePrefix = "/vsizip/" + osCompressedFilename + "/";
1444
0
            poDS->osExternalCube = std::move(osCompressedFilename);
1445
0
        }
1446
6
        osEncodingType = "";
1447
6
    }
1448
1449
817
    if (!osEncodingType.empty())
1450
4
    {
1451
4
        if (!poDS->ParseCompressedImage())
1452
4
        {
1453
4
            delete poDS;
1454
4
            return nullptr;
1455
4
        }
1456
4
    }
1457
813
    else
1458
813
    {
1459
813
        CPLString osPrefix;
1460
1461
813
        if (osUncompressedFilename != "")
1462
8
            osPrefix = "UNCOMPRESSED_FILE.";
1463
1464
        // Added ability to see into OBJECT = FILE section to support
1465
        // CRISM. Example file: hsp00017ba0_01_ra218s_trr3.lbl and *.img
1466
813
        if (strlen(poDS->GetKeyword("IMAGE.LINE_SAMPLES")) == 0 &&
1467
793
            strlen(poDS->GetKeyword("FILE.IMAGE.LINE_SAMPLES")) != 0)
1468
1
            osPrefix = "FILE.";
1469
1470
813
        if (!poDS->ParseImage(osPrefix, osFilenamePrefix))
1471
800
        {
1472
800
            delete poDS;
1473
800
            return nullptr;
1474
800
        }
1475
813
    }
1476
1477
    /* -------------------------------------------------------------------- */
1478
    /*      Set the coordinate system and geotransform.                     */
1479
    /* -------------------------------------------------------------------- */
1480
13
    poDS->ParseSRS();
1481
1482
    /* -------------------------------------------------------------------- */
1483
    /*      Transfer a few interesting keywords as metadata.                */
1484
    /* -------------------------------------------------------------------- */
1485
13
    static const char *const apszKeywords[] = {"FILTER_NAME",
1486
13
                                               "DATA_SET_ID",
1487
13
                                               "PRODUCT_ID",
1488
13
                                               "PRODUCER_INSTITUTION_NAME",
1489
13
                                               "PRODUCT_TYPE",
1490
13
                                               "MISSION_NAME",
1491
13
                                               "SPACECRAFT_NAME",
1492
13
                                               "INSTRUMENT_NAME",
1493
13
                                               "INSTRUMENT_ID",
1494
13
                                               "TARGET_NAME",
1495
13
                                               "CENTER_FILTER_WAVELENGTH",
1496
13
                                               "BANDWIDTH",
1497
13
                                               "PRODUCT_CREATION_TIME",
1498
13
                                               "START_TIME",
1499
13
                                               "STOP_TIME",
1500
13
                                               "NOTE",
1501
13
                                               nullptr};
1502
1503
221
    for (int i = 0; apszKeywords[i] != nullptr; i++)
1504
208
    {
1505
208
        const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
1506
1507
208
        if (pszKeywordValue != nullptr)
1508
208
            poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
1509
208
    }
1510
1511
    /* -------------------------------------------------------------------- */
1512
    /*      Initialize any PAM information.                                 */
1513
    /* -------------------------------------------------------------------- */
1514
13
    poDS->TryLoadXML();
1515
1516
    /* -------------------------------------------------------------------- */
1517
    /*      Check for overviews.                                            */
1518
    /* -------------------------------------------------------------------- */
1519
13
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1520
1521
13
    return poDS;
1522
817
}
1523
1524
/************************************************************************/
1525
/*                             GetKeyword()                             */
1526
/************************************************************************/
1527
1528
const char *PDSDataset::GetKeyword(const std::string &osPath,
1529
                                   const char *pszDefault)
1530
1531
17.5k
{
1532
17.5k
    return oKeywords.GetKeyword(osPath.c_str(), pszDefault);
1533
17.5k
}
1534
1535
/************************************************************************/
1536
/*                            GetKeywordSub()                           */
1537
/************************************************************************/
1538
1539
const char *PDSDataset::GetKeywordSub(const std::string &osPath, int iSubscript,
1540
                                      const char *pszDefault)
1541
1542
27
{
1543
27
    const char *pszResult = oKeywords.GetKeyword(osPath.c_str(), nullptr);
1544
1545
27
    if (pszResult == nullptr)
1546
0
        return pszDefault;
1547
1548
27
    if (pszResult[0] != '(')
1549
0
        return pszDefault;
1550
1551
27
    char **papszTokens =
1552
27
        CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
1553
1554
27
    if (iSubscript <= CSLCount(papszTokens))
1555
27
    {
1556
27
        osTempResult = papszTokens[iSubscript - 1];
1557
27
        CSLDestroy(papszTokens);
1558
27
        return osTempResult.c_str();
1559
27
    }
1560
1561
0
    CSLDestroy(papszTokens);
1562
0
    return pszDefault;
1563
27
}
1564
1565
/************************************************************************/
1566
/*                            GetKeywordUnit()                          */
1567
/************************************************************************/
1568
1569
const char *PDSDataset::GetKeywordUnit(const char *pszPath, int iSubscript,
1570
                                       const char *pszDefault)
1571
1572
8
{
1573
8
    const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
1574
1575
8
    if (pszResult == nullptr)
1576
0
        return pszDefault;
1577
1578
8
    char **papszTokens =
1579
8
        CSLTokenizeString2(pszResult, "</>", CSLT_HONOURSTRINGS);
1580
1581
8
    if (iSubscript <= CSLCount(papszTokens))
1582
6
    {
1583
6
        osTempResult = papszTokens[iSubscript - 1];
1584
6
        CSLDestroy(papszTokens);
1585
6
        return osTempResult.c_str();
1586
6
    }
1587
1588
2
    CSLDestroy(papszTokens);
1589
2
    return pszDefault;
1590
8
}
1591
1592
/************************************************************************/
1593
/*                            CleanString()                             */
1594
/*                                                                      */
1595
/* Removes single or double quotes, and converts spaces to underscores. */
1596
/************************************************************************/
1597
1598
CPLString PDSDataset::CleanString(const CPLString &osInput)
1599
1600
2.48k
{
1601
2.48k
    if ((osInput.size() < 2) ||
1602
869
        ((osInput.at(0) != '"' || osInput.back() != '"') &&
1603
822
         (osInput.at(0) != '\'' || osInput.back() != '\'')))
1604
2.44k
        return osInput;
1605
1606
47
    char *pszWrk = CPLStrdup(osInput.c_str() + 1);
1607
1608
47
    pszWrk[strlen(pszWrk) - 1] = '\0';
1609
1610
834
    for (int i = 0; pszWrk[i] != '\0'; i++)
1611
787
    {
1612
787
        if (pszWrk[i] == ' ')
1613
7
            pszWrk[i] = '_';
1614
787
    }
1615
1616
47
    CPLString osOutput = pszWrk;
1617
47
    CPLFree(pszWrk);
1618
47
    return osOutput;
1619
2.48k
}
1620
1621
/************************************************************************/
1622
/*                      GetMetadataDomainList()                         */
1623
/************************************************************************/
1624
1625
char **PDSDataset::GetMetadataDomainList()
1626
0
{
1627
0
    return BuildMetadataDomainList(nullptr, FALSE, "", "json:PDS", nullptr);
1628
0
}
1629
1630
/************************************************************************/
1631
/*                             GetMetadata()                            */
1632
/************************************************************************/
1633
1634
char **PDSDataset::GetMetadata(const char *pszDomain)
1635
24
{
1636
24
    if (pszDomain != nullptr && EQUAL(pszDomain, "json:PDS"))
1637
0
    {
1638
0
        return m_aosPDSMD.List();
1639
0
    }
1640
24
    return GDALPamDataset::GetMetadata(pszDomain);
1641
24
}
1642
1643
/************************************************************************/
1644
/*                         GDALRegister_PDS()                           */
1645
/************************************************************************/
1646
1647
void GDALRegister_PDS()
1648
1649
22
{
1650
22
    if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
1651
0
        return;
1652
1653
22
    GDALDriver *poDriver = new GDALDriver();
1654
22
    PDSDriverSetCommonMetadata(poDriver);
1655
1656
22
    poDriver->pfnOpen = PDSDataset::Open;
1657
1658
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1659
1660
#ifdef PDS_PLUGIN
1661
    GDALRegister_ISIS3();
1662
    GDALRegister_ISIS2();
1663
    GDALRegister_PDS4();
1664
    GDALRegister_VICAR();
1665
#endif
1666
22
}