/******************************************************************************
 *
 * Project:  PDS 4 Driver; Planetary Data System Format
 * Purpose:  Implementation of PDS4Dataset
 * Author:   Even Rouault, even.rouault at spatialys.com
 *
 ******************************************************************************
 * Copyright (c) 2019, Hobu Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "pds4dataset.h"
#include "ogr_vrt.h"

#include "ogr_p.h"

#include <algorithm>
#include <cassert>

/************************************************************************/
/* ==================================================================== */
/*                        PDS4TableBaseLayer                            */
/* ==================================================================== */
/************************************************************************/

PDS4TableBaseLayer::PDS4TableBaseLayer(PDS4Dataset* poDS,
                                       const char* pszName,
                                       const char* pszFilename) :
    m_poDS(poDS),
    m_poRawFeatureDefn(new OGRFeatureDefn(pszName)),
    m_poFeatureDefn(new OGRFeatureDefn(pszName)),
    m_osFilename(pszFilename)
{
    m_poRawFeatureDefn->SetGeomType(wkbNone);
    m_poRawFeatureDefn->Reference();
    m_poFeatureDefn->SetGeomType(wkbNone);
    m_poFeatureDefn->Reference();
    SetDescription(pszName);

    m_bKeepGeomColmuns = CPLFetchBool(
        m_poDS->GetOpenOptions(), "KEEP_GEOM_COLUMNS", false);
}

/************************************************************************/
/*                       ~PDS4TableBaseLayer()                          */
/************************************************************************/

PDS4TableBaseLayer::~PDS4TableBaseLayer()
{
    m_poFeatureDefn->Release();
    m_poRawFeatureDefn->Release();
    if( m_fp )
        VSIFCloseL(m_fp);
}

/************************************************************************/
/*                            RenameFileTo()                            */
/************************************************************************/

bool PDS4TableBaseLayer::RenameFileTo(const char* pszNewName)
{
    if( m_fp )
        VSIFCloseL(m_fp);
    m_fp = nullptr;
    CPLString osBackup(pszNewName);
    osBackup += ".bak";
    VSIRename(pszNewName, osBackup);
    bool bSuccess = VSIRename(m_osFilename, pszNewName) == 0;
    if( bSuccess )
    {
        m_fp = VSIFOpenL(pszNewName, "rb+");
        if( !m_fp )
        {
            VSIRename(osBackup, pszNewName);
            return false;
        }

        m_osFilename = pszNewName;
        VSIUnlink(osBackup);
        return true;
    }
    else
    {
        VSIRename(osBackup, pszNewName);
        return false;
    }
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/

char** PDS4TableBaseLayer::GetFileList() const
{
    return CSLAddString(nullptr, GetFileName());
}

/************************************************************************/
/*                          GetFeatureCount()                           */
/************************************************************************/

GIntBig PDS4TableBaseLayer::GetFeatureCount(int bForce)
{
    if( m_poAttrQuery != nullptr || m_poFilterGeom != nullptr )
    {
        return OGRLayer::GetFeatureCount(bForce);
    }
    return m_nFeatureCount;
}

/************************************************************************/
/*                           SetupGeomField()                           */
/************************************************************************/

void PDS4TableBaseLayer::SetupGeomField()
{
    const char* const *papszOpenOptions = m_poDS->GetOpenOptions();
    const char* pszWKT = CSLFetchNameValue(papszOpenOptions, "WKT");
    if( pszWKT == nullptr &&
        (m_iWKT = m_poRawFeatureDefn->GetFieldIndex("WKT")) >= 0 &&
        m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetType() == OFTString )
    {
        pszWKT = "WKT";
    }
    else
    {
        m_iWKT = -1;
    }
    if( pszWKT && !EQUAL(pszWKT, "") )
    {
        m_iWKT = m_poRawFeatureDefn->GetFieldIndex(pszWKT);
        if( m_iWKT < 0 )
        {
            CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s", pszWKT);
        }
        else if( m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetType() != OFTString )
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "The %s field should be of type String", pszWKT);
        }
        else
        {
            m_poFeatureDefn->SetGeomType(wkbUnknown);
        }
    }
    else
    {
        const char* pszLat = CSLFetchNameValue(papszOpenOptions, "LAT");
        const char* pszLong = CSLFetchNameValue(papszOpenOptions, "LONG");
        if( pszLat == nullptr && pszLong == nullptr &&
            (m_iLatField = m_poRawFeatureDefn->GetFieldIndex("Latitude")) >= 0 &&
            (m_iLongField = m_poRawFeatureDefn->GetFieldIndex("Longitude")) >= 0 &&
            m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetType() == OFTReal &&
            m_poRawFeatureDefn->GetFieldDefn(m_iLongField)->GetType() == OFTReal )
        {
            pszLat = "Latitude";
            pszLong = "Longitude";
        }
        else
        {
            m_iLatField = -1;
            m_iLongField = -1;
        }
        if( pszLat && pszLong && !EQUAL(pszLat, "") && !EQUAL(pszLong, "") )
        {
            m_iLatField = m_poRawFeatureDefn->GetFieldIndex(pszLat);
            m_iLongField = m_poRawFeatureDefn->GetFieldIndex(pszLong);
            if( m_iLatField < 0 )
            {
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s", pszLat);
            }
            else if( m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetType() != OFTReal )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                        "The %s field should be of type Real", pszLat);
                m_iLatField = -1;
            }
            if( m_iLongField < 0 )
            {
                CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s", pszLong);
            }
            else if( m_poRawFeatureDefn->GetFieldDefn(m_iLongField)->GetType() != OFTReal )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                        "The %s field should be of type Real", pszLong);
                m_iLongField = -1;
            }
            if( m_iLatField < 0 || m_iLongField < 0 )
            {
                m_iLatField = -1;
                m_iLongField = -1;
            }
            else
            {
                const char* pszAlt = CSLFetchNameValue(papszOpenOptions, "ALT");
                if( pszAlt == nullptr &&
                    (m_iAltField = m_poRawFeatureDefn->GetFieldIndex("Altitude")) >= 0 &&
                    m_poRawFeatureDefn->GetFieldDefn(m_iAltField)->GetType() == OFTReal )
                {
                    pszAlt = "Altitude";
                }
                else
                {
                    m_iAltField = -1;
                }
                if( pszAlt && !EQUAL(pszAlt, "") )
                {
                    m_iAltField = m_poRawFeatureDefn->GetFieldIndex(pszAlt);
                    if( m_iAltField < 0 )
                    {
                        CPLError(CE_Warning, CPLE_AppDefined, "Unknown field %s", pszAlt);
                    }
                    else if( m_poRawFeatureDefn->GetFieldDefn(m_iAltField)->GetType() != OFTReal )
                    {
                        CPLError(CE_Warning, CPLE_AppDefined,
                                "The %s field should be of type Real", pszAlt);
                        m_iAltField = -1;
                    }
                }
                m_poFeatureDefn->SetGeomType(m_iAltField >= 0 ? wkbPoint25D : wkbPoint);
            }
        }
    }

    for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( !m_bKeepGeomColmuns &&
            (i == m_iWKT || i == m_iLatField || i == m_iLongField || i == m_iAltField) )
        {
            // do nothing;
        }
        else
        {
            m_poFeatureDefn->AddFieldDefn(m_poRawFeatureDefn->GetFieldDefn(i));
        }
    }
}

/************************************************************************/
/*                      AddGeometryFromFields()                         */
/************************************************************************/

OGRFeature* PDS4TableBaseLayer::AddGeometryFromFields(OGRFeature* poRawFeature)
{
    OGRFeature* poFeature = new OGRFeature(m_poFeatureDefn);
    poFeature->SetFID(poRawFeature->GetFID());
    for( int i = 0, j = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( !m_bKeepGeomColmuns &&
            (i == m_iWKT || i == m_iLatField || i == m_iLongField || i == m_iAltField) )
        {
            // do nothing;
        }
        else
        {
            poFeature->SetField(j, poRawFeature->GetRawFieldRef(i));
            j++;
        }
    }

    if( m_iWKT >= 0 )
    {
        const char* pszWKT = poRawFeature->GetFieldAsString(m_iWKT);
        if( pszWKT && pszWKT[0] != '\0' )
        {
            OGRGeometry* poGeom = nullptr;
            OGRGeometryFactory::createFromWkt(pszWKT, nullptr, &poGeom);
            if( poGeom )
            {
                poGeom->assignSpatialReference(GetSpatialRef());
                poFeature->SetGeometryDirectly(poGeom);
            }
        }
    }
    else if( m_iLatField >= 0 && m_iLongField >= 0 &&
             poRawFeature->IsFieldSetAndNotNull(m_iLatField) &&
             poRawFeature->IsFieldSetAndNotNull(m_iLongField) )
    {
        double dfLat = poRawFeature->GetFieldAsDouble(m_iLatField);
        double dfLong = poRawFeature->GetFieldAsDouble(m_iLongField);
        OGRPoint* poPoint;
        if( m_iAltField >= 0 && poRawFeature->IsFieldSetAndNotNull(m_iAltField) )
        {
            double dfAlt = poRawFeature->GetFieldAsDouble(m_iAltField);
            poPoint = new OGRPoint(dfLong, dfLat, dfAlt);
        }
        else
        {
            poPoint = new OGRPoint(dfLong, dfLat);
        }
        poPoint->assignSpatialReference(GetSpatialRef());
        poFeature->SetGeometryDirectly(poPoint);
    }
    return poFeature;
}

/************************************************************************/
/*                      AddFieldsFromGeometry()                         */
/************************************************************************/

OGRFeature* PDS4TableBaseLayer::AddFieldsFromGeometry(OGRFeature* poFeature)
{
    OGRFeature* poRawFeature = new OGRFeature(m_poRawFeatureDefn);
    for( int i = 0, j = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( !m_bKeepGeomColmuns &&
            (i == m_iWKT || i == m_iLatField || i == m_iLongField || i == m_iAltField) )
        {
            // do nothing;
        }
        else
        {
            poRawFeature->SetField(i, poFeature->GetRawFieldRef(j));
            j++;
        }
    }

    auto poGeom = poFeature->GetGeometryRef();
    if( poGeom )
    {
        if( m_iLongField >= 0 && m_iLatField >= 0 &&
            wkbFlatten(poGeom->getGeometryType()) == wkbPoint )
        {
            auto poPoint = poGeom->toPoint();
            poRawFeature->SetField(m_iLongField, poPoint->getX());
            poRawFeature->SetField(m_iLatField, poPoint->getY());
            if( m_iAltField >= 0 && poGeom->getGeometryType() == wkbPoint25D )
            {
                poRawFeature->SetField(m_iAltField, poPoint->getZ());
            }
        }
        else if( m_iWKT >= 0 )
        {
            char* pszWKT = nullptr;
            poGeom->exportToWkt(&pszWKT);
            if( pszWKT )
            {
                poRawFeature->SetField(m_iWKT, pszWKT);
            }
            CPLFree(pszWKT);
        }
    }
    return poRawFeature;
}

/************************************************************************/
/*                         MarkHeaderDirty()                            */
/************************************************************************/

void PDS4TableBaseLayer::MarkHeaderDirty()
{
    m_bDirtyHeader = true;
    m_poDS->MarkHeaderDirty();
}

/************************************************************************/
/*              RefreshFileAreaObservationalBeginningCommon()           */
/************************************************************************/

CPLXMLNode* PDS4TableBaseLayer::RefreshFileAreaObservationalBeginningCommon(
                                                CPLXMLNode* psFAO,
                                                const CPLString& osPrefix,
                                                const char* pszTableEltName,
                                                CPLString& osDescription)
{
    CPLXMLNode* psFile = CPLGetXMLNode(psFAO, (osPrefix + "File").c_str());
    CPLAssert(psFile);
    CPLXMLNode* psfile_size = CPLGetXMLNode(psFile, (osPrefix + "file_size").c_str());
    if( psfile_size )
    {
        CPLRemoveXMLChild(psFile, psfile_size);
        CPLDestroyXMLNode(psfile_size);
    }

    CPLXMLNode* psHeader = CPLGetXMLNode(psFAO, (osPrefix + "Header").c_str());
    if( psHeader )
    {
        CPLRemoveXMLChild(psFAO, psHeader);
        CPLDestroyXMLNode(psHeader);
    }

    CPLString osTableEltName(osPrefix + pszTableEltName);
    CPLXMLNode* psTable = CPLGetXMLNode(psFAO, osTableEltName);
    CPLString osName;
    CPLString osLocalIdentifier;
    if( psTable )
    {
        osName = CPLGetXMLValue(psTable, (osPrefix + "name").c_str(), "");
        osLocalIdentifier = CPLGetXMLValue(psTable, (osPrefix + "local_identifier").c_str(), "");
        osDescription = CPLGetXMLValue(psTable, (osPrefix + "description").c_str(), "");
        CPLRemoveXMLChild(psFAO, psTable);
        CPLDestroyXMLNode(psTable);
    }

    // Write Table_Delimited/Table_Character/Table_Binary
    psTable = CPLCreateXMLNode(psFAO, CXT_Element, osTableEltName);
    if( !osName.empty() )
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "name").c_str(), osName);
    if( !osLocalIdentifier.empty() )
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "local_identifier").c_str(),
                                    osLocalIdentifier);
    else
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "local_identifier").c_str(),
                                    GetName());

    CPLXMLNode* psOffset = CPLCreateXMLElementAndValue(
        psTable, (osPrefix + "offset").c_str(),
        CPLSPrintf(CPL_FRMT_GUIB, m_nOffset));
    CPLAddXMLAttributeAndValue(psOffset, "unit", "byte");

    return psTable;
}

/************************************************************************/
/* ==================================================================== */
/*                        PDS4FixedWidthTable                           */
/* ==================================================================== */
/************************************************************************/

PDS4FixedWidthTable::PDS4FixedWidthTable(PDS4Dataset* poDS,
                                       const char* pszName,
                                       const char* pszFilename) :
    PDS4TableBaseLayer(poDS, pszName, pszFilename)
{
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void PDS4FixedWidthTable::ResetReading()
{
    m_nFID = 1;
}

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

OGRFeature* PDS4FixedWidthTable::GetNextFeature()
{
    while(true)
    {
        auto poFeature = GetFeature(m_nFID);
        if( poFeature == nullptr )
        {
            return nullptr;
        }
        ++m_nFID;

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

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int PDS4FixedWidthTable::TestCapability(const char* pszCap)
{
    if( EQUAL(pszCap, OLCRandomRead) ||
        EQUAL(pszCap, OLCStringsAsUTF8) )
    {
        return true;
    }
    if( EQUAL(pszCap, OLCFastFeatureCount) )
    {
        return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
    }
    if( EQUAL(pszCap, OLCCreateField) )
    {
        return m_poDS->GetAccess() == GA_Update && m_nFeatureCount == 0;
    }
    if( EQUAL(pszCap, OLCSequentialWrite) ||
        EQUAL(pszCap, OLCRandomWrite) )
    {
        return m_poDS->GetAccess() == GA_Update;
    }
    return false;
}

/************************************************************************/
/*                            ISetFeature()                             */
/************************************************************************/

OGRErr PDS4FixedWidthTable::ISetFeature( OGRFeature *poFeature )
{
    if( poFeature->GetFID() <= 0 || poFeature->GetFID() > m_nFeatureCount )
    {
        return OGRERR_NON_EXISTING_FEATURE;
    }
    if( m_poDS->GetAccess() != GA_Update )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Dataset opened in read-only mode");
        return OGRERR_FAILURE;
    }
    CPLAssert( static_cast<int>(m_osBuffer.size()) == m_nRecordSize );
    if( m_bHasCRLF )
    {
        CPLAssert( m_nRecordSize > 2 );
    }

    VSIFSeekL(m_fp, m_nOffset + (poFeature->GetFID() - 1) * m_nRecordSize, SEEK_SET);
    memset(&m_osBuffer[0], ' ', m_nRecordSize);

    OGRFeature* poRawFeature = AddFieldsFromGeometry(poFeature);
    for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( !poRawFeature->IsFieldSetAndNotNull(i) )
        {
            continue;
        }
        CPLString osBuffer;
        const CPLString& osDT(m_aoFields[i].m_osDataType);
        const auto eType( m_poRawFeatureDefn->GetFieldDefn(i)->GetType() );
        if( osDT == "ASCII_Real" )
        {
            CPLString osFormat;
            osFormat.Printf("%%.%dg", m_aoFields[i].m_nLength - 2);
            osBuffer.Printf(osFormat.c_str(), poRawFeature->GetFieldAsDouble(i));
        }
        else if( osDT == "ASCII_Integer" ||
                 osDT == "ASCII_NonNegative_Integer" ||
                 eType == OFTString )
        {
            osBuffer = poRawFeature->GetFieldAsString(i);
        }
        else if( osDT == "ASCII_Boolean" )
        {
            osBuffer = poRawFeature->GetFieldAsInteger(i) == 1 ? "1" : "0";
        }
        else if( osDT == "IEEE754LSBDouble" )
        {
            double dfVal = poRawFeature->GetFieldAsDouble(i);
            CPL_LSBPTR64(&dfVal);
            osBuffer.resize(sizeof(dfVal));
            memcpy(&osBuffer[0], &dfVal, sizeof(dfVal));
        }
        else if( osDT == "IEEE754MSBDouble" )
        {
            double dfVal = poRawFeature->GetFieldAsDouble(i);
            CPL_MSBPTR64(&dfVal);
            osBuffer.resize(sizeof(dfVal));
            memcpy(&osBuffer[0], &dfVal, sizeof(dfVal));
        }
        else if( osDT == "IEEE754LSBSingle" )
        {
            float fVal = static_cast<float>(poRawFeature->GetFieldAsDouble(i));
            CPL_LSBPTR32(&fVal);
            osBuffer.resize(sizeof(fVal));
            memcpy(&osBuffer[0], &fVal, sizeof(fVal));
        }
        else if( osDT == "IEEE754MSBSingle" )
        {
            float fVal = static_cast<float>(poRawFeature->GetFieldAsDouble(i));
            CPL_MSBPTR32(&fVal);
            osBuffer.resize(sizeof(fVal));
            memcpy(&osBuffer[0], &fVal, sizeof(fVal));
        }
        else if( osDT == "SignedByte" )
        {
            signed char bVal = static_cast<signed char>(
                std::max(-128, std::min(127, poRawFeature->GetFieldAsInteger(i))));
            osBuffer.resize(sizeof(bVal));
            memcpy(&osBuffer[0], &bVal, sizeof(bVal));
        }
        else if( osDT == "UnsignedByte" )
        {
            GByte ubVal = static_cast<GByte>(
                std::max(0, std::min(255, poRawFeature->GetFieldAsInteger(i))));
            osBuffer.resize(sizeof(ubVal));
            memcpy(&osBuffer[0], &ubVal, sizeof(ubVal));
        }
        else if( osDT == "SignedLSB2" )
        {
            GInt16 sVal = static_cast<GInt16>(std::max(
                -32768, std::min(32767, poRawFeature->GetFieldAsInteger(i))));
            CPL_LSBPTR16(&sVal);
            osBuffer.resize(sizeof(sVal));
            memcpy(&osBuffer[0], &sVal, sizeof(sVal));
        }
        else if( osDT == "SignedMSB2" )
        {
            GInt16 sVal = static_cast<GInt16>(std::max(
                -32768, std::min(32767, poRawFeature->GetFieldAsInteger(i))));
            CPL_MSBPTR16(&sVal);
            osBuffer.resize(sizeof(sVal));
            memcpy(&osBuffer[0], &sVal, sizeof(sVal));
        }
        else if( osDT == "UnsignedLSB2" )
        {
            GUInt16 usVal = static_cast<GUInt16>(std::max(
                0, std::min(65535, poRawFeature->GetFieldAsInteger(i))));
            CPL_LSBPTR16(&usVal);
            osBuffer.resize(sizeof(usVal));
            memcpy(&osBuffer[0], &usVal, sizeof(usVal));
        }
        else if( osDT == "UnsignedMSB2" )
        {
            GUInt16 usVal = static_cast<GUInt16>(std::max(
                0, std::min(65535, poRawFeature->GetFieldAsInteger(i))));
            CPL_MSBPTR16(&usVal);
            osBuffer.resize(sizeof(usVal));
            memcpy(&osBuffer[0], &usVal, sizeof(usVal));
        }
        else if( osDT == "SignedLSB4" )
        {
            GInt32 nVal = poRawFeature->GetFieldAsInteger(i);
            CPL_LSBPTR32(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "SignedMSB4" )
        {
            GInt32 nVal = poRawFeature->GetFieldAsInteger(i);
            CPL_MSBPTR32(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "UnsignedLSB4" )
        {
            GUInt32 nVal = static_cast<GUInt32>(
                std::max(0, poRawFeature->GetFieldAsInteger(i)));
            CPL_LSBPTR32(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "UnsignedMSB4" )
        {
            GUInt32 nVal = static_cast<GUInt32>(
                std::max(0, poRawFeature->GetFieldAsInteger(i)));
            CPL_MSBPTR32(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "SignedLSB8" )
        {
            GInt64 nVal = poRawFeature->GetFieldAsInteger64(i);
            CPL_LSBPTR64(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "SignedMSB8" )
        {
            GInt64 nVal = poRawFeature->GetFieldAsInteger64(i);
            CPL_MSBPTR64(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "UnsignedLSB8" )
        {
            GUInt64 nVal = static_cast<GUInt64>(
                std::max(static_cast<GIntBig>(0),
                         poRawFeature->GetFieldAsInteger64(i)));
            CPL_LSBPTR64(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "UnsignedMSB8" )
        {
            GUInt64 nVal = static_cast<GUInt64>(
                std::max(static_cast<GIntBig>(0),
                         poRawFeature->GetFieldAsInteger64(i)));
            CPL_MSBPTR64(&nVal);
            osBuffer.resize(sizeof(nVal));
            memcpy(&osBuffer[0], &nVal, sizeof(nVal));
        }
        else if( osDT == "ASCII_Date_Time_YMD" ||
                 osDT == "ASCII_Date_Time_YMD_UTC" )
        {
            char* pszDateTime = OGRGetXMLDateTime(poRawFeature->GetRawFieldRef(i));
            osBuffer = pszDateTime;
            CPLFree(pszDateTime);
        }
        else if( osDT == "ASCII_Date_YMD" )
        {
            int nYear, nMonth, nDay;
            poRawFeature->GetFieldAsDateTime(i, &nYear, &nMonth, &nDay,
                                          nullptr, nullptr,
                                          static_cast<float*>(nullptr), nullptr);
            osBuffer.Printf("%04d-%02d-%02d", nYear, nMonth, nDay);
        }
        else if( osDT == "ASCII_Time" )
        {
            int nHour, nMin;
            float fSec;
            poRawFeature->GetFieldAsDateTime(i, nullptr, nullptr, nullptr,
                                          &nHour, &nMin, &fSec, nullptr);
            osBuffer.Printf("%02d:%02d:%05.3f", nHour, nMin, fSec);
        }

        if( !osBuffer.empty() &&
            osBuffer.size() <= static_cast<size_t>(m_aoFields[i].m_nLength) )
        {
            memcpy(&m_osBuffer[m_aoFields[i].m_nOffset +
                                m_aoFields[i].m_nLength - osBuffer.size()],
                    &osBuffer[0], osBuffer.size());
        }
        else if( !osBuffer.empty() )
        {
            if( eType == OFTString )
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Value %s for field %s is too large. Truncating it",
                         osBuffer.c_str(),
                         m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());
                memcpy(&m_osBuffer[m_aoFields[i].m_nOffset],
                       osBuffer.data(), m_aoFields[i].m_nLength);
            }
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Value %s for field %s is too large. Omitting i",
                         osBuffer.c_str(),
                         m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());
            }
        }
    }
    delete poRawFeature;

    if( m_bHasCRLF )
    {
        m_osBuffer[m_osBuffer.size() - 2] = '\r';
        m_osBuffer[m_osBuffer.size() - 1] = '\n';
    }

    if( VSIFWriteL(&m_osBuffer[0], m_nRecordSize, 1, m_fp) != 1 )
    {
        return OGRERR_FAILURE;
    }

    return OGRERR_NONE;
}

/************************************************************************/
/*                           ICreateFeature()                           */
/************************************************************************/

OGRErr PDS4FixedWidthTable::ICreateFeature( OGRFeature *poFeature )
{
    m_nFeatureCount ++;
    poFeature->SetFID(m_nFeatureCount);
    OGRErr eErr = ISetFeature(poFeature);
    if( eErr == OGRERR_NONE )
    {
        MarkHeaderDirty();
    }
    else
    {
        poFeature->SetFID(-1);
        m_nFeatureCount --;
    }
    return eErr;
}

/************************************************************************/
/*                              GetFeature()                            */
/************************************************************************/

OGRFeature* PDS4FixedWidthTable::GetFeature(GIntBig nFID)
{
    if( nFID <= 0 || nFID > m_nFeatureCount )
    {
        return nullptr;
    }
    VSIFSeekL(m_fp, m_nOffset + (nFID-1) * m_nRecordSize, SEEK_SET);
    if( VSIFReadL(&m_osBuffer[0], m_nRecordSize, 1, m_fp) != 1 )
    {
        return nullptr;
    }
    OGRFeature* poRawFeature = new OGRFeature(m_poRawFeatureDefn);
    poRawFeature->SetFID(nFID);
    for( int i = 0; i < poRawFeature->GetFieldCount(); i++ )
    {
        CPLString osVal(
            m_osBuffer.substr(m_aoFields[i].m_nOffset, m_aoFields[i].m_nLength));

        const CPLString& osDT(m_aoFields[i].m_osDataType);
        if( STARTS_WITH(osDT, "ASCII_") || STARTS_WITH(osDT, "UTF8_") )
        {
            osVal.Trim();
            if( osVal.empty() )
            {
                continue;
            }
        }

        if( osDT == "IEEE754LSBDouble" )
        {
            double dfVal;
            CPLAssert(osVal.size() == sizeof(dfVal));
            memcpy(&dfVal, osVal.data(), sizeof(dfVal));
            CPL_LSBPTR64(&dfVal);
            poRawFeature->SetField(i, dfVal);
        }
        else if( osDT == "IEEE754MSBDouble" )
        {
            double dfVal;
            CPLAssert(osVal.size() == sizeof(dfVal));
            memcpy(&dfVal, osVal.data(), sizeof(dfVal));
            CPL_MSBPTR64(&dfVal);
            poRawFeature->SetField(i, dfVal);
        }
        else if( osDT == "IEEE754LSBSingle" )
        {
            float fVal;
            CPLAssert(osVal.size() == sizeof(fVal));
            memcpy(&fVal, osVal.data(), sizeof(fVal));
            CPL_LSBPTR32(&fVal);
            poRawFeature->SetField(i, fVal);
        }
        else if( osDT == "IEEE754MSBSingle" )
        {
            float fVal;
            CPLAssert(osVal.size() == sizeof(fVal));
            memcpy(&fVal, osVal.data(), sizeof(fVal));
            CPL_MSBPTR32(&fVal);
            poRawFeature->SetField(i, fVal);
        }
        else if( osDT == "SignedByte" )
        {
            signed char bVal;
            CPLAssert(osVal.size() == sizeof(bVal));
            memcpy(&bVal, osVal.data(), sizeof(bVal));
            poRawFeature->SetField(i, bVal);
        }
        else if( osDT == "UnsignedByte" )
        {
            GByte bVal;
            CPLAssert(osVal.size() == sizeof(bVal));
            memcpy(&bVal, osVal.data(), sizeof(bVal));
            poRawFeature->SetField(i, bVal);
        }
        else if( osDT == "SignedLSB2" )
        {
            GInt16 sVal;
            CPLAssert(osVal.size() == sizeof(sVal));
            memcpy(&sVal, osVal.data(), sizeof(sVal));
            CPL_LSBPTR16(&sVal);
            poRawFeature->SetField(i, sVal);
        }
        else if( osDT == "SignedMSB2" )
        {
            GInt16 sVal;
            CPLAssert(osVal.size() == sizeof(sVal));
            memcpy(&sVal, osVal.data(), sizeof(sVal));
            CPL_MSBPTR16(&sVal);
            poRawFeature->SetField(i, sVal);
        }
        else if( osDT == "UnsignedLSB2" )
        {
            GUInt16 usVal;
            CPLAssert(osVal.size() == sizeof(usVal));
            memcpy(&usVal, osVal.data(), sizeof(usVal));
            CPL_LSBPTR16(&usVal);
            poRawFeature->SetField(i, usVal);
        }
        else if( osDT == "UnsignedMSB2" )
        {
            GUInt16 usVal;
            CPLAssert(osVal.size() == sizeof(usVal));
            memcpy(&usVal, osVal.data(), sizeof(usVal));
            CPL_MSBPTR16(&usVal);
            poRawFeature->SetField(i, usVal);
        }
        else if( osDT == "SignedLSB4" )
        {
            GInt32 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_LSBPTR32(&nVal);
            poRawFeature->SetField(i, nVal);
        }
        else if( osDT == "SignedMSB4" )
        {
            GInt32 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_MSBPTR32(&nVal);
            poRawFeature->SetField(i, nVal);
        }
        else if( osDT == "UnsignedLSB4" )
        {
            GUInt32 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_LSBPTR32(&nVal);
            poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
        }
        else if( osDT == "UnsignedMSB4" )
        {
            GUInt32 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_MSBPTR32(&nVal);
            poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
        }
        else if( osDT == "SignedLSB8" )
        {
            GInt64 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_LSBPTR64(&nVal);
            poRawFeature->SetField(i, nVal);
        }
        else if( osDT == "SignedMSB8" )
        {
            GInt64 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_MSBPTR64(&nVal);
            poRawFeature->SetField(i, nVal);
        }
        else if( osDT == "UnsignedLSB8" )
        {
            GUInt64 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_LSBPTR64(&nVal);
            poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
        }
        else if( osDT == "UnsignedMSB8" )
        {
            GUInt64 nVal;
            CPLAssert(osVal.size() == sizeof(nVal));
            memcpy(&nVal, osVal.data(), sizeof(nVal));
            CPL_MSBPTR64(&nVal);
            poRawFeature->SetField(i, static_cast<GIntBig>(nVal));
        }
        else if( osDT == "ASCII_Boolean" )
        {
            poRawFeature->SetField(i, EQUAL(osVal, "t") || EQUAL(osVal, "1") ? 1 : 0);
        }
        else
        {
            poRawFeature->SetField(i, osVal.c_str());
        }
    }
    OGRFeature* poFeature = AddGeometryFromFields(poRawFeature);
    delete poRawFeature;
    return poFeature;
}

/************************************************************************/
/*                     GetFieldTypeFromPDS4DataType()                   */
/************************************************************************/

static OGRFieldType GetFieldTypeFromPDS4DataType(const char* pszDataType,
                                                 int nDTSize,
                                                 OGRFieldSubType& eSubType,
                                                 bool& error)
{
    OGRFieldType eType = OFTString;
    eSubType = OFSTNone;
    error = false;
    if( EQUAL(pszDataType, "ASCII_Boolean") )
    {
        eSubType = OFSTBoolean;
        eType = OFTInteger;
    }
    else if( EQUAL(pszDataType, "ASCII_Date_Time_YMD") ||
             EQUAL(pszDataType, "ASCII_Date_Time_YMD_UTC") )
    {
        eType = OFTDateTime;
    }
    else if( EQUAL(pszDataType, "ASCII_Date_YMD") )
    {
        eType = OFTDate;
    }
    else if( EQUAL(pszDataType, "ASCII_Integer") ||
             EQUAL(pszDataType, "ASCII_NonNegative_Integer") )
    {
        eType = OFTInteger;
    }
    else if( EQUAL(pszDataType, "SignedByte") ||
             EQUAL(pszDataType, "UnsignedByte") )
    {
        if( nDTSize != 1 )
            error = true;
        eType = OFTInteger;
    }
    else if( EQUAL(pszDataType, "SignedLSB2") ||
             EQUAL(pszDataType, "SignedMSB2") )
    {
        if( nDTSize != 2 )
            error = true;
        eType = OFTInteger;
        eSubType = OFSTInt16;
    }
    else if( EQUAL(pszDataType, "UnsignedLSB2") ||
             EQUAL(pszDataType, "UnsignedMSB2") )
    {
        if( nDTSize != 2 )
            error = true;
        eType = OFTInteger;
    }
    else if( EQUAL(pszDataType, "SignedLSB4") ||
             EQUAL(pszDataType, "SignedMSB4") )
    {
        if( nDTSize != 4 )
            error = true;
        eType = OFTInteger;
    }
    else if( EQUAL(pszDataType, "UnsignedLSB4") ||
             EQUAL(pszDataType, "UnsignedMSB4") )
    {
        if( nDTSize != 4 )
            error = true;
        // Use larger data type as > 2 billion values don't hold on signed int32
        eType = OFTInteger64;
    }
    else if( EQUAL(pszDataType, "SignedLSB8") ||
             EQUAL(pszDataType, "SignedMSB8") )
    {
        if( nDTSize != 8 )
            error = true;
        eType = OFTInteger64;
    }
    else if( EQUAL(pszDataType, "UnsignedLSB8") ||
             EQUAL(pszDataType, "UnsignedMSB8") )
    {
        if( nDTSize != 8 )
            error = true;
        // Hope that we won't get value larger than > 2^63...
        eType = OFTInteger64;
    }
    else if( EQUAL(pszDataType, "ASCII_Real") )
    {
        eType = OFTReal;
    }
    else if( EQUAL(pszDataType, "IEEE754LSBDouble") ||
             EQUAL(pszDataType, "IEEE754MSBDouble") )
    {
        if( nDTSize != 8 )
            error = true;
        eType = OFTReal;
    }
    else if( EQUAL(pszDataType, "IEEE754LSBSingle") ||
             EQUAL(pszDataType, "IEEE754MSBSingle") )
    {
        if( nDTSize != 4 )
            error = true;
        eType = OFTReal;
        eSubType = OFSTFloat32;
    }
    else if( EQUAL(pszDataType, "ASCII_Time") )
    {
        eType = OFTTime;
    }
    return eType;
}

/************************************************************************/
/*                            ReadTableDef()                            */
/************************************************************************/

bool PDS4FixedWidthTable::ReadTableDef(const CPLXMLNode* psTable)
{
    CPLAssert( m_fp == nullptr );
    m_fp = VSIFOpenL(m_osFilename,
                     (m_poDS->GetAccess() == GA_ReadOnly ) ? "rb" : "r+b");
    if( !m_fp )
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
                 m_osFilename.c_str());
        return false;
    }

    m_nOffset = static_cast<GUIntBig>(
        CPLAtoGIntBig(CPLGetXMLValue(psTable, "offset", "0")));

    m_nFeatureCount = CPLAtoGIntBig(
        CPLGetXMLValue(psTable, "records", "-1"));

    m_bHasCRLF = EQUAL(
        CPLGetXMLValue(psTable, "record_delimiter", ""),
        "Carriage-Return Line-Feed");

    const CPLXMLNode* psRecord =
        CPLGetXMLNode(psTable, ("Record_" + GetSubType()).c_str());
    if( !psRecord )
    {
        return false;
    }
    m_nRecordSize = atoi(CPLGetXMLValue(psRecord, "record_length", "0"));
    if( m_nRecordSize <= (m_bHasCRLF ? 2 : 0) || m_nRecordSize > 1000 * 1000 )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid record_length");
        return false;
    }
    m_osBuffer.resize(m_nRecordSize);
    if( !ReadFields(psRecord, 0, "") )
    {
        return false;
    }

    SetupGeomField();

    return true;
}

/************************************************************************/
/*                             ReadFields()                             */
/************************************************************************/

bool PDS4FixedWidthTable::ReadFields(const CPLXMLNode* psParent,
                                     int nBaseOffset,
                                     const CPLString& osSuffixFieldName)
{
    for( const CPLXMLNode* psIter = psParent->psChild;
                           psIter; psIter = psIter->psNext )
    {
        if( psIter->eType == CXT_Element &&
            strcmp(psIter->pszValue, ("Field_" + GetSubType()).c_str()) == 0 )
        {
            const char* pszName = CPLGetXMLValue(psIter, "name", nullptr);
            if( !pszName )
            {
                return false;
            }
            const char* pszLoc = CPLGetXMLValue(psIter, "field_location", nullptr);
            if( !pszLoc )
            {
                return false;
            }
            const char* pszDataType = CPLGetXMLValue(psIter, "data_type", nullptr);
            if( !pszDataType )
            {
                return false;
            }
            const char* pszFieldLength = CPLGetXMLValue(psIter, "field_length", nullptr);
            if( !pszFieldLength )
            {
                return false;
            }
            Field f;
            f.m_nOffset = nBaseOffset + atoi(pszLoc) - 1; // Location is 1-based
            if( f.m_nOffset < 0 || f.m_nOffset >= m_nRecordSize )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid field_location");
                return false;
            }
            f.m_nLength = atoi(pszFieldLength);
            if( f.m_nLength <= 0 || f.m_nLength > m_nRecordSize - (m_bHasCRLF ? 2 : 0) - f.m_nOffset )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid field_length");
                return false;
            }
            f.m_osDataType = pszDataType;
            f.m_osUnit = CPLGetXMLValue(psIter, "unit", "");
            f.m_osDescription = CPLGetXMLValue(psIter, "description", "");

            const char* pszFieldFormat = CPLGetXMLValue(psIter, "field_format", "");

            CPLXMLNode* psSpecialConstants = const_cast<CPLXMLNode*>(
                CPLGetXMLNode(psIter, "Special_Constants"));
            if( psSpecialConstants )
            {
                auto psNext = psSpecialConstants->psNext;
                psSpecialConstants->psNext = nullptr;
                char* pszXML = CPLSerializeXMLTree(psSpecialConstants);
                psSpecialConstants->psNext = psNext;
                if( pszXML )
                {
                    f.m_osSpecialConstantsXML = pszXML;
                    CPLFree(pszXML);
                }
            }

            m_aoFields.push_back(f);

            OGRFieldSubType eSubType = OFSTNone;
            bool error = false;
            auto eType = GetFieldTypeFromPDS4DataType(pszDataType, f.m_nLength, eSubType, error);
            if( error )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Inconsistent field_length w.r.t datatype");
                return false;
            }
            if( STARTS_WITH(f.m_osDataType, "ASCII_") &&
                eType == OFTInteger && f.m_nLength >= 10 )
            {
                eType = OFTInteger64;
            }
            OGRFieldDefn oFieldDefn((pszName + osSuffixFieldName).c_str(), eType);
            oFieldDefn.SetSubType(eSubType);
            if( eType != OFTReal &&
                (STARTS_WITH(f.m_osDataType, "ASCII_") ||
                 STARTS_WITH(f.m_osDataType, "UTF_8")) )
            {
                oFieldDefn.SetWidth(f.m_nLength);
            }
            else if( (eType == OFTInteger || eType == OFTInteger64) &&
                     pszFieldFormat &&
                     pszFieldFormat[0] == '%' &&
                     pszFieldFormat[strlen(pszFieldFormat)-1] == 'd' )
            {
                oFieldDefn.SetWidth(atoi(pszFieldFormat+1));
            }
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
        }
        else if( psIter->eType == CXT_Element &&
                 strcmp(psIter->pszValue, ("Group_Field_" + GetSubType()).c_str()) == 0 )
        {
            const char* pszRepetitions = CPLGetXMLValue(psIter, "repetitions", nullptr);
            if( !pszRepetitions )
            {
                return false;
            }
            const char* pszGroupLocation = CPLGetXMLValue(psIter, "group_location", nullptr);
            if( !pszGroupLocation )
            {
                return false;
            }
            const char* pszGroupLength = CPLGetXMLValue(psIter, "group_length", nullptr);
            if( !pszGroupLength )
            {
                return false;
            }
            int nRepetitions = std::min(1000, atoi(pszRepetitions));
            if( nRepetitions <= 0 )
            {
                return false;
            }
            int nGroupOffset = atoi(pszGroupLocation) - 1; // Location is 1-based
            if( nGroupOffset < 0 || nGroupOffset >= m_nRecordSize )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid group_location");
                return false;
            }
            int nGroupLength = atoi(pszGroupLength);
            if( nGroupLength <= 0 ||
                nGroupLength > m_nRecordSize - (m_bHasCRLF ? 2 : 0) - nGroupOffset ||
                (nGroupLength % nRepetitions) != 0 )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid group_length");
                return false;
            }
            int nGroupOneRepititionLength = nGroupLength / nRepetitions;
            for( int i = 0; i < nRepetitions; i++ )
            {
                if( !ReadFields(psIter, nGroupOffset + i * nGroupOneRepititionLength,
                                osSuffixFieldName + "_" + CPLSPrintf("%d", i+1)) )
                {
                    return false;
                }
            }
        }
    }
    return true;
}

/************************************************************************/
/*                      RefreshFileAreaObservational()                  */
/************************************************************************/

void PDS4FixedWidthTable::RefreshFileAreaObservational(CPLXMLNode* psFAO)
{
    CPLString osPrefix;
    if( STARTS_WITH(psFAO->pszValue, "pds:") )
        osPrefix = "pds:";

    CPLString osDescription;
    CPLXMLNode* psTable = RefreshFileAreaObservationalBeginningCommon(
        psFAO, osPrefix, ("Table_" + GetSubType()).c_str(), osDescription);

    CPLCreateXMLElementAndValue(psTable,
                                (osPrefix + "records").c_str(),
                                CPLSPrintf(CPL_FRMT_GIB, m_nFeatureCount));
    if( !osDescription.empty() )
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "description").c_str(),
                                    osDescription);
    if( m_bHasCRLF )
    {
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "record_delimiter").c_str(),
                                    "Carriage-Return Line-Feed");
    }

    // Write Record_Character / Record_Binary
    CPLXMLNode* psRecord = CPLCreateXMLNode(
        psTable, CXT_Element, (osPrefix + "Record_" + GetSubType()).c_str());
    CPLCreateXMLElementAndValue(psRecord,
                        (osPrefix + "fields").c_str(),
                        CPLSPrintf("%d", static_cast<int>(m_aoFields.size())));
    CPLCreateXMLElementAndValue(psRecord,
                                (osPrefix + "groups").c_str(), "0");
    CPLXMLNode* psrecord_length = CPLCreateXMLElementAndValue(
        psRecord, (osPrefix + "record_length").c_str(),
        CPLSPrintf("%d", m_nRecordSize));
    CPLAddXMLAttributeAndValue(psrecord_length, "unit", "byte");

    CPLAssert(static_cast<int>(m_aoFields.size()) ==
                        m_poRawFeatureDefn->GetFieldCount());

    for(int i = 0; i < static_cast<int>(m_aoFields.size()); i++ )
    {
        auto& f= m_aoFields[i];
        auto poFieldDefn = m_poRawFeatureDefn->GetFieldDefn(i);

        CPLXMLNode* psField = CPLCreateXMLNode(
            psRecord, CXT_Element, (osPrefix + "Field_" + GetSubType()).c_str());

        CPLCreateXMLElementAndValue(psField,
                        (osPrefix + "name").c_str(),
                        poFieldDefn->GetNameRef());

        CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "field_number").c_str(),
                                    CPLSPrintf("%d", i+1));

        auto psfield_location = CPLCreateXMLElementAndValue(psField,
                            (osPrefix + "field_location").c_str(),
                            CPLSPrintf("%d", f.m_nOffset + 1));
        CPLAddXMLAttributeAndValue(psfield_location, "unit", "byte");

        CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "data_type").c_str(),
                                    f.m_osDataType.c_str());

        auto psfield_length = CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "field_length").c_str(),
                                    CPLSPrintf("%d", f.m_nLength));
        CPLAddXMLAttributeAndValue(psfield_length, "unit", "byte");

        const auto eType(poFieldDefn->GetType());
        const int nWidth = poFieldDefn->GetWidth();
        if( (eType == OFTInteger || eType == OFTInteger64) &&
            nWidth > 0 )
        {
            CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "field_format").c_str(),
                                    CPLSPrintf("%%%dd", nWidth));
        }

        if( !f.m_osUnit.empty() )
        {
            CPLCreateXMLElementAndValue(psField,
                                        (osPrefix + "unit").c_str(),
                                        m_aoFields[i].m_osUnit.c_str());
        }

        if( !f.m_osDescription.empty() )
        {
            CPLCreateXMLElementAndValue(psField,
                                        (osPrefix + "description").c_str(),
                                        m_aoFields[i].m_osDescription.c_str());
        }
        if( !f.m_osSpecialConstantsXML.empty() )
        {
            auto psSpecialConstants = CPLParseXMLString(f.m_osSpecialConstantsXML);
            if( psSpecialConstants )
            {
                CPLAddXMLChild(psField, psSpecialConstants);
            }
        }
    }
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr PDS4FixedWidthTable::CreateField( OGRFieldDefn *poFieldIn, int )

{
    if( m_poDS->GetAccess() != GA_Update )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Dataset opened in read-only mode");
        return OGRERR_FAILURE;
    }
    if( m_nFeatureCount > 0 )
    {
        return OGRERR_FAILURE;
    }

    Field f;
    if( !m_aoFields.empty() )
    {
        f.m_nOffset = m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
    }

    if( !CreateFieldInternal(poFieldIn->GetType(), poFieldIn->GetSubType(),
                             poFieldIn->GetWidth(), f) )
    {
        return OGRERR_FAILURE;
    }

    MarkHeaderDirty();
    m_aoFields.push_back(f);
    m_poRawFeatureDefn->AddFieldDefn(poFieldIn);
    m_poFeatureDefn->AddFieldDefn(poFieldIn);
    m_nRecordSize += f.m_nLength;
    m_osBuffer.resize(m_nRecordSize);

    return OGRERR_NONE;
}

/************************************************************************/
/*                          InitializeNewLayer()                        */
/************************************************************************/

bool PDS4FixedWidthTable::InitializeNewLayer(
                                OGRSpatialReference* poSRS,
                                bool bForceGeographic,
                                OGRwkbGeometryType eGType,
                                const char* const* papszOptions)
{
    CPLAssert( m_fp == nullptr );
    m_fp = VSIFOpenL(m_osFilename, "wb+");
    if( !m_fp )
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", m_osFilename.c_str());
        return false;
    }
    m_aosLCO.Assign(CSLDuplicate(papszOptions));

    m_nRecordSize = 0;

    const char* pszGeomColumns = CSLFetchNameValueDef(papszOptions, "GEOM_COLUMNS", "AUTO");
    if( EQUAL(pszGeomColumns, "WKT") )
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "GEOM_COLUMNS=WKT only supported for delimited/CSV tables");
    }

    if( (EQUAL(pszGeomColumns, "AUTO") && wkbFlatten(eGType) == wkbPoint &&
                (bForceGeographic || (poSRS && poSRS->IsGeographic()))) ||
        (EQUAL(pszGeomColumns, "LONG_LAT") && eGType != wkbNone) )
    {
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "LAT", "Latitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iLatField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_nOffset = m_aoFields.empty() ? 0 :
                m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
            CreateFieldInternal(OFTReal, OFSTNone, 0, f);
            m_aoFields.push_back(f);
            m_nRecordSize += f.m_nLength;
        }
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "LONG", "Longitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iLongField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_nOffset = m_aoFields.empty() ? 0 :
                m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
            CreateFieldInternal(OFTReal, OFSTNone, 0, f);
            m_aoFields.push_back(f);
            m_nRecordSize += f.m_nLength;
        }
        if( eGType == wkbPoint25D )
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "ALT", "Altitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iAltField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_nOffset = m_aoFields.empty() ? 0 :
                m_aoFields.back().m_nOffset + m_aoFields.back().m_nLength;
            CreateFieldInternal(OFTReal, OFSTNone, 0, f);
            m_aoFields.push_back(f);
            m_nRecordSize += f.m_nLength;
        }

        m_poRawFeatureDefn->SetGeomType(eGType);

        m_poFeatureDefn->SetGeomType(eGType);
        if( poSRS )
        {
            auto poSRSClone = poSRS->Clone();
            poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
            m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRSClone);
            poSRSClone->Release();
        }
    }

    if( m_bHasCRLF )
    {
        m_nRecordSize += 2;
    }
    m_osBuffer.resize(m_nRecordSize);

    m_nFeatureCount = 0;
    MarkHeaderDirty();
    return true;
}

/************************************************************************/
/* ==================================================================== */
/*                         PDS4TableCharacter                           */
/* ==================================================================== */
/************************************************************************/

PDS4TableCharacter::PDS4TableCharacter(PDS4Dataset* poDS,
                                       const char* pszName,
                                       const char* pszFilename) :
    PDS4FixedWidthTable(poDS, pszName, pszFilename)
{
    m_bHasCRLF = true;
}

/************************************************************************/
/*                        CreateFieldInternal()                         */
/************************************************************************/

bool PDS4TableCharacter::CreateFieldInternal(OGRFieldType eType,
                                             OGRFieldSubType eSubType,
                                             int nWidth, Field& f)
{
    if( nWidth > 0 )
    {
        f.m_nLength = nWidth;
    }
    else
    {
        if( eType == OFTString )
        {
            f.m_nLength = 64;
        }
        else if( eType == OFTInteger )
        {
            f.m_nLength = eSubType == OFSTBoolean ? 1 : 11;
        }
        else if( eType == OFTInteger64 )
        {
            f.m_nLength = 21;
        }
        else if( eType == OFTReal )
        {
            f.m_nLength = 16;
        }
        else if( eType == OFTDateTime )
        {
            // YYYY-MM-DDTHH:MM:SS.sssZ
            f.m_nLength = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 3 + 1;
        }
        else if( eType == OFTDate )
        {
            // YYYY-MM-DD
            f.m_nLength = 4 + 1 + 2 + 1 + 2;
        }
        else if( eType == OFTTime )
        {
            // HH:MM:SS.sss
            f.m_nLength = 2 + 1 + 2 + 1 + 2 + 1 + 3;
        }
    }
    if( eType == OFTString )
    {
        f.m_osDataType = "UTF8_String";
    }
    else if( eType == OFTInteger )
    {
        f.m_osDataType = eSubType == OFSTBoolean ?
                            "ASCII_Boolean" : "ASCII_Integer";
    }
    else if( eType == OFTInteger64 )
    {
        f.m_osDataType = "ASCII_Integer";
    }
    else if( eType == OFTReal )
    {
        f.m_osDataType = "ASCII_Real";
    }
    else if( eType == OFTDateTime )
    {
        f.m_osDataType = "ASCII_Date_Time_YMD";
    }
    else if( eType == OFTDate )
    {
        f.m_osDataType = "ASCII_Date_YMD";
    }
    else if( eType == OFTTime )
    {
        f.m_osDataType = "ASCII_Time";
    }
    else
    {
        return false;
    }
    return true;
}

/************************************************************************/
/* ==================================================================== */
/*                         PDS4TableBinary                              */
/* ==================================================================== */
/************************************************************************/

PDS4TableBinary::PDS4TableBinary(PDS4Dataset* poDS,
                                       const char* pszName,
                                       const char* pszFilename) :
    PDS4FixedWidthTable(poDS, pszName, pszFilename)
{
}

/************************************************************************/
/*                        CreateFieldInternal()                         */
/************************************************************************/

bool PDS4TableBinary::CreateFieldInternal(OGRFieldType eType,
                                          OGRFieldSubType eSubType,
                                          int nWidth, Field& f)
{
    CPLString osEndianness(CPLGetConfigOption("PDS4_ENDIANNESS", "LSB"));
    CPLString osSignedness(CPLGetConfigOption("PDS4_SIGNEDNESS", "Signed"));

    if( eType == OFTString )
    {
        f.m_osDataType = "UTF8_String";
        f.m_nLength = nWidth > 0 ? nWidth : 64;
    }
    else if( eType == OFTInteger )
    {
        f.m_osDataType = nWidth > 0 && nWidth <= 2 ? osSignedness + "Byte":
                         eSubType == OFSTBoolean ? CPLString("ASCII_Boolean") :
                         eSubType == OFSTInt16   ? osSignedness + osEndianness + "2" :
                                                   osSignedness + osEndianness + "4";
        f.m_nLength = nWidth > 0 && nWidth <= 2 ? 1 :
                      eSubType == OFSTBoolean ? 1 :
                      eSubType == OFSTInt16   ? 2 :
                                                4;
    }
    else if( eType == OFTInteger64 )
    {
        f.m_osDataType = osSignedness + osEndianness + "8";
        f.m_nLength = 8;
    }
    else if( eType == OFTReal )
    {
        f.m_osDataType = eSubType == OFSTFloat32 ?
                            "IEEE754" + osEndianness + "Single" :
                            "IEEE754" + osEndianness + "Double";
        f.m_nLength = eSubType == OFSTFloat32 ? 4 : 8;
    }
    else if( eType == OFTDateTime )
    {
        f.m_osDataType = "ASCII_Date_Time_YMD";
        // YYYY-MM-DDTHH:MM:SS.sssZ
        f.m_nLength = 4 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 2 + 1 + 3 + 1;
    }
    else if( eType == OFTDate )
    {
        f.m_osDataType = "ASCII_Date_YMD";
        // YYYY-MM-DD
        f.m_nLength = 4 + 1 + 2 + 1 + 2;
    }
    else if( eType == OFTTime )
    {
        f.m_osDataType = "ASCII_Time";
        // HH:MM:SS.sss
        f.m_nLength = 2 + 1 + 2 + 1 + 2 + 1 + 3;
    }
    else
    {
        return false;
    }
    return true;
}


/************************************************************************/
/* ==================================================================== */
/*                         PDS4DelimitedTable                           */
/* ==================================================================== */
/************************************************************************/

PDS4DelimitedTable::PDS4DelimitedTable(PDS4Dataset* poDS,
                                       const char* pszName,
                                       const char* pszFilename) :
    PDS4TableBaseLayer(poDS, pszName, pszFilename)
{
}

/************************************************************************/
/*                          ~PDS4DelimitedTable()                       */
/************************************************************************/

PDS4DelimitedTable::~PDS4DelimitedTable()
{
    if( m_bDirtyHeader )
        GenerateVRT();
}

/************************************************************************/
/*                             GenerateVRT()                            */
/************************************************************************/

void PDS4DelimitedTable::GenerateVRT()
{
    CPLString osVRTFilename = CPLResetExtension(m_osFilename, "vrt");
    if( m_bCreation )
    {
        // In creation mode, generate the VRT, unless explicitly disabled by
        // CREATE_VRT=NO
        if( !m_aosLCO.FetchBool("CREATE_VRT", true) )
            return;
    }
    else
    {
        // In a update situation, only generates the VRT if ones already exists
        VSIStatBufL sStat;
        if( VSIStatL(osVRTFilename, &sStat) != 0 )
            return;
    }

    CPLXMLNode* psRoot = CPLCreateXMLNode(nullptr, CXT_Element,
                                          "OGRVRTDataSource");
    CPLXMLNode* psLayer = CPLCreateXMLNode(psRoot, CXT_Element,
                                          "OGRVRTLayer");
    CPLAddXMLAttributeAndValue(psLayer, "name", GetName());

    CPLXMLNode* psSrcDataSource = CPLCreateXMLElementAndValue(psLayer,
                                "SrcDataSource", CPLGetFilename(m_osFilename));
    CPLAddXMLAttributeAndValue(psSrcDataSource, "relativeToVRT", "1");

    CPLCreateXMLElementAndValue(psLayer, "SrcLayer", GetName());

    CPLCreateXMLElementAndValue(psLayer, "GeometryType",
        OGRVRTGetSerializedGeometryType(GetGeomType()).c_str());

    if( GetSpatialRef() )
    {
        char* pszWKT = nullptr;
        GetSpatialRef()->exportToWkt(&pszWKT);
        if( pszWKT )
        {
            CPLCreateXMLElementAndValue(psLayer, "LayerSRS", pszWKT);
            CPLFree(pszWKT);
        }
    }

    for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( i != m_iWKT && i != m_iLongField && i != m_iLatField &&
            i != m_iAltField )
        {
            OGRFieldDefn* poFieldDefn = m_poRawFeatureDefn->GetFieldDefn(i);
            CPLXMLNode* psField = CPLCreateXMLNode(psLayer, CXT_Element, "Field");
            CPLAddXMLAttributeAndValue(psField, "name", poFieldDefn->GetNameRef());
            CPLAddXMLAttributeAndValue(psField, "type",
                                    OGR_GetFieldTypeName(poFieldDefn->GetType()));
            if( poFieldDefn->GetSubType() != OFSTNone )
            {
                CPLAddXMLAttributeAndValue(psField, "subtype",
                                    OGR_GetFieldSubTypeName(poFieldDefn->GetSubType()));
            }
            if( poFieldDefn->GetWidth() > 0 && poFieldDefn->GetType() != OFTReal )
            {
                CPLAddXMLAttributeAndValue(psField, "width",
                                    CPLSPrintf("%d", poFieldDefn->GetWidth()));
            }
            CPLAddXMLAttributeAndValue(psField, "src", poFieldDefn->GetNameRef());
        }
    }

    if( m_iWKT >= 0 )
    {
        CPLXMLNode* psField = CPLCreateXMLNode(psLayer,
                                               CXT_Element, "GeometryField");
        CPLAddXMLAttributeAndValue(psField, "encoding", "WKT");
        CPLAddXMLAttributeAndValue(psField, "field",
                            m_poRawFeatureDefn->GetFieldDefn(m_iWKT)->GetNameRef());
    }
    else if( m_iLongField >= 0 && m_iLatField >= 0 )
    {
        CPLXMLNode* psField = CPLCreateXMLNode(psLayer,
                                               CXT_Element, "GeometryField");
        CPLAddXMLAttributeAndValue(psField, "encoding", "PointFromColumns");
        CPLAddXMLAttributeAndValue(psField, "x",
                m_poRawFeatureDefn->GetFieldDefn(m_iLongField)->GetNameRef());
        CPLAddXMLAttributeAndValue(psField, "y",
                m_poRawFeatureDefn->GetFieldDefn(m_iLatField)->GetNameRef());
        if( m_iAltField >= 0 )
        {
            CPLAddXMLAttributeAndValue(psField, "z",
                    m_poRawFeatureDefn->GetFieldDefn(m_iAltField)->GetNameRef());
        }
    }

    CPLSerializeXMLTreeToFile(psRoot, osVRTFilename);
    CPLDestroyXMLNode(psRoot);
}

/************************************************************************/
/*                            ResetReading()                            */
/************************************************************************/

void PDS4DelimitedTable::ResetReading()
{
    m_nFID = 1;
    VSIFSeekL(m_fp, m_nOffset, SEEK_SET);
}

/************************************************************************/
/*                         GetNextFeatureRaw()                          */
/************************************************************************/

OGRFeature* PDS4DelimitedTable::GetNextFeatureRaw()
{
    const char* pszLine = CPLReadLine2L(m_fp, 10 * 1024 * 1024, nullptr);
    if( pszLine == nullptr )
    {
        return nullptr;
    }

    char szDelimiter[2] = { m_chFieldDelimiter, 0 };
    char **papszFields =
        CSLTokenizeString2( pszLine, szDelimiter,
                            CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS );
    if( CSLCount(papszFields) != m_poRawFeatureDefn->GetFieldCount() )
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Did not get expected number of fields at line " CPL_FRMT_GIB,
                 m_nFID);
    }

    OGRFeature* poRawFeature = new OGRFeature(m_poRawFeatureDefn);
    poRawFeature->SetFID(m_nFID);
    m_nFID++;
    for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount() &&
                    papszFields && papszFields[i]; i++ )
    {
        if( !m_aoFields[i].m_osMissingConstant.empty() &&
            m_aoFields[i].m_osMissingConstant == papszFields[i] )
        {
            // do nothing
        }
        else if( m_aoFields[i].m_osDataType == "ASCII_Boolean" )
        {
            poRawFeature->SetField(i,
                EQUAL(papszFields[i], "t") || EQUAL(papszFields[i], "1") ? 1 : 0);
        }
        else
        {
            poRawFeature->SetField(i, papszFields[i]);
        }
    }

    CSLDestroy(papszFields);

    OGRFeature* poFeature = AddGeometryFromFields(poRawFeature);
    delete poRawFeature;
    return poFeature;
}

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

OGRFeature* PDS4DelimitedTable::GetNextFeature()
{
    while(true)
    {
        auto poFeature = GetNextFeatureRaw();
        if( poFeature == nullptr )
        {
            return nullptr;
        }

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

/************************************************************************/
/*                           TestCapability()                           */
/************************************************************************/

int PDS4DelimitedTable::TestCapability(const char* pszCap)
{
    if( EQUAL(pszCap, OLCRandomRead) ||
        EQUAL(pszCap, OLCStringsAsUTF8) )
    {
        return true;
    }
    if( EQUAL(pszCap, OLCFastFeatureCount) )
    {
        return m_poAttrQuery == nullptr && m_poFilterGeom == nullptr;
    }
    if( EQUAL(pszCap, OLCCreateField) )
    {
        return m_poDS->GetAccess() == GA_Update && m_nFeatureCount == 0;
    }
    if( EQUAL(pszCap, OLCSequentialWrite) )
    {
        return m_poDS->GetAccess() == GA_Update;
    }
    return false;
}

/************************************************************************/
/*                            QuoteIfNeeded()                           */
/************************************************************************/

CPLString PDS4DelimitedTable::QuoteIfNeeded(const char* pszVal)
{
    if( strchr(pszVal, m_chFieldDelimiter) == nullptr )
    {
        return pszVal;
    }
    return '"' + CPLString(pszVal) + '"';
}

/************************************************************************/
/*                           ICreateFeature()                           */
/************************************************************************/

OGRErr PDS4DelimitedTable::ICreateFeature( OGRFeature *poFeature )
{
    if( m_bAddWKTColumnPending )
    {
        OGRFieldDefn oFieldDefn(
            CSLFetchNameValueDef(m_aosLCO.List(), "WKT", "WKT"), OFTString);
        m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
        m_iWKT = m_poRawFeatureDefn->GetFieldCount() - 1;
        Field f;
        f.m_osDataType = "ASCII_String";
        m_aoFields.push_back(f);
        m_bAddWKTColumnPending = false;
    }

    if( m_nFeatureCount == 0 )
    {
        for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
        {
            if( i > 0 )
            {
                VSIFPrintfL(m_fp, "%c", m_chFieldDelimiter);
            }
            VSIFPrintfL(m_fp, "%s", QuoteIfNeeded(
                m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef()).c_str());
        }
        VSIFPrintfL(m_fp, "\r\n");
        m_nOffset = VSIFTellL(m_fp);
    }

    OGRFeature* poRawFeature = AddFieldsFromGeometry(poFeature);
    for( int i = 0; i < m_poRawFeatureDefn->GetFieldCount(); i++ )
    {
        if( i > 0 )
        {
            VSIFPrintfL(m_fp, "%c", m_chFieldDelimiter);
        }
        if( !poRawFeature->IsFieldSetAndNotNull(i) )
        {
            if( !m_aoFields[i].m_osMissingConstant.empty() )
            {
                VSIFPrintfL(m_fp, "%s",
                    QuoteIfNeeded(m_aoFields[i].m_osMissingConstant).c_str());
            }
            continue;
        }
        VSIFPrintfL(m_fp, "%s",
            QuoteIfNeeded(poRawFeature->GetFieldAsString(i)).c_str());
    }
    VSIFPrintfL(m_fp, "\r\n");
    delete poRawFeature;

    m_nFeatureCount ++;
    poFeature->SetFID(m_nFeatureCount);

    return OGRERR_NONE;
}

/************************************************************************/
/*                            CreateField()                             */
/************************************************************************/

OGRErr PDS4DelimitedTable::CreateField( OGRFieldDefn *poFieldIn, int )

{
    if( m_poDS->GetAccess() != GA_Update )
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Dataset opened in read-only mode");
        return OGRERR_FAILURE;
    }
    if( m_nFeatureCount > 0 )
    {
        return OGRERR_FAILURE;
    }

    const auto eType = poFieldIn->GetType();
    Field f;
    if( eType == OFTString )
    {
        f.m_osDataType = "UTF8_String";
    }
    else if( eType == OFTInteger )
    {
        f.m_osDataType = poFieldIn->GetSubType() == OFSTBoolean ?
                            "ASCII_Boolean" : "ASCII_Integer";
    }
    else if( eType == OFTInteger64 )
    {
        f.m_osDataType = "ASCII_Integer";
    }
    else if( eType == OFTReal )
    {
        f.m_osDataType = "ASCII_Real";
    }
    else if( eType == OFTDateTime )
    {
        f.m_osDataType = "ASCII_Date_Time_YMD";
    }
    else if( eType == OFTDate )
    {
        f.m_osDataType = "ASCII_Date_YMD";
    }
    else if( eType == OFTTime )
    {
        f.m_osDataType = "ASCII_Time";
    }
    else
    {
        return OGRERR_FAILURE;
    }

    MarkHeaderDirty();
    m_aoFields.push_back(f);
    m_poRawFeatureDefn->AddFieldDefn(poFieldIn);
    m_poFeatureDefn->AddFieldDefn(poFieldIn);

    return OGRERR_NONE;
}

/************************************************************************/
/*                            ReadTableDef()                            */
/************************************************************************/

bool PDS4DelimitedTable::ReadTableDef(const CPLXMLNode* psTable)
{
    CPLAssert( m_fp == nullptr );
    m_fp = VSIFOpenL(m_osFilename,
                     (m_poDS->GetAccess() == GA_ReadOnly ) ? "rb" : "r+b");
    if( !m_fp )
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
                 m_osFilename.c_str());
        return false;
    }

    m_nOffset = static_cast<GUIntBig>(
        CPLAtoGIntBig(CPLGetXMLValue(psTable, "offset", "0")));

    m_nFeatureCount = CPLAtoGIntBig(
        CPLGetXMLValue(psTable, "records", "-1"));

    bool bHasCRLF = EQUAL(
        CPLGetXMLValue(psTable, "record_delimiter", ""),
        "Carriage-Return Line-Feed");
    if( !bHasCRLF )
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "record_delimiter != 'Carriage-Return Line-Feed' not supported");
        return false;
    }

    const char* pszFieldDelimiter = CPLGetXMLValue(
        psTable, "field_delimiter", nullptr);
    if( pszFieldDelimiter == nullptr )
    {
        return false;
    }
    if( EQUAL(pszFieldDelimiter, "Comma") )
    {
        m_chFieldDelimiter = ',';
    }
    else if( EQUAL(pszFieldDelimiter, "Horizontal Tab") )
    {
        m_chFieldDelimiter = '\t';
    }
    else if( EQUAL(pszFieldDelimiter, "Semicolon") )
    {
        m_chFieldDelimiter = ';';
    }
    else if( EQUAL(pszFieldDelimiter, "Vertical Bar") )
    {
        m_chFieldDelimiter = '|';
    }
    else
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "field_delimiter value not supported");
        return false;
    }

    const CPLXMLNode* psRecord =
        CPLGetXMLNode(psTable, "Record_Delimited");
    if( !psRecord )
    {
        return false;
    }
    if( !ReadFields(psRecord, "") )
    {
        return false;
    }

    SetupGeomField();
    ResetReading();

    return true;
}

/************************************************************************/
/*                             ReadFields()                             */
/************************************************************************/

bool PDS4DelimitedTable::ReadFields(const CPLXMLNode* psParent,
                                    const CPLString& osSuffixFieldName)
{
    for( const CPLXMLNode* psIter = psParent->psChild;
                           psIter; psIter = psIter->psNext )
    {
        if( psIter->eType == CXT_Element &&
            strcmp(psIter->pszValue, "Field_Delimited") == 0 )
        {
            const char* pszName = CPLGetXMLValue(psIter, "name", nullptr);
            if( !pszName )
            {
                return false;
            }
            const char* pszDataType = CPLGetXMLValue(psIter, "data_type", nullptr);
            if( !pszDataType )
            {
                return false;
            }
            int nMaximumFieldLength =
                atoi(CPLGetXMLValue(psIter, "maximum_field_length", "0"));

            Field f;
            f.m_osDataType = pszDataType;
            f.m_osUnit = CPLGetXMLValue(psIter, "unit", "");
            f.m_osDescription = CPLGetXMLValue(psIter, "description", "");

            CPLXMLNode* psSpecialConstants = const_cast<CPLXMLNode*>(
                CPLGetXMLNode(psIter, "Special_Constants"));
            if( psSpecialConstants )
            {
                auto psNext = psSpecialConstants->psNext;
                psSpecialConstants->psNext = nullptr;
                char* pszXML = CPLSerializeXMLTree(psSpecialConstants);
                psSpecialConstants->psNext = psNext;
                if( pszXML )
                {
                    f.m_osSpecialConstantsXML = pszXML;
                    CPLFree(pszXML);
                }
            }
            f.m_osMissingConstant = CPLGetXMLValue(psIter,
                                    "Special_Constants.missing_constant", "");

            m_aoFields.push_back(f);

            OGRFieldSubType eSubType = OFSTNone;
            bool error = false;
            auto eType = GetFieldTypeFromPDS4DataType(pszDataType, 0, eSubType, error);
            if( error )
            {
                CPLError(CE_Failure, CPLE_AppDefined, "Binary fields not allowed");
                return false;
            }
            if( STARTS_WITH(f.m_osDataType, "ASCII_") &&
                eType == OFTInteger &&
                (nMaximumFieldLength == 0 || nMaximumFieldLength >= 10) )
            {
                eType = OFTInteger64;
            }
            OGRFieldDefn oFieldDefn((pszName + osSuffixFieldName).c_str(), eType);
            oFieldDefn.SetSubType(eSubType);
            if( eType != OFTReal &&
                (STARTS_WITH(f.m_osDataType, "ASCII_") ||
                 STARTS_WITH(f.m_osDataType, "UTF_8")) )
            {
                oFieldDefn.SetWidth(nMaximumFieldLength);
            }
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
        }
        else if( psIter->eType == CXT_Element &&
                 strcmp(psIter->pszValue, "Group_Field_Delimited") == 0 )
        {
            const char* pszRepetitions = CPLGetXMLValue(psIter, "repetitions", nullptr);
            if( !pszRepetitions )
            {
                return false;
            }
            int nRepetitions = std::min(1000, atoi(pszRepetitions));
            if( nRepetitions <= 0 )
            {
                return false;
            }
            for( int i = 0; i < nRepetitions; i++ )
            {
                if( !ReadFields(psIter,
                                osSuffixFieldName + "_" + CPLSPrintf("%d", i+1)) )
                {
                    return false;
                }
            }
        }
    }
    return true;
}

/************************************************************************/
/*                      RefreshFileAreaObservational()                  */
/************************************************************************/

void PDS4DelimitedTable::RefreshFileAreaObservational(CPLXMLNode* psFAO)
{
    CPLString osPrefix;
    if( STARTS_WITH(psFAO->pszValue, "pds:") )
        osPrefix = "pds:";

    CPLString osDescription;
    CPLXMLNode* psTable = RefreshFileAreaObservationalBeginningCommon(
        psFAO, osPrefix, "Table_Delimited", osDescription);

    CPLCreateXMLElementAndValue(psTable,
                                (osPrefix + "parsing_standard_id").c_str(),
                                "PDS DSV 1");

    CPLCreateXMLElementAndValue(psTable,
                                (osPrefix + "records").c_str(),
                                CPLSPrintf(CPL_FRMT_GIB, m_nFeatureCount));
    if( !osDescription.empty() )
        CPLCreateXMLElementAndValue(psTable,
                                    (osPrefix + "description").c_str(),
                                    osDescription);

    CPLCreateXMLElementAndValue(psTable,
                                (osPrefix + "record_delimiter").c_str(),
                                "Carriage-Return Line-Feed");

    CPLCreateXMLElementAndValue(psTable,
                                (osPrefix + "field_delimiter").c_str(),
                                m_chFieldDelimiter == '\t' ? "Horizontal Tab" :
                                m_chFieldDelimiter == ';' ?  "Semicolon" :
                                m_chFieldDelimiter == '|' ?  "Vertical Bar" :
                                                             "Comma");

    // Write Record_Delimited
    CPLXMLNode* psRecord = CPLCreateXMLNode(
        psTable, CXT_Element, (osPrefix + "Record_Delimited").c_str());

    CPLCreateXMLElementAndValue(psRecord,
                        (osPrefix + "fields").c_str(),
                        CPLSPrintf("%d", static_cast<int>(m_aoFields.size())));

    CPLCreateXMLElementAndValue(psRecord,
                                (osPrefix + "groups").c_str(), "0");


    CPLAssert(static_cast<int>(m_aoFields.size()) ==
                        m_poRawFeatureDefn->GetFieldCount());

    for(int i = 0; i < static_cast<int>(m_aoFields.size()); i++ )
    {
        auto& f= m_aoFields[i];

        CPLXMLNode* psField = CPLCreateXMLNode(
            psRecord, CXT_Element, (osPrefix + "Field_Delimited").c_str());

        CPLCreateXMLElementAndValue(psField,
                        (osPrefix + "name").c_str(),
                        m_poRawFeatureDefn->GetFieldDefn(i)->GetNameRef());

        CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "field_number").c_str(),
                                    CPLSPrintf("%d", i+1));

        CPLCreateXMLElementAndValue(psField,
                                    (osPrefix + "data_type").c_str(),
                                    f.m_osDataType.c_str());

        int nWidth = m_poRawFeatureDefn->GetFieldDefn(i)->GetWidth();
        if( nWidth > 0 )
        {
            auto psfield_length = CPLCreateXMLElementAndValue(psField,
                                        (osPrefix + "maximum_field_length").c_str(),
                                        CPLSPrintf("%d", nWidth));
            CPLAddXMLAttributeAndValue(psfield_length, "unit", "byte");
        }

        if( !f.m_osUnit.empty() )
        {
            CPLCreateXMLElementAndValue(psField,
                                        (osPrefix + "unit").c_str(),
                                        m_aoFields[i].m_osUnit.c_str());
        }

        if( !f.m_osDescription.empty() )
        {
            CPLCreateXMLElementAndValue(psField,
                                        (osPrefix + "description").c_str(),
                                        m_aoFields[i].m_osDescription.c_str());
        }

        if( !f.m_osSpecialConstantsXML.empty() )
        {
            auto psSpecialConstants = CPLParseXMLString(f.m_osSpecialConstantsXML);
            if( psSpecialConstants )
            {
                CPLAddXMLChild(psField, psSpecialConstants);
            }
        }
    }
}

/************************************************************************/
/*                            GetFileList()                             */
/************************************************************************/

char** PDS4DelimitedTable::GetFileList() const
{
    auto papszFileList = PDS4TableBaseLayer::GetFileList();
    CPLString osVRTFilename = CPLResetExtension(m_osFilename, "vrt");
    VSIStatBufL sStat;
    if( VSIStatL(osVRTFilename, &sStat) == 0 )
    {
        papszFileList = CSLAddString(papszFileList, osVRTFilename);
    }
    return papszFileList;
}

/************************************************************************/
/*                          InitializeNewLayer()                        */
/************************************************************************/

bool PDS4DelimitedTable::InitializeNewLayer(
                                OGRSpatialReference* poSRS,
                                bool bForceGeographic,
                                OGRwkbGeometryType eGType,
                                const char* const* papszOptions)
{
    CPLAssert( m_fp == nullptr );
    m_fp = VSIFOpenL(m_osFilename, "wb+");
    if( !m_fp )
    {
        CPLError(CE_Failure, CPLE_FileIO, "Cannot create %s", m_osFilename.c_str());
        return false;
    }
    m_aosLCO.Assign(CSLDuplicate(papszOptions));
    m_bCreation = true;

    // For testing purposes
    m_chFieldDelimiter = CPLGetConfigOption("OGR_PDS4_FIELD_DELIMITER", ",")[0];

    const char* pszGeomColumns = CSLFetchNameValueDef(papszOptions, "GEOM_COLUMNS", "AUTO");
    if( (EQUAL(pszGeomColumns, "AUTO") && wkbFlatten(eGType) == wkbPoint &&
                (bForceGeographic || (poSRS && poSRS->IsGeographic()))) ||
        (EQUAL(pszGeomColumns, "LONG_LAT") && eGType != wkbNone) )
    {
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "LAT", "Latitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iLatField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_osDataType = "ASCII_Real";
            m_aoFields.push_back(f);
        }
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "LONG", "Longitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iLongField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_osDataType = "ASCII_Real";
            m_aoFields.push_back(f);
        }
        if( eGType == wkbPoint25D )
        {
            OGRFieldDefn oFieldDefn(
                CSLFetchNameValueDef(papszOptions, "ALT", "Altitude"), OFTReal);
            m_poRawFeatureDefn->AddFieldDefn(&oFieldDefn);
            m_iAltField = m_poRawFeatureDefn->GetFieldCount() - 1;
            Field f;
            f.m_osDataType = "ASCII_Real";
            m_aoFields.push_back(f);
        }
    }
    else if( eGType != wkbNone &&
             (EQUAL(pszGeomColumns, "AUTO") || EQUAL(pszGeomColumns, "WKT")) )
    {
        m_bAddWKTColumnPending = true;
    }

    if( eGType != wkbNone )
    {
        m_poRawFeatureDefn->SetGeomType(eGType);

        m_poFeatureDefn->SetGeomType(eGType);
        if( poSRS )
        {
            auto poSRSClone = poSRS->Clone();
            poSRSClone->SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
            m_poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(poSRSClone);
            poSRSClone->Release();
        }
    }

    m_nFeatureCount = 0;
    MarkHeaderDirty();
    return true;
}

/************************************************************************/
/* ==================================================================== */
/*                           PDS4EditableSynchronizer                   */
/* ==================================================================== */
/************************************************************************/

template<class T>
OGRErr PDS4EditableSynchronizer<T>::EditableSyncToDisk(
                    OGRLayer* poEditableLayer, OGRLayer** ppoDecoratedLayer)
{
    auto poOriLayer = dynamic_cast<T*>(*ppoDecoratedLayer);
    CPLAssert(poOriLayer);

    CPLString osTmpFilename(poOriLayer->m_osFilename + ".tmp");
    auto poNewLayer = poOriLayer->NewLayer(
        poOriLayer->m_poDS, poOriLayer->GetName(), osTmpFilename);
    CPLStringList aosLCO(poOriLayer->m_aosLCO);
    if( poOriLayer->m_iLatField >= 0 )
    {
        aosLCO.SetNameValue("LAT",
            poOriLayer->m_poRawFeatureDefn->GetFieldDefn(
                poOriLayer->m_iLatField)->GetNameRef());
    }
    if( poOriLayer->m_iLongField >= 0 )
    {
        aosLCO.SetNameValue("LONG",
            poOriLayer->m_poRawFeatureDefn->GetFieldDefn(
                poOriLayer->m_iLongField)->GetNameRef());
    }
    if( poOriLayer->m_iAltField >= 0 )
    {
        aosLCO.SetNameValue("ALT",
            poOriLayer->m_poRawFeatureDefn->GetFieldDefn(
                poOriLayer->m_iAltField)->GetNameRef());
    }
    if( !poNewLayer->InitializeNewLayer(poOriLayer->GetSpatialRef(),
                                        poOriLayer->m_iLatField >= 0,
                                        poOriLayer->GetGeomType(),
                                        aosLCO.List()) )
    {
        delete poNewLayer;
        VSIUnlink(osTmpFilename);
        return OGRERR_FAILURE;
    }

    const auto copyField = [](typename T::Field& oDst, const typename T::Field& oSrc) 
    {
        oDst.m_osDescription = oSrc.m_osDescription;
        oDst.m_osUnit = oSrc.m_osUnit;
        oDst.m_osSpecialConstantsXML = oSrc.m_osSpecialConstantsXML;
    };

    if( poNewLayer->m_iLatField >= 0 )
    {
        copyField(poNewLayer->m_aoFields[poNewLayer->m_iLatField],
                  poOriLayer->m_aoFields[poOriLayer->m_iLatField]);
    }
    if( poNewLayer->m_iLongField >= 0 )
    {
        copyField(poNewLayer->m_aoFields[poNewLayer->m_iLongField],
                  poOriLayer->m_aoFields[poOriLayer->m_iLongField]);
    }
    if( poNewLayer->m_iAltField >= 0 )
    {
        copyField(poNewLayer->m_aoFields[poNewLayer->m_iAltField],
                  poOriLayer->m_aoFields[poOriLayer->m_iAltField]);
    }

    OGRFeatureDefn *poEditableFDefn = poEditableLayer->GetLayerDefn();
    for( int i = 0; i < poEditableFDefn->GetFieldCount(); i++ )
    {
        auto poFieldDefn = poEditableFDefn->GetFieldDefn(i);
        poNewLayer->CreateField(poFieldDefn, false);
        int idx = poOriLayer->m_poRawFeatureDefn->GetFieldIndex(
            poFieldDefn->GetNameRef());
        if( idx >= 0 )
        {
            copyField(poNewLayer->m_aoFields.back(),
                      poOriLayer->m_aoFields[idx]);
            OGRFieldDefn* poOriFieldDefn =
                poOriLayer->m_poRawFeatureDefn->GetFieldDefn(idx);
            if( poFieldDefn->GetType() == poOriFieldDefn->GetType() )
            {
                poNewLayer->m_aoFields.back().m_osDataType =
                    poOriLayer->m_aoFields[idx].m_osDataType;
            }
        }
    }

    poEditableLayer->ResetReading();

    // Disable all filters.
    const char* pszQueryStringConst = poEditableLayer->GetAttrQueryString();
    char* pszQueryStringBak = pszQueryStringConst ? CPLStrdup(pszQueryStringConst) : nullptr;
    poEditableLayer->SetAttributeFilter(nullptr);

    const int iFilterGeomIndexBak = poEditableLayer->GetGeomFieldFilter();
    OGRGeometry* poFilterGeomBak = poEditableLayer->GetSpatialFilter();
    if( poFilterGeomBak )
        poFilterGeomBak = poFilterGeomBak->clone();
    poEditableLayer->SetSpatialFilter(nullptr);

    auto aoMapSrcToTargetIdx = poNewLayer->GetLayerDefn()->
        ComputeMapForSetFrom(poEditableLayer->GetLayerDefn(), true);
    aoMapSrcToTargetIdx.push_back(-1); // add dummy entry to be sure that .data() is valid

    OGRErr eErr = OGRERR_NONE;
    for( auto&& poFeature: poEditableLayer )
    {
        OGRFeature *poNewFeature =
            new OGRFeature(poNewLayer->GetLayerDefn());
        poNewFeature->SetFrom(poFeature.get(), aoMapSrcToTargetIdx.data(), true);
        eErr = poNewLayer->CreateFeature(poNewFeature);
        delete poNewFeature;
        if( eErr != OGRERR_NONE )
        {
            break;
        }
    }

    // Restore filters.
    poEditableLayer->SetAttributeFilter(pszQueryStringBak);
    CPLFree(pszQueryStringBak);
    poEditableLayer->SetSpatialFilter(iFilterGeomIndexBak, poFilterGeomBak);
    delete poFilterGeomBak;

    if( eErr != OGRERR_NONE ||
        !poNewLayer->RenameFileTo(poOriLayer->GetFileName()) )
    {
        delete poNewLayer;
        VSIUnlink(osTmpFilename);
        return OGRERR_FAILURE;
    }

    delete poOriLayer;
    *ppoDecoratedLayer = poNewLayer;

    return OGRERR_NONE;
}

/************************************************************************/
/* ==================================================================== */
/*                         PDS4EditableLayer                            */
/* ==================================================================== */
/************************************************************************/

PDS4EditableLayer::PDS4EditableLayer(PDS4FixedWidthTable* poBaseLayer):
    OGREditableLayer(poBaseLayer, true,
                     new PDS4EditableSynchronizer<PDS4FixedWidthTable>(), true)
{
}

PDS4EditableLayer::PDS4EditableLayer(PDS4DelimitedTable* poBaseLayer):
    OGREditableLayer(poBaseLayer, true,
                     new PDS4EditableSynchronizer<PDS4DelimitedTable>(), true)
{
}

/************************************************************************/
/*                           GetBaseLayer()                             */
/************************************************************************/

PDS4TableBaseLayer* PDS4EditableLayer::GetBaseLayer() const
{
    auto ret = dynamic_cast<PDS4TableBaseLayer*>(OGREditableLayer::GetBaseLayer());
    assert(ret);
    return ret;
}

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

void PDS4EditableLayer::SetSpatialRef(OGRSpatialReference* poSRS)
{
    if( GetGeomType() != wkbNone )
    {
        GetLayerDefn()->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
        GetBaseLayer()->GetLayerDefn()->GetGeomFieldDefn(0)->SetSpatialRef(poSRS);
    }
}
