LCOV - code coverage report
Current view: top level - frmts/pds - pdsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 520 670 77.6 %
Date: 2026-04-15 22:10:00 Functions: 26 29 89.7 %

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

Generated by: LCOV version 1.14