Coverage Report

Created: 2025-06-09 07:02

/src/gdal/frmts/pds/isis2dataset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  ISIS Version 2 Driver
4
 * Purpose:  Implementation of ISIS2Dataset
5
 * Author:   Trent Hare (thare at usgs.gov),
6
 *           Robert Soricone (rsoricone at usgs.gov)
7
 *           Ludovic Mercier (ludovic.mercier at gmail.com)
8
 *           Frank Warmerdam (warmerdam at pobox.com)
9
 *
10
 * NOTE: Original code authored by Trent and Robert and placed in the public
11
 * domain as per US government policy.  I have (within my rights) appropriated
12
 * it and placed it under the following license.  This is not intended to
13
 * diminish Trent and Roberts contribution.
14
 ******************************************************************************
15
 * Copyright (c) 2006, Frank Warmerdam <warmerdam at pobox.com>
16
 * Copyright (c) 2008-2011, Even Rouault <even dot rouault at spatialys.com>
17
 *
18
 * SPDX-License-Identifier: MIT
19
 ****************************************************************************/
20
21
constexpr int NULL1 = 0;
22
constexpr int NULL2 = -32768;
23
constexpr double NULL3 = -3.4028226550889044521e+38;
24
25
#include "cpl_string.h"
26
#include "gdal_frmts.h"
27
#include "nasakeywordhandler.h"
28
#include "ogr_spatialref.h"
29
#include "rawdataset.h"
30
#include "pdsdrivercore.h"
31
32
/************************************************************************/
33
/* ==================================================================== */
34
/*                      ISISDataset     version2                        */
35
/* ==================================================================== */
36
/************************************************************************/
37
38
class ISIS2Dataset final : public RawDataset
39
{
40
    VSILFILE *fpImage;  // image data file.
41
    CPLString osExternalCube;
42
43
    NASAKeywordHandler oKeywords;
44
45
    int bGotTransform;
46
    double adfGeoTransform[6];
47
48
    OGRSpatialReference m_oSRS{};
49
50
    int parse_label(const char *file, char *keyword, char *value);
51
    int strstrip(char instr[], char outstr[], int position);
52
53
    CPLString oTempResult;
54
55
    static void CleanString(CPLString &osInput);
56
57
    const char *GetKeyword(const char *pszPath, const char *pszDefault = "");
58
    const char *GetKeywordSub(const char *pszPath, int iSubscript,
59
                              const char *pszDefault = "");
60
61
    CPLErr Close() override;
62
63
  public:
64
    ISIS2Dataset();
65
    virtual ~ISIS2Dataset();
66
67
    virtual CPLErr GetGeoTransform(double *padfTransform) override;
68
    const OGRSpatialReference *GetSpatialRef() const override;
69
70
    virtual char **GetFileList() override;
71
72
    static GDALDataset *Open(GDALOpenInfo *);
73
};
74
75
/************************************************************************/
76
/*                            ISIS2Dataset()                            */
77
/************************************************************************/
78
79
746
ISIS2Dataset::ISIS2Dataset() : fpImage(nullptr), bGotTransform(FALSE)
80
746
{
81
746
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
82
746
    adfGeoTransform[0] = 0.0;
83
746
    adfGeoTransform[1] = 1.0;
84
746
    adfGeoTransform[2] = 0.0;
85
746
    adfGeoTransform[3] = 0.0;
86
746
    adfGeoTransform[4] = 0.0;
87
746
    adfGeoTransform[5] = 1.0;
88
746
}
89
90
/************************************************************************/
91
/*                            ~ISIS2Dataset()                            */
92
/************************************************************************/
93
94
ISIS2Dataset::~ISIS2Dataset()
95
96
746
{
97
746
    ISIS2Dataset::Close();
98
746
}
99
100
/************************************************************************/
101
/*                              Close()                                 */
102
/************************************************************************/
103
104
CPLErr ISIS2Dataset::Close()
105
746
{
106
746
    CPLErr eErr = CE_None;
107
746
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
108
746
    {
109
746
        if (ISIS2Dataset::FlushCache(true) != CE_None)
110
0
            eErr = CE_Failure;
111
746
        if (fpImage != nullptr)
112
0
        {
113
0
            if (VSIFCloseL(fpImage) != 0)
114
0
                eErr = CE_Failure;
115
0
        }
116
746
        if (GDALPamDataset::Close() != CE_None)
117
0
            eErr = CE_Failure;
118
746
    }
119
746
    return eErr;
120
746
}
121
122
/************************************************************************/
123
/*                            GetFileList()                             */
124
/************************************************************************/
125
126
char **ISIS2Dataset::GetFileList()
127
128
0
{
129
0
    char **papszFileList = GDALPamDataset::GetFileList();
130
131
0
    if (!osExternalCube.empty())
132
0
        papszFileList = CSLAddString(papszFileList, osExternalCube);
133
134
0
    return papszFileList;
135
0
}
136
137
/************************************************************************/
138
/*                         GetSpatialRef()                              */
139
/************************************************************************/
140
141
const OGRSpatialReference *ISIS2Dataset::GetSpatialRef() const
142
0
{
143
0
    if (!m_oSRS.IsEmpty())
144
0
        return &m_oSRS;
145
0
    return GDALPamDataset::GetSpatialRef();
146
0
}
147
148
/************************************************************************/
149
/*                          GetGeoTransform()                           */
150
/************************************************************************/
151
152
CPLErr ISIS2Dataset::GetGeoTransform(double *padfTransform)
153
154
0
{
155
0
    if (bGotTransform)
156
0
    {
157
0
        memcpy(padfTransform, adfGeoTransform, sizeof(double) * 6);
158
0
        return CE_None;
159
0
    }
160
161
0
    return GDALPamDataset::GetGeoTransform(padfTransform);
162
0
}
163
164
/************************************************************************/
165
/*                                Open()                                */
166
/************************************************************************/
167
168
GDALDataset *ISIS2Dataset::Open(GDALOpenInfo *poOpenInfo)
169
746
{
170
    /* -------------------------------------------------------------------- */
171
    /*      Does this look like a CUBE or an IMAGE Primary Data Object?     */
172
    /* -------------------------------------------------------------------- */
173
746
    if (!ISIS2DriverIdentify(poOpenInfo) || poOpenInfo->fpL == nullptr)
174
0
        return nullptr;
175
176
746
    VSILFILE *fpQube = poOpenInfo->fpL;
177
746
    poOpenInfo->fpL = nullptr;
178
179
746
    auto poDS = std::make_unique<ISIS2Dataset>();
180
181
746
    if (!poDS->oKeywords.Ingest(fpQube, 0))
182
721
    {
183
721
        VSIFCloseL(fpQube);
184
721
        return nullptr;
185
721
    }
186
187
25
    VSIFCloseL(fpQube);
188
189
    /* -------------------------------------------------------------------- */
190
    /*      We assume the user is pointing to the label (i.e. .lab) file.   */
191
    /* -------------------------------------------------------------------- */
192
    // QUBE can be inline or detached and point to an image name
193
    // ^QUBE = 76
194
    // ^QUBE = ("ui31s015.img",6441<BYTES>) - has another label on the image
195
    // ^QUBE = "ui31s015.img" - which implies no label or skip value
196
197
25
    const char *pszQube = poDS->GetKeyword("^QUBE");
198
25
    int nQube = 0;
199
25
    int bByteLocation = FALSE;
200
25
    CPLString osTargetFile = poOpenInfo->pszFilename;
201
202
25
    if (pszQube[0] == '"')
203
0
    {
204
0
        const CPLString osTPath = CPLGetPathSafe(poOpenInfo->pszFilename);
205
0
        CPLString osFilename = pszQube;
206
0
        poDS->CleanString(osFilename);
207
0
        osTargetFile = CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
208
0
        poDS->osExternalCube = osTargetFile;
209
0
    }
210
25
    else if (pszQube[0] == '(')
211
0
    {
212
0
        const CPLString osTPath = CPLGetPathSafe(poOpenInfo->pszFilename);
213
0
        CPLString osFilename = poDS->GetKeywordSub("^QUBE", 1, "");
214
0
        poDS->CleanString(osFilename);
215
0
        osTargetFile = CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
216
0
        poDS->osExternalCube = osTargetFile;
217
218
0
        nQube = atoi(poDS->GetKeywordSub("^QUBE", 2, "1"));
219
0
        if (strstr(poDS->GetKeywordSub("^QUBE", 2, "1"), "<BYTES>") != nullptr)
220
0
            bByteLocation = true;
221
0
    }
222
25
    else
223
25
    {
224
25
        nQube = atoi(pszQube);
225
25
        if (strstr(pszQube, "<BYTES>") != nullptr)
226
0
            bByteLocation = true;
227
25
    }
228
229
    /* -------------------------------------------------------------------- */
230
    /*      Check if file an ISIS2 header file?  Read a few lines of text   */
231
    /*      searching for something starting with nrows or ncols.           */
232
    /* -------------------------------------------------------------------- */
233
234
    /* -------------------------------------------------------------------- */
235
    /*      Checks to see if this is valid ISIS2 cube                       */
236
    /*      SUFFIX_ITEM tag in .cub file should be (0,0,0); no side-planes  */
237
    /* -------------------------------------------------------------------- */
238
25
    const int s_ix = atoi(poDS->GetKeywordSub("QUBE.SUFFIX_ITEMS", 1));
239
25
    const int s_iy = atoi(poDS->GetKeywordSub("QUBE.SUFFIX_ITEMS", 2));
240
25
    const int s_iz = atoi(poDS->GetKeywordSub("QUBE.SUFFIX_ITEMS", 3));
241
242
25
    if (s_ix != 0 || s_iy != 0 || s_iz != 0)
243
0
    {
244
0
        CPLError(CE_Failure, CPLE_OpenFailed,
245
0
                 "*** ISIS 2 cube file has invalid SUFFIX_ITEMS parameters:\n"
246
0
                 "*** gdal isis2 driver requires (0, 0, 0), thus no sideplanes "
247
0
                 "or backplanes\n"
248
0
                 "found: (%i, %i, %i)\n\n",
249
0
                 s_ix, s_iy, s_iz);
250
0
        return nullptr;
251
0
    }
252
253
    /**************** end SUFFIX_ITEM check ***********************/
254
255
    /***********   Grab layout type (BSQ, BIP, BIL) ************/
256
    //  AXIS_NAME = (SAMPLE,LINE,BAND)
257
    /***********************************************************/
258
259
25
    char szLayout[10] = "BSQ";  // default to band seq.
260
25
    const char *value = poDS->GetKeyword("QUBE.AXIS_NAME", "");
261
25
    if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
262
0
        strcpy(szLayout, "BSQ");
263
25
    else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
264
0
        strcpy(szLayout, "BIP");
265
25
    else if (EQUAL(value, "(SAMPLE,BAND,LINE)") || EQUAL(value, ""))
266
25
        strcpy(szLayout, "BSQ");
267
0
    else
268
0
    {
269
0
        CPLError(CE_Failure, CPLE_OpenFailed,
270
0
                 "%s layout not supported. Abort\n\n", value);
271
0
        return nullptr;
272
0
    }
273
274
    /***********   Grab samples lines band ************/
275
25
    const int nCols = atoi(poDS->GetKeywordSub("QUBE.CORE_ITEMS", 1));
276
25
    const int nRows = atoi(poDS->GetKeywordSub("QUBE.CORE_ITEMS", 2));
277
25
    const int nBands = atoi(poDS->GetKeywordSub("QUBE.CORE_ITEMS", 3));
278
279
    /***********   Grab Qube record bytes  **********/
280
25
    const int record_bytes = atoi(poDS->GetKeyword("RECORD_BYTES"));
281
25
    if (record_bytes < 0)
282
0
    {
283
0
        return nullptr;
284
0
    }
285
286
25
    GUIntBig nSkipBytes = 0;
287
25
    if (nQube > 0 && bByteLocation)
288
0
        nSkipBytes = (nQube - 1);
289
25
    else if (nQube > 0)
290
0
        nSkipBytes = static_cast<GUIntBig>(nQube - 1) * record_bytes;
291
25
    else
292
25
        nSkipBytes = 0;
293
294
    /***********   Grab samples lines band ************/
295
25
    char chByteOrder = 'M';  // default to MSB
296
25
    CPLString osCoreItemType = poDS->GetKeyword("QUBE.CORE_ITEM_TYPE");
297
25
    if ((EQUAL(osCoreItemType, "PC_INTEGER")) ||
298
25
        (EQUAL(osCoreItemType, "PC_UNSIGNED_INTEGER")) ||
299
25
        (EQUAL(osCoreItemType, "PC_REAL")))
300
0
    {
301
0
        chByteOrder = 'I';
302
0
    }
303
304
    /********   Grab format type - isis2 only supports 8,16,32 *******/
305
25
    GDALDataType eDataType = GDT_Byte;
306
25
    bool bNoDataSet = false;
307
25
    double dfNoData = 0.0;
308
309
25
    int itype = atoi(poDS->GetKeyword("QUBE.CORE_ITEM_BYTES", ""));
310
25
    switch (itype)
311
25
    {
312
0
        case 1:
313
0
            eDataType = GDT_Byte;
314
0
            dfNoData = NULL1;
315
0
            bNoDataSet = true;
316
0
            break;
317
0
        case 2:
318
0
            if (strstr(osCoreItemType, "UNSIGNED") != nullptr)
319
0
            {
320
0
                dfNoData = 0;
321
0
                eDataType = GDT_UInt16;
322
0
            }
323
0
            else
324
0
            {
325
0
                dfNoData = NULL2;
326
0
                eDataType = GDT_Int16;
327
0
            }
328
0
            bNoDataSet = true;
329
0
            break;
330
0
        case 4:
331
0
            eDataType = GDT_Float32;
332
0
            dfNoData = NULL3;
333
0
            bNoDataSet = true;
334
0
            break;
335
0
        case 8:
336
0
            eDataType = GDT_Float64;
337
0
            dfNoData = NULL3;
338
0
            bNoDataSet = true;
339
0
            break;
340
25
        default:
341
25
            CPLError(CE_Failure, CPLE_AppDefined,
342
25
                     "Itype of %d is not supported in ISIS 2.", itype);
343
25
            return nullptr;
344
25
    }
345
346
    /***********   Grab Cellsize ************/
347
0
    double dfXDim = 1.0;
348
0
    double dfYDim = 1.0;
349
350
0
    value = poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.MAP_SCALE");
351
0
    if (strlen(value) > 0)
352
0
    {
353
        // Convert km to m
354
0
        dfXDim = static_cast<float>(CPLAtof(value) * 1000.0);
355
0
        dfYDim = static_cast<float>(CPLAtof(value) * 1000.0 * -1);
356
0
    }
357
358
    /***********   Grab LINE_PROJECTION_OFFSET ************/
359
0
    double dfULYMap = 0.5;
360
361
0
    value =
362
0
        poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.LINE_PROJECTION_OFFSET");
363
0
    if (strlen(value) > 0)
364
0
    {
365
0
        const double yulcenter = static_cast<float>(CPLAtof(value)) * dfYDim;
366
0
        dfULYMap = yulcenter - (dfYDim / 2);
367
0
    }
368
369
    /***********   Grab SAMPLE_PROJECTION_OFFSET ************/
370
0
    double dfULXMap = 0.5;
371
372
0
    value =
373
0
        poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.SAMPLE_PROJECTION_OFFSET");
374
0
    if (strlen(value) > 0)
375
0
    {
376
0
        const double xulcenter = static_cast<float>(CPLAtof(value)) * dfXDim;
377
0
        dfULXMap = xulcenter - (dfXDim / 2);
378
0
    }
379
380
    /***********  Grab TARGET_NAME  ************/
381
    /**** This is the planets name i.e. MARS ***/
382
0
    CPLString target_name = poDS->GetKeyword("QUBE.TARGET_NAME");
383
384
    /***********   Grab MAP_PROJECTION_TYPE ************/
385
0
    CPLString map_proj_name =
386
0
        poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.MAP_PROJECTION_TYPE");
387
0
    poDS->CleanString(map_proj_name);
388
389
    /***********   Grab SEMI-MAJOR ************/
390
0
    const double semi_major =
391
0
        CPLAtof(poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.A_AXIS_RADIUS")) *
392
0
        1000.0;
393
394
    /***********   Grab semi-minor ************/
395
0
    const double semi_minor =
396
0
        CPLAtof(poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.C_AXIS_RADIUS")) *
397
0
        1000.0;
398
399
    /***********   Grab CENTER_LAT ************/
400
0
    const double center_lat =
401
0
        CPLAtof(poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.CENTER_LATITUDE"));
402
403
    /***********   Grab CENTER_LON ************/
404
0
    const double center_lon =
405
0
        CPLAtof(poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.CENTER_LONGITUDE"));
406
407
    /***********   Grab 1st std parallel ************/
408
0
    const double first_std_parallel = CPLAtof(
409
0
        poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.FIRST_STANDARD_PARALLEL"));
410
411
    /***********   Grab 2nd std parallel ************/
412
0
    const double second_std_parallel = CPLAtof(
413
0
        poDS->GetKeyword("QUBE.IMAGE_MAP_PROJECTION.SECOND_STANDARD_PARALLEL"));
414
415
    /*** grab  PROJECTION_LATITUDE_TYPE = "PLANETOCENTRIC" ****/
416
    // Need to further study how ocentric/ographic will effect the gdal library.
417
    // So far we will use this fact to define a sphere or ellipse for some
418
    // projections Frank - may need to talk this over
419
0
    bool bIsGeographic = true;
420
0
    value =
421
0
        poDS->GetKeyword("CUBE.IMAGE_MAP_PROJECTION.PROJECTION_LATITUDE_TYPE");
422
0
    if (EQUAL(value, "\"PLANETOCENTRIC\""))
423
0
        bIsGeographic = false;
424
425
0
    CPLDebug("ISIS2", "using projection %s", map_proj_name.c_str());
426
427
0
    OGRSpatialReference oSRS;
428
0
    oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
429
0
    bool bProjectionSet = true;
430
431
    // Set oSRS projection and parameters
432
0
    if ((EQUAL(map_proj_name, "EQUIRECTANGULAR_CYLINDRICAL")) ||
433
0
        (EQUAL(map_proj_name, "EQUIRECTANGULAR")) ||
434
0
        (EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")))
435
0
    {
436
0
        oSRS.OGRSpatialReference::SetEquirectangular2(0.0, center_lon,
437
0
                                                      center_lat, 0, 0);
438
0
    }
439
0
    else if (EQUAL(map_proj_name, "ORTHOGRAPHIC"))
440
0
    {
441
0
        oSRS.OGRSpatialReference::SetOrthographic(center_lat, center_lon, 0, 0);
442
0
    }
443
0
    else if ((EQUAL(map_proj_name, "SINUSOIDAL")) ||
444
0
             (EQUAL(map_proj_name, "SINUSOIDAL_EQUAL-AREA")))
445
0
    {
446
0
        oSRS.OGRSpatialReference::SetSinusoidal(center_lon, 0, 0);
447
0
    }
448
0
    else if (EQUAL(map_proj_name, "MERCATOR"))
449
0
    {
450
0
        oSRS.OGRSpatialReference::SetMercator(center_lat, center_lon, 1, 0, 0);
451
0
    }
452
0
    else if (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC"))
453
0
    {
454
0
        oSRS.OGRSpatialReference::SetPS(center_lat, center_lon, 1, 0, 0);
455
0
    }
456
0
    else if (EQUAL(map_proj_name, "TRANSVERSE_MERCATOR"))
457
0
    {
458
0
        oSRS.OGRSpatialReference::SetTM(center_lat, center_lon, 1, 0, 0);
459
0
    }
460
0
    else if (EQUAL(map_proj_name, "LAMBERT_CONFORMAL_CONIC"))
461
0
    {
462
0
        oSRS.OGRSpatialReference::SetLCC(first_std_parallel,
463
0
                                         second_std_parallel, center_lat,
464
0
                                         center_lon, 0, 0);
465
0
    }
466
0
    else if (EQUAL(map_proj_name, ""))
467
0
    {
468
        /* no projection */
469
0
        bProjectionSet = false;
470
0
    }
471
0
    else
472
0
    {
473
0
        CPLDebug("ISIS2",
474
0
                 "Dataset projection %s is not supported. Continuing...",
475
0
                 map_proj_name.c_str());
476
0
        bProjectionSet = false;
477
0
    }
478
479
0
    if (bProjectionSet)
480
0
    {
481
        // Create projection name, i.e. MERCATOR MARS and set as ProjCS keyword
482
0
        const CPLString proj_target_name = map_proj_name + " " + target_name;
483
0
        oSRS.SetProjCS(proj_target_name);  // set ProjCS keyword
484
485
        // The geographic/geocentric name will be the same basic name as the
486
        // body name 'GCS' = Geographic/Geocentric Coordinate System
487
0
        const CPLString geog_name = "GCS_" + target_name;
488
489
        // The datum and sphere names will be the same basic name aas the planet
490
0
        const CPLString datum_name = "D_" + target_name;
491
492
0
        CPLString sphere_name = std::move(target_name);
493
494
        // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
495
0
        double iflattening = 0.0;
496
0
        if ((semi_major - semi_minor) < 0.0000001)
497
0
            iflattening = 0;
498
0
        else
499
0
            iflattening = semi_major / (semi_major - semi_minor);
500
501
        // Set the body size but take into consideration which proj is being
502
        // used to help w/ proj4 compatibility The use of a Sphere, polar radius
503
        // or ellipse here is based on how ISIS does it internally
504
0
        if (((EQUAL(map_proj_name, "STEREOGRAPHIC") &&
505
0
              (fabs(center_lat) == 90))) ||
506
0
            (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC")))
507
0
        {
508
0
            if (bIsGeographic)
509
0
            {
510
                // Geograpraphic, so set an ellipse
511
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
512
0
                               iflattening, "Reference_Meridian", 0.0);
513
0
            }
514
0
            else
515
0
            {
516
                // Geocentric, so force a sphere using the semi-minor axis. I
517
                // hope...
518
0
                sphere_name += "_polarRadius";
519
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_minor,
520
0
                               0.0, "Reference_Meridian", 0.0);
521
0
            }
522
0
        }
523
0
        else if ((EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
524
0
                 (EQUAL(map_proj_name, "ORTHOGRAPHIC")) ||
525
0
                 (EQUAL(map_proj_name, "STEREOGRAPHIC")) ||
526
0
                 (EQUAL(map_proj_name, "SINUSOIDAL_EQUAL-AREA")) ||
527
0
                 (EQUAL(map_proj_name, "SINUSOIDAL")))
528
0
        {
529
            // ISIS uses the spherical equation for these projections so force
530
            // a sphere.
531
0
            oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
532
0
                           "Reference_Meridian", 0.0);
533
0
        }
534
0
        else if ((EQUAL(map_proj_name, "EQUIRECTANGULAR_CYLINDRICAL")) ||
535
0
                 (EQUAL(map_proj_name, "EQUIRECTANGULAR")))
536
0
        {
537
            // Calculate localRadius using ISIS3 simple elliptical method
538
            //   not the more standard Radius of Curvature method
539
            // PI = 4 * atan(1);
540
0
            if (center_lon == 0)
541
0
            {  // No need to calculate local radius
542
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
543
0
                               0.0, "Reference_Meridian", 0.0);
544
0
            }
545
0
            else
546
0
            {
547
0
                const double radLat = center_lat * M_PI / 180;  // in radians
548
0
                const double localRadius =
549
0
                    semi_major * semi_minor /
550
0
                    sqrt(pow(semi_minor * cos(radLat), 2) +
551
0
                         pow(semi_major * sin(radLat), 2));
552
0
                sphere_name += "_localRadius";
553
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, localRadius,
554
0
                               0.0, "Reference_Meridian", 0.0);
555
0
                CPLDebug("ISIS2", "local radius: %f", localRadius);
556
0
            }
557
0
        }
558
0
        else
559
0
        {
560
            // All other projections: Mercator, Transverse Mercator, Lambert
561
            // Conformal, etc. Geographic, so set an ellipse
562
0
            if (bIsGeographic)
563
0
            {
564
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
565
0
                               iflattening, "Reference_Meridian", 0.0);
566
0
            }
567
0
            else
568
0
            {
569
                // Geocentric, so force a sphere. I hope...
570
0
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
571
0
                               0.0, "Reference_Meridian", 0.0);
572
0
            }
573
0
        }
574
575
        // translate back into a projection string.
576
0
        poDS->m_oSRS = std::move(oSRS);
577
0
    }
578
579
    /* END ISIS2 Label Read */
580
    /*++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++*/
581
582
    /* -------------------------------------------------------------------- */
583
    /*      Did we get the required keywords?  If not we return with        */
584
    /*      this never having been considered to be a match. This isn't     */
585
    /*      an error!                                                       */
586
    /* -------------------------------------------------------------------- */
587
0
    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
588
0
        !GDALCheckBandCount(nBands, false))
589
0
    {
590
0
        return nullptr;
591
0
    }
592
593
    /* -------------------------------------------------------------------- */
594
    /*      Capture some information from the file that is of interest.     */
595
    /* -------------------------------------------------------------------- */
596
0
    poDS->nRasterXSize = nCols;
597
0
    poDS->nRasterYSize = nRows;
598
599
    /* -------------------------------------------------------------------- */
600
    /*      Open target binary file.                                        */
601
    /* -------------------------------------------------------------------- */
602
603
0
    if (poOpenInfo->eAccess == GA_ReadOnly)
604
0
        poDS->fpImage = VSIFOpenL(osTargetFile, "rb");
605
0
    else
606
0
        poDS->fpImage = VSIFOpenL(osTargetFile, "r+b");
607
608
0
    if (poDS->fpImage == nullptr)
609
0
    {
610
0
        CPLError(CE_Failure, CPLE_OpenFailed,
611
0
                 "Failed to open %s with write permission.\n%s",
612
0
                 osTargetFile.c_str(), VSIStrerror(errno));
613
0
        return nullptr;
614
0
    }
615
616
0
    poDS->eAccess = poOpenInfo->eAccess;
617
618
    /* -------------------------------------------------------------------- */
619
    /*      Compute the line offset.                                        */
620
    /* -------------------------------------------------------------------- */
621
0
    int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
622
0
    int nLineOffset, nPixelOffset;
623
0
    vsi_l_offset nBandOffset;
624
625
0
    if (EQUAL(szLayout, "BIP"))
626
0
    {
627
0
        nPixelOffset = nItemSize * nBands;
628
0
        if (nPixelOffset > INT_MAX / nBands)
629
0
        {
630
0
            return nullptr;
631
0
        }
632
0
        nLineOffset = nPixelOffset * nCols;
633
0
        nBandOffset = nItemSize;
634
0
    }
635
0
    else if (EQUAL(szLayout, "BSQ"))
636
0
    {
637
0
        nPixelOffset = nItemSize;
638
0
        if (nPixelOffset > INT_MAX / nCols)
639
0
        {
640
0
            return nullptr;
641
0
        }
642
0
        nLineOffset = nPixelOffset * nCols;
643
0
        nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nRows;
644
0
    }
645
0
    else /* assume BIL */
646
0
    {
647
0
        nPixelOffset = nItemSize;
648
0
        if (nPixelOffset > INT_MAX / nBands ||
649
0
            nPixelOffset * nBands > INT_MAX / nCols)
650
0
        {
651
0
            return nullptr;
652
0
        }
653
0
        nLineOffset = nItemSize * nBands * nCols;
654
0
        nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nCols;
655
0
    }
656
657
    /* -------------------------------------------------------------------- */
658
    /*      Create band information objects.                                */
659
    /* -------------------------------------------------------------------- */
660
0
    for (int i = 0; i < nBands; i++)
661
0
    {
662
0
        auto poBand = RawRasterBand::Create(
663
0
            poDS.get(), i + 1, poDS->fpImage, nSkipBytes + nBandOffset * i,
664
0
            nPixelOffset, nLineOffset, eDataType,
665
0
            chByteOrder == 'I' || chByteOrder == 'L'
666
0
                ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
667
0
                : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
668
0
            RawRasterBand::OwnFP::NO);
669
0
        if (!poBand)
670
0
            return nullptr;
671
672
0
        if (bNoDataSet)
673
0
            poBand->SetNoDataValue(dfNoData);
674
675
        // Set offset/scale values at the PAM level.
676
0
        poBand->SetOffset(CPLAtofM(poDS->GetKeyword("QUBE.CORE_BASE", "0.0")));
677
0
        poBand->SetScale(
678
0
            CPLAtofM(poDS->GetKeyword("QUBE.CORE_MULTIPLIER", "1.0")));
679
680
0
        poDS->SetBand(i + 1, std::move(poBand));
681
0
    }
682
683
    /* -------------------------------------------------------------------- */
684
    /*      Check for a .prj file. For isis2 I would like to keep this in   */
685
    /* -------------------------------------------------------------------- */
686
0
    const CPLString osPath = CPLGetPathSafe(poOpenInfo->pszFilename);
687
0
    const CPLString osName = CPLGetBasenameSafe(poOpenInfo->pszFilename);
688
0
    const std::string osPrjFile = CPLFormCIFilenameSafe(osPath, osName, "prj");
689
690
0
    VSILFILE *fp = VSIFOpenL(osPrjFile.c_str(), "r");
691
0
    if (fp != nullptr)
692
0
    {
693
0
        VSIFCloseL(fp);
694
695
0
        char **papszLines = CSLLoad(osPrjFile.c_str());
696
697
0
        poDS->m_oSRS.importFromESRI(papszLines);
698
699
0
        CSLDestroy(papszLines);
700
0
    }
701
702
0
    if (dfULXMap != 0.5 || dfULYMap != 0.5 || dfXDim != 1.0 || dfYDim != 1.0)
703
0
    {
704
0
        poDS->bGotTransform = TRUE;
705
0
        poDS->adfGeoTransform[0] = dfULXMap;
706
0
        poDS->adfGeoTransform[1] = dfXDim;
707
0
        poDS->adfGeoTransform[2] = 0.0;
708
0
        poDS->adfGeoTransform[3] = dfULYMap;
709
0
        poDS->adfGeoTransform[4] = 0.0;
710
0
        poDS->adfGeoTransform[5] = dfYDim;
711
0
    }
712
713
0
    if (!poDS->bGotTransform)
714
0
        poDS->bGotTransform = GDALReadWorldFile(poOpenInfo->pszFilename, "cbw",
715
0
                                                poDS->adfGeoTransform);
716
717
0
    if (!poDS->bGotTransform)
718
0
        poDS->bGotTransform = GDALReadWorldFile(poOpenInfo->pszFilename, "wld",
719
0
                                                poDS->adfGeoTransform);
720
721
    /* -------------------------------------------------------------------- */
722
    /*      Initialize any PAM information.                                 */
723
    /* -------------------------------------------------------------------- */
724
0
    poDS->SetDescription(poOpenInfo->pszFilename);
725
0
    poDS->TryLoadXML();
726
727
    /* -------------------------------------------------------------------- */
728
    /*      Check for overviews.                                            */
729
    /* -------------------------------------------------------------------- */
730
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
731
732
0
    return poDS.release();
733
0
}
734
735
/************************************************************************/
736
/*                             GetKeyword()                             */
737
/************************************************************************/
738
739
const char *ISIS2Dataset::GetKeyword(const char *pszPath,
740
                                     const char *pszDefault)
741
742
125
{
743
125
    return oKeywords.GetKeyword(pszPath, pszDefault);
744
125
}
745
746
/************************************************************************/
747
/*                            GetKeywordSub()                           */
748
/************************************************************************/
749
750
const char *ISIS2Dataset::GetKeywordSub(const char *pszPath, int iSubscript,
751
                                        const char *pszDefault)
752
753
150
{
754
150
    const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
755
756
150
    if (pszResult == nullptr)
757
150
        return pszDefault;
758
759
0
    if (pszResult[0] != '(')
760
0
        return pszDefault;
761
762
0
    char **papszTokens =
763
0
        CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
764
765
0
    if (iSubscript <= CSLCount(papszTokens))
766
0
    {
767
0
        oTempResult = papszTokens[iSubscript - 1];
768
0
        CSLDestroy(papszTokens);
769
0
        return oTempResult.c_str();
770
0
    }
771
772
0
    CSLDestroy(papszTokens);
773
0
    return pszDefault;
774
0
}
775
776
/************************************************************************/
777
/*                            CleanString()                             */
778
/*                                                                      */
779
/* Removes single or double quotes, and converts spaces to underscores. */
780
/* The change is made in-place to CPLString.                            */
781
/************************************************************************/
782
783
void ISIS2Dataset::CleanString(CPLString &osInput)
784
785
0
{
786
0
    if ((osInput.size() < 2) ||
787
0
        ((osInput.at(0) != '"' || osInput.back() != '"') &&
788
0
         (osInput.at(0) != '\'' || osInput.back() != '\'')))
789
0
        return;
790
791
0
    char *pszWrk = CPLStrdup(osInput.c_str() + 1);
792
793
0
    pszWrk[strlen(pszWrk) - 1] = '\0';
794
795
0
    for (int i = 0; pszWrk[i] != '\0'; i++)
796
0
    {
797
0
        if (pszWrk[i] == ' ')
798
0
            pszWrk[i] = '_';
799
0
    }
800
801
0
    osInput = pszWrk;
802
0
    CPLFree(pszWrk);
803
0
}
804
805
/************************************************************************/
806
/*                         GDALRegister_ISIS2()                         */
807
/************************************************************************/
808
809
void GDALRegister_ISIS2()
810
811
2
{
812
2
    if (GDALGetDriverByName(ISIS2_DRIVER_NAME) != nullptr)
813
0
        return;
814
815
2
    GDALDriver *poDriver = new GDALDriver();
816
2
    ISIS2DriverSetCommonMetadata(poDriver);
817
818
2
    poDriver->pfnOpen = ISIS2Dataset::Open;
819
820
2
    GetGDALDriverManager()->RegisterDriver(poDriver);
821
2
}