/******************************************************************************
 *
 * Project:  VICAR Driver; JPL/MIPL VICAR Format
 * Purpose:  Implementation of VICARDataset
 * Author:   Sebastian Walter <sebastian dot walter at fu-berlin dot de>
 *
 * NOTE: This driver code is loosely based on the ISIS and PDS drivers.
 * It is not intended to diminish the contribution of the original authors
 ******************************************************************************
 * Copyright (c) 2014, Sebastian Walter <sebastian dot walter at fu-berlin dot
 *de>
 *
 * 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.
 ****************************************************************************/

constexpr int NULL1 = 0;
constexpr int NULL2 = -32768;
constexpr double NULL3 = -32768.0;

#include "cpl_port.h"

#include "cpl_safemaths.hpp"
#include "cpl_vax.h"
#include "cpl_vsi_error.h"
#include "vicardataset.h"
#include "nasakeywordhandler.h"
#include "vicarkeywordhandler.h"

#include "gtiff.h"
#include "geotiff.h"
#include "tifvsi.h"
#include "xtiffio.h"
#include "gt_wkt_srs_priv.h"

#include <exception>
#include <limits>
#include <string>

/* GeoTIFF 1.0 geokeys */

static const geokey_t GTiffAsciiKeys[] = {GTCitationGeoKey, GeogCitationGeoKey,
                                          PCSCitationGeoKey,
                                          VerticalCitationGeoKey};

static const geokey_t GTiffDoubleKeys[] = {
    GeogInvFlatteningGeoKey,      GeogSemiMajorAxisGeoKey,
    GeogSemiMinorAxisGeoKey,      ProjAzimuthAngleGeoKey,
    ProjCenterLatGeoKey,          ProjCenterLongGeoKey,
    ProjFalseEastingGeoKey,       ProjFalseNorthingGeoKey,
    ProjFalseOriginEastingGeoKey, ProjFalseOriginLatGeoKey,
    ProjFalseOriginLongGeoKey,    ProjFalseOriginNorthingGeoKey,
    ProjLinearUnitSizeGeoKey,     ProjNatOriginLatGeoKey,
    ProjNatOriginLongGeoKey,      ProjOriginLatGeoKey,
    ProjOriginLongGeoKey,         ProjRectifiedGridAngleGeoKey,
    ProjScaleAtNatOriginGeoKey,   ProjScaleAtOriginGeoKey,
    ProjStdParallel1GeoKey,       ProjStdParallel2GeoKey,
    ProjStdParallelGeoKey,        ProjStraightVertPoleLongGeoKey,
    GeogLinearUnitSizeGeoKey,     GeogAngularUnitSizeGeoKey,
    GeogPrimeMeridianLongGeoKey,  ProjCenterEastingGeoKey,
    ProjCenterNorthingGeoKey,     ProjScaleAtCenterGeoKey};

static const geokey_t GTiffShortKeys[] = {
    GTModelTypeGeoKey,      GTRasterTypeGeoKey,      GeogAngularUnitsGeoKey,
    GeogEllipsoidGeoKey,    GeogGeodeticDatumGeoKey, GeographicTypeGeoKey,
    ProjCoordTransGeoKey,   ProjLinearUnitsGeoKey,   ProjectedCSTypeGeoKey,
    ProjectionGeoKey,       GeogPrimeMeridianGeoKey, GeogLinearUnitsGeoKey,
    GeogAzimuthUnitsGeoKey, VerticalCSTypeGeoKey,    VerticalDatumGeoKey,
    VerticalUnitsGeoKey};

/************************************************************************/
/*                     OGRVICARBinaryPrefixesLayer                      */
/************************************************************************/

class OGRVICARBinaryPrefixesLayer final : public OGRLayer
{
    VSILFILE *m_fp = nullptr;
    OGRFeatureDefn *m_poFeatureDefn = nullptr;
    int m_iRecord = 0;
    int m_nRecords = 0;
    vsi_l_offset m_nFileOffset = 0;
    vsi_l_offset m_nStride = 0;
    bool m_bError = false;
    bool m_bByteSwapIntegers = false;
    RawRasterBand::ByteOrder m_eBREALByteOrder;

    enum Type
    {
        FIELD_UNKNOWN,
        FIELD_UNSIGNED_CHAR,
        FIELD_UNSIGNED_SHORT,
        FIELD_UNSIGNED_INT,
        FIELD_SHORT,
        FIELD_INT,
        FIELD_FLOAT,
        FIELD_DOUBLE,
    };
    static Type GetTypeFromString(const char *pszStr);

    struct Field
    {
        int nOffset;
        Type eType;
    };
    std::vector<Field> m_aoFields;
    std::vector<GByte> m_abyRecord;

    OGRFeature *GetNextRawFeature();

  public:
    OGRVICARBinaryPrefixesLayer(VSILFILE *fp, int nRecords,
                                const CPLJSONObject &oDef,
                                vsi_l_offset nFileOffset, vsi_l_offset nStride,
                                RawRasterBand::ByteOrder eBINTByteOrder,
                                RawRasterBand::ByteOrder eBREALByteOrder);
    ~OGRVICARBinaryPrefixesLayer();

    bool HasError() const
    {
        return m_bError;
    }

    void ResetReading() override
    {
        m_iRecord = 0;
    }
    OGRFeatureDefn *GetLayerDefn() override
    {
        return m_poFeatureDefn;
    }
    OGRFeature *GetNextFeature() override;
    int TestCapability(const char *) override
    {
        return false;
    }
};

/************************************************************************/
/*                       GetTypeFromString()                            */
/************************************************************************/

OGRVICARBinaryPrefixesLayer::Type
OGRVICARBinaryPrefixesLayer::GetTypeFromString(const char *pszStr)
{
    if (EQUAL(pszStr, "unsigned char") || EQUAL(pszStr, "unsigned byte"))
        return FIELD_UNSIGNED_CHAR;
    if (EQUAL(pszStr, "unsigned short"))
        return FIELD_UNSIGNED_SHORT;
    if (EQUAL(pszStr, "unsigned int"))
        return FIELD_UNSIGNED_INT;
    if (EQUAL(pszStr, "short"))
        return FIELD_SHORT;
    if (EQUAL(pszStr, "int"))
        return FIELD_INT;
    if (EQUAL(pszStr, "float"))
        return FIELD_FLOAT;
    if (EQUAL(pszStr, "double"))
        return FIELD_DOUBLE;
    return FIELD_UNKNOWN;
}

/************************************************************************/
/*                     OGRVICARBinaryPrefixesLayer()                    */
/************************************************************************/

OGRVICARBinaryPrefixesLayer::OGRVICARBinaryPrefixesLayer(
    VSILFILE *fp, int nRecords, const CPLJSONObject &oDef,
    vsi_l_offset nFileOffset, vsi_l_offset nStride,
    RawRasterBand::ByteOrder eBINTByteOrder,
    RawRasterBand::ByteOrder eBREALByteOrder)
    : m_fp(fp), m_nRecords(nRecords), m_nFileOffset(nFileOffset),
      m_nStride(nStride),
#ifdef CPL_LSB
      m_bByteSwapIntegers(eBINTByteOrder !=
                          RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN),
#else
      m_bByteSwapIntegers(eBINTByteOrder !=
                          RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN),
#endif
      m_eBREALByteOrder(eBREALByteOrder)
{
    m_poFeatureDefn = new OGRFeatureDefn("binary_prefixes");
    SetDescription(m_poFeatureDefn->GetName());
    m_poFeatureDefn->Reference();
    m_poFeatureDefn->SetGeomType(wkbNone);
    int nRecordSize = oDef.GetInteger("size");
    const auto oFields = oDef.GetObj("fields");
    if (oFields.IsValid() && oFields.GetType() == CPLJSONObject::Type::Array)
    {
        auto oFieldsArray = oFields.ToArray();
        int nOffset = 0;
        for (int i = 0; i < oFieldsArray.Size(); i++)
        {
            auto oField = oFieldsArray[i];
            if (oField.GetType() == CPLJSONObject::Type::Object)
            {
                auto osName = oField.GetString("name");
                auto osType = oField.GetString("type");
                auto bHidden = oField.GetBool("hidden");
                auto eType = GetTypeFromString(osType.c_str());
                if (eType == FIELD_UNKNOWN)
                {
                    CPLError(CE_Failure, CPLE_NotSupported,
                             "Field %s of type %s not supported",
                             osName.c_str(), osType.c_str());
                    m_bError = true;
                    return;
                }
                else if (!osName.empty())
                {
                    OGRFieldType eFieldType(OFTMaxType);
                    Field f;
                    f.nOffset = nOffset;
                    f.eType = eType;
                    switch (eType)
                    {
                        case FIELD_UNSIGNED_CHAR:
                            nOffset += 1;
                            eFieldType = OFTInteger;
                            break;
                        case FIELD_UNSIGNED_SHORT:
                            nOffset += 2;
                            eFieldType = OFTInteger;
                            break;
                        case FIELD_UNSIGNED_INT:
                            nOffset += 4;
                            eFieldType = OFTInteger64;
                            break;
                        case FIELD_SHORT:
                            nOffset += 2;
                            eFieldType = OFTInteger;
                            break;
                        case FIELD_INT:
                            nOffset += 4;
                            eFieldType = OFTInteger;
                            break;
                        case FIELD_FLOAT:
                            nOffset += 4;
                            eFieldType = OFTReal;
                            break;
                        case FIELD_DOUBLE:
                            nOffset += 8;
                            eFieldType = OFTReal;
                            break;
                        default:
                            CPLAssert(false);
                            break;
                    }
                    if (nOffset > nRecordSize)
                    {
                        CPLError(CE_Failure, CPLE_AppDefined,
                                 "Field definitions not consistent with "
                                 "declared record size");
                        m_bError = true;
                        return;
                    }
                    if (!bHidden)
                    {
                        m_aoFields.push_back(f);
                        OGRFieldDefn oFieldDefn(osName.c_str(), eFieldType);
                        m_poFeatureDefn->AddFieldDefn(&oFieldDefn);
                    }
                }
                else
                {
                    m_bError = true;
                }
            }
            else
            {
                m_bError = true;
            }
            if (m_bError)
            {
                CPLError(CE_Failure, CPLE_AppDefined,
                         "Error while reading binary prefix definition");
                return;
            }
        }
    }
    m_abyRecord.resize(nRecordSize);
}

/************************************************************************/
/*                    ~OGRVICARBinaryPrefixesLayer()                    */
/************************************************************************/

OGRVICARBinaryPrefixesLayer::~OGRVICARBinaryPrefixesLayer()
{
    m_poFeatureDefn->Release();
}

/************************************************************************/
/*                         GetNextRawFeature()                          */
/************************************************************************/

OGRFeature *OGRVICARBinaryPrefixesLayer::GetNextRawFeature()
{
    if (m_iRecord >= m_nRecords)
        return nullptr;

    if (VSIFSeekL(m_fp, m_nFileOffset + m_iRecord * m_nStride, SEEK_SET) != 0 ||
        VSIFReadL(&m_abyRecord[0], m_abyRecord.size(), 1, m_fp) != 1)
    {
        return nullptr;
    }

    OGRFeature *poFeature = new OGRFeature(m_poFeatureDefn);
    for (int i = 0; i < poFeature->GetFieldCount(); i++)
    {
        int nOffset = m_aoFields[i].nOffset;
        switch (m_aoFields[i].eType)
        {
            case FIELD_UNSIGNED_CHAR:
                poFeature->SetField(i, m_abyRecord[nOffset]);
                break;
            case FIELD_UNSIGNED_SHORT:
            {
                unsigned short v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_bByteSwapIntegers)
                {
                    CPL_SWAP16PTR(&v);
                }
                poFeature->SetField(i, v);
                break;
            }
            case FIELD_UNSIGNED_INT:
            {
                unsigned int v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_bByteSwapIntegers)
                {
                    CPL_SWAP32PTR(&v);
                }
                poFeature->SetField(i, static_cast<GIntBig>(v));
                break;
            }
            case FIELD_SHORT:
            {
                short v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_bByteSwapIntegers)
                {
                    CPL_SWAP16PTR(&v);
                }
                poFeature->SetField(i, v);
                break;
            }
            case FIELD_INT:
            {
                int v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_bByteSwapIntegers)
                {
                    CPL_SWAP32PTR(&v);
                }
                poFeature->SetField(i, v);
                break;
            }
            case FIELD_FLOAT:
            {
                float v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_eBREALByteOrder == RawRasterBand::ByteOrder::ORDER_VAX)
                {
                    CPLVaxToIEEEFloat(&v);
                }
                else if (m_eBREALByteOrder !=
#ifdef CPL_LSB
                         RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
#else
                         RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN
#endif
                )
                {
                    CPL_SWAP32PTR(&v);
                }
                poFeature->SetField(i, v);
                break;
            }
            case FIELD_DOUBLE:
            {
                double v;
                memcpy(&v, &m_abyRecord[nOffset], sizeof(v));
                if (m_eBREALByteOrder == RawRasterBand::ByteOrder::ORDER_VAX)
                {
                    CPLVaxToIEEEDouble(&v);
                }
                else if (m_eBREALByteOrder !=
#ifdef CPL_LSB
                         RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
#else
                         RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN
#endif
                )
                {
                    CPL_SWAP64PTR(&v);
                }
                poFeature->SetField(i, v);
                break;
            }
            default:
                CPLAssert(false);
        }
    }
    poFeature->SetFID(m_iRecord);
    m_iRecord++;
    return poFeature;
}

/************************************************************************/
/*                           GetNextFeature()                           */
/************************************************************************/

OGRFeature *OGRVICARBinaryPrefixesLayer::GetNextFeature()
{
    while (true)
    {
        auto poFeature = GetNextRawFeature();
        if (poFeature == nullptr)
            return nullptr;

        if ((m_poFilterGeom == nullptr ||
             FilterGeometry(poFeature->GetGeometryRef())) &&
            (m_poAttrQuery == nullptr || m_poAttrQuery->Evaluate(poFeature)))
        {
            return poFeature;
        }
        else
            delete poFeature;
    }
}

/************************************************************************/
/*                         VICARRawRasterBand                           */
/************************************************************************/

class VICARRawRasterBand final : public RawRasterBand
{
  protected:
    friend class VICARDataset;

  public:
    VICARRawRasterBand(VICARDataset *poDSIn, int nBandIn, VSILFILE *fpRawIn,
                       vsi_l_offset nImgOffsetIn, int nPixelOffsetIn,
                       int nLineOffsetIn, GDALDataType eDataTypeIn,
                       ByteOrder eByteOrderIn);

    virtual CPLErr IReadBlock(int, int, void *) override;
    virtual CPLErr IWriteBlock(int, int, void *) override;

    virtual CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
                             GDALDataType, GSpacing nPixelSpace,
                             GSpacing nLineSpace,
                             GDALRasterIOExtraArg *psExtraArg) override;
};

/************************************************************************/
/*                        VICARRawRasterBand()                          */
/************************************************************************/

VICARRawRasterBand::VICARRawRasterBand(VICARDataset *poDSIn, int nBandIn,
                                       VSILFILE *fpRawIn,
                                       vsi_l_offset nImgOffsetIn,
                                       int nPixelOffsetIn, int nLineOffsetIn,
                                       GDALDataType eDataTypeIn,
                                       ByteOrder eByteOrderIn)
    : RawRasterBand(poDSIn, nBandIn, fpRawIn, nImgOffsetIn, nPixelOffsetIn,
                    nLineOffsetIn, eDataTypeIn, eByteOrderIn,
                    RawRasterBand::OwnFP::NO)
{
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr VICARRawRasterBand::IReadBlock(int nXBlock, int nYBlock, void *pImage)

{
    VICARDataset *poGDS = reinterpret_cast<VICARDataset *>(poDS);
    if (!poGDS->m_bIsLabelWritten)
        poGDS->WriteLabel();
    return RawRasterBand::IReadBlock(nXBlock, nYBlock, pImage);
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr VICARRawRasterBand::IWriteBlock(int nXBlock, int nYBlock, void *pImage)

{
    VICARDataset *poGDS = reinterpret_cast<VICARDataset *>(poDS);
    if (!poGDS->m_bIsLabelWritten)
        poGDS->WriteLabel();
    return RawRasterBand::IWriteBlock(nXBlock, nYBlock, pImage);
}

/************************************************************************/
/*                             IRasterIO()                              */
/************************************************************************/

CPLErr VICARRawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
                                     int nXSize, int nYSize, void *pData,
                                     int nBufXSize, int nBufYSize,
                                     GDALDataType eBufType,
                                     GSpacing nPixelSpace, GSpacing nLineSpace,
                                     GDALRasterIOExtraArg *psExtraArg)

{
    VICARDataset *poGDS = reinterpret_cast<VICARDataset *>(poDS);
    if (!poGDS->m_bIsLabelWritten)
        poGDS->WriteLabel();
    return RawRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
                                    pData, nBufXSize, nBufYSize, eBufType,
                                    nPixelSpace, nLineSpace, psExtraArg);
}

/************************************************************************/
/*                        VICARBASICRasterBand                          */
/************************************************************************/

class VICARBASICRasterBand final : public GDALPamRasterBand
{
  public:
    VICARBASICRasterBand(VICARDataset *poDSIn, int nBandIn, GDALDataType eType);

    virtual CPLErr IReadBlock(int, int, void *) override;
    virtual CPLErr IWriteBlock(int, int, void *) override;
};

/************************************************************************/
/*                        VICARBASICRasterBand()                        */
/************************************************************************/

VICARBASICRasterBand::VICARBASICRasterBand(VICARDataset *poDSIn, int nBandIn,
                                           GDALDataType eType)
{
    poDS = poDSIn;
    nBand = nBandIn;
    nBlockXSize = poDSIn->GetRasterXSize();
    nBlockYSize = 1;
    eDataType = eType;
}

namespace
{
class DecodeEncodeException : public std::exception
{
  public:
    DecodeEncodeException() = default;
};
}  // namespace

//////////////////////////////////////////////////////////////////////////
/// Below functions are adapted from Public Domain VICAR project
/// from
/// https://github.com/nasa/VICAR/blob/master/vos/rtl/source/basic_compression.c
//////////////////////////////////////////////////////////////////////////

/* masking array used in the algorithm to take out bits in memory */
const unsigned int cod1mask[25] = {
    0x0,      0x1,      0x3,      0x7,     0xf,     0x1f,    0x3f,
    0x7f,     0xff,     0x1ff,    0x3ff,   0x7ff,   0xfff,   0x1fff,
    0x3fff,   0x7fff,   0xffff,   0x1ffff, 0x3ffff, 0x7ffff, 0xfffff,
    0x1fffff, 0x3fffff, 0x7fffff, 0xffffff};

/*****************************************************/
/* This function is a helper function for the BASIC  */
/* compression algorithm to get a specified number   */
/* of bits from buffer, convert it to a number and   */
/* return it.                                        */
/*****************************************************/
static unsigned char grab1(int nbit, const unsigned char *buffer,
                           size_t buffer_size, size_t &buffer_pos,
                           int &bit1ptr) /* bit position in the current byte of
                                            encrypted or decrypted buffer*/
{
    unsigned char val;
    int shift = 8 - nbit - (bit1ptr);

    if (buffer_pos >= buffer_size)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Out of decoding buffer");
        throw DecodeEncodeException();
    }

    if (shift > 0)
    {
        val = (buffer[buffer_pos] >> shift) & cod1mask[nbit];
        bit1ptr += nbit;

        return val;
    }
    if (shift < 0)
    {
        unsigned v1 = buffer[buffer_pos] & cod1mask[nbit + shift];
        buffer_pos++;

        if (buffer_pos >= buffer_size)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Out of decoding buffer");
            throw DecodeEncodeException();
        }

        unsigned v2 = (buffer[buffer_pos] >> (8 + shift)) & cod1mask[-shift];

        val = static_cast<unsigned char>((v1 << (-shift)) + v2);

        bit1ptr = -shift;

        return val;
    }
    val = buffer[buffer_pos] & cod1mask[nbit];
    buffer_pos++;
    bit1ptr = 0;

    return val;
}

/*****************************************************/
/* This function is the decoding algorithm for BASIC */
/* compression.  The encoded buffer is passed into   */
/* code and the decoded buffer is passed out in buf. */
/*****************************************************/
static void basic_decode(const unsigned char *code, size_t code_size,
                         unsigned char *buf, int ns, int wid)
{
    int runInt = -3;
    unsigned char runChar;
    unsigned int nval = 999999;
    static const int cmprtrns1[7] = {-3, -2, -1, 0, 1, 2, 3};
    size_t buffer_pos = 0;
    int bit1ptr = 0;
    unsigned int old = 0;
    const int ptop = ns * wid;

    for (int iw = 0; iw < wid; iw++)
    {
        for (int ip = iw; ip < ptop; ip += wid)
        {
            if (runInt > (-3))
            {
                buf[ip] = static_cast<unsigned char>(nval);
                runInt--;
                continue;
            }
            unsigned char val = grab1(3, code, code_size, buffer_pos, bit1ptr);

            if (val < 7)
            {
                nval = CPLUnsanitizedAdd<unsigned>(old, cmprtrns1[val]);
                buf[ip] = static_cast<unsigned char>(nval);
                old = nval;
                continue;
            }
            val = grab1(1, code, code_size, buffer_pos, bit1ptr);

            if (val)
            {
                runChar = grab1(4, code, code_size, buffer_pos, bit1ptr);
                if (runChar == 15)
                {
                    runChar = grab1(8, code, code_size, buffer_pos, bit1ptr);

                    if (runChar == 255)
                    {
                        unsigned char part0 =
                            grab1(8, code, code_size, buffer_pos, bit1ptr);
                        unsigned char part1 =
                            grab1(8, code, code_size, buffer_pos, bit1ptr);
                        unsigned char part2 =
                            grab1(8, code, code_size, buffer_pos, bit1ptr);
                        runInt = part0 | (part1 << 8) | (part2 << 16);
                    }
                    else
                        runInt = runChar + 15;
                }
                else
                    runInt = runChar;

                val = grab1(3, code, code_size, buffer_pos, bit1ptr);
                if (val < 7)
                    nval = CPLUnsanitizedAdd<unsigned>(old, cmprtrns1[val]);
                else
                    nval = grab1(8, code, code_size, buffer_pos, bit1ptr);
                buf[ip] = static_cast<unsigned char>(nval);
                old = nval;
            }
            else
            {
                val = grab1(8, code, code_size, buffer_pos, bit1ptr);
                buf[ip] = val;
                old = val;
            }
        }
    }
}

/*****************************************************/
/* This function is a helper function for the BASIC  */
/* encoding operation.  It puts the value in val into*/
/* memory location pointed by pcode1+reg1 into nbit  */
/* number of bits.                                   */
/*****************************************************/
static void emit1(unsigned char val, int nbit, unsigned char *reg1,
                  int &bit1ptr, unsigned char *coded_buffer,
                  size_t &coded_buffer_pos, size_t coded_buffer_size)
{
    int shift;

    shift = 8 - nbit - bit1ptr;
    if (shift > 0)
    {
        *reg1 = static_cast<unsigned char>(*reg1 | (val << shift));
        bit1ptr += nbit;
        return;
    }
    if (shift < 0)
    {
        if (coded_buffer_pos >= coded_buffer_size)
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Out of encoding buffer");
            throw DecodeEncodeException();
        }
        coded_buffer[coded_buffer_pos] =
            static_cast<unsigned char>(*reg1 | (val >> (-shift)));
        coded_buffer_pos++;
        *reg1 = static_cast<unsigned char>(val << (8 + shift));
        bit1ptr = -shift;
        return;
    }
    if (coded_buffer_pos >= coded_buffer_size)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Out of encoding buffer");
        throw DecodeEncodeException();
    }
    coded_buffer[coded_buffer_pos] = static_cast<unsigned char>(*reg1 | val);
    coded_buffer_pos++;

    *reg1 = 0;
    bit1ptr = 0;
}

/*****************************************************/
/* This function is meat of the BASIC encoding       */
/* algorithm.  This function is called repeatedly by */
/* the basic_encode function to compress the data    */
/* according to its run length (run), last 2         */
/* different values (vold and old), and the current  */
/* value (val), into the memory location pointed by  */
/* pcode1+reg1.                                      */
/*****************************************************/
static void basic_encrypt(int *run, int *old, int *vold, int val,
                          unsigned char *reg1, int &bit1ptr,
                          unsigned char *coded_buffer, size_t &coded_buffer_pos,
                          size_t coded_buffer_size)
{
    if (*run < 4)
    {
        if (abs(*old - *vold) < 4)
            emit1((unsigned char)(*old - *vold + 3), 3, reg1, bit1ptr,
                  coded_buffer, coded_buffer_pos, coded_buffer_size);
        else
        {
            emit1((unsigned char)14, 4, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
            emit1((unsigned char)(*old), 8, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
        }

        while (*run > 1)
        {
            emit1((unsigned char)3, 3, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
            (*run)--;
        }

        *vold = *old;
        *old = val;
    }
    else
    {
        emit1((unsigned char)15, 4, reg1, bit1ptr, coded_buffer,
              coded_buffer_pos, coded_buffer_size);
        if (*run < 19)
        {
            emit1((unsigned char)(*run - 4), 4, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
        }
        else
        {
            emit1((unsigned char)15, 4, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
            if (*run < 274)
            {
                emit1((char)(*run - 19), 8, reg1, bit1ptr, coded_buffer,
                      coded_buffer_pos, coded_buffer_size);
            }
            else
            {
                emit1((unsigned char)255, 8, reg1, bit1ptr, coded_buffer,
                      coded_buffer_pos, coded_buffer_size);

                unsigned char part0 =
                    static_cast<unsigned char>((*run - 4) & 0xff);
                unsigned char part1 =
                    static_cast<unsigned char>(((*run - 4) >> 8) & 0xff);
                unsigned char part2 =
                    static_cast<unsigned char>(((*run - 4) >> 16) & 0xff);
                emit1(part0, 8, reg1, bit1ptr, coded_buffer, coded_buffer_pos,
                      coded_buffer_size);
                emit1(part1, 8, reg1, bit1ptr, coded_buffer, coded_buffer_pos,
                      coded_buffer_size);
                emit1(part2, 8, reg1, bit1ptr, coded_buffer, coded_buffer_pos,
                      coded_buffer_size);
            }
        }
        if (abs(*old - *vold) < 4)
        {
            emit1((unsigned char)(*old - *vold + 3), 3, reg1, bit1ptr,
                  coded_buffer, coded_buffer_pos, coded_buffer_size);
        }
        else
        {
            emit1((unsigned char)7, 3, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
            emit1((unsigned char)(*old), 8, reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);
        }
        *vold = *old;
        *old = val;
        *run = 1;
    }
}

/*****************************************************/
/* This function loops through the data given by     */
/* unencodedBuf, keeping track of run length.  When  */
/* the value of the data changes, it passes the run  */
/* length, last 2 differing values, the current      */
/* value, and the pointer in pcode1 buffer to encode */
/* the data into, to basic_encrypt function.         */
/*****************************************************/
static void basic_encode(const unsigned char *unencodedBuf,
                         unsigned char *coded_buffer, size_t coded_buffer_size,
                         int ns, int wid, size_t *totBytes)
{
    int val = 0;
    int bit1ptr = 0;
    const int ptop = ns * wid;
    unsigned char reg1 = 0;
    int run = 0;
    int old = unencodedBuf[0];
    int vold = 999999;

    size_t coded_buffer_pos = 0;

    for (int iw = 0; iw < wid; iw++)
    {
        for (int ip = iw; ip < ptop; ip += wid)
        {
            val = unencodedBuf[ip];

            if (val == old)
                run++;
            else
                basic_encrypt(&run, &old, &vold, val, &reg1, bit1ptr,
                              coded_buffer, coded_buffer_pos,
                              coded_buffer_size);
        }
    }

    /* purge of last code */
    basic_encrypt(&run, &old, &vold, val, &reg1, bit1ptr, coded_buffer,
                  coded_buffer_pos, coded_buffer_size);

    if (coded_buffer_pos >= coded_buffer_size)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Out of encoding buffer");
        throw DecodeEncodeException();
    }
    coded_buffer[coded_buffer_pos] = reg1;

    *totBytes = coded_buffer_pos;
    if (bit1ptr > 0)
        (*totBytes)++;
}

//////////////////////////////////////////////////////////////////////////
/// End of VICAR code
//////////////////////////////////////////////////////////////////////////

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr VICARBASICRasterBand::IReadBlock(int /*nXBlock*/, int nYBlock,
                                        void *pImage)

{
    VICARDataset *poGDS = reinterpret_cast<VICARDataset *>(poDS);

    const int nRecord = (nBand - 1) * nRasterYSize + nYBlock;
    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);

    if (poGDS->eAccess == GA_Update &&
        poGDS->m_anRecordOffsets[nRecord + 1] == 0)
    {
        memset(pImage, 0, static_cast<size_t>(nDTSize) * nRasterXSize);
        return CE_None;
    }

    // Find at which offset the compressed record is.
    // For BASIC compression, each compressed run is preceded by a uint32 value
    // givin its size, including the size of this uint32 value
    // For BASIC2 compression, the uint32 sizes of all records are put
    // immediately after the label.
    for (; poGDS->m_nLastRecordOffset <= nRecord; poGDS->m_nLastRecordOffset++)
    {
        CPLAssert(poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset + 1] ==
                  0);

        if (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC)
        {
            VSIFSeekL(poGDS->fpImage,
                      poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset] -
                          sizeof(GUInt32),
                      SEEK_SET);
        }
        else
        {
            VSIFSeekL(poGDS->fpImage,
                      poGDS->m_nImageOffsetWithoutNBB +
                          static_cast<vsi_l_offset>(sizeof(GUInt32)) *
                              poGDS->m_nLastRecordOffset,
                      SEEK_SET);
        }
        GUInt32 nSize;
        VSIFReadL(&nSize, 1, sizeof(nSize), poGDS->fpImage);
        CPL_LSBPTR32(&nSize);
        if ((poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC &&
             nSize <= sizeof(GUInt32)) ||
            (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC2 && nSize == 0))
        {
            CPLError(CE_Failure, CPLE_AppDefined, "Wrong size at record %d",
                     poGDS->m_nLastRecordOffset);
            return CE_Failure;
        }

        poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset + 1] =
            poGDS->m_anRecordOffsets[poGDS->m_nLastRecordOffset] + nSize;
    }

    unsigned int nSize;
    if (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC)
    {
        nSize = static_cast<unsigned>(poGDS->m_anRecordOffsets[nRecord + 1] -
                                      poGDS->m_anRecordOffsets[nRecord] -
                                      sizeof(GUInt32));
    }
    else
    {
        nSize = static_cast<unsigned>(poGDS->m_anRecordOffsets[nRecord + 1] -
                                      poGDS->m_anRecordOffsets[nRecord]);
    }
    if (nSize > 100 * 1000 * 1000 ||
        (nSize > 1000 &&
         (nSize - 11) / 4 > static_cast<unsigned>(nRasterXSize) * nDTSize))
    {
        return CE_Failure;
    }
    if (poGDS->m_abyCodedBuffer.size() < nSize)
    {
        try
        {
            poGDS->m_abyCodedBuffer.resize(nSize);
        }
        catch (const std::exception &e)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
            return CE_Failure;
        }
    }
    if (VSIFSeekL(poGDS->fpImage, poGDS->m_anRecordOffsets[nRecord],
                  SEEK_SET) != 0 ||
        VSIFReadL(&poGDS->m_abyCodedBuffer[0], nSize, 1, poGDS->fpImage) != 1)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot read record %d", nRecord);
        return CE_Failure;
    }

    try
    {
        basic_decode(poGDS->m_abyCodedBuffer.data(), nSize,
                     static_cast<unsigned char *>(pImage), nRasterXSize,
                     nDTSize);
    }
    catch (const DecodeEncodeException &)
    {
        return CE_Failure;
    }
#ifdef CPL_MSB
    if (nDTSize > 1)
    {
        GDALSwapWords(pImage, nDTSize, nRasterXSize, nDTSize);
    }
#endif
    return CE_None;
}

/************************************************************************/
/*                            IWriteBlock()                             */
/************************************************************************/

CPLErr VICARBASICRasterBand::IWriteBlock(int /*nXBlock*/, int nYBlock,
                                         void *pImage)

{
    VICARDataset *poGDS = reinterpret_cast<VICARDataset *>(poDS);
    if (poGDS->eAccess == GA_ReadOnly)
        return CE_Failure;
    if (!poGDS->m_bIsLabelWritten)
    {
        poGDS->WriteLabel();
        poGDS->m_nLabelSize = VSIFTellL(poGDS->fpImage);
        poGDS->m_anRecordOffsets[0] = poGDS->m_nLabelSize;
        if (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC)
        {
            poGDS->m_anRecordOffsets[0] += sizeof(GUInt32);
        }
        else
        {
            poGDS->m_anRecordOffsets[0] +=
                static_cast<vsi_l_offset>(sizeof(GUInt32)) * nRasterYSize;
        }
    }
    if (nYBlock != poGDS->m_nLastRecordOffset)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Lines must be written in sequential order");
        return CE_Failure;
    }

    const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
    const size_t nMaxEncodedSize =
        static_cast<size_t>(nRasterXSize) * nDTSize +
        static_cast<size_t>(nRasterXSize) * nDTSize / 2 + 11;
    if (poGDS->m_abyCodedBuffer.size() < nMaxEncodedSize)
    {
        try
        {
            poGDS->m_abyCodedBuffer.resize(nMaxEncodedSize);
        }
        catch (const std::exception &e)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
            return CE_Failure;
        }
    }

#ifdef CPL_MSB
    if (nDTSize > 1)
    {
        GDALSwapWords(pImage, nDTSize, nRasterXSize, nDTSize);
    }
#endif

    size_t nCodedSize = 0;
    try
    {
        basic_encode(
            static_cast<unsigned char *>(pImage), &poGDS->m_abyCodedBuffer[0],
            poGDS->m_abyCodedBuffer.size(), nRasterXSize, nDTSize, &nCodedSize);
    }
    catch (const DecodeEncodeException &)
    {
        return CE_Failure;
    }

#ifdef CPL_MSB
    if (nDTSize > 1)
    {
        GDALSwapWords(pImage, nDTSize, nRasterXSize, nDTSize);
    }
#endif

    if (poGDS->m_eCompress == VICARDataset::COMPRESS_BASIC)
    {
        VSIFSeekL(poGDS->fpImage,
                  poGDS->m_anRecordOffsets[nYBlock] - sizeof(GUInt32),
                  SEEK_SET);
        GUInt32 nSizeToWrite =
            static_cast<GUInt32>(nCodedSize + sizeof(GUInt32));
        CPL_LSBPTR32(&nSizeToWrite);
        VSIFWriteL(&nSizeToWrite, sizeof(GUInt32), 1, poGDS->fpImage);
        VSIFWriteL(poGDS->m_abyCodedBuffer.data(), nCodedSize, 1,
                   poGDS->fpImage);
        poGDS->m_anRecordOffsets[nYBlock + 1] =
            poGDS->m_anRecordOffsets[nYBlock] + nCodedSize + sizeof(GUInt32);
    }
    else
    {
        VSIFSeekL(poGDS->fpImage,
                  poGDS->m_nLabelSize +
                      static_cast<vsi_l_offset>(nYBlock) * sizeof(GUInt32),
                  SEEK_SET);
        GUInt32 nSizeToWrite = static_cast<GUInt32>(nCodedSize);
        CPL_LSBPTR32(&nSizeToWrite);
        VSIFWriteL(&nSizeToWrite, sizeof(GUInt32), 1, poGDS->fpImage);
        VSIFSeekL(poGDS->fpImage, poGDS->m_anRecordOffsets[nYBlock], SEEK_SET);
        VSIFWriteL(poGDS->m_abyCodedBuffer.data(), nCodedSize, 1,
                   poGDS->fpImage);
        poGDS->m_anRecordOffsets[nYBlock + 1] =
            poGDS->m_anRecordOffsets[nYBlock] + nCodedSize;
    }

    poGDS->m_nLastRecordOffset++;

    return CE_None;
}

/************************************************************************/
/*                            VICARDataset()                            */
/************************************************************************/

VICARDataset::VICARDataset()

{
    m_oJSonLabel.Deinit();
    m_oSrcJSonLabel.Deinit();
}

/************************************************************************/
/*                           ~VICARDataset()                            */
/************************************************************************/

VICARDataset::~VICARDataset()

{
    VICARDataset::Close();
}

/************************************************************************/
/*                              Close()                                 */
/************************************************************************/

CPLErr VICARDataset::Close()
{
    CPLErr eErr = CE_None;
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
    {
        if (!m_bIsLabelWritten)
            WriteLabel();

        if (VICARDataset::FlushCache(true) != CE_None)
            eErr = CE_Failure;

        PatchLabel();
        if (fpImage)
            VSIFCloseL(fpImage);

        if (GDALPamDataset::Close() != CE_None)
            eErr = CE_Failure;
    }
    return eErr;
}

/************************************************************************/
/*                           GetSpatialRef()                            */
/************************************************************************/

const OGRSpatialReference *VICARDataset::GetSpatialRef() const

{
    if (!m_oSRS.IsEmpty())
        return &m_oSRS;

    return GDALPamDataset::GetSpatialRef();
}

/************************************************************************/
/*                           SetSpatialRef()                            */
/************************************************************************/

CPLErr VICARDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
{
    if (eAccess == GA_ReadOnly)
        return GDALPamDataset::SetSpatialRef(poSRS);
    if (poSRS)
        m_oSRS = *poSRS;
    else
        m_oSRS.Clear();
    InvalidateLabel();
    return CE_None;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr VICARDataset::GetGeoTransform(double *padfTransform)

{
    if (m_bGotTransform)
    {
        memcpy(padfTransform, &m_adfGeoTransform[0], sizeof(double) * 6);
        return CE_None;
    }

    return GDALPamDataset::GetGeoTransform(padfTransform);
}

/************************************************************************/
/*                          SetGeoTransform()                           */
/************************************************************************/

CPLErr VICARDataset::SetGeoTransform(double *padfTransform)

{
    if (eAccess == GA_ReadOnly)
        return GDALPamDataset::SetGeoTransform(padfTransform);
    if (padfTransform[1] <= 0.0 || padfTransform[1] != -padfTransform[5] ||
        padfTransform[2] != 0.0 || padfTransform[4] != 0.0)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Only north-up geotransform with square pixels supported");
        return CE_Failure;
    }
    m_bGotTransform = true;
    memcpy(&m_adfGeoTransform[0], padfTransform, sizeof(double) * 6);
    InvalidateLabel();
    return CE_None;
}

/************************************************************************/
/*                         GetLabelOffset()                             */
/************************************************************************/

int VICARDataset::GetLabelOffset(GDALOpenInfo *poOpenInfo)

{
    if (poOpenInfo->pabyHeader == nullptr || poOpenInfo->fpL == nullptr)
        return -1;

    std::string osHeader;
    const char *pszHeader =
        reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
    // Some PDS3 images include a VICAR header pointed by ^IMAGE_HEADER.
    // If the user sets GDAL_TRY_PDS3_WITH_VICAR=YES, then we will gracefully
    // hand over the file to the VICAR dataset.
    vsi_l_offset nOffset = 0;
    if (CPLTestBool(CPLGetConfigOption("GDAL_TRY_PDS3_WITH_VICAR", "NO")) &&
        !STARTS_WITH(poOpenInfo->pszFilename, "/vsisubfile/") &&
        (nOffset = VICARDataset::GetVICARLabelOffsetFromPDS3(
             pszHeader, poOpenInfo->fpL, osHeader)) > 0)
    {
        pszHeader = osHeader.c_str();
    }

    if ((poOpenInfo->nOpenFlags & GDAL_OF_RASTER) == 0 &&
        (poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) != 0)
    {
        // If opening in vector-only mode, then check when have NBB != 0
        const char *pszNBB = strstr(pszHeader, "NBB");
        if (pszNBB == nullptr)
            return -1;
        const char *pszEqualSign = strchr(pszNBB, '=');
        if (pszEqualSign == nullptr)
            return -1;
        if (atoi(pszEqualSign + 1) == 0)
            return -1;
    }
    if (strstr(pszHeader, "LBLSIZE") != nullptr &&
        strstr(pszHeader, "FORMAT") != nullptr &&
        strstr(pszHeader, "NL") != nullptr &&
        strstr(pszHeader, "NS") != nullptr &&
        strstr(pszHeader, "NB") != nullptr)
    {
        return static_cast<int>(nOffset);
    }
    return -1;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int VICARDataset::Identify(GDALOpenInfo *poOpenInfo)

{
    return GetLabelOffset(poOpenInfo) >= 0;
}

/************************************************************************/
/*                        GetRawBinaryLayout()                          */
/************************************************************************/

bool VICARDataset::GetRawBinaryLayout(GDALDataset::RawBinaryLayout &sLayout)
{
    if (!RawDataset::GetRawBinaryLayout(sLayout))
        return false;
    sLayout.osRawFilename = GetDescription();
    return true;
}

/************************************************************************/
/*                      GetMetadataDomainList()                         */
/************************************************************************/

char **VICARDataset::GetMetadataDomainList()
{
    return BuildMetadataDomainList(nullptr, FALSE, "", "json:VICAR", nullptr);
}

/************************************************************************/
/*                             GetMetadata()                            */
/************************************************************************/

char **VICARDataset::GetMetadata(const char *pszDomain)
{
    if (pszDomain != nullptr && EQUAL(pszDomain, "json:VICAR"))
    {
        if (m_aosVICARMD.empty())
        {
            if (eAccess == GA_Update && !m_oJSonLabel.IsValid())
            {
                BuildLabel();
            }
            CPLAssert(m_oJSonLabel.IsValid());
            const CPLString osJson =
                m_oJSonLabel.Format(CPLJSONObject::PrettyFormat::Pretty);
            m_aosVICARMD.InsertString(0, osJson.c_str());
        }
        return m_aosVICARMD.List();
    }
    return GDALPamDataset::GetMetadata(pszDomain);
}

/************************************************************************/
/*                           InvalidateLabel()                          */
/************************************************************************/

void VICARDataset::InvalidateLabel()
{
    m_oJSonLabel.Deinit();
    m_aosVICARMD.Clear();
}

/************************************************************************/
/*                             SetMetadata()                            */
/************************************************************************/

CPLErr VICARDataset::SetMetadata(char **papszMD, const char *pszDomain)
{
    if (m_bUseSrcLabel && eAccess == GA_Update && pszDomain != nullptr &&
        EQUAL(pszDomain, "json:VICAR"))
    {
        m_oSrcJSonLabel.Deinit();
        InvalidateLabel();
        if (papszMD != nullptr && papszMD[0] != nullptr)
        {
            CPLJSONDocument oJSONDocument;
            const GByte *pabyData = reinterpret_cast<const GByte *>(papszMD[0]);
            if (!oJSONDocument.LoadMemory(pabyData))
            {
                return CE_Failure;
            }

            m_oSrcJSonLabel = oJSONDocument.GetRoot();
            if (!m_oSrcJSonLabel.IsValid())
            {
                return CE_Failure;
            }
        }
        return CE_None;
    }
    return GDALPamDataset::SetMetadata(papszMD, pszDomain);
}

/************************************************************************/
/*                         SerializeString()                            */
/************************************************************************/

static std::string SerializeString(const std::string &s)
{
    return '\'' + CPLString(s).replaceAll('\'', "''").replaceAll('\n', "\\n") +
           '\'';
}

/************************************************************************/
/*                        WriteLabelItemValue()                         */
/************************************************************************/

static void WriteLabelItemValue(std::string &osLabel, const CPLJSONObject &obj)
{
    const auto eType(obj.GetType());
    if (eType == CPLJSONObject::Type::Boolean)
    {
        osLabel += CPLSPrintf("%d", obj.ToBool() ? 1 : 0);
    }
    else if (eType == CPLJSONObject::Type::Integer)
    {
        osLabel += CPLSPrintf("%d", obj.ToInteger());
    }
    else if (eType == CPLJSONObject::Type::Long)
    {
        std::string osVal(
            CPLSPrintf("%.18g", static_cast<double>(obj.ToLong())));
        if (osVal.find('.') == std::string::npos)
            osVal += ".0";
        osLabel += osVal;
    }
    else if (eType == CPLJSONObject::Type::Double)
    {
        double dfVal = obj.ToDouble();
        if (dfVal >= static_cast<double>(std::numeric_limits<GIntBig>::min()) &&
            dfVal <= static_cast<double>(std::numeric_limits<GIntBig>::max()) &&
            static_cast<double>(static_cast<GIntBig>(dfVal)) == dfVal)
        {
            std::string osVal(CPLSPrintf("%.18g", dfVal));
            if (osVal.find('.') == std::string::npos)
                osVal += ".0";
            osLabel += osVal;
        }
        else
        {
            osLabel += CPLSPrintf("%.15g", dfVal);
        }
    }
    else if (eType == CPLJSONObject::Type::String)
    {
        osLabel += SerializeString(obj.ToString());
    }
    else if (eType == CPLJSONObject::Type::Array)
    {
        const auto oArray = obj.ToArray();
        osLabel += '(';
        for (int i = 0; i < oArray.Size(); i++)
        {
            if (i > 0)
                osLabel += ',';
            WriteLabelItemValue(osLabel, oArray[i]);
        }
        osLabel += ')';
    }
    else if (eType == CPLJSONObject::Type::Null)
    {
        osLabel += "'NULL'";
    }
    else
    {
        osLabel +=
            SerializeString(obj.Format(CPLJSONObject::PrettyFormat::Plain));
    }
}

/************************************************************************/
/*                      SanitizeItemName()                              */
/************************************************************************/

static std::string SanitizeItemName(const std::string &osItemName)
{
    std::string osRet(osItemName);
    if (osRet.size() > 32)
        osRet.resize(32);
    if (osRet.empty())
        return "UNNAMED";
    if (osRet[0] < 'A' || osRet[0] > 'Z')
        osRet[0] = 'X';  // item name must start with a letter
    for (size_t i = 1; i < osRet.size(); i++)
    {
        char ch = osRet[i];
        if (ch >= 'a' && ch <= 'z')
            osRet[i] = ch - 'a' + 'A';
        else if (!((ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') ||
                   ch == '_'))
        {
            osRet[i] = '_';
        }
    }
    if (osRet != osItemName)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Label item name %s has been sanitized to %s",
                 osItemName.c_str(), osRet.c_str());
    }
    return osRet;
}

/************************************************************************/
/*                        WriteLabelItem()                              */
/************************************************************************/

static void WriteLabelItem(std::string &osLabel, const CPLJSONObject &obj,
                           const std::string &osItemName = std::string())
{
    osLabel += ' ';
    osLabel +=
        SanitizeItemName(osItemName.empty() ? obj.GetName() : osItemName);
    osLabel += '=';
    WriteLabelItemValue(osLabel, obj);
}

/************************************************************************/
/*                           WriteLabel()                               */
/************************************************************************/

void VICARDataset::WriteLabel()
{
    m_bIsLabelWritten = true;

    if (!m_oJSonLabel.IsValid())
        BuildLabel();

    std::string osLabel;
    auto children = m_oJSonLabel.GetChildren();
    for (const auto &child : children)
    {
        const auto osName(child.GetName());
        if (osName == "LBLSIZE" || osName == "PROPERTY" || osName == "TASK")
            continue;
        std::string osNameSubst;
        if (osName == "DAT_TIM" || osName == "USER")
        {
            osNameSubst = osName + '_';
        }
        WriteLabelItem(osLabel, child, osNameSubst);
    }

    auto property = m_oJSonLabel.GetObj("PROPERTY");
    if (property.IsValid() && property.GetType() == CPLJSONObject::Type::Object)
    {
        children = property.GetChildren();
        for (const auto &child : children)
        {
            if (child.GetType() == CPLJSONObject::Type::Object)
            {
                osLabel += " PROPERTY=" + SerializeString(child.GetName());
                auto childrenProperty = child.GetChildren();
                for (const auto &childProperty : childrenProperty)
                {
                    const auto osName(child.GetName());
                    std::string osNameSubst;
                    if (osName == "LBLSIZE" || osName == "PROPERTY" ||
                        osName == "TASK" || osName == "DAT_TIM" ||
                        osName == "USER")
                    {
                        osNameSubst = osName + '_';
                    }
                    WriteLabelItem(osLabel, childProperty, osNameSubst);
                }
            }
        }
    }

    auto task = m_oJSonLabel.GetObj("TASK");
    if (task.IsValid() && task.GetType() == CPLJSONObject::Type::Object)
    {
        children = task.GetChildren();
        for (const auto &child : children)
        {
            if (child.GetType() == CPLJSONObject::Type::Object)
            {
                osLabel += " TASK=" + SerializeString(child.GetName());
                auto oUser = child.GetObj("USER");
                if (oUser.IsValid())
                    WriteLabelItem(osLabel, oUser);
                auto oDatTim = child.GetObj("DAT_TIM");
                if (oDatTim.IsValid())
                    WriteLabelItem(osLabel, oDatTim);
                auto childrenProperty = child.GetChildren();
                for (const auto &childProperty : childrenProperty)
                {
                    const auto osName(child.GetName());
                    if (osName == "USER" || osName == "DAT_TIM")
                        continue;
                    std::string osNameSubst;
                    if (osName == "LBLSIZE" || osName == "PROPERTY" ||
                        osName == "TASK")
                    {
                        osNameSubst = osName + '_';
                    }
                    WriteLabelItem(osLabel, childProperty, osNameSubst);
                }
            }
        }
    }

    // Figure out label size, round it to the next multiple of RECSIZE
    constexpr size_t MAX_LOG10_LBLSIZE = 10;
    size_t nLabelSize = strlen("LBLSIZE=") + MAX_LOG10_LBLSIZE + osLabel.size();
    nLabelSize =
        (nLabelSize + m_nRecordSize - 1) / m_nRecordSize * m_nRecordSize;
    std::string osLabelSize(
        CPLSPrintf("LBLSIZE=%d", static_cast<int>(nLabelSize)));
    while (osLabelSize.size() < strlen("LBLSIZE=") + MAX_LOG10_LBLSIZE)
        osLabelSize += ' ';
    osLabel = osLabelSize + osLabel;
    CPLAssert(osLabel.size() <= nLabelSize);

    // Write label
    VSIFSeekL(fpImage, 0, SEEK_SET);
    VSIFWriteL(osLabel.data(), 1, osLabel.size(), fpImage);
    const size_t nZeroPadding = nLabelSize - osLabel.size();
    if (nZeroPadding)
    {
        VSIFWriteL(std::string(nZeroPadding, '\0').data(), 1, nZeroPadding,
                   fpImage);
    }

    if (m_bInitToNodata && m_eCompress == COMPRESS_NONE)
    {
        const int nDTSize =
            GDALGetDataTypeSizeBytes(GetRasterBand(1)->GetRasterDataType());
        VSIFTruncateL(fpImage, VSIFTellL(fpImage) +
                                   static_cast<vsi_l_offset>(nRasterXSize) *
                                       nRasterYSize * nBands * nDTSize);
    }

    // Patch band offsets to take into account label
    for (int i = 0; i < nBands; i++)
    {
        auto poBand = dynamic_cast<VICARRawRasterBand *>(GetRasterBand(i + 1));
        if (poBand)
            poBand->nImgOffset += nLabelSize;
    }
}

/************************************************************************/
/*                           PatchLabel()                               */
/************************************************************************/

void VICARDataset::PatchLabel()
{
    if (eAccess == GA_ReadOnly || m_eCompress == COMPRESS_NONE)
        return;

    VSIFSeekL(fpImage, 0, SEEK_END);
    const vsi_l_offset nFileSize = VSIFTellL(fpImage);
    VSIFSeekL(fpImage, 0, SEEK_SET);
    std::string osBuffer;
    osBuffer.resize(1024);
    size_t nRead = VSIFReadL(&osBuffer[0], 1, 1024, fpImage);

    {
        CPLString osEOCI1;
        osEOCI1.Printf("%u", static_cast<unsigned>(nFileSize));
        while (osEOCI1.size() < 10)
            osEOCI1 += ' ';
        size_t nPos = osBuffer.find("EOCI1=");
        CPLAssert(nPos <= nRead - (strlen("EOCI1=") + 10));
        memcpy(&osBuffer[nPos + strlen("EOCI1=")], osEOCI1.data(), 10);
    }

    {
        CPLString osEOCI2;
        osEOCI2.Printf("%u", static_cast<unsigned>(nFileSize >> 32));
        while (osEOCI2.size() < 10)
            osEOCI2 += ' ';
        size_t nPos = osBuffer.find("EOCI2=");
        CPLAssert(nPos <= nRead - (strlen("EOCI2=") + 10));
        memcpy(&osBuffer[nPos + strlen("EOCI2=")], osEOCI2.data(), 10);
    }
    VSIFSeekL(fpImage, 0, SEEK_SET);
    VSIFWriteL(&osBuffer[0], 1, nRead, fpImage);
}

/**
 * Get or create CPLJSONObject.
 * @param  oParent Parent CPLJSONObject.
 * @param  osKey  Key name.
 * @return         CPLJSONObject class instance.
 */
static CPLJSONObject GetOrCreateJSONObject(CPLJSONObject &oParent,
                                           const std::string &osKey)
{
    CPLJSONObject oChild = oParent[osKey];
    if (oChild.IsValid() && oChild.GetType() != CPLJSONObject::Type::Object)
    {
        oParent.Delete(osKey);
        oChild.Deinit();
    }

    if (!oChild.IsValid())
    {
        oChild = CPLJSONObject();
        oParent.Add(osKey, oChild);
    }
    return oChild;
}

/************************************************************************/
/*                           BuildLabel()                               */
/************************************************************************/

void VICARDataset::BuildLabel()
{
    CPLJSONObject oLabel = m_oSrcJSonLabel;
    if (!oLabel.IsValid())
    {
        oLabel = CPLJSONObject();
    }

    oLabel.Set("LBLSIZE", 0);  // to be overridden later

    if (!oLabel.GetObj("TYPE").IsValid())
        oLabel.Set("TYPE", "IMAGE");

    const auto eType = GetRasterBand(1)->GetRasterDataType();
    const char *pszFormat = "";
    switch (eType)
    {
        case GDT_Byte:
            pszFormat = "BYTE";
            break;
        case GDT_Int16:
            pszFormat = "HALF";
            break;
        case GDT_Int32:
            pszFormat = "FULL";
            break;
        case GDT_Float32:
            pszFormat = "REAL";
            break;
        case GDT_Float64:
            pszFormat = "DOUB";
            break;
        case GDT_CFloat32:
            pszFormat = "COMP";
            break;
        default:
            CPLAssert(false);
            break;
    }
    oLabel.Set("FORMAT", pszFormat);

    oLabel.Set("BUFSIZ", m_nRecordSize);  // arbitrary value
    oLabel.Set("DIM", 3);
    oLabel.Set("EOL", 0);
    oLabel.Set("RECSIZE", m_nRecordSize);
    oLabel.Set("ORG", "BSQ");
    oLabel.Set("NL", nRasterYSize);
    oLabel.Set("NS", nRasterXSize);
    oLabel.Set("NB", nBands);
    oLabel.Set("N1", nRasterXSize);
    oLabel.Set("N2", nRasterYSize);
    oLabel.Set("N3", nBands);
    oLabel.Set("N4", 0);
    oLabel.Set("NBB", 0);
    oLabel.Set("NLB", 0);
    oLabel.Set("HOST", "X86-64-LINX");
    oLabel.Set("INTFMT", "LOW");
    oLabel.Set("REALFMT", "RIEEE");
    oLabel.Set("BHOST", "X86-64-LINX");
    oLabel.Set("BINTFMT", "LOW");
    if (!oLabel.GetObj("BLTYPE").IsValid())
        oLabel.Set("BLTYPE", "");
    oLabel.Set("COMPRESS", m_eCompress == COMPRESS_BASIC    ? "BASIC"
                           : m_eCompress == COMPRESS_BASIC2 ? "BASIC2"
                                                            : "NONE");
    if (m_eCompress == COMPRESS_NONE)
    {
        oLabel.Set("EOCI1", 0);
        oLabel.Set("EOCI2", 0);
    }
    else
    {
        // To be later patched. Those fake values must take 10 bytes
        // (8 + 2 single quotes) so that they can be later replaced by a
        // integer of maximum value 4294967295 (10 digits)
        oLabel.Set("EOCI1", "XXXXXXXX");
        oLabel.Set("EOCI2", "XXXXXXXX");
    }

    if (m_bUseSrcMap)
    {
        auto oMap = oLabel.GetObj("PROPERTY/MAP");
        if (oMap.IsValid() && oMap.GetType() == CPLJSONObject::Type::Object)
        {
            if (!m_osTargetName.empty())
                oMap.Set("TARGET_NAME", m_osTargetName);
            if (!m_osLatitudeType.empty())
                oMap.Set("COORDINATE_SYSTEM_NAME", m_osLatitudeType);
            if (!m_osLongitudeDirection.empty())
                oMap.Set("POSITIVE_LONGITUDE_DIRECTION",
                         m_osLongitudeDirection);
        }
    }
    else if (m_bGeoRefFormatIsMIPL)
    {
        auto oProperty = oLabel.GetObj("PROPERTY");
        if (oProperty.IsValid())
        {
            oProperty.Delete("MAP");
            oProperty.Delete("GEOTIFF");
        }
        if (!m_oSRS.IsEmpty())
        {
            BuildLabelPropertyMap(oLabel);
        }
    }
    else
    {
        auto oProperty = oLabel.GetObj("PROPERTY");
        if (oProperty.IsValid())
        {
            oProperty.Delete("MAP");
            oProperty.Delete("GEOTIFF");
        }
        if (!m_oSRS.IsEmpty())
        {
            BuildLabelPropertyGeoTIFF(oLabel);
        }
    }

    m_oJSonLabel = oLabel;
}

/************************************************************************/
/*                        BuildLabelPropertyMap()                       */
/************************************************************************/

void VICARDataset::BuildLabelPropertyMap(CPLJSONObject &oLabel)
{
    if (m_oSRS.IsProjected() || m_oSRS.IsGeographic())
    {
        auto oProperty = GetOrCreateJSONObject(oLabel, "PROPERTY");
        auto oMap = GetOrCreateJSONObject(oProperty, "MAP");

        const char *pszDatum = m_oSRS.GetAttrValue("DATUM");
        CPLString osTargetName(m_osTargetName);
        if (osTargetName.empty())
        {
            if (pszDatum && STARTS_WITH(pszDatum, "D_"))
            {
                osTargetName = pszDatum + 2;
            }
            else if (pszDatum)
            {
                osTargetName = pszDatum;
            }
        }
        if (!osTargetName.empty())
            oMap.Add("TARGET_NAME", osTargetName);

        oMap.Add("A_AXIS_RADIUS", m_oSRS.GetSemiMajor() / 1000.0);
        oMap.Add("B_AXIS_RADIUS", m_oSRS.GetSemiMajor() / 1000.0);
        oMap.Add("C_AXIS_RADIUS", m_oSRS.GetSemiMinor() / 1000.0);

        if (!m_osLatitudeType.empty())
            oMap.Add("COORDINATE_SYSTEM_NAME", m_osLatitudeType);
        else
            oMap.Add("COORDINATE_SYSTEM_NAME", "PLANETOCENTRIC");

        if (!m_osLongitudeDirection.empty())
            oMap.Add("POSITIVE_LONGITUDE_DIRECTION", m_osLongitudeDirection);
        else
            oMap.Add("POSITIVE_LONGITUDE_DIRECTION", "EAST");

        const char *pszProjection = m_oSRS.GetAttrValue("PROJECTION");
        if (pszProjection == nullptr)
        {
            oMap.Add("MAP_PROJECTION_TYPE", "SIMPLE_CYLINDRICAL");
            oMap.Add("CENTER_LONGITUDE", 0.0);
            oMap.Add("CENTER_LATITUDE", 0.0);
        }
        else if (EQUAL(pszProjection, SRS_PT_EQUIRECTANGULAR))
        {
            oMap.Add("MAP_PROJECTION_TYPE", "EQUIRECTANGULAR");
            if (m_oSRS.GetNormProjParm(SRS_PP_LATITUDE_OF_ORIGIN, 0.0) != 0.0)
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Ignoring %s. Only 0 value supported",
                         SRS_PP_LATITUDE_OF_ORIGIN);
            }
            oMap.Add("CENTER_LONGITUDE",
                     m_oSRS.GetNormProjParm(SRS_PP_CENTRAL_MERIDIAN, 0.0));
            const double dfCenterLat =
                m_oSRS.GetNormProjParm(SRS_PP_STANDARD_PARALLEL_1, 0.0);
            oMap.Add("CENTER_LATITUDE", dfCenterLat);
        }
        else if (EQUAL(pszProjection, SRS_PT_SINUSOIDAL))
        {
            oMap.Add("MAP_PROJECTION_TYPE", "SINUSOIDAL");
            oMap.Add("CENTER_LONGITUDE",
                     m_oSRS.GetNormProjParm(SRS_PP_LONGITUDE_OF_CENTER, 0.0));
            oMap.Add("CENTER_LATITUDE", 0.0);
        }
        else
        {
            CPLError(CE_Warning, CPLE_NotSupported,
                     "Projection %s not supported", pszProjection);
        }

        if (oMap["MAP_PROJECTION_TYPE"].IsValid())
        {
            if (m_oSRS.GetNormProjParm(SRS_PP_FALSE_EASTING, 0.0) != 0.0)
            {
                CPLError(CE_Warning, CPLE_NotSupported,
                         "Ignoring %s. Only 0 value supported",
                         SRS_PP_FALSE_EASTING);
            }
            if (m_oSRS.GetNormProjParm(SRS_PP_FALSE_NORTHING, 0.0) != 0.0)
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Ignoring %s. Only 0 value supported",
                         SRS_PP_FALSE_NORTHING);
            }

            if (m_bGotTransform)
            {
                const double dfDegToMeter =
                    m_oSRS.GetSemiMajor() * M_PI / 180.0;
                if (m_oSRS.IsProjected())
                {
                    const double dfLinearUnits = m_oSRS.GetLinearUnits();
                    const double dfScale = m_adfGeoTransform[1] * dfLinearUnits;
                    oMap.Add("SAMPLE_PROJECTION_OFFSET",
                             -m_adfGeoTransform[0] * dfLinearUnits / dfScale -
                                 0.5);
                    oMap.Add("LINE_PROJECTION_OFFSET",
                             m_adfGeoTransform[3] * dfLinearUnits / dfScale -
                                 0.5);
                    oMap.Add("MAP_SCALE", dfScale / 1000.0);
                }
                else if (m_oSRS.IsGeographic())
                {
                    const double dfScale = m_adfGeoTransform[1] * dfDegToMeter;
                    oMap.Add("SAMPLE_PROJECTION_OFFSET",
                             -m_adfGeoTransform[0] * dfDegToMeter / dfScale -
                                 0.5);
                    oMap.Add("LINE_PROJECTION_OFFSET",
                             m_adfGeoTransform[3] * dfDegToMeter / dfScale -
                                 0.5);
                    oMap.Add("MAP_SCALE", dfScale / 1000.0);
                }
            }
        }
    }
    else
    {
        CPLError(CE_Warning, CPLE_NotSupported, "SRS not supported");
    }
}

/************************************************************************/
/*                    BuildLabelPropertyGeoTIFF()                       */
/************************************************************************/

void VICARDataset::BuildLabelPropertyGeoTIFF(CPLJSONObject &oLabel)
{
    auto oProperty = GetOrCreateJSONObject(oLabel, "PROPERTY");
    auto oGeoTIFF = GetOrCreateJSONObject(oProperty, "GEOTIFF");

    // Ported from Vicar Open Source: Afids expects to be able to read
    // NITF_NROWS and NITF_NCOLS

    oGeoTIFF.Add("NITF_NROWS", nRasterYSize);
    oGeoTIFF.Add("NITF_NCOLS", nRasterXSize);

    // Create a in-memory GeoTIFF file

    char szFilename[100] = {};
    snprintf(szFilename, sizeof(szFilename), "/vsimem/vicar_tmp_%p.tif", this);
    GDALDriver *poGTiffDriver =
        GDALDriver::FromHandle(GDALGetDriverByName("GTiff"));
    if (poGTiffDriver == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "GTiff driver not available");
        return;
    }
    const char *const apszOptions[] = {"GEOTIFF_VERSION=1.0", nullptr};
    auto poDS = std::unique_ptr<GDALDataset>(
        poGTiffDriver->Create(szFilename, 1, 1, 1, GDT_Byte, apszOptions));
    if (!poDS)
        return;
    poDS->SetSpatialRef(&m_oSRS);
    if (m_bGotTransform)
        poDS->SetGeoTransform(&m_adfGeoTransform[0]);
    poDS->SetMetadataItem(GDALMD_AREA_OR_POINT,
                          GetMetadataItem(GDALMD_AREA_OR_POINT));
    poDS.reset();

    // Open it with libtiff/libgeotiff
    VSILFILE *fpL = VSIFOpenL(szFilename, "r");
    if (fpL == nullptr)
    {
        VSIUnlink(szFilename);
        return;
    }

    TIFF *hTIFF = VSI_TIFFOpen(szFilename, "r", fpL);
    CPLAssert(hTIFF);

    GTIF *hGTIF = GTIFNew(hTIFF);
    CPLAssert(hGTIF);

    // Get geotiff keys and write them as VICAR metadata
    for (const auto &gkey : GTiffShortKeys)
    {
        unsigned short val = 0;
        if (GDALGTIFKeyGetSHORT(hGTIF, gkey, &val, 0, 1))
        {
            oGeoTIFF.Add(
                CPLString(GTIFKeyName(gkey)).toupper(),
                CPLSPrintf("%d(%s)", val, GTIFValueNameEx(hGTIF, gkey, val)));
        }
    }

    for (const auto &gkey : GTiffDoubleKeys)
    {
        double val = 0;
        if (GDALGTIFKeyGetDOUBLE(hGTIF, gkey, &val, 0, 1))
        {
            oGeoTIFF.Add(CPLString(GTIFKeyName(gkey)).toupper(),
                         CPLSPrintf("%.18g", val));
        }
    }

    for (const auto &gkey : GTiffAsciiKeys)
    {
        char szAscii[1024];
        if (GDALGTIFKeyGetASCII(hGTIF, gkey, szAscii,
                                static_cast<int>(sizeof(szAscii))))
        {
            oGeoTIFF.Add(CPLString(GTIFKeyName(gkey)).toupper(), szAscii);
        }
    }

    GTIFFree(hGTIF);

    // Get geotiff tags and write them as VICAR metadata
    const std::map<int, const char *> oMapTagCodeToName = {
        {TIFFTAG_GEOPIXELSCALE, "MODELPIXELSCALETAG"},
        {TIFFTAG_GEOTIEPOINTS, "MODELTIEPOINTTAG"},
        {TIFFTAG_GEOTRANSMATRIX, "MODELTRANSFORMATIONTAG"}};

    for (const auto &kv : oMapTagCodeToName)
    {
        uint16_t nCount = 0;
        double *padfValues = nullptr;
        if (TIFFGetField(hTIFF, kv.first, &nCount, &padfValues))
        {
            std::string osVal("(");
            for (uint16_t i = 0; i < nCount; ++i)
            {
                if (i > 0)
                    osVal += ',';
                osVal += CPLSPrintf("%.18g", padfValues[i]);
            }
            osVal += ')';
            oGeoTIFF.Add(kv.second, osVal);
        }
    }

    XTIFFClose(hTIFF);
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
    VSIUnlink(szFilename);
}

/************************************************************************/
/*                       ReadProjectionFromMapGroup()                   */
/************************************************************************/

void VICARDataset::ReadProjectionFromMapGroup()
{
    double dfXDim = 1.0;
    double dfYDim = 1.0;

    const char *value = GetKeyword("MAP.MAP_SCALE");
    if (strlen(value) > 0)
    {
        dfXDim = CPLAtof(value) * 1000.0;
        dfYDim = CPLAtof(value) * -1 * 1000.0;
    }

    const double dfSampleOffset_Shift =
        CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Shift", "0.5"));

    const double dfLineOffset_Shift =
        CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Shift", "0.5"));

    const double dfSampleOffset_Mult =
        CPLAtof(CPLGetConfigOption("PDS_SampleProjOffset_Mult", "-1.0"));

    const double dfLineOffset_Mult =
        CPLAtof(CPLGetConfigOption("PDS_LineProjOffset_Mult", "1.0"));

    /***********   Grab LINE_PROJECTION_OFFSET ************/
    double dfULYMap = 0.5;

    value = GetKeyword("MAP.LINE_PROJECTION_OFFSET");
    if (strlen(value) > 0)
    {
        const double yulcenter = CPLAtof(value);
        dfULYMap =
            ((yulcenter + dfLineOffset_Shift) * -dfYDim * dfLineOffset_Mult);
    }
    /***********   Grab SAMPLE_PROJECTION_OFFSET ************/
    double dfULXMap = 0.5;

    value = GetKeyword("MAP.SAMPLE_PROJECTION_OFFSET");
    if (strlen(value) > 0)
    {
        const double xulcenter = CPLAtof(value);
        dfULXMap =
            ((xulcenter + dfSampleOffset_Shift) * dfXDim * dfSampleOffset_Mult);
    }

    /* ==================================================================== */
    /*      Get the coordinate system.                                      */
    /* ==================================================================== */
    bool bProjectionSet = true;

    /***********  Grab TARGET_NAME  ************/
    /**** This is the planets name i.e. MARS ***/
    const CPLString target_name = GetKeyword("MAP.TARGET_NAME");

    /**********   Grab MAP_PROJECTION_TYPE *****/
    const CPLString map_proj_name = GetKeyword("MAP.MAP_PROJECTION_TYPE");

    /******  Grab semi_major & convert to KM ******/
    const double semi_major = CPLAtof(GetKeyword("MAP.A_AXIS_RADIUS")) * 1000.0;

    /******  Grab semi-minor & convert to KM ******/
    const double semi_minor = CPLAtof(GetKeyword("MAP.C_AXIS_RADIUS")) * 1000.0;

    /***********   Grab CENTER_LAT ************/
    const double center_lat = CPLAtof(GetKeyword("MAP.CENTER_LATITUDE"));

    /***********   Grab CENTER_LON ************/
    const double center_lon = CPLAtof(GetKeyword("MAP.CENTER_LONGITUDE"));

    /**********   Grab 1st std parallel *******/
    const double first_std_parallel =
        CPLAtof(GetKeyword("MAP.FIRST_STANDARD_PARALLEL"));

    /**********   Grab 2nd std parallel *******/
    const double second_std_parallel =
        CPLAtof(GetKeyword("MAP.SECOND_STANDARD_PARALLEL"));

    /*** grab  PROJECTION_LATITUDE_TYPE = "PLANETOCENTRIC" ****/
    // Need to further study how ocentric/ographic will effect the gdal library.
    // So far we will use this fact to define a sphere or ellipse for some
    // projections Frank - may need to talk this over
    bool bIsGeographic = true;
    value = GetKeyword("MAP.COORDINATE_SYSTEM_NAME");
    if (EQUAL(value, "PLANETOCENTRIC"))
        bIsGeographic = false;

    /**   Set oSRS projection and parameters --- all PDS supported types added
    if apparently supported in oSRS "AITOFF",  ** Not supported in GDAL??
          "ALBERS",
          "BONNE",
          "BRIESEMEISTER",   ** Not supported in GDAL??
          "CYLINDRICAL EQUAL AREA",
          "EQUIDISTANT",
          "EQUIRECTANGULAR",
          "GNOMONIC",
          "HAMMER",    ** Not supported in GDAL??
          "HENDU",     ** Not supported in GDAL??
          "LAMBERT AZIMUTHAL EQUAL AREA",
          "LAMBERT CONFORMAL",
          "MERCATOR",
          "MOLLWEIDE",
          "OBLIQUE CYLINDRICAL",
          "ORTHOGRAPHIC",
          "SIMPLE CYLINDRICAL",
          "SINUSOIDAL",
          "STEREOGRAPHIC",
          "TRANSVERSE MERCATOR",
          "VAN DER GRINTEN",     ** Not supported in GDAL??
          "WERNER"     ** Not supported in GDAL??
    **/
    CPLDebug("PDS", "using projection %s\n\n", map_proj_name.c_str());

    OGRSpatialReference oSRS;

    if ((EQUAL(map_proj_name, "EQUIRECTANGULAR")) ||
        (EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
        (EQUAL(map_proj_name, "EQUIDISTANT")))
    {
        oSRS.SetEquirectangular2(0.0, center_lon, center_lat, 0, 0);
    }
    else if (EQUAL(map_proj_name, "ORTHOGRAPHIC"))
    {
        oSRS.SetOrthographic(center_lat, center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "SINUSOIDAL"))
    {
        oSRS.SetSinusoidal(center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "MERCATOR"))
    {
        oSRS.SetMercator(center_lat, center_lon, 1, 0, 0);
    }
    else if (EQUAL(map_proj_name, "STEREOGRAPHIC"))
    {
        if ((fabs(center_lat) - 90) < 0.0000001)
        {
            oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
        }
        else
        {
            oSRS.SetStereographic(center_lat, center_lon, 1, 0, 0);
        }
    }
    else if (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC"))
    {
        oSRS.SetPS(center_lat, center_lon, 1, 0, 0);
    }
    else if (EQUAL(map_proj_name, "TRANSVERSE_MERCATOR"))
    {
        oSRS.SetTM(center_lat, center_lon, 1, 0, 0);
    }
    else if (EQUAL(map_proj_name, "LAMBERT_CONFORMAL_CONIC"))
    {
        oSRS.SetLCC(first_std_parallel, second_std_parallel, center_lat,
                    center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "LAMBERT_AZIMUTHAL_EQUAL_AREA"))
    {
        oSRS.SetLAEA(center_lat, center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "CYLINDRICAL_EQUAL_AREA"))
    {
        oSRS.SetCEA(first_std_parallel, center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "MOLLWEIDE"))
    {
        oSRS.SetMollweide(center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "ALBERS"))
    {
        oSRS.SetACEA(first_std_parallel, second_std_parallel, center_lat,
                     center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "BONNE"))
    {
        oSRS.SetBonne(first_std_parallel, center_lon, 0, 0);
    }
    else if (EQUAL(map_proj_name, "GNOMONIC"))
    {
        oSRS.SetGnomonic(center_lat, center_lon, 0, 0);
#ifdef FIXME
    }
    else if (EQUAL(map_proj_name, "OBLIQUE_CYLINDRICAL"))
    {
        // hope Swiss Oblique Cylindrical is the same
        oSRS.SetSOC(center_lat, center_lon, 0, 0);
#endif
    }
    else
    {
        CPLDebug("VICAR",
                 "Dataset projection %s is not supported. Continuing...",
                 map_proj_name.c_str());
        bProjectionSet = false;
    }

    if (bProjectionSet)
    {
        // Create projection name, i.e. MERCATOR MARS and set as ProjCS keyword
        const CPLString proj_target_name = map_proj_name + " " + target_name;
        oSRS.SetProjCS(proj_target_name);  // set ProjCS keyword

        // The geographic/geocentric name will be the same basic name as the
        // body name 'GCS' = Geographic/Geocentric Coordinate System
        const CPLString geog_name = "GCS_" + target_name;

        // The datum and sphere names will be the same basic name aas the planet
        const CPLString datum_name = "D_" + target_name;
        CPLString sphere_name = target_name;  // + "_IAU_IAG");  //Might not be
                                              // IAU defined so don't add

        // calculate inverse flattening from major and minor axis: 1/f = a/(a-b)
        double iflattening = 0.0;
        if ((semi_major - semi_minor) < 0.0000001)
            iflattening = 0;
        else
            iflattening = semi_major / (semi_major - semi_minor);

        // Set the body size but take into consideration which proj is being
        // used to help w/ compatibility Notice that most PDS projections are
        // spherical based on the fact that ISIS/PICS are spherical Set the body
        // size but take into consideration which proj is being used to help w/
        // proj4 compatibility The use of a Sphere, polar radius or ellipse here
        // is based on how ISIS does it internally
        if (((EQUAL(map_proj_name, "STEREOGRAPHIC") &&
              (fabs(center_lat) == 90))) ||
            (EQUAL(map_proj_name, "POLAR_STEREOGRAPHIC")))
        {
            if (bIsGeographic)
            {
                // Geograpraphic, so set an ellipse
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
                               iflattening, "Reference_Meridian", 0.0);
            }
            else
            {
                // Geocentric, so force a sphere using the semi-minor axis. I
                // hope...
                sphere_name += "_polarRadius";
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_minor,
                               0.0, "Reference_Meridian", 0.0);
            }
        }
        else if ((EQUAL(map_proj_name, "SIMPLE_CYLINDRICAL")) ||
                 (EQUAL(map_proj_name, "EQUIDISTANT")) ||
                 (EQUAL(map_proj_name, "ORTHOGRAPHIC")) ||
                 (EQUAL(map_proj_name, "STEREOGRAPHIC")) ||
                 (EQUAL(map_proj_name, "SINUSOIDAL")))
        {
            // isis uses the spherical equation for these projections so force a
            // sphere
            oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
                           "Reference_Meridian", 0.0);
        }
        else if (EQUAL(map_proj_name, "EQUIRECTANGULAR"))
        {
            // isis uses local radius as a sphere, which is pre-calculated in
            // the PDS label as the semi-major
            sphere_name += "_localRadius";
            oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major, 0.0,
                           "Reference_Meridian", 0.0);
        }
        else
        {
            // All other projections: Mercator, Transverse Mercator, Lambert
            // Conformal, etc. Geographic, so set an ellipse
            if (bIsGeographic)
            {
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
                               iflattening, "Reference_Meridian", 0.0);
            }
            else
            {
                // Geocentric, so force a sphere. I hope...
                oSRS.SetGeogCS(geog_name, datum_name, sphere_name, semi_major,
                               0.0, "Reference_Meridian", 0.0);
            }
        }

        m_oSRS = oSRS;
        m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    }
    if (bProjectionSet)
    {
        m_bGotTransform = true;
        m_adfGeoTransform[0] = dfULXMap;
        m_adfGeoTransform[1] = dfXDim;
        m_adfGeoTransform[2] = 0.0;
        m_adfGeoTransform[3] = dfULYMap;
        m_adfGeoTransform[4] = 0.0;
        m_adfGeoTransform[5] = dfYDim;
    }
}

/************************************************************************/
/*                    ReadProjectionFromGeoTIFFGroup()                  */
/************************************************************************/

void VICARDataset::ReadProjectionFromGeoTIFFGroup()
{
    m_bGeoRefFormatIsMIPL = true;

    // We will build a in-memory temporary GeoTIFF file from the VICAR GEOTIFF
    // metadata items.

    char szFilename[100] = {};
    snprintf(szFilename, sizeof(szFilename), "/vsimem/vicar_tmp_%p.tif", this);

    /* -------------------------------------------------------------------- */
    /*      Initialization of libtiff and libgeotiff.                       */
    /* -------------------------------------------------------------------- */
    GTiffOneTimeInit();
    LibgeotiffOneTimeInit();

    /* -------------------------------------------------------------------- */
    /*      Initialize access to the memory geotiff structure.              */
    /* -------------------------------------------------------------------- */
    VSILFILE *fpL = VSIFOpenL(szFilename, "w");
    if (fpL == nullptr)
        return;

    TIFF *hTIFF = VSI_TIFFOpen(szFilename, "w", fpL);

    if (hTIFF == nullptr)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "TIFF/GeoTIFF structure is corrupt.");
        CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));
        return;
    }

    /* -------------------------------------------------------------------- */
    /*      Write some minimal set of image parameters.                     */
    /* -------------------------------------------------------------------- */
    TIFFSetField(hTIFF, TIFFTAG_IMAGEWIDTH, 1);
    TIFFSetField(hTIFF, TIFFTAG_IMAGELENGTH, 1);
    TIFFSetField(hTIFF, TIFFTAG_BITSPERSAMPLE, 8);
    TIFFSetField(hTIFF, TIFFTAG_SAMPLESPERPIXEL, 1);
    TIFFSetField(hTIFF, TIFFTAG_ROWSPERSTRIP, 1);
    TIFFSetField(hTIFF, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
    TIFFSetField(hTIFF, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_MINISBLACK);

    /* -------------------------------------------------------------------- */
    /*      Write geotiff keys from VICAR metadata                          */
    /* -------------------------------------------------------------------- */
    GTIF *hGTIF = GTIFNew(hTIFF);
    CPLAssert(hGTIF);

    for (const auto &gkey : GTiffAsciiKeys)
    {
        const char *pszValue = GetKeyword(
            ("GEOTIFF." + CPLString(GTIFKeyName(gkey)).toupper()).c_str(),
            nullptr);
        if (pszValue)
        {
            GTIFKeySet(hGTIF, gkey, TYPE_ASCII,
                       static_cast<int>(strlen(pszValue)), pszValue);
        }
    }

    for (const auto &gkey : GTiffDoubleKeys)
    {
        const char *pszValue = GetKeyword(
            ("GEOTIFF." + CPLString(GTIFKeyName(gkey)).toupper()).c_str(),
            nullptr);
        if (pszValue)
        {
            GTIFKeySet(hGTIF, gkey, TYPE_DOUBLE, 1, CPLAtof(pszValue));
        }
    }

    for (const auto &gkey : GTiffShortKeys)
    {
        const char *pszValue = GetKeyword(
            ("GEOTIFF." + CPLString(GTIFKeyName(gkey)).toupper()).c_str(),
            nullptr);
        if (pszValue)
        {
            GTIFKeySet(hGTIF, gkey, TYPE_SHORT, 1, atoi(pszValue));
        }
    }

    GTIFWriteKeys(hGTIF);
    GTIFFree(hGTIF);

    /* -------------------------------------------------------------------- */
    /*      Write geotiff tags from VICAR metadata                          */
    /* -------------------------------------------------------------------- */

    const std::map<const char *, int> oMapTagNameToCode = {
        {"MODELPIXELSCALETAG", TIFFTAG_GEOPIXELSCALE},
        {"MODELTIEPOINTTAG", TIFFTAG_GEOTIEPOINTS},
        {"MODELTRANSFORMATIONTAG", TIFFTAG_GEOTRANSMATRIX},
    };

    for (const auto &kv : oMapTagNameToCode)
    {
        const char *pszValue =
            GetKeyword((std::string("GEOTIFF.") + kv.first).c_str(), nullptr);
        if (pszValue)
        {
            // Remove leading ( and trailing ), and replace comma by space
            // to separate on it.
            const CPLStringList aosTokens(
                CSLTokenizeString2(CPLString(pszValue)
                                       .replaceAll('(', "")
                                       .replaceAll(')', "")
                                       .replaceAll(',', ' ')
                                       .c_str(),
                                   " ", 0));
            if (!aosTokens.empty())
            {
                std::vector<double> adfValues;
                for (int i = 0; i < aosTokens.size(); ++i)
                    adfValues.push_back(CPLAtof(aosTokens[i]));
                TIFFSetField(hTIFF, kv.second, aosTokens.size(), &adfValues[0]);
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Finalize the geotiff file.                                      */
    /* -------------------------------------------------------------------- */

    char bySmallImage = 0;

    TIFFWriteEncodedStrip(hTIFF, 0, &bySmallImage, 1);
    TIFFWriteDirectory(hTIFF);

    XTIFFClose(hTIFF);
    CPL_IGNORE_RET_VAL(VSIFCloseL(fpL));

    /* -------------------------------------------------------------------- */
    /*      Get georeferencing from file.                                   */
    /* -------------------------------------------------------------------- */
    auto poGTiffDS =
        std::unique_ptr<GDALDataset>(GDALDataset::Open(szFilename));
    if (poGTiffDS)
    {
        auto poSRS = poGTiffDS->GetSpatialRef();
        if (poSRS)
            m_oSRS = *poSRS;

        if (poGTiffDS->GetGeoTransform(&m_adfGeoTransform[0]) == CE_None)
        {
            m_bGotTransform = true;
        }

        const char *pszAreaOrPoint =
            poGTiffDS->GetMetadataItem(GDALMD_AREA_OR_POINT);
        if (pszAreaOrPoint)
            GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, pszAreaOrPoint);
    }

    VSIUnlink(szFilename);
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *VICARDataset::Open(GDALOpenInfo *poOpenInfo)
{
    /* -------------------------------------------------------------------- */
    /*      Does this look like a VICAR dataset?                            */
    /* -------------------------------------------------------------------- */
    const int nLabelOffset = GetLabelOffset(poOpenInfo);
    if (nLabelOffset < 0)
        return nullptr;
    if (nLabelOffset > 0)
    {
        CPLString osSubFilename;
        osSubFilename.Printf("/vsisubfile/%d,%s", nLabelOffset,
                             poOpenInfo->pszFilename);
        GDALOpenInfo oOpenInfo(osSubFilename.c_str(), poOpenInfo->eAccess);
        return Open(&oOpenInfo);
    }

    auto poDS = cpl::make_unique<VICARDataset>();
    poDS->fpImage = poOpenInfo->fpL;
    poOpenInfo->fpL = nullptr;
    if (!poDS->oKeywords.Ingest(poDS->fpImage, poOpenInfo->pabyHeader))
    {
        return nullptr;
    }

    /************ CHECK INSTRUMENT/DATA *****************/

    bool bIsDTM = false;
    const char *value = poDS->GetKeyword("DTM.DTM_OFFSET");
    if (!EQUAL(value, ""))
    {
        bIsDTM = true;
    }

    bool bInstKnown = false;
    // Check for HRSC
    if (EQUAL(poDS->GetKeyword("BLTYPE"), "M94_HRSC"))
        bInstKnown = true;
    // Check for Framing Camera on Dawn
    else if (EQUAL(poDS->GetKeyword("INSTRUMENT_ID"), "FC2"))
        bInstKnown = true;

    /************ Grab dimensions *****************/

    const int nCols = atoi(poDS->GetKeyword("NS"));
    const int nRows = atoi(poDS->GetKeyword("NL"));
    const int nBands = atoi(poDS->GetKeyword("NB"));

    if (!GDALCheckDatasetDimensions(nCols, nRows) ||
        !GDALCheckBandCount(nBands, false))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "File %s appears to be a VICAR file, but failed to find some "
                 "required keywords.",
                 poOpenInfo->pszFilename);
        return nullptr;
    }

    const GDALDataType eDataType =
        GetDataTypeFromFormat(poDS->GetKeyword("FORMAT"));
    if (eDataType == GDT_Unknown)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Could not find known VICAR label entries!\n");
        return nullptr;
    }
    double dfNoData = 0.0;
    if (eDataType == GDT_Byte)
    {
        dfNoData = NULL1;
    }
    else if (eDataType == GDT_Int16)
    {
        dfNoData = NULL2;
    }
    else if (eDataType == GDT_Float32)
    {
        dfNoData = NULL3;
    }

    /***** CHECK ENDIANNESS **************/

    RawRasterBand::ByteOrder eByteOrder =
        RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
    if (GDALDataTypeIsInteger(eDataType))
    {
        value = poDS->GetKeyword("INTFMT", "LOW");
        if (EQUAL(value, "LOW"))
        {
            eByteOrder = RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
        }
        else if (EQUAL(value, "HIGH"))
        {
            eByteOrder = RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
        }
        else
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "INTFMT=%s layout not supported.", value);
            return nullptr;
        }
    }
    else
    {
        value = poDS->GetKeyword("REALFMT", "VAX");
        if (EQUAL(value, "RIEEE"))
        {
            eByteOrder = RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
        }
        else if (EQUAL(value, "IEEE"))
        {
            eByteOrder = RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
        }
        else if (EQUAL(value, "VAX"))
        {
            eByteOrder = RawRasterBand::ByteOrder::ORDER_VAX;
        }
        else
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "REALFMT=%s layout not supported.", value);
            return nullptr;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Capture some information from the file that is of interest.     */
    /* -------------------------------------------------------------------- */
    poDS->nRasterXSize = nCols;
    poDS->nRasterYSize = nRows;

    if (poDS->GetKeyword("MAP.MAP_PROJECTION_TYPE")[0] != '\0')
    {
        poDS->ReadProjectionFromMapGroup();
    }
    else if (poDS->GetKeyword("GEOTIFF.GTMODELTYPEGEOKEY")[0] != '\0' ||
             poDS->GetKeyword("GEOTIFF.MODELTIEPOINTTAG")[0] != '\0')
    {
        poDS->ReadProjectionFromGeoTIFFGroup();
    }

    if (!poDS->m_bGotTransform)
        poDS->m_bGotTransform = CPL_TO_BOOL(GDALReadWorldFile(
            poOpenInfo->pszFilename, "wld", &poDS->m_adfGeoTransform[0]));

    poDS->eAccess = poOpenInfo->eAccess;
    poDS->m_oJSonLabel = poDS->oKeywords.GetJsonObject();

    /* -------------------------------------------------------------------- */
    /*      Compute the line offsets.                                        */
    /* -------------------------------------------------------------------- */

    GUInt64 nPixelOffset;
    GUInt64 nLineOffset;
    GUInt64 nBandOffset;
    GUInt64 nImageOffsetWithoutNBB;
    GUInt64 nNBB;
    GUInt64 nImageSize;
    if (!GetSpacings(poDS->oKeywords, nPixelOffset, nLineOffset, nBandOffset,
                     nImageOffsetWithoutNBB, nNBB, nImageSize) ||
        nImageOffsetWithoutNBB > std::numeric_limits<GUInt64>::max() -
                                     (nNBB + nBandOffset * (nBands - 1)))
    {
        CPLDebug("VICAR", "Invalid spacings found");
        return nullptr;
    }

    poDS->m_nRecordSize = atoi(poDS->GetKeyword("RECSIZE", ""));

    if (nNBB != 0)
    {
        const char *pszBLType = poDS->GetKeyword("BLTYPE", nullptr);
        const char *pszVicarConf = CPLFindFile("gdal", "vicar.json");
        if (pszBLType && pszVicarConf && poDS->m_nRecordSize > 0)
        {

            RawRasterBand::ByteOrder eBINTByteOrder =
                RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
            value = poDS->GetKeyword("BINTFMT", "LOW");
            if (EQUAL(value, "LOW"))
            {
                eBINTByteOrder = RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
            }
            else if (EQUAL(value, "HIGH"))
            {
                eBINTByteOrder = RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
            }
            else
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "BINTFMT=%s layout not supported.", value);
            }

            RawRasterBand::ByteOrder eBREALByteOrder =
                RawRasterBand::ByteOrder::ORDER_VAX;
            value = poDS->GetKeyword("BREALFMT", "VAX");
            if (EQUAL(value, "RIEEE"))
            {
                eBREALByteOrder = RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN;
            }
            else if (EQUAL(value, "IEEE"))
            {
                eBREALByteOrder = RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
            }
            else if (EQUAL(value, "VAX"))
            {
                eBREALByteOrder = RawRasterBand::ByteOrder::ORDER_VAX;
            }
            else
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "BREALFMT=%s layout not supported.", value);
            }

            CPLJSONDocument oDoc;
            if (oDoc.Load(pszVicarConf))
            {
                const auto oRoot = oDoc.GetRoot();
                if (oRoot.GetType() == CPLJSONObject::Type::Object)
                {
                    auto oDef = oRoot.GetObj(pszBLType);
                    if (oDef.IsValid() &&
                        oDef.GetType() == CPLJSONObject::Type::Object &&
                        static_cast<GUInt64>(oDef.GetInteger("size")) == nNBB)
                    {
                        auto poLayer =
                            std::unique_ptr<OGRVICARBinaryPrefixesLayer>(
                                new OGRVICARBinaryPrefixesLayer(
                                    poDS->fpImage,
                                    static_cast<int>(nImageSize /
                                                     poDS->m_nRecordSize),
                                    oDef, nImageOffsetWithoutNBB,
                                    poDS->m_nRecordSize, eBINTByteOrder,
                                    eBREALByteOrder));
                        if (!poLayer->HasError())
                        {
                            poDS->m_poLayer = std::move(poLayer);
                        }
                    }
                }
            }
        }
    }

    poDS->m_nImageOffsetWithoutNBB =
        static_cast<vsi_l_offset>(nImageOffsetWithoutNBB);

    CPLString osCompress = poDS->GetKeyword("COMPRESS", "NONE");
    if (EQUAL(osCompress, "BASIC") || EQUAL(osCompress, "BASIC2"))
    {
        if (poOpenInfo->eAccess == GA_Update)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Update of compressed VICAR file not supported");
            return nullptr;
        }
        poDS->SetMetadataItem("COMPRESS", osCompress, "IMAGE_STRUCTURE");
        poDS->m_eCompress =
            EQUAL(osCompress, "BASIC") ? COMPRESS_BASIC : COMPRESS_BASIC2;
        if (poDS->nRasterYSize > 100 * 1000 * 1000 / nBands)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too many records for compressed dataset");
            return nullptr;
        }
        if (!GDALDataTypeIsInteger(eDataType))
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Data type incompatible of compression");
            return nullptr;
        }
        // To avoid potential issues in basic_decode()
        const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
        if (nDTSize == 0 || poDS->nRasterXSize > INT_MAX / nDTSize)
        {
            CPLError(CE_Failure, CPLE_NotSupported, "Too large scanline");
            return nullptr;
        }
        const int nRecords = poDS->nRasterYSize * nBands;
        try
        {
            // + 1 to store implicitly the size of the last record
            poDS->m_anRecordOffsets.resize(nRecords + 1);
        }
        catch (const std::exception &e)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
            return nullptr;
        }
        if (poDS->m_eCompress == COMPRESS_BASIC)
        {
            poDS->m_anRecordOffsets[0] =
                poDS->m_nImageOffsetWithoutNBB + sizeof(GUInt32);
        }
        else
        {
            poDS->m_anRecordOffsets[0] =
                poDS->m_nImageOffsetWithoutNBB + sizeof(GUInt32) * nRecords;
        }
    }
    else if (!EQUAL(osCompress, "NONE"))
    {
        CPLError(CE_Failure, CPLE_NotSupported, "COMPRESS=%s not supported",
                 osCompress.c_str());
        return nullptr;
    }

    /* -------------------------------------------------------------------- */
    /*      Create band information objects.                                */
    /* -------------------------------------------------------------------- */
    for (int i = 0; i < nBands; i++)
    {
        std::unique_ptr<GDALRasterBand> poBand;

        if (poDS->m_eCompress == COMPRESS_BASIC ||
            poDS->m_eCompress == COMPRESS_BASIC2)
        {
            poBand = cpl::make_unique<VICARBASICRasterBand>(poDS.get(), i + 1,
                                                            eDataType);
        }
        else
        {
            auto poRawBand = cpl::make_unique<VICARRawRasterBand>(
                poDS.get(), i + 1, poDS->fpImage,
                static_cast<vsi_l_offset>(nImageOffsetWithoutNBB + nNBB +
                                          nBandOffset * i),
                static_cast<int>(nPixelOffset), static_cast<int>(nLineOffset),
                eDataType, eByteOrder);
            if (!poRawBand->IsValid())
            {
                return nullptr;
            }
            poBand = std::move(poRawBand);
        }

        // only set NoData if instrument is supported
        if (bInstKnown)
            poBand->SetNoDataValue(dfNoData);
        if (bIsDTM)
        {
            poBand->SetScale(static_cast<double>(
                CPLAtof(poDS->GetKeyword("DTM.DTM_SCALING_FACTOR"))));
            poBand->SetOffset(static_cast<double>(
                CPLAtof(poDS->GetKeyword("DTM.DTM_OFFSET"))));
            const char *pszMin =
                poDS->GetKeyword("DTM.DTM_MINIMUM_DN", nullptr);
            const char *pszMax =
                poDS->GetKeyword("DTM.DTM_MAXIMUM_DN", nullptr);
            if (pszMin != nullptr && pszMax != nullptr)
                poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax), 0, 0);
            const char *pszNoData =
                poDS->GetKeyword("DTM.DTM_MISSING_DN", nullptr);
            if (pszNoData != nullptr)
                poBand->SetNoDataValue(CPLAtofM(pszNoData));
        }
        else if (EQUAL(poDS->GetKeyword("BLTYPE"), "M94_HRSC"))
        {
            double scale = CPLAtof(
                poDS->GetKeyword("DLRTO8.REFLECTANCE_SCALING_FACTOR", "-1."));
            if (scale < 0.)
            {
                scale = CPLAtof(
                    poDS->GetKeyword("HRCAL.REFLECTANCE_SCALING_FACTOR", "1."));
            }
            poBand->SetScale(scale);
            double offset =
                CPLAtof(poDS->GetKeyword("DLRTO8.REFLECTANCE_OFFSET", "-1."));
            if (offset < 0.)
            {
                offset =
                    CPLAtof(poDS->GetKeyword("HRCAL.REFLECTANCE_OFFSET", "0."));
            }
            poBand->SetOffset(offset);
        }
        const char *pszMin = poDS->GetKeyword("STATISTICS.MINIMUM", nullptr);
        const char *pszMax = poDS->GetKeyword("STATISTICS.MAXIMUM", nullptr);
        const char *pszMean = poDS->GetKeyword("STATISTICS.MEAN", nullptr);
        const char *pszStdDev =
            poDS->GetKeyword("STATISTICS.STANDARD_DEVIATION", nullptr);
        if (pszMin != nullptr && pszMax != nullptr && pszMean != nullptr &&
            pszStdDev != nullptr)
            poBand->SetStatistics(CPLAtofM(pszMin), CPLAtofM(pszMax),
                                  CPLAtofM(pszMean), CPLAtofM(pszStdDev));

        poDS->SetBand(i + 1, std::move(poBand));
    }

    /* -------------------------------------------------------------------- */
    /*      Instrument-specific keywords as metadata.                       */
    /* -------------------------------------------------------------------- */

    /******************   HRSC    ******************************/

    if (EQUAL(poDS->GetKeyword("BLTYPE"), "M94_HRSC"))
    {
        poDS->SetMetadataItem(
            "SPACECRAFT_NAME",
            poDS->GetKeyword("M94_INSTRUMENT.INSTRUMENT_HOST_NAME"));
        poDS->SetMetadataItem("PRODUCT_TYPE", poDS->GetKeyword("TYPE"));

        if (EQUAL(poDS->GetKeyword("M94_INSTRUMENT.DETECTOR_ID"),
                  "MEX_HRSC_SRC"))
        {
            static const char *const apszKeywords[] = {
                "M94_ORBIT.IMAGE_TIME",
                "FILE.EVENT_TYPE",
                "FILE.PROCESSING_LEVEL_ID",
                "M94_INSTRUMENT.DETECTOR_ID",
                "M94_CAMERAS.EXPOSURE_DURATION",
                "HRCONVER.INSTRUMENT_TEMPERATURE",
                nullptr};
            for (int i = 0; apszKeywords[i] != nullptr; i++)
            {
                const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
                if (pszKeywordValue != nullptr)
                    poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
            }
        }
        else
        {
            static const char *const apszKeywords[] = {
                "M94_ORBIT.START_TIME",
                "M94_ORBIT.STOP_TIME",
                "M94_INSTRUMENT.DETECTOR_ID",
                "M94_CAMERAS.MACROPIXEL_SIZE",
                "FILE.EVENT_TYPE",
                "M94_INSTRUMENT.MISSION_PHASE_NAME",
                "HRORTHO.SPICE_FILE_NAME",
                "HRCONVER.MISSING_FRAMES",
                "HRCONVER.OVERFLOW_FRAMES",
                "HRCONVER.ERROR_FRAMES",
                "HRFOOT.BEST_GROUND_SAMPLING_DISTANCE",
                "DLRTO8.RADIANCE_SCALING_FACTOR",
                "DLRTO8.RADIANCE_OFFSET",
                "DLRTO8.REFLECTANCE_SCALING_FACTOR",
                "DLRTO8.REFLECTANCE_OFFSET",
                "HRCAL.RADIANCE_SCALING_FACTOR",
                "HRCAL.RADIANCE_OFFSET",
                "HRCAL.REFLECTANCE_SCALING_FACTOR",
                "HRCAL.REFLECTANCE_OFFSET",
                "HRORTHO.DTM_NAME",
                "HRORTHO.EXTORI_FILE_NAME",
                "HRORTHO.GEOMETRIC_CALIB_FILE_NAME",
                nullptr};
            for (int i = 0; apszKeywords[i] != nullptr; i++)
            {
                const char *pszKeywordValue =
                    poDS->GetKeyword(apszKeywords[i], nullptr);
                if (pszKeywordValue != nullptr)
                    poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
            }
        }
    }
    if (bIsDTM && EQUAL(poDS->GetKeyword("MAP.TARGET_NAME"), "MARS"))
    {
        poDS->SetMetadataItem("SPACECRAFT_NAME", "MARS_EXPRESS");
        poDS->SetMetadataItem("PRODUCT_TYPE", "DTM");
        static const char *const apszKeywords[] = {
            "DTM.DTM_MISSING_DN",     "DTM.DTM_OFFSET",
            "DTM.DTM_SCALING_FACTOR", "DTM.DTM_A_AXIS_RADIUS",
            "DTM.DTM_B_AXIS_RADIUS",  "DTM.DTM_C_AXIS_RADIUS",
            "DTM.DTM_DESC",           "DTM.DTM_MINIMUM_DN",
            "DTM.DTM_MAXIMUM_DN",     nullptr};
        for (int i = 0; apszKeywords[i] != nullptr; i++)
        {
            const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
            if (pszKeywordValue != nullptr)
                poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
        }
    }

    /******************   DAWN   ******************************/
    else if (EQUAL(poDS->GetKeyword("INSTRUMENT_ID"), "FC2"))
    {
        poDS->SetMetadataItem("SPACECRAFT_NAME", "DAWN");
        static const char *const apszKeywords[] = {
            "ORBIT_NUMBER",
            "FILTER_NUMBER",
            "FRONT_DOOR_STATUS",
            "FIRST_LINE",
            "FIRST_LINE_SAMPLE",
            "PRODUCER_INSTITUTION_NAME",
            "SOURCE_FILE_NAME",
            "PROCESSING_LEVEL_ID",
            "TARGET_NAME",
            "LIMB_IN_IMAGE",
            "POLE_IN_IMAGE",
            "REFLECTANCE_SCALING_FACTOR",
            "SPICE_FILE_NAME",
            "SPACECRAFT_CENTRIC_LATITUDE",
            "SPACECRAFT_EASTERN_LONGITUDE",
            "FOOTPRINT_POSITIVE_LONGITUDE",
            nullptr};
        for (int i = 0; apszKeywords[i] != nullptr; i++)
        {
            const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
            if (pszKeywordValue != nullptr)
                poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
        }
    }
    else if (bIsDTM && (EQUAL(poDS->GetKeyword("TARGET_NAME"), "VESTA") ||
                        EQUAL(poDS->GetKeyword("TARGET_NAME"), "CERES")))
    {
        poDS->SetMetadataItem("SPACECRAFT_NAME", "DAWN");
        poDS->SetMetadataItem("PRODUCT_TYPE", "DTM");
        static const char *const apszKeywords[] = {
            "DTM_MISSING_DN",
            "DTM_OFFSET",
            "DTM_SCALING_FACTOR",
            "DTM_A_AXIS_RADIUS",
            "DTM_B_AXIS_RADIUS",
            "DTM_C_AXIS_RADIUS",
            "DTM_MINIMUM_DN",
            "DTM_MAXIMUM_DN",
            "MAP_PROJECTION_TYPE",
            "COORDINATE_SYSTEM_NAME",
            "POSITIVE_LONGITUDE_DIRECTION",
            "MAP_SCALE",
            "CENTER_LONGITUDE",
            "LINE_PROJECTION_OFFSET",
            "SAMPLE_PROJECTION_OFFSET",
            nullptr};
        for (int i = 0; apszKeywords[i] != nullptr; i++)
        {
            const char *pszKeywordValue = poDS->GetKeyword(apszKeywords[i]);
            if (pszKeywordValue != nullptr)
                poDS->SetMetadataItem(apszKeywords[i], pszKeywordValue);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize any PAM information.                                 */
    /* -------------------------------------------------------------------- */
    poDS->TryLoadXML();

    /* -------------------------------------------------------------------- */
    /*      Check for overviews.                                            */
    /* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);

    return poDS.release();
}

/************************************************************************/
/*                             GetKeyword()                             */
/************************************************************************/

const char *VICARDataset::GetKeyword(const char *pszPath,
                                     const char *pszDefault)

{
    return oKeywords.GetKeyword(pszPath, pszDefault);
}

/************************************************************************/
/*                        GetDataTypeFromFormat()                       */
/************************************************************************/

GDALDataType VICARDataset::GetDataTypeFromFormat(const char *pszFormat)
{
    if (EQUAL(pszFormat, "BYTE"))
        return GDT_Byte;

    if (EQUAL(pszFormat, "HALF") || EQUAL(pszFormat, "WORD"))
        return GDT_Int16;

    if (EQUAL(pszFormat, "FULL") || EQUAL(pszFormat, "LONG"))
        return GDT_Int32;

    if (EQUAL(pszFormat, "REAL"))
        return GDT_Float32;

    if (EQUAL(pszFormat, "DOUB"))
        return GDT_Float64;

    if (EQUAL(pszFormat, "COMP") || EQUAL(pszFormat, "COMPLEX"))
        return GDT_CFloat32;

    return GDT_Unknown;
}

/************************************************************************/
/*                             GetSpacings()                            */
/************************************************************************/

bool VICARDataset::GetSpacings(const VICARKeywordHandler &keywords,
                               GUInt64 &nPixelOffset, GUInt64 &nLineOffset,
                               GUInt64 &nBandOffset,
                               GUInt64 &nImageOffsetWithoutNBB, GUInt64 &nNBB,
                               GUInt64 &nImageSize)
{
    const GDALDataType eDataType =
        GetDataTypeFromFormat(keywords.GetKeyword("FORMAT", ""));
    if (eDataType == GDT_Unknown)
        return false;
    const GUInt64 nItemSize = GDALGetDataTypeSizeBytes(eDataType);
    const char *value = keywords.GetKeyword("ORG", "BSQ");
    // number of bytes of binary prefix before each record
    nNBB = atoi(keywords.GetKeyword("NBB", ""));
    const GUInt64 nCols64 = atoi(keywords.GetKeyword("NS", ""));
    const GUInt64 nRows64 = atoi(keywords.GetKeyword("NL", ""));
    const GUInt64 nBands64 = atoi(keywords.GetKeyword("NB", ""));
    try
    {
        if (EQUAL(value, "BIP"))
        {
            nPixelOffset = (CPLSM(nItemSize) * CPLSM(nBands64)).v();
            nBandOffset = nItemSize;
            nLineOffset =
                (CPLSM(nNBB) + CPLSM(nPixelOffset) * CPLSM(nCols64)).v();
            nImageSize = (CPLSM(nLineOffset) * CPLSM(nRows64)).v();
        }
        else if (EQUAL(value, "BIL"))
        {
            nPixelOffset = nItemSize;
            nBandOffset = (CPLSM(nItemSize) * CPLSM(nCols64)).v();
            nLineOffset =
                (CPLSM(nNBB) + CPLSM(nBandOffset) * CPLSM(nBands64)).v();
            nImageSize = (CPLSM(nLineOffset) * CPLSM(nRows64)).v();
        }
        else if (EQUAL(value, "BSQ"))
        {
            nPixelOffset = nItemSize;
            nLineOffset =
                (CPLSM(nNBB) + CPLSM(nPixelOffset) * CPLSM(nCols64)).v();
            nBandOffset = (CPLSM(nLineOffset) * CPLSM(nRows64)).v();
            nImageSize = (CPLSM(nBandOffset) * CPLSM(nBands64)).v();
        }
        else
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "ORG=%s layout not supported.", value);
            return false;
        }
    }
    catch (const CPLSafeIntOverflow &)
    {
        return false;
    }

    const GUInt64 nLabelSize = atoi(keywords.GetKeyword("LBLSIZE", ""));
    const GUInt64 nRecordSize = atoi(keywords.GetKeyword("RECSIZE", ""));
    const GUInt64 nNLB = atoi(keywords.GetKeyword("NLB", ""));
    try
    {
        nImageOffsetWithoutNBB =
            (CPLSM(nLabelSize) + CPLSM(nRecordSize) * CPLSM(nNLB) + CPLSM(nNBB))
                .v();
        nImageOffsetWithoutNBB -= nNBB;
    }
    catch (const CPLSafeIntOverflow &)
    {
        return false;
    }
    return true;
}

/************************************************************************/
/*                           Create()                                   */
/************************************************************************/

GDALDataset *VICARDataset::Create(const char *pszFilename, int nXSize,
                                  int nYSize, int nBandsIn, GDALDataType eType,
                                  char **papszOptions)
{
    return CreateInternal(pszFilename, nXSize, nYSize, nBandsIn, eType,
                          papszOptions);
}

VICARDataset *VICARDataset::CreateInternal(const char *pszFilename, int nXSize,
                                           int nYSize, int nBandsIn,
                                           GDALDataType eType,
                                           char **papszOptions)
{
    if (eType != GDT_Byte && eType != GDT_Int16 && eType != GDT_Int32 &&
        eType != GDT_Float32 && eType != GDT_Float64 && eType != GDT_CFloat32)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data type");
        return nullptr;
    }

    const int nPixelOffset = GDALGetDataTypeSizeBytes(eType);
    if (nXSize == 0 || nYSize == 0 || nPixelOffset > INT_MAX / nXSize)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Unsupported raster dimensions");
        return nullptr;
    }
    const int nLineOffset = nXSize * nPixelOffset;

    if (nBandsIn == 0 || nBandsIn > 32767)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
        return nullptr;
    }

    const char *pszCompress =
        CSLFetchNameValueDef(papszOptions, "COMPRESS", "NONE");
    CompressMethod eCompress = COMPRESS_NONE;
    if (EQUAL(pszCompress, "NONE"))
    {
        eCompress = COMPRESS_NONE;
    }
    else if (EQUAL(pszCompress, "BASIC"))
    {
        eCompress = COMPRESS_BASIC;
    }
    else if (EQUAL(pszCompress, "BASIC2"))
    {
        eCompress = COMPRESS_BASIC2;
    }
    else
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported COMPRESS value");
        return nullptr;
    }
    if (eCompress != COMPRESS_NONE &&
        (!GDALDataTypeIsInteger(eType) || nBandsIn != 1))
    {
        CPLError(
            CE_Failure, CPLE_NotSupported,
            "BASIC/BASIC2 compression only supports one-band integer datasets");
        return nullptr;
    }

    std::vector<vsi_l_offset> anRecordOffsets;
    if (eCompress != COMPRESS_NONE)
    {
        const GUInt64 nMaxEncodedSize =
            static_cast<GUInt64>(nXSize) * nPixelOffset +
            static_cast<GUInt64>(nXSize) * nPixelOffset / 2 + 11;
        // To avoid potential later int overflows
        if (nMaxEncodedSize > static_cast<GUInt64>(INT_MAX))
        {
            CPLError(CE_Failure, CPLE_NotSupported, "Too large scanline");
            return nullptr;
        }
        if (nYSize > 100 * 1000 * 1000)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Too many records for compressed dataset");
            return nullptr;
        }
        try
        {
            // + 1 to store implicitly the size of the last record
            anRecordOffsets.resize(nYSize + 1);
        }
        catch (const std::exception &e)
        {
            CPLError(CE_Failure, CPLE_OutOfMemory, "%s", e.what());
            return nullptr;
        }
    }

    CPLJSONObject oSrcJSonLabel;
    oSrcJSonLabel.Deinit();

    const char *pszLabel = CSLFetchNameValue(papszOptions, "LABEL");
    if (pszLabel)
    {
        CPLJSONDocument oJSONDocument;
        if (pszLabel[0] == '{')
        {
            const GByte *pabyData = reinterpret_cast<const GByte *>(pszLabel);
            if (!oJSONDocument.LoadMemory(pabyData))
            {
                return nullptr;
            }
        }
        else
        {
            if (!oJSONDocument.Load(pszLabel))
            {
                return nullptr;
            }
        }

        oSrcJSonLabel = oJSONDocument.GetRoot();
        if (!oSrcJSonLabel.IsValid())
        {
            return nullptr;
        }
    }

    VSILFILE *fp = VSIFOpenExL(pszFilename, "wb+", true);
    if (fp == nullptr)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s: %s", pszFilename,
                 VSIGetLastErrorMsg());
        return nullptr;
    }

    VICARDataset *poDS = new VICARDataset();
    poDS->fpImage = fp;
    poDS->nRasterXSize = nXSize;
    poDS->nRasterYSize = nYSize;
    poDS->m_nRecordSize = nLineOffset;
    poDS->m_bIsLabelWritten = false;
    poDS->m_bGeoRefFormatIsMIPL = EQUAL(
        CSLFetchNameValueDef(papszOptions, "GEOREF_FORMAT", "MIPL"), "MIPL");
    poDS->m_bUseSrcLabel = CPLFetchBool(papszOptions, "USE_SRC_LABEL", true);
    poDS->m_bUseSrcMap = CPLFetchBool(papszOptions, "USE_SRC_MAP", false);
    poDS->m_osLatitudeType =
        CSLFetchNameValueDef(papszOptions, "COORDINATE_SYSTEM_NAME", "");
    poDS->m_osLongitudeDirection =
        CSLFetchNameValueDef(papszOptions, "POSITIVE_LONGITUDE_DIRECTION", "");
    poDS->m_osTargetName =
        CSLFetchNameValueDef(papszOptions, "TARGET_NAME", "");
    poDS->m_bInitToNodata = true;
    poDS->m_oSrcJSonLabel = oSrcJSonLabel;
    poDS->m_eCompress = eCompress;
    poDS->m_anRecordOffsets = std::move(anRecordOffsets);
    poDS->eAccess = GA_Update;

    /* -------------------------------------------------------------------- */
    /*      Create band information objects.                                */
    /* -------------------------------------------------------------------- */
    const vsi_l_offset nBandOffset =
        static_cast<vsi_l_offset>(nLineOffset) * nYSize;
    for (int i = 0; i < nBandsIn; i++)
    {
        GDALRasterBand *poBand;
        if (eCompress != COMPRESS_NONE)
        {
            poBand = new VICARBASICRasterBand(poDS, i + 1, eType);
        }
        else
        {
            poBand = new VICARRawRasterBand(
                poDS, i + 1, poDS->fpImage,
                i * nBandOffset,  // will be set later to final value since we
                                  // need to include the label size
                nPixelOffset, nLineOffset, eType,
                RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN);
        }
        poDS->SetBand(i + 1, poBand);
    }

    return poDS;
}

/************************************************************************/
/*                            CreateCopy()                              */
/************************************************************************/

GDALDataset *VICARDataset::CreateCopy(const char *pszFilename,
                                      GDALDataset *poSrcDS, int /*bStrict*/,
                                      char **papszOptions,
                                      GDALProgressFunc pfnProgress,
                                      void *pProgressData)
{
    if (poSrcDS->GetRasterCount() == 0)
    {
        CPLError(CE_Failure, CPLE_NotSupported, "Unsupported band count");
        return nullptr;
    }

    const int nXSize = poSrcDS->GetRasterXSize();
    const int nYSize = poSrcDS->GetRasterYSize();
    const int nBands = poSrcDS->GetRasterCount();
    GDALDataType eType = poSrcDS->GetRasterBand(1)->GetRasterDataType();
    auto poDS = std::unique_ptr<VICARDataset>(CreateInternal(
        pszFilename, nXSize, nYSize, nBands, eType, papszOptions));
    if (poDS == nullptr)
        return nullptr;

    double adfGeoTransform[6] = {0.0};
    if (poSrcDS->GetGeoTransform(adfGeoTransform) == CE_None &&
        (adfGeoTransform[0] != 0.0 || adfGeoTransform[1] != 1.0 ||
         adfGeoTransform[2] != 0.0 || adfGeoTransform[3] != 0.0 ||
         adfGeoTransform[4] != 0.0 || adfGeoTransform[5] != 1.0))
    {
        poDS->SetGeoTransform(adfGeoTransform);
    }

    auto poSrcSRS = poSrcDS->GetSpatialRef();
    if (poSrcSRS)
    {
        poDS->SetSpatialRef(poSrcSRS);
    }

    if (poDS->m_bUseSrcLabel && !poDS->m_oSrcJSonLabel.IsValid())
    {
        char **papszMD_VICAR = poSrcDS->GetMetadata("json:VICAR");
        if (papszMD_VICAR != nullptr)
        {
            poDS->SetMetadata(papszMD_VICAR, "json:VICAR");
        }
    }

    poDS->m_bInitToNodata = false;
    CPLErr eErr = GDALDatasetCopyWholeRaster(poSrcDS, poDS.get(), nullptr,
                                             pfnProgress, pProgressData);
    poDS->FlushCache(false);
    if (eErr != CE_None)
    {
        return nullptr;
    }

    return poDS.release();
}

/************************************************************************/
/*                     GetVICARLabelOffsetFromPDS3()                    */
/************************************************************************/

vsi_l_offset
VICARDataset::GetVICARLabelOffsetFromPDS3(const char *pszHdr, VSILFILE *fp,
                                          std::string &osVICARHeader)
{
    const char *pszPDSVersionID = strstr(pszHdr, "PDS_VERSION_ID");
    int nOffset = 0;
    if (pszPDSVersionID)
        nOffset = static_cast<int>(pszPDSVersionID - pszHdr);

    NASAKeywordHandler oKeywords;
    if (oKeywords.Ingest(fp, nOffset))
    {
        const int nRecordBytes =
            atoi(oKeywords.GetKeyword("RECORD_BYTES", "0"));
        const int nImageHeader =
            atoi(oKeywords.GetKeyword("^IMAGE_HEADER", "0"));
        if (nRecordBytes > 0 && nImageHeader > 0)
        {
            const auto nImgHeaderOffset =
                static_cast<vsi_l_offset>(nImageHeader - 1) * nRecordBytes;
            osVICARHeader.resize(1024);
            size_t nMemb;
            if (VSIFSeekL(fp, nImgHeaderOffset, SEEK_SET) == 0 &&
                (nMemb = VSIFReadL(&osVICARHeader[0], 1, osVICARHeader.size(),
                                   fp)) != 0 &&
                osVICARHeader.find("LBLSIZE") != std::string::npos)
            {
                osVICARHeader.resize(nMemb);
                return nImgHeaderOffset;
            }
        }
    }
    return 0;
}

/************************************************************************/
/*                         GDALRegister_VICAR()                         */
/************************************************************************/

void GDALRegister_VICAR()

{
    if (GDALGetDriverByName("VICAR") != nullptr)
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription("VICAR");
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
    poDriver->SetMetadataItem(GDAL_DCAP_VECTOR, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "MIPL VICAR file");
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/vicar.html");
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES,
                              "Byte Int16 Int32 Float32 Float64 CFloat32");
    poDriver->SetMetadataItem(
        GDAL_DMD_CREATIONOPTIONLIST,
        "<CreationOptionList>"
        "  <Option name='GEOREF_FORMAT' type='string-select' "
        "description='How to encode georeferencing information' "
        "default='MIPL'>"
        "     <Value>MIPL</Value>"
        "     <Value>GEOTIFF</Value>"
        "  </Option>"
        "  <Option name='COORDINATE_SYSTEM_NAME' type='string-select' "
        "description='Value of MAP.COORDINATE_SYSTEM_NAME' "
        "default='PLANETOCENTRIC'>"
        "     <Value>PLANETOCENTRIC</Value>"
        "     <Value>PLANETOGRAPHIC</Value>"
        "  </Option>"
        "  <Option name='POSITIVE_LONGITUDE_DIRECTION' type='string-select' "
        "description='Value of MAP.POSITIVE_LONGITUDE_DIRECTION' "
        "default='EAST'>"
        "     <Value>EAST</Value>"
        "     <Value>WEST</Value>"
        "  </Option>"
        "  <Option name='TARGET_NAME' type='string' description='Value of "
        "MAP.TARGET_NAME'/>"
        "  <Option name='USE_SRC_LABEL' type='boolean' "
        "description='Whether to use source label in VICAR to VICAR "
        "conversions' "
        "default='YES'/>"
        "  <Option name='USE_SRC_MAP' type='boolean' "
        "description='Whether to use MAP property from source label in "
        "VICAR to VICAR conversions' "
        "default='NO'/>"
        "  <Option name='LABEL' type='string' "
        "description='Label to use, either as a JSON string or a filename "
        "containing one'/>"
        "  <Option name='COMPRESS' type='string-select' "
        "description='Compression method' default='NONE'>"
        "     <Value>NONE</Value>"
        "     <Value>BASIC</Value>"
        "     <Value>BASIC2</Value>"
        "  </Option>"
        "</CreationOptionList>");

    poDriver->pfnOpen = VICARDataset::Open;
    poDriver->pfnIdentify = VICARDataset::Identify;
    poDriver->pfnCreate = VICARDataset::Create;
    poDriver->pfnCreateCopy = VICARDataset::CreateCopy;

    GetGDALDriverManager()->RegisterDriver(poDriver);
}
