LCOV - code coverage report
Current view: top level - frmts/pds - pdsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 522 672 77.7 %
Date: 2026-05-08 18:52:02 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 =
     731          12 :             CPL_TO_BOOL(GDALReadWorldFile(pszFilename, "psw", m_gt.data()));
     732             : 
     733          37 :     if (!bGotTransform)
     734          12 :         bGotTransform =
     735          12 :             CPL_TO_BOOL(GDALReadWorldFile(pszFilename, "wld", m_gt.data()));
     736          37 : }
     737             : 
     738             : /************************************************************************/
     739             : /*                         GetRawBinaryLayout()                         */
     740             : /************************************************************************/
     741             : 
     742           2 : bool PDSDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
     743             : {
     744           2 :     if (!RawDataset::GetRawBinaryLayout(sLayout))
     745           0 :         return false;
     746           2 :     sLayout.osRawFilename = m_osImageFilename;
     747           2 :     return true;
     748             : }
     749             : 
     750             : /************************************************************************/
     751             : /*                         PDSConvertFromHex()                          */
     752             : /************************************************************************/
     753             : 
     754           3 : static GUInt32 PDSConvertFromHex(const char *pszVal)
     755             : {
     756           3 :     if (!STARTS_WITH_CI(pszVal, "16#"))
     757           0 :         return 0;
     758             : 
     759           3 :     pszVal += 3;
     760           3 :     GUInt32 nVal = 0;
     761          27 :     while (*pszVal != '#' && *pszVal != '\0')
     762             :     {
     763          24 :         nVal <<= 4;
     764          24 :         if (*pszVal >= '0' && *pszVal <= '9')
     765           3 :             nVal += *pszVal - '0';
     766          21 :         else if (*pszVal >= 'A' && *pszVal <= 'F')
     767          21 :             nVal += *pszVal - 'A' + 10;
     768             :         else
     769           0 :             return 0;
     770          24 :         pszVal++;
     771             :     }
     772             : 
     773           3 :     return nVal;
     774             : }
     775             : 
     776             : /************************************************************************/
     777             : /*                             ParseImage()                             */
     778             : /************************************************************************/
     779             : 
     780          33 : int PDSDataset::ParseImage(const CPLString &osPrefix,
     781             :                            const CPLString &osFilenamePrefix)
     782             : {
     783             :     /* ------------------------------------------------------------------- */
     784             :     /*      We assume the user is pointing to the label (i.e. .lbl) file.  */
     785             :     /* ------------------------------------------------------------------- */
     786             :     // IMAGE can be inline or detached and point to an image name
     787             :     // ^IMAGE = 3
     788             :     // ^IMAGE             = "GLOBAL_ALBEDO_8PPD.IMG"
     789             :     // ^IMAGE             = "MEGT90N000CB.IMG"
     790             :     // ^IMAGE             = ("FOO.IMG",1)       -- start at record 1 (1 based)
     791             :     // ^IMAGE             = ("FOO.IMG")         -- start at record 1 equiv of
     792             :     // ("FOO.IMG",1) ^IMAGE             = ("FOO.IMG", 5 <BYTES>) -- start at
     793             :     // byte 5 (the fifth byte in the file) ^IMAGE             = 10851 <BYTES>
     794             :     // ^SPECTRAL_QUBE = 5  for multi-band images
     795             :     // ^QUBE = 5  for multi-band images
     796             : 
     797          66 :     CPLString osImageKeyword = "IMAGE";
     798          99 :     CPLString osQube = GetKeyword(osPrefix + "^" + osImageKeyword, "");
     799          33 :     m_osImageFilename = GetDescription();
     800             : 
     801          33 :     if (EQUAL(osQube, ""))
     802             :     {
     803           0 :         osImageKeyword = "SPECTRAL_QUBE";
     804           0 :         osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
     805             :     }
     806             : 
     807          33 :     if (EQUAL(osQube, ""))
     808             :     {
     809           0 :         osImageKeyword = "QUBE";
     810           0 :         osQube = GetKeyword(osPrefix + "^" + osImageKeyword);
     811             :     }
     812             : 
     813          33 :     const int nQube = atoi(osQube);
     814          33 :     int nDetachedOffset = 0;
     815          33 :     bool bDetachedOffsetInBytes = false;
     816             : 
     817          33 :     if (!osQube.empty() && osQube[0] == '(')
     818             :     {
     819           9 :         osQube = "\"";
     820           9 :         osQube += GetKeywordSub(osPrefix + "^" + osImageKeyword, 1);
     821           9 :         osQube += "\"";
     822             :         nDetachedOffset =
     823           9 :             atoi(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2, "1"));
     824           9 :         if (nDetachedOffset >= 1)
     825           9 :             nDetachedOffset -= 1;
     826             : 
     827             :         // If this is not explicitly in bytes, then it is assumed to be in
     828             :         // records, and we need to translate to bytes.
     829          18 :         if (strstr(GetKeywordSub(osPrefix + "^" + osImageKeyword, 2),
     830           9 :                    "<BYTES>") != nullptr)
     831           2 :             bDetachedOffsetInBytes = true;
     832             :     }
     833             : 
     834          33 :     if (!osQube.empty() && osQube[0] == '"')
     835             :     {
     836          13 :         const CPLString osFilename = CleanString(osQube);
     837          13 :         if (CPLHasPathTraversal(osFilename.c_str()))
     838             :         {
     839           0 :             CPLError(CE_Failure, CPLE_NotSupported,
     840             :                      "Path traversal detected in %s", osFilename.c_str());
     841           0 :             return false;
     842             :         }
     843          13 :         if (!osFilenamePrefix.empty())
     844             :         {
     845           3 :             m_osImageFilename = osFilenamePrefix + osFilename;
     846             :         }
     847             :         else
     848             :         {
     849          20 :             CPLString osTPath = CPLGetPathSafe(GetDescription());
     850             :             m_osImageFilename =
     851          10 :                 CPLFormCIFilenameSafe(osTPath, osFilename, nullptr);
     852          10 :             osExternalCube = m_osImageFilename;
     853             :         }
     854             :     }
     855             : 
     856             :     /* -------------------------------------------------------------------- */
     857             :     /*      Checks to see if this is raw PDS image not compressed image     */
     858             :     /*      so ENCODING_TYPE either does not exist or it equals "N/A".      */
     859             :     /*      or "DCT_DECOMPRESSED".                                          */
     860             :     /*      Compressed types will not be supported in this routine          */
     861             :     /* -------------------------------------------------------------------- */
     862             : 
     863             :     const CPLString osEncodingType =
     864          99 :         CleanString(GetKeyword(osPrefix + "IMAGE.ENCODING_TYPE", "N/A"));
     865          33 :     if (!EQUAL(osEncodingType, "N/A") &&
     866           0 :         !EQUAL(osEncodingType, "DCT_DECOMPRESSED"))
     867             :     {
     868           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     869             :                  "*** PDS image file has an ENCODING_TYPE parameter:\n"
     870             :                  "*** GDAL PDS driver does not support compressed image types\n"
     871             :                  "found: (%s)\n\n",
     872             :                  osEncodingType.c_str());
     873           0 :         return FALSE;
     874             :     }
     875             :     /**************** end ENCODING_TYPE check ***********************/
     876             : 
     877             :     /***********   Grab layout type (BSQ, BIP, BIL) ************/
     878             :     //  AXIS_NAME = (SAMPLE,LINE,BAND)
     879             :     /***********   Grab samples lines band        **************/
     880             :     /** if AXIS_NAME = "" then Bands=1 and Sample and Lines   **/
     881             :     /** are there own keywords  "LINES" and "LINE_SAMPLES"    **/
     882             :     /** if not NULL then CORE_ITEMS keyword i.e. (234,322,2)  **/
     883             :     /***********************************************************/
     884          33 :     int eLayout = PDS_BSQ;  // default to band seq.
     885          33 :     int nRows, nCols, l_nBands = 1;
     886             : 
     887          99 :     CPLString value = GetKeyword(osPrefix + osImageKeyword + ".AXIS_NAME", "");
     888          33 :     if (EQUAL(value, "(SAMPLE,LINE,BAND)"))
     889             :     {
     890           0 :         eLayout = PDS_BSQ;
     891             :         nCols =
     892           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
     893             :         nRows =
     894           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
     895             :         l_nBands =
     896           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
     897             :     }
     898          33 :     else if (EQUAL(value, "(BAND,LINE,SAMPLE)"))
     899             :     {
     900           0 :         eLayout = PDS_BIP;
     901             :         l_nBands =
     902           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
     903             :         nRows =
     904           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
     905             :         nCols =
     906           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
     907             :     }
     908          33 :     else if (EQUAL(value, "(SAMPLE,BAND,LINE)"))
     909             :     {
     910           0 :         eLayout = PDS_BIL;
     911             :         nCols =
     912           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 1));
     913             :         l_nBands =
     914           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 2));
     915             :         nRows =
     916           0 :             atoi(GetKeywordSub(osPrefix + osImageKeyword + ".CORE_ITEMS", 3));
     917             :     }
     918          33 :     else if (EQUAL(value, ""))
     919             :     {
     920          33 :         eLayout = PDS_BSQ;
     921             :         nCols =
     922          33 :             atoi(GetKeyword(osPrefix + osImageKeyword + ".LINE_SAMPLES", ""));
     923          33 :         nRows = atoi(GetKeyword(osPrefix + osImageKeyword + ".LINES", ""));
     924          33 :         l_nBands = atoi(GetKeyword(osPrefix + osImageKeyword + ".BANDS", "1"));
     925             :     }
     926             :     else
     927             :     {
     928           0 :         CPLError(CE_Failure, CPLE_OpenFailed,
     929             :                  "%s layout not supported. Abort\n\n", value.c_str());
     930           0 :         return FALSE;
     931             :     }
     932             : 
     933             :     CPLString osBAND_STORAGE_TYPE =
     934          66 :         GetKeyword(osPrefix + "IMAGE.BAND_STORAGE_TYPE", "");
     935          33 :     if (EQUAL(osBAND_STORAGE_TYPE, "BAND_SEQUENTIAL"))
     936             :     {
     937          12 :         eLayout = PDS_BSQ;
     938             :     }
     939          21 :     else if (EQUAL(osBAND_STORAGE_TYPE, "PIXEL_INTERLEAVED"))
     940             :     {
     941           0 :         eLayout = PDS_BIP;
     942             :     }
     943          21 :     else if (EQUAL(osBAND_STORAGE_TYPE, "LINE_INTERLEAVED"))
     944             :     {
     945           2 :         eLayout = PDS_BIL;
     946             :     }
     947          19 :     else if (!osBAND_STORAGE_TYPE.empty())
     948             :     {
     949           4 :         CPLDebug("PDS", "Unhandled BAND_STORAGE_TYPE = %s",
     950             :                  osBAND_STORAGE_TYPE.c_str());
     951             :     }
     952             : 
     953             :     /***********   Grab Qube record bytes  **********/
     954          33 :     int record_bytes = atoi(GetKeyword(osPrefix + "IMAGE.RECORD_BYTES"));
     955          33 :     if (record_bytes == 0)
     956          33 :         record_bytes = atoi(GetKeyword(osPrefix + "RECORD_BYTES"));
     957             : 
     958             :     // this can happen with "record_type = undefined".
     959          33 :     if (record_bytes < 0)
     960           0 :         return FALSE;
     961          33 :     if (record_bytes == 0)
     962           3 :         record_bytes = 1;
     963             : 
     964          33 :     int nSkipBytes = 0;
     965             :     try
     966             :     {
     967          33 :         if (nQube > 0)
     968             :         {
     969          20 :             if (osQube.find("<BYTES>") != CPLString::npos)
     970           3 :                 nSkipBytes = (CPLSM(nQube) - CPLSM(1)).v();
     971             :             else
     972          17 :                 nSkipBytes = (CPLSM(nQube - 1) * CPLSM(record_bytes)).v();
     973             :         }
     974          13 :         else if (nDetachedOffset > 0)
     975             :         {
     976           4 :             if (bDetachedOffsetInBytes)
     977           2 :                 nSkipBytes = nDetachedOffset;
     978             :             else
     979             :             {
     980           2 :                 nSkipBytes = (CPLSM(nDetachedOffset) * CPLSM(record_bytes)).v();
     981             :             }
     982             :         }
     983             :         else
     984           9 :             nSkipBytes = 0;
     985             :     }
     986           0 :     catch (const CPLSafeIntOverflow &)
     987             :     {
     988           0 :         return FALSE;
     989             :     }
     990             : 
     991             :     const int nLinePrefixBytes =
     992          33 :         atoi(GetKeyword(osPrefix + "IMAGE.LINE_PREFIX_BYTES", ""));
     993          33 :     if (nLinePrefixBytes < 0)
     994           0 :         return false;
     995          33 :     nSkipBytes += nLinePrefixBytes;
     996             : 
     997             :     /***********   Grab SAMPLE_TYPE *****************/
     998             :     /** if keyword not found leave as "M" or "MSB" **/
     999             : 
    1000          66 :     CPLString osST = GetKeyword(osPrefix + "IMAGE.SAMPLE_TYPE");
    1001          33 :     if (osST.size() >= 2 && osST[0] == '"' && osST.back() == '"')
    1002           4 :         osST = osST.substr(1, osST.size() - 2);
    1003             : 
    1004          33 :     char chByteOrder = 'M';  // default to MSB
    1005          64 :     if ((EQUAL(osST, "LSB_INTEGER")) || (EQUAL(osST, "LSB")) ||  // just in case
    1006          31 :         (EQUAL(osST, "LSB_UNSIGNED_INTEGER")) ||
    1007          27 :         (EQUAL(osST, "LSB_SIGNED_INTEGER")) ||
    1008          27 :         (EQUAL(osST, "UNSIGNED_INTEGER")) || (EQUAL(osST, "VAX_REAL")) ||
    1009          12 :         (EQUAL(osST, "VAX_INTEGER")) ||
    1010          76 :         (EQUAL(osST, "PC_INTEGER")) ||  // just in case
    1011          12 :         (EQUAL(osST, "PC_REAL")))
    1012             :     {
    1013          26 :         chByteOrder = 'I';
    1014             :     }
    1015             : 
    1016             :     /**** Grab format type - pds supports 1,2,4,8,16,32,64 (in theory) **/
    1017             :     /**** I have only seen 8, 16, 32 (float) in released datasets      **/
    1018          33 :     GDALDataType eDataType = GDT_UInt8;
    1019          33 :     int nSuffixItems = 0;
    1020          33 :     int nSuffixLines = 0;
    1021          33 :     int nSuffixBytes = 4;  // Default as per PDS specification
    1022          33 :     double dfNoData = 0.0;
    1023          33 :     double dfScale = 1.0;
    1024          33 :     double dfOffset = 0.0;
    1025          33 :     const char *pszUnit = nullptr;
    1026          33 :     const char *pszDesc = nullptr;
    1027             : 
    1028          66 :     CPLString osSB = GetKeyword(osPrefix + "IMAGE.SAMPLE_BITS", "");
    1029          33 :     if (!osSB.empty())
    1030             :     {
    1031          33 :         const int itype = atoi(osSB);
    1032          33 :         switch (itype)
    1033             :         {
    1034          23 :             case 8:
    1035          23 :                 eDataType = GDT_UInt8;
    1036          23 :                 dfNoData = PDS_NULL1;
    1037          23 :                 break;
    1038           5 :             case 16:
    1039           5 :                 if (strstr(osST, "UNSIGNED") != nullptr)
    1040             :                 {
    1041           3 :                     dfNoData = PDS_NULL1;
    1042           3 :                     eDataType = GDT_UInt16;
    1043             :                 }
    1044             :                 else
    1045             :                 {
    1046           2 :                     eDataType = GDT_Int16;
    1047           2 :                     dfNoData = PDS_NULL2;
    1048             :                 }
    1049           5 :                 break;
    1050           5 :             case 32:
    1051           5 :                 eDataType = GDT_Float32;
    1052           5 :                 dfNoData = PDS_NULL3;
    1053           5 :                 break;
    1054           0 :             case 64:
    1055           0 :                 eDataType = GDT_Float64;
    1056           0 :                 dfNoData = PDS_NULL3;
    1057           0 :                 break;
    1058           0 :             default:
    1059           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1060             :                          "Sample_bits of %d is not supported in this gdal PDS "
    1061             :                          "reader.",
    1062             :                          itype);
    1063           0 :                 return FALSE;
    1064             :         }
    1065             : 
    1066          33 :         dfOffset = CPLAtofM(GetKeyword(osPrefix + "IMAGE.OFFSET", "0.0"));
    1067             :         dfScale =
    1068          33 :             CPLAtofM(GetKeyword(osPrefix + "IMAGE.SCALING_FACTOR", "1.0"));
    1069             :     }
    1070             :     else /* No IMAGE object, search for the QUBE. */
    1071             :     {
    1072           0 :         osSB = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_ITEM_BYTES", "");
    1073           0 :         const int itype = atoi(osSB);
    1074           0 :         switch (itype)
    1075             :         {
    1076           0 :             case 1:
    1077           0 :                 eDataType = GDT_UInt8;
    1078           0 :                 break;
    1079           0 :             case 2:
    1080           0 :                 if (strstr(osST, "UNSIGNED") != nullptr)
    1081           0 :                     eDataType = GDT_UInt16;
    1082             :                 else
    1083           0 :                     eDataType = GDT_Int16;
    1084           0 :                 break;
    1085           0 :             case 4:
    1086           0 :                 eDataType = GDT_Float32;
    1087           0 :                 break;
    1088           0 :             default:
    1089           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    1090             :                          "CORE_ITEM_BYTES of %d is not supported in this gdal "
    1091             :                          "PDS reader.",
    1092             :                          itype);
    1093           0 :                 return FALSE;
    1094             :         }
    1095             : 
    1096             :         /* Parse suffix dimensions if defined. */
    1097           0 :         value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", "");
    1098           0 :         if (!value.empty())
    1099             :         {
    1100           0 :             value = GetKeyword(osPrefix + "SPECTRAL_QUBE.SUFFIX_BYTES", "");
    1101           0 :             if (!value.empty())
    1102           0 :                 nSuffixBytes = atoi(value);
    1103             : 
    1104             :             nSuffixItems =
    1105           0 :                 atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 1));
    1106             :             nSuffixLines =
    1107           0 :                 atoi(GetKeywordSub(osPrefix + "SPECTRAL_QUBE.SUFFIX_ITEMS", 2));
    1108             :         }
    1109             : 
    1110           0 :         value = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NULL", "");
    1111           0 :         if (!value.empty())
    1112           0 :             dfNoData = CPLAtofM(value);
    1113             : 
    1114             :         dfOffset =
    1115           0 :             CPLAtofM(GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_BASE", "0.0"));
    1116           0 :         dfScale = CPLAtofM(
    1117           0 :             GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_MULTIPLIER", "1.0"));
    1118           0 :         pszUnit = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_UNIT", nullptr);
    1119           0 :         pszDesc = GetKeyword(osPrefix + "SPECTRAL_QUBE.CORE_NAME", nullptr);
    1120             :     }
    1121             : 
    1122             :     /* -------------------------------------------------------------------- */
    1123             :     /*      Is there a specific nodata value in the file? Either the        */
    1124             :     /*      MISSING or MISSING_CONSTANT keywords are nodata.                */
    1125             :     /* -------------------------------------------------------------------- */
    1126             : 
    1127          33 :     const char *pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING", nullptr);
    1128          33 :     if (pszMissing == nullptr)
    1129          30 :         pszMissing = GetKeyword(osPrefix + "IMAGE.MISSING_CONSTANT", nullptr);
    1130             : 
    1131          33 :     if (pszMissing != nullptr)
    1132             :     {
    1133           9 :         if (*pszMissing == '"')
    1134           3 :             pszMissing++;
    1135             : 
    1136             :         /* For example : MISSING_CONSTANT             = "16#FF7FFFFB#" */
    1137           9 :         if (STARTS_WITH_CI(pszMissing, "16#") &&
    1138           3 :             strlen(pszMissing) >= 3 + 8 + 1 && pszMissing[3 + 8] == '#' &&
    1139           0 :             (eDataType == GDT_Float32 || eDataType == GDT_Float64))
    1140             :         {
    1141           3 :             GUInt32 nVal = PDSConvertFromHex(pszMissing);
    1142             :             float fVal;
    1143           3 :             memcpy(&fVal, &nVal, 4);
    1144           3 :             dfNoData = fVal;
    1145             :         }
    1146             :         else
    1147           6 :             dfNoData = CPLAtofM(pszMissing);
    1148             :     }
    1149             : 
    1150             :     /* -------------------------------------------------------------------- */
    1151             :     /*      Did we get the required keywords?  If not we return with        */
    1152             :     /*      this never having been considered to be a match. This isn't     */
    1153             :     /*      an error!                                                       */
    1154             :     /* -------------------------------------------------------------------- */
    1155          66 :     if (!GDALCheckDatasetDimensions(nCols, nRows) ||
    1156          33 :         !GDALCheckBandCount(l_nBands, false))
    1157             :     {
    1158           0 :         return FALSE;
    1159             :     }
    1160             : 
    1161             :     /* -------------------------------------------------------------------- */
    1162             :     /*      Capture some information from the file that is of interest.     */
    1163             :     /* -------------------------------------------------------------------- */
    1164          33 :     nRasterXSize = nCols;
    1165          33 :     nRasterYSize = nRows;
    1166             : 
    1167             :     /* -------------------------------------------------------------------- */
    1168             :     /*      Open target binary file.                                        */
    1169             :     /* -------------------------------------------------------------------- */
    1170             : 
    1171          33 :     if (eAccess == GA_ReadOnly)
    1172             :     {
    1173          33 :         fpImage = VSIFOpenL(m_osImageFilename, "rb");
    1174          33 :         if (fpImage == nullptr)
    1175             :         {
    1176           0 :             CPLError(CE_Failure, CPLE_OpenFailed, "Failed to open %s.\n%s",
    1177           0 :                      m_osImageFilename.c_str(), VSIStrerror(errno));
    1178           0 :             return FALSE;
    1179             :         }
    1180             :     }
    1181             :     else
    1182             :     {
    1183           0 :         fpImage = VSIFOpenL(m_osImageFilename, "r+b");
    1184           0 :         if (fpImage == nullptr)
    1185             :         {
    1186           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    1187             :                      "Failed to open %s with write permission.\n%s",
    1188           0 :                      m_osImageFilename.c_str(), VSIStrerror(errno));
    1189           0 :             return FALSE;
    1190             :         }
    1191             :     }
    1192             : 
    1193             :     /* -------------------------------------------------------------------- */
    1194             :     /*      Compute the line offset.                                        */
    1195             :     /* -------------------------------------------------------------------- */
    1196          33 :     const int nItemSize = GDALGetDataTypeSizeBytes(eDataType);
    1197             : 
    1198             :     // Needed for N1349177584_2.LBL from
    1199             :     // https://trac.osgeo.org/gdal/attachment/ticket/3355/PDS-TestFiles.zip
    1200          33 :     int nLineOffset = nLinePrefixBytes;
    1201             : 
    1202             :     int nPixelOffset;
    1203             :     vsi_l_offset nBandOffset;
    1204             : 
    1205         186 :     const auto CPLSM64 = [](int x) { return CPLSM(static_cast<int64_t>(x)); };
    1206             : 
    1207             :     try
    1208             :     {
    1209          33 :         if (eLayout == PDS_BIP)
    1210             :         {
    1211           0 :             nPixelOffset = (CPLSM(nItemSize) * CPLSM(l_nBands)).v();
    1212           0 :             nBandOffset = nItemSize;
    1213             :             nLineOffset =
    1214           0 :                 (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
    1215             :         }
    1216          33 :         else if (eLayout == PDS_BSQ)
    1217             :         {
    1218          31 :             nPixelOffset = nItemSize;
    1219             :             nLineOffset =
    1220          31 :                 (CPLSM(nLineOffset) + CPLSM(nPixelOffset) * CPLSM(nCols)).v();
    1221          31 :             nBandOffset = static_cast<vsi_l_offset>(
    1222          31 :                 (CPLSM64(nLineOffset) * CPLSM64(nRows) +
    1223          31 :                  CPLSM64(nSuffixLines) *
    1224          62 :                      (CPLSM64(nCols) + CPLSM64(nSuffixItems)) *
    1225         124 :                      CPLSM64(nSuffixBytes))
    1226          31 :                     .v());
    1227             :         }
    1228             :         else /* assume BIL */
    1229             :         {
    1230           2 :             nPixelOffset = nItemSize;
    1231           2 :             nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols)).v();
    1232             :             nLineOffset =
    1233           2 :                 (CPLSM(nLineOffset) +
    1234           6 :                  CPLSM(static_cast<int>(nBandOffset)) * CPLSM(l_nBands))
    1235           2 :                     .v();
    1236             :         }
    1237             :     }
    1238           0 :     catch (const CPLSafeIntOverflow &)
    1239             :     {
    1240           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Integer overflow");
    1241           0 :         return FALSE;
    1242             :     }
    1243             : 
    1244             :     /* -------------------------------------------------------------------- */
    1245             :     /*      Create band information objects.                                */
    1246             :     /* -------------------------------------------------------------------- */
    1247         278 :     for (int i = 0; i < l_nBands; i++)
    1248             :     {
    1249             :         auto poBand = RawRasterBand::Create(
    1250             :             this, i + 1, fpImage,
    1251         245 :             nSkipBytes + static_cast<vsi_l_offset>(nBandOffset) * i,
    1252             :             nPixelOffset, nLineOffset, eDataType,
    1253             :             chByteOrder == 'I' || chByteOrder == 'L'
    1254         245 :                 ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    1255             :                 : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN,
    1256         245 :             RawRasterBand::OwnFP::NO);
    1257         245 :         if (!poBand)
    1258           0 :             return FALSE;
    1259             : 
    1260         245 :         if (l_nBands == 1)
    1261             :         {
    1262             :             const char *pszMin =
    1263          31 :                 GetKeyword(osPrefix + "IMAGE.MINIMUM", nullptr);
    1264             :             const char *pszMax =
    1265          31 :                 GetKeyword(osPrefix + "IMAGE.MAXIMUM", nullptr);
    1266          31 :             const char *pszMean = GetKeyword(osPrefix + "IMAGE.MEAN", nullptr);
    1267             :             const char *pszStdDev =
    1268          31 :                 GetKeyword(osPrefix + "IMAGE.STANDARD_DEVIATION", nullptr);
    1269          31 :             if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
    1270             :                 pszStdDev != nullptr)
    1271             :             {
    1272           0 :                 poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
    1273           0 :                                       CPLAtofM(pszMean), CPLAtofM(pszStdDev));
    1274             :             }
    1275             :         }
    1276             : 
    1277         245 :         poBand->SetNoDataValue(dfNoData);
    1278             : 
    1279             :         // Set offset/scale values at the PAM level.
    1280         245 :         poBand->SetOffset(dfOffset);
    1281         245 :         poBand->SetScale(dfScale);
    1282         245 :         if (pszUnit)
    1283           0 :             poBand->SetUnitType(pszUnit);
    1284         245 :         if (pszDesc)
    1285           0 :             poBand->SetDescription(pszDesc);
    1286             : 
    1287         245 :         SetBand(i + 1, std::move(poBand));
    1288             :     }
    1289             : 
    1290          33 :     return TRUE;
    1291             : }
    1292             : 
    1293             : /************************************************************************/
    1294             : /* ==================================================================== */
    1295             : /*                         PDSWrapperRasterBand                         */
    1296             : /*                                                                      */
    1297             : /*      proxy for the jp2 or other compressed bands.                    */
    1298             : /* ==================================================================== */
    1299             : /************************************************************************/
    1300             : class PDSWrapperRasterBand final : public GDALProxyRasterBand
    1301             : {
    1302             :     GDALRasterBand *poBaseBand{};
    1303             :     double m_dfOffset = 0.0;
    1304             :     double m_dfScale = 1.0;
    1305             :     std::optional<double> m_dfNoData{};
    1306             : 
    1307             :     CPL_DISALLOW_COPY_ASSIGN(PDSWrapperRasterBand)
    1308             : 
    1309             :   protected:
    1310             :     virtual GDALRasterBand *
    1311             :     RefUnderlyingRasterBand(bool /*bForceOpen*/) const override;
    1312             : 
    1313             :   public:
    1314           4 :     PDSWrapperRasterBand(GDALRasterBand *poBaseBandIn, double dfOffset,
    1315             :                          double dfScale, std::optional<double> dfNoData)
    1316           4 :         : m_dfOffset(dfOffset), m_dfScale(dfScale), m_dfNoData(dfNoData)
    1317             :     {
    1318           4 :         this->poBaseBand = poBaseBandIn;
    1319           4 :         eDataType = poBaseBand->GetRasterDataType();
    1320           4 :         poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
    1321           4 :     }
    1322             : 
    1323           1 :     double GetScale(int *pbHasVal) override
    1324             :     {
    1325           1 :         if (pbHasVal)
    1326           1 :             *pbHasVal = m_dfScale != 1.0;
    1327           1 :         return m_dfScale;
    1328             :     }
    1329             : 
    1330           1 :     double GetOffset(int *pbHasVal) override
    1331             :     {
    1332           1 :         if (pbHasVal)
    1333           1 :             *pbHasVal = m_dfOffset != 1.0;
    1334           1 :         return m_dfOffset;
    1335             :     }
    1336             : 
    1337           1 :     double GetNoDataValue(int *pbHasVal) override
    1338             :     {
    1339           1 :         if (pbHasVal)
    1340           1 :             *pbHasVal = m_dfNoData.has_value();
    1341           1 :         return m_dfNoData.has_value() ? m_dfNoData.value() : 0.0;
    1342             :     }
    1343             : };
    1344             : 
    1345             : GDALRasterBand *
    1346           5 : PDSWrapperRasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
    1347             : {
    1348           5 :     return poBaseBand;
    1349             : }
    1350             : 
    1351             : /************************************************************************/
    1352             : /*                        ParseCompressedImage()                        */
    1353             : /************************************************************************/
    1354             : 
    1355           4 : int PDSDataset::ParseCompressedImage()
    1356             : 
    1357             : {
    1358             :     const CPLString osFileName =
    1359          12 :         CleanString(GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
    1360           4 :     if (CPLHasPathTraversal(osFileName.c_str()))
    1361             :     {
    1362           0 :         CPLError(CE_Failure, CPLE_NotSupported, "Path traversal detected in %s",
    1363             :                  osFileName.c_str());
    1364           0 :         return false;
    1365             :     }
    1366             : 
    1367           4 :     double dfOffset = 0;
    1368           4 :     double dfScale = 1;
    1369           4 :     std::optional<double> dfNoData;
    1370             :     const std::string osUncompressedFilename =
    1371          12 :         GetKeyword("COMPRESSED_FILE.UNCOMPRESSED_FILE_NAME", "");
    1372           8 :     if (!osUncompressedFilename.empty() &&
    1373           8 :         GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", "") == osUncompressedFilename)
    1374             : 
    1375             :     {
    1376           4 :         dfOffset = CPLAtof(GetKeyword("UNCOMPRESSED_FILE.IMAGE.OFFSET", "0.0"));
    1377           4 :         dfScale = CPLAtof(
    1378             :             GetKeyword("UNCOMPRESSED_FILE.IMAGE.SCALING_FACTOR", "1.0"));
    1379             :         const char *pszNull =
    1380           4 :             GetKeyword("UNCOMPRESSED_FILE.IMAGE.CORE_NULL", nullptr);
    1381           4 :         if (pszNull)
    1382           4 :             dfNoData = CPLAtof(pszNull);
    1383             :     }
    1384             : 
    1385           8 :     const CPLString osPath = CPLGetPathSafe(GetDescription());
    1386             :     const CPLString osFullFileName =
    1387           8 :         CPLFormFilenameSafe(osPath, osFileName, nullptr);
    1388             : 
    1389           4 :     poCompressedDS =
    1390           4 :         GDALDataset::FromHandle(GDALOpen(osFullFileName, GA_ReadOnly));
    1391             : 
    1392           4 :     if (poCompressedDS == nullptr)
    1393           0 :         return FALSE;
    1394             : 
    1395           4 :     nRasterXSize = poCompressedDS->GetRasterXSize();
    1396           4 :     nRasterYSize = poCompressedDS->GetRasterYSize();
    1397             : 
    1398           8 :     for (int iBand = 0; iBand < poCompressedDS->GetRasterCount(); iBand++)
    1399             :     {
    1400           4 :         SetBand(iBand + 1, new PDSWrapperRasterBand(
    1401           4 :                                poCompressedDS->GetRasterBand(iBand + 1),
    1402           4 :                                dfOffset, dfScale, dfNoData));
    1403             :     }
    1404             : 
    1405           4 :     return TRUE;
    1406             : }
    1407             : 
    1408             : /************************************************************************/
    1409             : /*                                Open()                                */
    1410             : /************************************************************************/
    1411             : 
    1412          39 : GDALDataset *PDSDataset::Open(GDALOpenInfo *poOpenInfo)
    1413             : {
    1414          39 :     if (!PDSDriverIdentify(poOpenInfo))
    1415           0 :         return nullptr;
    1416             : 
    1417          39 :     const char *pszHdr = reinterpret_cast<char *>(poOpenInfo->pabyHeader);
    1418          39 :     if (strstr(pszHdr, "PDS_VERSION_ID") != nullptr &&
    1419          39 :         strstr(pszHdr, "PDS3") == nullptr)
    1420             :     {
    1421           0 :         CPLError(
    1422             :             CE_Failure, CPLE_OpenFailed,
    1423             :             "It appears this is an older PDS image type.  Only PDS_VERSION_ID "
    1424             :             "= PDS3 are currently supported by this gdal PDS reader.");
    1425           0 :         return nullptr;
    1426             :     }
    1427             : 
    1428             :     /* -------------------------------------------------------------------- */
    1429             :     /*      Parse the keyword header.  Sometimes there is stuff             */
    1430             :     /*      before the PDS_VERSION_ID, which we want to ignore.             */
    1431             :     /* -------------------------------------------------------------------- */
    1432          39 :     VSILFILE *fpQube = poOpenInfo->fpL;
    1433          39 :     poOpenInfo->fpL = nullptr;
    1434             : 
    1435          39 :     PDSDataset *poDS = new PDSDataset();
    1436          39 :     poDS->SetDescription(poOpenInfo->pszFilename);
    1437          39 :     poDS->eAccess = poOpenInfo->eAccess;
    1438             : 
    1439          39 :     const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
    1440          39 :     int nOffset = 0;
    1441          39 :     if (pszPDSVersionID)
    1442          39 :         nOffset = static_cast<int>(pszPDSVersionID - pszHdr);
    1443             : 
    1444          39 :     if (!poDS->oKeywords.Ingest(fpQube, nOffset))
    1445             :     {
    1446           2 :         delete poDS;
    1447           2 :         VSIFCloseL(fpQube);
    1448           2 :         return nullptr;
    1449             :     }
    1450             :     poDS->m_aosPDSMD.InsertString(
    1451           0 :         0, poDS->oKeywords.GetJsonObject()
    1452          74 :                .Format(CPLJSONObject::PrettyFormat::Pretty)
    1453          74 :                .c_str());
    1454          37 :     VSIFCloseL(fpQube);
    1455             : 
    1456             :     /* -------------------------------------------------------------------- */
    1457             :     /*      Is this a compressed image with COMPRESSED_FILE subdomain?      */
    1458             :     /*                                                                      */
    1459             :     /*      The corresponding parse operations will read keywords,          */
    1460             :     /*      establish bands and raster size.                                */
    1461             :     /* -------------------------------------------------------------------- */
    1462             :     CPLString osEncodingType =
    1463         111 :         poDS->GetKeyword("COMPRESSED_FILE.ENCODING_TYPE", "");
    1464             : 
    1465             :     CPLString osCompressedFilename =
    1466         111 :         CleanString(poDS->GetKeyword("COMPRESSED_FILE.FILE_NAME", ""));
    1467             : 
    1468             :     const char *pszImageName =
    1469          37 :         poDS->GetKeyword("UNCOMPRESSED_FILE.IMAGE.NAME", "");
    1470             :     CPLString osUncompressedFilename =
    1471          37 :         CleanString(!EQUAL(pszImageName, "")
    1472             :                         ? pszImageName
    1473         146 :                         : poDS->GetKeyword("UNCOMPRESSED_FILE.FILE_NAME", ""));
    1474             : 
    1475             :     VSIStatBufL sStat;
    1476          74 :     CPLString osFilenamePrefix;
    1477             : 
    1478          40 :     if (EQUAL(osEncodingType, "ZIP") && !osCompressedFilename.empty() &&
    1479           3 :         !osUncompressedFilename.empty())
    1480             :     {
    1481           3 :         const CPLString osPath = CPLGetPathSafe(poDS->GetDescription());
    1482             :         osCompressedFilename =
    1483           3 :             CPLFormFilenameSafe(osPath, osCompressedFilename, nullptr);
    1484             :         osUncompressedFilename =
    1485           3 :             CPLFormFilenameSafe(osPath, osUncompressedFilename, nullptr);
    1486           3 :         if (VSIStatExL(osCompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) ==
    1487           6 :                 0 &&
    1488           3 :             VSIStatExL(osUncompressedFilename, &sStat, VSI_STAT_EXISTS_FLAG) !=
    1489             :                 0)
    1490             :         {
    1491           3 :             osFilenamePrefix = "/vsizip/" + osCompressedFilename + "/";
    1492           3 :             poDS->osExternalCube = std::move(osCompressedFilename);
    1493             :         }
    1494           3 :         osEncodingType = "";
    1495             :     }
    1496             : 
    1497          37 :     if (!osEncodingType.empty())
    1498             :     {
    1499           4 :         if (!poDS->ParseCompressedImage())
    1500             :         {
    1501           0 :             delete poDS;
    1502           0 :             return nullptr;
    1503             :         }
    1504             :     }
    1505             :     else
    1506             :     {
    1507          33 :         CPLString osPrefix;
    1508             : 
    1509          33 :         if (osUncompressedFilename != "")
    1510           5 :             osPrefix = "UNCOMPRESSED_FILE.";
    1511             : 
    1512             :         // Added ability to see into OBJECT = FILE section to support
    1513             :         // CRISM. Example file: hsp00017ba0_01_ra218s_trr3.lbl and *.img
    1514          73 :         if (strlen(poDS->GetKeyword("IMAGE.LINE_SAMPLES")) == 0 &&
    1515          40 :             strlen(poDS->GetKeyword("FILE.IMAGE.LINE_SAMPLES")) != 0)
    1516           2 :             osPrefix = "FILE.";
    1517             : 
    1518          33 :         if (!poDS->ParseImage(osPrefix, osFilenamePrefix))
    1519             :         {
    1520           0 :             delete poDS;
    1521           0 :             return nullptr;
    1522             :         }
    1523             :     }
    1524             : 
    1525             :     /* -------------------------------------------------------------------- */
    1526             :     /*      Set the coordinate system and geotransform.                     */
    1527             :     /* -------------------------------------------------------------------- */
    1528          37 :     poDS->ParseSRS();
    1529             : 
    1530             :     /* -------------------------------------------------------------------- */
    1531             :     /*      Transfer a few interesting keywords as metadata.                */
    1532             :     /* -------------------------------------------------------------------- */
    1533             :     static const char *const apszKeywords[] = {"FILTER_NAME",
    1534             :                                                "DATA_SET_ID",
    1535             :                                                "PRODUCT_ID",
    1536             :                                                "PRODUCER_INSTITUTION_NAME",
    1537             :                                                "PRODUCT_TYPE",
    1538             :                                                "MISSION_NAME",
    1539             :                                                "SPACECRAFT_NAME",
    1540             :                                                "INSTRUMENT_NAME",
    1541             :                                                "INSTRUMENT_ID",
    1542             :                                                "TARGET_NAME",
    1543             :                                                "CENTER_FILTER_WAVELENGTH",
    1544             :                                                "BANDWIDTH",
    1545             :                                                "PRODUCT_CREATION_TIME",
    1546             :                                                "START_TIME",
    1547             :                                                "STOP_TIME",
    1548             :                                                "NOTE",
    1549             :                                                nullptr};
    1550             : 
    1551         629 :     for (int i = 0; apszKeywords[i] != nullptr; i++)
    1552             :     {
    1553         592 :         const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
    1554             : 
    1555         592 :         if (pszKeywordValue != nullptr)
    1556         592 :             poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
    1557             :     }
    1558             : 
    1559             :     /* -------------------------------------------------------------------- */
    1560             :     /*      Initialize any PAM information.                                 */
    1561             :     /* -------------------------------------------------------------------- */
    1562          37 :     poDS->TryLoadXML();
    1563             : 
    1564             :     /* -------------------------------------------------------------------- */
    1565             :     /*      Check for overviews.                                            */
    1566             :     /* -------------------------------------------------------------------- */
    1567          37 :     poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
    1568             : 
    1569          37 :     return poDS;
    1570             : }
    1571             : 
    1572             : /************************************************************************/
    1573             : /*                             GetKeyword()                             */
    1574             : /************************************************************************/
    1575             : 
    1576        2021 : const char *PDSDataset::GetKeyword(const std::string &osPath,
    1577             :                                    const char *pszDefault)
    1578             : 
    1579             : {
    1580        2021 :     return oKeywords.GetKeyword(osPath.c_str(), pszDefault);
    1581             : }
    1582             : 
    1583             : /************************************************************************/
    1584             : /*                           GetKeywordSub()                            */
    1585             : /************************************************************************/
    1586             : 
    1587          27 : const char *PDSDataset::GetKeywordSub(const std::string &osPath, int iSubscript,
    1588             :                                       const char *pszDefault)
    1589             : 
    1590             : {
    1591          27 :     const char *pszResult = oKeywords.GetKeyword(osPath.c_str(), nullptr);
    1592             : 
    1593          27 :     if (pszResult == nullptr)
    1594           0 :         return pszDefault;
    1595             : 
    1596          27 :     if (pszResult[0] != '(')
    1597           0 :         return pszDefault;
    1598             : 
    1599             :     char **papszTokens =
    1600          27 :         CSLTokenizeString2(pszResult, "(,)", CSLT_HONOURSTRINGS);
    1601             : 
    1602          27 :     if (iSubscript <= CSLCount(papszTokens))
    1603             :     {
    1604          27 :         osTempResult = papszTokens[iSubscript - 1];
    1605          27 :         CSLDestroy(papszTokens);
    1606          27 :         return osTempResult.c_str();
    1607             :     }
    1608             : 
    1609           0 :     CSLDestroy(papszTokens);
    1610           0 :     return pszDefault;
    1611             : }
    1612             : 
    1613             : /************************************************************************/
    1614             : /*                           GetKeywordUnit()                           */
    1615             : /************************************************************************/
    1616             : 
    1617          25 : const char *PDSDataset::GetKeywordUnit(const char *pszPath, int iSubscript,
    1618             :                                        const char *pszDefault)
    1619             : 
    1620             : {
    1621          25 :     const char *pszResult = oKeywords.GetKeyword(pszPath, nullptr);
    1622             : 
    1623          25 :     if (pszResult == nullptr)
    1624           0 :         return pszDefault;
    1625             : 
    1626             :     char **papszTokens =
    1627          25 :         CSLTokenizeString2(pszResult, "</>", CSLT_HONOURSTRINGS);
    1628             : 
    1629          25 :     if (iSubscript <= CSLCount(papszTokens))
    1630             :     {
    1631          18 :         osTempResult = papszTokens[iSubscript - 1];
    1632          18 :         CSLDestroy(papszTokens);
    1633          18 :         return osTempResult.c_str();
    1634             :     }
    1635             : 
    1636           7 :     CSLDestroy(papszTokens);
    1637           7 :     return pszDefault;
    1638             : }
    1639             : 
    1640             : /************************************************************************/
    1641             : /*                            CleanString()                             */
    1642             : /*                                                                      */
    1643             : /* Removes single or double quotes, and converts spaces to underscores. */
    1644             : /************************************************************************/
    1645             : 
    1646         198 : CPLString PDSDataset::CleanString(const CPLString &osInput)
    1647             : 
    1648             : {
    1649         318 :     if ((osInput.size() < 2) ||
    1650         120 :         ((osInput.at(0) != '"' || osInput.back() != '"') &&
    1651          55 :          (osInput.at(0) != '\'' || osInput.back() != '\'')))
    1652         133 :         return osInput;
    1653             : 
    1654          65 :     char *pszWrk = CPLStrdup(osInput.c_str() + 1);
    1655             : 
    1656          65 :     pszWrk[strlen(pszWrk) - 1] = '\0';
    1657             : 
    1658        1015 :     for (int i = 0; pszWrk[i] != '\0'; i++)
    1659             :     {
    1660         950 :         if (pszWrk[i] == ' ')
    1661          16 :             pszWrk[i] = '_';
    1662             :     }
    1663             : 
    1664         130 :     CPLString osOutput(pszWrk);
    1665          65 :     CPLFree(pszWrk);
    1666          65 :     return osOutput;
    1667             : }
    1668             : 
    1669             : /************************************************************************/
    1670             : /*                       GetMetadataDomainList()                        */
    1671             : /************************************************************************/
    1672             : 
    1673           0 : char **PDSDataset::GetMetadataDomainList()
    1674             : {
    1675           0 :     return BuildMetadataDomainList(nullptr, FALSE, "", "json:PDS", nullptr);
    1676             : }
    1677             : 
    1678             : /************************************************************************/
    1679             : /*                            GetMetadata()                             */
    1680             : /************************************************************************/
    1681             : 
    1682           1 : CSLConstList PDSDataset::GetMetadata(const char *pszDomain)
    1683             : {
    1684           1 :     if (pszDomain != nullptr && EQUAL(pszDomain, "json:PDS"))
    1685             :     {
    1686           0 :         return m_aosPDSMD.List();
    1687             :     }
    1688           1 :     return GDALPamDataset::GetMetadata(pszDomain);
    1689             : }
    1690             : 
    1691             : /************************************************************************/
    1692             : /*                          GDALRegister_PDS()                          */
    1693             : /************************************************************************/
    1694             : 
    1695        2068 : void GDALRegister_PDS()
    1696             : 
    1697             : {
    1698        2068 :     if (GDALGetDriverByName(PDS_DRIVER_NAME) != nullptr)
    1699         263 :         return;
    1700             : 
    1701        1805 :     GDALDriver *poDriver = new GDALDriver();
    1702        1805 :     PDSDriverSetCommonMetadata(poDriver);
    1703             : 
    1704        1805 :     poDriver->pfnOpen = PDSDataset::Open;
    1705             : 
    1706        1805 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    1707             : 
    1708             : #ifdef PDS_PLUGIN
    1709             :     GDALRegister_ISIS3();
    1710             :     GDALRegister_ISIS2();
    1711             :     GDALRegister_PDS4();
    1712             :     GDALRegister_VICAR();
    1713             : #endif
    1714             : }

Generated by: LCOV version 1.14