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

Generated by: LCOV version 1.14