text: /******************************************************************************
   *
   * Project:  ASI CEOS Translator
   * Purpose:  GDALDataset driver for CEOS translator.
   * Author:   Frank Warmerdam, warmerdam@pobox.com
   *
   ******************************************************************************
   * Copyright (c) 2000, Atlantis Scientific Inc.
   * Copyright (c) 2009-2013, Even Rouault <even dot rouault at mines-paris dot org>
   *
   * Permission is hereby granted, free of charge, to any person obtaining a
   * copy of this software and associated documentation files (the "Software"),
   * to deal in the Software without restriction, including without limitation
   * the rights to use, copy, modify, merge, publish, distribute, sublicense,
   * and/or sell copies of the Software, and to permit persons to whom the
   * Software is furnished to do so, subject to the following conditions:
   *
   * The above copyright notice and this permission notice shall be included
   * in all copies or substantial portions of the Software.
   *
   * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   * DEALINGS IN THE SOFTWARE.
   ****************************************************************************/
  
  #include "ceos.h"
  #include "cpl_string.h"
  #include "gdal_frmts.h"
  #include "gdal_priv.h"
  #include "rawdataset.h"
  #include "ogr_srs_api.h"
  
  CPL_CVSID("$Id$")
  
  static GInt16 CastToGInt16(float val)
  {
      if ( val < -32768.0 )
          val = -32768.0;
  
      if ( val > 32767 )
          val = 32767.0;
  
      return static_cast<GInt16>(val);
  }
  
  static const char * const CeosExtension[][6] = {
  { "vol", "led", "img", "trl", "nul", "ext" },
  { "vol", "lea", "img", "trl", "nul", "ext" },
  { "vol", "led", "img", "tra", "nul", "ext" },
  { "vol", "lea", "img", "tra", "nul", "ext" },
  { "vdf", "slf", "sdf", "stf", "nvd", "ext" },
  
  { "vdf", "ldr", "img", "tra", "nul", "ext2" },
  
  /* Jers from Japan- not sure if this is generalized as much as it could be */
  { "VOLD", "Sarl_01", "Imop_%02d", "Sart_01", "NULL", "base" },
  
  /* Radarsat: basename, not extension */
  { "vdf_dat", "lea_%02d", "dat_%02d", "tra_%02d", "nul_vdf", "base" },
  
  /* Ers-1: basename, not extension */
  { "vdf_dat", "lea_%02d", "dat_%02d", "tra_%02d", "nul_dat", "base" },
  
  /* Ers-2 from Telaviv */
  { "volume", "leader", "image", "trailer", "nul_dat", "whole" },
  
  /* Ers-1 from D-PAF */
  { "VDF", "LF", "SLC", "", "", "ext" },
  
  /* Radarsat-1 per #2051 */
  { "vol", "sarl", "sard", "sart", "nvol", "ext" },
  
  /* end marker */
  { nullptr, nullptr, nullptr, nullptr, nullptr, nullptr }
  };
  
  static int
  ProcessData( VSILFILE *fp, int fileid, CeosSARVolume_t *sar, int max_records,
               vsi_l_offset max_bytes );
  
  static CeosTypeCode_t QuadToTC( int a, int b, int c, int d )
  {
      CeosTypeCode_t   abcd;
  
      abcd.UCharCode.Subtype1 = (unsigned char) a;
      abcd.UCharCode.Type = (unsigned char) b;
      abcd.UCharCode.Subtype2 = (unsigned char) c;
      abcd.UCharCode.Subtype3 = (unsigned char) d;
  
      return abcd;
  }
  
  #define LEADER_DATASET_SUMMARY_TC          QuadToTC( 18, 10, 18, 20 )
  #define LEADER_DATASET_SUMMARY_ERS2_TC     QuadToTC( 10, 10, 31, 20 )
  #define LEADER_RADIOMETRIC_COMPENSATION_TC QuadToTC( 18, 51, 18, 20 )
  #define VOLUME_DESCRIPTOR_RECORD_TC        QuadToTC( 192, 192, 18, 18 )
  #define IMAGE_HEADER_RECORD_TC             QuadToTC( 63, 192, 18, 18 )
  #define LEADER_RADIOMETRIC_DATA_RECORD_TC  QuadToTC( 18, 50, 18, 20 )
  #define LEADER_MAP_PROJ_RECORD_TC          QuadToTC( 10, 20, 31, 20 )
  
  // TODO: recond?
  /* JERS from Japan has MAP_PROJ recond with different identifiers */
  /* see CEOS-SAR-CCT Iss/Rev: 2/0 February 10, 1989 */
  #define LEADER_MAP_PROJ_RECORD_JERS_TC         QuadToTC( 18, 20, 18, 20 )
  
  /* For ERS calibration and incidence angle information */
  #define ERS_GENERAL_FACILITY_DATA_TC  QuadToTC( 10, 200, 31, 50 )
  #define ERS_GENERAL_FACILITY_DATA_ALT_TC QuadToTC( 10, 216, 31, 50 )
  
  #define RSAT_PROC_PARAM_TC QuadToTC( 18, 120, 18, 20 )
  
  /************************************************************************/
  /* ==================================================================== */
  /*                              SAR_CEOSDataset                         */
  /* ==================================================================== */
  /************************************************************************/
  
  class SAR_CEOSRasterBand;
  class CCPRasterBand;
  class PALSARRasterBand;
  
  class SAR_CEOSDataset : public GDALPamDataset
  {
      friend class SAR_CEOSRasterBand;
      friend class CCPRasterBand;
      friend class PALSARRasterBand;
  
      CeosSARVolume_t sVolume;
  
      VSILFILE    *fpImage;
  
      char        **papszTempMD;
  
      int         nGCPCount;
      GDAL_GCP    *pasGCPList;
  
      void        ScanForGCPs();
      void        ScanForMetadata();
      int         ScanForMapProjection();
  
    public:
      SAR_CEOSDataset();
      ~SAR_CEOSDataset() override;
  
      int GetGCPCount() override;
      const char *_GetGCPProjection() override;
      const OGRSpatialReference* GetGCPSpatialRef() const override {
          return GetGCPSpatialRefFromOldGetGCPProjection();
      }
      const GDAL_GCP *GetGCPs() override;
  
      char **GetMetadataDomainList() override;
      char **GetMetadata( const char * pszDomain ) override;
  
      static GDALDataset *Open( GDALOpenInfo * );
  };
  
  /************************************************************************/
  /* ==================================================================== */
  /*                          CCPRasterBand                               */
  /* ==================================================================== */
  /************************************************************************/
  
  class CCPRasterBand : public GDALPamRasterBand
  {
      friend class SAR_CEOSDataset;
  
    public:
      CCPRasterBand( SAR_CEOSDataset *, int, GDALDataType );
  
      CPLErr IReadBlock( int, int, void * ) override;
  };
  
  /************************************************************************/
  /* ==================================================================== */
  /*                        PALSARRasterBand                              */
  /* ==================================================================== */
  /************************************************************************/
  
  class PALSARRasterBand : public GDALPamRasterBand
  {
      friend class SAR_CEOSDataset;
  
    public:
      PALSARRasterBand( SAR_CEOSDataset *, int );
  
      CPLErr IReadBlock( int, int, void * ) override;
  };
  
  /************************************************************************/
  /* ==================================================================== */
  /*                       SAR_CEOSRasterBand                             */
  /* ==================================================================== */
  /************************************************************************/
  
  class SAR_CEOSRasterBand : public GDALPamRasterBand
  {
      friend class SAR_CEOSDataset;
  
    public:
      SAR_CEOSRasterBand( SAR_CEOSDataset *, int, GDALDataType );
  
      CPLErr IReadBlock( int, int, void * ) override;
  };
  
  /************************************************************************/
  /*                         SAR_CEOSRasterBand()                         */
  /************************************************************************/
  
  SAR_CEOSRasterBand::SAR_CEOSRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn,
                                          GDALDataType eType )
  
  {
      poDS = poGDSIn;
      nBand = nBandIn;
  
      eDataType = eType;
  
      nBlockXSize = poGDSIn->nRasterXSize;
      nBlockYSize = 1;
  }
  
  /************************************************************************/
  /*                             IReadBlock()                             */
  /************************************************************************/
  
  CPLErr SAR_CEOSRasterBand::IReadBlock( int /* nBlockXOff */,
                                         int nBlockYOff,
                                         void * pImage )
  {
      SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;
  
      struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);
  
      int offset;
      CalcCeosSARImageFilePosition( &(poGDS->sVolume), nBand,
                                    nBlockYOff + 1, nullptr, &offset );
  
      offset += ImageDesc->ImageDataStart;
  
  /* -------------------------------------------------------------------- */
  /*      Load all the pixel data associated with this scanline.          */
  /*      Ensure we handle multiple record scanlines properly.            */
  /* -------------------------------------------------------------------- */
      int nPixelsRead = 0;
  
      GByte *pabyRecord
          = (GByte *) CPLMalloc( ImageDesc->BytesPerPixel * nBlockXSize );
  
      for( int iRecord = 0; iRecord < ImageDesc->RecordsPerLine; iRecord++ )
      {
          int nPixelsToRead;
  
          if( nPixelsRead + ImageDesc->PixelsPerRecord > nBlockXSize )
              nPixelsToRead = nBlockXSize - nPixelsRead;
          else
              nPixelsToRead = ImageDesc->PixelsPerRecord;
  
          CPL_IGNORE_RET_VAL(VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ));
          CPL_IGNORE_RET_VAL(VSIFReadL( pabyRecord + nPixelsRead * ImageDesc->BytesPerPixel,
                     1, nPixelsToRead * ImageDesc->BytesPerPixel,
                     poGDS->fpImage ));
  
          nPixelsRead += nPixelsToRead;
          offset += ImageDesc->BytesPerRecord;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Copy the desired band out based on the size of the type, and    */
  /*      the interleaving mode.                                          */
  /* -------------------------------------------------------------------- */
      const int nBytesPerSample = GDALGetDataTypeSize( eDataType ) / 8;
  
      if( ImageDesc->ChannelInterleaving == CEOS_IL_PIXEL )
      {
          GDALCopyWords( pabyRecord + (nBand-1) * nBytesPerSample,
                         eDataType, ImageDesc->BytesPerPixel,
                         pImage, eDataType, nBytesPerSample,
                         nBlockXSize );
      }
      else if( ImageDesc->ChannelInterleaving == CEOS_IL_LINE )
      {
          GDALCopyWords( pabyRecord + (nBand-1) * nBytesPerSample * nBlockXSize,
                         eDataType, nBytesPerSample,
                         pImage, eDataType, nBytesPerSample,
                         nBlockXSize );
      }
      else if( ImageDesc->ChannelInterleaving == CEOS_IL_BAND )
      {
          memcpy( pImage, pabyRecord, nBytesPerSample * nBlockXSize );
      }
  
  #ifdef CPL_LSB
      GDALSwapWords( pImage, nBytesPerSample, nBlockXSize, nBytesPerSample );
  #endif
  
      CPLFree( pabyRecord );
  
      return CE_None;
  }
  
  /************************************************************************/
  /* ==================================================================== */
  /*                              CCPRasterBand                           */
  /* ==================================================================== */
  /************************************************************************/
  
  /************************************************************************/
  /*                           CCPRasterBand()                            */
  /************************************************************************/
  
  CCPRasterBand::CCPRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn,
                                GDALDataType eType )
  
  {
      poDS = poGDSIn;
      nBand = nBandIn;
  
      eDataType = eType;
  
      nBlockXSize = poGDSIn->nRasterXSize;
      nBlockYSize = 1;
  
      if( nBand == 1 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "HH" );
      else if( nBand == 2 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "HV" );
      else if( nBand == 3 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "VH" );
      else if( nBand == 4 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "VV" );
  }
  
  /************************************************************************/
  /*                             IReadBlock()                             */
  /************************************************************************/
  
  /* From: http://southport.jpl.nasa.gov/software/dcomp/dcomp.html
  
  ysca = sqrt{ [ (Byte(2) / 254 ) + 1.5] 2Byte(1) }
  
  Re(SHH) = byte(3) ysca/127
  
  Im(SHH) = byte(4) ysca/127
  
  Re(SHV) = byte(5) ysca/127
  
  Im(SHV) = byte(6) ysca/127
  
  Re(SVH) = byte(7) ysca/127
  
  Im(SVH) = byte(8) ysca/127
  
  Re(SVV) = byte(9) ysca/127
  
  Im(SVV) = byte(10) ysca/127
  
  */
  
  CPLErr CCPRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
                                    int nBlockYOff,
                                    void * pImage )
  {
      SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;
  
      struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);
  
      int offset = ImageDesc->FileDescriptorLength
          + ImageDesc->BytesPerRecord * nBlockYOff
          + ImageDesc->ImageDataStart;
  
  /* -------------------------------------------------------------------- */
  /*      Load all the pixel data associated with this scanline.          */
  /* -------------------------------------------------------------------- */
      const int nBytesToRead = ImageDesc->BytesPerPixel * nBlockXSize;
  
      GByte *pabyRecord = (GByte *) CPLMalloc( nBytesToRead );
  
      if( VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ) != 0
          || (int) VSIFReadL( pabyRecord, 1, nBytesToRead,
                             poGDS->fpImage ) != nBytesToRead )
      {
          CPLError( CE_Failure, CPLE_FileIO,
                    "Error reading %d bytes of CEOS record data at offset %d.\n"
                    "Reading file %s failed.",
                    nBytesToRead, offset, poGDS->GetDescription() );
          CPLFree( pabyRecord );
          return CE_Failure;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Initialize our power table if this is our first time through.   */
  /* -------------------------------------------------------------------- */
      static float afPowTable[256];
      static bool bPowTableInitialized = false;
  
      if( !bPowTableInitialized )
      {
          bPowTableInitialized = true;
  
          for( int i = 0; i < 256; i++ )
          {
              afPowTable[i] = (float)pow( 2.0, i-128 );
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      Copy the desired band out based on the size of the type, and    */
  /*      the interleaving mode.                                          */
  /* -------------------------------------------------------------------- */
      for( int iX = 0; iX < nBlockXSize; iX++ )
      {
          unsigned char *pabyGroup = pabyRecord + iX * ImageDesc->BytesPerPixel;
          signed char *Byte = (signed char*)pabyGroup-1; /* A ones based alias */
          double dfReSHH, dfImSHH, dfReSHV, dfImSHV,
              dfReSVH, dfImSVH, dfReSVV, dfImSVV;
  
          const double dfScale = sqrt( (Byte[2] / 254.0 + 1.5)
                                       * afPowTable[Byte[1] + 128] );
  
          if( nBand == 1 )
          {
              dfReSHH = Byte[3] * dfScale / 127.0;
              dfImSHH = Byte[4] * dfScale / 127.0;
  
              ((float *) pImage)[iX*2  ] = (float)dfReSHH;
              ((float *) pImage)[iX*2+1] = (float)dfImSHH;
          }
          else if( nBand == 2 )
          {
              dfReSHV = Byte[5] * dfScale / 127.0;
              dfImSHV = Byte[6] * dfScale / 127.0;
  
              ((float *) pImage)[iX*2  ] = (float)dfReSHV;
              ((float *) pImage)[iX*2+1] = (float)dfImSHV;
          }
          else if( nBand == 3 )
          {
              dfReSVH = Byte[7] * dfScale / 127.0;
              dfImSVH = Byte[8] * dfScale / 127.0;
  
              ((float *) pImage)[iX*2  ] = (float)dfReSVH;
              ((float *) pImage)[iX*2+1] = (float)dfImSVH;
          }
          else if( nBand == 4 )
          {
              dfReSVV = Byte[9] * dfScale / 127.0;
              dfImSVV = Byte[10]* dfScale / 127.0;
  
              ((float *) pImage)[iX*2  ] = (float)dfReSVV;
              ((float *) pImage)[iX*2+1] = (float)dfImSVV;
          }
      }
  
      CPLFree( pabyRecord );
  
      return CE_None;
  }
  
  /************************************************************************/
  /* ==================================================================== */
  /*                            PALSARRasterBand                          */
  /* ==================================================================== */
  /************************************************************************/
  
  /************************************************************************/
  /*                           PALSARRasterBand()                         */
  /************************************************************************/
  
  PALSARRasterBand::PALSARRasterBand( SAR_CEOSDataset *poGDSIn, int nBandIn )
  
  {
      poDS = poGDSIn;
      nBand = nBandIn;
  
      eDataType = GDT_CInt16;
  
      nBlockXSize = poGDSIn->nRasterXSize;
      nBlockYSize = 1;
  
      if( nBand == 1 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_11" );
      else if( nBand == 2 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_22" );
      else if( nBand == 3 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_33" );
      else if( nBand == 4 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_12" );
      else if( nBand == 5 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_13" );
      else if( nBand == 6 )
          SetMetadataItem( "POLARIMETRIC_INTERP", "Covariance_23" );
  }
  
  /************************************************************************/
  /*                             IReadBlock()                             */
  /*                                                                      */
  /*      Based on ERSDAC-VX-CEOS-004                                     */
  /************************************************************************/
  
  CPLErr PALSARRasterBand::IReadBlock( int /* nBlockXOff */,
                                       int nBlockYOff,
                                       void * pImage )
  {
      SAR_CEOSDataset *poGDS = (SAR_CEOSDataset *) poDS;
  
      struct CeosSARImageDesc *ImageDesc = &(poGDS->sVolume.ImageDesc);
  
      int offset = ImageDesc->FileDescriptorLength
          + ImageDesc->BytesPerRecord * nBlockYOff
          + ImageDesc->ImageDataStart;
  
  /* -------------------------------------------------------------------- */
  /*      Load all the pixel data associated with this scanline.          */
  /* -------------------------------------------------------------------- */
      const int nBytesToRead = ImageDesc->BytesPerPixel * nBlockXSize;
  
      GByte  *pabyRecord = (GByte *) CPLMalloc( nBytesToRead );
  
      if( VSIFSeekL( poGDS->fpImage, offset, SEEK_SET ) != 0
          || (int) VSIFReadL( pabyRecord, 1, nBytesToRead,
                             poGDS->fpImage ) != nBytesToRead )
      {
          CPLError( CE_Failure, CPLE_FileIO,
                    "Error reading %d bytes of CEOS record data at offset %d.\n"
                    "Reading file %s failed.",
                    nBytesToRead, offset, poGDS->GetDescription() );
          CPLFree( pabyRecord );
          return CE_Failure;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Copy the desired band out based on the size of the type, and    */
  /*      the interleaving mode.                                          */
  /* -------------------------------------------------------------------- */
      if( nBand == 1 || nBand == 2 || nBand == 3 )
      {
          // we need to pre-initialize things to set the imaginary component to 0
          memset( pImage, 0, nBlockXSize * 4 );
  
          GDALCopyWords( pabyRecord + 4*(nBand - 1), GDT_Int16, 18,
                         pImage, GDT_Int16, 4,
                         nBlockXSize );
  #ifdef CPL_LSB
          GDALSwapWords( pImage, 2, nBlockXSize, 4 );
  #endif
      }
      else
      {
          GDALCopyWords( pabyRecord + 6 + 4*(nBand - 4), GDT_CInt16, 18,
                         pImage, GDT_CInt16, 4,
                         nBlockXSize );
  #ifdef CPL_LSB
          GDALSwapWords( pImage, 2, nBlockXSize*2, 2 );
  #endif
      }
      CPLFree( pabyRecord );
  
  /* -------------------------------------------------------------------- */
  /*      Convert the values into covariance form as per:                 */
  /* -------------------------------------------------------------------- */
  /*
  ** 1) PALSAR- adjust so that it reads bands as a covariance matrix, and
  ** set polarimetric interpretation accordingly:
  **
  ** Covariance_11=HH*conj(HH): already there
  ** Covariance_22=2*HV*conj(HV): need a factor of 2
  ** Covariance_33=VV*conj(VV): already there
  ** Covariance_12=sqrt(2)*HH*conj(HV): need the sqrt(2) factor
  ** Covariance_13=HH*conj(VV): already there
  ** Covariance_23=sqrt(2)*HV*conj(VV): need to take the conjugate, then
  **               multiply by sqrt(2)
  **
  */
  
      if( nBand == 2 )
      {
          GInt16 *panLine = (GInt16 *) pImage;
  
          for( int i = 0; i < nBlockXSize * 2; i++ )
          {
            panLine[i] = (GInt16) CastToGInt16((float)2.0 * panLine[i]);
          }
      }
      else if( nBand == 4 )
      {
          const double sqrt_2 = pow(2.0,0.5);
          GInt16 *panLine = (GInt16 *) pImage;
  
          for( int i = 0; i < nBlockXSize * 2; i++ )
          {
            panLine[i] = (GInt16) CastToGInt16((float)floor(panLine[i] * sqrt_2 + 0.5));
          }
      }
      else if( nBand == 6 )
      {
          GInt16 *panLine = (GInt16 *) pImage;
          const double sqrt_2 = pow(2.0,0.5);
  
          // real portion - just multiple by sqrt(2)
          for( int i = 0; i < nBlockXSize * 2; i += 2 )
          {
            panLine[i] = (GInt16) CastToGInt16((float)floor(panLine[i] * sqrt_2 + 0.5));
          }
  
          // imaginary portion - conjugate and multiply
          for( int i = 1; i < nBlockXSize * 2; i += 2 )
          {
            panLine[i] = (GInt16) CastToGInt16((float)floor(-panLine[i] * sqrt_2 + 0.5));
          }
      }
  
      return CE_None;
  }
  
  /************************************************************************/
  /* ==================================================================== */
  /*                              SAR_CEOSDataset                         */
  /* ==================================================================== */
  /************************************************************************/
  
  /************************************************************************/
  /*                          SAR_CEOSDataset()                           */
  /************************************************************************/
  
  SAR_CEOSDataset::SAR_CEOSDataset() :
      fpImage(nullptr),
      papszTempMD(nullptr),
      nGCPCount(0),
      pasGCPList(nullptr)
  {
      sVolume.Flavor = 0;
      sVolume.Sensor = 0;
      sVolume.ProductType = 0;
      sVolume.FileNamingConvention = 0;
  
      sVolume.VolumeDirectoryFile = 0;
      sVolume.SARLeaderFile = 0;
      sVolume.ImagryOptionsFile = 0;
      sVolume.SARTrailerFile = 0;
      sVolume.NullVolumeDirectoryFile = 0;
  
      sVolume.ImageDesc.ImageDescValid = 0;
      sVolume.ImageDesc.NumChannels = 0;
      sVolume.ImageDesc.ChannelInterleaving = 0;
      sVolume.ImageDesc.DataType = 0;
      sVolume.ImageDesc.BytesPerRecord = 0;
      sVolume.ImageDesc.Lines = 0;
      sVolume.ImageDesc.TopBorderPixels = 0;
      sVolume.ImageDesc.BottomBorderPixels = 0;
      sVolume.ImageDesc.PixelsPerLine = 0;
      sVolume.ImageDesc.LeftBorderPixels = 0;
      sVolume.ImageDesc.RightBorderPixels = 0;
      sVolume.ImageDesc.BytesPerPixel = 0;
      sVolume.ImageDesc.RecordsPerLine = 0;
      sVolume.ImageDesc.PixelsPerRecord = 0;
      sVolume.ImageDesc.ImageDataStart = 0;
      sVolume.ImageDesc.ImageSuffixData = 0;
      sVolume.ImageDesc.FileDescriptorLength = 0;
      sVolume.ImageDesc.PixelOrder = 0;
      sVolume.ImageDesc.LineOrder = 0;
      sVolume.ImageDesc.PixelDataBytesPerRecord = 0;
  
      sVolume.RecordList = nullptr;
  }
  
  /************************************************************************/
  /*                          ~SAR_CEOSDataset()                          */
  /************************************************************************/
  
  SAR_CEOSDataset::~SAR_CEOSDataset()
  
  {
      FlushCache();
  
      CSLDestroy( papszTempMD );
  
      if( fpImage != nullptr )
          CPL_IGNORE_RET_VAL(VSIFCloseL( fpImage ));
  
      if( nGCPCount > 0 )
      {
          GDALDeinitGCPs( nGCPCount, pasGCPList );
          CPLFree( pasGCPList );
      }
  
      if( sVolume.RecordList )
      {
          for(Link_t *Links = sVolume.RecordList;
              Links != nullptr;
              Links = Links->next)
          {
              if(Links->object)
              {
                  DeleteCeosRecord( (CeosRecord_t *) Links->object );
                  Links->object = nullptr;
              }
          }
          DestroyList( sVolume.RecordList );
      }
      FreeRecipes();
  }
  
  /************************************************************************/
  /*                            GetGCPCount()                             */
  /************************************************************************/
  
  int SAR_CEOSDataset::GetGCPCount()
  
  {
      return nGCPCount;
  }
  
  /************************************************************************/
  /*                          GetGCPProjection()                          */
  /************************************************************************/
  
  const char *SAR_CEOSDataset::_GetGCPProjection()
  
  {
      if( nGCPCount > 0 )
          return SRS_WKT_WGS84_LAT_LONG;
  
      return "";
  }
  
  /************************************************************************/
  /*                               GetGCP()                               */
  /************************************************************************/
  
  const GDAL_GCP *SAR_CEOSDataset::GetGCPs()
  
  {
      return pasGCPList;
  }
  
  /************************************************************************/
  /*                      GetMetadataDomainList()                         */
  /************************************************************************/
  
  char **SAR_CEOSDataset::GetMetadataDomainList()
  {
      return CSLAddString(GDALDataset::GetMetadataDomainList(), "ceos-FFF-n-n-n-n:r");
  }
  
  /************************************************************************/
  /*                            GetMetadata()                             */
  /*                                                                      */
  /*      We provide our own GetMetadata() so that we can override        */
  /*      behavior for some very specialized domain names intended to     */
  /*      give us access to raw record data.                              */
  /*                                                                      */
  /*      The domain must look like:                                      */
  /*        ceos-FFF-n-n-n-n:r                                            */
  /*                                                                      */
  /*        FFF - The file id - one of vol, lea, img, trl or nul.         */
  /*        n-n-n-n - the record type code such as 18-10-18-20 for the    */
  /*        dataset summary record in the leader file.                    */
  /*        :r - The zero based record number to fetch (optional)         */
  /*                                                                      */
  /*      Note that only records that are pre-loaded will be              */
  /*      accessible, and this normally means that most image records     */
  /*      are not available.                                              */
  /************************************************************************/
  
  char **SAR_CEOSDataset::GetMetadata( const char * pszDomain )
  
  {
      if( pszDomain == nullptr || !STARTS_WITH_CI(pszDomain, "ceos-") )
          return GDALDataset::GetMetadata( pszDomain );
  
  /* -------------------------------------------------------------------- */
  /*      Identify which file to fetch the file from.                     */
  /* -------------------------------------------------------------------- */
      int nFileId = -1;
  
      if( STARTS_WITH_CI(pszDomain, "ceos-vol") )
      {
          nFileId = CEOS_VOLUME_DIR_FILE;
      }
      else if( STARTS_WITH_CI(pszDomain, "ceos-lea") )
      {
          nFileId = CEOS_LEADER_FILE;
      }
      else if( STARTS_WITH_CI(pszDomain, "ceos-img") )
      {
          nFileId = CEOS_IMAGRY_OPT_FILE;
      }
      else if( STARTS_WITH_CI(pszDomain, "ceos-trl") )
      {
          nFileId = CEOS_TRAILER_FILE;
      }
      else if( STARTS_WITH_CI(pszDomain, "ceos-nul") )
      {
          nFileId = CEOS_NULL_VOL_FILE;
      }
      else
          return nullptr;
  
      pszDomain += 8;
  
  /* -------------------------------------------------------------------- */
  /*      Identify the record type.                                       */
  /* -------------------------------------------------------------------- */
      int  a, b, c, d, nRecordIndex = -1;
  
      if( sscanf( pszDomain, "-%d-%d-%d-%d:%d",
                  &a, &b, &c, &d, &nRecordIndex ) != 5
          && sscanf( pszDomain, "-%d-%d-%d-%d",
                     &a, &b, &c, &d ) != 4 )
      {
          return nullptr;
      }
  
      CeosTypeCode_t sTypeCode = QuadToTC( a, b, c, d );
  
  /* -------------------------------------------------------------------- */
  /*      Try to fetch the record.                                        */
  /* -------------------------------------------------------------------- */
      CeosRecord_t *record =
          FindCeosRecord( sVolume.RecordList, sTypeCode, nFileId,
                          -1, nRecordIndex );
  
      if( record == nullptr )
          return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Massage the data into a safe textual format.  The RawRecord     */
  /*      just has zero bytes turned into spaces while the                */
  /*      EscapedRecord has regular backslash escaping applied to zero    */
  /*      chars, double quotes, and backslashes.                          */
  /*      just turn zero bytes into spaces.                               */
  /* -------------------------------------------------------------------- */
  
      CSLDestroy( papszTempMD );
  
      // Escaped version
      char *pszSafeCopy = CPLEscapeString( (char *) record->Buffer,
                                           record->Length,
                                           CPLES_BackslashQuotable );
      papszTempMD = CSLSetNameValue( nullptr, "EscapedRecord", pszSafeCopy );
      CPLFree( pszSafeCopy );
  
      // Copy with '\0' replaced by spaces.
  
      pszSafeCopy = (char *) CPLCalloc(1,record->Length+1);
      memcpy( pszSafeCopy, record->Buffer, record->Length );
  
      for( int i = 0; i < record->Length; i++ )
          if( pszSafeCopy[i] == '\0' )
              pszSafeCopy[i] = ' ';
  
      papszTempMD = CSLSetNameValue( papszTempMD, "RawRecord", pszSafeCopy );
  
      CPLFree( pszSafeCopy );
  
      return papszTempMD;
  }
  
  /************************************************************************/
  /*                          ScanForMetadata()                           */
  /************************************************************************/
  
  void SAR_CEOSDataset::ScanForMetadata()
  
  {
  /* -------------------------------------------------------------------- */
  /*      Get the volume id (with the sensor name)                        */
  /* -------------------------------------------------------------------- */
      CeosRecord_t *record
          = FindCeosRecord( sVolume.RecordList, VOLUME_DESCRIPTOR_RECORD_TC,
                            CEOS_VOLUME_DIR_FILE, -1, -1 );
  
      char szVolId[128];
      szVolId[0] = '\0';
      char szField[128];
      szField[0] = '\0';
      if( record != nullptr )
      {
          szVolId[16] = '\0';
  
          GetCeosField( record, 61, "A16", szVolId );
  
          SetMetadataItem( "CEOS_LOGICAL_VOLUME_ID", szVolId );
  
  /* -------------------------------------------------------------------- */
  /*      Processing facility                                             */
  /* -------------------------------------------------------------------- */
          szField[0] = '\0';
          szField[12] = '\0';
  
          GetCeosField( record, 149, "A12", szField );
  
          if( !STARTS_WITH_CI(szField, "            ") )
              SetMetadataItem( "CEOS_PROCESSING_FACILITY", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Agency                                                          */
  /* -------------------------------------------------------------------- */
          szField[8] = '\0';
  
          GetCeosField( record, 141, "A8", szField );
  
          if( !STARTS_WITH_CI(szField, "            ") )
              SetMetadataItem( "CEOS_PROCESSING_AGENCY", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Country                                                         */
  /* -------------------------------------------------------------------- */
          szField[12] = '\0';
  
          GetCeosField( record, 129, "A12", szField );
  
          if( !STARTS_WITH_CI(szField, "            ") )
              SetMetadataItem( "CEOS_PROCESSING_COUNTRY", szField );
  
  /* -------------------------------------------------------------------- */
  /*      software id.                                                    */
  /* -------------------------------------------------------------------- */
          szField[12] = '\0';
  
          GetCeosField( record, 33, "A12", szField );
  
          if( !STARTS_WITH_CI(szField, "            ") )
              SetMetadataItem( "CEOS_SOFTWARE_ID", szField );
  
  /* -------------------------------------------------------------------- */
  /*      product identifier.                                                    */
  /* -------------------------------------------------------------------- */
          szField[8] = '\0';
  
          GetCeosField( record, 261, "A8", szField );
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_PRODUCT_ID", szField );
  
  /* -------------------------------------------------------------------- */
  /*      volume identifier.                                                    */
  /* -------------------------------------------------------------------- */
          szField[16] = '\0';
  
          GetCeosField( record, 77, "A16", szField );
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_VOLSET_ID", szField );
      }
  
  /* ==================================================================== */
  /*      Dataset summary record.                                         */
  /* ==================================================================== */
      record = FindCeosRecord( sVolume.RecordList, LEADER_DATASET_SUMMARY_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList, LEADER_DATASET_SUMMARY_TC,
                                   CEOS_TRAILER_FILE, -1, -1 );
  
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList,
                                   LEADER_DATASET_SUMMARY_ERS2_TC,
                                   CEOS_LEADER_FILE, -1, -1 );
  
      if( record != nullptr )
      {
  /* -------------------------------------------------------------------- */
  /*      Get the acquisition date.                                       */
  /* -------------------------------------------------------------------- */
          szField[0] = '\0';
          szField[32] = '\0';
  
          GetCeosField( record, 69, "A32", szField );
  
          SetMetadataItem( "CEOS_ACQUISITION_TIME", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Ascending/Descending                                            */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 101, "A16", szField );
          szField[16] = '\0';
  
          if( strstr(szVolId,"RSAT") != nullptr
              && !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_ASC_DES", szField );
  
  /* -------------------------------------------------------------------- */
  /*      True heading - at least for ERS2.                               */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 149, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_TRUE_HEADING", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Ellipsoid                                                       */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 165, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_ELLIPSOID", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Semimajor, semiminor axis                                       */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 181, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_SEMI_MAJOR", szField );
  
          GetCeosField( record, 197, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_SEMI_MINOR", szField );
  
  /* -------------------------------------------------------------------- */
  /*      SCENE LENGTH KM                                                 */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 341, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_SCENE_LENGTH_KM", szField );
  
  /* -------------------------------------------------------------------- */
  /*      SCENE WIDTH KM                                                  */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 357, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_SCENE_WIDTH_KM", szField );
  
  /* -------------------------------------------------------------------- */
  /*      MISSION ID                                                      */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 397, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_MISSION_ID", szField );
  
  /* -------------------------------------------------------------------- */
  /*      SENSOR ID                                                      */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 413, "A32", szField );
          szField[32] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                                ") )
              SetMetadataItem( "CEOS_SENSOR_ID", szField );
  
  /* -------------------------------------------------------------------- */
  /*      ORBIT NUMBER                                                    */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 445, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_ORBIT_NUMBER", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Platform latitude                                               */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 453, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_PLATFORM_LATITUDE", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Platform longitude                                               */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 461, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_PLATFORM_LONGITUDE", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Platform heading - at least for ERS2.                           */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 469, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_PLATFORM_HEADING", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Look Angle.                                                     */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 477, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_SENSOR_CLOCK_ANGLE", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Incidence angle                                                 */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 485, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_INC_ANGLE", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Pixel time direction indicator                                  */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 1527, "A8", szField );
          szField[8] = '\0';
  
          if( !STARTS_WITH_CI(szField, "        ") )
              SetMetadataItem( "CEOS_PIXEL_TIME_DIR", szField );
  
  /* -------------------------------------------------------------------- */
  /*      Line spacing                                                    */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 1687, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_LINE_SPACING_METERS", szField );
  /* -------------------------------------------------------------------- */
  /*      Pixel spacing                                                    */
  /* -------------------------------------------------------------------- */
          GetCeosField( record, 1703, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_PIXEL_SPACING_METERS", szField );
      }
  
  /* -------------------------------------------------------------------- */
  /*      Get the beam mode, for radarsat.                                */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList,
                               LEADER_RADIOMETRIC_COMPENSATION_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( strstr(szVolId,"RSAT") != nullptr && record != nullptr )
      {
          szField[16] = '\0';
  
          GetCeosField( record, 4189, "A16", szField );
  
          SetMetadataItem( "CEOS_BEAM_TYPE", szField );
      }
  
  /* ==================================================================== */
  /*      ERS calibration and incidence angle info                        */
  /* ==================================================================== */
      record = FindCeosRecord( sVolume.RecordList, ERS_GENERAL_FACILITY_DATA_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList,
                                   ERS_GENERAL_FACILITY_DATA_ALT_TC,
                                   CEOS_LEADER_FILE, -1, -1 );
  
      if( record != nullptr )
      {
          GetCeosField( record, 13 , "A64", szField );
          szField[64] = '\0';
  
          /* Avoid PCS records, which don't contain necessary info */
          if( strstr( szField, "GENERAL") == nullptr )
              record = nullptr;
      }
  
      if( record != nullptr )
      {
          GetCeosField( record, 583 , "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_INC_ANGLE_FIRST_RANGE", szField );
  
          GetCeosField( record, 599 , "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_INC_ANGLE_CENTRE_RANGE", szField );
  
          GetCeosField( record, 615, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_INC_ANGLE_LAST_RANGE", szField );
  
          GetCeosField( record, 663, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_CALIBRATION_CONSTANT_K", szField );
  
          GetCeosField( record, 1855, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C0", szField );
  
          GetCeosField( record, 1875, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C1", szField );
  
          GetCeosField( record, 1895, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C2", szField );
  
          GetCeosField( record, 1915, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C3", szField );
      }
  /* -------------------------------------------------------------------- */
  /*      Detailed Processing Parameters (Radarsat)                       */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList, RSAT_PROC_PARAM_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList, RSAT_PROC_PARAM_TC,
                               CEOS_TRAILER_FILE, -1, -1 );
  
      if( record != nullptr )
      {
          GetCeosField( record, 192, "A21", szField );
          szField[21] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                     ") )
              SetMetadataItem( "CEOS_PROC_START", szField );
  
          GetCeosField( record, 213, "A21", szField );
          szField[21] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                     ") )
              SetMetadataItem( "CEOS_PROC_STOP", szField );
  
          GetCeosField( record, 4649, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_0", szField );
  
          GetCeosField( record, 4665, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_1", szField );
  
          GetCeosField( record, 4681, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_2", szField );
  
          GetCeosField( record, 4697, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_3", szField );
  
          GetCeosField( record, 4713, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_4", szField );
  
          GetCeosField( record, 4729, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_5", szField );
  
          GetCeosField( record, 4745, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_EPH_ORB_DATA_6", szField );
  
          GetCeosField( record, 4908, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C0", szField );
  
          GetCeosField( record, 4924, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C1", szField );
  
          GetCeosField( record, 4940, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C2", szField );
  
          GetCeosField( record, 4956, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C3", szField );
  
          GetCeosField( record, 4972, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C4", szField );
  
          GetCeosField( record, 4988, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_GROUND_TO_SLANT_C5", szField );
  
          GetCeosField( record, 7334, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_INC_ANGLE_FIRST_RANGE", szField );
  
          GetCeosField( record, 7350, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_INC_ANGLE_LAST_RANGE", szField );
      }
  /* -------------------------------------------------------------------- */
  /*      Get process-to-raw data coordinate translation values.  These   */
  /*      are likely specific to Atlantis APP products.                   */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList,
                               IMAGE_HEADER_RECORD_TC,
                               CEOS_IMAGRY_OPT_FILE, -1, -1 );
  
      if( record != nullptr )
      {
          GetCeosField( record, 449, "A4", szField );
          szField[4] = '\0';
  
          if( !STARTS_WITH_CI(szField, "    ") )
              SetMetadataItem( "CEOS_DM_CORNER", szField );
  
          GetCeosField( record, 453, "A4", szField );
          szField[4] = '\0';
  
          if( !STARTS_WITH_CI(szField, "    ") )
              SetMetadataItem( "CEOS_DM_TRANSPOSE", szField );
  
          GetCeosField( record, 457, "A4", szField );
          szField[4] = '\0';
  
          if( !STARTS_WITH_CI(szField, "    ") )
              SetMetadataItem( "CEOS_DM_START_SAMPLE", szField );
  
          GetCeosField( record, 461, "A5", szField );
          szField[5] = '\0';
  
          if( !STARTS_WITH_CI(szField, "     ") )
              SetMetadataItem( "CEOS_DM_START_PULSE", szField );
  
          GetCeosField( record, 466, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_DM_FAST_ALPHA", szField );
  
          GetCeosField( record, 482, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_DM_FAST_BETA", szField );
  
          GetCeosField( record, 498, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_DM_SLOW_ALPHA", szField );
  
          GetCeosField( record, 514, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_DM_SLOW_BETA", szField );
  
          GetCeosField( record, 530, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_DM_FAST_ALPHA_2", szField );
      }
  
  /* -------------------------------------------------------------------- */
  /*      Try to find calibration information from Radiometric Data       */
  /*      Record.                                                         */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList,
                               LEADER_RADIOMETRIC_DATA_RECORD_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList,
                               LEADER_RADIOMETRIC_DATA_RECORD_TC,
                               CEOS_TRAILER_FILE, -1, -1 );
  
      if( record != nullptr )
      {
          GetCeosField( record, 8317, "A16", szField );
          szField[16] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                ") )
              SetMetadataItem( "CEOS_CALIBRATION_OFFSET", szField );
      }
  
  /* -------------------------------------------------------------------- */
  /*      For ERS Standard Format Landsat scenes we pick up the           */
  /*      calibration offset and gain from the Radiometric Ancillary      */
  /*      Record.                                                         */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList,
                               QuadToTC( 0x3f, 0x24, 0x12, 0x09 ),
                               CEOS_LEADER_FILE, -1, -1 );
      if( record != nullptr )
      {
          GetCeosField( record, 29, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_OFFSET_A0", szField );
  
          GetCeosField( record, 49, "A20", szField );
          szField[20] = '\0';
  
          if( !STARTS_WITH_CI(szField, "                    ") )
              SetMetadataItem( "CEOS_GAIN_A1", szField );
      }
  
  /* -------------------------------------------------------------------- */
  /*      For ERS Standard Format Landsat scenes we pick up the           */
  /*      gain setting from the Scene Header Record.                      */
  /* -------------------------------------------------------------------- */
      record = FindCeosRecord( sVolume.RecordList,
                               QuadToTC( 0x12, 0x12, 0x12, 0x09 ),
                               CEOS_LEADER_FILE, -1, -1 );
      if( record != nullptr )
      {
          GetCeosField( record, 1486, "A1", szField );
          szField[1] = '\0';
  
          if( szField[0] == 'H' || szField[0] == 'V' )
              SetMetadataItem( "CEOS_GAIN_SETTING", szField );
      }
  }
  
  /************************************************************************/
  /*                        ScanForMapProjection()                        */
  /*                                                                      */
  /*      Try to find a map projection record, and read corner points     */
  /*      from it.  This has only been tested with ERS products.          */
  /************************************************************************/
  
  int SAR_CEOSDataset::ScanForMapProjection()
  
  {
  /* -------------------------------------------------------------------- */
  /*      Find record, and try to determine if it has useful GCPs.        */
  /* -------------------------------------------------------------------- */
  
      CeosRecord_t *record = FindCeosRecord( sVolume.RecordList,
                                             LEADER_MAP_PROJ_RECORD_TC,
                                             CEOS_LEADER_FILE, -1, -1 );
  
      /* JERS from Japan */
      if( record == nullptr )
          record = FindCeosRecord( sVolume.RecordList,
                               LEADER_MAP_PROJ_RECORD_JERS_TC,
                               CEOS_LEADER_FILE, -1, -1 );
  
      if( record == nullptr )
          return FALSE;
  
      char szField[100];
      memset( szField, 0, 17 );
      GetCeosField( record, 29, "A16", szField );
  
      if( !STARTS_WITH_CI(szField, "Slant Range") && !STARTS_WITH_CI(szField, "Ground Range")
          && !STARTS_WITH_CI(szField, "GEOCODED") )
          return FALSE;
  
      GetCeosField( record, 1073, "A16", szField );
      if( STARTS_WITH_CI(szField, "        ") )
          return FALSE;
  
  /* -------------------------------------------------------------------- */
  /*      Read corner points.                                             */
  /* -------------------------------------------------------------------- */
      nGCPCount = 4;
      pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nGCPCount);
  
      GDALInitGCPs( nGCPCount, pasGCPList );
  
      for( int i = 0; i < nGCPCount; i++ )
      {
          char         szId[32];
  
          snprintf( szId, sizeof(szId), "%d", i+1 );
          pasGCPList[i].pszId = CPLStrdup( szId );
  
          GetCeosField( record, 1073+32*i, "A16", szField );
          pasGCPList[i].dfGCPY = CPLAtof(szField);
          GetCeosField( record, 1089+32*i, "A16", szField );
          pasGCPList[i].dfGCPX = CPLAtof(szField);
          pasGCPList[i].dfGCPZ = 0.0;
      }
  
      pasGCPList[0].dfGCPLine = 0.5;
      pasGCPList[0].dfGCPPixel = 0.5;
  
      pasGCPList[1].dfGCPLine = 0.5;
      pasGCPList[1].dfGCPPixel = nRasterXSize-0.5;
  
      pasGCPList[2].dfGCPLine = nRasterYSize-0.5;
      pasGCPList[2].dfGCPPixel = nRasterXSize-0.5;
  
      pasGCPList[3].dfGCPLine = nRasterYSize-0.5;
      pasGCPList[3].dfGCPPixel = 0.5;
  
      return TRUE;
  }
  
  /************************************************************************/
  /*                            ScanForGCPs()                             */
  /************************************************************************/
  
  void SAR_CEOSDataset::ScanForGCPs()
  
  {
  /* -------------------------------------------------------------------- */
  /*      Do we have a standard 180 bytes of prefix data (192 bytes       */
  /*      including the record marker information)?  If not, it is        */
  /*      unlikely that the GCPs are available.                           */
  /* -------------------------------------------------------------------- */
      if( sVolume.ImageDesc.ImageDataStart < 192 )
      {
          ScanForMapProjection();
          return;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Just sample fix scanlines through the image for GCPs, to        */
  /*      return 15 GCPs.  That is an adequate coverage for most          */
  /*      purposes.  A GCP is collected from the beginning, middle and    */
  /*      end of each scanline.                                           */
  /* -------------------------------------------------------------------- */
      nGCPCount = 0;
      int nGCPMax = 15;
      pasGCPList = (GDAL_GCP *) CPLCalloc(sizeof(GDAL_GCP),nGCPMax);
  
      int nStep = (GetRasterYSize()-1) / (nGCPMax / 3 - 1);
      for( int iScanline = 0; iScanline < GetRasterYSize(); iScanline += nStep )
      {
          if( nGCPCount > nGCPMax-3 )
              break;
  
          int nFileOffset;
          CalcCeosSARImageFilePosition( &sVolume, 1, iScanline+1, nullptr,
                                        &nFileOffset );
  
          GInt32 anRecord[192/4];
          if( VSIFSeekL( fpImage, nFileOffset, SEEK_SET ) != 0
              || VSIFReadL( anRecord, 1, 192, fpImage ) != 192 )
              break;
  
          /* loop over first, middle and last pixel gcps */
  
          for( int iGCP = 0; iGCP < 3; iGCP++ )
          {
              const int nLat  = CPL_MSBWORD32( anRecord[132/4 + iGCP] );
              const int nLong = CPL_MSBWORD32( anRecord[144/4 + iGCP] );
  
              if( nLat != 0 || nLong != 0 )
              {
                  GDALInitGCPs( 1, pasGCPList + nGCPCount );
  
                  CPLFree( pasGCPList[nGCPCount].pszId );
  
                  char szId[32];
                  snprintf( szId, sizeof(szId), "%d", nGCPCount+1 );
                  pasGCPList[nGCPCount].pszId = CPLStrdup( szId );
  
                  pasGCPList[nGCPCount].dfGCPX = nLong / 1000000.0;
                  pasGCPList[nGCPCount].dfGCPY = nLat / 1000000.0;
                  pasGCPList[nGCPCount].dfGCPZ = 0.0;
  
                  pasGCPList[nGCPCount].dfGCPLine = iScanline + 0.5;
  
                  if( iGCP == 0 )
                      pasGCPList[nGCPCount].dfGCPPixel = 0.5;
                  else if( iGCP == 1 )
                      pasGCPList[nGCPCount].dfGCPPixel =
                          GetRasterXSize() / 2.0;
                  else
                      pasGCPList[nGCPCount].dfGCPPixel =
                          GetRasterXSize() - 0.5;
  
                  nGCPCount++;
              }
          }
      }
      /* If general GCP's were not found, look for Map Projection (e.g. JERS) */
      if( nGCPCount == 0 )
      {
          ScanForMapProjection();
          return;
      }
  }
  
  /************************************************************************/
  /*                                Open()                                */
  /************************************************************************/
  
  GDALDataset *SAR_CEOSDataset::Open( GDALOpenInfo * poOpenInfo )
  
  {
  /* -------------------------------------------------------------------- */
  /*      Does this appear to be a valid ceos leader record?              */
  /* -------------------------------------------------------------------- */
      if( poOpenInfo->nHeaderBytes < CEOS_HEADER_LENGTH || poOpenInfo->fpL == nullptr )
          return nullptr;
  
      if( (poOpenInfo->pabyHeader[4] != 0x3f
           && poOpenInfo->pabyHeader[4] != 0x32)
          || poOpenInfo->pabyHeader[5] != 0xc0
          || poOpenInfo->pabyHeader[6] != 0x12
          || poOpenInfo->pabyHeader[7] != 0x12 )
          return nullptr;
  
      // some products (#1862) have byte swapped record length/number
      // values and will blow stuff up -- explicitly ignore if record index
      // value appears to be little endian.
      if( poOpenInfo->pabyHeader[0] != 0 )
          return nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Confirm the requested access is supported.                      */
  /* -------------------------------------------------------------------- */
      if( poOpenInfo->eAccess == GA_Update )
      {
          CPLError( CE_Failure, CPLE_NotSupported,
                    "The SAR_CEOS driver does not support update access to existing"
                    " datasets.\n" );
          return nullptr;
      }
  
      VSILFILE *fp = poOpenInfo->fpL;
      poOpenInfo->fpL = nullptr;
  
  /* -------------------------------------------------------------------- */
  /*      Create a corresponding GDALDataset.                             */
  /* -------------------------------------------------------------------- */
  
      SAR_CEOSDataset *poDS = new SAR_CEOSDataset();
      CeosSARVolume_t *psVolume = &(poDS->sVolume);
      InitCeosSARVolume( psVolume, 0 );
  
  /* -------------------------------------------------------------------- */
  /*      Try to read the current file as an imagery file.                */
  /* -------------------------------------------------------------------- */
  
      psVolume->ImagryOptionsFile = TRUE;
      if( ProcessData( fp, CEOS_IMAGRY_OPT_FILE, psVolume, 4, VSI_L_OFFSET_MAX) != CE_None )
      {
          delete poDS;
          CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
          return nullptr;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Try the various filenames.                                      */
  /* -------------------------------------------------------------------- */
      char *pszPath = CPLStrdup(CPLGetPath(poOpenInfo->pszFilename));
      char *pszBasename = CPLStrdup(CPLGetBasename(poOpenInfo->pszFilename));
      char *pszExtension = CPLStrdup(CPLGetExtension(poOpenInfo->pszFilename));
  
      int nBand;
      if( strlen(pszBasename) > 4 )
          nBand = atoi( pszBasename + 4 );
      else
          nBand = 0;
  
      for( int iFile = 0; iFile < 5;iFile++ )
      {
          /* skip image file ... we already did it */
          if( iFile == 2 )
              continue;
  
          int e = 0;
          while( CeosExtension[e][iFile] != nullptr )
          {
              char *pszFilename = nullptr;
  
              /* build filename */
              if( EQUAL(CeosExtension[e][5],"base") )
              {
                  char    szMadeBasename[32];
  
                  snprintf( szMadeBasename, sizeof(szMadeBasename),
                            CeosExtension[e][iFile], nBand );
                  pszFilename = CPLStrdup(
                      CPLFormFilename(pszPath,szMadeBasename, pszExtension));
              }
              else if( EQUAL(CeosExtension[e][5],"ext") )
              {
                  pszFilename = CPLStrdup(
                      CPLFormFilename(pszPath,pszBasename,
                                      CeosExtension[e][iFile]));
              }
              else if( EQUAL(CeosExtension[e][5],"whole") )
              {
                  pszFilename = CPLStrdup(
                      CPLFormFilename(pszPath,CeosExtension[e][iFile],""));
              }
  
              // This is for SAR SLC as per the SAR Toolbox (from ASF).
              else if( EQUAL(CeosExtension[e][5],"ext2") )
              {
                  char szThisExtension[32];
  
                  if( strlen(pszExtension) > 3 )
                      snprintf( szThisExtension, sizeof(szThisExtension), "%s%s",
                               CeosExtension[e][iFile],
                               pszExtension+3 );
                  else
                      snprintf( szThisExtension, sizeof(szThisExtension), "%s",
                               CeosExtension[e][iFile] );
  
                  pszFilename = CPLStrdup(
                      CPLFormFilename(pszPath,pszBasename,szThisExtension));
              }
  
              CPLAssert( pszFilename != nullptr );
              if( pszFilename == nullptr )
                  return nullptr;
  
              /* try to open */
              VSILFILE *process_fp = VSIFOpenL( pszFilename, "rb" );
  
              /* try upper case */
              if( process_fp == nullptr )
              {
                  for( int i = static_cast<int>(strlen(pszFilename))-1;
                       i >= 0 && pszFilename[i] != '/' && pszFilename[i] != '\\';
                       i-- )
                  {
                      if( pszFilename[i] >= 'a' && pszFilename[i] <= 'z' )
                          pszFilename[i] = pszFilename[i] - 'a' + 'A';
                  }
  
                  process_fp = VSIFOpenL( pszFilename, "rb" );
              }
  
              if( process_fp != nullptr )
              {
                  CPLDebug( "CEOS", "Opened %s.\n", pszFilename );
  
                  CPL_IGNORE_RET_VAL(VSIFSeekL( process_fp, 0, SEEK_END ));
                  if( ProcessData( process_fp, iFile, psVolume, -1,
                                   VSIFTellL( process_fp ) ) == 0 )
                  {
                      switch( iFile )
                      {
                        case 0: psVolume->VolumeDirectoryFile = TRUE;
                          break;
                        case 1: psVolume->SARLeaderFile = TRUE;
                          break;
                        case 3: psVolume->SARTrailerFile = TRUE;
                          break;
                        case 4: psVolume->NullVolumeDirectoryFile = TRUE;
                          break;
                      }
  
                      CPL_IGNORE_RET_VAL(VSIFCloseL( process_fp ));
                      CPLFree( pszFilename );
                      break; /* Exit the while loop, we have this data type*/
                  }
  
                  CPL_IGNORE_RET_VAL(VSIFCloseL( process_fp ));
              }
  
              CPLFree( pszFilename );
  
              e++;
          }
      }
  
      CPLFree( pszPath );
      CPLFree( pszBasename );
      CPLFree( pszExtension );
  
  /* -------------------------------------------------------------------- */
  /*      Check that we have an image description.                        */
  /* -------------------------------------------------------------------- */
      GetCeosSARImageDesc( psVolume );
      struct CeosSARImageDesc *psImageDesc = &(psVolume->ImageDesc);
      if( !psImageDesc->ImageDescValid )
      {
          delete poDS;
  
          CPLDebug( "CEOS",
                    "Unable to extract CEOS image description\n"
                    "from %s.",
                    poOpenInfo->pszFilename );
  
          CPL_IGNORE_RET_VAL(VSIFCloseL(fp));
  
          return nullptr;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Establish image type.                                           */
  /* -------------------------------------------------------------------- */
      GDALDataType eType;
  
      switch( psImageDesc->DataType )
      {
        case CEOS_TYP_CHAR:
        case CEOS_TYP_UCHAR:
          eType = GDT_Byte;
          break;
  
        case CEOS_TYP_SHORT:
          eType = GDT_Int16;
          break;
  
        case CEOS_TYP_COMPLEX_SHORT:
        case CEOS_TYP_PALSAR_COMPLEX_SHORT:
          eType = GDT_CInt16;
          break;
  
        case CEOS_TYP_USHORT:
          eType = GDT_UInt16;
          break;
  
        case CEOS_TYP_LONG:
          eType = GDT_Int32;
          break;
  
        case CEOS_TYP_ULONG:
          eType = GDT_UInt32;
          break;
  
        case CEOS_TYP_FLOAT:
          eType = GDT_Float32;
          break;
  
        case CEOS_TYP_DOUBLE:
          eType = GDT_Float64;
          break;
  
        case CEOS_TYP_COMPLEX_FLOAT:
        case CEOS_TYP_CCP_COMPLEX_FLOAT:
          eType = GDT_CFloat32;
          break;
  
        default:
          CPLError( CE_Failure, CPLE_AppDefined,
                    "Unsupported CEOS image data type %d.\n",
                    psImageDesc->DataType );
          delete poDS;
          return nullptr;
      }
  
  /* -------------------------------------------------------------------- */
  /*      Capture some information from the file that is of interest.     */
  /* -------------------------------------------------------------------- */
      poDS->nRasterXSize = psImageDesc->PixelsPerLine;
      poDS->nRasterYSize = psImageDesc->Lines;
  
      const int bNative =
  #ifdef CPL_LSB
      FALSE
  #else
      TRUE
  #endif
      ;
  
  /* -------------------------------------------------------------------- */
  /*      Special case for compressed cross products.                     */
  /* -------------------------------------------------------------------- */
      if( psImageDesc->DataType == CEOS_TYP_CCP_COMPLEX_FLOAT )
      {
          for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
          {
              poDS->SetBand( poDS->nBands+1,
                             new CCPRasterBand( poDS, poDS->nBands+1, eType ) );
          }
  
          /* mark this as a Scattering Matrix product */
          if ( poDS->GetRasterCount() == 4 ) {
              poDS->SetMetadataItem( "MATRIX_REPRESENTATION", "SCATTERING" );
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      Special case for PALSAR data.                                   */
  /* -------------------------------------------------------------------- */
      else if( psImageDesc->DataType == CEOS_TYP_PALSAR_COMPLEX_SHORT )
      {
          for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
          {
              poDS->SetBand( poDS->nBands+1,
                             new PALSARRasterBand( poDS, poDS->nBands+1 ) );
          }
  
          /* mark this as a Symmetrized Covariance product if appropriate */
          if ( poDS->GetRasterCount() == 6 ) {
              poDS->SetMetadataItem( "MATRIX_REPRESENTATION",
                  "SYMMETRIZED_COVARIANCE" );
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      Roll our own ...                                                */
  /* -------------------------------------------------------------------- */
      else if( psImageDesc->RecordsPerLine > 1
               || psImageDesc->DataType == CEOS_TYP_CHAR
               || psImageDesc->DataType == CEOS_TYP_LONG
               || psImageDesc->DataType == CEOS_TYP_ULONG
               || psImageDesc->DataType == CEOS_TYP_DOUBLE )
      {
          for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
          {
              poDS->SetBand( poDS->nBands+1,
                             new SAR_CEOSRasterBand( poDS, poDS->nBands+1,
                                                     eType ) );
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      Use raw services for well behaved files.                        */
  /* -------------------------------------------------------------------- */
      else
      {
          int StartData;
          CalcCeosSARImageFilePosition( psVolume, 1, 1, nullptr, &StartData );
  
          /*StartData += psImageDesc->ImageDataStart; */
  
          int nLineSize, nLineSize2;
          CalcCeosSARImageFilePosition( psVolume, 1, 1, nullptr, &nLineSize );
          CalcCeosSARImageFilePosition( psVolume, 1, 2, nullptr, &nLineSize2 );
  
          nLineSize = nLineSize2 - nLineSize;
  
          for( int iBand = 0; iBand < psImageDesc->NumChannels; iBand++ )
          {
              int           nStartData, nPixelOffset, nLineOffset;
  
              if( psImageDesc->ChannelInterleaving == CEOS_IL_PIXEL )
              {
                  CalcCeosSARImageFilePosition(psVolume,1,1,nullptr,&nStartData);
  
                  nStartData += psImageDesc->ImageDataStart;
                  nStartData += psImageDesc->BytesPerPixel * iBand;
  
                  nPixelOffset =
                      psImageDesc->BytesPerPixel * psImageDesc->NumChannels;
                  nLineOffset = nLineSize;
              }
              else if( psImageDesc->ChannelInterleaving == CEOS_IL_LINE )
              {
                  CalcCeosSARImageFilePosition(psVolume, iBand+1, 1, nullptr,
                                               &nStartData);
  
                  nStartData += psImageDesc->ImageDataStart;
                  nPixelOffset = psImageDesc->BytesPerPixel;
                  nLineOffset = nLineSize * psImageDesc->NumChannels;
              }
              else if( psImageDesc->ChannelInterleaving == CEOS_IL_BAND )
              {
                  CalcCeosSARImageFilePosition(psVolume, iBand+1, 1, nullptr,
                                               &nStartData);
  
                  nStartData += psImageDesc->ImageDataStart;
                  nPixelOffset = psImageDesc->BytesPerPixel;
                  nLineOffset = nLineSize;
              }
              else
              {
                  CPLAssert( false );
                  return nullptr;
              }
  
              poDS->SetBand( poDS->nBands+1,
                      new RawRasterBand(
                          poDS, poDS->nBands+1, fp,
                          nStartData, nPixelOffset, nLineOffset,
                          eType, bNative, RawRasterBand::OwnFP::NO ) );
          }
      }
  
  /* -------------------------------------------------------------------- */
  /*      Adopt the file pointer.                                         */
  /* -------------------------------------------------------------------- */
      poDS->fpImage = fp;
  
  /* -------------------------------------------------------------------- */
  /*      Collect metadata.                                               */
  /* -------------------------------------------------------------------- */
      poDS->ScanForMetadata();
  
  /* -------------------------------------------------------------------- */
  /*      Check for GCPs.                                                 */
  /* -------------------------------------------------------------------- */
      poDS->ScanForGCPs();
  
  /* -------------------------------------------------------------------- */
  /*      Initialize any PAM information.                                 */
  /* -------------------------------------------------------------------- */
      poDS->SetDescription( poOpenInfo->pszFilename );
      poDS->TryLoadXML();
  
  /* -------------------------------------------------------------------- */
  /*      Open overviews.                                                 */
  /* -------------------------------------------------------------------- */
      poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );
  
      return poDS;
  }
  
  /************************************************************************/
  /*                            ProcessData()                             */
  /************************************************************************/
  static int
  ProcessData( VSILFILE *fp, int fileid, CeosSARVolume_t *sar, int max_records,
               vsi_l_offset max_bytes )
  
  {
      unsigned char      temp_buffer[CEOS_HEADER_LENGTH];
      unsigned char      *temp_body = nullptr;
      int                start = 0;
      int                CurrentBodyLength = 0;
      int                CurrentType = 0;
      int                CurrentSequence = 0;
      int                iThisRecord = 0;
  
      while(max_records != 0 && max_bytes != 0)
      {
          iThisRecord++;
  
          if( VSIFSeekL( fp, start, SEEK_SET ) != 0 ||
              VSIFReadL( temp_buffer, 1, CEOS_HEADER_LENGTH, fp ) != CEOS_HEADER_LENGTH )
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Corrupt CEOS File - cannot read record %d.",
                        iThisRecord );
              CPLFree(temp_body);
              return CE_Failure;
          }
          CeosRecord_t *record =
              (CeosRecord_t *) CPLMalloc( sizeof( CeosRecord_t ) );
          record->Length = DetermineCeosRecordBodyLength( temp_buffer );
  
          CeosToNative( &(record->Sequence), temp_buffer, 4, 4 );
  
          if( iThisRecord != record->Sequence )
          {
              if( fileid == CEOS_IMAGRY_OPT_FILE && iThisRecord == 2 )
              {
                  CPLDebug( "SAR_CEOS", "Ignoring CEOS file with wrong second record sequence number - likely it has padded records." );
                  CPLFree(record);
                  CPLFree(temp_body);
                  return CE_Warning;
              }
              else
              {
                  CPLError( CE_Failure, CPLE_AppDefined,
                            "Corrupt CEOS File - got record seq# %d instead of the expected %d.",
                            record->Sequence, iThisRecord );
                  CPLFree(record);
                  CPLFree(temp_body);
                  return CE_Failure;
              }
          }
  
          if( record->Length <= CEOS_HEADER_LENGTH )
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Corrupt CEOS File - cannot read record %d.",
                        iThisRecord );
              CPLFree(record);
              CPLFree(temp_body);
              return CE_Failure;
          }
  
          if( record->Length > CurrentBodyLength )
          {
              unsigned char* temp_body_new = (unsigned char *)
                      VSI_REALLOC_VERBOSE( temp_body, record->Length );
              if( temp_body_new == nullptr )
              {
                  CPLFree(record);
                  CPLFree(temp_body);
                  return CE_Failure;
              }
              temp_body = temp_body_new;
              CurrentBodyLength = record->Length;
          }
  
          int nToRead = record->Length-CEOS_HEADER_LENGTH;
          if( (int)VSIFReadL( temp_body, 1, nToRead,fp) != nToRead )
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Corrupt CEOS File - cannot read record %d.",
                        iThisRecord );
              CPLFree(record);
              CPLFree(temp_body);
              return CE_Failure;
          }
  
          InitCeosRecordWithHeader( record, temp_buffer, temp_body );
          if( record->Length == 0 )
          {
              CPLError( CE_Failure, CPLE_AppDefined,
                        "Corrupt CEOS File - invalid record %d.",
                        iThisRecord );
              CPLFree(record);
              CPLFree(temp_body);
              return CE_Failure;
          }
  
          if( CurrentType == record->TypeCode.Int32Code )
              record->Subsequence = ++CurrentSequence;
          else {
              CurrentType = record->TypeCode.Int32Code;
              record->Subsequence = 0;
              CurrentSequence = 0;
          }
  
          record->FileId = fileid;
  
          Link_t *TheLink = ceos2CreateLink( record );
  
          if( sar->RecordList == nullptr )
              sar->RecordList = TheLink;
          else
              sar->RecordList = InsertLink( sar->RecordList, TheLink );
  
          start += record->Length;
  
          if(max_records > 0)
              max_records--;
          if(max_bytes > 0)
          {
            if( (vsi_l_offset)record->Length <= max_bytes )
                  max_bytes -= record->Length;
              else {
                  CPLDebug( "SAR_CEOS", "Partial record found.  %d > " CPL_FRMT_GUIB,
                            record->Length, max_bytes );
                  max_bytes = 0;
              }
          }
      }
  
      CPLFree(temp_body);
  
      return CE_None;
  }
  
  /************************************************************************/
  /*                       GDALRegister_SAR_CEOS()                        */
  /************************************************************************/
  
  void GDALRegister_SAR_CEOS()
  
  {
      if( GDALGetDriverByName( "SAR_CEOS" ) != nullptr )
          return;
  
      GDALDriver *poDriver = new GDALDriver();
  
      poDriver->SetDescription( "SAR_CEOS" );
      poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
      poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                                 "CEOS SAR Image" );
      poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
                                 "frmt_various.html#SAR_CEOS" );
      poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );
  
      poDriver->pfnOpen = SAR_CEOSDataset::Open;
  
      GetGDALDriverManager()->RegisterDriver( poDriver );
  }
  