LCOV - code coverage report
Current view: top level - frmts/pds - pds4dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2282 2618 87.2 %
Date: 2026-06-19 21:24:00 Functions: 85 89 95.5 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  PDS 4 Driver; Planetary Data System Format
       4             :  * Purpose:  Implementation of PDS4Dataset
       5             :  * Author:   Even Rouault, even.rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2017, Hobu Inc
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_vsi_error.h"
      14             : #include "gdal_proxy.h"
      15             : #include "gdal_frmts.h"
      16             : #include "rawdataset.h"
      17             : #include "vrtdataset.h"
      18             : #include "ogrsf_frmts.h"
      19             : #include "ogr_spatialref.h"
      20             : #include "gdal_priv_templates.hpp"
      21             : #include "ogreditablelayer.h"
      22             : #include "pds4dataset.h"
      23             : #include "pdsdrivercore.h"
      24             : 
      25             : #ifdef EMBED_RESOURCE_FILES
      26             : #include "embedded_resources.h"
      27             : #endif
      28             : 
      29             : #include <cstdlib>
      30             : #include <vector>
      31             : #include <algorithm>
      32             : #include <optional>
      33             : 
      34             : #define TIFF_GEOTIFF_STRING "TIFF 6.0"
      35             : #define BIGTIFF_GEOTIFF_STRING "TIFF 6.0"
      36             : #define PREEXISTING_BINARY_FILE                                                \
      37             :     "Binary file pre-existing PDS4 label. This comment is used by GDAL to "    \
      38             :     "avoid deleting the binary file when the label is deleted. Keep it to "    \
      39             :     "preserve this behavior."
      40             : 
      41             : #define CURRENT_CART_VERSION "1O00_1970"
      42             : 
      43             : /************************************************************************/
      44             : /*                       PDS4WrapperRasterBand()                        */
      45             : /************************************************************************/
      46             : 
      47          24 : PDS4WrapperRasterBand::PDS4WrapperRasterBand(GDALRasterBand *poBaseBandIn)
      48          24 :     : m_poBaseBand(poBaseBandIn)
      49             : {
      50          24 :     eDataType = m_poBaseBand->GetRasterDataType();
      51          24 :     m_poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
      52          24 : }
      53             : 
      54             : /************************************************************************/
      55             : /*                            SetMaskBand()                             */
      56             : /************************************************************************/
      57             : 
      58           0 : void PDS4WrapperRasterBand::SetMaskBand(
      59             :     std::unique_ptr<GDALRasterBand> poMaskBand)
      60             : {
      61           0 :     poMask.reset(std::move(poMaskBand));
      62           0 :     nMaskFlags = 0;
      63           0 : }
      64             : 
      65             : /************************************************************************/
      66             : /*                             GetOffset()                              */
      67             : /************************************************************************/
      68             : 
      69          18 : double PDS4WrapperRasterBand::GetOffset(int *pbSuccess)
      70             : {
      71          18 :     if (pbSuccess)
      72          18 :         *pbSuccess = m_bHasOffset;
      73          18 :     return m_dfOffset;
      74             : }
      75             : 
      76             : /************************************************************************/
      77             : /*                              GetScale()                              */
      78             : /************************************************************************/
      79             : 
      80          18 : double PDS4WrapperRasterBand::GetScale(int *pbSuccess)
      81             : {
      82          18 :     if (pbSuccess)
      83          18 :         *pbSuccess = m_bHasScale;
      84          18 :     return m_dfScale;
      85             : }
      86             : 
      87             : /************************************************************************/
      88             : /*                             SetOffset()                              */
      89             : /************************************************************************/
      90             : 
      91           1 : CPLErr PDS4WrapperRasterBand::SetOffset(double dfNewOffset)
      92             : {
      93           1 :     m_dfOffset = dfNewOffset;
      94           1 :     m_bHasOffset = true;
      95             : 
      96           1 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
      97           1 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
      98           1 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetOffset(dfNewOffset);
      99             : 
     100           1 :     return CE_None;
     101             : }
     102             : 
     103             : /************************************************************************/
     104             : /*                              SetScale()                              */
     105             : /************************************************************************/
     106             : 
     107           1 : CPLErr PDS4WrapperRasterBand::SetScale(double dfNewScale)
     108             : {
     109           1 :     m_dfScale = dfNewScale;
     110           1 :     m_bHasScale = true;
     111             : 
     112           1 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     113           1 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     114           1 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetScale(dfNewScale);
     115             : 
     116           1 :     return CE_None;
     117             : }
     118             : 
     119             : /************************************************************************/
     120             : /*                           GetNoDataValue()                           */
     121             : /************************************************************************/
     122             : 
     123          35 : double PDS4WrapperRasterBand::GetNoDataValue(int *pbSuccess)
     124             : {
     125          35 :     if (m_bHasNoDataInt64)
     126             :     {
     127           0 :         if (pbSuccess)
     128           0 :             *pbSuccess = true;
     129           0 :         return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
     130             :     }
     131             : 
     132          35 :     if (m_bHasNoDataUInt64)
     133             :     {
     134           0 :         if (pbSuccess)
     135           0 :             *pbSuccess = true;
     136           0 :         return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
     137             :     }
     138             : 
     139          35 :     if (pbSuccess)
     140          35 :         *pbSuccess = m_bHasNoData;
     141          35 :     return m_dfNoData;
     142             : }
     143             : 
     144             : /************************************************************************/
     145             : /*                           SetNoDataValue()                           */
     146             : /************************************************************************/
     147             : 
     148           9 : CPLErr PDS4WrapperRasterBand::SetNoDataValue(double dfNewNoData)
     149             : {
     150           9 :     m_dfNoData = dfNewNoData;
     151           9 :     m_bHasNoData = true;
     152             : 
     153           9 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     154           9 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     155           9 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValue(
     156           9 :             dfNewNoData);
     157             : 
     158           9 :     return CE_None;
     159             : }
     160             : 
     161             : /************************************************************************/
     162             : /*                       GetNoDataValueAsInt64()                        */
     163             : /************************************************************************/
     164             : 
     165           3 : int64_t PDS4WrapperRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
     166             : {
     167           3 :     if (pbSuccess)
     168           3 :         *pbSuccess = m_bHasNoDataInt64;
     169           3 :     return m_nNoDataInt64;
     170             : }
     171             : 
     172             : /************************************************************************/
     173             : /*                       SetNoDataValueAsInt64()                        */
     174             : /************************************************************************/
     175             : 
     176           1 : CPLErr PDS4WrapperRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
     177             : {
     178           1 :     m_nNoDataInt64 = nNewNoData;
     179           1 :     m_bHasNoDataInt64 = true;
     180             : 
     181           1 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     182           1 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     183           1 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
     184           1 :             nNewNoData);
     185             : 
     186           1 :     return CE_None;
     187             : }
     188             : 
     189             : /************************************************************************/
     190             : /*                       GetNoDataValueAsUInt64()                       */
     191             : /************************************************************************/
     192             : 
     193           3 : uint64_t PDS4WrapperRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
     194             : {
     195           3 :     if (pbSuccess)
     196           3 :         *pbSuccess = m_bHasNoDataUInt64;
     197           3 :     return m_nNoDataUInt64;
     198             : }
     199             : 
     200             : /************************************************************************/
     201             : /*                       SetNoDataValueAsUInt64()                       */
     202             : /************************************************************************/
     203             : 
     204           1 : CPLErr PDS4WrapperRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
     205             : {
     206           1 :     m_nNoDataUInt64 = nNewNoData;
     207           1 :     m_bHasNoDataUInt64 = true;
     208             : 
     209           1 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     210           1 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     211           1 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
     212           1 :             nNewNoData);
     213             : 
     214           1 :     return CE_None;
     215             : }
     216             : 
     217             : /************************************************************************/
     218             : /*                                Fill()                                */
     219             : /************************************************************************/
     220             : 
     221           2 : CPLErr PDS4WrapperRasterBand::Fill(double dfRealValue, double dfImaginaryValue)
     222             : {
     223           2 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     224           2 :     if (poGDS->m_bMustInitImageFile)
     225             :     {
     226           2 :         if (!poGDS->InitImageFile())
     227           0 :             return CE_Failure;
     228             :     }
     229           2 :     return GDALProxyRasterBand::Fill(dfRealValue, dfImaginaryValue);
     230             : }
     231             : 
     232             : /************************************************************************/
     233             : /*                            IWriteBlock()                             */
     234             : /************************************************************************/
     235             : 
     236           0 : CPLErr PDS4WrapperRasterBand::IWriteBlock(int nXBlock, int nYBlock,
     237             :                                           void *pImage)
     238             : 
     239             : {
     240           0 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     241           0 :     if (poGDS->m_bMustInitImageFile)
     242             :     {
     243           0 :         if (!poGDS->InitImageFile())
     244           0 :             return CE_Failure;
     245             :     }
     246           0 :     return GDALProxyRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
     247             : }
     248             : 
     249             : /************************************************************************/
     250             : /*                             IRasterIO()                              */
     251             : /************************************************************************/
     252             : 
     253         115 : CPLErr PDS4WrapperRasterBand::IRasterIO(
     254             :     GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
     255             :     void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
     256             :     GSpacing nPixelSpace, GSpacing nLineSpace, GDALRasterIOExtraArg *psExtraArg)
     257             : 
     258             : {
     259         115 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     260         115 :     if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
     261             :     {
     262           9 :         if (!poGDS->InitImageFile())
     263           0 :             return CE_Failure;
     264             :     }
     265         115 :     return GDALProxyRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     266             :                                           pData, nBufXSize, nBufYSize, eBufType,
     267         115 :                                           nPixelSpace, nLineSpace, psExtraArg);
     268             : }
     269             : 
     270             : /************************************************************************/
     271             : /*                         PDS4RawRasterBand()                          */
     272             : /************************************************************************/
     273             : 
     274         460 : PDS4RawRasterBand::PDS4RawRasterBand(GDALDataset *l_poDS, int l_nBand,
     275             :                                      VSILFILE *l_fpRaw,
     276             :                                      vsi_l_offset l_nImgOffset,
     277             :                                      int l_nPixelOffset, int l_nLineOffset,
     278             :                                      GDALDataType l_eDataType,
     279         460 :                                      RawRasterBand::ByteOrder eByteOrderIn)
     280             :     : RawRasterBand(l_poDS, l_nBand, l_fpRaw, l_nImgOffset, l_nPixelOffset,
     281             :                     l_nLineOffset, l_eDataType, eByteOrderIn,
     282         460 :                     RawRasterBand::OwnFP::NO)
     283             : {
     284         460 : }
     285             : 
     286             : /************************************************************************/
     287             : /*                            SetMaskBand()                             */
     288             : /************************************************************************/
     289             : 
     290          43 : void PDS4RawRasterBand::SetMaskBand(std::unique_ptr<GDALRasterBand> poMaskBand)
     291             : {
     292          43 :     poMask.reset(std::move(poMaskBand));
     293          43 :     nMaskFlags = 0;
     294          43 : }
     295             : 
     296             : /************************************************************************/
     297             : /*                             GetOffset()                              */
     298             : /************************************************************************/
     299             : 
     300         127 : double PDS4RawRasterBand::GetOffset(int *pbSuccess)
     301             : {
     302         127 :     if (pbSuccess)
     303         119 :         *pbSuccess = m_bHasOffset;
     304         127 :     return m_dfOffset;
     305             : }
     306             : 
     307             : /************************************************************************/
     308             : /*                              GetScale()                              */
     309             : /************************************************************************/
     310             : 
     311         127 : double PDS4RawRasterBand::GetScale(int *pbSuccess)
     312             : {
     313         127 :     if (pbSuccess)
     314         119 :         *pbSuccess = m_bHasScale;
     315         127 :     return m_dfScale;
     316             : }
     317             : 
     318             : /************************************************************************/
     319             : /*                             SetOffset()                              */
     320             : /************************************************************************/
     321             : 
     322         289 : CPLErr PDS4RawRasterBand::SetOffset(double dfNewOffset)
     323             : {
     324         289 :     m_dfOffset = dfNewOffset;
     325         289 :     m_bHasOffset = true;
     326         289 :     return CE_None;
     327             : }
     328             : 
     329             : /************************************************************************/
     330             : /*                              SetScale()                              */
     331             : /************************************************************************/
     332             : 
     333         289 : CPLErr PDS4RawRasterBand::SetScale(double dfNewScale)
     334             : {
     335         289 :     m_dfScale = dfNewScale;
     336         289 :     m_bHasScale = true;
     337         289 :     return CE_None;
     338             : }
     339             : 
     340             : /************************************************************************/
     341             : /*                           GetNoDataValue()                           */
     342             : /************************************************************************/
     343             : 
     344         230 : double PDS4RawRasterBand::GetNoDataValue(int *pbSuccess)
     345             : {
     346         230 :     if (m_bHasNoDataInt64)
     347             :     {
     348           0 :         if (pbSuccess)
     349           0 :             *pbSuccess = true;
     350           0 :         return GDALGetNoDataValueCastToDouble(m_nNoDataInt64);
     351             :     }
     352             : 
     353         230 :     if (m_bHasNoDataUInt64)
     354             :     {
     355           0 :         if (pbSuccess)
     356           0 :             *pbSuccess = true;
     357           0 :         return GDALGetNoDataValueCastToDouble(m_nNoDataUInt64);
     358             :     }
     359             : 
     360         230 :     if (pbSuccess)
     361         230 :         *pbSuccess = m_bHasNoData;
     362         230 :     return m_dfNoData;
     363             : }
     364             : 
     365             : /************************************************************************/
     366             : /*                           SetNoDataValue()                           */
     367             : /************************************************************************/
     368             : 
     369          92 : CPLErr PDS4RawRasterBand::SetNoDataValue(double dfNewNoData)
     370             : {
     371          92 :     m_dfNoData = dfNewNoData;
     372          92 :     m_bHasNoData = true;
     373          92 :     return CE_None;
     374             : }
     375             : 
     376             : /************************************************************************/
     377             : /*                       GetNoDataValueAsInt64()                        */
     378             : /************************************************************************/
     379             : 
     380           9 : int64_t PDS4RawRasterBand::GetNoDataValueAsInt64(int *pbSuccess)
     381             : {
     382           9 :     if (pbSuccess)
     383           9 :         *pbSuccess = m_bHasNoDataInt64;
     384           9 :     return m_nNoDataInt64;
     385             : }
     386             : 
     387             : /************************************************************************/
     388             : /*                       SetNoDataValueAsInt64()                        */
     389             : /************************************************************************/
     390             : 
     391           3 : CPLErr PDS4RawRasterBand::SetNoDataValueAsInt64(int64_t nNewNoData)
     392             : {
     393           3 :     m_nNoDataInt64 = nNewNoData;
     394           3 :     m_bHasNoDataInt64 = true;
     395             : 
     396           3 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     397           3 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     398           0 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsInt64(
     399           0 :             nNewNoData);
     400             : 
     401           3 :     return CE_None;
     402             : }
     403             : 
     404             : /************************************************************************/
     405             : /*                       GetNoDataValueAsUInt64()                       */
     406             : /************************************************************************/
     407             : 
     408           9 : uint64_t PDS4RawRasterBand::GetNoDataValueAsUInt64(int *pbSuccess)
     409             : {
     410           9 :     if (pbSuccess)
     411           9 :         *pbSuccess = m_bHasNoDataUInt64;
     412           9 :     return m_nNoDataUInt64;
     413             : }
     414             : 
     415             : /************************************************************************/
     416             : /*                       SetNoDataValueAsUInt64()                       */
     417             : /************************************************************************/
     418             : 
     419           3 : CPLErr PDS4RawRasterBand::SetNoDataValueAsUInt64(uint64_t nNewNoData)
     420             : {
     421           3 :     m_nNoDataUInt64 = nNewNoData;
     422           3 :     m_bHasNoDataUInt64 = true;
     423             : 
     424           3 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     425           3 :     if (poGDS->m_poExternalDS && eAccess == GA_Update)
     426           0 :         poGDS->m_poExternalDS->GetRasterBand(nBand)->SetNoDataValueAsUInt64(
     427           0 :             nNewNoData);
     428             : 
     429           3 :     return CE_None;
     430             : }
     431             : 
     432             : /************************************************************************/
     433             : /*                             IReadBlock()                             */
     434             : /************************************************************************/
     435             : 
     436         132 : CPLErr PDS4RawRasterBand::IWriteBlock(int nXBlock, int nYBlock, void *pImage)
     437             : 
     438             : {
     439         132 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     440         132 :     if (poGDS->m_bMustInitImageFile)
     441             :     {
     442           0 :         if (!poGDS->InitImageFile())
     443           0 :             return CE_Failure;
     444             :     }
     445             : 
     446         132 :     return RawRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
     447             : }
     448             : 
     449             : /************************************************************************/
     450             : /*                             IRasterIO()                              */
     451             : /************************************************************************/
     452             : 
     453        1106 : CPLErr PDS4RawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     454             :                                     int nXSize, int nYSize, void *pData,
     455             :                                     int nBufXSize, int nBufYSize,
     456             :                                     GDALDataType eBufType, GSpacing nPixelSpace,
     457             :                                     GSpacing nLineSpace,
     458             :                                     GDALRasterIOExtraArg *psExtraArg)
     459             : 
     460             : {
     461        1106 :     PDS4Dataset *poGDS = cpl::down_cast<PDS4Dataset *>(poDS);
     462        1106 :     if (eRWFlag == GF_Write && poGDS->m_bMustInitImageFile)
     463             :     {
     464          10 :         if (!poGDS->InitImageFile())
     465           0 :             return CE_Failure;
     466             :     }
     467             : 
     468        1106 :     return RawRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     469             :                                     pData, nBufXSize, nBufYSize, eBufType,
     470        1106 :                                     nPixelSpace, nLineSpace, psExtraArg);
     471             : }
     472             : 
     473             : /************************************************************************/
     474             : /*                            PDS4MaskBand()                            */
     475             : /************************************************************************/
     476             : 
     477          43 : PDS4MaskBand::PDS4MaskBand(GDALRasterBand *poBaseBand,
     478          43 :                            const std::vector<double> &adfConstants)
     479          43 :     : m_poBaseBand(poBaseBand), m_pBuffer(nullptr), m_adfConstants(adfConstants)
     480             : {
     481          43 :     eDataType = GDT_UInt8;
     482          43 :     poBaseBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
     483          43 :     nRasterXSize = poBaseBand->GetXSize();
     484          43 :     nRasterYSize = poBaseBand->GetYSize();
     485          43 : }
     486             : 
     487             : /************************************************************************/
     488             : /*                           ~PDS4MaskBand()                            */
     489             : /************************************************************************/
     490             : 
     491          86 : PDS4MaskBand::~PDS4MaskBand()
     492             : {
     493          43 :     VSIFree(m_pBuffer);
     494          86 : }
     495             : 
     496             : /************************************************************************/
     497             : /*                              FillMask()                              */
     498             : /************************************************************************/
     499             : 
     500             : template <class T>
     501          88 : static void FillMask(void *pvBuffer, GByte *pabyDst, int nReqXSize,
     502             :                      int nReqYSize, int nBlockXSize,
     503             :                      const std::vector<double> &adfConstants)
     504             : {
     505          88 :     const T *pSrc = static_cast<T *>(pvBuffer);
     506         176 :     std::vector<T> aConstants;
     507         248 :     for (size_t i = 0; i < adfConstants.size(); i++)
     508             :     {
     509             :         T cst;
     510         160 :         GDALCopyWord(adfConstants[i], cst);
     511         160 :         aConstants.push_back(cst);
     512             :     }
     513             : 
     514         176 :     for (int y = 0; y < nReqYSize; y++)
     515             :     {
     516        1696 :         for (int x = 0; x < nReqXSize; x++)
     517             :         {
     518        1608 :             const T nSrc = pSrc[y * nBlockXSize + x];
     519        1608 :             if (std::find(aConstants.begin(), aConstants.end(), nSrc) !=
     520             :                 aConstants.end())
     521             :             {
     522           6 :                 pabyDst[y * nBlockXSize + x] = 0;
     523             :             }
     524             :             else
     525             :             {
     526        1602 :                 pabyDst[y * nBlockXSize + x] = 255;
     527             :             }
     528             :         }
     529             :     }
     530          88 : }
     531             : 
     532             : /************************************************************************/
     533             : /*                             IReadBlock()                             */
     534             : /************************************************************************/
     535             : 
     536          88 : CPLErr PDS4MaskBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     537             : 
     538             : {
     539          88 :     const GDALDataType eSrcDT = m_poBaseBand->GetRasterDataType();
     540          88 :     const int nSrcDTSize = GDALGetDataTypeSizeBytes(eSrcDT);
     541          88 :     if (m_pBuffer == nullptr)
     542             :     {
     543          12 :         m_pBuffer = VSI_MALLOC3_VERBOSE(nBlockXSize, nBlockYSize, nSrcDTSize);
     544          12 :         if (m_pBuffer == nullptr)
     545           0 :             return CE_Failure;
     546             :     }
     547             : 
     548          88 :     const int nXOff = nBlockXOff * nBlockXSize;
     549          88 :     const int nReqXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
     550          88 :     const int nYOff = nBlockYOff * nBlockYSize;
     551          88 :     const int nReqYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
     552             : 
     553         176 :     if (m_poBaseBand->RasterIO(GF_Read, nXOff, nYOff, nReqXSize, nReqYSize,
     554             :                                m_pBuffer, nReqXSize, nReqYSize, eSrcDT,
     555             :                                nSrcDTSize,
     556          88 :                                static_cast<GSpacing>(nSrcDTSize) * nBlockXSize,
     557          88 :                                nullptr) != CE_None)
     558             :     {
     559           0 :         return CE_Failure;
     560             :     }
     561             : 
     562          88 :     GByte *pabyDst = static_cast<GByte *>(pImage);
     563          88 :     if (eSrcDT == GDT_UInt8)
     564             :     {
     565          81 :         FillMask<GByte>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     566          81 :                         m_adfConstants);
     567             :     }
     568           7 :     else if (eSrcDT == GDT_Int8)
     569             :     {
     570           1 :         FillMask<GInt8>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     571           1 :                         m_adfConstants);
     572             :     }
     573           6 :     else if (eSrcDT == GDT_UInt16)
     574             :     {
     575           1 :         FillMask<GUInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     576           1 :                           m_adfConstants);
     577             :     }
     578           5 :     else if (eSrcDT == GDT_Int16)
     579             :     {
     580           1 :         FillMask<GInt16>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     581           1 :                          m_adfConstants);
     582             :     }
     583           4 :     else if (eSrcDT == GDT_UInt32)
     584             :     {
     585           1 :         FillMask<GUInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     586           1 :                           m_adfConstants);
     587             :     }
     588           3 :     else if (eSrcDT == GDT_Int32)
     589             :     {
     590           1 :         FillMask<GInt32>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     591           1 :                          m_adfConstants);
     592             :     }
     593           2 :     else if (eSrcDT == GDT_UInt64)
     594             :     {
     595           0 :         FillMask<uint64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize,
     596           0 :                            nBlockXSize, m_adfConstants);
     597             :     }
     598           2 :     else if (eSrcDT == GDT_Int64)
     599             :     {
     600           0 :         FillMask<int64_t>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     601           0 :                           m_adfConstants);
     602             :     }
     603           2 :     else if (eSrcDT == GDT_Float32)
     604             :     {
     605           1 :         FillMask<float>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     606           1 :                         m_adfConstants);
     607             :     }
     608           1 :     else if (eSrcDT == GDT_Float64)
     609             :     {
     610           1 :         FillMask<double>(m_pBuffer, pabyDst, nReqXSize, nReqYSize, nBlockXSize,
     611           1 :                          m_adfConstants);
     612             :     }
     613             : 
     614          88 :     return CE_None;
     615             : }
     616             : 
     617             : /************************************************************************/
     618             : /*                            PDS4Dataset()                             */
     619             : /************************************************************************/
     620             : 
     621         488 : PDS4Dataset::PDS4Dataset()
     622             : {
     623         488 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     624         488 : }
     625             : 
     626             : /************************************************************************/
     627             : /*                            ~PDS4Dataset()                            */
     628             : /************************************************************************/
     629             : 
     630         976 : PDS4Dataset::~PDS4Dataset()
     631             : {
     632         488 :     PDS4Dataset::Close();
     633         976 : }
     634             : 
     635             : /************************************************************************/
     636             : /*                               Close()                                */
     637             : /************************************************************************/
     638             : 
     639         840 : CPLErr PDS4Dataset::Close(GDALProgressFunc, void *)
     640             : {
     641         840 :     CPLErr eErr = CE_None;
     642         840 :     if (nOpenFlags != OPEN_FLAGS_CLOSED)
     643             :     {
     644         488 :         if (m_bMustInitImageFile)
     645             :         {
     646          72 :             if (!InitImageFile())
     647           0 :                 eErr = CE_Failure;
     648             :         }
     649             : 
     650         488 :         if (PDS4Dataset::FlushCache(true) != CE_None)
     651           1 :             eErr = CE_Failure;
     652             : 
     653         488 :         if (m_bCreateHeader || m_bDirtyHeader)
     654         194 :             WriteHeader();
     655         488 :         if (m_fpImage)
     656         337 :             VSIFCloseL(m_fpImage);
     657         488 :         CSLDestroy(m_papszCreationOptions);
     658         488 :         PDS4Dataset::CloseDependentDatasets();
     659             : 
     660         488 :         if (GDALPamDataset::Close() != CE_None)
     661           0 :             eErr = CE_Failure;
     662             :     }
     663         840 :     return eErr;
     664             : }
     665             : 
     666             : /************************************************************************/
     667             : /*                         GetRawBinaryLayout()                         */
     668             : /************************************************************************/
     669             : 
     670           1 : bool PDS4Dataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
     671             : {
     672           1 :     if (!RawDataset::GetRawBinaryLayout(sLayout))
     673           0 :         return false;
     674           1 :     sLayout.osRawFilename = m_osImageFilename;
     675           1 :     return true;
     676             : }
     677             : 
     678             : /************************************************************************/
     679             : /*                       CloseDependentDatasets()                       */
     680             : /************************************************************************/
     681             : 
     682         488 : int PDS4Dataset::CloseDependentDatasets()
     683             : {
     684         488 :     int bHasDroppedRef = GDALPamDataset::CloseDependentDatasets();
     685             : 
     686         488 :     if (m_poExternalDS)
     687             :     {
     688          18 :         bHasDroppedRef = FALSE;
     689          18 :         delete m_poExternalDS;
     690          18 :         m_poExternalDS = nullptr;
     691             : 
     692          42 :         for (int iBand = 0; iBand < nBands; iBand++)
     693             :         {
     694          24 :             delete papoBands[iBand];
     695          24 :             papoBands[iBand] = nullptr;
     696             :         }
     697          18 :         nBands = 0;
     698             :     }
     699             : 
     700         488 :     return bHasDroppedRef;
     701             : }
     702             : 
     703             : /************************************************************************/
     704             : /*                           GetSpatialRef()                            */
     705             : /************************************************************************/
     706             : 
     707          44 : const OGRSpatialReference *PDS4Dataset::GetSpatialRef() const
     708             : {
     709          44 :     if (!m_oSRS.IsEmpty())
     710          40 :         return &m_oSRS;
     711           4 :     return GDALPamDataset::GetSpatialRef();
     712             : }
     713             : 
     714             : /************************************************************************/
     715             : /*                           SetSpatialRef()                            */
     716             : /************************************************************************/
     717             : 
     718         101 : CPLErr PDS4Dataset::SetSpatialRef(const OGRSpatialReference *poSRS)
     719             : 
     720             : {
     721         101 :     if (eAccess == GA_ReadOnly)
     722           0 :         return CE_Failure;
     723         101 :     m_oSRS.Clear();
     724         101 :     if (poSRS)
     725         101 :         m_oSRS = *poSRS;
     726         101 :     if (m_poExternalDS)
     727          10 :         m_poExternalDS->SetSpatialRef(poSRS);
     728         101 :     return CE_None;
     729             : }
     730             : 
     731             : /************************************************************************/
     732             : /*                          GetGeoTransform()                           */
     733             : /************************************************************************/
     734             : 
     735          48 : CPLErr PDS4Dataset::GetGeoTransform(GDALGeoTransform &gt) const
     736             : 
     737             : {
     738          48 :     if (m_bGotTransform)
     739             :     {
     740          46 :         gt = m_gt;
     741          46 :         return CE_None;
     742             :     }
     743             : 
     744           2 :     return GDALPamDataset::GetGeoTransform(gt);
     745             : }
     746             : 
     747             : /************************************************************************/
     748             : /*                          SetGeoTransform()                           */
     749             : /************************************************************************/
     750             : 
     751         101 : CPLErr PDS4Dataset::SetGeoTransform(const GDALGeoTransform &gt)
     752             : 
     753             : {
     754         101 :     if (!((gt.xscale > 0.0 && gt.xrot == 0.0 && gt.yrot == 0.0 &&
     755         100 :            gt.yscale < 0.0) ||
     756           1 :           (gt.xscale == 0.0 && gt.xrot > 0.0 && gt.yrot > 0.0 &&
     757           1 :            gt.yscale == 0.0)))
     758             :     {
     759           0 :         CPLError(CE_Failure, CPLE_NotSupported,
     760             :                  "Only north-up geotransform or map_projection_rotation=90 "
     761             :                  "supported");
     762           0 :         return CE_Failure;
     763             :     }
     764         101 :     m_gt = gt;
     765         101 :     m_bGotTransform = true;
     766         101 :     if (m_poExternalDS)
     767          10 :         m_poExternalDS->SetGeoTransform(m_gt);
     768         101 :     return CE_None;
     769             : }
     770             : 
     771             : /************************************************************************/
     772             : /*                            SetMetadata()                             */
     773             : /************************************************************************/
     774             : 
     775           8 : CPLErr PDS4Dataset::SetMetadata(CSLConstList papszMD, const char *pszDomain)
     776             : {
     777           8 :     if (m_bUseSrcLabel && eAccess == GA_Update && pszDomain != nullptr &&
     778           8 :         EQUAL(pszDomain, "xml:PDS4"))
     779             :     {
     780           6 :         if (papszMD != nullptr && papszMD[0] != nullptr)
     781             :         {
     782           6 :             m_osXMLPDS4 = papszMD[0];
     783             :         }
     784           6 :         return CE_None;
     785             :     }
     786           2 :     return GDALPamDataset::SetMetadata(papszMD, pszDomain);
     787             : }
     788             : 
     789             : /************************************************************************/
     790             : /*                            GetFileList()                             */
     791             : /************************************************************************/
     792             : 
     793         137 : char **PDS4Dataset::GetFileList()
     794             : {
     795         137 :     char **papszFileList = GDALPamDataset::GetFileList();
     796         274 :     if (!m_osXMLFilename.empty() &&
     797         137 :         CSLFindString(papszFileList, m_osXMLFilename) < 0)
     798             :     {
     799           0 :         papszFileList = CSLAddString(papszFileList, m_osXMLFilename);
     800             :     }
     801         137 :     if (!m_osImageFilename.empty())
     802             :     {
     803          88 :         papszFileList = CSLAddString(papszFileList, m_osImageFilename);
     804             :     }
     805         201 :     for (const auto &poLayer : m_apoLayers)
     806             :     {
     807          64 :         auto papszTemp = poLayer->GetFileList();
     808          64 :         papszFileList = CSLInsertStrings(papszFileList, -1, papszTemp);
     809          64 :         CSLDestroy(papszTemp);
     810             :     }
     811         137 :     return papszFileList;
     812             : }
     813             : 
     814             : /************************************************************************/
     815             : /*                           GetLinearValue()                           */
     816             : /************************************************************************/
     817             : 
     818             : static const struct
     819             : {
     820             :     const char *pszUnit;
     821             :     double dfToMeter;
     822             : } apsLinearUnits[] = {
     823             :     {"AU", 149597870700.0}, {"Angstrom", 1e-10}, {"cm", 1e-2}, {"km", 1e3},
     824             :     {"micrometer", 1e-6},   {"mm", 1e-3},        {"nm", 1e-9}};
     825             : 
     826         802 : static double GetLinearValue(const CPLXMLNode *psParent,
     827             :                              const char *pszElementName)
     828             : {
     829         802 :     const CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
     830         802 :     if (psNode == nullptr)
     831           0 :         return 0.0;
     832         802 :     double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
     833         802 :     const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
     834         802 :     if (pszUnit && !EQUAL(pszUnit, "m"))
     835             :     {
     836          21 :         bool bFound = false;
     837          84 :         for (size_t i = 0; i < CPL_ARRAYSIZE(apsLinearUnits); i++)
     838             :         {
     839          84 :             if (EQUAL(pszUnit, apsLinearUnits[i].pszUnit))
     840             :             {
     841          21 :                 dfVal *= apsLinearUnits[i].dfToMeter;
     842          21 :                 bFound = true;
     843          21 :                 break;
     844             :             }
     845             :         }
     846          21 :         if (!bFound)
     847             :         {
     848           0 :             CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
     849             :                      pszUnit, pszElementName);
     850             :         }
     851             :     }
     852         802 :     return dfVal;
     853             : }
     854             : 
     855             : /************************************************************************/
     856             : /*                         GetResolutionValue()                         */
     857             : /************************************************************************/
     858             : 
     859             : static const struct
     860             : {
     861             :     const char *pszUnit;
     862             :     double dfToMeter;
     863             : } apsResolutionUnits[] = {
     864             :     {"km/pixel", 1e3},
     865             :     {"mm/pixel", 1e-3},
     866             : };
     867             : 
     868         316 : static double GetResolutionValue(CPLXMLNode *psParent,
     869             :                                  const char *pszElementName)
     870             : {
     871         316 :     CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
     872         316 :     if (psNode == nullptr)
     873           0 :         return 0.0;
     874         316 :     double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
     875         316 :     const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
     876         316 :     if (pszUnit && !EQUAL(pszUnit, "m/pixel"))
     877             :     {
     878          21 :         bool bFound = false;
     879          21 :         for (size_t i = 0; i < CPL_ARRAYSIZE(apsResolutionUnits); i++)
     880             :         {
     881          21 :             if (EQUAL(pszUnit, apsResolutionUnits[i].pszUnit))
     882             :             {
     883          21 :                 dfVal *= apsResolutionUnits[i].dfToMeter;
     884          21 :                 bFound = true;
     885          21 :                 break;
     886             :             }
     887             :         }
     888          21 :         if (!bFound)
     889             :         {
     890           0 :             CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
     891             :                      pszUnit, pszElementName);
     892             :         }
     893             :     }
     894         316 :     return dfVal;
     895             : }
     896             : 
     897             : /************************************************************************/
     898             : /*                          GetAngularValue()                           */
     899             : /************************************************************************/
     900             : 
     901             : static const struct
     902             : {
     903             :     const char *pszUnit;
     904             :     double dfToDeg;
     905             : } apsAngularUnits[] = {{"arcmin", 1. / 60.},
     906             :                        {"arcsec", 1. / 3600},
     907             :                        {"hr", 15.0},
     908             :                        {"mrad", 180.0 / M_PI / 1000.},
     909             :                        {"rad", 180.0 / M_PI}};
     910             : 
     911         819 : static double GetAngularValue(CPLXMLNode *psParent, const char *pszElementName,
     912             :                               bool *pbGotVal = nullptr)
     913             : {
     914         819 :     CPLXMLNode *psNode = CPLGetXMLNode(psParent, pszElementName);
     915         819 :     if (psNode == nullptr)
     916             :     {
     917         424 :         if (pbGotVal)
     918         265 :             *pbGotVal = false;
     919         424 :         return 0.0;
     920             :     }
     921         395 :     double dfVal = CPLAtof(CPLGetXMLValue(psNode, nullptr, ""));
     922         395 :     const char *pszUnit = CPLGetXMLValue(psNode, "unit", nullptr);
     923         395 :     if (pszUnit && !EQUAL(pszUnit, "deg"))
     924             :     {
     925           0 :         bool bFound = false;
     926           0 :         for (size_t i = 0; i < CPL_ARRAYSIZE(apsAngularUnits); i++)
     927             :         {
     928           0 :             if (EQUAL(pszUnit, apsAngularUnits[i].pszUnit))
     929             :             {
     930           0 :                 dfVal *= apsAngularUnits[i].dfToDeg;
     931           0 :                 bFound = true;
     932           0 :                 break;
     933             :             }
     934             :         }
     935           0 :         if (!bFound)
     936             :         {
     937           0 :             CPLError(CE_Warning, CPLE_AppDefined, "Unknown unit '%s' for '%s'",
     938             :                      pszUnit, pszElementName);
     939             :         }
     940             :     }
     941         395 :     if (pbGotVal)
     942         220 :         *pbGotVal = true;
     943         395 :     return dfVal;
     944             : }
     945             : 
     946             : /************************************************************************/
     947             : /*                         ReadGeoreferencing()                         */
     948             : /************************************************************************/
     949             : 
     950             : // See https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1G00_1950.xsd, (GDAL 3.4)
     951             : //     https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1D00_1933.xsd,
     952             : //     https://raw.githubusercontent.com/nasa-pds-data-dictionaries/ldd-cart/master/build/1.B.0.0/PDS4_CART_1B00.xsd,
     953             : //     https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.xsd
     954             : // and the corresponding .sch files
     955         295 : void PDS4Dataset::ReadGeoreferencing(CPLXMLNode *psProduct)
     956             : {
     957         295 :     CPLXMLNode *psCart = CPLGetXMLNode(
     958             :         psProduct, "Observation_Area.Discipline_Area.Cartography");
     959         295 :     if (psCart == nullptr)
     960             :     {
     961         133 :         CPLDebug("PDS4",
     962             :                  "Did not find Observation_Area.Discipline_Area.Cartography");
     963         133 :         return;
     964             :     }
     965             : 
     966             :     // Bounding box: informative only
     967             :     CPLXMLNode *psBounding =
     968         162 :         CPLGetXMLNode(psCart, "Spatial_Domain.Bounding_Coordinates");
     969         162 :     if (psBounding)
     970             :     {
     971             :         const char *pszWest =
     972         162 :             CPLGetXMLValue(psBounding, "west_bounding_coordinate", nullptr);
     973             :         const char *pszEast =
     974         162 :             CPLGetXMLValue(psBounding, "east_bounding_coordinate", nullptr);
     975             :         const char *pszNorth =
     976         162 :             CPLGetXMLValue(psBounding, "north_bounding_coordinate", nullptr);
     977             :         const char *pszSouth =
     978         162 :             CPLGetXMLValue(psBounding, "south_bounding_coordinate", nullptr);
     979         162 :         if (pszWest)
     980         162 :             CPLDebug("PDS4", "West: %s", pszWest);
     981         162 :         if (pszEast)
     982         162 :             CPLDebug("PDS4", "East: %s", pszEast);
     983         162 :         if (pszNorth)
     984         162 :             CPLDebug("PDS4", "North: %s", pszNorth);
     985         162 :         if (pszSouth)
     986         162 :             CPLDebug("PDS4", "South: %s", pszSouth);
     987             :     }
     988             : 
     989             :     CPLXMLNode *psSR =
     990         162 :         CPLGetXMLNode(psCart, "Spatial_Reference_Information.Horizontal_"
     991             :                               "Coordinate_System_Definition");
     992         162 :     if (psSR == nullptr)
     993             :     {
     994           0 :         CPLDebug("PDS4", "Did not find Spatial_Reference_Information."
     995             :                          "Horizontal_Coordinate_System_Definition");
     996           0 :         return;
     997             :     }
     998             : 
     999         162 :     double dfLongitudeMultiplier = 1;
    1000         162 :     const CPLXMLNode *psGeodeticModel = CPLGetXMLNode(psSR, "Geodetic_Model");
    1001         162 :     if (psGeodeticModel != nullptr)
    1002             :     {
    1003         162 :         if (EQUAL(CPLGetXMLValue(psGeodeticModel, "longitude_direction", ""),
    1004             :                   "Positive West"))
    1005             :         {
    1006           3 :             dfLongitudeMultiplier = -1;
    1007             :         }
    1008             :     }
    1009             : 
    1010         324 :     OGRSpatialReference oSRS;
    1011             :     CPLXMLNode *psGridCoordinateSystem =
    1012         162 :         CPLGetXMLNode(psSR, "Planar.Grid_Coordinate_System");
    1013         162 :     CPLXMLNode *psMapProjection = CPLGetXMLNode(psSR, "Planar.Map_Projection");
    1014         324 :     CPLString osProjName;
    1015         162 :     double dfCenterLon = 0.0;
    1016         162 :     double dfCenterLat = 0.0;
    1017         162 :     double dfStdParallel1 = 0.0;
    1018         162 :     double dfStdParallel2 = 0.0;
    1019         162 :     double dfScale = 1.0;
    1020         162 :     double dfMapProjectionRotation = 0.0;
    1021         162 :     if (psGridCoordinateSystem != nullptr)
    1022             :     {
    1023             :         osProjName = CPLGetXMLValue(psGridCoordinateSystem,
    1024           0 :                                     "grid_coordinate_system_name", "");
    1025           0 :         if (!osProjName.empty())
    1026             :         {
    1027           0 :             if (osProjName == "Universal Transverse Mercator")
    1028             :             {
    1029           0 :                 CPLXMLNode *psUTMZoneNumber = CPLGetXMLNode(
    1030             :                     psGridCoordinateSystem,
    1031             :                     "Universal_Transverse_Mercator.utm_zone_number");
    1032           0 :                 if (psUTMZoneNumber)
    1033             :                 {
    1034             :                     int nZone =
    1035           0 :                         atoi(CPLGetXMLValue(psUTMZoneNumber, nullptr, ""));
    1036           0 :                     oSRS.SetUTM(std::abs(nZone), nZone >= 0);
    1037             :                 }
    1038             :             }
    1039           0 :             else if (osProjName == "Universal Polar Stereographic")
    1040             :             {
    1041           0 :                 CPLXMLNode *psProjParamNode = CPLGetXMLNode(
    1042             :                     psGridCoordinateSystem,
    1043             :                     "Universal_Polar_Stereographic.Polar_Stereographic");
    1044           0 :                 if (psProjParamNode)
    1045             :                 {
    1046           0 :                     dfCenterLon =
    1047           0 :                         GetAngularValue(psProjParamNode,
    1048             :                                         "longitude_of_central_meridian") *
    1049             :                         dfLongitudeMultiplier;
    1050           0 :                     dfCenterLat = GetAngularValue(
    1051             :                         psProjParamNode, "latitude_of_projection_origin");
    1052           0 :                     dfScale = CPLAtof(CPLGetXMLValue(
    1053             :                         psProjParamNode, "scale_factor_at_projection_origin",
    1054             :                         "1"));
    1055           0 :                     oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
    1056             :                 }
    1057             :             }
    1058             :             else
    1059             :             {
    1060           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1061             :                          "grid_coordinate_system_name = %s not supported",
    1062             :                          osProjName.c_str());
    1063             :             }
    1064             :         }
    1065             :     }
    1066         162 :     else if (psMapProjection != nullptr)
    1067             :     {
    1068         161 :         osProjName = CPLGetXMLValue(psMapProjection, "map_projection_name", "");
    1069         161 :         if (!osProjName.empty())
    1070             :         {
    1071         161 :             CPLXMLNode *psProjParamNode = CPLGetXMLNode(
    1072             :                 psMapProjection,
    1073         322 :                 CPLString(osProjName).replaceAll(' ', '_').c_str());
    1074         161 :             if (psProjParamNode == nullptr &&
    1075             :                 // typo in https://pds.nasa.gov/pds4/cart/v1/PDS4_CART_1700.sch
    1076           0 :                 EQUAL(osProjName, "Orothographic"))
    1077             :             {
    1078             :                 psProjParamNode =
    1079           0 :                     CPLGetXMLNode(psMapProjection, "Orthographic");
    1080             :             }
    1081         161 :             bool bGotStdParallel1 = false;
    1082         161 :             bool bGotStdParallel2 = false;
    1083         161 :             bool bGotScale = false;
    1084         161 :             if (psProjParamNode)
    1085             :             {
    1086         161 :                 bool bGotCenterLon = false;
    1087         161 :                 dfCenterLon = GetAngularValue(psProjParamNode,
    1088             :                                               "longitude_of_central_meridian",
    1089             :                                               &bGotCenterLon) *
    1090             :                               dfLongitudeMultiplier;
    1091         161 :                 if (!bGotCenterLon)
    1092             :                 {
    1093           2 :                     dfCenterLon =
    1094           2 :                         GetAngularValue(psProjParamNode,
    1095             :                                         "straight_vertical_longitude_from_pole",
    1096             :                                         &bGotCenterLon) *
    1097             :                         dfLongitudeMultiplier;
    1098             :                 }
    1099         161 :                 dfCenterLat = GetAngularValue(psProjParamNode,
    1100             :                                               "latitude_of_projection_origin");
    1101         161 :                 dfStdParallel1 = GetAngularValue(
    1102             :                     psProjParamNode, "standard_parallel_1", &bGotStdParallel1);
    1103         161 :                 dfStdParallel2 = GetAngularValue(
    1104             :                     psProjParamNode, "standard_parallel_2", &bGotStdParallel2);
    1105             :                 const char *pszScaleParam =
    1106         161 :                     (osProjName == "Transverse Mercator")
    1107         161 :                         ? "scale_factor_at_central_meridian"
    1108         161 :                         : "scale_factor_at_projection_origin";
    1109             :                 const char *pszScaleVal =
    1110         161 :                     CPLGetXMLValue(psProjParamNode, pszScaleParam, nullptr);
    1111         161 :                 bGotScale = pszScaleVal != nullptr;
    1112         161 :                 dfScale = (pszScaleVal) ? CPLAtof(pszScaleVal) : 1.0;
    1113             : 
    1114             :                 dfMapProjectionRotation =
    1115         161 :                     GetAngularValue(psProjParamNode, "map_projection_rotation");
    1116             :             }
    1117             : 
    1118             :             CPLXMLNode *psObliqueAzimuth =
    1119         161 :                 CPLGetXMLNode(psProjParamNode, "Oblique_Line_Azimuth");
    1120             :             CPLXMLNode *psObliquePoint =
    1121         161 :                 CPLGetXMLNode(psProjParamNode, "Oblique_Line_Point");
    1122             : 
    1123         161 :             if (EQUAL(osProjName, "Equirectangular"))
    1124             :             {
    1125          53 :                 oSRS.SetEquirectangular2(dfCenterLat, dfCenterLon,
    1126             :                                          dfStdParallel1, 0, 0);
    1127             :             }
    1128         108 :             else if (EQUAL(osProjName, "Lambert Conformal Conic"))
    1129             :             {
    1130           4 :                 if (bGotScale)
    1131             :                 {
    1132           2 :                     if ((bGotStdParallel1 && dfStdParallel1 != dfCenterLat) ||
    1133           0 :                         (bGotStdParallel2 && dfStdParallel2 != dfCenterLat))
    1134             :                     {
    1135           0 :                         CPLError(
    1136             :                             CE_Warning, CPLE_AppDefined,
    1137             :                             "Ignoring standard_parallel_1 and/or "
    1138             :                             "standard_parallel_2 with LCC_1SP formulation");
    1139             :                     }
    1140           2 :                     oSRS.SetLCC1SP(dfCenterLat, dfCenterLon, dfScale, 0, 0);
    1141             :                 }
    1142             :                 else
    1143             :                 {
    1144           2 :                     oSRS.SetLCC(dfStdParallel1, dfStdParallel2, dfCenterLat,
    1145             :                                 dfCenterLon, 0, 0);
    1146             :                 }
    1147             :             }
    1148         104 :             else if (EQUAL(osProjName, "Mercator"))
    1149             :             {
    1150           6 :                 if (bGotScale)
    1151             :                 {
    1152           4 :                     oSRS.SetMercator(dfCenterLat,  // should be 0 normally
    1153             :                                      dfCenterLon, dfScale, 0.0, 0.0);
    1154             :                 }
    1155             :                 else
    1156             :                 {
    1157           2 :                     oSRS.SetMercator2SP(dfStdParallel1,
    1158             :                                         dfCenterLat,  // should be 0 normally
    1159             :                                         dfCenterLon, 0.0, 0.0);
    1160             :                 }
    1161             :             }
    1162          98 :             else if (EQUAL(osProjName, "Orthographic"))
    1163             :             {
    1164           2 :                 oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0.0, 0.0);
    1165             :             }
    1166          98 :             else if (EQUAL(osProjName, "Oblique Mercator") &&
    1167           2 :                      (psObliqueAzimuth != nullptr || psObliquePoint != nullptr))
    1168             :             {
    1169           4 :                 if (psObliqueAzimuth)
    1170             :                 {
    1171             :                     // Not sure of this
    1172           2 :                     dfCenterLon = CPLAtof(
    1173             :                         CPLGetXMLValue(psObliqueAzimuth,
    1174             :                                        "azimuth_measure_point_longitude", "0"));
    1175             : 
    1176           2 :                     double dfAzimuth = CPLAtof(CPLGetXMLValue(
    1177             :                         psObliqueAzimuth, "azimuthal_angle", "0"));
    1178           2 :                     oSRS.SetProjection(
    1179             :                         SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER);
    1180           2 :                     oSRS.SetNormProjParm(SRS_PP_LATITUDE_OF_CENTER,
    1181             :                                          dfCenterLat);
    1182           2 :                     oSRS.SetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER,
    1183             :                                          dfCenterLon);
    1184           2 :                     oSRS.SetNormProjParm(SRS_PP_AZIMUTH, dfAzimuth);
    1185             :                     // SetNormProjParm( SRS_PP_RECTIFIED_GRID_ANGLE,
    1186             :                     // dfRectToSkew );
    1187           2 :                     oSRS.SetNormProjParm(SRS_PP_SCALE_FACTOR, dfScale);
    1188           2 :                     oSRS.SetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    1189           2 :                     oSRS.SetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    1190             :                 }
    1191             :                 else
    1192             :                 {
    1193           2 :                     double dfLat1 = 0.0;
    1194           2 :                     double dfLong1 = 0.0;
    1195           2 :                     double dfLat2 = 0.0;
    1196           2 :                     double dfLong2 = 0.0;
    1197           2 :                     CPLXMLNode *psPoint = CPLGetXMLNode(
    1198             :                         psObliquePoint, "Oblique_Line_Point_Group");
    1199           2 :                     if (psPoint)
    1200             :                     {
    1201           2 :                         dfLat1 = CPLAtof(CPLGetXMLValue(
    1202             :                             psPoint, "oblique_line_latitude", "0.0"));
    1203           2 :                         dfLong1 = CPLAtof(CPLGetXMLValue(
    1204             :                             psPoint, "oblique_line_longitude", "0.0"));
    1205           2 :                         psPoint = psPoint->psNext;
    1206           2 :                         if (psPoint && psPoint->eType == CXT_Element &&
    1207           2 :                             EQUAL(psPoint->pszValue,
    1208             :                                   "Oblique_Line_Point_Group"))
    1209             :                         {
    1210           2 :                             dfLat2 = CPLAtof(CPLGetXMLValue(
    1211             :                                 psPoint, "oblique_line_latitude", "0.0"));
    1212           2 :                             dfLong2 = CPLAtof(CPLGetXMLValue(
    1213             :                                 psPoint, "oblique_line_longitude", "0.0"));
    1214             :                         }
    1215             :                     }
    1216           2 :                     oSRS.SetHOM2PNO(dfCenterLat, dfLat1, dfLong1, dfLat2,
    1217             :                                     dfLong2, dfScale, 0.0, 0.0);
    1218             :                 }
    1219             :             }
    1220          92 :             else if (EQUAL(osProjName, "Polar Stereographic"))
    1221             :             {
    1222           2 :                 oSRS.SetPS(dfCenterLat, dfCenterLon, dfScale, 0, 0);
    1223             :             }
    1224          90 :             else if (EQUAL(osProjName, "Polyconic"))
    1225             :             {
    1226           2 :                 oSRS.SetPolyconic(dfCenterLat, dfCenterLon, 0, 0);
    1227             :             }
    1228          88 :             else if (EQUAL(osProjName, "Sinusoidal"))
    1229             :             {
    1230           4 :                 oSRS.SetSinusoidal(dfCenterLon, 0, 0);
    1231             :             }
    1232          84 :             else if (EQUAL(osProjName, "Transverse Mercator"))
    1233             :             {
    1234          78 :                 oSRS.SetTM(dfCenterLat, dfCenterLon, dfScale, 0, 0);
    1235             :             }
    1236             : 
    1237             :             // Below values are valid map_projection_name according to
    1238             :             // the schematron but they don't have a dedicated element to
    1239             :             // hold the projection parameter. Assumed the schema is extended
    1240             :             // similarly to the existing for a few obvious ones
    1241           6 :             else if (EQUAL(osProjName, "Albers Conical Equal Area"))
    1242             :             {
    1243           0 :                 oSRS.SetACEA(dfStdParallel1, dfStdParallel2, dfCenterLat,
    1244             :                              dfCenterLon, 0.0, 0.0);
    1245             :             }
    1246           6 :             else if (EQUAL(osProjName, "Azimuthal Equidistant"))
    1247             :             {
    1248           0 :                 oSRS.SetAE(dfCenterLat, dfCenterLon, 0, 0);
    1249             :             }
    1250           6 :             else if (EQUAL(osProjName, "Equidistant Conic"))
    1251             :             {
    1252           0 :                 oSRS.SetEC(dfStdParallel1, dfStdParallel2, dfCenterLat,
    1253             :                            dfCenterLon, 0.0, 0.0);
    1254             :             }
    1255             :             // Unhandled: General Vertical Near-sided Projection
    1256           6 :             else if (EQUAL(osProjName, "Gnomonic"))
    1257             :             {
    1258           0 :                 oSRS.SetGnomonic(dfCenterLat, dfCenterLon, 0, 0);
    1259             :             }
    1260           6 :             else if (EQUAL(osProjName, "Lambert Azimuthal Equal Area"))
    1261             :             {
    1262           2 :                 oSRS.SetLAEA(dfCenterLat, dfCenterLon, 0, 0);
    1263             :             }
    1264           4 :             else if (EQUAL(osProjName, "Miller Cylindrical"))
    1265             :             {
    1266           0 :                 oSRS.SetMC(dfCenterLat, dfCenterLon, 0, 0);
    1267             :             }
    1268           4 :             else if (EQUAL(osProjName, "Orothographic")  // typo
    1269           4 :                      || EQUAL(osProjName, "Orthographic"))
    1270             :             {
    1271           0 :                 osProjName = "Orthographic";
    1272           0 :                 oSRS.SetOrthographic(dfCenterLat, dfCenterLon, 0, 0);
    1273             :             }
    1274           4 :             else if (EQUAL(osProjName, "Robinson"))
    1275             :             {
    1276           0 :                 oSRS.SetRobinson(dfCenterLon, 0, 0);
    1277             :             }
    1278             :             // Unhandled: Space Oblique Mercator
    1279           4 :             else if (EQUAL(osProjName, "Stereographic"))
    1280             :             {
    1281           0 :                 oSRS.SetStereographic(dfCenterLat, dfCenterLon, dfScale, 0, 0);
    1282             :             }
    1283           4 :             else if (EQUAL(osProjName, "van der Grinten"))
    1284             :             {
    1285           0 :                 oSRS.SetVDG(dfCenterLon, 0, 0);
    1286             :             }
    1287           4 :             else if (EQUAL(osProjName, "Oblique Cylindrical"))
    1288             :             {
    1289           4 :                 const double poleLatitude = GetAngularValue(
    1290             :                     psProjParamNode, "oblique_proj_pole_latitude");
    1291             :                 const double poleLongitude =
    1292           4 :                     GetAngularValue(psProjParamNode,
    1293             :                                     "oblique_proj_pole_longitude") *
    1294           4 :                     dfLongitudeMultiplier;
    1295           4 :                 const double poleRotation = GetAngularValue(
    1296             :                     psProjParamNode, "oblique_proj_pole_rotation");
    1297             : 
    1298           8 :                 CPLString oProj4String;
    1299             :                 // Cf isis3dataset.cpp comments for ObliqueCylindrical
    1300             :                 oProj4String.Printf("+proj=ob_tran +o_proj=eqc +o_lon_p=%.17g "
    1301             :                                     "+o_lat_p=%.17g +lon_0=%.17g",
    1302             :                                     -poleRotation, 180 - poleLatitude,
    1303           4 :                                     poleLongitude);
    1304           4 :                 oSRS.SetFromUserInput(oProj4String);
    1305             :             }
    1306             :             else
    1307             :             {
    1308           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1309             :                          "map_projection_name = %s not supported",
    1310             :                          osProjName.c_str());
    1311             :             }
    1312             :         }
    1313             :     }
    1314             :     else
    1315             :     {
    1316           1 :         CPLXMLNode *psGeographic = CPLGetXMLNode(psSR, "Geographic");
    1317           1 :         if (GetLayerCount() && psGeographic)
    1318             :         {
    1319             :             // do nothing
    1320             :         }
    1321             :         else
    1322             :         {
    1323           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1324             :                      "Planar.Map_Projection not found");
    1325             :         }
    1326             :     }
    1327             : 
    1328         162 :     if (oSRS.IsProjected())
    1329             :     {
    1330         161 :         oSRS.SetLinearUnits("Metre", 1.0);
    1331             :     }
    1332             : 
    1333         162 :     if (psGeodeticModel != nullptr)
    1334             :     {
    1335             :         const char *pszLatitudeType =
    1336         162 :             CPLGetXMLValue(psGeodeticModel, "latitude_type", "");
    1337         162 :         bool bIsOgraphic = EQUAL(pszLatitudeType, "Planetographic");
    1338             : 
    1339             :         const bool bUseLDD1930RadiusNames =
    1340         162 :             CPLGetXMLNode(psGeodeticModel, "a_axis_radius") != nullptr;
    1341             : 
    1342             :         // Before PDS CART schema pre-1.B.10.0 (pre LDD version 1.9.3.0),
    1343             :         // the confusing semi_major_radius, semi_minor_radius and polar_radius
    1344             :         // were used but did not follow the recommended
    1345             :         // FGDC names. Using both "semi" and "radius" in the same keyword,
    1346             :         // which both mean half, does not make sense.
    1347         162 :         const char *pszAAxis =
    1348         162 :             bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius";
    1349         162 :         const char *pszBAxis =
    1350         162 :             bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius";
    1351         162 :         const char *pszCAxis =
    1352         162 :             bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius";
    1353             : 
    1354         162 :         const double dfSemiMajor = GetLinearValue(psGeodeticModel, pszAAxis);
    1355             : 
    1356             :         // a_axis_radius and b_axis_radius should be the same in most cases
    1357             :         // unless a triaxial body is being defined. This should be extremely
    1358             :         // rare (and not used) since the IAU generally defines a best-fit sphere
    1359             :         // for triaxial bodies: https://astrogeology.usgs.gov/groups/IAU-WGCCRE
    1360         162 :         const double dfBValue = GetLinearValue(psGeodeticModel, pszBAxis);
    1361         162 :         if (dfSemiMajor != dfBValue)
    1362             :         {
    1363           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1364             :                      "%s = %f m, different from "
    1365             :                      "%s = %f, will be ignored",
    1366             :                      pszBAxis, dfBValue, pszAAxis, dfSemiMajor);
    1367             :         }
    1368             : 
    1369         162 :         const double dfPolarRadius = GetLinearValue(psGeodeticModel, pszCAxis);
    1370             :         // Use the polar_radius as the actual semi minor
    1371         162 :         const double dfSemiMinor = dfPolarRadius;
    1372             : 
    1373             :         // Compulsory
    1374         162 :         const char *pszTargetName = CPLGetXMLValue(
    1375             :             psProduct, "Observation_Area.Target_Identification.name",
    1376             :             "unknown");
    1377             : 
    1378         162 :         if (oSRS.IsProjected())
    1379             :         {
    1380         483 :             CPLString osProjTargetName = osProjName + " " + pszTargetName;
    1381         161 :             oSRS.SetProjCS(osProjTargetName);
    1382             :         }
    1383             : 
    1384         486 :         CPLString osGeogName = CPLString("GCS_") + pszTargetName;
    1385             : 
    1386             :         CPLString osSphereName =
    1387         324 :             CPLGetXMLValue(psGeodeticModel, "spheroid_name", pszTargetName);
    1388         324 :         CPLString osDatumName = "D_" + osSphereName;
    1389             : 
    1390             :         // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
    1391         162 :         double dfInvFlattening = 0;
    1392         162 :         if ((dfSemiMajor - dfSemiMinor) >= 0.00000001)
    1393             :         {
    1394          82 :             dfInvFlattening = dfSemiMajor / (dfSemiMajor - dfSemiMinor);
    1395             :         }
    1396             : 
    1397             :         //(if stereographic with center lat ==90) or (polar stereographic )
    1398         324 :         if ((EQUAL(osProjName, "STEREOGRAPHIC") && fabs(dfCenterLat) == 90) ||
    1399         162 :             (EQUAL(osProjName, "POLAR STEREOGRAPHIC")))
    1400             :         {
    1401           2 :             if (bIsOgraphic)
    1402             :             {
    1403           0 :                 oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
    1404             :                                dfSemiMajor, dfInvFlattening,
    1405             :                                "Reference_Meridian", 0.0);
    1406             :             }
    1407             :             else
    1408             :             {
    1409           2 :                 osSphereName += "_polarRadius";
    1410           2 :                 oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
    1411             :                                dfPolarRadius, 0.0, "Reference_Meridian", 0.0);
    1412             :             }
    1413             :         }
    1414         160 :         else if ((EQUAL(osProjName, "EQUIRECTANGULAR")) ||
    1415         107 :                  (EQUAL(osProjName, "ORTHOGRAPHIC")) ||
    1416         372 :                  (EQUAL(osProjName, "STEREOGRAPHIC")) ||
    1417         105 :                  (EQUAL(osProjName, "SINUSOIDAL")))
    1418             :         {
    1419          59 :             oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName, dfSemiMajor,
    1420             :                            0.0, "Reference_Meridian", 0.0);
    1421             :         }
    1422             :         else
    1423             :         {
    1424         101 :             if (bIsOgraphic)
    1425             :             {
    1426           2 :                 oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
    1427             :                                dfSemiMajor, dfInvFlattening,
    1428             :                                "Reference_Meridian", 0.0);
    1429             :             }
    1430             :             else
    1431             :             {
    1432          99 :                 oSRS.SetGeogCS(osGeogName, osDatumName, osSphereName,
    1433             :                                dfSemiMajor, 0.0, "Reference_Meridian", 0.0);
    1434             :             }
    1435             :         }
    1436             :     }
    1437             : 
    1438             :     CPLXMLNode *psPCI =
    1439         162 :         CPLGetXMLNode(psSR, "Planar.Planar_Coordinate_Information");
    1440         162 :     CPLXMLNode *psGT = CPLGetXMLNode(psSR, "Planar.Geo_Transformation");
    1441         162 :     if (psPCI && psGT)
    1442             :     {
    1443             :         const char *pszPCIEncoding =
    1444         158 :             CPLGetXMLValue(psPCI, "planar_coordinate_encoding_method", "");
    1445         158 :         CPLXMLNode *psCR = CPLGetXMLNode(psPCI, "Coordinate_Representation");
    1446         158 :         if (!EQUAL(pszPCIEncoding, "Coordinate Pair"))
    1447             :         {
    1448           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    1449             :                      "planar_coordinate_encoding_method = %s not supported",
    1450             :                      pszPCIEncoding);
    1451             :         }
    1452         158 :         else if (psCR != nullptr)
    1453             :         {
    1454         158 :             double dfXRes = GetResolutionValue(psCR, "pixel_resolution_x");
    1455         158 :             double dfYRes = GetResolutionValue(psCR, "pixel_resolution_y");
    1456         158 :             double dfULX = GetLinearValue(psGT, "upperleft_corner_x");
    1457         158 :             double dfULY = GetLinearValue(psGT, "upperleft_corner_y");
    1458             : 
    1459             :             // The PDS4 specification is not really clear about the
    1460             :             // origin convention, but it appears from
    1461             :             // https://github.com/OSGeo/gdal/issues/735 that it matches GDAL
    1462             :             // top-left corner of top-left pixel
    1463         158 :             m_gt.xorig = dfULX;
    1464         158 :             m_gt.xscale = dfXRes;
    1465         158 :             m_gt.xrot = 0.0;
    1466         158 :             m_gt.yorig = dfULY;
    1467         158 :             m_gt.yrot = 0.0;
    1468         158 :             m_gt.yscale = -dfYRes;
    1469         158 :             m_bGotTransform = true;
    1470             : 
    1471         158 :             if (dfMapProjectionRotation != 0)
    1472             :             {
    1473           4 :                 const double sin_rot =
    1474             :                     dfMapProjectionRotation == 90
    1475           4 :                         ? 1.0
    1476           0 :                         : sin(dfMapProjectionRotation / 180 * M_PI);
    1477           4 :                 const double cos_rot =
    1478             :                     dfMapProjectionRotation == 90
    1479           4 :                         ? 0.0
    1480           0 :                         : cos(dfMapProjectionRotation / 180 * M_PI);
    1481           4 :                 const double gt_1 = cos_rot * m_gt.xscale - sin_rot * m_gt.yrot;
    1482           4 :                 const double gt_2 = cos_rot * m_gt.xrot - sin_rot * m_gt.yscale;
    1483           4 :                 const double gt_0 = cos_rot * m_gt.xorig - sin_rot * m_gt.yorig;
    1484           4 :                 const double gt_4 = sin_rot * m_gt.xscale + cos_rot * m_gt.yrot;
    1485           4 :                 const double gt_5 = sin_rot * m_gt.xrot + cos_rot * m_gt.yscale;
    1486           4 :                 const double gt_3 = sin_rot * m_gt.xorig + cos_rot * m_gt.yorig;
    1487           4 :                 m_gt.xscale = gt_1;
    1488           4 :                 m_gt.xrot = gt_2;
    1489           4 :                 m_gt.xorig = gt_0;
    1490           4 :                 m_gt.yrot = gt_4;
    1491           4 :                 m_gt.yscale = gt_5;
    1492           4 :                 m_gt.yorig = gt_3;
    1493             :             }
    1494             :         }
    1495             :     }
    1496             : 
    1497         162 :     if (!oSRS.IsEmpty())
    1498             :     {
    1499         162 :         if (GetRasterCount())
    1500             :         {
    1501         158 :             m_oSRS = std::move(oSRS);
    1502             :         }
    1503           4 :         else if (GetLayerCount())
    1504             :         {
    1505           8 :             for (auto &poLayer : m_apoLayers)
    1506             :             {
    1507           4 :                 if (poLayer->GetGeomType() != wkbNone)
    1508             :                 {
    1509           4 :                     auto poSRSClone = oSRS.Clone();
    1510           4 :                     poLayer->SetSpatialRef(poSRSClone);
    1511           4 :                     poSRSClone->Release();
    1512             :                 }
    1513             :             }
    1514             :         }
    1515             :     }
    1516             : }
    1517             : 
    1518             : /************************************************************************/
    1519             : /*                              GetLayer()                              */
    1520             : /************************************************************************/
    1521             : 
    1522         319 : const OGRLayer *PDS4Dataset::GetLayer(int nIndex) const
    1523             : {
    1524         319 :     if (nIndex < 0 || nIndex >= GetLayerCount())
    1525           8 :         return nullptr;
    1526         311 :     return m_apoLayers[nIndex].get();
    1527             : }
    1528             : 
    1529             : /************************************************************************/
    1530             : /*                         FixupTableFilename()                         */
    1531             : /************************************************************************/
    1532             : 
    1533          97 : static std::string FixupTableFilename(const std::string &osFilename)
    1534             : {
    1535             :     VSIStatBufL sStat;
    1536          97 :     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    1537             :     {
    1538          97 :         return osFilename;
    1539             :     }
    1540           0 :     const std::string osExt = CPLGetExtensionSafe(osFilename.c_str());
    1541           0 :     if (!osExt.empty())
    1542             :     {
    1543           0 :         std::string osTry(osFilename);
    1544           0 :         if (osExt[0] >= 'a' && osExt[0] <= 'z')
    1545             :         {
    1546           0 :             osTry = CPLResetExtensionSafe(osFilename.c_str(),
    1547           0 :                                           CPLString(osExt).toupper());
    1548             :         }
    1549             :         else
    1550             :         {
    1551           0 :             osTry = CPLResetExtensionSafe(osFilename.c_str(),
    1552           0 :                                           CPLString(osExt).tolower());
    1553             :         }
    1554           0 :         if (VSIStatL(osTry.c_str(), &sStat) == 0)
    1555             :         {
    1556           0 :             return osTry;
    1557             :         }
    1558             :     }
    1559           0 :     return osFilename;
    1560             : }
    1561             : 
    1562             : /************************************************************************/
    1563             : /*                         OpenTableCharacter()                         */
    1564             : /************************************************************************/
    1565             : 
    1566          22 : bool PDS4Dataset::OpenTableCharacter(const char *pszFilename,
    1567             :                                      const CPLXMLNode *psTable)
    1568             : {
    1569          22 :     if (CPLHasPathTraversal(pszFilename))
    1570             :     {
    1571           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1572             :                  "OpenTableCharacter(): path traversal detected: %s",
    1573             :                  pszFilename);
    1574           0 :         return false;
    1575             :     }
    1576          44 :     std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1577          22 :     if (cpl::starts_with(osLayerName,
    1578          44 :                          CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
    1579          26 :         osLayerName = osLayerName.substr(
    1580          39 :             CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
    1581             : 
    1582          44 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1583          66 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1584             :     auto poLayer = std::make_unique<PDS4TableCharacter>(
    1585          44 :         this, osLayerName.c_str(), osFullFilename.c_str());
    1586          22 :     if (!poLayer->ReadTableDef(psTable))
    1587             :     {
    1588           0 :         return false;
    1589             :     }
    1590          22 :     m_apoLayers.push_back(
    1591          44 :         std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
    1592          22 :     return true;
    1593             : }
    1594             : 
    1595             : /************************************************************************/
    1596             : /*                          OpenTableBinary()                           */
    1597             : /************************************************************************/
    1598             : 
    1599          11 : bool PDS4Dataset::OpenTableBinary(const char *pszFilename,
    1600             :                                   const CPLXMLNode *psTable)
    1601             : {
    1602          11 :     if (CPLHasPathTraversal(pszFilename))
    1603             :     {
    1604           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1605             :                  "OpenTableBinary(): path traversal detected: %s", pszFilename);
    1606           0 :         return false;
    1607             :     }
    1608             : 
    1609          22 :     std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1610          11 :     if (cpl::starts_with(osLayerName,
    1611          22 :                          CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
    1612          20 :         osLayerName = osLayerName.substr(
    1613          30 :             CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
    1614             : 
    1615          22 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1616          33 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1617           0 :     auto poLayer = std::make_unique<PDS4TableBinary>(this, osLayerName.c_str(),
    1618          22 :                                                      osFullFilename.c_str());
    1619          11 :     if (!poLayer->ReadTableDef(psTable))
    1620             :     {
    1621           0 :         return false;
    1622             :     }
    1623          11 :     m_apoLayers.push_back(
    1624          22 :         std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
    1625          11 :     return true;
    1626             : }
    1627             : 
    1628             : /************************************************************************/
    1629             : /*                         OpenTableDelimited()                         */
    1630             : /************************************************************************/
    1631             : 
    1632          64 : bool PDS4Dataset::OpenTableDelimited(const char *pszFilename,
    1633             :                                      const CPLXMLNode *psTable)
    1634             : {
    1635          64 :     if (CPLHasPathTraversal(pszFilename))
    1636             :     {
    1637           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    1638             :                  "OpenTableDelimited(): path traversal detected: %s",
    1639             :                  pszFilename);
    1640           0 :         return false;
    1641             :     }
    1642             : 
    1643         128 :     std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1644          64 :     if (cpl::starts_with(osLayerName,
    1645         128 :                          CPLGetBasenameSafe(m_osXMLFilename.c_str()) + "_"))
    1646         120 :         osLayerName = osLayerName.substr(
    1647         180 :             CPLGetBasenameSafe(m_osXMLFilename.c_str()).size() + 1);
    1648             : 
    1649         128 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1650         192 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1651             :     auto poLayer = std::make_unique<PDS4DelimitedTable>(
    1652         128 :         this, osLayerName.c_str(), osFullFilename.c_str());
    1653          64 :     if (!poLayer->ReadTableDef(psTable))
    1654             :     {
    1655           0 :         return false;
    1656             :     }
    1657          64 :     m_apoLayers.push_back(
    1658         128 :         std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
    1659          64 :     return true;
    1660             : }
    1661             : 
    1662             : /************************************************************************/
    1663             : /*                          ConstantToDouble()                          */
    1664             : /************************************************************************/
    1665             : 
    1666         180 : static std::optional<double> ConstantToDouble(const char *pszItem,
    1667             :                                               const char *pszVal)
    1668             : {
    1669         180 :     if (STARTS_WITH(pszVal, "0x"))
    1670             :     {
    1671          21 :         if (strlen(pszVal) == strlen("0x") + 2 * sizeof(float))
    1672             :         {
    1673          15 :             char *endptr = nullptr;
    1674             :             const uint32_t nVal =
    1675          15 :                 static_cast<uint32_t>(std::strtoull(pszVal, &endptr, 0));
    1676          15 :             if (endptr == pszVal + strlen(pszVal))
    1677             :             {
    1678             :                 float fVal;
    1679          15 :                 memcpy(&fVal, &nVal, sizeof(nVal));
    1680          15 :                 return fVal;
    1681             :             }
    1682             :         }
    1683           6 :         else if (strlen(pszVal) == strlen("0x") + 2 * sizeof(double))
    1684             :         {
    1685           6 :             char *endptr = nullptr;
    1686             :             const uint64_t nVal =
    1687           6 :                 static_cast<uint64_t>(std::strtoull(pszVal, &endptr, 0));
    1688           6 :             if (endptr == pszVal + strlen(pszVal))
    1689             :             {
    1690             :                 double dfVal;
    1691           6 :                 memcpy(&dfVal, &nVal, sizeof(nVal));
    1692           6 :                 return dfVal;
    1693             :             }
    1694             :         }
    1695           0 :         CPLError(CE_Failure, CPLE_AppDefined, "Invalid value for '%s': '%s'",
    1696             :                  pszItem, pszVal);
    1697           0 :         return std::nullopt;
    1698             :     }
    1699             :     else
    1700             :     {
    1701         159 :         char *endptr = nullptr;
    1702         159 :         double dfVal = std::strtod(pszVal, &endptr);
    1703         159 :         if (endptr == pszVal + strlen(pszVal))
    1704             :         {
    1705         159 :             return dfVal;
    1706             :         }
    1707             :         else
    1708             :         {
    1709           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1710             :                      "Invalid value for '%s': '%s'", pszItem, pszVal);
    1711           0 :             return std::nullopt;
    1712             :         }
    1713             :     }
    1714             : }
    1715             : 
    1716             : /************************************************************************/
    1717             : /*                                Open()                                */
    1718             : /************************************************************************/
    1719             : 
    1720             : // See https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.xsd
    1721             : // and https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.sch
    1722         306 : std::unique_ptr<PDS4Dataset> PDS4Dataset::OpenInternal(GDALOpenInfo *poOpenInfo)
    1723             : {
    1724         306 :     if (!PDS4DriverIdentify(poOpenInfo))
    1725           0 :         return nullptr;
    1726             : 
    1727         612 :     CPLString osXMLFilename(poOpenInfo->pszFilename);
    1728         306 :     int nFAOIdxLookup = -1;
    1729         306 :     int nArrayIdxLookup = -1;
    1730         306 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:"))
    1731             :     {
    1732             :         char **papszTokens =
    1733          15 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
    1734          15 :         int nCount = CSLCount(papszTokens);
    1735          15 :         if (nCount == 5 && strlen(papszTokens[1]) == 1 &&
    1736           1 :             (papszTokens[2][0] == '\\' || papszTokens[2][0] == '/'))
    1737             :         {
    1738           1 :             osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1739           1 :             nFAOIdxLookup = atoi(papszTokens[3]);
    1740           1 :             nArrayIdxLookup = atoi(papszTokens[4]);
    1741             :         }
    1742          14 :         else if (nCount == 5 && (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1743           0 :                                  EQUAL(papszTokens[1], "/vsicurl/https")))
    1744             :         {
    1745           0 :             osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1746           0 :             nFAOIdxLookup = atoi(papszTokens[3]);
    1747           0 :             nArrayIdxLookup = atoi(papszTokens[4]);
    1748             :         }
    1749          14 :         else if (nCount == 4)
    1750             :         {
    1751          13 :             osXMLFilename = papszTokens[1];
    1752          13 :             nFAOIdxLookup = atoi(papszTokens[2]);
    1753          13 :             nArrayIdxLookup = atoi(papszTokens[3]);
    1754             :         }
    1755             :         else
    1756             :         {
    1757           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1758             :                      "Invalid syntax for PDS4 subdataset name");
    1759           1 :             CSLDestroy(papszTokens);
    1760           1 :             return nullptr;
    1761             :         }
    1762          14 :         CSLDestroy(papszTokens);
    1763             :     }
    1764             : 
    1765         610 :     CPLXMLTreeCloser oCloser(CPLParseXMLFile(osXMLFilename));
    1766         305 :     CPLXMLNode *psRoot = oCloser.get();
    1767         305 :     CPLStripXMLNamespace(psRoot, nullptr, TRUE);
    1768             : 
    1769         610 :     GDALAccess eAccess = STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:")
    1770         305 :                              ? GA_ReadOnly
    1771             :                              : poOpenInfo->eAccess;
    1772             : 
    1773         305 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    1774         305 :     if (psProduct == nullptr)
    1775             :     {
    1776           4 :         eAccess = GA_ReadOnly;
    1777           4 :         psProduct = CPLGetXMLNode(psRoot, "=Product_Ancillary");
    1778           4 :         if (psProduct == nullptr)
    1779             :         {
    1780           4 :             psProduct = CPLGetXMLNode(psRoot, "=Product_Collection");
    1781             :         }
    1782             :     }
    1783         305 :     if (psProduct == nullptr)
    1784             :     {
    1785           3 :         return nullptr;
    1786             :     }
    1787             : 
    1788             :     // Test case:
    1789             :     // https://starbase.jpl.nasa.gov/pds4/1700/dph_example_products/test_Images_DisplaySettings/TestPattern_Image/TestPattern.xml
    1790         302 :     const char *pszVertDir = CPLGetXMLValue(
    1791             :         psProduct,
    1792             :         "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
    1793             :         "vertical_display_direction",
    1794             :         "");
    1795         302 :     const bool bBottomToTop = EQUAL(pszVertDir, "Bottom to Top");
    1796             : 
    1797         302 :     const char *pszHorizDir = CPLGetXMLValue(
    1798             :         psProduct,
    1799             :         "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
    1800             :         "horizontal_display_direction",
    1801             :         "");
    1802         302 :     const bool bRightToLeft = EQUAL(pszHorizDir, "Right to Left");
    1803             : 
    1804         604 :     auto poDS = std::make_unique<PDS4Dataset>();
    1805         302 :     poDS->m_osXMLFilename = osXMLFilename;
    1806         302 :     poDS->eAccess = eAccess;
    1807         302 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1808             : 
    1809         604 :     CPLStringList aosSubdatasets;
    1810         302 :     int nFAOIdx = 0;
    1811        2946 :     for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
    1812        2644 :          psIter = psIter->psNext)
    1813             :     {
    1814        2646 :         if (psIter->eType != CXT_Element ||
    1815         933 :             (strcmp(psIter->pszValue, "File_Area_Observational") != 0 &&
    1816         602 :              strcmp(psIter->pszValue, "File_Area_Ancillary") != 0 &&
    1817         602 :              strcmp(psIter->pszValue, "File_Area_Inventory") != 0))
    1818             :         {
    1819        2314 :             continue;
    1820             :         }
    1821             : 
    1822         332 :         nFAOIdx++;
    1823         332 :         CPLXMLNode *psFile = CPLGetXMLNode(psIter, "File");
    1824         332 :         if (psFile == nullptr)
    1825             :         {
    1826           1 :             continue;
    1827             :         }
    1828         331 :         const char *pszFilename = CPLGetXMLValue(psFile, "file_name", nullptr);
    1829         331 :         if (pszFilename == nullptr)
    1830             :         {
    1831           1 :             continue;
    1832             :         }
    1833             : 
    1834         693 :         for (CPLXMLNode *psSubIter = psFile->psChild; psSubIter != nullptr;
    1835         363 :              psSubIter = psSubIter->psNext)
    1836             :         {
    1837         363 :             if (psSubIter->eType == CXT_Comment &&
    1838          14 :                 EQUAL(psSubIter->pszValue, PREEXISTING_BINARY_FILE))
    1839             :             {
    1840          14 :                 poDS->m_bCreatedFromExistingBinaryFile = true;
    1841             :             }
    1842             :         }
    1843             : 
    1844         330 :         int nArrayIdx = 0;
    1845         330 :         for (CPLXMLNode *psSubIter = psIter->psChild;
    1846        1050 :              (nFAOIdxLookup < 0 || nFAOIdxLookup == nFAOIdx) &&
    1847             :              psSubIter != nullptr;
    1848         720 :              psSubIter = psSubIter->psNext)
    1849             :         {
    1850         722 :             if (psSubIter->eType != CXT_Element)
    1851             :             {
    1852         504 :                 continue;
    1853             :             }
    1854         722 :             int nDIM = 0;
    1855         722 :             if (STARTS_WITH(psSubIter->pszValue, "Array_1D"))
    1856             :             {
    1857           0 :                 nDIM = 1;
    1858             :             }
    1859         722 :             else if (STARTS_WITH(psSubIter->pszValue, "Array_2D"))
    1860             :             {
    1861           6 :                 nDIM = 2;
    1862             :             }
    1863         716 :             else if (STARTS_WITH(psSubIter->pszValue, "Array_3D"))
    1864             :             {
    1865         241 :                 nDIM = 3;
    1866             :             }
    1867         475 :             else if (strcmp(psSubIter->pszValue, "Array") == 0)
    1868             :             {
    1869           3 :                 nDIM = atoi(CPLGetXMLValue(psSubIter, "axes", "0"));
    1870             :             }
    1871         472 :             else if (strcmp(psSubIter->pszValue, "Table_Character") == 0)
    1872             :             {
    1873          22 :                 poDS->OpenTableCharacter(pszFilename, psSubIter);
    1874          22 :                 continue;
    1875             :             }
    1876         450 :             else if (strcmp(psSubIter->pszValue, "Table_Binary") == 0)
    1877             :             {
    1878          11 :                 poDS->OpenTableBinary(pszFilename, psSubIter);
    1879          11 :                 continue;
    1880             :             }
    1881         439 :             else if (strcmp(psSubIter->pszValue, "Table_Delimited") == 0 ||
    1882         376 :                      strcmp(psSubIter->pszValue, "Inventory") == 0)
    1883             :             {
    1884          64 :                 poDS->OpenTableDelimited(pszFilename, psSubIter);
    1885          64 :                 continue;
    1886             :             }
    1887         625 :             if (nDIM == 0)
    1888             :             {
    1889         375 :                 continue;
    1890             :             }
    1891         250 :             if (!(nDIM >= 1 && nDIM <= 3))
    1892             :             {
    1893           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1894             :                          "Array with %d dimensions not supported", nDIM);
    1895           0 :                 continue;
    1896             :             }
    1897             : 
    1898         250 :             nArrayIdx++;
    1899             :             // Does it match a selected subdataset ?
    1900         250 :             if (nArrayIdxLookup > 0 && nArrayIdx != nArrayIdxLookup)
    1901             :             {
    1902          13 :                 continue;
    1903             :             }
    1904             : 
    1905             :             const char *pszArrayName =
    1906         237 :                 CPLGetXMLValue(psSubIter, "name", nullptr);
    1907             :             const char *pszArrayId =
    1908         237 :                 CPLGetXMLValue(psSubIter, "local_identifier", nullptr);
    1909             :             vsi_l_offset nOffset = static_cast<vsi_l_offset>(
    1910         237 :                 CPLAtoGIntBig(CPLGetXMLValue(psSubIter, "offset", "0")));
    1911             : 
    1912             :             const char *pszAxisIndexOrder =
    1913         237 :                 CPLGetXMLValue(psSubIter, "axis_index_order", "");
    1914         237 :             if (!EQUAL(pszAxisIndexOrder, "Last Index Fastest"))
    1915             :             {
    1916           1 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1917             :                          "axis_index_order = '%s' unhandled",
    1918             :                          pszAxisIndexOrder);
    1919           1 :                 continue;
    1920             :             }
    1921             : 
    1922             :             // Figure out data type
    1923             :             const char *pszDataType =
    1924         236 :                 CPLGetXMLValue(psSubIter, "Element_Array.data_type", "");
    1925         236 :             GDALDataType eDT = GDT_UInt8;
    1926         236 :             bool bLSBOrder = strstr(pszDataType, "LSB") != nullptr;
    1927             : 
    1928             :             // ComplexLSB16', 'ComplexLSB8', 'ComplexMSB16', 'ComplexMSB8',
    1929             :             // 'IEEE754LSBDouble', 'IEEE754LSBSingle', 'IEEE754MSBDouble',
    1930             :             // 'IEEE754MSBSingle', 'SignedBitString', 'SignedByte',
    1931             :             // 'SignedLSB2', 'SignedLSB4', 'SignedLSB8', 'SignedMSB2',
    1932             :             // 'SignedMSB4', 'SignedMSB8', 'UnsignedBitString', 'UnsignedByte',
    1933             :             // 'UnsignedLSB2', 'UnsignedLSB4', 'UnsignedLSB8', 'UnsignedMSB2',
    1934             :             // 'UnsignedMSB4', 'UnsignedMSB8'
    1935         236 :             if (EQUAL(pszDataType, "ComplexLSB16") ||
    1936         232 :                 EQUAL(pszDataType, "ComplexMSB16"))
    1937             :             {
    1938           6 :                 eDT = GDT_CFloat64;
    1939             :             }
    1940         230 :             else if (EQUAL(pszDataType, "ComplexLSB8") ||
    1941         226 :                      EQUAL(pszDataType, "ComplexMSB8"))
    1942             :             {
    1943           4 :                 eDT = GDT_CFloat32;
    1944             :             }
    1945         226 :             else if (EQUAL(pszDataType, "IEEE754LSBDouble") ||
    1946         219 :                      EQUAL(pszDataType, "IEEE754MSBDouble"))
    1947             :             {
    1948           7 :                 eDT = GDT_Float64;
    1949             :             }
    1950         219 :             else if (EQUAL(pszDataType, "IEEE754LSBSingle") ||
    1951         208 :                      EQUAL(pszDataType, "IEEE754MSBSingle"))
    1952             :             {
    1953          12 :                 eDT = GDT_Float32;
    1954             :             }
    1955             :             // SignedBitString unhandled
    1956         207 :             else if (EQUAL(pszDataType, "SignedByte"))
    1957             :             {
    1958           6 :                 eDT = GDT_Int8;
    1959             :             }
    1960         201 :             else if (EQUAL(pszDataType, "SignedLSB2") ||
    1961         197 :                      EQUAL(pszDataType, "SignedMSB2"))
    1962             :             {
    1963           6 :                 eDT = GDT_Int16;
    1964             :             }
    1965         195 :             else if (EQUAL(pszDataType, "SignedLSB4") ||
    1966         191 :                      EQUAL(pszDataType, "SignedMSB4"))
    1967             :             {
    1968           4 :                 eDT = GDT_Int32;
    1969             :             }
    1970         191 :             else if (EQUAL(pszDataType, "SignedLSB8") ||
    1971         187 :                      EQUAL(pszDataType, "SignedMSB8"))
    1972             :             {
    1973           4 :                 eDT = GDT_Int64;
    1974             :             }
    1975         187 :             else if (EQUAL(pszDataType, "UnsignedByte"))
    1976             :             {
    1977         170 :                 eDT = GDT_UInt8;
    1978             :             }
    1979          17 :             else if (EQUAL(pszDataType, "UnsignedLSB2") ||
    1980           9 :                      EQUAL(pszDataType, "UnsignedMSB2"))
    1981             :             {
    1982           8 :                 eDT = GDT_UInt16;
    1983             :             }
    1984           9 :             else if (EQUAL(pszDataType, "UnsignedLSB4") ||
    1985           5 :                      EQUAL(pszDataType, "UnsignedMSB4"))
    1986             :             {
    1987           4 :                 eDT = GDT_UInt32;
    1988             :             }
    1989           5 :             else if (EQUAL(pszDataType, "UnsignedLSB8") ||
    1990           1 :                      EQUAL(pszDataType, "UnsignedMSB8"))
    1991             :             {
    1992           4 :                 eDT = GDT_UInt64;
    1993             :             }
    1994             :             else
    1995             :             {
    1996           1 :                 CPLDebug("PDS4", "data_type = '%s' unhandled", pszDataType);
    1997           1 :                 continue;
    1998             :             }
    1999             : 
    2000         235 :             poDS->m_osUnits =
    2001         470 :                 CPLGetXMLValue(psSubIter, "Element_Array.unit", "");
    2002             : 
    2003         235 :             double dfValueOffset = CPLAtof(
    2004             :                 CPLGetXMLValue(psSubIter, "Element_Array.value_offset", "0"));
    2005         235 :             double dfValueScale = CPLAtof(
    2006             :                 CPLGetXMLValue(psSubIter, "Element_Array.scaling_factor", "1"));
    2007             : 
    2008             :             // Parse Axis_Array elements
    2009         235 :             int l_nBands = 1;
    2010         235 :             int nLines = 0;
    2011         235 :             int nSamples = 0;
    2012         235 :             std::vector<int> anElements;
    2013         235 :             std::vector<std::string> axisNames;
    2014         235 :             std::vector<char> dimSemantics;
    2015         235 :             anElements.resize(nDIM);
    2016         235 :             axisNames.resize(nDIM);
    2017         235 :             dimSemantics.resize(nDIM);
    2018         235 :             int iBandIdx = -1;
    2019         235 :             int iLineIdx = -1;
    2020         235 :             int iSampleIdx = -1;
    2021         235 :             int nAxisOKCount = 0;
    2022         235 :             for (CPLXMLNode *psAxisIter = psSubIter->psChild;
    2023        2161 :                  psAxisIter != nullptr; psAxisIter = psAxisIter->psNext)
    2024             :             {
    2025        1927 :                 if (psAxisIter->eType != CXT_Element ||
    2026        1927 :                     strcmp(psAxisIter->pszValue, "Axis_Array") != 0)
    2027             :                 {
    2028        1224 :                     continue;
    2029             :                 }
    2030             :                 const char *pszAxisName =
    2031         703 :                     CPLGetXMLValue(psAxisIter, "axis_name", nullptr);
    2032             :                 const char *pszElements =
    2033         703 :                     CPLGetXMLValue(psAxisIter, "elements", nullptr);
    2034             :                 const char *pszSequenceNumber =
    2035         703 :                     CPLGetXMLValue(psAxisIter, "sequence_number", nullptr);
    2036         703 :                 if (pszAxisName == nullptr || pszElements == nullptr ||
    2037             :                     pszSequenceNumber == nullptr)
    2038             :                 {
    2039           1 :                     continue;
    2040             :                 }
    2041         702 :                 int nSeqNumber = atoi(pszSequenceNumber);
    2042         702 :                 if (nSeqNumber < 1 || nSeqNumber > nDIM)
    2043             :                 {
    2044           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2045             :                              "Invalid sequence_number = %s", pszSequenceNumber);
    2046           2 :                     continue;
    2047             :                 }
    2048         700 :                 int nElements = atoi(pszElements);
    2049         700 :                 if (nElements <= 0)
    2050             :                 {
    2051           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    2052             :                              "Invalid elements = %s", pszElements);
    2053           1 :                     continue;
    2054             :                 }
    2055             : 
    2056         699 :                 const int nIdx = nSeqNumber - 1;
    2057         699 :                 if (STARTS_WITH_CI(pszAxisName, "Line"))
    2058             :                 {
    2059         234 :                     if (iLineIdx < 0)
    2060         234 :                         iLineIdx = nIdx;
    2061             :                     else
    2062             :                     {
    2063           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2064             :                                  "Several axis with Line identifier");
    2065           0 :                         break;
    2066             :                     }
    2067             :                 }
    2068         465 :                 else if (STARTS_WITH_CI(pszAxisName, "Sample"))
    2069             :                 {
    2070         234 :                     if (iSampleIdx < 0)
    2071         234 :                         iSampleIdx = nIdx;
    2072             :                     else
    2073             :                     {
    2074           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2075             :                                  "Several axis with Sample identifier");
    2076           0 :                         break;
    2077             :                     }
    2078             :                 }
    2079         231 :                 else if (STARTS_WITH_CI(pszAxisName, "Band"))
    2080             :                 {
    2081         229 :                     if (iBandIdx < 0)
    2082         228 :                         iBandIdx = nIdx;
    2083             :                     else
    2084             :                     {
    2085           1 :                         CPLError(CE_Warning, CPLE_AppDefined,
    2086             :                                  "Several axis with Band identifier");
    2087           1 :                         break;
    2088             :                     }
    2089             :                 }
    2090             : 
    2091         698 :                 anElements[nIdx] = nElements;
    2092         698 :                 axisNames[nIdx] = pszAxisName;
    2093             : 
    2094         698 :                 ++nAxisOKCount;
    2095             :             }
    2096             : 
    2097         235 :             if (nAxisOKCount != nDIM)
    2098             :             {
    2099           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    2100             :                          "Found only %d Axis_Array elements. %d expected",
    2101             :                          nAxisOKCount, nDIM);
    2102           1 :                 continue;
    2103             :             }
    2104             : 
    2105         234 :             if (nDIM == 1)
    2106             :             {
    2107           0 :                 dimSemantics[0] = 'S';
    2108           0 :                 nLines = 1;
    2109           0 :                 nSamples = anElements[0];
    2110             :             }
    2111         234 :             else if (nDIM == 2)
    2112             :             {
    2113           6 :                 if (iLineIdx < 0 || iSampleIdx < 0)
    2114             :                 {
    2115           0 :                     CPLDebug("PDS4", "Assume that axis %s is Line",
    2116           0 :                              axisNames[0].c_str());
    2117           0 :                     CPLDebug("PDS4", "Assume that axis %s is Sample",
    2118           0 :                              axisNames[1].c_str());
    2119           0 :                     iLineIdx = 0;
    2120           0 :                     iSampleIdx = 1;
    2121             :                 }
    2122           6 :                 CPLAssert(iLineIdx >= 0 && iLineIdx < 2);
    2123           6 :                 CPLAssert(iSampleIdx >= 0 && iSampleIdx < 2);
    2124           6 :                 dimSemantics[iLineIdx] = 'L';
    2125           6 :                 dimSemantics[iSampleIdx] = 'S';
    2126           6 :                 nLines = anElements[iLineIdx];
    2127           6 :                 nSamples = anElements[iSampleIdx];
    2128             :             }
    2129             :             else /* if (nDim == 3) */
    2130             :             {
    2131         228 :                 if (iLineIdx < 0 || iSampleIdx < 0)
    2132             :                 {
    2133           0 :                     CPLDebug("PDS4", "Assume that axis %s is Band",
    2134           0 :                              axisNames[0].c_str());
    2135           0 :                     CPLDebug("PDS4", "Assume that axis %s is Line",
    2136           0 :                              axisNames[1].c_str());
    2137           0 :                     CPLDebug("PDS4", "Assume that axis %s is Sample",
    2138           0 :                              axisNames[2].c_str());
    2139           0 :                     iBandIdx = 0;
    2140           0 :                     iLineIdx = 1;
    2141           0 :                     iSampleIdx = 2;
    2142             :                 }
    2143         228 :                 else if (iBandIdx < 0)
    2144             :                 {
    2145           1 :                     CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
    2146           1 :                     CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
    2147           1 :                     bool abUsedIndices[3] = {false, false, false};
    2148           1 :                     abUsedIndices[iLineIdx] = true;
    2149           1 :                     abUsedIndices[iSampleIdx] = true;
    2150           4 :                     for (int i = 0; i < 3; ++i)
    2151             :                     {
    2152           3 :                         if (!abUsedIndices[i])
    2153           1 :                             iBandIdx = i;
    2154             :                     }
    2155             :                 }
    2156         228 :                 CPLAssert(iLineIdx >= 0 && iLineIdx < 3);
    2157         228 :                 CPLAssert(iSampleIdx >= 0 && iSampleIdx < 3);
    2158         228 :                 CPLAssert(iBandIdx >= 0 && iSampleIdx < 3);
    2159         228 :                 dimSemantics[iBandIdx] = 'B';
    2160         228 :                 dimSemantics[iLineIdx] = 'L';
    2161         228 :                 dimSemantics[iSampleIdx] = 'S';
    2162         228 :                 l_nBands = anElements[iBandIdx];
    2163         228 :                 nLines = anElements[iLineIdx];
    2164         228 :                 nSamples = anElements[iSampleIdx];
    2165             :             }
    2166             : 
    2167         468 :             if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
    2168         234 :                 !GDALCheckBandCount(l_nBands, FALSE))
    2169             :             {
    2170           1 :                 continue;
    2171             :             }
    2172             : 
    2173             :             // Compute pixel, line and band spacing
    2174         233 :             vsi_l_offset nSpacing = GDALGetDataTypeSizeBytes(eDT);
    2175         233 :             int nPixelOffset = 0;
    2176         233 :             int nLineOffset = 0;
    2177         233 :             vsi_l_offset nBandOffset = 0;
    2178         233 :             int nCountPreviousDim = 1;
    2179         924 :             for (int i = nDIM - 1; i >= 0; i--)
    2180             :             {
    2181         693 :                 if (dimSemantics[i] == 'S')
    2182             :                 {
    2183         233 :                     if (nCountPreviousDim > 0 &&
    2184         233 :                         nSpacing > static_cast<vsi_l_offset>(INT_MAX /
    2185             :                                                              nCountPreviousDim))
    2186             :                     {
    2187           1 :                         CPLError(CE_Failure, CPLE_NotSupported,
    2188             :                                  "Integer overflow");
    2189           1 :                         return nullptr;
    2190             :                     }
    2191         232 :                     nPixelOffset =
    2192         232 :                         static_cast<int>(nSpacing * nCountPreviousDim);
    2193         232 :                     nSpacing = nPixelOffset;
    2194             :                 }
    2195         460 :                 else if (dimSemantics[i] == 'L')
    2196             :                 {
    2197         233 :                     if (nCountPreviousDim > 0 &&
    2198         233 :                         nSpacing > static_cast<vsi_l_offset>(INT_MAX /
    2199             :                                                              nCountPreviousDim))
    2200             :                     {
    2201           1 :                         CPLError(CE_Failure, CPLE_NotSupported,
    2202             :                                  "Integer overflow");
    2203           1 :                         return nullptr;
    2204             :                     }
    2205         232 :                     nLineOffset =
    2206         232 :                         static_cast<int>(nSpacing * nCountPreviousDim);
    2207         232 :                     nSpacing = nLineOffset;
    2208             :                 }
    2209             :                 else
    2210             :                 {
    2211         227 :                     nBandOffset = nSpacing * nCountPreviousDim;
    2212         227 :                     nSpacing = nBandOffset;
    2213             :                 }
    2214         691 :                 nCountPreviousDim = anElements[i];
    2215             :             }
    2216             : 
    2217             :             // Retrieve no data value
    2218         231 :             bool bNoDataSet = false;
    2219         231 :             double dfNoData = 0.0;
    2220         231 :             bool bNoDataSetInt64 = false;
    2221         231 :             int64_t nNoDataInt64 = 0;
    2222         231 :             bool bNoDataSetUInt64 = false;
    2223         231 :             int64_t nNoDataUInt64 = 0;
    2224         231 :             std::vector<double> adfConstants;
    2225         231 :             CPLXMLNode *psSC = CPLGetXMLNode(psSubIter, "Special_Constants");
    2226         231 :             if (psSC)
    2227             :             {
    2228             :                 const auto GetNoDataFromConstant =
    2229          76 :                     [psSC, eDT, &bNoDataSetInt64, &nNoDataInt64,
    2230             :                      &bNoDataSetUInt64, &nNoDataUInt64, &bNoDataSet,
    2231         372 :                      &dfNoData](const char *pszConstantName)
    2232             :                 {
    2233          76 :                     if (const char *pszVal =
    2234          76 :                             CPLGetXMLValue(psSC, pszConstantName, nullptr))
    2235             :                     {
    2236          75 :                         if (eDT == GDT_Int64)
    2237             :                         {
    2238           2 :                             bNoDataSetInt64 = true;
    2239           2 :                             nNoDataInt64 = std::strtoll(pszVal, nullptr, 10);
    2240             :                         }
    2241          73 :                         else if (eDT == GDT_UInt64)
    2242             :                         {
    2243           2 :                             bNoDataSetUInt64 = true;
    2244           2 :                             nNoDataUInt64 = std::strtoull(pszVal, nullptr, 10);
    2245             :                         }
    2246             :                         else
    2247             :                         {
    2248             :                             auto val =
    2249          71 :                                 ConstantToDouble(pszConstantName, pszVal);
    2250          71 :                             if (val)
    2251             :                             {
    2252          71 :                                 bNoDataSet = true;
    2253          71 :                                 dfNoData = *val;
    2254             :                             }
    2255             :                         }
    2256          75 :                         return true;
    2257             :                     }
    2258           1 :                     return false;
    2259          75 :                 };
    2260             : 
    2261          75 :                 if (!GetNoDataFromConstant("missing_constant"))
    2262             :                 {
    2263             :                     // For example in https://d34uoeqxvp7znu.cloudfront.net/data/l2/200908/20090811/m3g20090811t012112_l2.xml
    2264           1 :                     GetNoDataFromConstant("invalid_constant");
    2265             :                 }
    2266             : 
    2267          75 :                 const char *const apszConstantNames[] = {
    2268             :                     "saturated_constant",
    2269             :                     "missing_constant",
    2270             :                     "error_constant",
    2271             :                     "invalid_constant",
    2272             :                     "unknown_constant",
    2273             :                     "not_applicable_constant",
    2274             :                     "high_instrument_saturation",
    2275             :                     "high_representation_saturation",
    2276             :                     "low_instrument_saturation",
    2277             :                     "low_representation_saturation"};
    2278         825 :                 for (const char *pszItem : apszConstantNames)
    2279             :                 {
    2280         750 :                     if (const char *pszConstant =
    2281         750 :                             CPLGetXMLValue(psSC, pszItem, nullptr))
    2282             :                     {
    2283         109 :                         auto val = ConstantToDouble(pszItem, pszConstant);
    2284         109 :                         if (val)
    2285             :                         {
    2286         109 :                             adfConstants.push_back(*val);
    2287             :                         }
    2288             :                     }
    2289             :                 }
    2290             :             }
    2291             : 
    2292             :             // Add subdatasets
    2293         231 :             const int nSDSIdx = 1 + aosSubdatasets.size() / 2;
    2294             :             aosSubdatasets.SetNameValue(
    2295             :                 CPLSPrintf("SUBDATASET_%d_NAME", nSDSIdx),
    2296             :                 CPLSPrintf("PDS4:%s:%d:%d", osXMLFilename.c_str(), nFAOIdx,
    2297         231 :                            nArrayIdx));
    2298             :             aosSubdatasets.SetNameValue(
    2299             :                 CPLSPrintf("SUBDATASET_%d_DESC", nSDSIdx),
    2300             :                 CPLSPrintf("Image file %s, array %s", pszFilename,
    2301             :                            pszArrayName ? pszArrayName
    2302         231 :                            : pszArrayId ? pszArrayId
    2303         462 :                                         : CPLSPrintf("%d", nArrayIdx)));
    2304             : 
    2305         231 :             if (poDS->nBands != 0)
    2306          14 :                 continue;
    2307             : 
    2308             :             const std::string osImageFullFilename = CPLFormFilenameSafe(
    2309         217 :                 CPLGetPathSafe(osXMLFilename.c_str()).c_str(), pszFilename,
    2310         217 :                 nullptr);
    2311         217 :             if (CPLHasPathTraversal(pszFilename))
    2312             :             {
    2313           0 :                 CPLError(CE_Failure, CPLE_NotSupported,
    2314             :                          "Path traversal detected in %s", pszFilename);
    2315           0 :                 return nullptr;
    2316             :             }
    2317         217 :             VSILFILE *fp = VSIFOpenExL(
    2318             :                 osImageFullFilename.c_str(),
    2319         217 :                 (poOpenInfo->eAccess == GA_Update) ? "rb+" : "rb", true);
    2320         217 :             if (fp == nullptr)
    2321             :             {
    2322           1 :                 CPLError(CE_Warning, CPLE_FileIO, "Cannot open %s: %s",
    2323             :                          osImageFullFilename.c_str(), VSIGetLastErrorMsg());
    2324           1 :                 continue;
    2325             :             }
    2326         216 :             poDS->nRasterXSize = nSamples;
    2327         216 :             poDS->nRasterYSize = nLines;
    2328         216 :             poDS->m_osImageFilename = osImageFullFilename;
    2329         216 :             poDS->m_fpImage = fp;
    2330         216 :             poDS->m_bIsLSB = bLSBOrder;
    2331             : 
    2332          35 :             if (l_nBands > 1 && dimSemantics[0] == 'B' &&
    2333         251 :                 dimSemantics[1] == 'L' && dimSemantics[2] == 'S')
    2334             :             {
    2335          27 :                 poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
    2336             :                                                    GDAL_MDD_IMAGE_STRUCTURE);
    2337             :             }
    2338          35 :             if (l_nBands > 1 && dimSemantics[0] == 'L' &&
    2339         251 :                 dimSemantics[1] == 'S' && dimSemantics[2] == 'B')
    2340             :             {
    2341           6 :                 poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    2342             :                                                    GDAL_MDD_IMAGE_STRUCTURE);
    2343             :             }
    2344             : 
    2345         216 :             CPLXMLNode *psOS = CPLGetXMLNode(psSubIter, "Object_Statistics");
    2346         216 :             const char *pszMin = nullptr;
    2347         216 :             const char *pszMax = nullptr;
    2348         216 :             const char *pszMean = nullptr;
    2349         216 :             const char *pszStdDev = nullptr;
    2350         216 :             if (psOS)
    2351             :             {
    2352          17 :                 pszMin = CPLGetXMLValue(psOS, "minimum", nullptr);
    2353          17 :                 pszMax = CPLGetXMLValue(psOS, "maximum", nullptr);
    2354          17 :                 pszMean = CPLGetXMLValue(psOS, "mean", nullptr);
    2355          17 :                 pszStdDev = CPLGetXMLValue(psOS, "standard_deviation", nullptr);
    2356             :             }
    2357             : 
    2358         501 :             for (int i = 0; i < l_nBands; i++)
    2359             :             {
    2360         285 :                 vsi_l_offset nThisBandOffset = nOffset + nBandOffset * i;
    2361         285 :                 if (bBottomToTop)
    2362             :                 {
    2363           2 :                     nThisBandOffset +=
    2364           2 :                         static_cast<vsi_l_offset>(nLines - 1) * nLineOffset;
    2365             :                 }
    2366         285 :                 if (bRightToLeft)
    2367             :                 {
    2368           1 :                     nThisBandOffset +=
    2369           1 :                         static_cast<vsi_l_offset>(nSamples - 1) * nPixelOffset;
    2370             :                 }
    2371             :                 auto poBand = std::make_unique<PDS4RawRasterBand>(
    2372         285 :                     poDS.get(), i + 1, poDS->m_fpImage, nThisBandOffset,
    2373         285 :                     bRightToLeft ? -nPixelOffset : nPixelOffset,
    2374         285 :                     bBottomToTop ? -nLineOffset : nLineOffset, eDT,
    2375         285 :                     bLSBOrder ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    2376         285 :                               : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
    2377         285 :                 if (!poBand->IsValid())
    2378           0 :                     return nullptr;
    2379         285 :                 if (bNoDataSet)
    2380             :                 {
    2381          75 :                     poBand->SetNoDataValue(dfNoData);
    2382             :                 }
    2383         210 :                 else if (bNoDataSetInt64)
    2384             :                 {
    2385           2 :                     poBand->SetNoDataValueAsInt64(nNoDataInt64);
    2386             :                 }
    2387         208 :                 else if (bNoDataSetUInt64)
    2388             :                 {
    2389           2 :                     poBand->SetNoDataValueAsUInt64(nNoDataUInt64);
    2390             :                 }
    2391         285 :                 poBand->SetOffset(dfValueOffset);
    2392         285 :                 poBand->SetScale(dfValueScale);
    2393             : 
    2394         285 :                 if (l_nBands == 1)
    2395             :                 {
    2396         181 :                     if (pszMin)
    2397             :                     {
    2398          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2399             :                             "STATISTICS_MINIMUM", pszMin);
    2400             :                     }
    2401         181 :                     if (pszMax)
    2402             :                     {
    2403          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2404             :                             "STATISTICS_MAXIMUM", pszMax);
    2405             :                     }
    2406         181 :                     if (pszMean)
    2407             :                     {
    2408          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2409             :                             "STATISTICS_MEAN", pszMean);
    2410             :                     }
    2411         181 :                     if (pszStdDev)
    2412             :                     {
    2413          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2414             :                             "STATISTICS_STDDEV", pszStdDev);
    2415             :                     }
    2416             :                 }
    2417             : 
    2418             :                 // Only instantiate explicit mask band if we have at least one
    2419             :                 // special constant (that is not the missing_constant,
    2420             :                 // already exposed as nodata value)
    2421         558 :                 if (!GDALDataTypeIsComplex(eDT) &&
    2422         538 :                     (CPLTestBool(CPLGetConfigOption("PDS4_FORCE_MASK", "NO")) ||
    2423         499 :                      adfConstants.size() >= 2 ||
    2424         282 :                      (adfConstants.size() == 1 && !bNoDataSet)))
    2425             :                 {
    2426          86 :                     poBand->SetMaskBand(std::make_unique<PDS4MaskBand>(
    2427          86 :                         poBand.get(), adfConstants));
    2428             :                 }
    2429             : 
    2430         285 :                 poDS->SetBand(i + 1, std::move(poBand));
    2431             :             }
    2432             :         }
    2433             :     }
    2434             : 
    2435         300 :     if (nFAOIdxLookup < 0 && aosSubdatasets.size() > 2)
    2436             :     {
    2437          10 :         poDS->GDALDataset::SetMetadata(aosSubdatasets.List(),
    2438             :                                        GDAL_MDD_SUBDATASETS);
    2439             :     }
    2440         290 :     else if (poDS->nBands == 0 &&
    2441         300 :              (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    2442          10 :              (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    2443             :     {
    2444           5 :         return nullptr;
    2445             :     }
    2446         285 :     else if (poDS->m_apoLayers.empty() &&
    2447         294 :              (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    2448           9 :              (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
    2449             :     {
    2450           0 :         return nullptr;
    2451             :     }
    2452             : 
    2453             :     // Expose XML content in xml:PDS4 metadata domain
    2454         295 :     GByte *pabyRet = nullptr;
    2455         295 :     CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osXMLFilename, &pabyRet, nullptr,
    2456             :                                      10 * 1024 * 1024));
    2457         295 :     if (pabyRet)
    2458             :     {
    2459         295 :         char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
    2460         295 :         poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
    2461             :     }
    2462         295 :     VSIFree(pabyRet);
    2463             : 
    2464             :     /*--------------------------------------------------------------------------*/
    2465             :     /*  Parse georeferencing info */
    2466             :     /*--------------------------------------------------------------------------*/
    2467         295 :     poDS->ReadGeoreferencing(psProduct);
    2468             : 
    2469             :     /*--------------------------------------------------------------------------*/
    2470             :     /*  Check for overviews */
    2471             :     /*--------------------------------------------------------------------------*/
    2472         295 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    2473             : 
    2474             :     /*--------------------------------------------------------------------------*/
    2475             :     /*  Initialize any PAM information */
    2476             :     /*--------------------------------------------------------------------------*/
    2477         295 :     poDS->SetDescription(poOpenInfo->pszFilename);
    2478         295 :     poDS->TryLoadXML();
    2479             : 
    2480         295 :     return poDS;
    2481             : }
    2482             : 
    2483             : /************************************************************************/
    2484             : /*                          IsCARTVersionGTE()                          */
    2485             : /************************************************************************/
    2486             : 
    2487             : // Returns true is pszCur >= pszRef
    2488             : // Must be things like 1900, 1B00, 1D00_1933 ...
    2489         489 : static bool IsCARTVersionGTE(const char *pszCur, const char *pszRef)
    2490             : {
    2491         489 :     return strcmp(pszCur, pszRef) >= 0;
    2492             : }
    2493             : 
    2494             : /************************************************************************/
    2495             : /*                        WriteGeoreferencing()                         */
    2496             : /************************************************************************/
    2497             : 
    2498          98 : void PDS4Dataset::WriteGeoreferencing(CPLXMLNode *psCart,
    2499             :                                       const char *pszCARTVersion)
    2500             : {
    2501          98 :     bool bHasBoundingBox = false;
    2502          98 :     double adfX[4] = {0};
    2503          98 :     double adfY[4] = {0};
    2504          98 :     CPLString osPrefix;
    2505          98 :     const char *pszColon = strchr(psCart->pszValue, ':');
    2506          98 :     if (pszColon)
    2507          98 :         osPrefix.assign(psCart->pszValue, pszColon - psCart->pszValue + 1);
    2508             : 
    2509          98 :     if (m_bGotTransform)
    2510             :     {
    2511          96 :         bHasBoundingBox = true;
    2512             : 
    2513             :         // upper left
    2514          96 :         adfX[0] = m_gt.xorig;
    2515          96 :         adfY[0] = m_gt.yorig;
    2516             : 
    2517             :         // upper right
    2518          96 :         adfX[1] = m_gt.xorig + m_gt.xscale * nRasterXSize;
    2519          96 :         adfY[1] = m_gt.yorig;
    2520             : 
    2521             :         // lower left
    2522          96 :         adfX[2] = m_gt.xorig;
    2523          96 :         adfY[2] = m_gt.yorig + m_gt.yscale * nRasterYSize;
    2524             : 
    2525             :         // lower right
    2526          96 :         adfX[3] = m_gt.xorig + m_gt.xscale * nRasterXSize;
    2527          96 :         adfY[3] = m_gt.yorig + m_gt.yscale * nRasterYSize;
    2528             :     }
    2529             :     else
    2530             :     {
    2531           2 :         OGRLayer *poLayer = GetLayer(0);
    2532           2 :         OGREnvelope sEnvelope;
    2533           2 :         if (poLayer->GetExtent(&sEnvelope) == OGRERR_NONE)
    2534             :         {
    2535           1 :             bHasBoundingBox = true;
    2536             : 
    2537           1 :             adfX[0] = sEnvelope.MinX;
    2538           1 :             adfY[0] = sEnvelope.MaxY;
    2539             : 
    2540           1 :             adfX[1] = sEnvelope.MaxX;
    2541           1 :             adfY[1] = sEnvelope.MaxY;
    2542             : 
    2543           1 :             adfX[2] = sEnvelope.MinX;
    2544           1 :             adfY[2] = sEnvelope.MinY;
    2545             : 
    2546           1 :             adfX[3] = sEnvelope.MaxX;
    2547           1 :             adfY[3] = sEnvelope.MinY;
    2548             :         }
    2549             :     }
    2550             : 
    2551          98 :     if (bHasBoundingBox && !m_oSRS.IsGeographic())
    2552             :     {
    2553          33 :         bHasBoundingBox = false;
    2554          33 :         OGRSpatialReference *poSRSLongLat = m_oSRS.CloneGeogCS();
    2555          33 :         if (poSRSLongLat)
    2556             :         {
    2557          33 :             poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2558             :             OGRCoordinateTransformation *poCT =
    2559          33 :                 OGRCreateCoordinateTransformation(&m_oSRS, poSRSLongLat);
    2560          33 :             if (poCT)
    2561             :             {
    2562          33 :                 if (poCT->Transform(4, adfX, adfY))
    2563             :                 {
    2564          33 :                     bHasBoundingBox = true;
    2565             :                 }
    2566          33 :                 delete poCT;
    2567             :             }
    2568          33 :             delete poSRSLongLat;
    2569             :         }
    2570             :     }
    2571             : 
    2572          98 :     if (!bHasBoundingBox)
    2573             :     {
    2574             :         // Write dummy values
    2575           1 :         adfX[0] = -180.0;
    2576           1 :         adfY[0] = 90.0;
    2577           1 :         adfX[1] = 180.0;
    2578           1 :         adfY[1] = 90.0;
    2579           1 :         adfX[2] = -180.0;
    2580           1 :         adfY[2] = -90.0;
    2581           1 :         adfX[3] = 180.0;
    2582           1 :         adfY[3] = -90.0;
    2583             :     }
    2584             : 
    2585         196 :     const char *pszLongitudeDirection = CSLFetchNameValueDef(
    2586          98 :         m_papszCreationOptions, "LONGITUDE_DIRECTION", "Positive East");
    2587          98 :     const double dfLongitudeMultiplier =
    2588          98 :         EQUAL(pszLongitudeDirection, "Positive West") ? -1 : 1;
    2589         212 :     const auto FixLong = [dfLongitudeMultiplier](double dfLon)
    2590         212 :     { return dfLon * dfLongitudeMultiplier; };
    2591             : 
    2592             :     // Note: starting with CART 1900, Spatial_Domain is actually optional
    2593          98 :     CPLXMLNode *psSD = CPLCreateXMLNode(psCart, CXT_Element,
    2594         196 :                                         (osPrefix + "Spatial_Domain").c_str());
    2595          98 :     CPLXMLNode *psBC = CPLCreateXMLNode(
    2596         196 :         psSD, CXT_Element, (osPrefix + "Bounding_Coordinates").c_str());
    2597             : 
    2598             :     const char *pszBoundingDegrees =
    2599          98 :         CSLFetchNameValue(m_papszCreationOptions, "BOUNDING_DEGREES");
    2600          98 :     double dfWest = FixLong(
    2601          98 :         std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3])));
    2602          98 :     double dfEast = FixLong(
    2603          98 :         std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3])));
    2604             :     double dfNorth =
    2605          98 :         std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
    2606             :     double dfSouth =
    2607          98 :         std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
    2608          98 :     if (pszBoundingDegrees)
    2609             :     {
    2610           1 :         char **papszTokens = CSLTokenizeString2(pszBoundingDegrees, ",", 0);
    2611           1 :         if (CSLCount(papszTokens) == 4)
    2612             :         {
    2613           1 :             dfWest = CPLAtof(papszTokens[0]);
    2614           1 :             dfSouth = CPLAtof(papszTokens[1]);
    2615           1 :             dfEast = CPLAtof(papszTokens[2]);
    2616           1 :             dfNorth = CPLAtof(papszTokens[3]);
    2617             :         }
    2618           1 :         CSLDestroy(papszTokens);
    2619             :     }
    2620             : 
    2621         196 :     CPLAddXMLAttributeAndValue(
    2622             :         CPLCreateXMLElementAndValue(
    2623         196 :             psBC, (osPrefix + "west_bounding_coordinate").c_str(),
    2624             :             CPLSPrintf("%.17g", dfWest)),
    2625             :         "unit", "deg");
    2626         196 :     CPLAddXMLAttributeAndValue(
    2627             :         CPLCreateXMLElementAndValue(
    2628         196 :             psBC, (osPrefix + "east_bounding_coordinate").c_str(),
    2629             :             CPLSPrintf("%.17g", dfEast)),
    2630             :         "unit", "deg");
    2631         196 :     CPLAddXMLAttributeAndValue(
    2632             :         CPLCreateXMLElementAndValue(
    2633         196 :             psBC, (osPrefix + "north_bounding_coordinate").c_str(),
    2634             :             CPLSPrintf("%.17g", dfNorth)),
    2635             :         "unit", "deg");
    2636         196 :     CPLAddXMLAttributeAndValue(
    2637             :         CPLCreateXMLElementAndValue(
    2638         196 :             psBC, (osPrefix + "south_bounding_coordinate").c_str(),
    2639             :             CPLSPrintf("%.17g", dfSouth)),
    2640             :         "unit", "deg");
    2641             : 
    2642             :     CPLXMLNode *psSRI =
    2643          98 :         CPLCreateXMLNode(psCart, CXT_Element,
    2644         196 :                          (osPrefix + "Spatial_Reference_Information").c_str());
    2645          98 :     CPLXMLNode *psHCSD = CPLCreateXMLNode(
    2646             :         psSRI, CXT_Element,
    2647         196 :         (osPrefix + "Horizontal_Coordinate_System_Definition").c_str());
    2648             : 
    2649          98 :     double dfUnrotatedULX = m_gt.xorig;
    2650          98 :     double dfUnrotatedULY = m_gt.yorig;
    2651          98 :     double dfUnrotatedResX = m_gt.xscale;
    2652          98 :     double dfUnrotatedResY = m_gt.yscale;
    2653          98 :     double dfMapProjectionRotation = 0.0;
    2654          98 :     if (m_gt.xscale == 0.0 && m_gt.xrot > 0.0 && m_gt.yrot > 0.0 &&
    2655           1 :         m_gt.yscale == 0.0)
    2656             :     {
    2657           1 :         dfUnrotatedULX = m_gt.yorig;
    2658           1 :         dfUnrotatedULY = -m_gt.xorig;
    2659           1 :         dfUnrotatedResX = m_gt.yrot;
    2660           1 :         dfUnrotatedResY = -m_gt.xrot;
    2661           1 :         dfMapProjectionRotation = 90.0;
    2662             :     }
    2663             : 
    2664          98 :     if (GetRasterCount() || m_oSRS.IsProjected())
    2665             :     {
    2666          97 :         CPLXMLNode *psPlanar = CPLCreateXMLNode(psHCSD, CXT_Element,
    2667         194 :                                                 (osPrefix + "Planar").c_str());
    2668          97 :         CPLXMLNode *psMP = CPLCreateXMLNode(
    2669         194 :             psPlanar, CXT_Element, (osPrefix + "Map_Projection").c_str());
    2670          97 :         const char *pszProjection = m_oSRS.GetAttrValue("PROJECTION");
    2671         194 :         CPLString pszPDS4ProjectionName = "";
    2672             :         typedef std::pair<const char *, double> ProjParam;
    2673         194 :         std::vector<ProjParam> aoProjParams;
    2674             : 
    2675             :         const bool bUse_CART_1933_Or_Later =
    2676          97 :             IsCARTVersionGTE(pszCARTVersion, "1D00_1933");
    2677             : 
    2678             :         const bool bUse_CART_1950_Or_Later =
    2679          97 :             IsCARTVersionGTE(pszCARTVersion, "1G00_1950");
    2680             : 
    2681             :         const bool bUse_CART_1970_Or_Later =
    2682          97 :             IsCARTVersionGTE(pszCARTVersion, "1O00_1970");
    2683             : 
    2684          97 :         if (pszProjection == nullptr)
    2685             :         {
    2686          63 :             pszPDS4ProjectionName = "Equirectangular";
    2687          63 :             if (bUse_CART_1933_Or_Later)
    2688             :             {
    2689          61 :                 aoProjParams.push_back(
    2690          61 :                     ProjParam("latitude_of_projection_origin", 0.0));
    2691          61 :                 aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
    2692          61 :                 aoProjParams.push_back(
    2693         122 :                     ProjParam("longitude_of_central_meridian", 0.0));
    2694             :             }
    2695             :             else
    2696             :             {
    2697           2 :                 aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
    2698           2 :                 aoProjParams.push_back(
    2699           2 :                     ProjParam("longitude_of_central_meridian", 0.0));
    2700           2 :                 aoProjParams.push_back(
    2701           4 :                     ProjParam("latitude_of_projection_origin", 0.0));
    2702             :             }
    2703             :         }
    2704             : 
    2705          34 :         else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
    2706             :         {
    2707           2 :             pszPDS4ProjectionName = "Equirectangular";
    2708           2 :             if (bUse_CART_1933_Or_Later)
    2709             :             {
    2710           2 :                 aoProjParams.push_back(ProjParam(
    2711           0 :                     "latitude_of_projection_origin",
    2712           2 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2713           2 :                 aoProjParams.push_back(ProjParam(
    2714           0 :                     "standard_parallel_1",
    2715           2 :                     m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
    2716           2 :                 aoProjParams.push_back(
    2717           0 :                     ProjParam("longitude_of_central_meridian",
    2718           4 :                               FixLong(m_oSRS.GetNormProjParm(
    2719             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2720             :             }
    2721             :             else
    2722             :             {
    2723           0 :                 aoProjParams.push_back(ProjParam(
    2724           0 :                     "standard_parallel_1",
    2725           0 :                     m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
    2726           0 :                 aoProjParams.push_back(
    2727           0 :                     ProjParam("longitude_of_central_meridian",
    2728           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2729             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2730           0 :                 aoProjParams.push_back(ProjParam(
    2731           0 :                     "latitude_of_projection_origin",
    2732           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2733             :             }
    2734             :         }
    2735             : 
    2736          32 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
    2737             :         {
    2738           1 :             pszPDS4ProjectionName = "Lambert Conformal Conic";
    2739           1 :             if (bUse_CART_1933_Or_Later)
    2740             :             {
    2741           1 :                 if (bUse_CART_1970_Or_Later)
    2742             :                 {
    2743             :                     // Note: in EPSG (and PROJ "metadata" part), there is
    2744             :                     // no standard_parallel_1 parameter for LCC_1SP. But
    2745             :                     // for historical reason we do map the latitude of origin
    2746             :                     // to +lat_1 (in addition to +lat_0). So do the same here.
    2747           1 :                     aoProjParams.push_back(
    2748           0 :                         ProjParam("standard_parallel_1",
    2749           2 :                                   m_oSRS.GetNormProjParm(
    2750             :                                       SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2751             :                 }
    2752           1 :                 aoProjParams.push_back(
    2753           0 :                     ProjParam("longitude_of_central_meridian",
    2754           1 :                               FixLong(m_oSRS.GetNormProjParm(
    2755             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2756           1 :                 aoProjParams.push_back(ProjParam(
    2757           0 :                     "latitude_of_projection_origin",
    2758           1 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2759           1 :                 aoProjParams.push_back(ProjParam(
    2760           0 :                     "scale_factor_at_projection_origin",
    2761           2 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2762             :             }
    2763             :             else
    2764             :             {
    2765           0 :                 aoProjParams.push_back(ProjParam(
    2766           0 :                     "scale_factor_at_projection_origin",
    2767           0 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2768           0 :                 aoProjParams.push_back(
    2769           0 :                     ProjParam("longitude_of_central_meridian",
    2770           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2771             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2772           0 :                 aoProjParams.push_back(ProjParam(
    2773           0 :                     "latitude_of_projection_origin",
    2774           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2775             :             }
    2776             :         }
    2777             : 
    2778          31 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
    2779             :         {
    2780           1 :             pszPDS4ProjectionName = "Lambert Conformal Conic";
    2781           1 :             aoProjParams.push_back(ProjParam(
    2782           0 :                 "standard_parallel_1",
    2783           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
    2784           1 :             aoProjParams.push_back(ProjParam(
    2785           0 :                 "standard_parallel_2",
    2786           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0)));
    2787           1 :             aoProjParams.push_back(ProjParam(
    2788           0 :                 "longitude_of_central_meridian",
    2789           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2790           1 :             aoProjParams.push_back(ProjParam(
    2791           0 :                 "latitude_of_projection_origin",
    2792           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2793             :         }
    2794             : 
    2795          30 :         else if (EQUAL(pszProjection,
    2796             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
    2797             :         {
    2798           1 :             pszPDS4ProjectionName = "Oblique Mercator";
    2799             :             // Proj params defined later
    2800             :         }
    2801             : 
    2802          29 :         else if (EQUAL(pszProjection,
    2803             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
    2804             :         {
    2805           1 :             pszPDS4ProjectionName = "Oblique Mercator";
    2806             :             // Proj params defined later
    2807             :         }
    2808             : 
    2809          28 :         else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
    2810             :         {
    2811           1 :             pszPDS4ProjectionName = "Polar Stereographic";
    2812           1 :             if (bUse_CART_1950_Or_Later)
    2813             :             {
    2814           1 :                 aoProjParams.push_back(
    2815           0 :                     ProjParam("longitude_of_central_meridian",
    2816           1 :                               FixLong(m_oSRS.GetNormProjParm(
    2817             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2818           1 :                 aoProjParams.push_back(ProjParam(
    2819           0 :                     "latitude_of_projection_origin",
    2820           1 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2821           1 :                 aoProjParams.push_back(ProjParam(
    2822           0 :                     "scale_factor_at_projection_origin",
    2823           2 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2824             :             }
    2825             :             else
    2826             :             {
    2827           0 :                 aoProjParams.push_back(
    2828           0 :                     ProjParam(bUse_CART_1933_Or_Later
    2829           0 :                                   ? "longitude_of_central_meridian"
    2830             :                                   : "straight_vertical_longitude_from_pole",
    2831           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2832             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2833           0 :                 aoProjParams.push_back(ProjParam(
    2834           0 :                     "scale_factor_at_projection_origin",
    2835           0 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2836           0 :                 aoProjParams.push_back(ProjParam(
    2837           0 :                     "latitude_of_projection_origin",
    2838           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2839             :             }
    2840             :         }
    2841             : 
    2842          27 :         else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
    2843             :         {
    2844           1 :             pszPDS4ProjectionName = "Polyconic";
    2845           1 :             aoProjParams.push_back(ProjParam(
    2846           0 :                 "longitude_of_central_meridian",
    2847           1 :                 m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
    2848           1 :             aoProjParams.push_back(ProjParam(
    2849           0 :                 "latitude_of_projection_origin",
    2850           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2851             :         }
    2852          26 :         else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
    2853             :         {
    2854           2 :             pszPDS4ProjectionName = "Sinusoidal";
    2855           2 :             aoProjParams.push_back(ProjParam(
    2856           0 :                 "longitude_of_central_meridian",
    2857           2 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2858           2 :             aoProjParams.push_back(ProjParam(
    2859           0 :                 "latitude_of_projection_origin",
    2860           4 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2861             :         }
    2862             : 
    2863          24 :         else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    2864             :         {
    2865          18 :             pszPDS4ProjectionName = "Transverse Mercator";
    2866          18 :             aoProjParams.push_back(
    2867           0 :                 ProjParam("scale_factor_at_central_meridian",
    2868          18 :                           m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2869          18 :             aoProjParams.push_back(ProjParam(
    2870           0 :                 "longitude_of_central_meridian",
    2871          18 :                 m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
    2872          18 :             aoProjParams.push_back(ProjParam(
    2873           0 :                 "latitude_of_projection_origin",
    2874          36 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2875             :         }
    2876           6 :         else if (EQUAL(pszProjection, SRS_PT_ORTHOGRAPHIC))
    2877             :         {
    2878           1 :             pszPDS4ProjectionName = "Orthographic";
    2879           1 :             aoProjParams.push_back(ProjParam(
    2880           0 :                 "longitude_of_central_meridian",
    2881           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2882           1 :             aoProjParams.push_back(ProjParam(
    2883           0 :                 "latitude_of_projection_origin",
    2884           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2885             :         }
    2886             : 
    2887           5 :         else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
    2888             :         {
    2889           2 :             pszPDS4ProjectionName = "Mercator";
    2890           2 :             aoProjParams.push_back(ProjParam(
    2891           0 :                 "longitude_of_central_meridian",
    2892           2 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2893           2 :             aoProjParams.push_back(ProjParam(
    2894           0 :                 "latitude_of_projection_origin",
    2895           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2896           2 :             aoProjParams.push_back(
    2897           0 :                 ProjParam("scale_factor_at_projection_origin",
    2898           4 :                           m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2899             :         }
    2900             : 
    2901           3 :         else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
    2902             :         {
    2903           1 :             pszPDS4ProjectionName = "Mercator";
    2904           1 :             aoProjParams.push_back(ProjParam(
    2905           0 :                 "standard_parallel_1",
    2906           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
    2907           1 :             aoProjParams.push_back(ProjParam(
    2908           0 :                 "longitude_of_central_meridian",
    2909           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2910           1 :             aoProjParams.push_back(ProjParam(
    2911           0 :                 "latitude_of_projection_origin",
    2912           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2913             :         }
    2914             : 
    2915           2 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
    2916             :         {
    2917           1 :             pszPDS4ProjectionName = "Lambert Azimuthal Equal Area";
    2918           1 :             aoProjParams.push_back(ProjParam(
    2919           0 :                 "longitude_of_central_meridian",
    2920           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2921           1 :             aoProjParams.push_back(ProjParam(
    2922           0 :                 "latitude_of_projection_origin",
    2923           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2924             :         }
    2925             : 
    2926           1 :         else if (EQUAL(pszProjection, "custom_proj4"))
    2927             :         {
    2928             :             const char *pszProj4 =
    2929           1 :                 m_oSRS.GetExtension("PROJCS", "PROJ4", nullptr);
    2930           1 :             if (pszProj4 && strstr(pszProj4, "+proj=ob_tran") &&
    2931           1 :                 strstr(pszProj4, "+o_proj=eqc"))
    2932             :             {
    2933           1 :                 pszPDS4ProjectionName = "Oblique Cylindrical";
    2934             :                 const auto FetchParam =
    2935           3 :                     [](const char *pszProj4Str, const char *pszKey)
    2936             :                 {
    2937           6 :                     CPLString needle;
    2938           3 :                     needle.Printf("+%s=", pszKey);
    2939           3 :                     const char *pszVal = strstr(pszProj4Str, needle.c_str());
    2940           3 :                     if (pszVal)
    2941           3 :                         return CPLAtof(pszVal + needle.size());
    2942           0 :                     return 0.0;
    2943             :                 };
    2944             : 
    2945           1 :                 double dfLonP = FetchParam(pszProj4, "o_lon_p");
    2946           1 :                 double dfLatP = FetchParam(pszProj4, "o_lat_p");
    2947           1 :                 double dfLon0 = FetchParam(pszProj4, "lon_0");
    2948           1 :                 double dfPoleRotation = -dfLonP;
    2949           1 :                 double dfPoleLatitude = 180 - dfLatP;
    2950           1 :                 double dfPoleLongitude = dfLon0;
    2951             : 
    2952           1 :                 aoProjParams.push_back(ProjParam("map_projection_rotation",
    2953             :                                                  dfMapProjectionRotation));
    2954           1 :                 aoProjParams.push_back(
    2955           1 :                     ProjParam("oblique_proj_pole_latitude", dfPoleLatitude));
    2956           1 :                 aoProjParams.push_back(ProjParam("oblique_proj_pole_longitude",
    2957           1 :                                                  FixLong(dfPoleLongitude)));
    2958           1 :                 aoProjParams.push_back(
    2959           2 :                     ProjParam("oblique_proj_pole_rotation", dfPoleRotation));
    2960             :             }
    2961             :             else
    2962             :             {
    2963           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    2964             :                          "Projection %s not supported", pszProjection);
    2965             :             }
    2966             :         }
    2967             :         else
    2968             :         {
    2969           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    2970             :                      "Projection %s not supported", pszProjection);
    2971             :         }
    2972         194 :         CPLCreateXMLElementAndValue(psMP,
    2973         194 :                                     (osPrefix + "map_projection_name").c_str(),
    2974             :                                     pszPDS4ProjectionName);
    2975          97 :         CPLXMLNode *psProj = CPLCreateXMLNode(
    2976             :             psMP, CXT_Element,
    2977         194 :             CPLString(osPrefix + pszPDS4ProjectionName).replaceAll(' ', '_'));
    2978         380 :         for (size_t i = 0; i < aoProjParams.size(); i++)
    2979             :         {
    2980         566 :             CPLXMLNode *psParam = CPLCreateXMLElementAndValue(
    2981         566 :                 psProj, (osPrefix + aoProjParams[i].first).c_str(),
    2982         283 :                 CPLSPrintf("%.17g", aoProjParams[i].second));
    2983         283 :             if (!STARTS_WITH(aoProjParams[i].first, "scale_factor"))
    2984             :             {
    2985         261 :                 CPLAddXMLAttributeAndValue(psParam, "unit", "deg");
    2986             :             }
    2987             :         }
    2988             : 
    2989          97 :         if (pszProjection &&
    2990          34 :             EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
    2991             :         {
    2992             :             CPLXMLNode *psOLA =
    2993           1 :                 CPLCreateXMLNode(nullptr, CXT_Element,
    2994           2 :                                  (osPrefix + "Oblique_Line_Azimuth").c_str());
    2995           2 :             CPLAddXMLAttributeAndValue(
    2996             :                 CPLCreateXMLElementAndValue(
    2997           2 :                     psOLA, (osPrefix + "azimuthal_angle").c_str(),
    2998             :                     CPLSPrintf("%.17g",
    2999             :                                m_oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0))),
    3000             :                 "unit", "deg");
    3001             :             ;
    3002             :             // Not completely sure of this
    3003           2 :             CPLAddXMLAttributeAndValue(
    3004             :                 CPLCreateXMLElementAndValue(
    3005             :                     psOLA,
    3006           2 :                     (osPrefix + "azimuth_measure_point_longitude").c_str(),
    3007             :                     CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
    3008             :                                             SRS_PP_CENTRAL_MERIDIAN, 0.0)))),
    3009             :                 "unit", "deg");
    3010             : 
    3011           1 :             if (bUse_CART_1933_Or_Later)
    3012             :             {
    3013           1 :                 CPLAddXMLChild(psProj, psOLA);
    3014             : 
    3015           1 :                 CPLAddXMLAttributeAndValue(
    3016             :                     CPLCreateXMLElementAndValue(
    3017             :                         psProj,
    3018           2 :                         (osPrefix + "longitude_of_central_meridian").c_str(),
    3019             :                         "0"),
    3020             :                     "unit", "deg");
    3021             : 
    3022             :                 const double dfScaleFactor =
    3023           1 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
    3024           1 :                 if (dfScaleFactor != 1.0)
    3025             :                 {
    3026           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    3027             :                              "Scale factor on initial support = %.17g cannot "
    3028             :                              "be encoded in PDS4",
    3029             :                              dfScaleFactor);
    3030             :                 }
    3031             :             }
    3032             :             else
    3033             :             {
    3034           0 :                 CPLCreateXMLElementAndValue(
    3035             :                     psProj,
    3036           0 :                     (osPrefix + "scale_factor_at_projection_origin").c_str(),
    3037             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3038             :                                             SRS_PP_SCALE_FACTOR, 0.0)));
    3039             : 
    3040           0 :                 CPLAddXMLChild(psProj, psOLA);
    3041             :             }
    3042             : 
    3043           2 :             CPLAddXMLAttributeAndValue(
    3044             :                 CPLCreateXMLElementAndValue(
    3045             :                     psProj,
    3046           2 :                     (osPrefix + "latitude_of_projection_origin").c_str(),
    3047             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3048             :                                             SRS_PP_LATITUDE_OF_ORIGIN, 0.0))),
    3049           1 :                 "unit", "deg");
    3050             :         }
    3051          96 :         else if (pszProjection &&
    3052          33 :                  EQUAL(pszProjection,
    3053             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
    3054             :         {
    3055           1 :             if (bUse_CART_1933_Or_Later)
    3056             :             {
    3057             :                 const double dfScaleFactor =
    3058           1 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
    3059           1 :                 if (dfScaleFactor != 1.0)
    3060             :                 {
    3061           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    3062             :                              "Scale factor on initial support = %.17g cannot "
    3063             :                              "be encoded in PDS4",
    3064             :                              dfScaleFactor);
    3065             :                 }
    3066             :             }
    3067             :             else
    3068             :             {
    3069           0 :                 CPLCreateXMLElementAndValue(
    3070             :                     psProj,
    3071           0 :                     (osPrefix + "scale_factor_at_projection_origin").c_str(),
    3072             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3073             :                                             SRS_PP_SCALE_FACTOR, 0.0)));
    3074             :             }
    3075             : 
    3076           1 :             CPLXMLNode *psOLP = CPLCreateXMLNode(
    3077           2 :                 psProj, CXT_Element, (osPrefix + "Oblique_Line_Point").c_str());
    3078           1 :             CPLXMLNode *psOLPG1 = CPLCreateXMLNode(
    3079             :                 psOLP, CXT_Element,
    3080           2 :                 (osPrefix + "Oblique_Line_Point_Group").c_str());
    3081           2 :             CPLAddXMLAttributeAndValue(
    3082             :                 CPLCreateXMLElementAndValue(
    3083           2 :                     psOLPG1, (osPrefix + "oblique_line_latitude").c_str(),
    3084             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3085             :                                             SRS_PP_LATITUDE_OF_POINT_1, 0.0))),
    3086             :                 "unit", "deg");
    3087           2 :             CPLAddXMLAttributeAndValue(
    3088             :                 CPLCreateXMLElementAndValue(
    3089           2 :                     psOLPG1, (osPrefix + "oblique_line_longitude").c_str(),
    3090             :                     CPLSPrintf("%.17g",
    3091             :                                FixLong(m_oSRS.GetNormProjParm(
    3092             :                                    SRS_PP_LONGITUDE_OF_POINT_1, 0.0)))),
    3093             :                 "unit", "deg");
    3094           1 :             CPLXMLNode *psOLPG2 = CPLCreateXMLNode(
    3095             :                 psOLP, CXT_Element,
    3096           2 :                 (osPrefix + "Oblique_Line_Point_Group").c_str());
    3097           2 :             CPLAddXMLAttributeAndValue(
    3098             :                 CPLCreateXMLElementAndValue(
    3099           2 :                     psOLPG2, (osPrefix + "oblique_line_latitude").c_str(),
    3100             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3101             :                                             SRS_PP_LATITUDE_OF_POINT_2, 0.0))),
    3102             :                 "unit", "deg");
    3103           2 :             CPLAddXMLAttributeAndValue(
    3104             :                 CPLCreateXMLElementAndValue(
    3105           2 :                     psOLPG2, (osPrefix + "oblique_line_longitude").c_str(),
    3106             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    3107             :                                             SRS_PP_LONGITUDE_OF_POINT_2, 0.0))),
    3108             :                 "unit", "deg");
    3109             : 
    3110           1 :             if (bUse_CART_1933_Or_Later)
    3111             :             {
    3112           1 :                 CPLAddXMLAttributeAndValue(
    3113             :                     CPLCreateXMLElementAndValue(
    3114             :                         psProj,
    3115           2 :                         (osPrefix + "longitude_of_central_meridian").c_str(),
    3116             :                         "0"),
    3117             :                     "unit", "deg");
    3118             :             }
    3119             : 
    3120           2 :             CPLAddXMLAttributeAndValue(
    3121             :                 CPLCreateXMLElementAndValue(
    3122             :                     psProj,
    3123           2 :                     (osPrefix + "latitude_of_projection_origin").c_str(),
    3124             :                     CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
    3125             :                                             SRS_PP_LATITUDE_OF_ORIGIN, 0.0)))),
    3126             :                 "unit", "deg");
    3127             :         }
    3128             : 
    3129          97 :         CPLXMLNode *psCR = nullptr;
    3130          97 :         if (m_bGotTransform || !IsCARTVersionGTE(pszCARTVersion, "1B00"))
    3131             :         {
    3132          96 :             CPLXMLNode *psPCI = CPLCreateXMLNode(
    3133             :                 psPlanar, CXT_Element,
    3134         192 :                 (osPrefix + "Planar_Coordinate_Information").c_str());
    3135          96 :             CPLCreateXMLElementAndValue(
    3136         192 :                 psPCI, (osPrefix + "planar_coordinate_encoding_method").c_str(),
    3137             :                 "Coordinate Pair");
    3138          96 :             psCR = CPLCreateXMLNode(
    3139             :                 psPCI, CXT_Element,
    3140         192 :                 (osPrefix + "Coordinate_Representation").c_str());
    3141             :         }
    3142          97 :         const double dfLinearUnits = m_oSRS.GetLinearUnits();
    3143          97 :         const double dfDegToMeter = m_oSRS.GetSemiMajor() * M_PI / 180.0;
    3144             : 
    3145          97 :         if (psCR == nullptr)
    3146             :         {
    3147             :             // do nothing
    3148             :         }
    3149          96 :         else if (!m_bGotTransform)
    3150             :         {
    3151           0 :             CPLAddXMLAttributeAndValue(
    3152             :                 CPLCreateXMLElementAndValue(
    3153           0 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(), "0"),
    3154             :                 "unit", "m/pixel");
    3155           0 :             CPLAddXMLAttributeAndValue(
    3156             :                 CPLCreateXMLElementAndValue(
    3157           0 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(), "0"),
    3158             :                 "unit", "m/pixel");
    3159           0 :             CPLAddXMLAttributeAndValue(
    3160             :                 CPLCreateXMLElementAndValue(
    3161           0 :                     psCR, (osPrefix + "pixel_scale_x").c_str(), "0"),
    3162             :                 "unit", "pixel/deg");
    3163           0 :             CPLAddXMLAttributeAndValue(
    3164             :                 CPLCreateXMLElementAndValue(
    3165           0 :                     psCR, (osPrefix + "pixel_scale_y").c_str(), "0"),
    3166             :                 "unit", "pixel/deg");
    3167             :         }
    3168          96 :         else if (m_oSRS.IsGeographic())
    3169             :         {
    3170         126 :             CPLAddXMLAttributeAndValue(
    3171             :                 CPLCreateXMLElementAndValue(
    3172         126 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(),
    3173             :                     CPLSPrintf("%.17g", dfUnrotatedResX * dfDegToMeter)),
    3174             :                 "unit", "m/pixel");
    3175          63 :             CPLAddXMLAttributeAndValue(
    3176             :                 CPLCreateXMLElementAndValue(
    3177         126 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(),
    3178          63 :                     CPLSPrintf("%.17g", -dfUnrotatedResY * dfDegToMeter)),
    3179             :                 "unit", "m/pixel");
    3180         126 :             CPLAddXMLAttributeAndValue(
    3181             :                 CPLCreateXMLElementAndValue(
    3182         126 :                     psCR, (osPrefix + "pixel_scale_x").c_str(),
    3183             :                     CPLSPrintf("%.17g", 1.0 / (dfUnrotatedResX))),
    3184             :                 "unit", "pixel/deg");
    3185         126 :             CPLAddXMLAttributeAndValue(
    3186             :                 CPLCreateXMLElementAndValue(
    3187         126 :                     psCR, (osPrefix + "pixel_scale_y").c_str(),
    3188             :                     CPLSPrintf("%.17g", 1.0 / (-dfUnrotatedResY))),
    3189             :                 "unit", "pixel/deg");
    3190             :         }
    3191          33 :         else if (m_oSRS.IsProjected())
    3192             :         {
    3193          66 :             CPLAddXMLAttributeAndValue(
    3194             :                 CPLCreateXMLElementAndValue(
    3195          66 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(),
    3196             :                     CPLSPrintf("%.17g", dfUnrotatedResX * dfLinearUnits)),
    3197             :                 "unit", "m/pixel");
    3198          33 :             CPLAddXMLAttributeAndValue(
    3199             :                 CPLCreateXMLElementAndValue(
    3200          66 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(),
    3201          33 :                     CPLSPrintf("%.17g", -dfUnrotatedResY * dfLinearUnits)),
    3202             :                 "unit", "m/pixel");
    3203          33 :             CPLAddXMLAttributeAndValue(
    3204             :                 CPLCreateXMLElementAndValue(
    3205          66 :                     psCR, (osPrefix + "pixel_scale_x").c_str(),
    3206             :                     CPLSPrintf("%.17g", dfDegToMeter /
    3207          33 :                                             (dfUnrotatedResX * dfLinearUnits))),
    3208             :                 "unit", "pixel/deg");
    3209          33 :             CPLAddXMLAttributeAndValue(
    3210             :                 CPLCreateXMLElementAndValue(
    3211          66 :                     psCR, (osPrefix + "pixel_scale_y").c_str(),
    3212          33 :                     CPLSPrintf("%.17g", dfDegToMeter / (-dfUnrotatedResY *
    3213             :                                                         dfLinearUnits))),
    3214             :                 "unit", "pixel/deg");
    3215             :         }
    3216             : 
    3217          97 :         if (m_bGotTransform)
    3218             :         {
    3219             :             CPLXMLNode *psGT =
    3220          96 :                 CPLCreateXMLNode(psPlanar, CXT_Element,
    3221         192 :                                  (osPrefix + "Geo_Transformation").c_str());
    3222             :             const double dfFalseEasting =
    3223          96 :                 m_oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    3224             :             const double dfFalseNorthing =
    3225          96 :                 m_oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    3226          96 :             const double dfULX = -dfFalseEasting + dfUnrotatedULX;
    3227          96 :             const double dfULY = -dfFalseNorthing + dfUnrotatedULY;
    3228          96 :             if (m_oSRS.IsGeographic())
    3229             :             {
    3230         126 :                 CPLAddXMLAttributeAndValue(
    3231             :                     CPLCreateXMLElementAndValue(
    3232         126 :                         psGT, (osPrefix + "upperleft_corner_x").c_str(),
    3233             :                         CPLSPrintf("%.17g", dfULX * dfDegToMeter)),
    3234             :                     "unit", "m");
    3235         126 :                 CPLAddXMLAttributeAndValue(
    3236             :                     CPLCreateXMLElementAndValue(
    3237         126 :                         psGT, (osPrefix + "upperleft_corner_y").c_str(),
    3238             :                         CPLSPrintf("%.17g", dfULY * dfDegToMeter)),
    3239             :                     "unit", "m");
    3240             :             }
    3241          33 :             else if (m_oSRS.IsProjected())
    3242             :             {
    3243          66 :                 CPLAddXMLAttributeAndValue(
    3244             :                     CPLCreateXMLElementAndValue(
    3245          66 :                         psGT, (osPrefix + "upperleft_corner_x").c_str(),
    3246             :                         CPLSPrintf("%.17g", dfULX * dfLinearUnits)),
    3247             :                     "unit", "m");
    3248          66 :                 CPLAddXMLAttributeAndValue(
    3249             :                     CPLCreateXMLElementAndValue(
    3250          66 :                         psGT, (osPrefix + "upperleft_corner_y").c_str(),
    3251             :                         CPLSPrintf("%.17g", dfULY * dfLinearUnits)),
    3252             :                     "unit", "m");
    3253             :             }
    3254             :         }
    3255             :     }
    3256             :     else
    3257             :     {
    3258           1 :         CPLXMLNode *psGeographic = CPLCreateXMLNode(
    3259           2 :             psHCSD, CXT_Element, (osPrefix + "Geographic").c_str());
    3260           1 :         if (!IsCARTVersionGTE(pszCARTVersion, "1B00"))
    3261             :         {
    3262           0 :             CPLAddXMLAttributeAndValue(
    3263             :                 CPLCreateXMLElementAndValue(
    3264           0 :                     psGeographic, (osPrefix + "latitude_resolution").c_str(),
    3265             :                     "0"),
    3266             :                 "unit", "deg");
    3267           0 :             CPLAddXMLAttributeAndValue(
    3268             :                 CPLCreateXMLElementAndValue(
    3269           0 :                     psGeographic, (osPrefix + "longitude_resolution").c_str(),
    3270             :                     "0"),
    3271             :                 "unit", "deg");
    3272             :         }
    3273             :     }
    3274             : 
    3275          98 :     CPLXMLNode *psGM = CPLCreateXMLNode(psHCSD, CXT_Element,
    3276         196 :                                         (osPrefix + "Geodetic_Model").c_str());
    3277         196 :     const char *pszLatitudeType = CSLFetchNameValueDef(
    3278          98 :         m_papszCreationOptions, "LATITUDE_TYPE", "Planetocentric");
    3279             :     // Fix case
    3280          98 :     if (EQUAL(pszLatitudeType, "Planetocentric"))
    3281          97 :         pszLatitudeType = "Planetocentric";
    3282           1 :     else if (EQUAL(pszLatitudeType, "Planetographic"))
    3283           1 :         pszLatitudeType = "Planetographic";
    3284          98 :     CPLCreateXMLElementAndValue(psGM, (osPrefix + "latitude_type").c_str(),
    3285             :                                 pszLatitudeType);
    3286             : 
    3287          98 :     const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
    3288          98 :     if (pszDatum && STARTS_WITH(pszDatum, "D_"))
    3289             :     {
    3290           4 :         CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
    3291             :                                     pszDatum + 2);
    3292             :     }
    3293          94 :     else if (pszDatum)
    3294             :     {
    3295          94 :         CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
    3296             :                                     pszDatum);
    3297             :     }
    3298             : 
    3299          98 :     double dfSemiMajor = m_oSRS.GetSemiMajor();
    3300          98 :     double dfSemiMinor = m_oSRS.GetSemiMinor();
    3301          98 :     const char *pszRadii = CSLFetchNameValue(m_papszCreationOptions, "RADII");
    3302          98 :     if (pszRadii)
    3303             :     {
    3304           1 :         char **papszTokens = CSLTokenizeString2(pszRadii, " ,", 0);
    3305           1 :         if (CSLCount(papszTokens) == 2)
    3306             :         {
    3307           1 :             dfSemiMajor = CPLAtof(papszTokens[0]);
    3308           1 :             dfSemiMinor = CPLAtof(papszTokens[1]);
    3309             :         }
    3310           1 :         CSLDestroy(papszTokens);
    3311             :     }
    3312             : 
    3313             :     const bool bUseLDD1930RadiusNames =
    3314          98 :         IsCARTVersionGTE(pszCARTVersion, "1B10_1930");
    3315             : 
    3316         196 :     CPLAddXMLAttributeAndValue(
    3317             :         CPLCreateXMLElementAndValue(
    3318             :             psGM,
    3319         196 :             (osPrefix +
    3320             :              (bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius"))
    3321             :                 .c_str(),
    3322             :             CPLSPrintf("%.17g", dfSemiMajor)),
    3323             :         "unit", "m");
    3324             :     // No, this is not a bug. The PDS4  b_axis_radius/semi_minor_radius is the
    3325             :     // minor radius on the equatorial plane. Which in WKT doesn't really exist,
    3326             :     // so reuse the WKT semi major
    3327         196 :     CPLAddXMLAttributeAndValue(
    3328             :         CPLCreateXMLElementAndValue(
    3329             :             psGM,
    3330         196 :             (osPrefix +
    3331             :              (bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius"))
    3332             :                 .c_str(),
    3333             :             CPLSPrintf("%.17g", dfSemiMajor)),
    3334             :         "unit", "m");
    3335         196 :     CPLAddXMLAttributeAndValue(
    3336             :         CPLCreateXMLElementAndValue(
    3337             :             psGM,
    3338         196 :             (osPrefix +
    3339             :              (bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius"))
    3340             :                 .c_str(),
    3341             :             CPLSPrintf("%.17g", dfSemiMinor)),
    3342             :         "unit", "m");
    3343             : 
    3344             :     // Fix case
    3345          98 :     if (EQUAL(pszLongitudeDirection, "Positive East"))
    3346          97 :         pszLongitudeDirection = "Positive East";
    3347           1 :     else if (EQUAL(pszLongitudeDirection, "Positive West"))
    3348           1 :         pszLongitudeDirection = "Positive West";
    3349          98 :     CPLCreateXMLElementAndValue(psGM,
    3350         196 :                                 (osPrefix + "longitude_direction").c_str(),
    3351             :                                 pszLongitudeDirection);
    3352          98 : }
    3353             : 
    3354             : /************************************************************************/
    3355             : /*                        SubstituteVariables()                         */
    3356             : /************************************************************************/
    3357             : 
    3358       16490 : void PDS4Dataset::SubstituteVariables(CPLXMLNode *psNode, char **papszDict)
    3359             : {
    3360       16490 :     if (psNode->eType == CXT_Text && psNode->pszValue &&
    3361        6668 :         strstr(psNode->pszValue, "${"))
    3362             :     {
    3363        1382 :         CPLString osVal(psNode->pszValue);
    3364             : 
    3365        1556 :         if (strstr(psNode->pszValue, "${TITLE}") != nullptr &&
    3366         174 :             CSLFetchNameValue(papszDict, "VAR_TITLE") == nullptr)
    3367             :         {
    3368         160 :             const CPLString osTitle(CPLGetFilename(GetDescription()));
    3369         160 :             CPLError(CE_Warning, CPLE_AppDefined,
    3370             :                      "VAR_TITLE not defined. Using %s by default",
    3371             :                      osTitle.c_str());
    3372         160 :             osVal.replaceAll("${TITLE}", osTitle);
    3373             :         }
    3374             : 
    3375        4599 :         for (char **papszIter = papszDict; papszIter && *papszIter; papszIter++)
    3376             :         {
    3377        3217 :             if (STARTS_WITH_CI(*papszIter, "VAR_"))
    3378             :             {
    3379        2746 :                 char *pszKey = nullptr;
    3380        2746 :                 const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    3381        2746 :                 if (pszKey && pszValue)
    3382             :                 {
    3383        2746 :                     const char *pszVarName = pszKey + strlen("VAR_");
    3384             :                     osVal.replaceAll(
    3385        2746 :                         (CPLString("${") + pszVarName + "}").c_str(), pszValue);
    3386             :                     osVal.replaceAll(
    3387        5492 :                         CPLString(CPLString("${") + pszVarName + "}")
    3388        2746 :                             .tolower()
    3389             :                             .c_str(),
    3390        8238 :                         CPLString(pszValue).tolower());
    3391        2746 :                     CPLFree(pszKey);
    3392             :                 }
    3393             :             }
    3394             :         }
    3395        1382 :         if (osVal.find("${") != std::string::npos)
    3396             :         {
    3397         778 :             CPLError(CE_Warning, CPLE_AppDefined, "%s could not be substituted",
    3398             :                      osVal.c_str());
    3399             :         }
    3400        1382 :         CPLFree(psNode->pszValue);
    3401        1382 :         psNode->pszValue = CPLStrdup(osVal);
    3402             :     }
    3403             : 
    3404       32799 :     for (CPLXMLNode *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
    3405             :     {
    3406       16309 :         SubstituteVariables(psIter, papszDict);
    3407             :     }
    3408       16490 : }
    3409             : 
    3410             : /************************************************************************/
    3411             : /*                           InitImageFile()                            */
    3412             : /************************************************************************/
    3413             : 
    3414          93 : bool PDS4Dataset::InitImageFile()
    3415             : {
    3416          93 :     m_bMustInitImageFile = false;
    3417             : 
    3418          93 :     int bHasNoData = FALSE;
    3419          93 :     double dfNoData = 0;
    3420          93 :     int bHasNoDataAsInt64 = FALSE;
    3421          93 :     int64_t nNoDataInt64 = 0;
    3422          93 :     int bHasNoDataAsUInt64 = FALSE;
    3423          93 :     uint64_t nNoDataUInt64 = 0;
    3424          93 :     const GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
    3425          93 :     if (eDT == GDT_Int64)
    3426             :     {
    3427           4 :         nNoDataInt64 =
    3428           4 :             GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoDataAsInt64);
    3429           4 :         if (!bHasNoDataAsInt64)
    3430           2 :             nNoDataInt64 = 0;
    3431             :     }
    3432          89 :     else if (eDT == GDT_UInt64)
    3433             :     {
    3434           4 :         nNoDataUInt64 =
    3435           4 :             GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoDataAsUInt64);
    3436           4 :         if (!bHasNoDataAsUInt64)
    3437           2 :             nNoDataUInt64 = 0;
    3438             :     }
    3439             :     else
    3440             :     {
    3441          85 :         dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    3442          85 :         if (!bHasNoData)
    3443          69 :             dfNoData = 0;
    3444             :     }
    3445             : 
    3446          93 :     if (m_poExternalDS)
    3447             :     {
    3448             :         int nBlockXSize, nBlockYSize;
    3449          18 :         GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    3450          18 :         const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
    3451          18 :         const int nBlockSizeBytes = nBlockXSize * nBlockYSize * nDTSize;
    3452          18 :         const int l_nBlocksPerColumn = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
    3453             : 
    3454          18 :         if (nBands == 1 || EQUAL(m_osInterleave, "BSQ"))
    3455             :         {
    3456             :             // We need to make sure that blocks are written in the right order
    3457          38 :             for (int i = 0; i < nBands; i++)
    3458             :             {
    3459          21 :                 if (eDT == GDT_Int64)
    3460             :                 {
    3461           1 :                     if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
    3462             :                             GF_Write, 0, 0, nRasterXSize, nRasterYSize,
    3463           1 :                             &nNoDataInt64, 1, 1, eDT, 0, 0, nullptr) != CE_None)
    3464             :                     {
    3465           0 :                         return false;
    3466             :                     }
    3467             :                 }
    3468          20 :                 else if (eDT == GDT_UInt64)
    3469             :                 {
    3470           1 :                     if (m_poExternalDS->GetRasterBand(i + 1)->RasterIO(
    3471             :                             GF_Write, 0, 0, nRasterXSize, nRasterYSize,
    3472             :                             &nNoDataUInt64, 1, 1, eDT, 0, 0,
    3473           1 :                             nullptr) != CE_None)
    3474             :                     {
    3475           0 :                         return false;
    3476             :                     }
    3477             :                 }
    3478             :                 else
    3479             :                 {
    3480          19 :                     if (m_poExternalDS->GetRasterBand(i + 1)->Fill(dfNoData) !=
    3481             :                         CE_None)
    3482             :                     {
    3483           0 :                         return false;
    3484             :                     }
    3485             :                 }
    3486             :             }
    3487          17 :             m_poExternalDS->FlushCache(false);
    3488             : 
    3489             :             // Check that blocks are effectively written in expected order.
    3490          17 :             GIntBig nLastOffset = 0;
    3491          38 :             for (int i = 0; i < nBands; i++)
    3492             :             {
    3493         333 :                 for (int y = 0; y < l_nBlocksPerColumn; y++)
    3494             :                 {
    3495             :                     const char *pszBlockOffset =
    3496         624 :                         m_poExternalDS->GetRasterBand(i + 1)->GetMetadataItem(
    3497         312 :                             CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
    3498         312 :                     if (pszBlockOffset)
    3499             :                     {
    3500         312 :                         GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
    3501         312 :                         if (i != 0 || y != 0)
    3502             :                         {
    3503         295 :                             if (nOffset != nLastOffset + nBlockSizeBytes)
    3504             :                             {
    3505           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
    3506             :                                          "Block %d,%d band %d not at expected "
    3507             :                                          "offset",
    3508             :                                          0, y, i + 1);
    3509           0 :                                 return false;
    3510             :                             }
    3511             :                         }
    3512         312 :                         nLastOffset = nOffset;
    3513             :                     }
    3514             :                     else
    3515             :                     {
    3516           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    3517             :                                  "Block %d,%d band %d not at expected "
    3518             :                                  "offset",
    3519             :                                  0, y, i + 1);
    3520           0 :                         return false;
    3521             :                     }
    3522             :                 }
    3523             :             }
    3524             :         }
    3525             :         else
    3526             :         {
    3527           1 :             void *pBlockData = VSI_MALLOC_VERBOSE(nBlockSizeBytes);
    3528           1 :             if (pBlockData == nullptr)
    3529           0 :                 return false;
    3530           1 :             if (eDT == GDT_Int64)
    3531             :             {
    3532           0 :                 GDALCopyWords(&nNoDataInt64, eDT, 0, pBlockData, eDT, nDTSize,
    3533             :                               nBlockXSize * nBlockYSize);
    3534             :             }
    3535           1 :             else if (eDT == GDT_UInt64)
    3536             :             {
    3537           0 :                 GDALCopyWords(&nNoDataUInt64, eDT, 0, pBlockData, eDT, nDTSize,
    3538             :                               nBlockXSize * nBlockYSize);
    3539             :             }
    3540             :             else
    3541             :             {
    3542           1 :                 GDALCopyWords(&dfNoData, GDT_Float64, 0, pBlockData, eDT,
    3543             :                               nDTSize, nBlockXSize * nBlockYSize);
    3544             :             }
    3545           2 :             for (int y = 0; y < l_nBlocksPerColumn; y++)
    3546             :             {
    3547           4 :                 for (int i = 0; i < nBands; i++)
    3548             :                 {
    3549           3 :                     if (m_poExternalDS->GetRasterBand(i + 1)->WriteBlock(
    3550           3 :                             0, y, pBlockData) != CE_None)
    3551             :                     {
    3552           0 :                         VSIFree(pBlockData);
    3553           0 :                         return false;
    3554             :                     }
    3555             :                 }
    3556             :             }
    3557           1 :             VSIFree(pBlockData);
    3558           1 :             m_poExternalDS->FlushCache(false);
    3559             : 
    3560             :             // Check that blocks are effectively written in expected order.
    3561           1 :             GIntBig nLastOffset = 0;
    3562           2 :             for (int y = 0; y < l_nBlocksPerColumn; y++)
    3563             :             {
    3564             :                 const char *pszBlockOffset =
    3565           2 :                     m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    3566           1 :                         CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
    3567           1 :                 if (pszBlockOffset)
    3568             :                 {
    3569           1 :                     GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
    3570           1 :                     if (y != 0)
    3571             :                     {
    3572           0 :                         if (nOffset !=
    3573           0 :                             nLastOffset +
    3574           0 :                                 static_cast<GIntBig>(nBlockSizeBytes) * nBands)
    3575             :                         {
    3576           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
    3577             :                                      "Block %d,%d not at expected "
    3578             :                                      "offset",
    3579             :                                      0, y);
    3580           0 :                             return false;
    3581             :                         }
    3582             :                     }
    3583           1 :                     nLastOffset = nOffset;
    3584             :                 }
    3585             :                 else
    3586             :                 {
    3587           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3588             :                              "Block %d,%d not at expected "
    3589             :                              "offset",
    3590             :                              0, y);
    3591           0 :                     return false;
    3592             :                 }
    3593             :             }
    3594             :         }
    3595             : 
    3596          18 :         return true;
    3597             :     }
    3598             : 
    3599          75 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
    3600          75 :     const vsi_l_offset nFileSize = static_cast<vsi_l_offset>(nRasterXSize) *
    3601          75 :                                    nRasterYSize * nBands * nDTSize;
    3602          75 :     if ((eDT == GDT_Int64 && (nNoDataInt64 == 0 || !bHasNoDataAsInt64)) ||
    3603          73 :         (eDT == GDT_UInt64 && (nNoDataUInt64 == 0 || !bHasNoDataAsUInt64)) ||
    3604          70 :         (eDT != GDT_Int64 && eDT != GDT_UInt64 &&
    3605          69 :          (dfNoData == 0 || !bHasNoData)))
    3606             :     {
    3607          66 :         if (VSIFTruncateL(m_fpImage, nFileSize) != 0)
    3608             :         {
    3609           0 :             CPLError(CE_Failure, CPLE_FileIO,
    3610             :                      "Cannot create file of size " CPL_FRMT_GUIB " bytes",
    3611             :                      nFileSize);
    3612           0 :             return false;
    3613             :         }
    3614             :     }
    3615             :     else
    3616             :     {
    3617           9 :         size_t nLineSize = static_cast<size_t>(nRasterXSize) * nDTSize;
    3618           9 :         void *pData = VSI_MALLOC_VERBOSE(nLineSize);
    3619           9 :         if (pData == nullptr)
    3620           0 :             return false;
    3621           9 :         if (eDT == GDT_Int64)
    3622             :         {
    3623           1 :             GDALCopyWords(&nNoDataInt64, eDT, 0, pData, eDT, nDTSize,
    3624             :                           nRasterXSize);
    3625             :         }
    3626           8 :         else if (eDT == GDT_UInt64)
    3627             :         {
    3628           1 :             GDALCopyWords(&nNoDataUInt64, eDT, 0, pData, eDT, nDTSize,
    3629             :                           nRasterXSize);
    3630             :         }
    3631             :         else
    3632             :         {
    3633           7 :             GDALCopyWords(&dfNoData, GDT_Float64, 0, pData, eDT, nDTSize,
    3634             :                           nRasterXSize);
    3635             :         }
    3636             : #ifdef CPL_MSB
    3637             :         if (GDALDataTypeIsComplex(eDT))
    3638             :         {
    3639             :             GDALSwapWords(pData, nDTSize / 2, nRasterXSize * 2, nDTSize / 2);
    3640             :         }
    3641             :         else
    3642             :         {
    3643             :             GDALSwapWords(pData, nDTSize, nRasterXSize, nDTSize);
    3644             :         }
    3645             : #endif
    3646          18 :         for (vsi_l_offset i = 0;
    3647          18 :              i < static_cast<vsi_l_offset>(nRasterYSize) * nBands; i++)
    3648             :         {
    3649           9 :             size_t nBytesWritten = VSIFWriteL(pData, 1, nLineSize, m_fpImage);
    3650           9 :             if (nBytesWritten != nLineSize)
    3651             :             {
    3652           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    3653             :                          "Cannot create file of size " CPL_FRMT_GUIB " bytes",
    3654             :                          nFileSize);
    3655           0 :                 VSIFree(pData);
    3656           0 :                 return false;
    3657             :             }
    3658             :         }
    3659           9 :         VSIFree(pData);
    3660             :     }
    3661          75 :     return true;
    3662             : }
    3663             : 
    3664             : /************************************************************************/
    3665             : /*                        GetSpecialConstants()                         */
    3666             : /************************************************************************/
    3667             : 
    3668          12 : static CPLXMLNode *GetSpecialConstants(const CPLString &osPrefix,
    3669             :                                        CPLXMLNode *psFileAreaObservational)
    3670             : {
    3671          27 :     for (CPLXMLNode *psIter = psFileAreaObservational->psChild; psIter;
    3672          15 :          psIter = psIter->psNext)
    3673             :     {
    3674          48 :         if (psIter->eType == CXT_Element &&
    3675          48 :             STARTS_WITH(psIter->pszValue, (osPrefix + "Array").c_str()))
    3676             :         {
    3677             :             CPLXMLNode *psSC =
    3678          11 :                 CPLGetXMLNode(psIter, (osPrefix + "Special_Constants").c_str());
    3679          11 :             if (psSC)
    3680             :             {
    3681           9 :                 CPLXMLNode *psNext = psSC->psNext;
    3682           9 :                 psSC->psNext = nullptr;
    3683           9 :                 CPLXMLNode *psRet = CPLCloneXMLTree(psSC);
    3684           9 :                 psSC->psNext = psNext;
    3685           9 :                 return psRet;
    3686             :             }
    3687             :         }
    3688             :     }
    3689           3 :     return nullptr;
    3690             : }
    3691             : 
    3692             : /************************************************************************/
    3693             : /*                       WriteHeaderAppendCase()                        */
    3694             : /************************************************************************/
    3695             : 
    3696           4 : void PDS4Dataset::WriteHeaderAppendCase()
    3697             : {
    3698           4 :     CPLXMLTreeCloser oCloser(CPLParseXMLFile(GetDescription()));
    3699           4 :     CPLXMLNode *psRoot = oCloser.get();
    3700           4 :     if (psRoot == nullptr)
    3701           0 :         return;
    3702           4 :     CPLString osPrefix;
    3703           4 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    3704           4 :     if (psProduct == nullptr)
    3705             :     {
    3706           0 :         psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
    3707           0 :         if (psProduct)
    3708           0 :             osPrefix = "pds:";
    3709             :     }
    3710           4 :     if (psProduct == nullptr)
    3711             :     {
    3712           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3713             :                  "Cannot find Product_Observational element");
    3714           0 :         return;
    3715             :     }
    3716           4 :     CPLXMLNode *psFAO = CPLGetXMLNode(
    3717           8 :         psProduct, (osPrefix + "File_Area_Observational").c_str());
    3718           4 :     if (psFAO == nullptr)
    3719             :     {
    3720           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3721             :                  "Cannot find File_Area_Observational element");
    3722           0 :         return;
    3723             :     }
    3724             : 
    3725           4 :     WriteArray(osPrefix, psFAO, nullptr, nullptr);
    3726             : 
    3727           4 :     CPLSerializeXMLTreeToFile(psRoot, GetDescription());
    3728             : }
    3729             : 
    3730             : /************************************************************************/
    3731             : /*                             WriteArray()                             */
    3732             : /************************************************************************/
    3733             : 
    3734         135 : void PDS4Dataset::WriteArray(const CPLString &osPrefix, CPLXMLNode *psFAO,
    3735             :                              const char *pszLocalIdentifierDefault,
    3736             :                              CPLXMLNode *psTemplateSpecialConstants)
    3737             : {
    3738         270 :     const char *pszArrayType = CSLFetchNameValueDef(
    3739         135 :         m_papszCreationOptions, "ARRAY_TYPE", "Array_3D_Image");
    3740         135 :     const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
    3741             :     CPLXMLNode *psArray =
    3742         135 :         CPLCreateXMLNode(psFAO, CXT_Element, (osPrefix + pszArrayType).c_str());
    3743             : 
    3744         270 :     const char *pszLocalIdentifier = CSLFetchNameValueDef(
    3745         135 :         m_papszCreationOptions, "ARRAY_IDENTIFIER", pszLocalIdentifierDefault);
    3746         135 :     if (pszLocalIdentifier)
    3747             :     {
    3748         131 :         CPLCreateXMLElementAndValue(psArray,
    3749         262 :                                     (osPrefix + "local_identifier").c_str(),
    3750             :                                     pszLocalIdentifier);
    3751             :     }
    3752             : 
    3753         135 :     GUIntBig nOffset = m_nBaseOffset;
    3754         135 :     if (m_poExternalDS)
    3755             :     {
    3756             :         const char *pszOffset =
    3757          18 :             m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    3758          18 :                 "BLOCK_OFFSET_0_0", "TIFF");
    3759          18 :         if (pszOffset)
    3760          18 :             nOffset = CPLAtoGIntBig(pszOffset);
    3761             :     }
    3762         270 :     CPLAddXMLAttributeAndValue(
    3763         270 :         CPLCreateXMLElementAndValue(psArray, (osPrefix + "offset").c_str(),
    3764             :                                     CPLSPrintf(CPL_FRMT_GUIB, nOffset)),
    3765             :         "unit", "byte");
    3766         135 :     CPLCreateXMLElementAndValue(psArray, (osPrefix + "axes").c_str(),
    3767             :                                 (bIsArray2D) ? "2" : "3");
    3768         135 :     CPLCreateXMLElementAndValue(
    3769         270 :         psArray, (osPrefix + "axis_index_order").c_str(), "Last Index Fastest");
    3770         135 :     CPLXMLNode *psElementArray = CPLCreateXMLNode(
    3771         270 :         psArray, CXT_Element, (osPrefix + "Element_Array").c_str());
    3772         135 :     GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
    3773         135 :     const char *pszDataType =
    3774         187 :         (eDT == GDT_UInt8)    ? "UnsignedByte"
    3775         101 :         : (eDT == GDT_Int8)   ? "SignedByte"
    3776          91 :         : (eDT == GDT_UInt16) ? "UnsignedLSB2"
    3777          79 :         : (eDT == GDT_Int16)  ? (m_bIsLSB ? "SignedLSB2" : "SignedMSB2")
    3778          70 :         : (eDT == GDT_UInt32) ? (m_bIsLSB ? "UnsignedLSB4" : "UnsignedMSB4")
    3779          62 :         : (eDT == GDT_Int32)  ? (m_bIsLSB ? "SignedLSB4" : "SignedMSB4")
    3780          54 :         : (eDT == GDT_UInt64) ? (m_bIsLSB ? "UnsignedLSB8" : "UnsignedMSB8")
    3781          46 :         : (eDT == GDT_Int64)  ? (m_bIsLSB ? "SignedLSB8" : "SignedMSB8")
    3782             :         : (eDT == GDT_Float32)
    3783          35 :             ? (m_bIsLSB ? "IEEE754LSBSingle" : "IEEE754MSBSingle")
    3784             :         : (eDT == GDT_Float64)
    3785          22 :             ? (m_bIsLSB ? "IEEE754LSBDouble" : "IEEE754MSBDouble")
    3786          12 :         : (eDT == GDT_CFloat32) ? (m_bIsLSB ? "ComplexLSB8" : "ComplexMSB8")
    3787           4 :         : (eDT == GDT_CFloat64) ? (m_bIsLSB ? "ComplexLSB16" : "ComplexMSB16")
    3788             :                                 : "should not happen";
    3789         135 :     CPLCreateXMLElementAndValue(psElementArray,
    3790         270 :                                 (osPrefix + "data_type").c_str(), pszDataType);
    3791             : 
    3792         135 :     const char *pszUnits = GetRasterBand(1)->GetUnitType();
    3793         135 :     const char *pszUnitsCO = CSLFetchNameValue(m_papszCreationOptions, "UNIT");
    3794         135 :     if (pszUnitsCO)
    3795             :     {
    3796           0 :         pszUnits = pszUnitsCO;
    3797             :     }
    3798         135 :     if (pszUnits && pszUnits[0] != 0)
    3799             :     {
    3800           1 :         CPLCreateXMLElementAndValue(psElementArray, (osPrefix + "unit").c_str(),
    3801             :                                     pszUnits);
    3802             :     }
    3803             : 
    3804         135 :     int bHasScale = FALSE;
    3805         135 :     double dfScale = GetRasterBand(1)->GetScale(&bHasScale);
    3806         135 :     if (bHasScale && dfScale != 1.0)
    3807             :     {
    3808          10 :         CPLCreateXMLElementAndValue(psElementArray,
    3809          10 :                                     (osPrefix + "scaling_factor").c_str(),
    3810             :                                     CPLSPrintf("%.17g", dfScale));
    3811             :     }
    3812             : 
    3813         135 :     int bHasOffset = FALSE;
    3814         135 :     double dfOffset = GetRasterBand(1)->GetOffset(&bHasOffset);
    3815         135 :     if (bHasOffset && dfOffset != 1.0)
    3816             :     {
    3817          10 :         CPLCreateXMLElementAndValue(psElementArray,
    3818          10 :                                     (osPrefix + "value_offset").c_str(),
    3819             :                                     CPLSPrintf("%.17g", dfOffset));
    3820             :     }
    3821             : 
    3822             :     // Axis definitions
    3823             :     {
    3824         135 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3825         270 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3826         270 :         CPLCreateXMLElementAndValue(
    3827         270 :             psAxis, (osPrefix + "axis_name").c_str(),
    3828         135 :             EQUAL(m_osInterleave, "BSQ")
    3829             :                 ? "Band"
    3830             :                 :
    3831             :                 /* EQUAL(m_osInterleave, "BIL") ? "Line" : */
    3832             :                 "Line");
    3833         270 :         CPLCreateXMLElementAndValue(
    3834         270 :             psAxis, (osPrefix + "elements").c_str(),
    3835             :             CPLSPrintf("%d",
    3836         135 :                        EQUAL(m_osInterleave, "BSQ")
    3837             :                            ? nBands
    3838             :                            :
    3839             :                            /* EQUAL(m_osInterleave, "BIL") ? nRasterYSize : */
    3840             :                            nRasterYSize));
    3841         135 :         CPLCreateXMLElementAndValue(
    3842         270 :             psAxis, (osPrefix + "sequence_number").c_str(), "1");
    3843             :     }
    3844             :     {
    3845         135 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3846         270 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3847         135 :         CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
    3848         135 :                                     EQUAL(m_osInterleave, "BSQ")   ? "Line"
    3849          11 :                                     : EQUAL(m_osInterleave, "BIL") ? "Band"
    3850             :                                                                    : "Sample");
    3851         270 :         CPLCreateXMLElementAndValue(
    3852         270 :             psAxis, (osPrefix + "elements").c_str(),
    3853         135 :             CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterYSize
    3854          11 :                              : EQUAL(m_osInterleave, "BIL") ? nBands
    3855             :                                                             : nRasterXSize));
    3856         135 :         CPLCreateXMLElementAndValue(
    3857         270 :             psAxis, (osPrefix + "sequence_number").c_str(), "2");
    3858             :     }
    3859         135 :     if (!bIsArray2D)
    3860             :     {
    3861         134 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3862         268 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3863         134 :         CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
    3864         134 :                                     EQUAL(m_osInterleave, "BSQ")   ? "Sample"
    3865          10 :                                     : EQUAL(m_osInterleave, "BIL") ? "Sample"
    3866             :                                                                    : "Band");
    3867         268 :         CPLCreateXMLElementAndValue(
    3868         268 :             psAxis, (osPrefix + "elements").c_str(),
    3869         134 :             CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterXSize
    3870          10 :                              : EQUAL(m_osInterleave, "BIL") ? nRasterXSize
    3871             :                                                             : nBands));
    3872         134 :         CPLCreateXMLElementAndValue(
    3873         268 :             psAxis, (osPrefix + "sequence_number").c_str(), "3");
    3874             :     }
    3875             : 
    3876         135 :     int bHasNoData = FALSE;
    3877         135 :     int64_t nNoDataInt64 = 0;
    3878         135 :     uint64_t nNoDataUInt64 = 0;
    3879         135 :     double dfNoData = 0;
    3880         135 :     if (eDT == GDT_Int64)
    3881           4 :         nNoDataInt64 = GetRasterBand(1)->GetNoDataValueAsInt64(&bHasNoData);
    3882         131 :     else if (eDT == GDT_UInt64)
    3883           4 :         nNoDataUInt64 = GetRasterBand(1)->GetNoDataValueAsUInt64(&bHasNoData);
    3884             :     else
    3885         127 :         dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    3886             : 
    3887         270 :     std::string osNoData;
    3888         135 :     if (bHasNoData)
    3889             :     {
    3890          29 :         if (eDT == GDT_Int64)
    3891           2 :             osNoData = std::to_string(nNoDataInt64);
    3892          27 :         else if (eDT == GDT_UInt64)
    3893           2 :             osNoData = std::to_string(nNoDataUInt64);
    3894          25 :         else if (GDALDataTypeIsInteger(GetRasterBand(1)->GetRasterDataType()))
    3895          20 :             osNoData = std::to_string(static_cast<int64_t>(dfNoData));
    3896           5 :         else if (eDT == GDT_Float32)
    3897             :         {
    3898             :             uint32_t nVal;
    3899           3 :             float fVal = static_cast<float>(dfNoData);
    3900           3 :             memcpy(&nVal, &fVal, sizeof(nVal));
    3901           3 :             osNoData = CPLSPrintf("0x%08X", nVal);
    3902             :         }
    3903             :         else
    3904             :         {
    3905             :             uint64_t nVal;
    3906           2 :             memcpy(&nVal, &dfNoData, sizeof(nVal));
    3907             :             osNoData =
    3908           2 :                 CPLSPrintf("0x%16llX", static_cast<unsigned long long>(nVal));
    3909             :         }
    3910             :     }
    3911             : 
    3912         135 :     if (psTemplateSpecialConstants)
    3913             :     {
    3914           9 :         CPLAddXMLChild(psArray, psTemplateSpecialConstants);
    3915           9 :         if (bHasNoData)
    3916             :         {
    3917             :             CPLXMLNode *psMC =
    3918           6 :                 CPLGetXMLNode(psTemplateSpecialConstants,
    3919          12 :                               (osPrefix + "missing_constant").c_str());
    3920           6 :             if (psMC != nullptr)
    3921             :             {
    3922           4 :                 if (psMC->psChild && psMC->psChild->eType == CXT_Text)
    3923             :                 {
    3924           4 :                     CPLFree(psMC->psChild->pszValue);
    3925           4 :                     psMC->psChild->pszValue = CPLStrdup(osNoData.c_str());
    3926             :                 }
    3927             :             }
    3928             :             else
    3929             :             {
    3930             :                 CPLXMLNode *psSaturatedConstant =
    3931           2 :                     CPLGetXMLNode(psTemplateSpecialConstants,
    3932           4 :                                   (osPrefix + "saturated_constant").c_str());
    3933           4 :                 psMC = CPLCreateXMLElementAndValue(
    3934           4 :                     nullptr, (osPrefix + "missing_constant").c_str(),
    3935             :                     osNoData.c_str());
    3936             :                 CPLXMLNode *psNext;
    3937           2 :                 if (psSaturatedConstant)
    3938             :                 {
    3939           1 :                     psNext = psSaturatedConstant->psNext;
    3940           1 :                     psSaturatedConstant->psNext = psMC;
    3941             :                 }
    3942             :                 else
    3943             :                 {
    3944           1 :                     psNext = psTemplateSpecialConstants->psChild;
    3945           1 :                     psTemplateSpecialConstants->psChild = psMC;
    3946             :                 }
    3947           2 :                 psMC->psNext = psNext;
    3948             :             }
    3949             :         }
    3950             :     }
    3951         126 :     else if (bHasNoData)
    3952             :     {
    3953          23 :         CPLXMLNode *psSC = CPLCreateXMLNode(
    3954          46 :             psArray, CXT_Element, (osPrefix + "Special_Constants").c_str());
    3955          46 :         CPLCreateXMLElementAndValue(
    3956          46 :             psSC, (osPrefix + "missing_constant").c_str(), osNoData.c_str());
    3957             :     }
    3958         135 : }
    3959             : 
    3960             : /************************************************************************/
    3961             : /*                         WriteVectorLayers()                          */
    3962             : /************************************************************************/
    3963             : 
    3964         189 : void PDS4Dataset::WriteVectorLayers(CPLXMLNode *psProduct)
    3965             : {
    3966         378 :     CPLString osPrefix;
    3967         189 :     if (STARTS_WITH(psProduct->pszValue, "pds:"))
    3968           0 :         osPrefix = "pds:";
    3969             : 
    3970         262 :     for (auto &poLayer : m_apoLayers)
    3971             :     {
    3972          73 :         if (!poLayer->IsDirtyHeader())
    3973           4 :             continue;
    3974             : 
    3975          69 :         if (poLayer->GetFeatureCount(false) == 0)
    3976             :         {
    3977          16 :             CPLError(CE_Warning, CPLE_AppDefined,
    3978             :                      "Writing header for layer %s which has 0 features. "
    3979             :                      "This is not legal in PDS4",
    3980          16 :                      poLayer->GetName());
    3981             :         }
    3982             : 
    3983          69 :         if (poLayer->GetRawFieldCount() == 0)
    3984             :         {
    3985          16 :             CPLError(CE_Warning, CPLE_AppDefined,
    3986             :                      "Writing header for layer %s which has 0 fields. "
    3987             :                      "This is not legal in PDS4",
    3988          16 :                      poLayer->GetName());
    3989             :         }
    3990             : 
    3991             :         const std::string osRelativePath(
    3992         138 :             CPLExtractRelativePath(CPLGetPathSafe(m_osXMLFilename).c_str(),
    3993         207 :                                    poLayer->GetFileName(), nullptr));
    3994             : 
    3995          69 :         bool bFound = false;
    3996         634 :         for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
    3997         565 :              psIter = psIter->psNext)
    3998             :         {
    3999         733 :             if (psIter->eType == CXT_Element &&
    4000         164 :                 strcmp(psIter->pszValue,
    4001         733 :                        (osPrefix + "File_Area_Observational").c_str()) == 0)
    4002             :             {
    4003          23 :                 const char *pszFilename = CPLGetXMLValue(
    4004             :                     psIter,
    4005          46 :                     (osPrefix + "File." + osPrefix + "file_name").c_str(), "");
    4006          23 :                 if (strcmp(pszFilename, osRelativePath.c_str()) == 0)
    4007             :                 {
    4008           4 :                     poLayer->RefreshFileAreaObservational(psIter);
    4009           4 :                     bFound = true;
    4010           4 :                     break;
    4011             :                 }
    4012             :             }
    4013             :         }
    4014          69 :         if (!bFound)
    4015             :         {
    4016          65 :             CPLXMLNode *psFAO = CPLCreateXMLNode(
    4017             :                 psProduct, CXT_Element,
    4018         130 :                 (osPrefix + "File_Area_Observational").c_str());
    4019          65 :             CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
    4020         130 :                                                   (osPrefix + "File").c_str());
    4021         130 :             CPLCreateXMLElementAndValue(psFile,
    4022         130 :                                         (osPrefix + "file_name").c_str(),
    4023             :                                         osRelativePath.c_str());
    4024          65 :             poLayer->RefreshFileAreaObservational(psFAO);
    4025             :         }
    4026             :     }
    4027         189 : }
    4028             : 
    4029             : /************************************************************************/
    4030             : /*                            CreateHeader()                            */
    4031             : /************************************************************************/
    4032             : 
    4033         181 : void PDS4Dataset::CreateHeader(CPLXMLNode *psProduct,
    4034             :                                const char *pszCARTVersion)
    4035             : {
    4036         181 :     CPLString osPrefix;
    4037         181 :     if (STARTS_WITH(psProduct->pszValue, "pds:"))
    4038           0 :         osPrefix = "pds:";
    4039             : 
    4040         181 :     if (m_oSRS.IsEmpty() && GetLayerCount() >= 1)
    4041             :     {
    4042          46 :         if (const auto poSRS = GetLayer(0)->GetSpatialRef())
    4043           2 :             m_oSRS = *poSRS;
    4044             :     }
    4045             : 
    4046         280 :     if (!m_oSRS.IsEmpty() &&
    4047          99 :         CSLFetchNameValue(m_papszCreationOptions, "VAR_TARGET") == nullptr)
    4048             :     {
    4049          98 :         const char *pszTarget = nullptr;
    4050          98 :         if (fabs(m_oSRS.GetSemiMajor() - 6378137) < 0.001 * 6378137)
    4051             :         {
    4052          77 :             pszTarget = "Earth";
    4053          77 :             m_papszCreationOptions = CSLSetNameValue(
    4054             :                 m_papszCreationOptions, "VAR_TARGET_TYPE", "Planet");
    4055             :         }
    4056             :         else
    4057             :         {
    4058          21 :             const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
    4059          21 :             if (pszDatum && STARTS_WITH(pszDatum, "D_"))
    4060             :             {
    4061           3 :                 pszTarget = pszDatum + 2;
    4062             :             }
    4063          18 :             else if (pszDatum)
    4064             :             {
    4065          18 :                 pszTarget = pszDatum;
    4066             :             }
    4067             :         }
    4068          98 :         if (pszTarget)
    4069             :         {
    4070          98 :             m_papszCreationOptions = CSLSetNameValue(m_papszCreationOptions,
    4071             :                                                      "VAR_TARGET", pszTarget);
    4072             :         }
    4073             :     }
    4074         181 :     SubstituteVariables(psProduct, m_papszCreationOptions);
    4075             : 
    4076             :     // Remove <Discipline_Area>/<disp:Display_Settings> if there is no raster
    4077         181 :     if (GetRasterCount() == 0)
    4078             :     {
    4079             :         CPLXMLNode *psDisciplineArea =
    4080         141 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    4081          94 :                                       osPrefix + "Discipline_Area")
    4082             :                                          .c_str());
    4083          47 :         if (psDisciplineArea)
    4084             :         {
    4085             :             CPLXMLNode *psDisplaySettings =
    4086          47 :                 CPLGetXMLNode(psDisciplineArea, "disp:Display_Settings");
    4087          47 :             if (psDisplaySettings)
    4088             :             {
    4089          47 :                 CPLRemoveXMLChild(psDisciplineArea, psDisplaySettings);
    4090          47 :                 CPLDestroyXMLNode(psDisplaySettings);
    4091             :             }
    4092             :         }
    4093             :     }
    4094             : 
    4095             :     // Depending on the version of the DISP schema, Local_Internal_Reference
    4096             :     // may be in the disp: namespace or the default one.
    4097             :     const auto GetLocalIdentifierReferenceFromDisciplineArea =
    4098         224 :         [](const CPLXMLNode *psDisciplineArea, const char *pszDefault)
    4099             :     {
    4100         224 :         return CPLGetXMLValue(
    4101             :             psDisciplineArea,
    4102             :             "disp:Display_Settings.Local_Internal_Reference."
    4103             :             "local_identifier_reference",
    4104             :             CPLGetXMLValue(
    4105             :                 psDisciplineArea,
    4106             :                 "disp:Display_Settings.disp:Local_Internal_Reference."
    4107             :                 "local_identifier_reference",
    4108         224 :                 pszDefault));
    4109             :     };
    4110             : 
    4111         181 :     if (GetRasterCount() || !m_oSRS.IsEmpty())
    4112             :     {
    4113             :         CPLXMLNode *psDisciplineArea =
    4114         408 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    4115         272 :                                       osPrefix + "Discipline_Area")
    4116             :                                          .c_str());
    4117         136 :         if (GetRasterCount() && !(m_bGotTransform && !m_oSRS.IsEmpty()))
    4118             :         {
    4119             :             // if we have no georeferencing, strip any existing georeferencing
    4120             :             // from the template
    4121          37 :             if (psDisciplineArea)
    4122             :             {
    4123             :                 CPLXMLNode *psCart =
    4124          33 :                     CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
    4125          33 :                 if (psCart == nullptr)
    4126          32 :                     psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
    4127          33 :                 if (psCart)
    4128             :                 {
    4129           1 :                     CPLRemoveXMLChild(psDisciplineArea, psCart);
    4130           1 :                     CPLDestroyXMLNode(psCart);
    4131             :                 }
    4132             : 
    4133          33 :                 if (CPLGetXMLNode(psDisciplineArea,
    4134          33 :                                   "sp:Spectral_Characteristics"))
    4135             :                 {
    4136             :                     const char *pszArrayType =
    4137           1 :                         CSLFetchNameValue(m_papszCreationOptions, "ARRAY_TYPE");
    4138             :                     // The schematron PDS4_SP_1100.sch requires that
    4139             :                     // sp:local_identifier_reference is used by
    4140             :                     // Array_[2D|3D]_Spectrum/pds:local_identifier
    4141           1 :                     if (pszArrayType == nullptr)
    4142             :                     {
    4143           1 :                         m_papszCreationOptions =
    4144           1 :                             CSLSetNameValue(m_papszCreationOptions,
    4145             :                                             "ARRAY_TYPE", "Array_3D_Spectrum");
    4146             :                     }
    4147           0 :                     else if (!EQUAL(pszArrayType, "Array_2D_Spectrum") &&
    4148           0 :                              !EQUAL(pszArrayType, "Array_3D_Spectrum"))
    4149             :                     {
    4150           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    4151             :                                  "PDS4_SP_xxxx.sch schematron requires the "
    4152             :                                  "use of ARRAY_TYPE=Array_2D_Spectrum or "
    4153             :                                  "Array_3D_Spectrum");
    4154             :                     }
    4155             :                 }
    4156             :             }
    4157             :         }
    4158             :         else
    4159             :         {
    4160          99 :             if (psDisciplineArea == nullptr)
    4161             :             {
    4162           2 :                 CPLXMLNode *psTI = CPLGetXMLNode(
    4163           4 :                     psProduct, (osPrefix + "Observation_Area." + osPrefix +
    4164             :                                 "Target_Identification")
    4165             :                                    .c_str());
    4166           2 :                 if (psTI == nullptr)
    4167             :                 {
    4168           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4169             :                              "Cannot find Target_Identification element in "
    4170             :                              "template");
    4171           1 :                     return;
    4172             :                 }
    4173             :                 psDisciplineArea =
    4174           1 :                     CPLCreateXMLNode(nullptr, CXT_Element,
    4175           2 :                                      (osPrefix + "Discipline_Area").c_str());
    4176           1 :                 if (psTI->psNext)
    4177           0 :                     psDisciplineArea->psNext = psTI->psNext;
    4178           1 :                 psTI->psNext = psDisciplineArea;
    4179             :             }
    4180             :             CPLXMLNode *psCart =
    4181          98 :                 CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
    4182          98 :             if (psCart == nullptr)
    4183          93 :                 psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
    4184          98 :             if (psCart == nullptr)
    4185             :             {
    4186          93 :                 psCart = CPLCreateXMLNode(psDisciplineArea, CXT_Element,
    4187             :                                           "cart:Cartography");
    4188          93 :                 if (CPLGetXMLNode(psProduct, "xmlns:cart") == nullptr)
    4189             :                 {
    4190             :                     CPLXMLNode *psNS =
    4191           1 :                         CPLCreateXMLNode(nullptr, CXT_Attribute, "xmlns:cart");
    4192           1 :                     CPLCreateXMLNode(psNS, CXT_Text,
    4193             :                                      "http://pds.nasa.gov/pds4/cart/v1");
    4194           1 :                     CPLAddXMLChild(psProduct, psNS);
    4195             :                     CPLXMLNode *psSchemaLoc =
    4196           1 :                         CPLGetXMLNode(psProduct, "xsi:schemaLocation");
    4197           1 :                     if (psSchemaLoc != nullptr &&
    4198           1 :                         psSchemaLoc->psChild != nullptr &&
    4199           1 :                         psSchemaLoc->psChild->pszValue != nullptr)
    4200             :                     {
    4201           2 :                         CPLString osCartSchema;
    4202           1 :                         if (strstr(psSchemaLoc->psChild->pszValue,
    4203             :                                    "PDS4_PDS_1800.xsd"))
    4204             :                         {
    4205             :                             // GDAL 2.4
    4206             :                             osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
    4207           1 :                                            "PDS4_CART_1700.xsd";
    4208           1 :                             pszCARTVersion = "1700";
    4209             :                         }
    4210           0 :                         else if (strstr(psSchemaLoc->psChild->pszValue,
    4211             :                                         "PDS4_PDS_1B00.xsd"))
    4212             :                         {
    4213             :                             // GDAL 3.0
    4214             :                             osCartSchema =
    4215             :                                 "https://raw.githubusercontent.com/"
    4216             :                                 "nasa-pds-data-dictionaries/ldd-cart/master/"
    4217           0 :                                 "build/1.B.0.0/PDS4_CART_1B00.xsd";
    4218           0 :                             pszCARTVersion = "1B00";
    4219             :                         }
    4220           0 :                         else if (strstr(psSchemaLoc->psChild->pszValue,
    4221             :                                         "PDS4_PDS_1D00.xsd"))
    4222             :                         {
    4223             :                             // GDAL 3.1
    4224             :                             osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
    4225           0 :                                            "PDS4_CART_1D00_1933.xsd";
    4226           0 :                             pszCARTVersion = "1D00_1933";
    4227             :                         }
    4228           0 :                         else if (strstr(psSchemaLoc->psChild->pszValue,
    4229             :                                         "PDS4_PDS_1G00_1950.xsd"))
    4230             :                         {
    4231             :                             // GDAL 3.4
    4232             :                             osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
    4233           0 :                                            "PDS4_CART_1G00_1950.xsd";
    4234           0 :                             pszCARTVersion = "1G00_1950";
    4235             :                         }
    4236             :                         else
    4237             :                         {
    4238             :                             // GDAL 3.12
    4239             :                             osCartSchema =
    4240             :                                 "https://pds.nasa.gov/pds4/cart/v1/"
    4241           0 :                                 "PDS4_CART_" CURRENT_CART_VERSION ".xsd";
    4242           0 :                             pszCARTVersion = CURRENT_CART_VERSION;
    4243             :                         }
    4244           1 :                         CPLString osNewVal(psSchemaLoc->psChild->pszValue);
    4245             :                         osNewVal +=
    4246           1 :                             " http://pds.nasa.gov/pds4/cart/v1 " + osCartSchema;
    4247           1 :                         CPLFree(psSchemaLoc->psChild->pszValue);
    4248           1 :                         psSchemaLoc->psChild->pszValue = CPLStrdup(osNewVal);
    4249             :                     }
    4250             :                 }
    4251             :             }
    4252             :             else
    4253             :             {
    4254           5 :                 if (psCart->psChild)
    4255             :                 {
    4256           5 :                     CPLDestroyXMLNode(psCart->psChild);
    4257           5 :                     psCart->psChild = nullptr;
    4258             :                 }
    4259             :             }
    4260             : 
    4261          98 :             if (IsCARTVersionGTE(pszCARTVersion, "1900"))
    4262             :             {
    4263             :                 const char *pszLocalIdentifier =
    4264          93 :                     GetLocalIdentifierReferenceFromDisciplineArea(
    4265             :                         psDisciplineArea,
    4266          93 :                         GetRasterCount() == 0 && GetLayerCount() > 0
    4267           2 :                             ? GetLayer(0)->GetName()
    4268             :                             : "image");
    4269          93 :                 CPLXMLNode *psLIR = CPLCreateXMLNode(
    4270             :                     psCart, CXT_Element,
    4271         186 :                     (osPrefix + "Local_Internal_Reference").c_str());
    4272          93 :                 CPLCreateXMLElementAndValue(
    4273         186 :                     psLIR, (osPrefix + "local_identifier_reference").c_str(),
    4274             :                     pszLocalIdentifier);
    4275          93 :                 CPLCreateXMLElementAndValue(
    4276         186 :                     psLIR, (osPrefix + "local_reference_type").c_str(),
    4277             :                     "cartography_parameters_to_image_object");
    4278             :             }
    4279             : 
    4280          98 :             WriteGeoreferencing(psCart, pszCARTVersion);
    4281             :         }
    4282             : 
    4283         270 :         const char *pszVertDir = CSLFetchNameValue(
    4284         135 :             m_papszCreationOptions, "VAR_VERTICAL_DISPLAY_DIRECTION");
    4285         135 :         if (pszVertDir)
    4286             :         {
    4287             :             CPLXMLNode *psVertDirNode =
    4288           1 :                 CPLGetXMLNode(psDisciplineArea,
    4289             :                               "disp:Display_Settings.disp:Display_Direction."
    4290             :                               "disp:vertical_display_direction");
    4291           1 :             if (psVertDirNode == nullptr)
    4292             :             {
    4293           0 :                 CPLError(
    4294             :                     CE_Warning, CPLE_AppDefined,
    4295             :                     "PDS4 template lacks a disp:vertical_display_direction "
    4296             :                     "element where to write %s",
    4297             :                     pszVertDir);
    4298             :             }
    4299             :             else
    4300             :             {
    4301           1 :                 CPLDestroyXMLNode(psVertDirNode->psChild);
    4302           1 :                 psVertDirNode->psChild =
    4303           1 :                     CPLCreateXMLNode(nullptr, CXT_Text, pszVertDir);
    4304             :             }
    4305             :         }
    4306             :     }
    4307             :     else
    4308             :     {
    4309             :         // Remove Observation_Area.Discipline_Area if it contains only
    4310             :         // <disp:Display_Settings> or is empty
    4311             :         CPLXMLNode *psObservationArea =
    4312          45 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area").c_str());
    4313          45 :         if (psObservationArea)
    4314             :         {
    4315          45 :             CPLXMLNode *psDisciplineArea = CPLGetXMLNode(
    4316          90 :                 psObservationArea, (osPrefix + "Discipline_Area").c_str());
    4317          45 :             if (psDisciplineArea &&
    4318          45 :                 (psDisciplineArea->psChild == nullptr ||
    4319           0 :                  (psDisciplineArea->psChild->eType == CXT_Element &&
    4320           0 :                   psDisciplineArea->psChild->psNext == nullptr &&
    4321           0 :                   strcmp(psDisciplineArea->psChild->pszValue,
    4322             :                          "disp:Display_Settings") == 0)))
    4323             :             {
    4324          45 :                 CPLRemoveXMLChild(psObservationArea, psDisciplineArea);
    4325          45 :                 CPLDestroyXMLNode(psDisciplineArea);
    4326             :             }
    4327             :         }
    4328             :     }
    4329             : 
    4330         180 :     if (m_bStripFileAreaObservationalFromTemplate)
    4331             :     {
    4332         180 :         m_bStripFileAreaObservationalFromTemplate = false;
    4333         180 :         CPLXMLNode *psObservationArea = nullptr;
    4334         180 :         CPLXMLNode *psPrev = nullptr;
    4335         180 :         CPLXMLNode *psTemplateSpecialConstants = nullptr;
    4336        1618 :         for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;)
    4337             :         {
    4338        1809 :             if (psIter->eType == CXT_Element &&
    4339        1809 :                 psIter->pszValue == osPrefix + "Observation_Area")
    4340             :             {
    4341         179 :                 psObservationArea = psIter;
    4342         179 :                 psPrev = psIter;
    4343         179 :                 psIter = psIter->psNext;
    4344             :             }
    4345        1631 :             else if (psIter->eType == CXT_Element &&
    4346         192 :                      (psIter->pszValue ==
    4347        1631 :                           osPrefix + "File_Area_Observational" ||
    4348         180 :                       psIter->pszValue ==
    4349        1439 :                           osPrefix + "File_Area_Observational_Supplemental"))
    4350             :             {
    4351          12 :                 if (psIter->pszValue == osPrefix + "File_Area_Observational")
    4352             :                 {
    4353             :                     psTemplateSpecialConstants =
    4354          12 :                         GetSpecialConstants(osPrefix, psIter);
    4355             :                 }
    4356          12 :                 if (psPrev)
    4357          12 :                     psPrev->psNext = psIter->psNext;
    4358             :                 else
    4359             :                 {
    4360           0 :                     CPLAssert(psProduct->psChild == psIter);
    4361           0 :                     psProduct->psChild = psIter->psNext;
    4362             :                 }
    4363          12 :                 CPLXMLNode *psNext = psIter->psNext;
    4364          12 :                 psIter->psNext = nullptr;
    4365          12 :                 CPLDestroyXMLNode(psIter);
    4366          12 :                 psIter = psNext;
    4367             :             }
    4368             :             else
    4369             :             {
    4370        1247 :                 psPrev = psIter;
    4371        1247 :                 psIter = psIter->psNext;
    4372             :             }
    4373             :         }
    4374         180 :         if (psObservationArea == nullptr)
    4375             :         {
    4376           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    4377             :                      "Cannot find Observation_Area in template");
    4378           1 :             CPLDestroyXMLNode(psTemplateSpecialConstants);
    4379           1 :             return;
    4380             :         }
    4381             : 
    4382         179 :         if (GetRasterCount())
    4383             :         {
    4384         132 :             CPLXMLNode *psFAOPrev = psObservationArea;
    4385         134 :             while (psFAOPrev->psNext != nullptr &&
    4386           4 :                    psFAOPrev->psNext->eType == CXT_Comment)
    4387             :             {
    4388           2 :                 psFAOPrev = psFAOPrev->psNext;
    4389             :             }
    4390         132 :             if (psFAOPrev->psNext != nullptr)
    4391             :             {
    4392             :                 // There may be an optional Reference_List element between
    4393             :                 // Observation_Area and File_Area_Observational
    4394           4 :                 if (!(psFAOPrev->psNext->eType == CXT_Element &&
    4395           2 :                       psFAOPrev->psNext->pszValue ==
    4396           4 :                           osPrefix + "Reference_List"))
    4397             :                 {
    4398           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4399             :                              "Unexpected content found after Observation_Area "
    4400             :                              "in template");
    4401           1 :                     CPLDestroyXMLNode(psTemplateSpecialConstants);
    4402           1 :                     return;
    4403             :                 }
    4404           1 :                 psFAOPrev = psFAOPrev->psNext;
    4405           2 :                 while (psFAOPrev->psNext != nullptr &&
    4406           1 :                        psFAOPrev->psNext->eType == CXT_Comment)
    4407             :                 {
    4408           1 :                     psFAOPrev = psFAOPrev->psNext;
    4409             :                 }
    4410           1 :                 if (psFAOPrev->psNext != nullptr)
    4411             :                 {
    4412           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    4413             :                              "Unexpected content found after Reference_List in "
    4414             :                              "template");
    4415           0 :                     CPLDestroyXMLNode(psTemplateSpecialConstants);
    4416           0 :                     return;
    4417             :                 }
    4418             :             }
    4419             : 
    4420         131 :             CPLXMLNode *psFAO = CPLCreateXMLNode(
    4421             :                 nullptr, CXT_Element,
    4422         262 :                 (osPrefix + "File_Area_Observational").c_str());
    4423         131 :             psFAOPrev->psNext = psFAO;
    4424             : 
    4425         131 :             CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
    4426         262 :                                                   (osPrefix + "File").c_str());
    4427         262 :             CPLCreateXMLElementAndValue(psFile,
    4428         262 :                                         (osPrefix + "file_name").c_str(),
    4429             :                                         CPLGetFilename(m_osImageFilename));
    4430         131 :             if (m_bCreatedFromExistingBinaryFile)
    4431             :             {
    4432           7 :                 CPLCreateXMLNode(psFile, CXT_Comment, PREEXISTING_BINARY_FILE);
    4433             :             }
    4434             :             CPLXMLNode *psDisciplineArea =
    4435         393 :                 CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    4436         262 :                                           osPrefix + "Discipline_Area")
    4437             :                                              .c_str());
    4438             :             const char *pszLocalIdentifier =
    4439         131 :                 GetLocalIdentifierReferenceFromDisciplineArea(psDisciplineArea,
    4440             :                                                               "image");
    4441             : 
    4442         147 :             if (m_poExternalDS && m_poExternalDS->GetDriver() &&
    4443          16 :                 EQUAL(m_poExternalDS->GetDriver()->GetDescription(), "GTiff"))
    4444             :             {
    4445             :                 VSILFILE *fpTemp =
    4446          16 :                     VSIFOpenL(m_poExternalDS->GetDescription(), "rb");
    4447          16 :                 if (fpTemp)
    4448             :                 {
    4449          16 :                     GByte abySignature[4] = {0};
    4450          16 :                     VSIFReadL(abySignature, 1, 4, fpTemp);
    4451          16 :                     VSIFCloseL(fpTemp);
    4452          16 :                     const bool bBigTIFF =
    4453          16 :                         abySignature[2] == 43 || abySignature[3] == 43;
    4454             :                     m_osHeaderParsingStandard =
    4455          16 :                         bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
    4456             :                     const char *pszOffset =
    4457          16 :                         m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    4458          16 :                             "BLOCK_OFFSET_0_0", "TIFF");
    4459          16 :                     if (pszOffset)
    4460          16 :                         m_nBaseOffset = CPLAtoGIntBig(pszOffset);
    4461             :                 }
    4462             :             }
    4463             : 
    4464         131 :             if (!m_osHeaderParsingStandard.empty() && m_nBaseOffset > 0)
    4465             :             {
    4466          22 :                 CPLXMLNode *psHeader = CPLCreateXMLNode(
    4467          44 :                     psFAO, CXT_Element, (osPrefix + "Header").c_str());
    4468          22 :                 CPLAddXMLAttributeAndValue(
    4469             :                     CPLCreateXMLElementAndValue(
    4470          44 :                         psHeader, (osPrefix + "offset").c_str(), "0"),
    4471             :                     "unit", "byte");
    4472          22 :                 CPLAddXMLAttributeAndValue(
    4473             :                     CPLCreateXMLElementAndValue(
    4474          44 :                         psHeader, (osPrefix + "object_length").c_str(),
    4475             :                         CPLSPrintf(CPL_FRMT_GUIB,
    4476          22 :                                    static_cast<GUIntBig>(m_nBaseOffset))),
    4477             :                     "unit", "byte");
    4478          44 :                 CPLCreateXMLElementAndValue(
    4479          44 :                     psHeader, (osPrefix + "parsing_standard_id").c_str(),
    4480             :                     m_osHeaderParsingStandard.c_str());
    4481          22 :                 if (m_osHeaderParsingStandard == TIFF_GEOTIFF_STRING)
    4482             :                 {
    4483          18 :                     CPLCreateXMLElementAndValue(
    4484          36 :                         psHeader, (osPrefix + "description").c_str(),
    4485             :                         "TIFF/GeoTIFF header. The TIFF/GeoTIFF format is used "
    4486             :                         "throughout the geospatial and science communities "
    4487             :                         "to share geographic image data. ");
    4488             :                 }
    4489           4 :                 else if (m_osHeaderParsingStandard == BIGTIFF_GEOTIFF_STRING)
    4490             :                 {
    4491           0 :                     CPLCreateXMLElementAndValue(
    4492           0 :                         psHeader, (osPrefix + "description").c_str(),
    4493             :                         "BigTIFF/GeoTIFF header. The BigTIFF/GeoTIFF format is "
    4494             :                         "used "
    4495             :                         "throughout the geospatial and science communities "
    4496             :                         "to share geographic image data. ");
    4497             :                 }
    4498             :             }
    4499             : 
    4500         131 :             WriteArray(osPrefix, psFAO, pszLocalIdentifier,
    4501             :                        psTemplateSpecialConstants);
    4502             :         }
    4503             :     }
    4504             : }
    4505             : 
    4506             : /************************************************************************/
    4507             : /*                            WriteHeader()                             */
    4508             : /************************************************************************/
    4509             : 
    4510         194 : void PDS4Dataset::WriteHeader()
    4511             : {
    4512             :     const bool bAppend =
    4513         194 :         CPLFetchBool(m_papszCreationOptions, "APPEND_SUBDATASET", false);
    4514         194 :     if (bAppend)
    4515             :     {
    4516           4 :         WriteHeaderAppendCase();
    4517           5 :         return;
    4518             :     }
    4519             : 
    4520             :     CPLXMLNode *psRoot;
    4521         190 :     if (m_bCreateHeader)
    4522             :     {
    4523             :         CPLString osTemplateFilename =
    4524         182 :             CSLFetchNameValueDef(m_papszCreationOptions, "TEMPLATE", "");
    4525         182 :         if (!osTemplateFilename.empty())
    4526             :         {
    4527          20 :             if (STARTS_WITH(osTemplateFilename, "http://") ||
    4528          10 :                 STARTS_WITH(osTemplateFilename, "https://"))
    4529             :             {
    4530           0 :                 osTemplateFilename = "/vsicurl_streaming/" + osTemplateFilename;
    4531             :             }
    4532          10 :             psRoot = CPLParseXMLFile(osTemplateFilename);
    4533             :         }
    4534         172 :         else if (!m_osXMLPDS4.empty())
    4535           6 :             psRoot = CPLParseXMLString(m_osXMLPDS4);
    4536             :         else
    4537             :         {
    4538             : #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
    4539             : #ifdef EMBED_RESOURCE_FILES
    4540             :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4541             : #endif
    4542             :             const char *pszDefaultTemplateFilename =
    4543         166 :                 CPLFindFile("gdal", "pds4_template.xml");
    4544         166 :             if (pszDefaultTemplateFilename)
    4545             :             {
    4546         166 :                 psRoot = CPLParseXMLFile(pszDefaultTemplateFilename);
    4547             :             }
    4548             :             else
    4549             : #endif
    4550             :             {
    4551             : #ifdef EMBED_RESOURCE_FILES
    4552             :                 static const bool bOnce [[maybe_unused]] = []()
    4553             :                 {
    4554             :                     CPLDebug("PDS4", "Using embedded pds4_template.xml");
    4555             :                     return true;
    4556             :                 }();
    4557             :                 psRoot = CPLParseXMLString(PDS4GetEmbeddedTemplate());
    4558             : #else
    4559           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    4560             :                          "Cannot find pds4_template.xml and TEMPLATE "
    4561             :                          "creation option not specified");
    4562           0 :                 return;
    4563             : #endif
    4564             :             }
    4565             :         }
    4566             :     }
    4567             :     else
    4568             :     {
    4569           8 :         psRoot = CPLParseXMLFile(m_osXMLFilename);
    4570             :     }
    4571         190 :     CPLXMLTreeCloser oCloser(psRoot);
    4572         190 :     psRoot = oCloser.get();
    4573         190 :     if (psRoot == nullptr)
    4574           0 :         return;
    4575         190 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    4576         190 :     if (psProduct == nullptr)
    4577             :     {
    4578           1 :         psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
    4579             :     }
    4580         190 :     if (psProduct == nullptr)
    4581             :     {
    4582           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4583             :                  "Cannot find Product_Observational element in template");
    4584           1 :         return;
    4585             :     }
    4586             : 
    4587         189 :     if (m_bCreateHeader)
    4588             :     {
    4589         362 :         CPLString osCARTVersion(CURRENT_CART_VERSION);
    4590         181 :         char *pszXML = CPLSerializeXMLTree(psRoot);
    4591         181 :         if (pszXML)
    4592             :         {
    4593         181 :             const char *pszIter = pszXML;
    4594             :             while (true)
    4595             :             {
    4596         355 :                 const char *pszCartSchema = strstr(pszIter, "PDS4_CART_");
    4597         355 :                 if (pszCartSchema)
    4598             :                 {
    4599         348 :                     const char *pszXSDExtension = strstr(pszCartSchema, ".xsd");
    4600         348 :                     if (pszXSDExtension &&
    4601         348 :                         pszXSDExtension - pszCartSchema <= 20)
    4602             :                     {
    4603         174 :                         osCARTVersion = pszCartSchema + strlen("PDS4_CART_");
    4604         174 :                         osCARTVersion.resize(pszXSDExtension - pszCartSchema -
    4605             :                                              strlen("PDS4_CART_"));
    4606         174 :                         break;
    4607             :                     }
    4608             :                     else
    4609             :                     {
    4610         174 :                         pszIter = pszCartSchema + 1;
    4611             :                     }
    4612             :                 }
    4613             :                 else
    4614             :                 {
    4615           7 :                     break;
    4616             :                 }
    4617         174 :             }
    4618             : 
    4619         181 :             CPLFree(pszXML);
    4620             :         }
    4621             : 
    4622         181 :         CreateHeader(psProduct, osCARTVersion.c_str());
    4623             :     }
    4624             : 
    4625         189 :     WriteVectorLayers(psProduct);
    4626             : 
    4627         189 :     CPLSerializeXMLTreeToFile(psRoot, GetDescription());
    4628             : }
    4629             : 
    4630             : /************************************************************************/
    4631             : /*                            ICreateLayer()                            */
    4632             : /************************************************************************/
    4633             : 
    4634          66 : OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName,
    4635             :                                     const OGRGeomFieldDefn *poGeomFieldDefn,
    4636             :                                     CSLConstList papszOptions)
    4637             : {
    4638             :     const char *pszTableType =
    4639          66 :         CSLFetchNameValueDef(papszOptions, "TABLE_TYPE", "DELIMITED");
    4640          66 :     if (!EQUAL(pszTableType, "CHARACTER") && !EQUAL(pszTableType, "BINARY") &&
    4641          55 :         !EQUAL(pszTableType, "DELIMITED"))
    4642             :     {
    4643           0 :         return nullptr;
    4644             :     }
    4645             : 
    4646          66 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    4647             :     const auto poSpatialRef =
    4648          66 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    4649             : 
    4650         126 :     const char *pszExt = EQUAL(pszTableType, "CHARACTER") ? "dat"
    4651          60 :                          : EQUAL(pszTableType, "BINARY")  ? "bin"
    4652             :                                                           : "csv";
    4653         132 :     std::string osBasename(pszName);
    4654         629 :     for (char &ch : osBasename)
    4655             :     {
    4656         563 :         if (!isalnum(static_cast<unsigned char>(ch)) &&
    4657          53 :             static_cast<unsigned>(ch) <= 127)
    4658          53 :             ch = '_';
    4659             :     }
    4660             : 
    4661         198 :     CPLString osFullFilename(CPLFormFilenameSafe(
    4662         132 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(),
    4663         198 :         CPLGetBasenameSafe(m_osXMLFilename.c_str()).c_str(), nullptr));
    4664          66 :     osFullFilename += '_';
    4665          66 :     osFullFilename += osBasename.c_str();
    4666          66 :     osFullFilename += '.';
    4667          66 :     osFullFilename += pszExt;
    4668             :     VSIStatBufL sStat;
    4669          66 :     if (VSIStatL(osFullFilename, &sStat) == 0)
    4670             :     {
    4671           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    4672             :                  "%s already exists. Please delete it before, or "
    4673             :                  "rename the layer",
    4674             :                  osFullFilename.c_str());
    4675           0 :         return nullptr;
    4676             :     }
    4677             : 
    4678          66 :     if (EQUAL(pszTableType, "DELIMITED"))
    4679             :     {
    4680             :         auto poLayer =
    4681          55 :             std::make_unique<PDS4DelimitedTable>(this, pszName, osFullFilename);
    4682          55 :         if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
    4683             :                                          papszOptions))
    4684             :         {
    4685           1 :             return nullptr;
    4686             :         }
    4687          54 :         m_apoLayers.push_back(
    4688         108 :             std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
    4689             :     }
    4690             :     else
    4691             :     {
    4692           0 :         std::unique_ptr<PDS4FixedWidthTable> poLayer;
    4693          11 :         if (EQUAL(pszTableType, "CHARACTER"))
    4694          12 :             poLayer = std::make_unique<PDS4TableCharacter>(this, pszName,
    4695           6 :                                                            osFullFilename);
    4696             :         else
    4697          10 :             poLayer = std::make_unique<PDS4TableBinary>(this, pszName,
    4698           5 :                                                         osFullFilename);
    4699          11 :         if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
    4700             :                                          papszOptions))
    4701             :         {
    4702           0 :             return nullptr;
    4703             :         }
    4704          11 :         m_apoLayers.push_back(
    4705          22 :             std::make_unique<PDS4EditableLayer>(std::move(poLayer)));
    4706             :     }
    4707          65 :     return m_apoLayers.back().get();
    4708             : }
    4709             : 
    4710             : /************************************************************************/
    4711             : /*                           TestCapability()                           */
    4712             : /************************************************************************/
    4713             : 
    4714          69 : int PDS4Dataset::TestCapability(const char *pszCap) const
    4715             : {
    4716          69 :     if (EQUAL(pszCap, ODsCCreateLayer))
    4717          35 :         return eAccess == GA_Update;
    4718          34 :     else if (EQUAL(pszCap, ODsCZGeometries))
    4719           6 :         return TRUE;
    4720             :     else
    4721          28 :         return FALSE;
    4722             : }
    4723             : 
    4724             : /************************************************************************/
    4725             : /*                               Create()                               */
    4726             : /************************************************************************/
    4727             : 
    4728         140 : GDALDataset *PDS4Dataset::Create(const char *pszFilename, int nXSize,
    4729             :                                  int nYSize, int nBandsIn, GDALDataType eType,
    4730             :                                  CSLConstList papszOptions)
    4731             : {
    4732         280 :     return CreateInternal(pszFilename, nullptr, nXSize, nYSize, nBandsIn, eType,
    4733             :                           papszOptions)
    4734         140 :         .release();
    4735             : }
    4736             : 
    4737             : /************************************************************************/
    4738             : /*                           CreateInternal()                           */
    4739             : /************************************************************************/
    4740             : 
    4741         201 : std::unique_ptr<PDS4Dataset> PDS4Dataset::CreateInternal(
    4742             :     const char *pszFilename, GDALDataset *poSrcDS, int nXSize, int nYSize,
    4743             :     int nBandsIn, GDALDataType eType, const char *const *papszOptionsIn)
    4744             : {
    4745         402 :     CPLStringList aosOptions(papszOptionsIn);
    4746             : 
    4747         201 :     if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eType == GDT_Unknown)
    4748             :     {
    4749             :         // Vector file creation
    4750          94 :         auto poDS = std::make_unique<PDS4Dataset>();
    4751          47 :         poDS->SetDescription(pszFilename);
    4752          47 :         poDS->nRasterXSize = 0;
    4753          47 :         poDS->nRasterYSize = 0;
    4754          47 :         poDS->eAccess = GA_Update;
    4755          47 :         poDS->m_osXMLFilename = pszFilename;
    4756          47 :         poDS->m_bCreateHeader = true;
    4757          47 :         poDS->m_bStripFileAreaObservationalFromTemplate = true;
    4758          47 :         poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
    4759          47 :         poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
    4760          47 :         return poDS;
    4761             :     }
    4762             : 
    4763         154 :     if (nXSize == 0)
    4764           0 :         return nullptr;
    4765             : 
    4766         154 :     if (!(eType == GDT_UInt8 || eType == GDT_Int8 || eType == GDT_Int16 ||
    4767          50 :           eType == GDT_UInt16 || eType == GDT_Int32 || eType == GDT_UInt32 ||
    4768          35 :           eType == GDT_Int64 || eType == GDT_UInt64 || eType == GDT_Float32 ||
    4769          20 :           eType == GDT_Float64 || eType == GDT_CFloat32 ||
    4770          10 :           eType == GDT_CFloat64))
    4771             :     {
    4772           6 :         CPLError(
    4773             :             CE_Failure, CPLE_NotSupported,
    4774             :             "The PDS4 driver does not supporting creating files of type %s.",
    4775             :             GDALGetDataTypeName(eType));
    4776           6 :         return nullptr;
    4777             :     }
    4778             : 
    4779         148 :     if (nBandsIn == 0)
    4780             :     {
    4781           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid number of bands");
    4782           1 :         return nullptr;
    4783             :     }
    4784             : 
    4785             :     const char *pszArrayType =
    4786         147 :         aosOptions.FetchNameValueDef("ARRAY_TYPE", "Array_3D_Image");
    4787         147 :     const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
    4788         147 :     if (nBandsIn > 1 && bIsArray2D)
    4789             :     {
    4790           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    4791             :                  "ARRAY_TYPE=%s is not supported for a multi-band raster",
    4792             :                  pszArrayType);
    4793           1 :         return nullptr;
    4794             :     }
    4795             : 
    4796             :     /* -------------------------------------------------------------------- */
    4797             :     /*      Compute pixel, line and band offsets                            */
    4798             :     /* -------------------------------------------------------------------- */
    4799         146 :     const int nItemSize = GDALGetDataTypeSizeBytes(eType);
    4800             :     int nLineOffset, nPixelOffset;
    4801             :     vsi_l_offset nBandOffset;
    4802             : 
    4803             :     const char *pszInterleave =
    4804         146 :         aosOptions.FetchNameValueDef(GDALMD_INTERLEAVE, "BSQ");
    4805         146 :     if (bIsArray2D)
    4806           1 :         pszInterleave = "BIP";
    4807             : 
    4808         146 :     if (EQUAL(pszInterleave, "BIP"))
    4809             :     {
    4810           4 :         nPixelOffset = nItemSize * nBandsIn;
    4811           4 :         if (nPixelOffset > INT_MAX / nBandsIn)
    4812             :         {
    4813           0 :             return nullptr;
    4814             :         }
    4815           4 :         nLineOffset = nPixelOffset * nXSize;
    4816           4 :         nBandOffset = nItemSize;
    4817             :     }
    4818         142 :     else if (EQUAL(pszInterleave, "BSQ"))
    4819             :     {
    4820         138 :         nPixelOffset = nItemSize;
    4821         138 :         if (nPixelOffset > INT_MAX / nXSize)
    4822             :         {
    4823           0 :             return nullptr;
    4824             :         }
    4825         138 :         nLineOffset = nPixelOffset * nXSize;
    4826         138 :         nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nYSize;
    4827             :     }
    4828           4 :     else if (EQUAL(pszInterleave, "BIL"))
    4829             :     {
    4830           3 :         nPixelOffset = nItemSize;
    4831           3 :         if (nPixelOffset > INT_MAX / nBandsIn ||
    4832           3 :             nPixelOffset * nBandsIn > INT_MAX / nXSize)
    4833             :         {
    4834           0 :             return nullptr;
    4835             :         }
    4836           3 :         nLineOffset = nItemSize * nBandsIn * nXSize;
    4837           3 :         nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nXSize;
    4838             :     }
    4839             :     else
    4840             :     {
    4841           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for INTERLEAVE");
    4842           1 :         return nullptr;
    4843             :     }
    4844             : 
    4845             :     const char *pszImageFormat =
    4846         145 :         aosOptions.FetchNameValueDef("IMAGE_FORMAT", "RAW");
    4847         145 :     const char *pszImageExtension = aosOptions.FetchNameValueDef(
    4848         145 :         "IMAGE_EXTENSION", EQUAL(pszImageFormat, "RAW") ? "img" : "tif");
    4849             :     CPLString osImageFilename(aosOptions.FetchNameValueDef(
    4850             :         "IMAGE_FILENAME",
    4851         290 :         CPLResetExtensionSafe(pszFilename, pszImageExtension).c_str()));
    4852             : 
    4853         145 :     const bool bAppend = aosOptions.FetchBool("APPEND_SUBDATASET", false);
    4854         145 :     if (bAppend)
    4855             :     {
    4856           4 :         GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    4857           4 :         auto poExistingPDS4 = OpenInternal(&oOpenInfo);
    4858           4 :         if (!poExistingPDS4)
    4859             :         {
    4860           0 :             return nullptr;
    4861             :         }
    4862           4 :         osImageFilename = poExistingPDS4->m_osImageFilename;
    4863           4 :         poExistingPDS4.reset();
    4864             : 
    4865             :         auto poImageDS = std::unique_ptr<GDALDataset>(
    4866           8 :             GDALDataset::Open(osImageFilename, GDAL_OF_RASTER));
    4867           6 :         if (poImageDS && poImageDS->GetDriver() &&
    4868           2 :             EQUAL(poImageDS->GetDriver()->GetDescription(), "GTiff"))
    4869             :         {
    4870           2 :             pszImageFormat = "GEOTIFF";
    4871             :         }
    4872             :     }
    4873             : 
    4874         145 :     GDALDataset *poExternalDS = nullptr;
    4875         145 :     VSILFILE *fpImage = nullptr;
    4876         145 :     vsi_l_offset nBaseOffset = 0;
    4877         145 :     bool bIsLSB = true;
    4878         290 :     CPLString osHeaderParsingStandard;
    4879             :     const bool bCreateLabelOnly =
    4880         145 :         aosOptions.FetchBool("CREATE_LABEL_ONLY", false);
    4881         145 :     if (bCreateLabelOnly)
    4882             :     {
    4883           8 :         if (poSrcDS == nullptr)
    4884             :         {
    4885           0 :             CPLError(
    4886             :                 CE_Failure, CPLE_AppDefined,
    4887             :                 "CREATE_LABEL_ONLY is only compatible with CreateCopy() mode");
    4888           1 :             return nullptr;
    4889             :         }
    4890           8 :         RawBinaryLayout sLayout;
    4891           8 :         if (!poSrcDS->GetRawBinaryLayout(sLayout))
    4892             :         {
    4893           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    4894             :                      "Source dataset is not compatible with raw binary format");
    4895           1 :             return nullptr;
    4896             :         }
    4897           7 :         if ((nBandsIn > 1 &&
    4898           7 :              sLayout.eInterleaving == RawBinaryLayout::Interleaving::UNKNOWN) ||
    4899           6 :             (nBandsIn == 1 &&
    4900           6 :              !(sLayout.nPixelOffset == nItemSize &&
    4901           6 :                sLayout.nLineOffset == sLayout.nPixelOffset * nXSize)))
    4902             :         {
    4903           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4904             :                      "Source dataset has an interleaving not handled in PDS4");
    4905           0 :             return nullptr;
    4906             :         }
    4907           7 :         fpImage = VSIFOpenL(sLayout.osRawFilename.c_str(), "rb");
    4908           7 :         if (fpImage == nullptr)
    4909             :         {
    4910           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open raw image %s",
    4911             :                      sLayout.osRawFilename.c_str());
    4912           0 :             return nullptr;
    4913             :         }
    4914           7 :         osImageFilename = sLayout.osRawFilename;
    4915           7 :         if (nBandsIn == 1 ||
    4916           1 :             sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIP)
    4917           7 :             pszInterleave = "BIP";
    4918           0 :         else if (sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIL)
    4919           0 :             pszInterleave = "BIL";
    4920             :         else
    4921           0 :             pszInterleave = "BSQ";
    4922           7 :         nBaseOffset = sLayout.nImageOffset;
    4923           7 :         nPixelOffset = static_cast<int>(sLayout.nPixelOffset);
    4924           7 :         nLineOffset = static_cast<int>(sLayout.nLineOffset);
    4925           7 :         nBandOffset = static_cast<vsi_l_offset>(sLayout.nBandOffset);
    4926           7 :         bIsLSB = sLayout.bLittleEndianOrder;
    4927           7 :         auto poSrcDriver = poSrcDS->GetDriver();
    4928           7 :         if (poSrcDriver)
    4929             :         {
    4930           7 :             auto pszDriverName = poSrcDriver->GetDescription();
    4931           7 :             if (EQUAL(pszDriverName, "GTiff"))
    4932             :             {
    4933           2 :                 GByte abySignature[4] = {0};
    4934           2 :                 VSIFReadL(abySignature, 1, 4, fpImage);
    4935           2 :                 const bool bBigTIFF =
    4936           2 :                     abySignature[2] == 43 || abySignature[3] == 43;
    4937             :                 osHeaderParsingStandard =
    4938           2 :                     bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
    4939             :             }
    4940           5 :             else if (EQUAL(pszDriverName, "ISIS3"))
    4941             :             {
    4942           1 :                 osHeaderParsingStandard = "ISIS3";
    4943             :             }
    4944           4 :             else if (EQUAL(pszDriverName, "VICAR"))
    4945             :             {
    4946           1 :                 osHeaderParsingStandard = "VICAR2";
    4947             :             }
    4948           3 :             else if (EQUAL(pszDriverName, "PDS"))
    4949             :             {
    4950           1 :                 osHeaderParsingStandard = "PDS3";
    4951             :             }
    4952           2 :             else if (EQUAL(pszDriverName, "FITS"))
    4953             :             {
    4954           1 :                 osHeaderParsingStandard = "FITS 3.0";
    4955             :                 aosOptions.SetNameValue("VAR_VERTICAL_DISPLAY_DIRECTION",
    4956           1 :                                         "Bottom to Top");
    4957             :             }
    4958             :         }
    4959             :     }
    4960         137 :     else if (EQUAL(pszImageFormat, "GEOTIFF"))
    4961             :     {
    4962          20 :         if (EQUAL(pszInterleave, "BIL"))
    4963             :         {
    4964           2 :             if (aosOptions.FetchBool("@INTERLEAVE_ADDED_AUTOMATICALLY", false))
    4965             :             {
    4966           1 :                 pszInterleave = "BSQ";
    4967             :             }
    4968             :             else
    4969             :             {
    4970           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    4971             :                          "INTERLEAVE=BIL not supported for GeoTIFF in PDS4");
    4972           1 :                 return nullptr;
    4973             :             }
    4974             :         }
    4975             :         GDALDriver *poDrv =
    4976          19 :             static_cast<GDALDriver *>(GDALGetDriverByName("GTiff"));
    4977          19 :         if (poDrv == nullptr)
    4978             :         {
    4979           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GTiff driver");
    4980           0 :             return nullptr;
    4981             :         }
    4982          19 :         char **papszGTiffOptions = nullptr;
    4983             : #ifdef notdef
    4984             :         // In practice I can't see which option we can really use
    4985             :         const char *pszGTiffOptions =
    4986             :             CSLFetchNameValueDef(papszOptions, "GEOTIFF_OPTIONS", "");
    4987             :         char **papszTokens = CSLTokenizeString2(pszGTiffOptions, ",", 0);
    4988             :         if (CPLFetchBool(papszTokens, "TILED", false))
    4989             :         {
    4990             :             CSLDestroy(papszTokens);
    4991             :             CPLError(CE_Failure, CPLE_AppDefined,
    4992             :                      "Tiled GeoTIFF is not supported for PDS4");
    4993             :             return NULL;
    4994             :         }
    4995             :         if (!EQUAL(CSLFetchNameValueDef(papszTokens, "COMPRESS", "NONE"),
    4996             :                    "NONE"))
    4997             :         {
    4998             :             CSLDestroy(papszTokens);
    4999             :             CPLError(CE_Failure, CPLE_AppDefined,
    5000             :                      "Compressed GeoTIFF is not supported for PDS4");
    5001             :             return NULL;
    5002             :         }
    5003             :         papszGTiffOptions =
    5004             :             CSLSetNameValue(papszGTiffOptions, "ENDIANNESS", "LITTLE");
    5005             :         for (int i = 0; papszTokens[i] != NULL; i++)
    5006             :         {
    5007             :             papszGTiffOptions = CSLAddString(papszGTiffOptions, papszTokens[i]);
    5008             :         }
    5009             :         CSLDestroy(papszTokens);
    5010             : #endif
    5011             : 
    5012             :         papszGTiffOptions =
    5013          19 :             CSLSetNameValue(papszGTiffOptions, GDALMD_INTERLEAVE,
    5014          19 :                             EQUAL(pszInterleave, "BSQ") ? "BAND" : "PIXEL");
    5015             :         // Will make sure that our blocks at nodata are not optimized
    5016             :         // away but indeed well written
    5017          19 :         papszGTiffOptions = CSLSetNameValue(
    5018             :             papszGTiffOptions, "@WRITE_EMPTY_TILES_SYNCHRONOUSLY", "YES");
    5019          19 :         if (nBandsIn > 1 && EQUAL(pszInterleave, "BSQ"))
    5020             :         {
    5021             :             papszGTiffOptions =
    5022           2 :                 CSLSetNameValue(papszGTiffOptions, "BLOCKYSIZE", "1");
    5023             :         }
    5024             : 
    5025          19 :         if (bAppend)
    5026             :         {
    5027             :             papszGTiffOptions =
    5028           2 :                 CSLAddString(papszGTiffOptions, "APPEND_SUBDATASET=YES");
    5029             :         }
    5030             : 
    5031          19 :         poExternalDS = poDrv->Create(osImageFilename, nXSize, nYSize, nBandsIn,
    5032             :                                      eType, papszGTiffOptions);
    5033          19 :         CSLDestroy(papszGTiffOptions);
    5034          19 :         if (poExternalDS == nullptr)
    5035             :         {
    5036           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
    5037             :                      osImageFilename.c_str());
    5038           1 :             return nullptr;
    5039             :         }
    5040             :     }
    5041             :     else
    5042             :     {
    5043         232 :         fpImage = VSIFOpenL(
    5044             :             osImageFilename,
    5045             :             bAppend                                                 ? "rb+"
    5046         115 :             : VSISupportsRandomWrite(osImageFilename.c_str(), true) ? "wb+"
    5047             :                                                                     : "wb");
    5048         117 :         if (fpImage == nullptr)
    5049             :         {
    5050           3 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
    5051             :                      osImageFilename.c_str());
    5052           3 :             return nullptr;
    5053             :         }
    5054         114 :         if (bAppend)
    5055             :         {
    5056           2 :             VSIFSeekL(fpImage, 0, SEEK_END);
    5057           2 :             nBaseOffset = VSIFTellL(fpImage);
    5058             :         }
    5059             :     }
    5060             : 
    5061         278 :     auto poDS = std::make_unique<PDS4Dataset>();
    5062         139 :     poDS->SetDescription(pszFilename);
    5063         139 :     poDS->m_bMustInitImageFile = true;
    5064         139 :     poDS->m_fpImage = fpImage;
    5065         139 :     poDS->m_nBaseOffset = nBaseOffset;
    5066         139 :     poDS->m_poExternalDS = poExternalDS;
    5067         139 :     poDS->nRasterXSize = nXSize;
    5068         139 :     poDS->nRasterYSize = nYSize;
    5069         139 :     poDS->eAccess = GA_Update;
    5070         139 :     poDS->m_osImageFilename = std::move(osImageFilename);
    5071         139 :     poDS->m_bCreateHeader = true;
    5072         139 :     poDS->m_bStripFileAreaObservationalFromTemplate = true;
    5073         139 :     poDS->m_osInterleave = pszInterleave;
    5074         139 :     poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
    5075         139 :     poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
    5076         139 :     poDS->m_bIsLSB = bIsLSB;
    5077         139 :     poDS->m_osHeaderParsingStandard = std::move(osHeaderParsingStandard);
    5078         139 :     poDS->m_bCreatedFromExistingBinaryFile = bCreateLabelOnly;
    5079             : 
    5080         139 :     if (EQUAL(pszInterleave, "BIP"))
    5081             :     {
    5082          10 :         poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "PIXEL",
    5083             :                                            GDAL_MDD_IMAGE_STRUCTURE);
    5084             :     }
    5085         129 :     else if (EQUAL(pszInterleave, "BSQ"))
    5086             :     {
    5087         128 :         poDS->GDALDataset::SetMetadataItem(GDALMD_INTERLEAVE, "BAND",
    5088             :                                            GDAL_MDD_IMAGE_STRUCTURE);
    5089             :     }
    5090             : 
    5091         338 :     for (int i = 0; i < nBandsIn; i++)
    5092             :     {
    5093         199 :         if (poDS->m_poExternalDS != nullptr)
    5094             :         {
    5095             :             auto poBand = std::make_unique<PDS4WrapperRasterBand>(
    5096          24 :                 poDS->m_poExternalDS->GetRasterBand(i + 1));
    5097          24 :             poDS->SetBand(i + 1, std::move(poBand));
    5098             :         }
    5099             :         else
    5100             :         {
    5101             :             auto poBand = std::make_unique<PDS4RawRasterBand>(
    5102         175 :                 poDS.get(), i + 1, poDS->m_fpImage,
    5103         175 :                 poDS->m_nBaseOffset + nBandOffset * i, nPixelOffset,
    5104             :                 nLineOffset, eType,
    5105         175 :                 bIsLSB ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    5106         175 :                        : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
    5107         175 :             poDS->SetBand(i + 1, std::move(poBand));
    5108             :         }
    5109             :     }
    5110             : 
    5111         139 :     return poDS;
    5112             : }
    5113             : 
    5114             : /************************************************************************/
    5115             : /*                      PDS4GetUnderlyingDataset()                      */
    5116             : /************************************************************************/
    5117             : 
    5118          65 : static GDALDataset *PDS4GetUnderlyingDataset(GDALDataset *poSrcDS)
    5119             : {
    5120         130 :     if (poSrcDS->GetDriver() != nullptr &&
    5121          65 :         poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
    5122             :     {
    5123           4 :         VRTDataset *poVRTDS = cpl::down_cast<VRTDataset *>(poSrcDS);
    5124           4 :         poSrcDS = poVRTDS->GetSingleSimpleSource();
    5125             :     }
    5126             : 
    5127          65 :     return poSrcDS;
    5128             : }
    5129             : 
    5130             : /************************************************************************/
    5131             : /*                             CreateCopy()                             */
    5132             : /************************************************************************/
    5133             : 
    5134          65 : GDALDataset *PDS4Dataset::CreateCopy(const char *pszFilename,
    5135             :                                      GDALDataset *poSrcDS, int bStrict,
    5136             :                                      CSLConstList papszOptions,
    5137             :                                      GDALProgressFunc pfnProgress,
    5138             :                                      void *pProgressData)
    5139             : {
    5140             :     const char *pszImageFormat =
    5141          65 :         CSLFetchNameValueDef(papszOptions, "IMAGE_FORMAT", "RAW");
    5142          65 :     GDALDataset *poSrcUnderlyingDS = PDS4GetUnderlyingDataset(poSrcDS);
    5143          65 :     if (poSrcUnderlyingDS == nullptr)
    5144           1 :         poSrcUnderlyingDS = poSrcDS;
    5145          73 :     if (EQUAL(pszImageFormat, "GEOTIFF") &&
    5146           8 :         strcmp(poSrcUnderlyingDS->GetDescription(),
    5147             :                CSLFetchNameValueDef(
    5148             :                    papszOptions, "IMAGE_FILENAME",
    5149          73 :                    CPLResetExtensionSafe(pszFilename, "tif").c_str())) == 0)
    5150             :     {
    5151           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    5152             :                  "Output file has same name as input file");
    5153           1 :         return nullptr;
    5154             :     }
    5155          64 :     if (poSrcDS->GetRasterCount() == 0)
    5156             :     {
    5157           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
    5158           1 :         return nullptr;
    5159             :     }
    5160             : 
    5161          63 :     const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
    5162          63 :     if (bAppend)
    5163             :     {
    5164           6 :         GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    5165           6 :         auto poExistingDS = OpenInternal(&oOpenInfo);
    5166           6 :         if (poExistingDS)
    5167             :         {
    5168           6 :             GDALGeoTransform existingGT;
    5169             :             const bool bExistingHasGT =
    5170           6 :                 poExistingDS->GetGeoTransform(existingGT) == CE_None;
    5171           6 :             GDALGeoTransform gt;
    5172           6 :             const bool bSrcHasGT = poSrcDS->GetGeoTransform(gt) == CE_None;
    5173             : 
    5174           6 :             CPLString osExistingProj4;
    5175           6 :             if (const auto poExistingSRS = poExistingDS->GetSpatialRef())
    5176             :             {
    5177           6 :                 char *pszExistingProj4 = nullptr;
    5178           6 :                 poExistingSRS->exportToProj4(&pszExistingProj4);
    5179           6 :                 if (pszExistingProj4)
    5180           6 :                     osExistingProj4 = pszExistingProj4;
    5181           6 :                 CPLFree(pszExistingProj4);
    5182             :             }
    5183           6 :             CPLString osSrcProj4;
    5184           6 :             if (const auto poSrcSRS = poSrcDS->GetSpatialRef())
    5185             :             {
    5186           6 :                 char *pszSrcProj4 = nullptr;
    5187           6 :                 poSrcSRS->exportToProj4(&pszSrcProj4);
    5188           6 :                 if (pszSrcProj4)
    5189           6 :                     osSrcProj4 = pszSrcProj4;
    5190           6 :                 CPLFree(pszSrcProj4);
    5191             :             }
    5192             : 
    5193           6 :             poExistingDS.reset();
    5194             : 
    5195             :             const auto maxRelErrorGT =
    5196           6 :                 [](const GDALGeoTransform &gt1, const GDALGeoTransform &gt2)
    5197             :             {
    5198           6 :                 double maxRelError = 0.0;
    5199          42 :                 for (int i = 0; i < 6; i++)
    5200             :                 {
    5201          36 :                     if (gt1[i] == 0.0)
    5202             :                     {
    5203          12 :                         maxRelError = std::max(maxRelError, std::abs(gt2[i]));
    5204             :                     }
    5205             :                     else
    5206             :                     {
    5207          24 :                         maxRelError =
    5208          48 :                             std::max(maxRelError, std::abs(gt2[i] - gt1[i]) /
    5209          24 :                                                       std::abs(gt1[i]));
    5210             :                     }
    5211             :                 }
    5212           6 :                 return maxRelError;
    5213             :             };
    5214             : 
    5215           6 :             if ((bExistingHasGT && !bSrcHasGT) ||
    5216          18 :                 (!bExistingHasGT && bSrcHasGT) ||
    5217           6 :                 (bExistingHasGT && bSrcHasGT &&
    5218           6 :                  maxRelErrorGT(existingGT, gt) > 1e-10))
    5219             :             {
    5220           1 :                 CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
    5221             :                          "Appending to a dataset with a different "
    5222             :                          "geotransform is not supported");
    5223           1 :                 if (bStrict)
    5224           1 :                     return nullptr;
    5225             :             }
    5226             :             // Do proj string comparison, as it is unlikely that
    5227             :             // OGRSpatialReference::IsSame() will lead to identical reasons due
    5228             :             // to PDS changing CRS names, etc...
    5229           5 :             if (osExistingProj4 != osSrcProj4)
    5230             :             {
    5231           1 :                 CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
    5232             :                          "Appending to a dataset with a different "
    5233             :                          "coordinate reference system is not supported");
    5234           1 :                 if (bStrict)
    5235           1 :                     return nullptr;
    5236             :             }
    5237             :         }
    5238             :     }
    5239             : 
    5240          61 :     const int nXSize = poSrcDS->GetRasterXSize();
    5241          61 :     const int nYSize = poSrcDS->GetRasterYSize();
    5242          61 :     const int nBands = poSrcDS->GetRasterCount();
    5243          61 :     GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    5244             :     auto poDS = CreateInternal(pszFilename, poSrcDS, nXSize, nYSize, nBands,
    5245         122 :                                eType, papszOptions);
    5246          61 :     if (poDS == nullptr)
    5247           6 :         return nullptr;
    5248             : 
    5249          55 :     GDALGeoTransform gt;
    5250          55 :     if (poSrcDS->GetGeoTransform(gt) == CE_None && gt != GDALGeoTransform())
    5251             :     {
    5252          52 :         poDS->SetGeoTransform(gt);
    5253             :     }
    5254             : 
    5255         110 :     if (poSrcDS->GetProjectionRef() != nullptr &&
    5256          55 :         strlen(poSrcDS->GetProjectionRef()) > 0)
    5257             :     {
    5258          52 :         poDS->SetProjection(poSrcDS->GetProjectionRef());
    5259             :     }
    5260             : 
    5261         137 :     for (int i = 1; i <= nBands; i++)
    5262             :     {
    5263          82 :         int bHasNoData = false;
    5264             : 
    5265          82 :         if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_Int64)
    5266             :         {
    5267             :             const auto nNoData =
    5268           0 :                 poSrcDS->GetRasterBand(i)->GetNoDataValueAsInt64(&bHasNoData);
    5269           0 :             if (bHasNoData)
    5270           0 :                 poDS->GetRasterBand(i)->SetNoDataValueAsInt64(nNoData);
    5271             :         }
    5272          82 :         else if (poSrcDS->GetRasterBand(i)->GetRasterDataType() == GDT_UInt64)
    5273             :         {
    5274             :             const auto nNoData =
    5275           0 :                 poSrcDS->GetRasterBand(i)->GetNoDataValueAsUInt64(&bHasNoData);
    5276           0 :             if (bHasNoData)
    5277           0 :                 poDS->GetRasterBand(i)->SetNoDataValueAsUInt64(nNoData);
    5278             :         }
    5279             :         else
    5280             :         {
    5281             :             const double dfNoData =
    5282          82 :                 poSrcDS->GetRasterBand(i)->GetNoDataValue(&bHasNoData);
    5283          82 :             if (bHasNoData)
    5284          14 :                 poDS->GetRasterBand(i)->SetNoDataValue(dfNoData);
    5285             :         }
    5286             : 
    5287          82 :         const double dfOffset = poSrcDS->GetRasterBand(i)->GetOffset();
    5288          82 :         if (dfOffset != 0.0)
    5289           3 :             poDS->GetRasterBand(i)->SetOffset(dfOffset);
    5290             : 
    5291          82 :         const double dfScale = poSrcDS->GetRasterBand(i)->GetScale();
    5292          82 :         if (dfScale != 1.0)
    5293           3 :             poDS->GetRasterBand(i)->SetScale(dfScale);
    5294             : 
    5295         164 :         poDS->GetRasterBand(i)->SetUnitType(
    5296          82 :             poSrcDS->GetRasterBand(i)->GetUnitType());
    5297             :     }
    5298             : 
    5299          55 :     if (poDS->m_bUseSrcLabel)
    5300             :     {
    5301          53 :         CSLConstList papszMD_PDS4 = poSrcDS->GetMetadata("xml:PDS4");
    5302          53 :         if (papszMD_PDS4 != nullptr)
    5303             :         {
    5304           6 :             poDS->SetMetadata(papszMD_PDS4, "xml:PDS4");
    5305             :         }
    5306             :     }
    5307             : 
    5308          55 :     if (poDS->m_poExternalDS == nullptr)
    5309             :     {
    5310             :         // We don't need to initialize the imagery as we are going to copy it
    5311             :         // completely
    5312          46 :         poDS->m_bMustInitImageFile = false;
    5313             :     }
    5314             : 
    5315          55 :     if (!CPLFetchBool(papszOptions, "CREATE_LABEL_ONLY", false))
    5316             :     {
    5317          48 :         CPLErr eErr = GDALDatasetCopyWholeRaster(poSrcDS, poDS.get(), nullptr,
    5318             :                                                  pfnProgress, pProgressData);
    5319          48 :         poDS->FlushCache(false);
    5320          48 :         if (eErr != CE_None)
    5321             :         {
    5322           1 :             return nullptr;
    5323             :         }
    5324             : 
    5325          47 :         if (CPLFetchBool(papszOptions, "PROPAGATE_SRC_METADATA", true))
    5326             :         {
    5327          46 :             CSLConstList papszISIS3MD = poSrcDS->GetMetadata("json:ISIS3");
    5328          46 :             if (papszISIS3MD)
    5329             :             {
    5330           2 :                 poDS->SetMetadata(papszISIS3MD, "json:ISIS3");
    5331             : 
    5332           2 :                 if (poDS->m_poExternalDS)
    5333           1 :                     poDS->m_poExternalDS->SetMetadata(papszISIS3MD,
    5334           1 :                                                       "json:ISIS3");
    5335             :             }
    5336             :         }
    5337             :     }
    5338             : 
    5339          54 :     return poDS.release();
    5340             : }
    5341             : 
    5342             : /************************************************************************/
    5343             : /*                               Delete()                               */
    5344             : /************************************************************************/
    5345             : 
    5346         109 : CPLErr PDS4Dataset::Delete(const char *pszFilename)
    5347             : 
    5348             : {
    5349             :     /* -------------------------------------------------------------------- */
    5350             :     /*      Collect file list.                                              */
    5351             :     /* -------------------------------------------------------------------- */
    5352         218 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    5353         218 :     auto poDS = PDS4Dataset::OpenInternal(&oOpenInfo);
    5354         109 :     if (poDS == nullptr)
    5355             :     {
    5356           0 :         if (CPLGetLastErrorNo() == 0)
    5357           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    5358             :                      "Unable to open %s to obtain file list.", pszFilename);
    5359             : 
    5360           0 :         return CE_Failure;
    5361             :     }
    5362             : 
    5363         109 :     char **papszFileList = poDS->GetFileList();
    5364         218 :     CPLString osImageFilename = poDS->m_osImageFilename;
    5365             :     bool bCreatedFromExistingBinaryFile =
    5366         109 :         poDS->m_bCreatedFromExistingBinaryFile;
    5367             : 
    5368         109 :     poDS.reset();
    5369             : 
    5370         109 :     if (CSLCount(papszFileList) == 0)
    5371             :     {
    5372           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    5373             :                  "Unable to determine files associated with %s, "
    5374             :                  "delete fails.",
    5375             :                  pszFilename);
    5376           0 :         CSLDestroy(papszFileList);
    5377           0 :         return CE_Failure;
    5378             :     }
    5379             : 
    5380             :     /* -------------------------------------------------------------------- */
    5381             :     /*      Delete all files.                                               */
    5382             :     /* -------------------------------------------------------------------- */
    5383         109 :     CPLErr eErr = CE_None;
    5384         392 :     for (int i = 0; papszFileList[i] != nullptr; ++i)
    5385             :     {
    5386         297 :         if (bCreatedFromExistingBinaryFile &&
    5387          14 :             EQUAL(papszFileList[i], osImageFilename))
    5388             :         {
    5389           7 :             continue;
    5390             :         }
    5391         276 :         if (VSIUnlink(papszFileList[i]) != 0)
    5392             :         {
    5393           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Deleting %s failed:\n%s",
    5394           0 :                      papszFileList[i], VSIStrerror(errno));
    5395           0 :             eErr = CE_Failure;
    5396             :         }
    5397             :     }
    5398             : 
    5399         109 :     CSLDestroy(papszFileList);
    5400             : 
    5401         109 :     return eErr;
    5402             : }
    5403             : 
    5404             : /************************************************************************/
    5405             : /*                         GDALRegister_PDS4()                          */
    5406             : /************************************************************************/
    5407             : 
    5408        2135 : void GDALRegister_PDS4()
    5409             : 
    5410             : {
    5411        2135 :     if (GDALGetDriverByName(PDS4_DRIVER_NAME) != nullptr)
    5412         263 :         return;
    5413             : 
    5414        1872 :     GDALDriver *poDriver = new GDALDriver();
    5415        1872 :     PDS4DriverSetCommonMetadata(poDriver);
    5416             : 
    5417        1872 :     poDriver->pfnOpen = PDS4Dataset::Open;
    5418        1872 :     poDriver->pfnCreate = PDS4Dataset::Create;
    5419        1872 :     poDriver->pfnCreateCopy = PDS4Dataset::CreateCopy;
    5420        1872 :     poDriver->pfnDelete = PDS4Dataset::Delete;
    5421             : 
    5422        1872 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    5423             : }

Generated by: LCOV version 1.14