LCOV - code coverage report
Current view: top level - frmts/pds - pds4dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2095 2363 88.7 %
Date: 2024-11-21 22:18:42 Functions: 75 77 97.4 %

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

Generated by: LCOV version 1.14