LCOV - code coverage report
Current view: top level - frmts/pds - pds4dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2104 2374 88.6 %
Date: 2025-01-18 12:42:00 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 std::string FixupTableFilename(const std::string &osFilename)
    1402             : {
    1403             :     VSIStatBufL sStat;
    1404          97 :     if (VSIStatL(osFilename.c_str(), &sStat) == 0)
    1405             :     {
    1406          97 :         return osFilename;
    1407             :     }
    1408           0 :     const std::string osExt = CPLGetExtensionSafe(osFilename.c_str());
    1409           0 :     if (!osExt.empty())
    1410             :     {
    1411           0 :         std::string osTry(osFilename);
    1412           0 :         if (osExt[0] >= 'a' && osExt[0] <= 'z')
    1413             :         {
    1414           0 :             osTry = CPLResetExtensionSafe(osFilename.c_str(),
    1415           0 :                                           CPLString(osExt).toupper());
    1416             :         }
    1417             :         else
    1418             :         {
    1419           0 :             osTry = CPLResetExtensionSafe(osFilename.c_str(),
    1420           0 :                                           CPLString(osExt).tolower());
    1421             :         }
    1422           0 :         if (VSIStatL(osTry.c_str(), &sStat) == 0)
    1423             :         {
    1424           0 :             return osTry;
    1425             :         }
    1426             :     }
    1427           0 :     return osFilename;
    1428             : }
    1429             : 
    1430             : /************************************************************************/
    1431             : /*                       OpenTableCharacter()                           */
    1432             : /************************************************************************/
    1433             : 
    1434          22 : bool PDS4Dataset::OpenTableCharacter(const char *pszFilename,
    1435             :                                      const CPLXMLNode *psTable)
    1436             : {
    1437          44 :     const std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1438          44 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1439          66 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1440             :     std::unique_ptr<PDS4TableCharacter> poLayer(new PDS4TableCharacter(
    1441          44 :         this, osLayerName.c_str(), osFullFilename.c_str()));
    1442          22 :     if (!poLayer->ReadTableDef(psTable))
    1443             :     {
    1444           0 :         return false;
    1445             :     }
    1446             :     std::unique_ptr<PDS4EditableLayer> poEditableLayer(
    1447          22 :         new PDS4EditableLayer(poLayer.release()));
    1448          22 :     m_apoLayers.push_back(std::move(poEditableLayer));
    1449          22 :     return true;
    1450             : }
    1451             : 
    1452             : /************************************************************************/
    1453             : /*                       OpenTableBinary()                              */
    1454             : /************************************************************************/
    1455             : 
    1456          11 : bool PDS4Dataset::OpenTableBinary(const char *pszFilename,
    1457             :                                   const CPLXMLNode *psTable)
    1458             : {
    1459          22 :     const std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1460          22 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1461          33 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1462             :     std::unique_ptr<PDS4TableBinary> poLayer(
    1463          22 :         new PDS4TableBinary(this, osLayerName.c_str(), osFullFilename.c_str()));
    1464          11 :     if (!poLayer->ReadTableDef(psTable))
    1465             :     {
    1466           0 :         return false;
    1467             :     }
    1468             :     std::unique_ptr<PDS4EditableLayer> poEditableLayer(
    1469          11 :         new PDS4EditableLayer(poLayer.release()));
    1470          11 :     m_apoLayers.push_back(std::move(poEditableLayer));
    1471          11 :     return true;
    1472             : }
    1473             : 
    1474             : /************************************************************************/
    1475             : /*                      OpenTableDelimited()                            */
    1476             : /************************************************************************/
    1477             : 
    1478          64 : bool PDS4Dataset::OpenTableDelimited(const char *pszFilename,
    1479             :                                      const CPLXMLNode *psTable)
    1480             : {
    1481         128 :     const std::string osLayerName(CPLGetBasenameSafe(pszFilename));
    1482         128 :     const std::string osFullFilename = FixupTableFilename(CPLFormFilenameSafe(
    1483         192 :         CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(), pszFilename, nullptr));
    1484             :     std::unique_ptr<PDS4DelimitedTable> poLayer(new PDS4DelimitedTable(
    1485         128 :         this, osLayerName.c_str(), osFullFilename.c_str()));
    1486          64 :     if (!poLayer->ReadTableDef(psTable))
    1487             :     {
    1488           0 :         return false;
    1489             :     }
    1490             :     std::unique_ptr<PDS4EditableLayer> poEditableLayer(
    1491          64 :         new PDS4EditableLayer(poLayer.release()));
    1492          64 :     m_apoLayers.push_back(std::move(poEditableLayer));
    1493          64 :     return true;
    1494             : }
    1495             : 
    1496             : /************************************************************************/
    1497             : /*                                Open()                                */
    1498             : /************************************************************************/
    1499             : 
    1500             : // See https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.xsd
    1501             : // and https://pds.nasa.gov/pds4/pds/v1/PDS4_PDS_1800.sch
    1502         286 : PDS4Dataset *PDS4Dataset::OpenInternal(GDALOpenInfo *poOpenInfo)
    1503             : {
    1504         286 :     if (!PDS4DriverIdentify(poOpenInfo))
    1505           0 :         return nullptr;
    1506             : 
    1507         572 :     CPLString osXMLFilename(poOpenInfo->pszFilename);
    1508         286 :     int nFAOIdxLookup = -1;
    1509         286 :     int nArrayIdxLookup = -1;
    1510         286 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:"))
    1511             :     {
    1512             :         char **papszTokens =
    1513          15 :             CSLTokenizeString2(poOpenInfo->pszFilename, ":", 0);
    1514          15 :         int nCount = CSLCount(papszTokens);
    1515          15 :         if (nCount == 5 && strlen(papszTokens[1]) == 1 &&
    1516           1 :             (papszTokens[2][0] == '\\' || papszTokens[2][0] == '/'))
    1517             :         {
    1518           1 :             osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1519           1 :             nFAOIdxLookup = atoi(papszTokens[3]);
    1520           1 :             nArrayIdxLookup = atoi(papszTokens[4]);
    1521             :         }
    1522          14 :         else if (nCount == 5 && (EQUAL(papszTokens[1], "/vsicurl/http") ||
    1523           0 :                                  EQUAL(papszTokens[1], "/vsicurl/https")))
    1524             :         {
    1525           0 :             osXMLFilename = CPLString(papszTokens[1]) + ":" + papszTokens[2];
    1526           0 :             nFAOIdxLookup = atoi(papszTokens[3]);
    1527           0 :             nArrayIdxLookup = atoi(papszTokens[4]);
    1528             :         }
    1529          14 :         else if (nCount == 4)
    1530             :         {
    1531          13 :             osXMLFilename = papszTokens[1];
    1532          13 :             nFAOIdxLookup = atoi(papszTokens[2]);
    1533          13 :             nArrayIdxLookup = atoi(papszTokens[3]);
    1534             :         }
    1535             :         else
    1536             :         {
    1537           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1538             :                      "Invalid syntax for PDS4 subdataset name");
    1539           1 :             CSLDestroy(papszTokens);
    1540           1 :             return nullptr;
    1541             :         }
    1542          14 :         CSLDestroy(papszTokens);
    1543             :     }
    1544             : 
    1545         570 :     CPLXMLTreeCloser oCloser(CPLParseXMLFile(osXMLFilename));
    1546         285 :     CPLXMLNode *psRoot = oCloser.get();
    1547         285 :     CPLStripXMLNamespace(psRoot, nullptr, TRUE);
    1548             : 
    1549         570 :     GDALAccess eAccess = STARTS_WITH_CI(poOpenInfo->pszFilename, "PDS4:")
    1550         285 :                              ? GA_ReadOnly
    1551             :                              : poOpenInfo->eAccess;
    1552             : 
    1553         285 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    1554         285 :     if (psProduct == nullptr)
    1555             :     {
    1556           4 :         eAccess = GA_ReadOnly;
    1557           4 :         psProduct = CPLGetXMLNode(psRoot, "=Product_Ancillary");
    1558           4 :         if (psProduct == nullptr)
    1559             :         {
    1560           4 :             psProduct = CPLGetXMLNode(psRoot, "=Product_Collection");
    1561             :         }
    1562             :     }
    1563         285 :     if (psProduct == nullptr)
    1564             :     {
    1565           3 :         return nullptr;
    1566             :     }
    1567             : 
    1568             :     // Test case:
    1569             :     // https://starbase.jpl.nasa.gov/pds4/1700/dph_example_products/test_Images_DisplaySettings/TestPattern_Image/TestPattern.xml
    1570         282 :     const char *pszVertDir = CPLGetXMLValue(
    1571             :         psProduct,
    1572             :         "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
    1573             :         "vertical_display_direction",
    1574             :         "");
    1575         282 :     const bool bBottomToTop = EQUAL(pszVertDir, "Bottom to Top");
    1576             : 
    1577         282 :     const char *pszHorizDir = CPLGetXMLValue(
    1578             :         psProduct,
    1579             :         "Observation_Area.Discipline_Area.Display_Settings.Display_Direction."
    1580             :         "horizontal_display_direction",
    1581             :         "");
    1582         282 :     const bool bRightToLeft = EQUAL(pszHorizDir, "Right to Left");
    1583             : 
    1584         564 :     auto poDS = std::make_unique<PDS4Dataset>();
    1585         282 :     poDS->m_osXMLFilename = osXMLFilename;
    1586         282 :     poDS->eAccess = eAccess;
    1587         282 :     poDS->papszOpenOptions = CSLDuplicate(poOpenInfo->papszOpenOptions);
    1588             : 
    1589         564 :     CPLStringList aosSubdatasets;
    1590         282 :     int nFAOIdx = 0;
    1591        2755 :     for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
    1592        2473 :          psIter = psIter->psNext)
    1593             :     {
    1594        2475 :         if (psIter->eType != CXT_Element ||
    1595         875 :             (strcmp(psIter->pszValue, "File_Area_Observational") != 0 &&
    1596         564 :              strcmp(psIter->pszValue, "File_Area_Ancillary") != 0 &&
    1597         564 :              strcmp(psIter->pszValue, "File_Area_Inventory") != 0))
    1598             :         {
    1599        2163 :             continue;
    1600             :         }
    1601             : 
    1602         312 :         nFAOIdx++;
    1603         312 :         CPLXMLNode *psFile = CPLGetXMLNode(psIter, "File");
    1604         312 :         if (psFile == nullptr)
    1605             :         {
    1606           1 :             continue;
    1607             :         }
    1608         311 :         const char *pszFilename = CPLGetXMLValue(psFile, "file_name", nullptr);
    1609         311 :         if (pszFilename == nullptr)
    1610             :         {
    1611           1 :             continue;
    1612             :         }
    1613             : 
    1614         653 :         for (CPLXMLNode *psSubIter = psFile->psChild; psSubIter != nullptr;
    1615         343 :              psSubIter = psSubIter->psNext)
    1616             :         {
    1617         343 :             if (psSubIter->eType == CXT_Comment &&
    1618          14 :                 EQUAL(psSubIter->pszValue, PREEXISTING_BINARY_FILE))
    1619             :             {
    1620          14 :                 poDS->m_bCreatedFromExistingBinaryFile = true;
    1621             :             }
    1622             :         }
    1623             : 
    1624         310 :         int nArrayIdx = 0;
    1625         310 :         for (CPLXMLNode *psSubIter = psIter->psChild;
    1626         983 :              (nFAOIdxLookup < 0 || nFAOIdxLookup == nFAOIdx) &&
    1627             :              psSubIter != nullptr;
    1628         673 :              psSubIter = psSubIter->psNext)
    1629             :         {
    1630         675 :             if (psSubIter->eType != CXT_Element)
    1631             :             {
    1632         477 :                 continue;
    1633             :             }
    1634         675 :             int nDIM = 0;
    1635         675 :             if (STARTS_WITH(psSubIter->pszValue, "Array_1D"))
    1636             :             {
    1637           0 :                 nDIM = 1;
    1638             :             }
    1639         675 :             else if (STARTS_WITH(psSubIter->pszValue, "Array_2D"))
    1640             :             {
    1641           6 :                 nDIM = 2;
    1642             :             }
    1643         669 :             else if (STARTS_WITH(psSubIter->pszValue, "Array_3D"))
    1644             :             {
    1645         221 :                 nDIM = 3;
    1646             :             }
    1647         448 :             else if (strcmp(psSubIter->pszValue, "Array") == 0)
    1648             :             {
    1649           3 :                 nDIM = atoi(CPLGetXMLValue(psSubIter, "axes", "0"));
    1650             :             }
    1651         445 :             else if (strcmp(psSubIter->pszValue, "Table_Character") == 0)
    1652             :             {
    1653          22 :                 poDS->OpenTableCharacter(pszFilename, psSubIter);
    1654          22 :                 continue;
    1655             :             }
    1656         423 :             else if (strcmp(psSubIter->pszValue, "Table_Binary") == 0)
    1657             :             {
    1658          11 :                 poDS->OpenTableBinary(pszFilename, psSubIter);
    1659          11 :                 continue;
    1660             :             }
    1661         412 :             else if (strcmp(psSubIter->pszValue, "Table_Delimited") == 0 ||
    1662         349 :                      strcmp(psSubIter->pszValue, "Inventory") == 0)
    1663             :             {
    1664          64 :                 poDS->OpenTableDelimited(pszFilename, psSubIter);
    1665          64 :                 continue;
    1666             :             }
    1667         578 :             if (nDIM == 0)
    1668             :             {
    1669         348 :                 continue;
    1670             :             }
    1671             : 
    1672         230 :             nArrayIdx++;
    1673             :             // Does it match a selected subdataset ?
    1674         230 :             if (nArrayIdxLookup > 0 && nArrayIdx != nArrayIdxLookup)
    1675             :             {
    1676          13 :                 continue;
    1677             :             }
    1678             : 
    1679             :             const char *pszArrayName =
    1680         217 :                 CPLGetXMLValue(psSubIter, "name", nullptr);
    1681             :             const char *pszArrayId =
    1682         217 :                 CPLGetXMLValue(psSubIter, "local_identifier", nullptr);
    1683             :             vsi_l_offset nOffset = static_cast<vsi_l_offset>(
    1684         217 :                 CPLAtoGIntBig(CPLGetXMLValue(psSubIter, "offset", "0")));
    1685             : 
    1686             :             const char *pszAxisIndexOrder =
    1687         217 :                 CPLGetXMLValue(psSubIter, "axis_index_order", "");
    1688         217 :             if (!EQUAL(pszAxisIndexOrder, "Last Index Fastest"))
    1689             :             {
    1690           1 :                 CPLError(CE_Warning, CPLE_NotSupported,
    1691             :                          "axis_index_order = '%s' unhandled",
    1692             :                          pszAxisIndexOrder);
    1693           1 :                 continue;
    1694             :             }
    1695             : 
    1696             :             // Figure out data type
    1697             :             const char *pszDataType =
    1698         216 :                 CPLGetXMLValue(psSubIter, "Element_Array.data_type", "");
    1699         216 :             GDALDataType eDT = GDT_Byte;
    1700         216 :             bool bLSBOrder = strstr(pszDataType, "LSB") != nullptr;
    1701             : 
    1702             :             // ComplexLSB16', 'ComplexLSB8', 'ComplexMSB16', 'ComplexMSB8',
    1703             :             // 'IEEE754LSBDouble', 'IEEE754LSBSingle', 'IEEE754MSBDouble',
    1704             :             // 'IEEE754MSBSingle', 'SignedBitString', 'SignedByte',
    1705             :             // 'SignedLSB2', 'SignedLSB4', 'SignedLSB8', 'SignedMSB2',
    1706             :             // 'SignedMSB4', 'SignedMSB8', 'UnsignedBitString', 'UnsignedByte',
    1707             :             // 'UnsignedLSB2', 'UnsignedLSB4', 'UnsignedLSB8', 'UnsignedMSB2',
    1708             :             // 'UnsignedMSB4', 'UnsignedMSB8'
    1709         216 :             if (EQUAL(pszDataType, "ComplexLSB16") ||
    1710         212 :                 EQUAL(pszDataType, "ComplexMSB16"))
    1711             :             {
    1712           6 :                 eDT = GDT_CFloat64;
    1713             :             }
    1714         210 :             else if (EQUAL(pszDataType, "ComplexLSB8") ||
    1715         206 :                      EQUAL(pszDataType, "ComplexMSB8"))
    1716             :             {
    1717           4 :                 eDT = GDT_CFloat32;
    1718             :             }
    1719         206 :             else if (EQUAL(pszDataType, "IEEE754LSBDouble") ||
    1720         202 :                      EQUAL(pszDataType, "IEEE754MSBDouble"))
    1721             :             {
    1722           4 :                 eDT = GDT_Float64;
    1723             :             }
    1724         202 :             else if (EQUAL(pszDataType, "IEEE754LSBSingle") ||
    1725         194 :                      EQUAL(pszDataType, "IEEE754MSBSingle"))
    1726             :             {
    1727           8 :                 eDT = GDT_Float32;
    1728             :             }
    1729             :             // SignedBitString unhandled
    1730         194 :             else if (EQUAL(pszDataType, "SignedByte"))
    1731             :             {
    1732           6 :                 eDT = GDT_Int8;
    1733             :             }
    1734         188 :             else if (EQUAL(pszDataType, "SignedLSB2") ||
    1735         184 :                      EQUAL(pszDataType, "SignedMSB2"))
    1736             :             {
    1737           6 :                 eDT = GDT_Int16;
    1738             :             }
    1739         182 :             else if (EQUAL(pszDataType, "SignedLSB4") ||
    1740         178 :                      EQUAL(pszDataType, "SignedMSB4"))
    1741             :             {
    1742           4 :                 eDT = GDT_Int32;
    1743             :             }
    1744             :             // SignedLSB8 and SignedMSB8 unhandled
    1745         178 :             else if (EQUAL(pszDataType, "UnsignedByte"))
    1746             :             {
    1747         167 :                 eDT = GDT_Byte;
    1748             :             }
    1749          11 :             else if (EQUAL(pszDataType, "UnsignedLSB2") ||
    1750           5 :                      EQUAL(pszDataType, "UnsignedMSB2"))
    1751             :             {
    1752           6 :                 eDT = GDT_UInt16;
    1753             :             }
    1754           5 :             else if (EQUAL(pszDataType, "UnsignedLSB4") ||
    1755           1 :                      EQUAL(pszDataType, "UnsignedMSB4"))
    1756             :             {
    1757           4 :                 eDT = GDT_UInt32;
    1758             :             }
    1759             :             // UnsignedLSB8 and UnsignedMSB8 unhandled
    1760             :             else
    1761             :             {
    1762           1 :                 CPLDebug("PDS4", "data_type = '%s' unhandled", pszDataType);
    1763           1 :                 continue;
    1764             :             }
    1765             : 
    1766         215 :             poDS->m_osUnits =
    1767         430 :                 CPLGetXMLValue(psSubIter, "Element_Array.unit", "");
    1768             : 
    1769         215 :             double dfValueOffset = CPLAtof(
    1770             :                 CPLGetXMLValue(psSubIter, "Element_Array.value_offset", "0"));
    1771         215 :             double dfValueScale = CPLAtof(
    1772             :                 CPLGetXMLValue(psSubIter, "Element_Array.scaling_factor", "1"));
    1773             : 
    1774             :             // Parse Axis_Array elements
    1775         215 :             char szOrder[4] = {0};
    1776         215 :             int l_nBands = 1;
    1777         215 :             int nLines = 0;
    1778         215 :             int nSamples = 0;
    1779         215 :             int nAxisFound = 0;
    1780         215 :             int anElements[3] = {0};
    1781         215 :             for (CPLXMLNode *psAxisIter = psSubIter->psChild;
    1782        1971 :                  psAxisIter != nullptr; psAxisIter = psAxisIter->psNext)
    1783             :             {
    1784        1756 :                 if (psAxisIter->eType != CXT_Element ||
    1785        1756 :                     strcmp(psAxisIter->pszValue, "Axis_Array") != 0)
    1786             :                 {
    1787        1113 :                     continue;
    1788             :                 }
    1789             :                 const char *pszAxisName =
    1790         643 :                     CPLGetXMLValue(psAxisIter, "axis_name", nullptr);
    1791             :                 const char *pszElements =
    1792         643 :                     CPLGetXMLValue(psAxisIter, "elements", nullptr);
    1793             :                 const char *pszSequenceNumber =
    1794         643 :                     CPLGetXMLValue(psAxisIter, "sequence_number", nullptr);
    1795         643 :                 if (pszAxisName == nullptr || pszElements == nullptr ||
    1796             :                     pszSequenceNumber == nullptr)
    1797             :                 {
    1798           1 :                     continue;
    1799             :                 }
    1800         642 :                 int nSeqNumber = atoi(pszSequenceNumber);
    1801         642 :                 if (nSeqNumber < 1 || nSeqNumber > nDIM)
    1802             :                 {
    1803           2 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1804             :                              "Invalid sequence_number = %s", pszSequenceNumber);
    1805           2 :                     continue;
    1806             :                 }
    1807         640 :                 int nElements = atoi(pszElements);
    1808         640 :                 if (nElements <= 0)
    1809             :                 {
    1810           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1811             :                              "Invalid elements = %s", pszElements);
    1812           1 :                     continue;
    1813             :                 }
    1814         639 :                 nSeqNumber--;
    1815         639 :                 if (szOrder[nSeqNumber] != '\0')
    1816             :                 {
    1817           1 :                     CPLError(CE_Warning, CPLE_AppDefined,
    1818             :                              "Invalid sequence_number = %s", pszSequenceNumber);
    1819           1 :                     continue;
    1820             :                 }
    1821         638 :                 if (EQUAL(pszAxisName, "Band") && nDIM == 3)
    1822             :                 {
    1823         209 :                     szOrder[nSeqNumber] = 'B';
    1824         209 :                     l_nBands = nElements;
    1825         209 :                     anElements[nSeqNumber] = nElements;
    1826         209 :                     nAxisFound++;
    1827             :                 }
    1828         429 :                 else if (EQUAL(pszAxisName, "Line"))
    1829             :                 {
    1830         214 :                     szOrder[nSeqNumber] = 'L';
    1831         214 :                     nLines = nElements;
    1832         214 :                     anElements[nSeqNumber] = nElements;
    1833         214 :                     nAxisFound++;
    1834             :                 }
    1835         215 :                 else if (EQUAL(pszAxisName, "Sample"))
    1836             :                 {
    1837         214 :                     szOrder[nSeqNumber] = 'S';
    1838         214 :                     nSamples = nElements;
    1839         214 :                     anElements[nSeqNumber] = nElements;
    1840         214 :                     nAxisFound++;
    1841             :                 }
    1842             :                 else
    1843             :                 {
    1844           1 :                     CPLError(CE_Warning, CPLE_NotSupported,
    1845             :                              "Unsupported axis_name = %s", pszAxisName);
    1846           1 :                     continue;
    1847             :                 }
    1848             :             }
    1849         215 :             if (nAxisFound != nDIM)
    1850             :             {
    1851           1 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1852             :                          "Found only %d Axis_Array elements. %d expected",
    1853             :                          nAxisFound, nDIM);
    1854           1 :                 continue;
    1855             :             }
    1856             : 
    1857         428 :             if (!GDALCheckDatasetDimensions(nSamples, nLines) ||
    1858         214 :                 !GDALCheckBandCount(l_nBands, FALSE))
    1859             :             {
    1860           1 :                 continue;
    1861             :             }
    1862             : 
    1863             :             // Compute pixel, line and band spacing
    1864         213 :             vsi_l_offset nSpacing = GDALGetDataTypeSizeBytes(eDT);
    1865         213 :             int nPixelOffset = 0;
    1866         213 :             int nLineOffset = 0;
    1867         213 :             vsi_l_offset nBandOffset = 0;
    1868         213 :             int nCountPreviousDim = 1;
    1869         844 :             for (int i = nDIM - 1; i >= 0; i--)
    1870             :             {
    1871         633 :                 if (szOrder[i] == 'S')
    1872             :                 {
    1873         213 :                     if (nSpacing >
    1874         213 :                         static_cast<vsi_l_offset>(INT_MAX / nCountPreviousDim))
    1875             :                     {
    1876           1 :                         CPLError(CE_Failure, CPLE_NotSupported,
    1877             :                                  "Integer overflow");
    1878           2 :                         return nullptr;
    1879             :                     }
    1880         212 :                     nPixelOffset =
    1881         212 :                         static_cast<int>(nSpacing * nCountPreviousDim);
    1882         212 :                     nSpacing = nPixelOffset;
    1883             :                 }
    1884         420 :                 else if (szOrder[i] == 'L')
    1885             :                 {
    1886         213 :                     if (nSpacing >
    1887         213 :                         static_cast<vsi_l_offset>(INT_MAX / nCountPreviousDim))
    1888             :                     {
    1889           1 :                         CPLError(CE_Failure, CPLE_NotSupported,
    1890             :                                  "Integer overflow");
    1891           1 :                         return nullptr;
    1892             :                     }
    1893         212 :                     nLineOffset =
    1894         212 :                         static_cast<int>(nSpacing * nCountPreviousDim);
    1895         212 :                     nSpacing = nLineOffset;
    1896             :                 }
    1897             :                 else
    1898             :                 {
    1899         207 :                     nBandOffset = nSpacing * nCountPreviousDim;
    1900         207 :                     nSpacing = nBandOffset;
    1901             :                 }
    1902         631 :                 nCountPreviousDim = anElements[i];
    1903             :             }
    1904             : 
    1905             :             // Retrieve no data value
    1906         211 :             bool bNoDataSet = false;
    1907         211 :             double dfNoData = 0.0;
    1908         211 :             std::vector<double> adfConstants;
    1909         211 :             CPLXMLNode *psSC = CPLGetXMLNode(psSubIter, "Special_Constants");
    1910         211 :             if (psSC)
    1911             :             {
    1912             :                 const char *pszMC =
    1913          60 :                     CPLGetXMLValue(psSC, "missing_constant", nullptr);
    1914          60 :                 if (pszMC)
    1915             :                 {
    1916          60 :                     bNoDataSet = true;
    1917          60 :                     dfNoData = CPLAtof(pszMC);
    1918             :                 }
    1919             : 
    1920          60 :                 const char *apszConstantNames[] = {
    1921             :                     "saturated_constant",
    1922             :                     "missing_constant",
    1923             :                     "error_constant",
    1924             :                     "invalid_constant",
    1925             :                     "unknown_constant",
    1926             :                     "not_applicable_constant",
    1927             :                     "high_instrument_saturation",
    1928             :                     "high_representation_saturation",
    1929             :                     "low_instrument_saturation",
    1930             :                     "low_representation_saturation"};
    1931         660 :                 for (size_t i = 0; i < CPL_ARRAYSIZE(apszConstantNames); i++)
    1932             :                 {
    1933             :                     const char *pszConstant =
    1934         600 :                         CPLGetXMLValue(psSC, apszConstantNames[i], nullptr);
    1935         600 :                     if (pszConstant)
    1936          94 :                         adfConstants.push_back(CPLAtof(pszConstant));
    1937             :                 }
    1938             :             }
    1939             : 
    1940             :             // Add subdatasets
    1941         211 :             const int nSDSIdx = 1 + aosSubdatasets.size() / 2;
    1942             :             aosSubdatasets.SetNameValue(
    1943             :                 CPLSPrintf("SUBDATASET_%d_NAME", nSDSIdx),
    1944             :                 CPLSPrintf("PDS4:%s:%d:%d", osXMLFilename.c_str(), nFAOIdx,
    1945         211 :                            nArrayIdx));
    1946             :             aosSubdatasets.SetNameValue(
    1947             :                 CPLSPrintf("SUBDATASET_%d_DESC", nSDSIdx),
    1948             :                 CPLSPrintf("Image file %s, array %s", pszFilename,
    1949             :                            pszArrayName ? pszArrayName
    1950         211 :                            : pszArrayId ? pszArrayId
    1951         422 :                                         : CPLSPrintf("%d", nArrayIdx)));
    1952             : 
    1953         211 :             if (poDS->nBands != 0)
    1954          14 :                 continue;
    1955             : 
    1956             :             const std::string osImageFullFilename = CPLFormFilenameSafe(
    1957         197 :                 CPLGetPathSafe(osXMLFilename.c_str()).c_str(), pszFilename,
    1958         197 :                 nullptr);
    1959         197 :             VSILFILE *fp = VSIFOpenExL(
    1960             :                 osImageFullFilename.c_str(),
    1961         197 :                 (poOpenInfo->eAccess == GA_Update) ? "rb+" : "rb", true);
    1962         197 :             if (fp == nullptr)
    1963             :             {
    1964           1 :                 CPLError(CE_Warning, CPLE_FileIO, "Cannt open %s: %s",
    1965             :                          osImageFullFilename.c_str(), VSIGetLastErrorMsg());
    1966           1 :                 continue;
    1967             :             }
    1968         196 :             poDS->nRasterXSize = nSamples;
    1969         196 :             poDS->nRasterYSize = nLines;
    1970         196 :             poDS->m_osImageFilename = osImageFullFilename;
    1971         196 :             poDS->m_fpImage = fp;
    1972         196 :             poDS->m_bIsLSB = bLSBOrder;
    1973             : 
    1974         196 :             if (memcmp(szOrder, "BLS", 3) == 0)
    1975             :             {
    1976         173 :                 poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "BAND",
    1977             :                                                    "IMAGE_STRUCTURE");
    1978             :             }
    1979          23 :             else if (memcmp(szOrder, "LSB", 3) == 0)
    1980             :             {
    1981          18 :                 poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    1982             :                                                    "IMAGE_STRUCTURE");
    1983             :             }
    1984             : 
    1985         196 :             CPLXMLNode *psOS = CPLGetXMLNode(psSubIter, "Object_Statistics");
    1986         196 :             const char *pszMin = nullptr;
    1987         196 :             const char *pszMax = nullptr;
    1988         196 :             const char *pszMean = nullptr;
    1989         196 :             const char *pszStdDev = nullptr;
    1990         196 :             if (psOS)
    1991             :             {
    1992          17 :                 pszMin = CPLGetXMLValue(psOS, "minimum", nullptr);
    1993          17 :                 pszMax = CPLGetXMLValue(psOS, "maximum", nullptr);
    1994          17 :                 pszMean = CPLGetXMLValue(psOS, "mean", nullptr);
    1995          17 :                 pszStdDev = CPLGetXMLValue(psOS, "standard_deviation", nullptr);
    1996             :             }
    1997             : 
    1998         455 :             for (int i = 0; i < l_nBands; i++)
    1999             :             {
    2000         259 :                 vsi_l_offset nThisBandOffset = nOffset + nBandOffset * i;
    2001         259 :                 if (bBottomToTop)
    2002             :                 {
    2003           2 :                     nThisBandOffset +=
    2004           2 :                         static_cast<vsi_l_offset>(nLines - 1) * nLineOffset;
    2005             :                 }
    2006         259 :                 if (bRightToLeft)
    2007             :                 {
    2008           1 :                     nThisBandOffset +=
    2009           1 :                         static_cast<vsi_l_offset>(nSamples - 1) * nPixelOffset;
    2010             :                 }
    2011             :                 auto poBand = std::make_unique<PDS4RawRasterBand>(
    2012         259 :                     poDS.get(), i + 1, poDS->m_fpImage, nThisBandOffset,
    2013         259 :                     bRightToLeft ? -nPixelOffset : nPixelOffset,
    2014         259 :                     bBottomToTop ? -nLineOffset : nLineOffset, eDT,
    2015         259 :                     bLSBOrder ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    2016         259 :                               : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
    2017         259 :                 if (!poBand->IsValid())
    2018           0 :                     return nullptr;
    2019         259 :                 if (bNoDataSet)
    2020             :                 {
    2021          62 :                     poBand->SetNoDataValue(dfNoData);
    2022             :                 }
    2023         259 :                 poBand->SetOffset(dfValueOffset);
    2024         259 :                 poBand->SetScale(dfValueScale);
    2025             : 
    2026         259 :                 if (l_nBands == 1)
    2027             :                 {
    2028         164 :                     if (pszMin)
    2029             :                     {
    2030          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2031             :                             "STATISTICS_MINIMUM", pszMin);
    2032             :                     }
    2033         164 :                     if (pszMax)
    2034             :                     {
    2035          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2036             :                             "STATISTICS_MAXIMUM", pszMax);
    2037             :                     }
    2038         164 :                     if (pszMean)
    2039             :                     {
    2040          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2041             :                             "STATISTICS_MEAN", pszMean);
    2042             :                     }
    2043         164 :                     if (pszStdDev)
    2044             :                     {
    2045          17 :                         poBand->GDALRasterBand::SetMetadataItem(
    2046             :                             "STATISTICS_STDDEV", pszStdDev);
    2047             :                     }
    2048             :                 }
    2049             : 
    2050             :                 // Only instantiate explicit mask band if we have at least one
    2051             :                 // special constant (that is not the missing_constant,
    2052             :                 // already exposed as nodata value)
    2053         506 :                 if (!GDALDataTypeIsComplex(eDT) &&
    2054         486 :                     (CPLTestBool(CPLGetConfigOption("PDS4_FORCE_MASK", "NO")) ||
    2055         447 :                      adfConstants.size() >= 2 ||
    2056         239 :                      (adfConstants.size() == 1 && !bNoDataSet)))
    2057             :                 {
    2058          78 :                     poBand->SetMaskBand(
    2059          39 :                         new PDS4MaskBand(poBand.get(), adfConstants));
    2060             :                 }
    2061             : 
    2062         259 :                 poDS->SetBand(i + 1, std::move(poBand));
    2063             :             }
    2064             :         }
    2065             :     }
    2066             : 
    2067         280 :     if (nFAOIdxLookup < 0 && aosSubdatasets.size() > 2)
    2068             :     {
    2069          10 :         poDS->GDALDataset::SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
    2070             :     }
    2071         270 :     else if (poDS->nBands == 0 &&
    2072         280 :              (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) != 0 &&
    2073          10 :              (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) == 0)
    2074             :     {
    2075           5 :         return nullptr;
    2076             :     }
    2077         265 :     else if (poDS->m_apoLayers.empty() &&
    2078         266 :              (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0 &&
    2079           1 :              (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0)
    2080             :     {
    2081           0 :         return nullptr;
    2082             :     }
    2083             : 
    2084             :     // Expose XML content in xml:PDS4 metadata domain
    2085         275 :     GByte *pabyRet = nullptr;
    2086         275 :     CPL_IGNORE_RET_VAL(VSIIngestFile(nullptr, osXMLFilename, &pabyRet, nullptr,
    2087             :                                      10 * 1024 * 1024));
    2088         275 :     if (pabyRet)
    2089             :     {
    2090         275 :         char *apszXML[2] = {reinterpret_cast<char *>(pabyRet), nullptr};
    2091         275 :         poDS->GDALDataset::SetMetadata(apszXML, "xml:PDS4");
    2092             :     }
    2093         275 :     VSIFree(pabyRet);
    2094             : 
    2095             :     /*--------------------------------------------------------------------------*/
    2096             :     /*  Parse georeferencing info */
    2097             :     /*--------------------------------------------------------------------------*/
    2098         275 :     poDS->ReadGeoreferencing(psProduct);
    2099             : 
    2100             :     /*--------------------------------------------------------------------------*/
    2101             :     /*  Check for overviews */
    2102             :     /*--------------------------------------------------------------------------*/
    2103         275 :     poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
    2104             : 
    2105             :     /*--------------------------------------------------------------------------*/
    2106             :     /*  Initialize any PAM information */
    2107             :     /*--------------------------------------------------------------------------*/
    2108         275 :     poDS->SetDescription(poOpenInfo->pszFilename);
    2109         275 :     poDS->TryLoadXML();
    2110             : 
    2111         275 :     return poDS.release();
    2112             : }
    2113             : 
    2114             : /************************************************************************/
    2115             : /*                         IsCARTVersionGTE()                           */
    2116             : /************************************************************************/
    2117             : 
    2118             : // Returns true is pszCur >= pszRef
    2119             : // Must be things like 1900, 1B00, 1D00_1933 ...
    2120         364 : static bool IsCARTVersionGTE(const char *pszCur, const char *pszRef)
    2121             : {
    2122         364 :     return strcmp(pszCur, pszRef) >= 0;
    2123             : }
    2124             : 
    2125             : /************************************************************************/
    2126             : /*                         WriteGeoreferencing()                        */
    2127             : /************************************************************************/
    2128             : 
    2129          91 : void PDS4Dataset::WriteGeoreferencing(CPLXMLNode *psCart,
    2130             :                                       const char *pszCARTVersion)
    2131             : {
    2132          91 :     bool bHasBoundingBox = false;
    2133          91 :     double adfX[4] = {0};
    2134          91 :     double adfY[4] = {0};
    2135          91 :     CPLString osPrefix;
    2136          91 :     const char *pszColon = strchr(psCart->pszValue, ':');
    2137          91 :     if (pszColon)
    2138          91 :         osPrefix.assign(psCart->pszValue, pszColon - psCart->pszValue + 1);
    2139             : 
    2140          91 :     if (m_bGotTransform)
    2141             :     {
    2142          89 :         bHasBoundingBox = true;
    2143             : 
    2144             :         // upper left
    2145          89 :         adfX[0] = m_adfGeoTransform[0];
    2146          89 :         adfY[0] = m_adfGeoTransform[3];
    2147             : 
    2148             :         // upper right
    2149          89 :         adfX[1] = m_adfGeoTransform[0] + m_adfGeoTransform[1] * nRasterXSize;
    2150          89 :         adfY[1] = m_adfGeoTransform[3];
    2151             : 
    2152             :         // lower left
    2153          89 :         adfX[2] = m_adfGeoTransform[0];
    2154          89 :         adfY[2] = m_adfGeoTransform[3] + m_adfGeoTransform[5] * nRasterYSize;
    2155             : 
    2156             :         // lower right
    2157          89 :         adfX[3] = m_adfGeoTransform[0] + m_adfGeoTransform[1] * nRasterXSize;
    2158          89 :         adfY[3] = m_adfGeoTransform[3] + m_adfGeoTransform[5] * nRasterYSize;
    2159             :     }
    2160             :     else
    2161             :     {
    2162           2 :         OGRLayer *poLayer = GetLayer(0);
    2163           2 :         OGREnvelope sEnvelope;
    2164           2 :         if (poLayer->GetExtent(&sEnvelope) == OGRERR_NONE)
    2165             :         {
    2166           1 :             bHasBoundingBox = true;
    2167             : 
    2168           1 :             adfX[0] = sEnvelope.MinX;
    2169           1 :             adfY[0] = sEnvelope.MaxY;
    2170             : 
    2171           1 :             adfX[1] = sEnvelope.MaxX;
    2172           1 :             adfY[1] = sEnvelope.MaxY;
    2173             : 
    2174           1 :             adfX[2] = sEnvelope.MinX;
    2175           1 :             adfY[2] = sEnvelope.MinY;
    2176             : 
    2177           1 :             adfX[3] = sEnvelope.MaxX;
    2178           1 :             adfY[3] = sEnvelope.MinY;
    2179             :         }
    2180             :     }
    2181             : 
    2182          91 :     if (bHasBoundingBox && !m_oSRS.IsGeographic())
    2183             :     {
    2184          31 :         bHasBoundingBox = false;
    2185          31 :         OGRSpatialReference *poSRSLongLat = m_oSRS.CloneGeogCS();
    2186          31 :         if (poSRSLongLat)
    2187             :         {
    2188          31 :             poSRSLongLat->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    2189             :             OGRCoordinateTransformation *poCT =
    2190          31 :                 OGRCreateCoordinateTransformation(&m_oSRS, poSRSLongLat);
    2191          31 :             if (poCT)
    2192             :             {
    2193          31 :                 if (poCT->Transform(4, adfX, adfY))
    2194             :                 {
    2195          31 :                     bHasBoundingBox = true;
    2196             :                 }
    2197          31 :                 delete poCT;
    2198             :             }
    2199          31 :             delete poSRSLongLat;
    2200             :         }
    2201             :     }
    2202             : 
    2203          91 :     if (!bHasBoundingBox)
    2204             :     {
    2205             :         // Write dummy values
    2206           1 :         adfX[0] = -180.0;
    2207           1 :         adfY[0] = 90.0;
    2208           1 :         adfX[1] = 180.0;
    2209           1 :         adfY[1] = 90.0;
    2210           1 :         adfX[2] = -180.0;
    2211           1 :         adfY[2] = -90.0;
    2212           1 :         adfX[3] = 180.0;
    2213           1 :         adfY[3] = -90.0;
    2214             :     }
    2215             : 
    2216         182 :     const char *pszLongitudeDirection = CSLFetchNameValueDef(
    2217          91 :         m_papszCreationOptions, "LONGITUDE_DIRECTION", "Positive East");
    2218          91 :     const double dfLongitudeMultiplier =
    2219          91 :         EQUAL(pszLongitudeDirection, "Positive West") ? -1 : 1;
    2220         198 :     const auto FixLong = [dfLongitudeMultiplier](double dfLon)
    2221         198 :     { return dfLon * dfLongitudeMultiplier; };
    2222             : 
    2223             :     // Note: starting with CART 1900, Spatial_Domain is actually optional
    2224          91 :     CPLXMLNode *psSD = CPLCreateXMLNode(psCart, CXT_Element,
    2225         182 :                                         (osPrefix + "Spatial_Domain").c_str());
    2226          91 :     CPLXMLNode *psBC = CPLCreateXMLNode(
    2227         182 :         psSD, CXT_Element, (osPrefix + "Bounding_Coordinates").c_str());
    2228             : 
    2229             :     const char *pszBoundingDegrees =
    2230          91 :         CSLFetchNameValue(m_papszCreationOptions, "BOUNDING_DEGREES");
    2231          91 :     double dfWest = FixLong(
    2232          91 :         std::min(std::min(adfX[0], adfX[1]), std::min(adfX[2], adfX[3])));
    2233          91 :     double dfEast = FixLong(
    2234          91 :         std::max(std::max(adfX[0], adfX[1]), std::max(adfX[2], adfX[3])));
    2235             :     double dfNorth =
    2236          91 :         std::max(std::max(adfY[0], adfY[1]), std::max(adfY[2], adfY[3]));
    2237             :     double dfSouth =
    2238          91 :         std::min(std::min(adfY[0], adfY[1]), std::min(adfY[2], adfY[3]));
    2239          91 :     if (pszBoundingDegrees)
    2240             :     {
    2241           1 :         char **papszTokens = CSLTokenizeString2(pszBoundingDegrees, ",", 0);
    2242           1 :         if (CSLCount(papszTokens) == 4)
    2243             :         {
    2244           1 :             dfWest = CPLAtof(papszTokens[0]);
    2245           1 :             dfSouth = CPLAtof(papszTokens[1]);
    2246           1 :             dfEast = CPLAtof(papszTokens[2]);
    2247           1 :             dfNorth = CPLAtof(papszTokens[3]);
    2248             :         }
    2249           1 :         CSLDestroy(papszTokens);
    2250             :     }
    2251             : 
    2252         182 :     CPLAddXMLAttributeAndValue(
    2253             :         CPLCreateXMLElementAndValue(
    2254         182 :             psBC, (osPrefix + "west_bounding_coordinate").c_str(),
    2255             :             CPLSPrintf("%.17g", dfWest)),
    2256             :         "unit", "deg");
    2257         182 :     CPLAddXMLAttributeAndValue(
    2258             :         CPLCreateXMLElementAndValue(
    2259         182 :             psBC, (osPrefix + "east_bounding_coordinate").c_str(),
    2260             :             CPLSPrintf("%.17g", dfEast)),
    2261             :         "unit", "deg");
    2262         182 :     CPLAddXMLAttributeAndValue(
    2263             :         CPLCreateXMLElementAndValue(
    2264         182 :             psBC, (osPrefix + "north_bounding_coordinate").c_str(),
    2265             :             CPLSPrintf("%.17g", dfNorth)),
    2266             :         "unit", "deg");
    2267         182 :     CPLAddXMLAttributeAndValue(
    2268             :         CPLCreateXMLElementAndValue(
    2269         182 :             psBC, (osPrefix + "south_bounding_coordinate").c_str(),
    2270             :             CPLSPrintf("%.17g", dfSouth)),
    2271             :         "unit", "deg");
    2272             : 
    2273             :     CPLXMLNode *psSRI =
    2274          91 :         CPLCreateXMLNode(psCart, CXT_Element,
    2275         182 :                          (osPrefix + "Spatial_Reference_Information").c_str());
    2276          91 :     CPLXMLNode *psHCSD = CPLCreateXMLNode(
    2277             :         psSRI, CXT_Element,
    2278         182 :         (osPrefix + "Horizontal_Coordinate_System_Definition").c_str());
    2279             : 
    2280          91 :     double dfUnrotatedULX = m_adfGeoTransform[0];
    2281          91 :     double dfUnrotatedULY = m_adfGeoTransform[3];
    2282          91 :     double dfUnrotatedResX = m_adfGeoTransform[1];
    2283          91 :     double dfUnrotatedResY = m_adfGeoTransform[5];
    2284          91 :     double dfMapProjectionRotation = 0.0;
    2285          91 :     if (m_adfGeoTransform[1] == 0.0 && m_adfGeoTransform[2] > 0.0 &&
    2286           1 :         m_adfGeoTransform[4] > 0.0 && m_adfGeoTransform[5] == 0.0)
    2287             :     {
    2288           1 :         dfUnrotatedULX = m_adfGeoTransform[3];
    2289           1 :         dfUnrotatedULY = -m_adfGeoTransform[0];
    2290           1 :         dfUnrotatedResX = m_adfGeoTransform[4];
    2291           1 :         dfUnrotatedResY = -m_adfGeoTransform[2];
    2292           1 :         dfMapProjectionRotation = 90.0;
    2293             :     }
    2294             : 
    2295          91 :     if (GetRasterCount() || m_oSRS.IsProjected())
    2296             :     {
    2297          90 :         CPLXMLNode *psPlanar = CPLCreateXMLNode(psHCSD, CXT_Element,
    2298         180 :                                                 (osPrefix + "Planar").c_str());
    2299          90 :         CPLXMLNode *psMP = CPLCreateXMLNode(
    2300         180 :             psPlanar, CXT_Element, (osPrefix + "Map_Projection").c_str());
    2301          90 :         const char *pszProjection = m_oSRS.GetAttrValue("PROJECTION");
    2302         180 :         CPLString pszPDS4ProjectionName = "";
    2303             :         typedef std::pair<const char *, double> ProjParam;
    2304         180 :         std::vector<ProjParam> aoProjParams;
    2305             : 
    2306             :         const bool bUse_CART_1933_Or_Later =
    2307          90 :             IsCARTVersionGTE(pszCARTVersion, "1D00_1933");
    2308             : 
    2309             :         const bool bUse_CART_1950_Or_Later =
    2310          90 :             IsCARTVersionGTE(pszCARTVersion, "1G00_1950");
    2311             : 
    2312          90 :         if (pszProjection == nullptr)
    2313             :         {
    2314          58 :             pszPDS4ProjectionName = "Equirectangular";
    2315          58 :             if (bUse_CART_1933_Or_Later)
    2316             :             {
    2317          56 :                 aoProjParams.push_back(
    2318          56 :                     ProjParam("latitude_of_projection_origin", 0.0));
    2319          56 :                 aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
    2320          56 :                 aoProjParams.push_back(
    2321         112 :                     ProjParam("longitude_of_central_meridian", 0.0));
    2322             :             }
    2323             :             else
    2324             :             {
    2325           2 :                 aoProjParams.push_back(ProjParam("standard_parallel_1", 0.0));
    2326           2 :                 aoProjParams.push_back(
    2327           2 :                     ProjParam("longitude_of_central_meridian", 0.0));
    2328           2 :                 aoProjParams.push_back(
    2329           4 :                     ProjParam("latitude_of_projection_origin", 0.0));
    2330             :             }
    2331             :         }
    2332             : 
    2333          32 :         else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
    2334             :         {
    2335           2 :             pszPDS4ProjectionName = "Equirectangular";
    2336           2 :             if (bUse_CART_1933_Or_Later)
    2337             :             {
    2338           2 :                 aoProjParams.push_back(ProjParam(
    2339           0 :                     "latitude_of_projection_origin",
    2340           2 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2341           2 :                 aoProjParams.push_back(ProjParam(
    2342           0 :                     "standard_parallel_1",
    2343           2 :                     m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
    2344           2 :                 aoProjParams.push_back(
    2345           0 :                     ProjParam("longitude_of_central_meridian",
    2346           4 :                               FixLong(m_oSRS.GetNormProjParm(
    2347             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2348             :             }
    2349             :             else
    2350             :             {
    2351           0 :                 aoProjParams.push_back(ProjParam(
    2352           0 :                     "standard_parallel_1",
    2353           0 :                     m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 1.0)));
    2354           0 :                 aoProjParams.push_back(
    2355           0 :                     ProjParam("longitude_of_central_meridian",
    2356           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2357             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2358           0 :                 aoProjParams.push_back(ProjParam(
    2359           0 :                     "latitude_of_projection_origin",
    2360           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2361             :             }
    2362             :         }
    2363             : 
    2364          30 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_1SP))
    2365             :         {
    2366           1 :             pszPDS4ProjectionName = "Lambert Conformal Conic";
    2367           1 :             if (bUse_CART_1933_Or_Later)
    2368             :             {
    2369           1 :                 aoProjParams.push_back(
    2370           0 :                     ProjParam("longitude_of_central_meridian",
    2371           1 :                               FixLong(m_oSRS.GetNormProjParm(
    2372             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2373           1 :                 aoProjParams.push_back(ProjParam(
    2374           0 :                     "latitude_of_projection_origin",
    2375           1 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2376           1 :                 aoProjParams.push_back(ProjParam(
    2377           0 :                     "scale_factor_at_projection_origin",
    2378           2 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2379             :             }
    2380             :             else
    2381             :             {
    2382           0 :                 aoProjParams.push_back(ProjParam(
    2383           0 :                     "scale_factor_at_projection_origin",
    2384           0 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2385           0 :                 aoProjParams.push_back(
    2386           0 :                     ProjParam("longitude_of_central_meridian",
    2387           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2388             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2389           0 :                 aoProjParams.push_back(ProjParam(
    2390           0 :                     "latitude_of_projection_origin",
    2391           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2392             :             }
    2393             :         }
    2394             : 
    2395          29 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_CONFORMAL_CONIC_2SP))
    2396             :         {
    2397           1 :             pszPDS4ProjectionName = "Lambert Conformal Conic";
    2398           1 :             aoProjParams.push_back(ProjParam(
    2399           0 :                 "standard_parallel_1",
    2400           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
    2401           1 :             aoProjParams.push_back(ProjParam(
    2402           0 :                 "standard_parallel_2",
    2403           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_2, 0.0)));
    2404           1 :             aoProjParams.push_back(ProjParam(
    2405           0 :                 "longitude_of_central_meridian",
    2406           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2407           1 :             aoProjParams.push_back(ProjParam(
    2408           0 :                 "latitude_of_projection_origin",
    2409           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2410             :         }
    2411             : 
    2412          28 :         else if (EQUAL(pszProjection,
    2413             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
    2414             :         {
    2415           1 :             pszPDS4ProjectionName = "Oblique Mercator";
    2416             :             // Proj params defined later
    2417             :         }
    2418             : 
    2419          27 :         else if (EQUAL(pszProjection,
    2420             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
    2421             :         {
    2422           1 :             pszPDS4ProjectionName = "Oblique Mercator";
    2423             :             // Proj params defined later
    2424             :         }
    2425             : 
    2426          26 :         else if (EQUAL(pszProjection, SRS_PT_POLAR_STEREOGRAPHIC))
    2427             :         {
    2428           1 :             pszPDS4ProjectionName = "Polar Stereographic";
    2429           1 :             if (bUse_CART_1950_Or_Later)
    2430             :             {
    2431           1 :                 aoProjParams.push_back(
    2432           0 :                     ProjParam("longitude_of_central_meridian",
    2433           1 :                               FixLong(m_oSRS.GetNormProjParm(
    2434             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2435           1 :                 aoProjParams.push_back(ProjParam(
    2436           0 :                     "latitude_of_projection_origin",
    2437           1 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2438           1 :                 aoProjParams.push_back(ProjParam(
    2439           0 :                     "scale_factor_at_projection_origin",
    2440           2 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2441             :             }
    2442             :             else
    2443             :             {
    2444           0 :                 aoProjParams.push_back(
    2445           0 :                     ProjParam(bUse_CART_1933_Or_Later
    2446           0 :                                   ? "longitude_of_central_meridian"
    2447             :                                   : "straight_vertical_longitude_from_pole",
    2448           0 :                               FixLong(m_oSRS.GetNormProjParm(
    2449             :                                   SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2450           0 :                 aoProjParams.push_back(ProjParam(
    2451           0 :                     "scale_factor_at_projection_origin",
    2452           0 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2453           0 :                 aoProjParams.push_back(ProjParam(
    2454           0 :                     "latitude_of_projection_origin",
    2455           0 :                     m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2456             :             }
    2457             :         }
    2458             : 
    2459          25 :         else if (EQUAL(pszProjection, SRS_PT_POLYCONIC))
    2460             :         {
    2461           1 :             pszPDS4ProjectionName = "Polyconic";
    2462           1 :             aoProjParams.push_back(ProjParam(
    2463           0 :                 "longitude_of_central_meridian",
    2464           1 :                 m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
    2465           1 :             aoProjParams.push_back(ProjParam(
    2466           0 :                 "latitude_of_projection_origin",
    2467           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2468             :         }
    2469          24 :         else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
    2470             :         {
    2471           2 :             pszPDS4ProjectionName = "Sinusoidal";
    2472           2 :             aoProjParams.push_back(ProjParam(
    2473           0 :                 "longitude_of_central_meridian",
    2474           2 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2475           2 :             aoProjParams.push_back(ProjParam(
    2476           0 :                 "latitude_of_projection_origin",
    2477           4 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2478             :         }
    2479             : 
    2480          22 :         else if (EQUAL(pszProjection, SRS_PT_TRANSVERSE_MERCATOR))
    2481             :         {
    2482          16 :             pszPDS4ProjectionName = "Transverse Mercator";
    2483          16 :             aoProjParams.push_back(
    2484           0 :                 ProjParam("scale_factor_at_central_meridian",
    2485          16 :                           m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2486          16 :             aoProjParams.push_back(ProjParam(
    2487           0 :                 "longitude_of_central_meridian",
    2488          16 :                 m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0)));
    2489          16 :             aoProjParams.push_back(ProjParam(
    2490           0 :                 "latitude_of_projection_origin",
    2491          32 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2492             :         }
    2493           6 :         else if (EQUAL(pszProjection, SRS_PT_ORTHOGRAPHIC))
    2494             :         {
    2495           1 :             pszPDS4ProjectionName = "Orthographic";
    2496           1 :             aoProjParams.push_back(ProjParam(
    2497           0 :                 "longitude_of_central_meridian",
    2498           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2499           1 :             aoProjParams.push_back(ProjParam(
    2500           0 :                 "latitude_of_projection_origin",
    2501           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2502             :         }
    2503             : 
    2504           5 :         else if (EQUAL(pszProjection, SRS_PT_MERCATOR_1SP))
    2505             :         {
    2506           2 :             pszPDS4ProjectionName = "Mercator";
    2507           2 :             aoProjParams.push_back(ProjParam(
    2508           0 :                 "longitude_of_central_meridian",
    2509           2 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2510           2 :             aoProjParams.push_back(ProjParam(
    2511           0 :                 "latitude_of_projection_origin",
    2512           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2513           2 :             aoProjParams.push_back(
    2514           0 :                 ProjParam("scale_factor_at_projection_origin",
    2515           4 :                           m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 1.0)));
    2516             :         }
    2517             : 
    2518           3 :         else if (EQUAL(pszProjection, SRS_PT_MERCATOR_2SP))
    2519             :         {
    2520           1 :             pszPDS4ProjectionName = "Mercator";
    2521           1 :             aoProjParams.push_back(ProjParam(
    2522           0 :                 "standard_parallel_1",
    2523           1 :                 m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0)));
    2524           1 :             aoProjParams.push_back(ProjParam(
    2525           0 :                 "longitude_of_central_meridian",
    2526           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2527           1 :             aoProjParams.push_back(ProjParam(
    2528           0 :                 "latitude_of_projection_origin",
    2529           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2530             :         }
    2531             : 
    2532           2 :         else if (EQUAL(pszProjection, SRS_PT_LAMBERT_AZIMUTHAL_EQUAL_AREA))
    2533             :         {
    2534           1 :             pszPDS4ProjectionName = "Lambert Azimuthal Equal Area";
    2535           1 :             aoProjParams.push_back(ProjParam(
    2536           0 :                 "longitude_of_central_meridian",
    2537           1 :                 FixLong(m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0))));
    2538           1 :             aoProjParams.push_back(ProjParam(
    2539           0 :                 "latitude_of_projection_origin",
    2540           2 :                 m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0)));
    2541             :         }
    2542             : 
    2543           1 :         else if (EQUAL(pszProjection, "custom_proj4"))
    2544             :         {
    2545             :             const char *pszProj4 =
    2546           1 :                 m_oSRS.GetExtension("PROJCS", "PROJ4", nullptr);
    2547           1 :             if (pszProj4 && strstr(pszProj4, "+proj=ob_tran") &&
    2548           1 :                 strstr(pszProj4, "+o_proj=eqc"))
    2549             :             {
    2550           1 :                 pszPDS4ProjectionName = "Oblique Cylindrical";
    2551             :                 const auto FetchParam =
    2552           3 :                     [](const char *pszProj4Str, const char *pszKey)
    2553             :                 {
    2554           6 :                     CPLString needle;
    2555           3 :                     needle.Printf("+%s=", pszKey);
    2556           3 :                     const char *pszVal = strstr(pszProj4Str, needle.c_str());
    2557           3 :                     if (pszVal)
    2558           3 :                         return CPLAtof(pszVal + needle.size());
    2559           0 :                     return 0.0;
    2560             :                 };
    2561             : 
    2562           1 :                 double dfLonP = FetchParam(pszProj4, "o_lon_p");
    2563           1 :                 double dfLatP = FetchParam(pszProj4, "o_lat_p");
    2564           1 :                 double dfLon0 = FetchParam(pszProj4, "lon_0");
    2565           1 :                 double dfPoleRotation = -dfLonP;
    2566           1 :                 double dfPoleLatitude = 180 - dfLatP;
    2567           1 :                 double dfPoleLongitude = dfLon0;
    2568             : 
    2569           1 :                 aoProjParams.push_back(ProjParam("map_projection_rotation",
    2570             :                                                  dfMapProjectionRotation));
    2571           1 :                 aoProjParams.push_back(
    2572           1 :                     ProjParam("oblique_proj_pole_latitude", dfPoleLatitude));
    2573           1 :                 aoProjParams.push_back(ProjParam("oblique_proj_pole_longitude",
    2574           1 :                                                  FixLong(dfPoleLongitude)));
    2575           1 :                 aoProjParams.push_back(
    2576           2 :                     ProjParam("oblique_proj_pole_rotation", dfPoleRotation));
    2577             :             }
    2578             :             else
    2579             :             {
    2580           0 :                 CPLError(CE_Warning, CPLE_NotSupported,
    2581             :                          "Projection %s not supported", pszProjection);
    2582             :             }
    2583             :         }
    2584             :         else
    2585             :         {
    2586           0 :             CPLError(CE_Warning, CPLE_NotSupported,
    2587             :                      "Projection %s not supported", pszProjection);
    2588             :         }
    2589         180 :         CPLCreateXMLElementAndValue(psMP,
    2590         180 :                                     (osPrefix + "map_projection_name").c_str(),
    2591             :                                     pszPDS4ProjectionName);
    2592          90 :         CPLXMLNode *psProj = CPLCreateXMLNode(
    2593             :             psMP, CXT_Element,
    2594         180 :             CPLString(osPrefix + pszPDS4ProjectionName).replaceAll(' ', '_'));
    2595         351 :         for (size_t i = 0; i < aoProjParams.size(); i++)
    2596             :         {
    2597         522 :             CPLXMLNode *psParam = CPLCreateXMLElementAndValue(
    2598         522 :                 psProj, (osPrefix + aoProjParams[i].first).c_str(),
    2599         261 :                 CPLSPrintf("%.17g", aoProjParams[i].second));
    2600         261 :             if (!STARTS_WITH(aoProjParams[i].first, "scale_factor"))
    2601             :             {
    2602         241 :                 CPLAddXMLAttributeAndValue(psParam, "unit", "deg");
    2603             :             }
    2604             :         }
    2605             : 
    2606          90 :         if (pszProjection &&
    2607          32 :             EQUAL(pszProjection, SRS_PT_HOTINE_OBLIQUE_MERCATOR_AZIMUTH_CENTER))
    2608             :         {
    2609             :             CPLXMLNode *psOLA =
    2610           1 :                 CPLCreateXMLNode(nullptr, CXT_Element,
    2611           2 :                                  (osPrefix + "Oblique_Line_Azimuth").c_str());
    2612           2 :             CPLAddXMLAttributeAndValue(
    2613             :                 CPLCreateXMLElementAndValue(
    2614           2 :                     psOLA, (osPrefix + "azimuthal_angle").c_str(),
    2615             :                     CPLSPrintf("%.17g",
    2616             :                                m_oSRS.GetNormProjParm(SRS_PP_AZIMUTH, 0.0))),
    2617             :                 "unit", "deg");
    2618             :             ;
    2619             :             // Not completely sure of this
    2620           2 :             CPLAddXMLAttributeAndValue(
    2621             :                 CPLCreateXMLElementAndValue(
    2622             :                     psOLA,
    2623           2 :                     (osPrefix + "azimuth_measure_point_longitude").c_str(),
    2624             :                     CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
    2625             :                                             SRS_PP_CENTRAL_MERIDIAN, 0.0)))),
    2626             :                 "unit", "deg");
    2627             : 
    2628           1 :             if (bUse_CART_1933_Or_Later)
    2629             :             {
    2630           1 :                 CPLAddXMLChild(psProj, psOLA);
    2631             : 
    2632           1 :                 CPLAddXMLAttributeAndValue(
    2633             :                     CPLCreateXMLElementAndValue(
    2634             :                         psProj,
    2635           2 :                         (osPrefix + "longitude_of_central_meridian").c_str(),
    2636             :                         "0"),
    2637             :                     "unit", "deg");
    2638             : 
    2639             :                 const double dfScaleFactor =
    2640           1 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
    2641           1 :                 if (dfScaleFactor != 1.0)
    2642             :                 {
    2643           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    2644             :                              "Scale factor on initial support = %.17g cannot "
    2645             :                              "be encoded in PDS4",
    2646             :                              dfScaleFactor);
    2647             :                 }
    2648             :             }
    2649             :             else
    2650             :             {
    2651           0 :                 CPLCreateXMLElementAndValue(
    2652             :                     psProj,
    2653           0 :                     (osPrefix + "scale_factor_at_projection_origin").c_str(),
    2654             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2655             :                                             SRS_PP_SCALE_FACTOR, 0.0)));
    2656             : 
    2657           0 :                 CPLAddXMLChild(psProj, psOLA);
    2658             :             }
    2659             : 
    2660           2 :             CPLAddXMLAttributeAndValue(
    2661             :                 CPLCreateXMLElementAndValue(
    2662             :                     psProj,
    2663           2 :                     (osPrefix + "latitude_of_projection_origin").c_str(),
    2664             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2665             :                                             SRS_PP_LATITUDE_OF_ORIGIN, 0.0))),
    2666           1 :                 "unit", "deg");
    2667             :         }
    2668          89 :         else if (pszProjection &&
    2669          31 :                  EQUAL(pszProjection,
    2670             :                        SRS_PT_HOTINE_OBLIQUE_MERCATOR_TWO_POINT_NATURAL_ORIGIN))
    2671             :         {
    2672           1 :             if (bUse_CART_1933_Or_Later)
    2673             :             {
    2674             :                 const double dfScaleFactor =
    2675           1 :                     m_oSRS.GetNormProjParm(SRS_PP_SCALE_FACTOR, 0.0);
    2676           1 :                 if (dfScaleFactor != 1.0)
    2677             :                 {
    2678           0 :                     CPLError(CE_Warning, CPLE_NotSupported,
    2679             :                              "Scale factor on initial support = %.17g cannot "
    2680             :                              "be encoded in PDS4",
    2681             :                              dfScaleFactor);
    2682             :                 }
    2683             :             }
    2684             :             else
    2685             :             {
    2686           0 :                 CPLCreateXMLElementAndValue(
    2687             :                     psProj,
    2688           0 :                     (osPrefix + "scale_factor_at_projection_origin").c_str(),
    2689             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2690             :                                             SRS_PP_SCALE_FACTOR, 0.0)));
    2691             :             }
    2692             : 
    2693           1 :             CPLXMLNode *psOLP = CPLCreateXMLNode(
    2694           2 :                 psProj, CXT_Element, (osPrefix + "Oblique_Line_Point").c_str());
    2695           1 :             CPLXMLNode *psOLPG1 = CPLCreateXMLNode(
    2696             :                 psOLP, CXT_Element,
    2697           2 :                 (osPrefix + "Oblique_Line_Point_Group").c_str());
    2698           2 :             CPLAddXMLAttributeAndValue(
    2699             :                 CPLCreateXMLElementAndValue(
    2700           2 :                     psOLPG1, (osPrefix + "oblique_line_latitude").c_str(),
    2701             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2702             :                                             SRS_PP_LATITUDE_OF_POINT_1, 0.0))),
    2703             :                 "unit", "deg");
    2704           2 :             CPLAddXMLAttributeAndValue(
    2705             :                 CPLCreateXMLElementAndValue(
    2706           2 :                     psOLPG1, (osPrefix + "oblique_line_longitude").c_str(),
    2707             :                     CPLSPrintf("%.17g",
    2708             :                                FixLong(m_oSRS.GetNormProjParm(
    2709             :                                    SRS_PP_LONGITUDE_OF_POINT_1, 0.0)))),
    2710             :                 "unit", "deg");
    2711           1 :             CPLXMLNode *psOLPG2 = CPLCreateXMLNode(
    2712             :                 psOLP, CXT_Element,
    2713           2 :                 (osPrefix + "Oblique_Line_Point_Group").c_str());
    2714           2 :             CPLAddXMLAttributeAndValue(
    2715             :                 CPLCreateXMLElementAndValue(
    2716           2 :                     psOLPG2, (osPrefix + "oblique_line_latitude").c_str(),
    2717             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2718             :                                             SRS_PP_LATITUDE_OF_POINT_2, 0.0))),
    2719             :                 "unit", "deg");
    2720           2 :             CPLAddXMLAttributeAndValue(
    2721             :                 CPLCreateXMLElementAndValue(
    2722           2 :                     psOLPG2, (osPrefix + "oblique_line_longitude").c_str(),
    2723             :                     CPLSPrintf("%.17g", m_oSRS.GetNormProjParm(
    2724             :                                             SRS_PP_LONGITUDE_OF_POINT_2, 0.0))),
    2725             :                 "unit", "deg");
    2726             : 
    2727           1 :             if (bUse_CART_1933_Or_Later)
    2728             :             {
    2729           1 :                 CPLAddXMLAttributeAndValue(
    2730             :                     CPLCreateXMLElementAndValue(
    2731             :                         psProj,
    2732           2 :                         (osPrefix + "longitude_of_central_meridian").c_str(),
    2733             :                         "0"),
    2734             :                     "unit", "deg");
    2735             :             }
    2736             : 
    2737           2 :             CPLAddXMLAttributeAndValue(
    2738             :                 CPLCreateXMLElementAndValue(
    2739             :                     psProj,
    2740           2 :                     (osPrefix + "latitude_of_projection_origin").c_str(),
    2741             :                     CPLSPrintf("%.17g", FixLong(m_oSRS.GetNormProjParm(
    2742             :                                             SRS_PP_LATITUDE_OF_ORIGIN, 0.0)))),
    2743             :                 "unit", "deg");
    2744             :         }
    2745             : 
    2746          90 :         CPLXMLNode *psCR = nullptr;
    2747          90 :         if (m_bGotTransform || !IsCARTVersionGTE(pszCARTVersion, "1B00"))
    2748             :         {
    2749          89 :             CPLXMLNode *psPCI = CPLCreateXMLNode(
    2750             :                 psPlanar, CXT_Element,
    2751         178 :                 (osPrefix + "Planar_Coordinate_Information").c_str());
    2752          89 :             CPLCreateXMLElementAndValue(
    2753         178 :                 psPCI, (osPrefix + "planar_coordinate_encoding_method").c_str(),
    2754             :                 "Coordinate Pair");
    2755          89 :             psCR = CPLCreateXMLNode(
    2756             :                 psPCI, CXT_Element,
    2757         178 :                 (osPrefix + "Coordinate_Representation").c_str());
    2758             :         }
    2759          90 :         const double dfLinearUnits = m_oSRS.GetLinearUnits();
    2760          90 :         const double dfDegToMeter = m_oSRS.GetSemiMajor() * M_PI / 180.0;
    2761             : 
    2762          90 :         if (psCR == nullptr)
    2763             :         {
    2764             :             // do nothing
    2765             :         }
    2766          89 :         else if (!m_bGotTransform)
    2767             :         {
    2768           0 :             CPLAddXMLAttributeAndValue(
    2769             :                 CPLCreateXMLElementAndValue(
    2770           0 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(), "0"),
    2771             :                 "unit", "m/pixel");
    2772           0 :             CPLAddXMLAttributeAndValue(
    2773             :                 CPLCreateXMLElementAndValue(
    2774           0 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(), "0"),
    2775             :                 "unit", "m/pixel");
    2776           0 :             CPLAddXMLAttributeAndValue(
    2777             :                 CPLCreateXMLElementAndValue(
    2778           0 :                     psCR, (osPrefix + "pixel_scale_x").c_str(), "0"),
    2779             :                 "unit", "pixel/deg");
    2780           0 :             CPLAddXMLAttributeAndValue(
    2781             :                 CPLCreateXMLElementAndValue(
    2782           0 :                     psCR, (osPrefix + "pixel_scale_y").c_str(), "0"),
    2783             :                 "unit", "pixel/deg");
    2784             :         }
    2785          89 :         else if (m_oSRS.IsGeographic())
    2786             :         {
    2787         116 :             CPLAddXMLAttributeAndValue(
    2788             :                 CPLCreateXMLElementAndValue(
    2789         116 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(),
    2790             :                     CPLSPrintf("%.17g", dfUnrotatedResX * dfDegToMeter)),
    2791             :                 "unit", "m/pixel");
    2792          58 :             CPLAddXMLAttributeAndValue(
    2793             :                 CPLCreateXMLElementAndValue(
    2794         116 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(),
    2795          58 :                     CPLSPrintf("%.17g", -dfUnrotatedResY * dfDegToMeter)),
    2796             :                 "unit", "m/pixel");
    2797         116 :             CPLAddXMLAttributeAndValue(
    2798             :                 CPLCreateXMLElementAndValue(
    2799         116 :                     psCR, (osPrefix + "pixel_scale_x").c_str(),
    2800             :                     CPLSPrintf("%.17g", 1.0 / (dfUnrotatedResX))),
    2801             :                 "unit", "pixel/deg");
    2802         116 :             CPLAddXMLAttributeAndValue(
    2803             :                 CPLCreateXMLElementAndValue(
    2804         116 :                     psCR, (osPrefix + "pixel_scale_y").c_str(),
    2805             :                     CPLSPrintf("%.17g", 1.0 / (-dfUnrotatedResY))),
    2806             :                 "unit", "pixel/deg");
    2807             :         }
    2808          31 :         else if (m_oSRS.IsProjected())
    2809             :         {
    2810          62 :             CPLAddXMLAttributeAndValue(
    2811             :                 CPLCreateXMLElementAndValue(
    2812          62 :                     psCR, (osPrefix + "pixel_resolution_x").c_str(),
    2813             :                     CPLSPrintf("%.17g", dfUnrotatedResX * dfLinearUnits)),
    2814             :                 "unit", "m/pixel");
    2815          31 :             CPLAddXMLAttributeAndValue(
    2816             :                 CPLCreateXMLElementAndValue(
    2817          62 :                     psCR, (osPrefix + "pixel_resolution_y").c_str(),
    2818          31 :                     CPLSPrintf("%.17g", -dfUnrotatedResY * dfLinearUnits)),
    2819             :                 "unit", "m/pixel");
    2820          31 :             CPLAddXMLAttributeAndValue(
    2821             :                 CPLCreateXMLElementAndValue(
    2822          62 :                     psCR, (osPrefix + "pixel_scale_x").c_str(),
    2823             :                     CPLSPrintf("%.17g", dfDegToMeter /
    2824          31 :                                             (dfUnrotatedResX * dfLinearUnits))),
    2825             :                 "unit", "pixel/deg");
    2826          31 :             CPLAddXMLAttributeAndValue(
    2827             :                 CPLCreateXMLElementAndValue(
    2828          62 :                     psCR, (osPrefix + "pixel_scale_y").c_str(),
    2829          31 :                     CPLSPrintf("%.17g", dfDegToMeter / (-dfUnrotatedResY *
    2830             :                                                         dfLinearUnits))),
    2831             :                 "unit", "pixel/deg");
    2832             :         }
    2833             : 
    2834          90 :         if (m_bGotTransform)
    2835             :         {
    2836             :             CPLXMLNode *psGT =
    2837          89 :                 CPLCreateXMLNode(psPlanar, CXT_Element,
    2838         178 :                                  (osPrefix + "Geo_Transformation").c_str());
    2839             :             const double dfFalseEasting =
    2840          89 :                 m_oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0);
    2841             :             const double dfFalseNorthing =
    2842          89 :                 m_oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0);
    2843          89 :             const double dfULX = -dfFalseEasting + dfUnrotatedULX;
    2844          89 :             const double dfULY = -dfFalseNorthing + dfUnrotatedULY;
    2845          89 :             if (m_oSRS.IsGeographic())
    2846             :             {
    2847         116 :                 CPLAddXMLAttributeAndValue(
    2848             :                     CPLCreateXMLElementAndValue(
    2849         116 :                         psGT, (osPrefix + "upperleft_corner_x").c_str(),
    2850             :                         CPLSPrintf("%.17g", dfULX * dfDegToMeter)),
    2851             :                     "unit", "m");
    2852         116 :                 CPLAddXMLAttributeAndValue(
    2853             :                     CPLCreateXMLElementAndValue(
    2854         116 :                         psGT, (osPrefix + "upperleft_corner_y").c_str(),
    2855             :                         CPLSPrintf("%.17g", dfULY * dfDegToMeter)),
    2856             :                     "unit", "m");
    2857             :             }
    2858          31 :             else if (m_oSRS.IsProjected())
    2859             :             {
    2860          62 :                 CPLAddXMLAttributeAndValue(
    2861             :                     CPLCreateXMLElementAndValue(
    2862          62 :                         psGT, (osPrefix + "upperleft_corner_x").c_str(),
    2863             :                         CPLSPrintf("%.17g", dfULX * dfLinearUnits)),
    2864             :                     "unit", "m");
    2865          62 :                 CPLAddXMLAttributeAndValue(
    2866             :                     CPLCreateXMLElementAndValue(
    2867          62 :                         psGT, (osPrefix + "upperleft_corner_y").c_str(),
    2868             :                         CPLSPrintf("%.17g", dfULY * dfLinearUnits)),
    2869             :                     "unit", "m");
    2870             :             }
    2871             :         }
    2872             :     }
    2873             :     else
    2874             :     {
    2875           1 :         CPLXMLNode *psGeographic = CPLCreateXMLNode(
    2876           2 :             psHCSD, CXT_Element, (osPrefix + "Geographic").c_str());
    2877           1 :         if (!IsCARTVersionGTE(pszCARTVersion, "1B00"))
    2878             :         {
    2879           0 :             CPLAddXMLAttributeAndValue(
    2880             :                 CPLCreateXMLElementAndValue(
    2881           0 :                     psGeographic, (osPrefix + "latitude_resolution").c_str(),
    2882             :                     "0"),
    2883             :                 "unit", "deg");
    2884           0 :             CPLAddXMLAttributeAndValue(
    2885             :                 CPLCreateXMLElementAndValue(
    2886           0 :                     psGeographic, (osPrefix + "longitude_resolution").c_str(),
    2887             :                     "0"),
    2888             :                 "unit", "deg");
    2889             :         }
    2890             :     }
    2891             : 
    2892          91 :     CPLXMLNode *psGM = CPLCreateXMLNode(psHCSD, CXT_Element,
    2893         182 :                                         (osPrefix + "Geodetic_Model").c_str());
    2894         182 :     const char *pszLatitudeType = CSLFetchNameValueDef(
    2895          91 :         m_papszCreationOptions, "LATITUDE_TYPE", "Planetocentric");
    2896             :     // Fix case
    2897          91 :     if (EQUAL(pszLatitudeType, "Planetocentric"))
    2898          90 :         pszLatitudeType = "Planetocentric";
    2899           1 :     else if (EQUAL(pszLatitudeType, "Planetographic"))
    2900           1 :         pszLatitudeType = "Planetographic";
    2901          91 :     CPLCreateXMLElementAndValue(psGM, (osPrefix + "latitude_type").c_str(),
    2902             :                                 pszLatitudeType);
    2903             : 
    2904          91 :     const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
    2905          91 :     if (pszDatum && STARTS_WITH(pszDatum, "D_"))
    2906             :     {
    2907           4 :         CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
    2908             :                                     pszDatum + 2);
    2909             :     }
    2910          87 :     else if (pszDatum)
    2911             :     {
    2912          87 :         CPLCreateXMLElementAndValue(psGM, (osPrefix + "spheroid_name").c_str(),
    2913             :                                     pszDatum);
    2914             :     }
    2915             : 
    2916          91 :     double dfSemiMajor = m_oSRS.GetSemiMajor();
    2917          91 :     double dfSemiMinor = m_oSRS.GetSemiMinor();
    2918          91 :     const char *pszRadii = CSLFetchNameValue(m_papszCreationOptions, "RADII");
    2919          91 :     if (pszRadii)
    2920             :     {
    2921           1 :         char **papszTokens = CSLTokenizeString2(pszRadii, " ,", 0);
    2922           1 :         if (CSLCount(papszTokens) == 2)
    2923             :         {
    2924           1 :             dfSemiMajor = CPLAtof(papszTokens[0]);
    2925           1 :             dfSemiMinor = CPLAtof(papszTokens[1]);
    2926             :         }
    2927           1 :         CSLDestroy(papszTokens);
    2928             :     }
    2929             : 
    2930             :     const bool bUseLDD1930RadiusNames =
    2931          91 :         IsCARTVersionGTE(pszCARTVersion, "1B10_1930");
    2932             : 
    2933         182 :     CPLAddXMLAttributeAndValue(
    2934             :         CPLCreateXMLElementAndValue(
    2935             :             psGM,
    2936         182 :             (osPrefix +
    2937             :              (bUseLDD1930RadiusNames ? "a_axis_radius" : "semi_major_radius"))
    2938             :                 .c_str(),
    2939             :             CPLSPrintf("%.17g", dfSemiMajor)),
    2940             :         "unit", "m");
    2941             :     // No, this is not a bug. The PDS4  b_axis_radius/semi_minor_radius is the
    2942             :     // minor radius on the equatorial plane. Which in WKT doesn't really exist,
    2943             :     // so reuse the WKT semi major
    2944         182 :     CPLAddXMLAttributeAndValue(
    2945             :         CPLCreateXMLElementAndValue(
    2946             :             psGM,
    2947         182 :             (osPrefix +
    2948             :              (bUseLDD1930RadiusNames ? "b_axis_radius" : "semi_minor_radius"))
    2949             :                 .c_str(),
    2950             :             CPLSPrintf("%.17g", dfSemiMajor)),
    2951             :         "unit", "m");
    2952         182 :     CPLAddXMLAttributeAndValue(
    2953             :         CPLCreateXMLElementAndValue(
    2954             :             psGM,
    2955         182 :             (osPrefix +
    2956             :              (bUseLDD1930RadiusNames ? "c_axis_radius" : "polar_radius"))
    2957             :                 .c_str(),
    2958             :             CPLSPrintf("%.17g", dfSemiMinor)),
    2959             :         "unit", "m");
    2960             : 
    2961             :     // Fix case
    2962          91 :     if (EQUAL(pszLongitudeDirection, "Positive East"))
    2963          90 :         pszLongitudeDirection = "Positive East";
    2964           1 :     else if (EQUAL(pszLongitudeDirection, "Positive West"))
    2965           1 :         pszLongitudeDirection = "Positive West";
    2966          91 :     CPLCreateXMLElementAndValue(psGM,
    2967         182 :                                 (osPrefix + "longitude_direction").c_str(),
    2968             :                                 pszLongitudeDirection);
    2969          91 : }
    2970             : 
    2971             : /************************************************************************/
    2972             : /*                         SubstituteVariables()                        */
    2973             : /************************************************************************/
    2974             : 
    2975       15164 : void PDS4Dataset::SubstituteVariables(CPLXMLNode *psNode, char **papszDict)
    2976             : {
    2977       15164 :     if (psNode->eType == CXT_Text && psNode->pszValue &&
    2978        6141 :         strstr(psNode->pszValue, "${"))
    2979             :     {
    2980        1246 :         CPLString osVal(psNode->pszValue);
    2981             : 
    2982        1403 :         if (strstr(psNode->pszValue, "${TITLE}") != nullptr &&
    2983         157 :             CSLFetchNameValue(papszDict, "VAR_TITLE") == nullptr)
    2984             :         {
    2985         143 :             const CPLString osTitle(CPLGetFilename(GetDescription()));
    2986         143 :             CPLError(CE_Warning, CPLE_AppDefined,
    2987             :                      "VAR_TITLE not defined. Using %s by default",
    2988             :                      osTitle.c_str());
    2989         143 :             osVal.replaceAll("${TITLE}", osTitle);
    2990             :         }
    2991             : 
    2992        4223 :         for (char **papszIter = papszDict; papszIter && *papszIter; papszIter++)
    2993             :         {
    2994        2977 :             if (STARTS_WITH_CI(*papszIter, "VAR_"))
    2995             :             {
    2996        2634 :                 char *pszKey = nullptr;
    2997        2634 :                 const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    2998        2634 :                 if (pszKey && pszValue)
    2999             :                 {
    3000        2634 :                     const char *pszVarName = pszKey + strlen("VAR_");
    3001             :                     osVal.replaceAll(
    3002        2634 :                         (CPLString("${") + pszVarName + "}").c_str(), pszValue);
    3003             :                     osVal.replaceAll(
    3004        5268 :                         CPLString(CPLString("${") + pszVarName + "}")
    3005        2634 :                             .tolower()
    3006             :                             .c_str(),
    3007        7902 :                         CPLString(pszValue).tolower());
    3008        2634 :                     CPLFree(pszKey);
    3009             :                 }
    3010             :             }
    3011             :         }
    3012        1246 :         if (osVal.find("${") != std::string::npos)
    3013             :         {
    3014         680 :             CPLError(CE_Warning, CPLE_AppDefined, "%s could not be substituted",
    3015             :                      osVal.c_str());
    3016             :         }
    3017        1246 :         CPLFree(psNode->pszValue);
    3018        1246 :         psNode->pszValue = CPLStrdup(osVal);
    3019             :     }
    3020             : 
    3021       30164 :     for (CPLXMLNode *psIter = psNode->psChild; psIter; psIter = psIter->psNext)
    3022             :     {
    3023       15000 :         SubstituteVariables(psIter, papszDict);
    3024             :     }
    3025       15164 : }
    3026             : 
    3027             : /************************************************************************/
    3028             : /*                         InitImageFile()                             */
    3029             : /************************************************************************/
    3030             : 
    3031          77 : bool PDS4Dataset::InitImageFile()
    3032             : {
    3033          77 :     m_bMustInitImageFile = false;
    3034             : 
    3035          77 :     if (m_poExternalDS)
    3036             :     {
    3037             :         int nBlockXSize, nBlockYSize;
    3038          11 :         GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
    3039          11 :         const GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
    3040          11 :         const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
    3041          11 :         const int nBlockSizeBytes = nBlockXSize * nBlockYSize * nDTSize;
    3042          11 :         const int l_nBlocksPerColumn = DIV_ROUND_UP(nRasterYSize, nBlockYSize);
    3043             : 
    3044          11 :         int bHasNoData = FALSE;
    3045          11 :         double dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    3046          11 :         if (!bHasNoData)
    3047           7 :             dfNoData = 0;
    3048             : 
    3049          11 :         if (nBands == 1 || EQUAL(m_osInterleave, "BSQ"))
    3050             :         {
    3051             :             // We need to make sure that blocks are written in the right order
    3052          24 :             for (int i = 0; i < nBands; i++)
    3053             :             {
    3054          14 :                 if (m_poExternalDS->GetRasterBand(i + 1)->Fill(dfNoData) !=
    3055             :                     CE_None)
    3056             :                 {
    3057           0 :                     return false;
    3058             :                 }
    3059             :             }
    3060          10 :             m_poExternalDS->FlushCache(false);
    3061             : 
    3062             :             // Check that blocks are effectively written in expected order.
    3063          10 :             GIntBig nLastOffset = 0;
    3064          24 :             for (int i = 0; i < nBands; i++)
    3065             :             {
    3066         319 :                 for (int y = 0; y < l_nBlocksPerColumn; y++)
    3067             :                 {
    3068             :                     const char *pszBlockOffset =
    3069         610 :                         m_poExternalDS->GetRasterBand(i + 1)->GetMetadataItem(
    3070         305 :                             CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
    3071         305 :                     if (pszBlockOffset)
    3072             :                     {
    3073         305 :                         GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
    3074         305 :                         if (i != 0 || y != 0)
    3075             :                         {
    3076         295 :                             if (nOffset != nLastOffset + nBlockSizeBytes)
    3077             :                             {
    3078           0 :                                 CPLError(CE_Warning, CPLE_AppDefined,
    3079             :                                          "Block %d,%d band %d not at expected "
    3080             :                                          "offset",
    3081             :                                          0, y, i + 1);
    3082           0 :                                 return false;
    3083             :                             }
    3084             :                         }
    3085         305 :                         nLastOffset = nOffset;
    3086             :                     }
    3087             :                     else
    3088             :                     {
    3089           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    3090             :                                  "Block %d,%d band %d not at expected "
    3091             :                                  "offset",
    3092             :                                  0, y, i + 1);
    3093           0 :                         return false;
    3094             :                     }
    3095             :                 }
    3096             :             }
    3097             :         }
    3098             :         else
    3099             :         {
    3100           1 :             void *pBlockData = VSI_MALLOC_VERBOSE(nBlockSizeBytes);
    3101           1 :             if (pBlockData == nullptr)
    3102           0 :                 return false;
    3103           1 :             GDALCopyWords(&dfNoData, GDT_Float64, 0, pBlockData, eDT, nDTSize,
    3104             :                           nBlockXSize * nBlockYSize);
    3105           2 :             for (int y = 0; y < l_nBlocksPerColumn; y++)
    3106             :             {
    3107           4 :                 for (int i = 0; i < nBands; i++)
    3108             :                 {
    3109           3 :                     if (m_poExternalDS->GetRasterBand(i + 1)->WriteBlock(
    3110           3 :                             0, y, pBlockData) != CE_None)
    3111             :                     {
    3112           0 :                         VSIFree(pBlockData);
    3113           0 :                         return false;
    3114             :                     }
    3115             :                 }
    3116             :             }
    3117           1 :             VSIFree(pBlockData);
    3118           1 :             m_poExternalDS->FlushCache(false);
    3119             : 
    3120             :             // Check that blocks are effectively written in expected order.
    3121           1 :             GIntBig nLastOffset = 0;
    3122           2 :             for (int y = 0; y < l_nBlocksPerColumn; y++)
    3123             :             {
    3124             :                 const char *pszBlockOffset =
    3125           2 :                     m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    3126           1 :                         CPLSPrintf("BLOCK_OFFSET_%d_%d", 0, y), "TIFF");
    3127           1 :                 if (pszBlockOffset)
    3128             :                 {
    3129           1 :                     GIntBig nOffset = CPLAtoGIntBig(pszBlockOffset);
    3130           1 :                     if (y != 0)
    3131             :                     {
    3132           0 :                         if (nOffset !=
    3133           0 :                             nLastOffset +
    3134           0 :                                 static_cast<GIntBig>(nBlockSizeBytes) * nBands)
    3135             :                         {
    3136           0 :                             CPLError(CE_Warning, CPLE_AppDefined,
    3137             :                                      "Block %d,%d not at expected "
    3138             :                                      "offset",
    3139             :                                      0, y);
    3140           0 :                             return false;
    3141             :                         }
    3142             :                     }
    3143           1 :                     nLastOffset = nOffset;
    3144             :                 }
    3145             :                 else
    3146             :                 {
    3147           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
    3148             :                              "Block %d,%d not at expected "
    3149             :                              "offset",
    3150             :                              0, y);
    3151           0 :                     return false;
    3152             :                 }
    3153             :             }
    3154             :         }
    3155             : 
    3156          11 :         return true;
    3157             :     }
    3158             : 
    3159          66 :     int bHasNoData = FALSE;
    3160          66 :     const double dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    3161          66 :     const GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
    3162          66 :     const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
    3163          66 :     const vsi_l_offset nFileSize = static_cast<vsi_l_offset>(nRasterXSize) *
    3164          66 :                                    nRasterYSize * nBands * nDTSize;
    3165          66 :     if (dfNoData == 0 || !bHasNoData)
    3166             :     {
    3167          62 :         if (VSIFTruncateL(m_fpImage, nFileSize) != 0)
    3168             :         {
    3169           0 :             CPLError(CE_Failure, CPLE_FileIO,
    3170             :                      "Cannot create file of size " CPL_FRMT_GUIB " bytes",
    3171             :                      nFileSize);
    3172           0 :             return false;
    3173             :         }
    3174             :     }
    3175             :     else
    3176             :     {
    3177           4 :         size_t nLineSize = static_cast<size_t>(nRasterXSize) * nDTSize;
    3178           4 :         void *pData = VSI_MALLOC_VERBOSE(nLineSize);
    3179           4 :         if (pData == nullptr)
    3180           0 :             return false;
    3181           4 :         GDALCopyWords(&dfNoData, GDT_Float64, 0, pData, eDT, nDTSize,
    3182             :                       nRasterXSize);
    3183             : #ifdef CPL_MSB
    3184             :         if (GDALDataTypeIsComplex(eDT))
    3185             :         {
    3186             :             GDALSwapWords(pData, nDTSize / 2, nRasterXSize * 2, nDTSize / 2);
    3187             :         }
    3188             :         else
    3189             :         {
    3190             :             GDALSwapWords(pData, nDTSize, nRasterXSize, nDTSize);
    3191             :         }
    3192             : #endif
    3193           8 :         for (vsi_l_offset i = 0;
    3194           8 :              i < static_cast<vsi_l_offset>(nRasterYSize) * nBands; i++)
    3195             :         {
    3196           4 :             size_t nBytesWritten = VSIFWriteL(pData, 1, nLineSize, m_fpImage);
    3197           4 :             if (nBytesWritten != nLineSize)
    3198             :             {
    3199           0 :                 CPLError(CE_Failure, CPLE_FileIO,
    3200             :                          "Cannot create file of size " CPL_FRMT_GUIB " bytes",
    3201             :                          nFileSize);
    3202           0 :                 VSIFree(pData);
    3203           0 :                 return false;
    3204             :             }
    3205             :         }
    3206           4 :         VSIFree(pData);
    3207             :     }
    3208          66 :     return true;
    3209             : }
    3210             : 
    3211             : /************************************************************************/
    3212             : /*                          GetSpecialConstants()                       */
    3213             : /************************************************************************/
    3214             : 
    3215          12 : static CPLXMLNode *GetSpecialConstants(const CPLString &osPrefix,
    3216             :                                        CPLXMLNode *psFileAreaObservational)
    3217             : {
    3218          27 :     for (CPLXMLNode *psIter = psFileAreaObservational->psChild; psIter;
    3219          15 :          psIter = psIter->psNext)
    3220             :     {
    3221          48 :         if (psIter->eType == CXT_Element &&
    3222          48 :             STARTS_WITH(psIter->pszValue, (osPrefix + "Array").c_str()))
    3223             :         {
    3224             :             CPLXMLNode *psSC =
    3225          11 :                 CPLGetXMLNode(psIter, (osPrefix + "Special_Constants").c_str());
    3226          11 :             if (psSC)
    3227             :             {
    3228           9 :                 CPLXMLNode *psNext = psSC->psNext;
    3229           9 :                 psSC->psNext = nullptr;
    3230           9 :                 CPLXMLNode *psRet = CPLCloneXMLTree(psSC);
    3231           9 :                 psSC->psNext = psNext;
    3232           9 :                 return psRet;
    3233             :             }
    3234             :         }
    3235             :     }
    3236           3 :     return nullptr;
    3237             : }
    3238             : 
    3239             : /************************************************************************/
    3240             : /*                          WriteHeaderAppendCase()                     */
    3241             : /************************************************************************/
    3242             : 
    3243           4 : void PDS4Dataset::WriteHeaderAppendCase()
    3244             : {
    3245           4 :     CPLXMLTreeCloser oCloser(CPLParseXMLFile(GetDescription()));
    3246           4 :     CPLXMLNode *psRoot = oCloser.get();
    3247           4 :     if (psRoot == nullptr)
    3248           0 :         return;
    3249           4 :     CPLString osPrefix;
    3250           4 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    3251           4 :     if (psProduct == nullptr)
    3252             :     {
    3253           0 :         psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
    3254           0 :         if (psProduct)
    3255           0 :             osPrefix = "pds:";
    3256             :     }
    3257           4 :     if (psProduct == nullptr)
    3258             :     {
    3259           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3260             :                  "Cannot find Product_Observational element");
    3261           0 :         return;
    3262             :     }
    3263           4 :     CPLXMLNode *psFAO = CPLGetXMLNode(
    3264           8 :         psProduct, (osPrefix + "File_Area_Observational").c_str());
    3265           4 :     if (psFAO == nullptr)
    3266             :     {
    3267           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    3268             :                  "Cannot find File_Area_Observational element");
    3269           0 :         return;
    3270             :     }
    3271             : 
    3272           4 :     WriteArray(osPrefix, psFAO, nullptr, nullptr);
    3273             : 
    3274           4 :     CPLSerializeXMLTreeToFile(psRoot, GetDescription());
    3275             : }
    3276             : 
    3277             : /************************************************************************/
    3278             : /*                              WriteArray()                            */
    3279             : /************************************************************************/
    3280             : 
    3281         118 : void PDS4Dataset::WriteArray(const CPLString &osPrefix, CPLXMLNode *psFAO,
    3282             :                              const char *pszLocalIdentifierDefault,
    3283             :                              CPLXMLNode *psTemplateSpecialConstants)
    3284             : {
    3285         236 :     const char *pszArrayType = CSLFetchNameValueDef(
    3286         118 :         m_papszCreationOptions, "ARRAY_TYPE", "Array_3D_Image");
    3287         118 :     const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
    3288             :     CPLXMLNode *psArray =
    3289         118 :         CPLCreateXMLNode(psFAO, CXT_Element, (osPrefix + pszArrayType).c_str());
    3290             : 
    3291         236 :     const char *pszLocalIdentifier = CSLFetchNameValueDef(
    3292         118 :         m_papszCreationOptions, "ARRAY_IDENTIFIER", pszLocalIdentifierDefault);
    3293         118 :     if (pszLocalIdentifier)
    3294             :     {
    3295         114 :         CPLCreateXMLElementAndValue(psArray,
    3296         228 :                                     (osPrefix + "local_identifier").c_str(),
    3297             :                                     pszLocalIdentifier);
    3298             :     }
    3299             : 
    3300         118 :     GUIntBig nOffset = m_nBaseOffset;
    3301         118 :     if (m_poExternalDS)
    3302             :     {
    3303             :         const char *pszOffset =
    3304          11 :             m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    3305          11 :                 "BLOCK_OFFSET_0_0", "TIFF");
    3306          11 :         if (pszOffset)
    3307          11 :             nOffset = CPLAtoGIntBig(pszOffset);
    3308             :     }
    3309         236 :     CPLAddXMLAttributeAndValue(
    3310         236 :         CPLCreateXMLElementAndValue(psArray, (osPrefix + "offset").c_str(),
    3311             :                                     CPLSPrintf(CPL_FRMT_GUIB, nOffset)),
    3312             :         "unit", "byte");
    3313         118 :     CPLCreateXMLElementAndValue(psArray, (osPrefix + "axes").c_str(),
    3314             :                                 (bIsArray2D) ? "2" : "3");
    3315         118 :     CPLCreateXMLElementAndValue(
    3316         236 :         psArray, (osPrefix + "axis_index_order").c_str(), "Last Index Fastest");
    3317         118 :     CPLXMLNode *psElementArray = CPLCreateXMLNode(
    3318         236 :         psArray, CXT_Element, (osPrefix + "Element_Array").c_str());
    3319         118 :     GDALDataType eDT = GetRasterBand(1)->GetRasterDataType();
    3320         118 :     const char *pszDataType =
    3321         156 :         (eDT == GDT_Byte)     ? "UnsignedByte"
    3322          73 :         : (eDT == GDT_Int8)   ? "SignedByte"
    3323          65 :         : (eDT == GDT_UInt16) ? "UnsignedLSB2"
    3324          55 :         : (eDT == GDT_Int16)  ? (m_bIsLSB ? "SignedLSB2" : "SignedMSB2")
    3325          46 :         : (eDT == GDT_UInt32) ? (m_bIsLSB ? "UnsignedLSB4" : "UnsignedMSB4")
    3326          38 :         : (eDT == GDT_Int32)  ? (m_bIsLSB ? "SignedLSB4" : "SignedMSB4")
    3327             :         : (eDT == GDT_Float32)
    3328          29 :             ? (m_bIsLSB ? "IEEE754LSBSingle" : "IEEE754MSBSingle")
    3329             :         : (eDT == GDT_Float64)
    3330          20 :             ? (m_bIsLSB ? "IEEE754LSBDouble" : "IEEE754MSBDouble")
    3331          12 :         : (eDT == GDT_CFloat32) ? (m_bIsLSB ? "ComplexLSB8" : "ComplexMSB8")
    3332           4 :         : (eDT == GDT_CFloat64) ? (m_bIsLSB ? "ComplexLSB16" : "ComplexMSB16")
    3333             :                                 : "should not happen";
    3334         118 :     CPLCreateXMLElementAndValue(psElementArray,
    3335         236 :                                 (osPrefix + "data_type").c_str(), pszDataType);
    3336             : 
    3337         118 :     const char *pszUnits = GetRasterBand(1)->GetUnitType();
    3338         118 :     const char *pszUnitsCO = CSLFetchNameValue(m_papszCreationOptions, "UNIT");
    3339         118 :     if (pszUnitsCO)
    3340             :     {
    3341           0 :         pszUnits = pszUnitsCO;
    3342             :     }
    3343         118 :     if (pszUnits && pszUnits[0] != 0)
    3344             :     {
    3345           1 :         CPLCreateXMLElementAndValue(psElementArray, (osPrefix + "unit").c_str(),
    3346             :                                     pszUnits);
    3347             :     }
    3348             : 
    3349         118 :     int bHasScale = FALSE;
    3350         118 :     double dfScale = GetRasterBand(1)->GetScale(&bHasScale);
    3351         118 :     if (bHasScale && dfScale != 1.0)
    3352             :     {
    3353          10 :         CPLCreateXMLElementAndValue(psElementArray,
    3354          10 :                                     (osPrefix + "scaling_factor").c_str(),
    3355             :                                     CPLSPrintf("%.17g", dfScale));
    3356             :     }
    3357             : 
    3358         118 :     int bHasOffset = FALSE;
    3359         118 :     double dfOffset = GetRasterBand(1)->GetOffset(&bHasOffset);
    3360         118 :     if (bHasOffset && dfOffset != 1.0)
    3361             :     {
    3362          10 :         CPLCreateXMLElementAndValue(psElementArray,
    3363          10 :                                     (osPrefix + "value_offset").c_str(),
    3364             :                                     CPLSPrintf("%.17g", dfOffset));
    3365             :     }
    3366             : 
    3367             :     // Axis definitions
    3368             :     {
    3369         118 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3370         236 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3371         236 :         CPLCreateXMLElementAndValue(
    3372         236 :             psAxis, (osPrefix + "axis_name").c_str(),
    3373         118 :             EQUAL(m_osInterleave, "BSQ")
    3374             :                 ? "Band"
    3375             :                 :
    3376             :                 /* EQUAL(m_osInterleave, "BIL") ? "Line" : */
    3377             :                 "Line");
    3378         236 :         CPLCreateXMLElementAndValue(
    3379         236 :             psAxis, (osPrefix + "elements").c_str(),
    3380             :             CPLSPrintf("%d",
    3381         118 :                        EQUAL(m_osInterleave, "BSQ")
    3382             :                            ? nBands
    3383             :                            :
    3384             :                            /* EQUAL(m_osInterleave, "BIL") ? nRasterYSize : */
    3385             :                            nRasterYSize));
    3386         118 :         CPLCreateXMLElementAndValue(
    3387         236 :             psAxis, (osPrefix + "sequence_number").c_str(), "1");
    3388             :     }
    3389             :     {
    3390         118 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3391         236 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3392         118 :         CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
    3393         118 :                                     EQUAL(m_osInterleave, "BSQ")   ? "Line"
    3394          11 :                                     : EQUAL(m_osInterleave, "BIL") ? "Band"
    3395             :                                                                    : "Sample");
    3396         236 :         CPLCreateXMLElementAndValue(
    3397         236 :             psAxis, (osPrefix + "elements").c_str(),
    3398         118 :             CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterYSize
    3399          11 :                              : EQUAL(m_osInterleave, "BIL") ? nBands
    3400             :                                                             : nRasterXSize));
    3401         118 :         CPLCreateXMLElementAndValue(
    3402         236 :             psAxis, (osPrefix + "sequence_number").c_str(), "2");
    3403             :     }
    3404         118 :     if (!bIsArray2D)
    3405             :     {
    3406         117 :         CPLXMLNode *psAxis = CPLCreateXMLNode(
    3407         234 :             psArray, CXT_Element, (osPrefix + "Axis_Array").c_str());
    3408         117 :         CPLCreateXMLElementAndValue(psAxis, (osPrefix + "axis_name").c_str(),
    3409         117 :                                     EQUAL(m_osInterleave, "BSQ")   ? "Sample"
    3410          10 :                                     : EQUAL(m_osInterleave, "BIL") ? "Sample"
    3411             :                                                                    : "Band");
    3412         234 :         CPLCreateXMLElementAndValue(
    3413         234 :             psAxis, (osPrefix + "elements").c_str(),
    3414         117 :             CPLSPrintf("%d", EQUAL(m_osInterleave, "BSQ")   ? nRasterXSize
    3415          10 :                              : EQUAL(m_osInterleave, "BIL") ? nRasterXSize
    3416             :                                                             : nBands));
    3417         117 :         CPLCreateXMLElementAndValue(
    3418         234 :             psAxis, (osPrefix + "sequence_number").c_str(), "3");
    3419             :     }
    3420             : 
    3421         118 :     int bHasNoData = FALSE;
    3422         118 :     double dfNoData = GetRasterBand(1)->GetNoDataValue(&bHasNoData);
    3423         118 :     if (psTemplateSpecialConstants)
    3424             :     {
    3425           9 :         CPLAddXMLChild(psArray, psTemplateSpecialConstants);
    3426           9 :         if (bHasNoData)
    3427             :         {
    3428             :             CPLXMLNode *psMC =
    3429           6 :                 CPLGetXMLNode(psTemplateSpecialConstants,
    3430          12 :                               (osPrefix + "missing_constant").c_str());
    3431           6 :             if (psMC != nullptr)
    3432             :             {
    3433           4 :                 if (psMC->psChild && psMC->psChild->eType == CXT_Text)
    3434             :                 {
    3435           4 :                     CPLFree(psMC->psChild->pszValue);
    3436           8 :                     psMC->psChild->pszValue =
    3437           4 :                         CPLStrdup(CPLSPrintf("%.17g", dfNoData));
    3438             :                 }
    3439             :             }
    3440             :             else
    3441             :             {
    3442             :                 CPLXMLNode *psSaturatedConstant =
    3443           2 :                     CPLGetXMLNode(psTemplateSpecialConstants,
    3444           4 :                                   (osPrefix + "saturated_constant").c_str());
    3445           4 :                 psMC = CPLCreateXMLElementAndValue(
    3446           4 :                     nullptr, (osPrefix + "missing_constant").c_str(),
    3447             :                     CPLSPrintf("%.17g", dfNoData));
    3448             :                 CPLXMLNode *psNext;
    3449           2 :                 if (psSaturatedConstant)
    3450             :                 {
    3451           1 :                     psNext = psSaturatedConstant->psNext;
    3452           1 :                     psSaturatedConstant->psNext = psMC;
    3453             :                 }
    3454             :                 else
    3455             :                 {
    3456           1 :                     psNext = psTemplateSpecialConstants->psChild;
    3457           1 :                     psTemplateSpecialConstants->psChild = psMC;
    3458             :                 }
    3459           2 :                 psMC->psNext = psNext;
    3460             :             }
    3461             :         }
    3462             :     }
    3463         109 :     else if (bHasNoData)
    3464             :     {
    3465          11 :         CPLXMLNode *psSC = CPLCreateXMLNode(
    3466          22 :             psArray, CXT_Element, (osPrefix + "Special_Constants").c_str());
    3467          22 :         CPLCreateXMLElementAndValue(psSC,
    3468          22 :                                     (osPrefix + "missing_constant").c_str(),
    3469             :                                     CPLSPrintf("%.17g", dfNoData));
    3470             :     }
    3471         118 : }
    3472             : 
    3473             : /************************************************************************/
    3474             : /*                          WriteVectorLayers()                         */
    3475             : /************************************************************************/
    3476             : 
    3477         171 : void PDS4Dataset::WriteVectorLayers(CPLXMLNode *psProduct)
    3478             : {
    3479         342 :     CPLString osPrefix;
    3480         171 :     if (STARTS_WITH(psProduct->pszValue, "pds:"))
    3481           0 :         osPrefix = "pds:";
    3482             : 
    3483         243 :     for (auto &poLayer : m_apoLayers)
    3484             :     {
    3485          72 :         if (!poLayer->IsDirtyHeader())
    3486           3 :             continue;
    3487             : 
    3488          69 :         if (poLayer->GetFeatureCount(false) == 0)
    3489             :         {
    3490          16 :             CPLError(CE_Warning, CPLE_AppDefined,
    3491             :                      "Writing header for layer %s which has 0 features. "
    3492             :                      "This is not legal in PDS4",
    3493          16 :                      poLayer->GetName());
    3494             :         }
    3495             : 
    3496          69 :         if (poLayer->GetRawFieldCount() == 0)
    3497             :         {
    3498          16 :             CPLError(CE_Warning, CPLE_AppDefined,
    3499             :                      "Writing header for layer %s which has 0 fields. "
    3500             :                      "This is not legal in PDS4",
    3501          16 :                      poLayer->GetName());
    3502             :         }
    3503             : 
    3504             :         const std::string osRelativePath(
    3505         138 :             CPLExtractRelativePath(CPLGetPathSafe(m_osXMLFilename).c_str(),
    3506         207 :                                    poLayer->GetFileName(), nullptr));
    3507             : 
    3508          69 :         bool bFound = false;
    3509         634 :         for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;
    3510         565 :              psIter = psIter->psNext)
    3511             :         {
    3512         733 :             if (psIter->eType == CXT_Element &&
    3513         164 :                 strcmp(psIter->pszValue,
    3514         733 :                        (osPrefix + "File_Area_Observational").c_str()) == 0)
    3515             :             {
    3516          23 :                 const char *pszFilename = CPLGetXMLValue(
    3517             :                     psIter,
    3518          46 :                     (osPrefix + "File." + osPrefix + "file_name").c_str(), "");
    3519          23 :                 if (strcmp(pszFilename, osRelativePath.c_str()) == 0)
    3520             :                 {
    3521           4 :                     poLayer->RefreshFileAreaObservational(psIter);
    3522           4 :                     bFound = true;
    3523           4 :                     break;
    3524             :                 }
    3525             :             }
    3526             :         }
    3527          69 :         if (!bFound)
    3528             :         {
    3529          65 :             CPLXMLNode *psFAO = CPLCreateXMLNode(
    3530             :                 psProduct, CXT_Element,
    3531         130 :                 (osPrefix + "File_Area_Observational").c_str());
    3532          65 :             CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
    3533         130 :                                                   (osPrefix + "File").c_str());
    3534         130 :             CPLCreateXMLElementAndValue(psFile,
    3535         130 :                                         (osPrefix + "file_name").c_str(),
    3536             :                                         osRelativePath.c_str());
    3537          65 :             poLayer->RefreshFileAreaObservational(psFAO);
    3538             :         }
    3539             :     }
    3540         171 : }
    3541             : 
    3542             : /************************************************************************/
    3543             : /*                            CreateHeader()                            */
    3544             : /************************************************************************/
    3545             : 
    3546         164 : void PDS4Dataset::CreateHeader(CPLXMLNode *psProduct,
    3547             :                                const char *pszCARTVersion)
    3548             : {
    3549         164 :     CPLString osPrefix;
    3550         164 :     if (STARTS_WITH(psProduct->pszValue, "pds:"))
    3551           0 :         osPrefix = "pds:";
    3552             : 
    3553         164 :     OGREnvelope sExtent;
    3554         210 :     if (m_oSRS.IsEmpty() && GetLayerCount() >= 1 &&
    3555          46 :         GetLayer(0)->GetSpatialRef() != nullptr)
    3556             :     {
    3557           2 :         const auto poSRS = GetLayer(0)->GetSpatialRef();
    3558           2 :         if (poSRS)
    3559           2 :             m_oSRS = *poSRS;
    3560             :     }
    3561             : 
    3562         256 :     if (!m_oSRS.IsEmpty() &&
    3563          92 :         CSLFetchNameValue(m_papszCreationOptions, "VAR_TARGET") == nullptr)
    3564             :     {
    3565          91 :         const char *pszTarget = nullptr;
    3566          91 :         if (fabs(m_oSRS.GetSemiMajor() - 6378137) < 0.001 * 6378137)
    3567             :         {
    3568          70 :             pszTarget = "Earth";
    3569          70 :             m_papszCreationOptions = CSLSetNameValue(
    3570             :                 m_papszCreationOptions, "VAR_TARGET_TYPE", "Planet");
    3571             :         }
    3572             :         else
    3573             :         {
    3574          21 :             const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
    3575          21 :             if (pszDatum && STARTS_WITH(pszDatum, "D_"))
    3576             :             {
    3577           3 :                 pszTarget = pszDatum + 2;
    3578             :             }
    3579          18 :             else if (pszDatum)
    3580             :             {
    3581          18 :                 pszTarget = pszDatum;
    3582             :             }
    3583             :         }
    3584          91 :         if (pszTarget)
    3585             :         {
    3586          91 :             m_papszCreationOptions = CSLSetNameValue(m_papszCreationOptions,
    3587             :                                                      "VAR_TARGET", pszTarget);
    3588             :         }
    3589             :     }
    3590         164 :     SubstituteVariables(psProduct, m_papszCreationOptions);
    3591             : 
    3592             :     // Remove <Discipline_Area>/<disp:Display_Settings> if there is no raster
    3593         164 :     if (GetRasterCount() == 0)
    3594             :     {
    3595             :         CPLXMLNode *psDisciplineArea =
    3596         141 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    3597          94 :                                       osPrefix + "Discipline_Area")
    3598             :                                          .c_str());
    3599          47 :         if (psDisciplineArea)
    3600             :         {
    3601             :             CPLXMLNode *psDisplaySettings =
    3602          47 :                 CPLGetXMLNode(psDisciplineArea, "disp:Display_Settings");
    3603          47 :             if (psDisplaySettings)
    3604             :             {
    3605          47 :                 CPLRemoveXMLChild(psDisciplineArea, psDisplaySettings);
    3606          47 :                 CPLDestroyXMLNode(psDisplaySettings);
    3607             :             }
    3608             :         }
    3609             :     }
    3610             : 
    3611             :     // Depending on the version of the DISP schema, Local_Internal_Reference
    3612             :     // may be in the disp: namespace or the default one.
    3613             :     const auto GetLocalIdentifierReferenceFromDisciplineArea =
    3614         200 :         [](const CPLXMLNode *psDisciplineArea, const char *pszDefault)
    3615             :     {
    3616         200 :         return CPLGetXMLValue(
    3617             :             psDisciplineArea,
    3618             :             "disp:Display_Settings.Local_Internal_Reference."
    3619             :             "local_identifier_reference",
    3620             :             CPLGetXMLValue(
    3621             :                 psDisciplineArea,
    3622             :                 "disp:Display_Settings.disp:Local_Internal_Reference."
    3623             :                 "local_identifier_reference",
    3624         200 :                 pszDefault));
    3625             :     };
    3626             : 
    3627         164 :     if (GetRasterCount() || !m_oSRS.IsEmpty())
    3628             :     {
    3629             :         CPLXMLNode *psDisciplineArea =
    3630         357 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    3631         238 :                                       osPrefix + "Discipline_Area")
    3632             :                                          .c_str());
    3633         119 :         if (GetRasterCount() && !(m_bGotTransform && !m_oSRS.IsEmpty()))
    3634             :         {
    3635             :             // if we have no georeferencing, strip any existing georeferencing
    3636             :             // from the template
    3637          27 :             if (psDisciplineArea)
    3638             :             {
    3639             :                 CPLXMLNode *psCart =
    3640          23 :                     CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
    3641          23 :                 if (psCart == nullptr)
    3642          22 :                     psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
    3643          23 :                 if (psCart)
    3644             :                 {
    3645           1 :                     CPLRemoveXMLChild(psDisciplineArea, psCart);
    3646           1 :                     CPLDestroyXMLNode(psCart);
    3647             :                 }
    3648             : 
    3649          23 :                 if (CPLGetXMLNode(psDisciplineArea,
    3650          23 :                                   "sp:Spectral_Characteristics"))
    3651             :                 {
    3652             :                     const char *pszArrayType =
    3653           1 :                         CSLFetchNameValue(m_papszCreationOptions, "ARRAY_TYPE");
    3654             :                     // The schematron PDS4_SP_1100.sch requires that
    3655             :                     // sp:local_identifier_reference is used by
    3656             :                     // Array_[2D|3D]_Spectrum/pds:local_identifier
    3657           1 :                     if (pszArrayType == nullptr)
    3658             :                     {
    3659           1 :                         m_papszCreationOptions =
    3660           1 :                             CSLSetNameValue(m_papszCreationOptions,
    3661             :                                             "ARRAY_TYPE", "Array_3D_Spectrum");
    3662             :                     }
    3663           0 :                     else if (!EQUAL(pszArrayType, "Array_2D_Spectrum") &&
    3664           0 :                              !EQUAL(pszArrayType, "Array_3D_Spectrum"))
    3665             :                     {
    3666           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    3667             :                                  "PDS4_SP_xxxx.sch schematron requires the "
    3668             :                                  "use of ARRAY_TYPE=Array_2D_Spectrum or "
    3669             :                                  "Array_3D_Spectrum");
    3670             :                     }
    3671             :                 }
    3672             :             }
    3673             :         }
    3674             :         else
    3675             :         {
    3676          92 :             if (psDisciplineArea == nullptr)
    3677             :             {
    3678           2 :                 CPLXMLNode *psTI = CPLGetXMLNode(
    3679           4 :                     psProduct, (osPrefix + "Observation_Area." + osPrefix +
    3680             :                                 "Target_Identification")
    3681             :                                    .c_str());
    3682           2 :                 if (psTI == nullptr)
    3683             :                 {
    3684           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    3685             :                              "Cannot find Target_Identification element in "
    3686             :                              "template");
    3687           1 :                     return;
    3688             :                 }
    3689             :                 psDisciplineArea =
    3690           1 :                     CPLCreateXMLNode(nullptr, CXT_Element,
    3691           2 :                                      (osPrefix + "Discipline_Area").c_str());
    3692           1 :                 if (psTI->psNext)
    3693           0 :                     psDisciplineArea->psNext = psTI->psNext;
    3694           1 :                 psTI->psNext = psDisciplineArea;
    3695             :             }
    3696             :             CPLXMLNode *psCart =
    3697          91 :                 CPLGetXMLNode(psDisciplineArea, "cart:Cartography");
    3698          91 :             if (psCart == nullptr)
    3699          86 :                 psCart = CPLGetXMLNode(psDisciplineArea, "Cartography");
    3700          91 :             if (psCart == nullptr)
    3701             :             {
    3702          86 :                 psCart = CPLCreateXMLNode(psDisciplineArea, CXT_Element,
    3703             :                                           "cart:Cartography");
    3704          86 :                 if (CPLGetXMLNode(psProduct, "xmlns:cart") == nullptr)
    3705             :                 {
    3706             :                     CPLXMLNode *psNS =
    3707           1 :                         CPLCreateXMLNode(nullptr, CXT_Attribute, "xmlns:cart");
    3708           1 :                     CPLCreateXMLNode(psNS, CXT_Text,
    3709             :                                      "http://pds.nasa.gov/pds4/cart/v1");
    3710           1 :                     CPLAddXMLChild(psProduct, psNS);
    3711             :                     CPLXMLNode *psSchemaLoc =
    3712           1 :                         CPLGetXMLNode(psProduct, "xsi:schemaLocation");
    3713           1 :                     if (psSchemaLoc != nullptr &&
    3714           1 :                         psSchemaLoc->psChild != nullptr &&
    3715           1 :                         psSchemaLoc->psChild->pszValue != nullptr)
    3716             :                     {
    3717           2 :                         CPLString osCartSchema;
    3718           1 :                         if (strstr(psSchemaLoc->psChild->pszValue,
    3719             :                                    "PDS4_PDS_1800.xsd"))
    3720             :                         {
    3721             :                             // GDAL 2.4
    3722             :                             osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
    3723           1 :                                            "PDS4_CART_1700.xsd";
    3724           1 :                             pszCARTVersion = "1700";
    3725             :                         }
    3726           0 :                         else if (strstr(psSchemaLoc->psChild->pszValue,
    3727             :                                         "PDS4_PDS_1B00.xsd"))
    3728             :                         {
    3729             :                             // GDAL 3.0
    3730             :                             osCartSchema =
    3731             :                                 "https://raw.githubusercontent.com/"
    3732             :                                 "nasa-pds-data-dictionaries/ldd-cart/master/"
    3733           0 :                                 "build/1.B.0.0/PDS4_CART_1B00.xsd";
    3734           0 :                             pszCARTVersion = "1B00";
    3735             :                         }
    3736           0 :                         else if (strstr(psSchemaLoc->psChild->pszValue,
    3737             :                                         "PDS4_PDS_1D00.xsd"))
    3738             :                         {
    3739             :                             // GDAL 3.1
    3740             :                             osCartSchema = "https://pds.nasa.gov/pds4/cart/v1/"
    3741           0 :                                            "PDS4_CART_1D00_1933.xsd";
    3742           0 :                             pszCARTVersion = "1D00_1933";
    3743             :                         }
    3744             :                         else
    3745             :                         {
    3746             :                             // GDAL 3.4
    3747             :                             osCartSchema =
    3748             :                                 "https://pds.nasa.gov/pds4/cart/v1/"
    3749           0 :                                 "PDS4_CART_" CURRENT_CART_VERSION ".xsd";
    3750           0 :                             pszCARTVersion = CURRENT_CART_VERSION;
    3751             :                         }
    3752           1 :                         CPLString osNewVal(psSchemaLoc->psChild->pszValue);
    3753             :                         osNewVal +=
    3754           1 :                             " http://pds.nasa.gov/pds4/cart/v1 " + osCartSchema;
    3755           1 :                         CPLFree(psSchemaLoc->psChild->pszValue);
    3756           1 :                         psSchemaLoc->psChild->pszValue = CPLStrdup(osNewVal);
    3757             :                     }
    3758             :                 }
    3759             :             }
    3760             :             else
    3761             :             {
    3762           5 :                 if (psCart->psChild)
    3763             :                 {
    3764           5 :                     CPLDestroyXMLNode(psCart->psChild);
    3765           5 :                     psCart->psChild = nullptr;
    3766             :                 }
    3767             :             }
    3768             : 
    3769          91 :             if (IsCARTVersionGTE(pszCARTVersion, "1900"))
    3770             :             {
    3771             :                 const char *pszLocalIdentifier =
    3772          86 :                     GetLocalIdentifierReferenceFromDisciplineArea(
    3773             :                         psDisciplineArea,
    3774          86 :                         GetRasterCount() == 0 && GetLayerCount() > 0
    3775           2 :                             ? GetLayer(0)->GetName()
    3776             :                             : "image");
    3777          86 :                 CPLXMLNode *psLIR = CPLCreateXMLNode(
    3778             :                     psCart, CXT_Element,
    3779         172 :                     (osPrefix + "Local_Internal_Reference").c_str());
    3780          86 :                 CPLCreateXMLElementAndValue(
    3781         172 :                     psLIR, (osPrefix + "local_identifier_reference").c_str(),
    3782             :                     pszLocalIdentifier);
    3783          86 :                 CPLCreateXMLElementAndValue(
    3784         172 :                     psLIR, (osPrefix + "local_reference_type").c_str(),
    3785             :                     "cartography_parameters_to_image_object");
    3786             :             }
    3787             : 
    3788          91 :             WriteGeoreferencing(psCart, pszCARTVersion);
    3789             :         }
    3790             : 
    3791         236 :         const char *pszVertDir = CSLFetchNameValue(
    3792         118 :             m_papszCreationOptions, "VAR_VERTICAL_DISPLAY_DIRECTION");
    3793         118 :         if (pszVertDir)
    3794             :         {
    3795             :             CPLXMLNode *psVertDirNode =
    3796           1 :                 CPLGetXMLNode(psDisciplineArea,
    3797             :                               "disp:Display_Settings.disp:Display_Direction."
    3798             :                               "disp:vertical_display_direction");
    3799           1 :             if (psVertDirNode == nullptr)
    3800             :             {
    3801           0 :                 CPLError(
    3802             :                     CE_Warning, CPLE_AppDefined,
    3803             :                     "PDS4 template lacks a disp:vertical_display_direction "
    3804             :                     "element where to write %s",
    3805             :                     pszVertDir);
    3806             :             }
    3807             :             else
    3808             :             {
    3809           1 :                 CPLDestroyXMLNode(psVertDirNode->psChild);
    3810           1 :                 psVertDirNode->psChild =
    3811           1 :                     CPLCreateXMLNode(nullptr, CXT_Text, pszVertDir);
    3812             :             }
    3813             :         }
    3814             :     }
    3815             :     else
    3816             :     {
    3817             :         // Remove Observation_Area.Discipline_Area if it contains only
    3818             :         // <disp:Display_Settings> or is empty
    3819             :         CPLXMLNode *psObservationArea =
    3820          45 :             CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area").c_str());
    3821          45 :         if (psObservationArea)
    3822             :         {
    3823          45 :             CPLXMLNode *psDisciplineArea = CPLGetXMLNode(
    3824          90 :                 psObservationArea, (osPrefix + "Discipline_Area").c_str());
    3825          45 :             if (psDisciplineArea &&
    3826          45 :                 (psDisciplineArea->psChild == nullptr ||
    3827           0 :                  (psDisciplineArea->psChild->eType == CXT_Element &&
    3828           0 :                   psDisciplineArea->psChild->psNext == nullptr &&
    3829           0 :                   strcmp(psDisciplineArea->psChild->pszValue,
    3830             :                          "disp:Display_Settings") == 0)))
    3831             :             {
    3832          45 :                 CPLRemoveXMLChild(psObservationArea, psDisciplineArea);
    3833          45 :                 CPLDestroyXMLNode(psDisciplineArea);
    3834             :             }
    3835             :         }
    3836             :     }
    3837             : 
    3838         163 :     if (m_bStripFileAreaObservationalFromTemplate)
    3839             :     {
    3840         163 :         m_bStripFileAreaObservationalFromTemplate = false;
    3841         163 :         CPLXMLNode *psObservationArea = nullptr;
    3842         163 :         CPLXMLNode *psPrev = nullptr;
    3843         163 :         CPLXMLNode *psTemplateSpecialConstants = nullptr;
    3844        1465 :         for (CPLXMLNode *psIter = psProduct->psChild; psIter != nullptr;)
    3845             :         {
    3846        1639 :             if (psIter->eType == CXT_Element &&
    3847        1639 :                 psIter->pszValue == osPrefix + "Observation_Area")
    3848             :             {
    3849         162 :                 psObservationArea = psIter;
    3850         162 :                 psPrev = psIter;
    3851         162 :                 psIter = psIter->psNext;
    3852             :             }
    3853        1478 :             else if (psIter->eType == CXT_Element &&
    3854         175 :                      (psIter->pszValue ==
    3855        1478 :                           osPrefix + "File_Area_Observational" ||
    3856         163 :                       psIter->pszValue ==
    3857        1303 :                           osPrefix + "File_Area_Observational_Supplemental"))
    3858             :             {
    3859          12 :                 if (psIter->pszValue == osPrefix + "File_Area_Observational")
    3860             :                 {
    3861             :                     psTemplateSpecialConstants =
    3862          12 :                         GetSpecialConstants(osPrefix, psIter);
    3863             :                 }
    3864          12 :                 if (psPrev)
    3865          12 :                     psPrev->psNext = psIter->psNext;
    3866             :                 else
    3867             :                 {
    3868           0 :                     CPLAssert(psProduct->psChild == psIter);
    3869           0 :                     psProduct->psChild = psIter->psNext;
    3870             :                 }
    3871          12 :                 CPLXMLNode *psNext = psIter->psNext;
    3872          12 :                 psIter->psNext = nullptr;
    3873          12 :                 CPLDestroyXMLNode(psIter);
    3874          12 :                 psIter = psNext;
    3875             :             }
    3876             :             else
    3877             :             {
    3878        1128 :                 psPrev = psIter;
    3879        1128 :                 psIter = psIter->psNext;
    3880             :             }
    3881             :         }
    3882         163 :         if (psObservationArea == nullptr)
    3883             :         {
    3884           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    3885             :                      "Cannot find Observation_Area in template");
    3886           1 :             CPLDestroyXMLNode(psTemplateSpecialConstants);
    3887           1 :             return;
    3888             :         }
    3889             : 
    3890         162 :         if (GetRasterCount())
    3891             :         {
    3892         115 :             CPLXMLNode *psFAOPrev = psObservationArea;
    3893         117 :             while (psFAOPrev->psNext != nullptr &&
    3894           4 :                    psFAOPrev->psNext->eType == CXT_Comment)
    3895             :             {
    3896           2 :                 psFAOPrev = psFAOPrev->psNext;
    3897             :             }
    3898         115 :             if (psFAOPrev->psNext != nullptr)
    3899             :             {
    3900             :                 // There may be an optional Reference_List element between
    3901             :                 // Observation_Area and File_Area_Observational
    3902           4 :                 if (!(psFAOPrev->psNext->eType == CXT_Element &&
    3903           2 :                       psFAOPrev->psNext->pszValue ==
    3904           4 :                           osPrefix + "Reference_List"))
    3905             :                 {
    3906           1 :                     CPLError(CE_Failure, CPLE_AppDefined,
    3907             :                              "Unexpected content found after Observation_Area "
    3908             :                              "in template");
    3909           1 :                     CPLDestroyXMLNode(psTemplateSpecialConstants);
    3910           1 :                     return;
    3911             :                 }
    3912           1 :                 psFAOPrev = psFAOPrev->psNext;
    3913           2 :                 while (psFAOPrev->psNext != nullptr &&
    3914           1 :                        psFAOPrev->psNext->eType == CXT_Comment)
    3915             :                 {
    3916           1 :                     psFAOPrev = psFAOPrev->psNext;
    3917             :                 }
    3918           1 :                 if (psFAOPrev->psNext != nullptr)
    3919             :                 {
    3920           0 :                     CPLError(CE_Failure, CPLE_AppDefined,
    3921             :                              "Unexpected content found after Reference_List in "
    3922             :                              "template");
    3923           0 :                     CPLDestroyXMLNode(psTemplateSpecialConstants);
    3924           0 :                     return;
    3925             :                 }
    3926             :             }
    3927             : 
    3928         114 :             CPLXMLNode *psFAO = CPLCreateXMLNode(
    3929             :                 nullptr, CXT_Element,
    3930         228 :                 (osPrefix + "File_Area_Observational").c_str());
    3931         114 :             psFAOPrev->psNext = psFAO;
    3932             : 
    3933         114 :             CPLXMLNode *psFile = CPLCreateXMLNode(psFAO, CXT_Element,
    3934         228 :                                                   (osPrefix + "File").c_str());
    3935         228 :             CPLCreateXMLElementAndValue(psFile,
    3936         228 :                                         (osPrefix + "file_name").c_str(),
    3937             :                                         CPLGetFilename(m_osImageFilename));
    3938         114 :             if (m_bCreatedFromExistingBinaryFile)
    3939             :             {
    3940           7 :                 CPLCreateXMLNode(psFile, CXT_Comment, PREEXISTING_BINARY_FILE);
    3941             :             }
    3942             :             CPLXMLNode *psDisciplineArea =
    3943         342 :                 CPLGetXMLNode(psProduct, (osPrefix + "Observation_Area." +
    3944         228 :                                           osPrefix + "Discipline_Area")
    3945             :                                              .c_str());
    3946             :             const char *pszLocalIdentifier =
    3947         114 :                 GetLocalIdentifierReferenceFromDisciplineArea(psDisciplineArea,
    3948             :                                                               "image");
    3949             : 
    3950         123 :             if (m_poExternalDS && m_poExternalDS->GetDriver() &&
    3951           9 :                 EQUAL(m_poExternalDS->GetDriver()->GetDescription(), "GTiff"))
    3952             :             {
    3953             :                 VSILFILE *fpTemp =
    3954           9 :                     VSIFOpenL(m_poExternalDS->GetDescription(), "rb");
    3955           9 :                 if (fpTemp)
    3956             :                 {
    3957           9 :                     GByte abySignature[4] = {0};
    3958           9 :                     VSIFReadL(abySignature, 1, 4, fpTemp);
    3959           9 :                     VSIFCloseL(fpTemp);
    3960           9 :                     const bool bBigTIFF =
    3961           9 :                         abySignature[2] == 43 || abySignature[3] == 43;
    3962             :                     m_osHeaderParsingStandard =
    3963           9 :                         bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
    3964             :                     const char *pszOffset =
    3965           9 :                         m_poExternalDS->GetRasterBand(1)->GetMetadataItem(
    3966           9 :                             "BLOCK_OFFSET_0_0", "TIFF");
    3967           9 :                     if (pszOffset)
    3968           9 :                         m_nBaseOffset = CPLAtoGIntBig(pszOffset);
    3969             :                 }
    3970             :             }
    3971             : 
    3972         114 :             if (!m_osHeaderParsingStandard.empty() && m_nBaseOffset > 0)
    3973             :             {
    3974          15 :                 CPLXMLNode *psHeader = CPLCreateXMLNode(
    3975          30 :                     psFAO, CXT_Element, (osPrefix + "Header").c_str());
    3976          15 :                 CPLAddXMLAttributeAndValue(
    3977             :                     CPLCreateXMLElementAndValue(
    3978          30 :                         psHeader, (osPrefix + "offset").c_str(), "0"),
    3979             :                     "unit", "byte");
    3980          15 :                 CPLAddXMLAttributeAndValue(
    3981             :                     CPLCreateXMLElementAndValue(
    3982          30 :                         psHeader, (osPrefix + "object_length").c_str(),
    3983             :                         CPLSPrintf(CPL_FRMT_GUIB,
    3984          15 :                                    static_cast<GUIntBig>(m_nBaseOffset))),
    3985             :                     "unit", "byte");
    3986          30 :                 CPLCreateXMLElementAndValue(
    3987          30 :                     psHeader, (osPrefix + "parsing_standard_id").c_str(),
    3988             :                     m_osHeaderParsingStandard.c_str());
    3989          15 :                 if (m_osHeaderParsingStandard == TIFF_GEOTIFF_STRING)
    3990             :                 {
    3991          11 :                     CPLCreateXMLElementAndValue(
    3992          22 :                         psHeader, (osPrefix + "description").c_str(),
    3993             :                         "TIFF/GeoTIFF header. The TIFF/GeoTIFF format is used "
    3994             :                         "throughout the geospatial and science communities "
    3995             :                         "to share geographic image data. ");
    3996             :                 }
    3997           4 :                 else if (m_osHeaderParsingStandard == BIGTIFF_GEOTIFF_STRING)
    3998             :                 {
    3999           0 :                     CPLCreateXMLElementAndValue(
    4000           0 :                         psHeader, (osPrefix + "description").c_str(),
    4001             :                         "BigTIFF/GeoTIFF header. The BigTIFF/GeoTIFF format is "
    4002             :                         "used "
    4003             :                         "throughout the geospatial and science communities "
    4004             :                         "to share geographic image data. ");
    4005             :                 }
    4006             :             }
    4007             : 
    4008         114 :             WriteArray(osPrefix, psFAO, pszLocalIdentifier,
    4009             :                        psTemplateSpecialConstants);
    4010             :         }
    4011             :     }
    4012             : }
    4013             : 
    4014             : /************************************************************************/
    4015             : /*                             WriteHeader()                            */
    4016             : /************************************************************************/
    4017             : 
    4018         176 : void PDS4Dataset::WriteHeader()
    4019             : {
    4020             :     const bool bAppend =
    4021         176 :         CPLFetchBool(m_papszCreationOptions, "APPEND_SUBDATASET", false);
    4022         176 :     if (bAppend)
    4023             :     {
    4024           4 :         WriteHeaderAppendCase();
    4025           5 :         return;
    4026             :     }
    4027             : 
    4028             :     CPLXMLNode *psRoot;
    4029         172 :     if (m_bCreateHeader)
    4030             :     {
    4031             :         CPLString osTemplateFilename =
    4032         165 :             CSLFetchNameValueDef(m_papszCreationOptions, "TEMPLATE", "");
    4033         165 :         if (!osTemplateFilename.empty())
    4034             :         {
    4035          20 :             if (STARTS_WITH(osTemplateFilename, "http://") ||
    4036          10 :                 STARTS_WITH(osTemplateFilename, "https://"))
    4037             :             {
    4038           0 :                 osTemplateFilename = "/vsicurl_streaming/" + osTemplateFilename;
    4039             :             }
    4040          10 :             psRoot = CPLParseXMLFile(osTemplateFilename);
    4041             :         }
    4042         155 :         else if (!m_osXMLPDS4.empty())
    4043           6 :             psRoot = CPLParseXMLString(m_osXMLPDS4);
    4044             :         else
    4045             :         {
    4046             : #ifndef USE_ONLY_EMBEDDED_RESOURCE_FILES
    4047             : #ifdef EMBED_RESOURCE_FILES
    4048             :             CPLErrorStateBackuper oErrorStateBackuper(CPLQuietErrorHandler);
    4049             : #endif
    4050             :             const char *pszDefaultTemplateFilename =
    4051         149 :                 CPLFindFile("gdal", "pds4_template.xml");
    4052         149 :             if (pszDefaultTemplateFilename)
    4053             :             {
    4054         149 :                 psRoot = CPLParseXMLFile(pszDefaultTemplateFilename);
    4055             :             }
    4056             :             else
    4057             : #endif
    4058             :             {
    4059             : #ifdef EMBED_RESOURCE_FILES
    4060             :                 static const bool bOnce [[maybe_unused]] = []()
    4061             :                 {
    4062             :                     CPLDebug("PDS4", "Using embedded pds4_template.xml");
    4063             :                     return true;
    4064             :                 }();
    4065             :                 psRoot = CPLParseXMLString(PDS4GetEmbeddedTemplate());
    4066             : #else
    4067           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
    4068             :                          "Cannot find pds4_template.xml and TEMPLATE "
    4069             :                          "creation option not specified");
    4070           0 :                 return;
    4071             : #endif
    4072             :             }
    4073             :         }
    4074             :     }
    4075             :     else
    4076             :     {
    4077           7 :         psRoot = CPLParseXMLFile(m_osXMLFilename);
    4078             :     }
    4079         172 :     CPLXMLTreeCloser oCloser(psRoot);
    4080         172 :     psRoot = oCloser.get();
    4081         172 :     if (psRoot == nullptr)
    4082           0 :         return;
    4083         172 :     CPLXMLNode *psProduct = CPLGetXMLNode(psRoot, "=Product_Observational");
    4084         172 :     if (psProduct == nullptr)
    4085             :     {
    4086           1 :         psProduct = CPLGetXMLNode(psRoot, "=pds:Product_Observational");
    4087             :     }
    4088         172 :     if (psProduct == nullptr)
    4089             :     {
    4090           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    4091             :                  "Cannot find Product_Observational element in template");
    4092           1 :         return;
    4093             :     }
    4094             : 
    4095         171 :     if (m_bCreateHeader)
    4096             :     {
    4097         328 :         CPLString osCARTVersion(CURRENT_CART_VERSION);
    4098         164 :         char *pszXML = CPLSerializeXMLTree(psRoot);
    4099         164 :         if (pszXML)
    4100             :         {
    4101         164 :             const char *pszIter = pszXML;
    4102             :             while (true)
    4103             :             {
    4104         321 :                 const char *pszCartSchema = strstr(pszIter, "PDS4_CART_");
    4105         321 :                 if (pszCartSchema)
    4106             :                 {
    4107         314 :                     const char *pszXSDExtension = strstr(pszCartSchema, ".xsd");
    4108         314 :                     if (pszXSDExtension &&
    4109         314 :                         pszXSDExtension - pszCartSchema <= 20)
    4110             :                     {
    4111         157 :                         osCARTVersion = pszCartSchema + strlen("PDS4_CART_");
    4112         157 :                         osCARTVersion.resize(pszXSDExtension - pszCartSchema -
    4113             :                                              strlen("PDS4_CART_"));
    4114         157 :                         break;
    4115             :                     }
    4116             :                     else
    4117             :                     {
    4118         157 :                         pszIter = pszCartSchema + 1;
    4119             :                     }
    4120             :                 }
    4121             :                 else
    4122             :                 {
    4123           7 :                     break;
    4124             :                 }
    4125         157 :             }
    4126             : 
    4127         164 :             CPLFree(pszXML);
    4128             :         }
    4129             : 
    4130         164 :         CreateHeader(psProduct, osCARTVersion.c_str());
    4131             :     }
    4132             : 
    4133         171 :     WriteVectorLayers(psProduct);
    4134             : 
    4135         171 :     CPLSerializeXMLTreeToFile(psRoot, GetDescription());
    4136             : }
    4137             : 
    4138             : /************************************************************************/
    4139             : /*                            ICreateLayer()                            */
    4140             : /************************************************************************/
    4141             : 
    4142          66 : OGRLayer *PDS4Dataset::ICreateLayer(const char *pszName,
    4143             :                                     const OGRGeomFieldDefn *poGeomFieldDefn,
    4144             :                                     CSLConstList papszOptions)
    4145             : {
    4146             :     const char *pszTableType =
    4147          66 :         CSLFetchNameValueDef(papszOptions, "TABLE_TYPE", "DELIMITED");
    4148          66 :     if (!EQUAL(pszTableType, "CHARACTER") && !EQUAL(pszTableType, "BINARY") &&
    4149          55 :         !EQUAL(pszTableType, "DELIMITED"))
    4150             :     {
    4151           0 :         return nullptr;
    4152             :     }
    4153             : 
    4154          66 :     const auto eGType = poGeomFieldDefn ? poGeomFieldDefn->GetType() : wkbNone;
    4155             :     const auto poSpatialRef =
    4156          66 :         poGeomFieldDefn ? poGeomFieldDefn->GetSpatialRef() : nullptr;
    4157             : 
    4158         126 :     const char *pszExt = EQUAL(pszTableType, "CHARACTER") ? "dat"
    4159          60 :                          : EQUAL(pszTableType, "BINARY")  ? "bin"
    4160             :                                                           : "csv";
    4161             : 
    4162             :     bool bSameDirectory =
    4163          66 :         CPLTestBool(CSLFetchNameValueDef(papszOptions, "SAME_DIRECTORY", "NO"));
    4164             : 
    4165         132 :     std::string osBasename(pszName);
    4166         629 :     for (char &ch : osBasename)
    4167             :     {
    4168         563 :         if (!isalnum(static_cast<unsigned char>(ch)) &&
    4169          53 :             static_cast<unsigned>(ch) <= 127)
    4170          53 :             ch = '_';
    4171             :     }
    4172             : 
    4173         132 :     CPLString osFullFilename;
    4174          66 :     if (bSameDirectory)
    4175             :     {
    4176             :         osFullFilename =
    4177           2 :             CPLFormFilenameSafe(CPLGetPathSafe(m_osXMLFilename.c_str()).c_str(),
    4178           1 :                                 osBasename.c_str(), pszExt);
    4179             :         VSIStatBufL sStat;
    4180           1 :         if (VSIStatL(osFullFilename, &sStat) == 0)
    4181             :         {
    4182           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4183             :                      "%s already exists. Please delete it before, or "
    4184             :                      "rename the layer",
    4185             :                      osFullFilename.c_str());
    4186           0 :             return nullptr;
    4187             :         }
    4188             :     }
    4189             :     else
    4190             :     {
    4191         195 :         CPLString osDirectory = CPLFormFilenameSafe(
    4192         130 :             CPLGetPathSafe(m_osXMLFilename).c_str(),
    4193         130 :             CPLGetBasenameSafe(m_osXMLFilename).c_str(), nullptr);
    4194             :         VSIStatBufL sStat;
    4195         108 :         if (VSIStatL(osDirectory, &sStat) != 0 &&
    4196          43 :             VSIMkdir(osDirectory, 0755) != 0)
    4197             :         {
    4198           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot create directory %s",
    4199             :                      osDirectory.c_str());
    4200           1 :             return nullptr;
    4201             :         }
    4202             :         osFullFilename =
    4203          64 :             CPLFormFilenameSafe(osDirectory, osBasename.c_str(), pszExt);
    4204             :     }
    4205             : 
    4206          65 :     if (EQUAL(pszTableType, "DELIMITED"))
    4207             :     {
    4208             :         std::unique_ptr<PDS4DelimitedTable> poLayer(
    4209          54 :             new PDS4DelimitedTable(this, pszName, osFullFilename));
    4210          54 :         if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
    4211             :                                          papszOptions))
    4212             :         {
    4213           0 :             return nullptr;
    4214             :         }
    4215             :         std::unique_ptr<PDS4EditableLayer> poEditableLayer(
    4216         108 :             new PDS4EditableLayer(poLayer.release()));
    4217          54 :         m_apoLayers.push_back(std::move(poEditableLayer));
    4218             :     }
    4219             :     else
    4220             :     {
    4221             :         std::unique_ptr<PDS4FixedWidthTable> poLayer(
    4222          11 :             EQUAL(pszTableType, "CHARACTER")
    4223             :                 ? static_cast<PDS4FixedWidthTable *>(
    4224           6 :                       new PDS4TableCharacter(this, pszName, osFullFilename))
    4225             :                 : static_cast<PDS4FixedWidthTable *>(
    4226          17 :                       new PDS4TableBinary(this, pszName, osFullFilename)));
    4227          11 :         if (!poLayer->InitializeNewLayer(poSpatialRef, false, eGType,
    4228             :                                          papszOptions))
    4229             :         {
    4230           0 :             return nullptr;
    4231             :         }
    4232             :         std::unique_ptr<PDS4EditableLayer> poEditableLayer(
    4233          22 :             new PDS4EditableLayer(poLayer.release()));
    4234          11 :         m_apoLayers.push_back(std::move(poEditableLayer));
    4235             :     }
    4236          65 :     return m_apoLayers.back().get();
    4237             : }
    4238             : 
    4239             : /************************************************************************/
    4240             : /*                           TestCapability()                           */
    4241             : /************************************************************************/
    4242             : 
    4243          69 : int PDS4Dataset::TestCapability(const char *pszCap)
    4244             : {
    4245          69 :     if (EQUAL(pszCap, ODsCCreateLayer))
    4246          35 :         return eAccess == GA_Update;
    4247          34 :     else if (EQUAL(pszCap, ODsCZGeometries))
    4248           6 :         return TRUE;
    4249             :     else
    4250          28 :         return FALSE;
    4251             : }
    4252             : 
    4253             : /************************************************************************/
    4254             : /*                             Create()                                 */
    4255             : /************************************************************************/
    4256             : 
    4257         130 : GDALDataset *PDS4Dataset::Create(const char *pszFilename, int nXSize,
    4258             :                                  int nYSize, int nBandsIn, GDALDataType eType,
    4259             :                                  char **papszOptions)
    4260             : {
    4261         130 :     return CreateInternal(pszFilename, nullptr, nXSize, nYSize, nBandsIn, eType,
    4262         130 :                           papszOptions);
    4263             : }
    4264             : 
    4265             : /************************************************************************/
    4266             : /*                           CreateInternal()                           */
    4267             : /************************************************************************/
    4268             : 
    4269         188 : PDS4Dataset *PDS4Dataset::CreateInternal(const char *pszFilename,
    4270             :                                          GDALDataset *poSrcDS, int nXSize,
    4271             :                                          int nYSize, int nBandsIn,
    4272             :                                          GDALDataType eType,
    4273             :                                          const char *const *papszOptionsIn)
    4274             : {
    4275         376 :     CPLStringList aosOptions(papszOptionsIn);
    4276             : 
    4277         188 :     if (nXSize == 0 && nYSize == 0 && nBandsIn == 0 && eType == GDT_Unknown)
    4278             :     {
    4279             :         // Vector file creation
    4280          47 :         PDS4Dataset *poDS = new PDS4Dataset();
    4281          47 :         poDS->SetDescription(pszFilename);
    4282          47 :         poDS->nRasterXSize = 0;
    4283          47 :         poDS->nRasterYSize = 0;
    4284          47 :         poDS->eAccess = GA_Update;
    4285          47 :         poDS->m_osXMLFilename = pszFilename;
    4286          47 :         poDS->m_bCreateHeader = true;
    4287          47 :         poDS->m_bStripFileAreaObservationalFromTemplate = true;
    4288          47 :         poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
    4289          47 :         poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
    4290          47 :         return poDS;
    4291             :     }
    4292             : 
    4293         141 :     if (nXSize == 0)
    4294           0 :         return nullptr;
    4295             : 
    4296         141 :     if (!(eType == GDT_Byte || eType == GDT_Int8 || eType == GDT_Int16 ||
    4297          40 :           eType == GDT_UInt16 || eType == GDT_Int32 || eType == GDT_UInt32 ||
    4298          27 :           eType == GDT_Float32 || eType == GDT_Float64 ||
    4299          18 :           eType == GDT_CFloat32 || eType == GDT_CFloat64))
    4300             :     {
    4301          10 :         CPLError(
    4302             :             CE_Failure, CPLE_NotSupported,
    4303             :             "The PDS4 driver does not supporting creating files of type %s.",
    4304             :             GDALGetDataTypeName(eType));
    4305          10 :         return nullptr;
    4306             :     }
    4307             : 
    4308         131 :     if (nBandsIn == 0)
    4309             :     {
    4310           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid number of bands");
    4311           1 :         return nullptr;
    4312             :     }
    4313             : 
    4314             :     const char *pszArrayType =
    4315         130 :         aosOptions.FetchNameValueDef("ARRAY_TYPE", "Array_3D_Image");
    4316         130 :     const bool bIsArray2D = STARTS_WITH(pszArrayType, "Array_2D");
    4317         130 :     if (nBandsIn > 1 && bIsArray2D)
    4318             :     {
    4319           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    4320             :                  "ARRAY_TYPE=%s is not supported for a multi-band raster",
    4321             :                  pszArrayType);
    4322           1 :         return nullptr;
    4323             :     }
    4324             : 
    4325             :     /* -------------------------------------------------------------------- */
    4326             :     /*      Compute pixel, line and band offsets                            */
    4327             :     /* -------------------------------------------------------------------- */
    4328         129 :     const int nItemSize = GDALGetDataTypeSizeBytes(eType);
    4329             :     int nLineOffset, nPixelOffset;
    4330             :     vsi_l_offset nBandOffset;
    4331             : 
    4332             :     const char *pszInterleave =
    4333         129 :         aosOptions.FetchNameValueDef("INTERLEAVE", "BSQ");
    4334         129 :     if (bIsArray2D)
    4335           1 :         pszInterleave = "BIP";
    4336             : 
    4337         129 :     if (EQUAL(pszInterleave, "BIP"))
    4338             :     {
    4339           4 :         nPixelOffset = nItemSize * nBandsIn;
    4340           4 :         if (nPixelOffset > INT_MAX / nBandsIn)
    4341             :         {
    4342           0 :             return nullptr;
    4343             :         }
    4344           4 :         nLineOffset = nPixelOffset * nXSize;
    4345           4 :         nBandOffset = nItemSize;
    4346             :     }
    4347         125 :     else if (EQUAL(pszInterleave, "BSQ"))
    4348             :     {
    4349         121 :         nPixelOffset = nItemSize;
    4350         121 :         if (nPixelOffset > INT_MAX / nXSize)
    4351             :         {
    4352           0 :             return nullptr;
    4353             :         }
    4354         121 :         nLineOffset = nPixelOffset * nXSize;
    4355         121 :         nBandOffset = static_cast<vsi_l_offset>(nLineOffset) * nYSize;
    4356             :     }
    4357           4 :     else if (EQUAL(pszInterleave, "BIL"))
    4358             :     {
    4359           3 :         nPixelOffset = nItemSize;
    4360           3 :         if (nPixelOffset > INT_MAX / nBandsIn ||
    4361           3 :             nPixelOffset * nBandsIn > INT_MAX / nXSize)
    4362             :         {
    4363           0 :             return nullptr;
    4364             :         }
    4365           3 :         nLineOffset = nItemSize * nBandsIn * nXSize;
    4366           3 :         nBandOffset = static_cast<vsi_l_offset>(nItemSize) * nXSize;
    4367             :     }
    4368             :     else
    4369             :     {
    4370           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Invalid value for INTERLEAVE");
    4371           1 :         return nullptr;
    4372             :     }
    4373             : 
    4374             :     const char *pszImageFormat =
    4375         128 :         aosOptions.FetchNameValueDef("IMAGE_FORMAT", "RAW");
    4376         128 :     const char *pszImageExtension = aosOptions.FetchNameValueDef(
    4377         128 :         "IMAGE_EXTENSION", EQUAL(pszImageFormat, "RAW") ? "img" : "tif");
    4378             :     CPLString osImageFilename(aosOptions.FetchNameValueDef(
    4379             :         "IMAGE_FILENAME",
    4380         256 :         CPLResetExtensionSafe(pszFilename, pszImageExtension).c_str()));
    4381             : 
    4382         128 :     const bool bAppend = aosOptions.FetchBool("APPEND_SUBDATASET", false);
    4383         128 :     if (bAppend)
    4384             :     {
    4385           4 :         GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    4386           4 :         auto poExistingPDS4 = static_cast<PDS4Dataset *>(Open(&oOpenInfo));
    4387           4 :         if (!poExistingPDS4)
    4388             :         {
    4389           0 :             return nullptr;
    4390             :         }
    4391           4 :         osImageFilename = poExistingPDS4->m_osImageFilename;
    4392           4 :         delete poExistingPDS4;
    4393             : 
    4394           4 :         auto poImageDS = GDALDataset::FromHandle(GDALOpenEx(
    4395             :             osImageFilename, GDAL_OF_RASTER, nullptr, nullptr, nullptr));
    4396           6 :         if (poImageDS && poImageDS->GetDriver() &&
    4397           2 :             EQUAL(poImageDS->GetDriver()->GetDescription(), "GTiff"))
    4398             :         {
    4399           2 :             pszImageFormat = "GEOTIFF";
    4400             :         }
    4401           4 :         delete poImageDS;
    4402             :     }
    4403             : 
    4404         128 :     GDALDataset *poExternalDS = nullptr;
    4405         128 :     VSILFILE *fpImage = nullptr;
    4406         128 :     vsi_l_offset nBaseOffset = 0;
    4407         128 :     bool bIsLSB = true;
    4408         256 :     CPLString osHeaderParsingStandard;
    4409             :     const bool bCreateLabelOnly =
    4410         128 :         aosOptions.FetchBool("CREATE_LABEL_ONLY", false);
    4411         128 :     if (bCreateLabelOnly)
    4412             :     {
    4413           8 :         if (poSrcDS == nullptr)
    4414             :         {
    4415           0 :             CPLError(
    4416             :                 CE_Failure, CPLE_AppDefined,
    4417             :                 "CREATE_LABEL_ONLY is only compatible of CreateCopy() mode");
    4418           1 :             return nullptr;
    4419             :         }
    4420           8 :         RawBinaryLayout sLayout;
    4421           8 :         if (!poSrcDS->GetRawBinaryLayout(sLayout))
    4422             :         {
    4423           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    4424             :                      "Source dataset is not compatible of a raw binary format");
    4425           1 :             return nullptr;
    4426             :         }
    4427           7 :         if ((nBandsIn > 1 &&
    4428           7 :              sLayout.eInterleaving == RawBinaryLayout::Interleaving::UNKNOWN) ||
    4429           6 :             (nBandsIn == 1 &&
    4430           6 :              !(sLayout.nPixelOffset == nItemSize &&
    4431           6 :                sLayout.nLineOffset == sLayout.nPixelOffset * nXSize)))
    4432             :         {
    4433           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    4434             :                      "Source dataset has an interleaving not handled in PDS4");
    4435           0 :             return nullptr;
    4436             :         }
    4437           7 :         fpImage = VSIFOpenL(sLayout.osRawFilename.c_str(), "rb");
    4438           7 :         if (fpImage == nullptr)
    4439             :         {
    4440           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot open raw image %s",
    4441             :                      sLayout.osRawFilename.c_str());
    4442           0 :             return nullptr;
    4443             :         }
    4444           7 :         osImageFilename = sLayout.osRawFilename;
    4445           7 :         if (nBandsIn == 1 ||
    4446           1 :             sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIP)
    4447           7 :             pszInterleave = "BIP";
    4448           0 :         else if (sLayout.eInterleaving == RawBinaryLayout::Interleaving::BIL)
    4449           0 :             pszInterleave = "BIL";
    4450             :         else
    4451           0 :             pszInterleave = "BSQ";
    4452           7 :         nBaseOffset = sLayout.nImageOffset;
    4453           7 :         nPixelOffset = static_cast<int>(sLayout.nPixelOffset);
    4454           7 :         nLineOffset = static_cast<int>(sLayout.nLineOffset);
    4455           7 :         nBandOffset = static_cast<vsi_l_offset>(sLayout.nBandOffset);
    4456           7 :         bIsLSB = sLayout.bLittleEndianOrder;
    4457           7 :         auto poSrcDriver = poSrcDS->GetDriver();
    4458           7 :         if (poSrcDriver)
    4459             :         {
    4460           7 :             auto pszDriverName = poSrcDriver->GetDescription();
    4461           7 :             if (EQUAL(pszDriverName, "GTiff"))
    4462             :             {
    4463           2 :                 GByte abySignature[4] = {0};
    4464           2 :                 VSIFReadL(abySignature, 1, 4, fpImage);
    4465           2 :                 const bool bBigTIFF =
    4466           2 :                     abySignature[2] == 43 || abySignature[3] == 43;
    4467             :                 osHeaderParsingStandard =
    4468           2 :                     bBigTIFF ? BIGTIFF_GEOTIFF_STRING : TIFF_GEOTIFF_STRING;
    4469             :             }
    4470           5 :             else if (EQUAL(pszDriverName, "ISIS3"))
    4471             :             {
    4472           1 :                 osHeaderParsingStandard = "ISIS3";
    4473             :             }
    4474           4 :             else if (EQUAL(pszDriverName, "VICAR"))
    4475             :             {
    4476           1 :                 osHeaderParsingStandard = "VICAR2";
    4477             :             }
    4478           3 :             else if (EQUAL(pszDriverName, "PDS"))
    4479             :             {
    4480           1 :                 osHeaderParsingStandard = "PDS3";
    4481             :             }
    4482           2 :             else if (EQUAL(pszDriverName, "FITS"))
    4483             :             {
    4484           1 :                 osHeaderParsingStandard = "FITS 3.0";
    4485             :                 aosOptions.SetNameValue("VAR_VERTICAL_DISPLAY_DIRECTION",
    4486           1 :                                         "Bottom to Top");
    4487             :             }
    4488             :         }
    4489             :     }
    4490         120 :     else if (EQUAL(pszImageFormat, "GEOTIFF"))
    4491             :     {
    4492          13 :         if (EQUAL(pszInterleave, "BIL"))
    4493             :         {
    4494           2 :             if (aosOptions.FetchBool("@INTERLEAVE_ADDED_AUTOMATICALLY", false))
    4495             :             {
    4496           1 :                 pszInterleave = "BSQ";
    4497             :             }
    4498             :             else
    4499             :             {
    4500           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    4501             :                          "INTERLEAVE=BIL not supported for GeoTIFF in PDS4");
    4502           1 :                 return nullptr;
    4503             :             }
    4504             :         }
    4505             :         GDALDriver *poDrv =
    4506          12 :             static_cast<GDALDriver *>(GDALGetDriverByName("GTiff"));
    4507          12 :         if (poDrv == nullptr)
    4508             :         {
    4509           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find GTiff driver");
    4510           0 :             return nullptr;
    4511             :         }
    4512          12 :         char **papszGTiffOptions = nullptr;
    4513             : #ifdef notdef
    4514             :         // In practice I can't see which option we can really use
    4515             :         const char *pszGTiffOptions =
    4516             :             CSLFetchNameValueDef(papszOptions, "GEOTIFF_OPTIONS", "");
    4517             :         char **papszTokens = CSLTokenizeString2(pszGTiffOptions, ",", 0);
    4518             :         if (CPLFetchBool(papszTokens, "TILED", false))
    4519             :         {
    4520             :             CSLDestroy(papszTokens);
    4521             :             CPLError(CE_Failure, CPLE_AppDefined,
    4522             :                      "Tiled GeoTIFF is not supported for PDS4");
    4523             :             return NULL;
    4524             :         }
    4525             :         if (!EQUAL(CSLFetchNameValueDef(papszTokens, "COMPRESS", "NONE"),
    4526             :                    "NONE"))
    4527             :         {
    4528             :             CSLDestroy(papszTokens);
    4529             :             CPLError(CE_Failure, CPLE_AppDefined,
    4530             :                      "Compressed GeoTIFF is not supported for PDS4");
    4531             :             return NULL;
    4532             :         }
    4533             :         papszGTiffOptions =
    4534             :             CSLSetNameValue(papszGTiffOptions, "ENDIANNESS", "LITTLE");
    4535             :         for (int i = 0; papszTokens[i] != NULL; i++)
    4536             :         {
    4537             :             papszGTiffOptions = CSLAddString(papszGTiffOptions, papszTokens[i]);
    4538             :         }
    4539             :         CSLDestroy(papszTokens);
    4540             : #endif
    4541             : 
    4542             :         papszGTiffOptions =
    4543          12 :             CSLSetNameValue(papszGTiffOptions, "INTERLEAVE",
    4544          12 :                             EQUAL(pszInterleave, "BSQ") ? "BAND" : "PIXEL");
    4545             :         // Will make sure that our blocks at nodata are not optimized
    4546             :         // away but indeed well written
    4547          12 :         papszGTiffOptions = CSLSetNameValue(
    4548             :             papszGTiffOptions, "@WRITE_EMPTY_TILES_SYNCHRONOUSLY", "YES");
    4549          12 :         if (nBandsIn > 1 && EQUAL(pszInterleave, "BSQ"))
    4550             :         {
    4551             :             papszGTiffOptions =
    4552           2 :                 CSLSetNameValue(papszGTiffOptions, "BLOCKYSIZE", "1");
    4553             :         }
    4554             : 
    4555          12 :         if (bAppend)
    4556             :         {
    4557             :             papszGTiffOptions =
    4558           2 :                 CSLAddString(papszGTiffOptions, "APPEND_SUBDATASET=YES");
    4559             :         }
    4560             : 
    4561          12 :         poExternalDS = poDrv->Create(osImageFilename, nXSize, nYSize, nBandsIn,
    4562             :                                      eType, papszGTiffOptions);
    4563          12 :         CSLDestroy(papszGTiffOptions);
    4564          12 :         if (poExternalDS == nullptr)
    4565             :         {
    4566           1 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
    4567             :                      osImageFilename.c_str());
    4568           1 :             return nullptr;
    4569             :         }
    4570             :     }
    4571             :     else
    4572             :     {
    4573         212 :         fpImage = VSIFOpenL(
    4574             :             osImageFilename,
    4575             :             bAppend                                                 ? "rb+"
    4576         105 :             : VSISupportsRandomWrite(osImageFilename.c_str(), true) ? "wb+"
    4577             :                                                                     : "wb");
    4578         107 :         if (fpImage == nullptr)
    4579             :         {
    4580           3 :             CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s",
    4581             :                      osImageFilename.c_str());
    4582           3 :             return nullptr;
    4583             :         }
    4584         104 :         if (bAppend)
    4585             :         {
    4586           2 :             VSIFSeekL(fpImage, 0, SEEK_END);
    4587           2 :             nBaseOffset = VSIFTellL(fpImage);
    4588             :         }
    4589             :     }
    4590             : 
    4591         244 :     auto poDS = std::make_unique<PDS4Dataset>();
    4592         122 :     poDS->SetDescription(pszFilename);
    4593         122 :     poDS->m_bMustInitImageFile = true;
    4594         122 :     poDS->m_fpImage = fpImage;
    4595         122 :     poDS->m_nBaseOffset = nBaseOffset;
    4596         122 :     poDS->m_poExternalDS = poExternalDS;
    4597         122 :     poDS->nRasterXSize = nXSize;
    4598         122 :     poDS->nRasterYSize = nYSize;
    4599         122 :     poDS->eAccess = GA_Update;
    4600         122 :     poDS->m_osImageFilename = std::move(osImageFilename);
    4601         122 :     poDS->m_bCreateHeader = true;
    4602         122 :     poDS->m_bStripFileAreaObservationalFromTemplate = true;
    4603         122 :     poDS->m_osInterleave = pszInterleave;
    4604         122 :     poDS->m_papszCreationOptions = CSLDuplicate(aosOptions.List());
    4605         122 :     poDS->m_bUseSrcLabel = aosOptions.FetchBool("USE_SRC_LABEL", true);
    4606         122 :     poDS->m_bIsLSB = bIsLSB;
    4607         122 :     poDS->m_osHeaderParsingStandard = std::move(osHeaderParsingStandard);
    4608         122 :     poDS->m_bCreatedFromExistingBinaryFile = bCreateLabelOnly;
    4609             : 
    4610         122 :     if (EQUAL(pszInterleave, "BIP"))
    4611             :     {
    4612          10 :         poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL",
    4613             :                                            "IMAGE_STRUCTURE");
    4614             :     }
    4615         112 :     else if (EQUAL(pszInterleave, "BSQ"))
    4616             :     {
    4617         111 :         poDS->GDALDataset::SetMetadataItem("INTERLEAVE", "BAND",
    4618             :                                            "IMAGE_STRUCTURE");
    4619             :     }
    4620             : 
    4621         300 :     for (int i = 0; i < nBandsIn; i++)
    4622             :     {
    4623         178 :         if (poDS->m_poExternalDS != nullptr)
    4624             :         {
    4625             :             auto poBand = std::make_unique<PDS4WrapperRasterBand>(
    4626          17 :                 poDS->m_poExternalDS->GetRasterBand(i + 1));
    4627          17 :             poDS->SetBand(i + 1, std::move(poBand));
    4628             :         }
    4629             :         else
    4630             :         {
    4631             :             auto poBand = std::make_unique<PDS4RawRasterBand>(
    4632         161 :                 poDS.get(), i + 1, poDS->m_fpImage,
    4633         161 :                 poDS->m_nBaseOffset + nBandOffset * i, nPixelOffset,
    4634             :                 nLineOffset, eType,
    4635         161 :                 bIsLSB ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
    4636         161 :                        : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN);
    4637         161 :             poDS->SetBand(i + 1, std::move(poBand));
    4638             :         }
    4639             :     }
    4640             : 
    4641         122 :     return poDS.release();
    4642             : }
    4643             : 
    4644             : /************************************************************************/
    4645             : /*                      PDS4GetUnderlyingDataset()                      */
    4646             : /************************************************************************/
    4647             : 
    4648          62 : static GDALDataset *PDS4GetUnderlyingDataset(GDALDataset *poSrcDS)
    4649             : {
    4650         124 :     if (poSrcDS->GetDriver() != nullptr &&
    4651          62 :         poSrcDS->GetDriver() == GDALGetDriverByName("VRT"))
    4652             :     {
    4653           3 :         VRTDataset *poVRTDS = reinterpret_cast<VRTDataset *>(poSrcDS);
    4654           3 :         poSrcDS = poVRTDS->GetSingleSimpleSource();
    4655             :     }
    4656             : 
    4657          62 :     return poSrcDS;
    4658             : }
    4659             : 
    4660             : /************************************************************************/
    4661             : /*                            CreateCopy()                              */
    4662             : /************************************************************************/
    4663             : 
    4664          62 : GDALDataset *PDS4Dataset::CreateCopy(const char *pszFilename,
    4665             :                                      GDALDataset *poSrcDS, int bStrict,
    4666             :                                      char **papszOptions,
    4667             :                                      GDALProgressFunc pfnProgress,
    4668             :                                      void *pProgressData)
    4669             : {
    4670             :     const char *pszImageFormat =
    4671          62 :         CSLFetchNameValueDef(papszOptions, "IMAGE_FORMAT", "RAW");
    4672          62 :     GDALDataset *poSrcUnderlyingDS = PDS4GetUnderlyingDataset(poSrcDS);
    4673          62 :     if (poSrcUnderlyingDS == nullptr)
    4674           1 :         poSrcUnderlyingDS = poSrcDS;
    4675          68 :     if (EQUAL(pszImageFormat, "GEOTIFF") &&
    4676           6 :         strcmp(poSrcUnderlyingDS->GetDescription(),
    4677             :                CSLFetchNameValueDef(
    4678             :                    papszOptions, "IMAGE_FILENAME",
    4679          68 :                    CPLResetExtensionSafe(pszFilename, "tif").c_str())) == 0)
    4680             :     {
    4681           1 :         CPLError(CE_Failure, CPLE_NotSupported,
    4682             :                  "Output file has same name as input file");
    4683           1 :         return nullptr;
    4684             :     }
    4685          61 :     if (poSrcDS->GetRasterCount() == 0)
    4686             :     {
    4687           1 :         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
    4688           1 :         return nullptr;
    4689             :     }
    4690             : 
    4691          60 :     const bool bAppend = CPLFetchBool(papszOptions, "APPEND_SUBDATASET", false);
    4692          60 :     if (bAppend)
    4693             :     {
    4694           6 :         GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    4695           6 :         GDALDataset *poExistingDS = Open(&oOpenInfo);
    4696           6 :         if (poExistingDS)
    4697             :         {
    4698           6 :             double adfExistingGT[6] = {0.0};
    4699             :             const bool bExistingHasGT =
    4700           6 :                 poExistingDS->GetGeoTransform(adfExistingGT) == CE_None;
    4701           6 :             double adfGeoTransform[6] = {0.0};
    4702             :             const bool bSrcHasGT =
    4703           6 :                 poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None;
    4704             : 
    4705           6 :             OGRSpatialReference oExistingSRS;
    4706           6 :             OGRSpatialReference oSrcSRS;
    4707           6 :             const char *pszExistingSRS = poExistingDS->GetProjectionRef();
    4708           6 :             const char *pszSrcSRS = poSrcDS->GetProjectionRef();
    4709           6 :             CPLString osExistingProj4;
    4710           6 :             if (pszExistingSRS && pszExistingSRS[0])
    4711             :             {
    4712           6 :                 oExistingSRS.SetFromUserInput(
    4713             :                     pszExistingSRS,
    4714             :                     OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    4715           6 :                 char *pszExistingProj4 = nullptr;
    4716           6 :                 oExistingSRS.exportToProj4(&pszExistingProj4);
    4717           6 :                 if (pszExistingProj4)
    4718           6 :                     osExistingProj4 = pszExistingProj4;
    4719           6 :                 CPLFree(pszExistingProj4);
    4720             :             }
    4721           6 :             CPLString osSrcProj4;
    4722           6 :             if (pszSrcSRS && pszSrcSRS[0])
    4723             :             {
    4724           6 :                 oSrcSRS.SetFromUserInput(
    4725             :                     pszSrcSRS,
    4726             :                     OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    4727           6 :                 char *pszSrcProj4 = nullptr;
    4728           6 :                 oSrcSRS.exportToProj4(&pszSrcProj4);
    4729           6 :                 if (pszSrcProj4)
    4730           6 :                     osSrcProj4 = pszSrcProj4;
    4731           6 :                 CPLFree(pszSrcProj4);
    4732             :             }
    4733             : 
    4734           6 :             delete poExistingDS;
    4735             : 
    4736             :             const auto maxRelErrorGT =
    4737           6 :                 [](const double adfGT1[6], const double adfGT2[6])
    4738             :             {
    4739           6 :                 double maxRelError = 0.0;
    4740          42 :                 for (int i = 0; i < 6; i++)
    4741             :                 {
    4742          36 :                     if (adfGT1[i] == 0.0)
    4743             :                     {
    4744          12 :                         maxRelError =
    4745          12 :                             std::max(maxRelError, std::abs(adfGT2[i]));
    4746             :                     }
    4747             :                     else
    4748             :                     {
    4749          24 :                         maxRelError = std::max(maxRelError,
    4750          48 :                                                std::abs(adfGT2[i] - adfGT1[i]) /
    4751          24 :                                                    std::abs(adfGT1[i]));
    4752             :                     }
    4753             :                 }
    4754           6 :                 return maxRelError;
    4755             :             };
    4756             : 
    4757           6 :             if ((bExistingHasGT && !bSrcHasGT) ||
    4758          18 :                 (!bExistingHasGT && bSrcHasGT) ||
    4759          12 :                 (bExistingHasGT && bSrcHasGT &&
    4760           6 :                  maxRelErrorGT(adfExistingGT, adfGeoTransform) > 1e-10))
    4761             :             {
    4762           1 :                 CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
    4763             :                          "Appending to a dataset with a different "
    4764             :                          "geotransform is not supported");
    4765           1 :                 if (bStrict)
    4766           1 :                     return nullptr;
    4767             :             }
    4768             :             // Do proj string comparison, as it is unlikely that
    4769             :             // OGRSpatialReference::IsSame() will lead to identical reasons due
    4770             :             // to PDS changing CRS names, etc...
    4771           5 :             if (osExistingProj4 != osSrcProj4)
    4772             :             {
    4773           1 :                 CPLError(bStrict ? CE_Failure : CE_Warning, CPLE_NotSupported,
    4774             :                          "Appending to a dataset with a different "
    4775             :                          "coordinate reference system is not supported");
    4776           1 :                 if (bStrict)
    4777           1 :                     return nullptr;
    4778             :             }
    4779             :         }
    4780             :     }
    4781             : 
    4782          58 :     const int nXSize = poSrcDS->GetRasterXSize();
    4783          58 :     const int nYSize = poSrcDS->GetRasterYSize();
    4784          58 :     const int nBands = poSrcDS->GetRasterCount();
    4785          58 :     GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    4786          58 :     PDS4Dataset *poDS = CreateInternal(pszFilename, poSrcDS, nXSize, nYSize,
    4787             :                                        nBands, eType, papszOptions);
    4788          58 :     if (poDS == nullptr)
    4789           6 :         return nullptr;
    4790             : 
    4791          52 :     double adfGeoTransform[6] = {0.0};
    4792         101 :     if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None &&
    4793          49 :         (adfGeoTransform[0] != 0.0 || adfGeoTransform[1] != 1.0 ||
    4794           0 :          adfGeoTransform[2] != 0.0 || adfGeoTransform[3] != 0.0 ||
    4795           0 :          adfGeoTransform[4] != 0.0 || adfGeoTransform[5] != 1.0))
    4796             :     {
    4797          49 :         poDS->SetGeoTransform(adfGeoTransform);
    4798             :     }
    4799             : 
    4800         104 :     if (poSrcDS->GetProjectionRef() != nullptr &&
    4801          52 :         strlen(poSrcDS->GetProjectionRef()) > 0)
    4802             :     {
    4803          49 :         poDS->SetProjection(poSrcDS->GetProjectionRef());
    4804             :     }
    4805             : 
    4806         131 :     for (int i = 1; i <= nBands; i++)
    4807             :     {
    4808          79 :         int bHasNoData = false;
    4809             :         const double dfNoData =
    4810          79 :             poSrcDS->GetRasterBand(i)->GetNoDataValue(&bHasNoData);
    4811          79 :         if (bHasNoData)
    4812          12 :             poDS->GetRasterBand(i)->SetNoDataValue(dfNoData);
    4813             : 
    4814          79 :         const double dfOffset = poSrcDS->GetRasterBand(i)->GetOffset();
    4815          79 :         if (dfOffset != 0.0)
    4816           3 :             poDS->GetRasterBand(i)->SetOffset(dfOffset);
    4817             : 
    4818          79 :         const double dfScale = poSrcDS->GetRasterBand(i)->GetScale();
    4819          79 :         if (dfScale != 1.0)
    4820           3 :             poDS->GetRasterBand(i)->SetScale(dfScale);
    4821             : 
    4822         158 :         poDS->GetRasterBand(i)->SetUnitType(
    4823          79 :             poSrcDS->GetRasterBand(i)->GetUnitType());
    4824             :     }
    4825             : 
    4826          52 :     if (poDS->m_bUseSrcLabel)
    4827             :     {
    4828          50 :         char **papszMD_PDS4 = poSrcDS->GetMetadata("xml:PDS4");
    4829          50 :         if (papszMD_PDS4 != nullptr)
    4830             :         {
    4831           6 :             poDS->SetMetadata(papszMD_PDS4, "xml:PDS4");
    4832             :         }
    4833             :     }
    4834             : 
    4835          52 :     if (poDS->m_poExternalDS == nullptr)
    4836             :     {
    4837             :         // We don't need to initialize the imagery as we are going to copy it
    4838             :         // completely
    4839          45 :         poDS->m_bMustInitImageFile = false;
    4840             :     }
    4841             : 
    4842          52 :     if (!CPLFetchBool(papszOptions, "CREATE_LABEL_ONLY", false))
    4843             :     {
    4844          45 :         CPLErr eErr = GDALDatasetCopyWholeRaster(poSrcDS, poDS, nullptr,
    4845             :                                                  pfnProgress, pProgressData);
    4846          45 :         poDS->FlushCache(false);
    4847          45 :         if (eErr != CE_None)
    4848             :         {
    4849           1 :             delete poDS;
    4850           1 :             return nullptr;
    4851             :         }
    4852             : 
    4853          44 :         char **papszISIS3MD = poSrcDS->GetMetadata("json:ISIS3");
    4854          44 :         if (papszISIS3MD)
    4855             :         {
    4856           1 :             poDS->SetMetadata(papszISIS3MD, "json:ISIS3");
    4857             :         }
    4858             :     }
    4859             : 
    4860          51 :     return poDS;
    4861             : }
    4862             : 
    4863             : /************************************************************************/
    4864             : /*                             Delete()                                 */
    4865             : /************************************************************************/
    4866             : 
    4867         108 : CPLErr PDS4Dataset::Delete(const char *pszFilename)
    4868             : 
    4869             : {
    4870             :     /* -------------------------------------------------------------------- */
    4871             :     /*      Collect file list.                                              */
    4872             :     /* -------------------------------------------------------------------- */
    4873         216 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    4874             :     auto poDS =
    4875         216 :         std::unique_ptr<PDS4Dataset>(PDS4Dataset::OpenInternal(&oOpenInfo));
    4876         108 :     if (poDS == nullptr)
    4877             :     {
    4878           0 :         if (CPLGetLastErrorNo() == 0)
    4879           0 :             CPLError(CE_Failure, CPLE_OpenFailed,
    4880             :                      "Unable to open %s to obtain file list.", pszFilename);
    4881             : 
    4882           0 :         return CE_Failure;
    4883             :     }
    4884             : 
    4885         108 :     char **papszFileList = poDS->GetFileList();
    4886         216 :     CPLString osImageFilename = poDS->m_osImageFilename;
    4887             :     bool bCreatedFromExistingBinaryFile =
    4888         108 :         poDS->m_bCreatedFromExistingBinaryFile;
    4889             : 
    4890         108 :     poDS.reset();
    4891             : 
    4892         108 :     if (CSLCount(papszFileList) == 0)
    4893             :     {
    4894           0 :         CPLError(CE_Failure, CPLE_NotSupported,
    4895             :                  "Unable to determine files associated with %s, "
    4896             :                  "delete fails.",
    4897             :                  pszFilename);
    4898           0 :         CSLDestroy(papszFileList);
    4899           0 :         return CE_Failure;
    4900             :     }
    4901             : 
    4902             :     /* -------------------------------------------------------------------- */
    4903             :     /*      Delete all files.                                               */
    4904             :     /* -------------------------------------------------------------------- */
    4905         108 :     CPLErr eErr = CE_None;
    4906         388 :     for (int i = 0; papszFileList[i] != nullptr; ++i)
    4907             :     {
    4908         294 :         if (bCreatedFromExistingBinaryFile &&
    4909          14 :             EQUAL(papszFileList[i], osImageFilename))
    4910             :         {
    4911           7 :             continue;
    4912             :         }
    4913         273 :         if (VSIUnlink(papszFileList[i]) != 0)
    4914             :         {
    4915           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Deleting %s failed:\n%s",
    4916           0 :                      papszFileList[i], VSIStrerror(errno));
    4917           0 :             eErr = CE_Failure;
    4918             :         }
    4919             :     }
    4920             : 
    4921         108 :     CSLDestroy(papszFileList);
    4922             : 
    4923         108 :     return eErr;
    4924             : }
    4925             : 
    4926             : /************************************************************************/
    4927             : /*                         GDALRegister_PDS4()                          */
    4928             : /************************************************************************/
    4929             : 
    4930        1682 : void GDALRegister_PDS4()
    4931             : 
    4932             : {
    4933        1682 :     if (GDALGetDriverByName(PDS4_DRIVER_NAME) != nullptr)
    4934         301 :         return;
    4935             : 
    4936        1381 :     GDALDriver *poDriver = new GDALDriver();
    4937        1381 :     PDS4DriverSetCommonMetadata(poDriver);
    4938             : 
    4939        1381 :     poDriver->pfnOpen = PDS4Dataset::Open;
    4940        1381 :     poDriver->pfnCreate = PDS4Dataset::Create;
    4941        1381 :     poDriver->pfnCreateCopy = PDS4Dataset::CreateCopy;
    4942        1381 :     poDriver->pfnDelete = PDS4Dataset::Delete;
    4943             : 
    4944        1381 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    4945             : }

Generated by: LCOV version 1.14