LCOV - code coverage report
Current view: top level - frmts/pds - pds4dataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 2093 2356 88.8 %
Date: 2024-05-03 15:49:35 Functions: 76 77 98.7 %

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

Generated by: LCOV version 1.14