LCOV - code coverage report
Current view: top level - frmts/pds - pdsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 510 656 77.7 %
Date: 2024-11-21 22:18:42 Functions: 25 28 89.3 %

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

Generated by: LCOV version 1.14