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

Generated by: LCOV version 1.14