/******************************************************************************
 *
 * Project:  NITF Read/Write Library
 * Purpose:  Module responsible for implementation of most NITFImage
 *           implementation.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 **********************************************************************
 * Copyright (c) 2002, Frank Warmerdam
 * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
 *
 * SPDX-License-Identifier: MIT
 ****************************************************************************/

#include "gdal.h"
#include "nitflib.h"
#include "mgrs.h"
#include "cpl_vsi.h"
#include "cpl_conv.h"
#include "cpl_string.h"

#include <inttypes.h>

#ifndef CPL_IGNORE_RET_VAL_INT_defined
#define CPL_IGNORE_RET_VAL_INT_defined

CPL_INLINE static void CPL_IGNORE_RET_VAL_INT(CPL_UNUSED int unused)
{
}
#endif

static int NITFReadIMRFCA(NITFImage *psImage, NITFRPC00BInfo *psRPC);
static char *NITFTrimWhite(char *);
#ifdef CPL_LSB
static void NITFSwapWords(NITFImage *psImage, void *pData, int nWordCount);
#endif

static void NITFLoadLocationTable(NITFImage *psImage);
static void NITFLoadColormapSubSection(NITFImage *psImage);
static void NITFLoadSubframeMaskTable(NITFImage *psImage,
                                      bool *pbBlockStartRead);
static int NITFLoadVQTables(NITFImage *psImage, int bTryGuessingOffset);
static int NITFReadGEOLOB(NITFImage *psImage);
static void NITFLoadAttributeSection(NITFImage *psImage);
static void NITFPossibleIGEOLOReorientation(NITFImage *psImage);

void NITFGetGCP(const char *pachCoord, double *pdfXYs, int iCoord);
int NITFReadBLOCKA_GCPs(NITFImage *psImage);

#define GOTO_header_too_small()                                                \
    do                                                                         \
    {                                                                          \
        nFaultyLine = __LINE__;                                                \
        goto header_too_small;                                                 \
    } while (0)

#define DIGIT_ZERO '0'

/************************************************************************/
/*                          NITFImageAccess()                           */
/************************************************************************/

NITFImage *NITFImageAccess(NITFFile *psFile, int iSegment)

{
    NITFImage *psImage;
    char *pachHeader;
    NITFSegmentInfo *psSegInfo;
    char szTemp[128];
    int nOffset, iBand, i;
    int nNICOM;
    const char *pszIID1;
    int nFaultyLine = -1;

    /* -------------------------------------------------------------------- */
    /*      Verify segment, and return existing image accessor if there     */
    /*      is one.                                                         */
    /* -------------------------------------------------------------------- */
    if (iSegment < 0 || iSegment >= psFile->nSegmentCount)
        return NULL;

    psSegInfo = psFile->pasSegmentInfo + iSegment;

    if (!EQUAL(psSegInfo->szSegmentType, "IM"))
        return NULL;

    if (psSegInfo->hAccess != NULL)
        return (NITFImage *)psSegInfo->hAccess;

    /* -------------------------------------------------------------------- */
    /*      Read the image subheader.                                       */
    /* -------------------------------------------------------------------- */
    if (psSegInfo->nSegmentHeaderSize < 370 + 1)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Image header too small");
        return NULL;
    }

    pachHeader = (char *)VSI_MALLOC_VERBOSE(psSegInfo->nSegmentHeaderSize);
    if (pachHeader == NULL)
    {
        return NULL;
    }

    if (VSIFSeekL(psFile->fp, psSegInfo->nSegmentHeaderStart, SEEK_SET) != 0 ||
        VSIFReadL(pachHeader, 1, psSegInfo->nSegmentHeaderSize, psFile->fp) !=
            psSegInfo->nSegmentHeaderSize)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Failed to read %u byte image subheader from " CPL_FRMT_GUIB
                 ".",
                 psSegInfo->nSegmentHeaderSize, psSegInfo->nSegmentHeaderStart);
        CPLFree(pachHeader);
        return NULL;
    }

    /* -------------------------------------------------------------------- */
    /*      Initialize image object.                                        */
    /* -------------------------------------------------------------------- */
    psImage = (NITFImage *)CPLCalloc(sizeof(NITFImage), 1);

    psImage->psFile = psFile;
    psImage->iSegment = iSegment;
    psImage->pachHeader = pachHeader;
    psImage->nIXSOFL = -1;

    psSegInfo->hAccess = psImage;

/* -------------------------------------------------------------------- */
/*      Collect a variety of information as metadata.                   */
/* -------------------------------------------------------------------- */
#define GetMD(target, hdr, start, length, name)                                \
    NITFExtractMetadata(&(target->papszMetadata), hdr, start, length,          \
                        "NITF_" #name);

    if (EQUAL(psFile->szVersion, "NITF02.10") ||
        EQUAL(psFile->szVersion, "NSIF01.00"))
    {
        GetMD(psImage, pachHeader, 2, 10, IID1);
        GetMD(psImage, pachHeader, 12, 14, IDATIM);
        GetMD(psImage, pachHeader, 26, 17, TGTID);
        GetMD(psImage, pachHeader, 43, 80, IID2);
        GetMD(psImage, pachHeader, 123, 1, ISCLAS);
        GetMD(psImage, pachHeader, 124, 2, ISCLSY);
        GetMD(psImage, pachHeader, 126, 11, ISCODE);
        GetMD(psImage, pachHeader, 137, 2, ISCTLH);
        GetMD(psImage, pachHeader, 139, 20, ISREL);
        GetMD(psImage, pachHeader, 159, 2, ISDCTP);
        GetMD(psImage, pachHeader, 161, 8, ISDCDT);
        GetMD(psImage, pachHeader, 169, 4, ISDCXM);
        GetMD(psImage, pachHeader, 173, 1, ISDG);
        GetMD(psImage, pachHeader, 174, 8, ISDGDT);
        GetMD(psImage, pachHeader, 182, 43, ISCLTX);
        GetMD(psImage, pachHeader, 225, 1, ISCATP);
        GetMD(psImage, pachHeader, 226, 40, ISCAUT);
        GetMD(psImage, pachHeader, 266, 1, ISCRSN);
        GetMD(psImage, pachHeader, 267, 8, ISSRDT);
        GetMD(psImage, pachHeader, 275, 15, ISCTLN);
        /* skip ENCRYPT - 1 character */
        GetMD(psImage, pachHeader, 291, 42, ISORCE);
        /* skip NROWS (8), and NCOLS (8) */
        GetMD(psImage, pachHeader, 349, 3, PVTYPE);
        GetMD(psImage, pachHeader, 352, 8, IREP);
        GetMD(psImage, pachHeader, 360, 8, ICAT);
        GetMD(psImage, pachHeader, 368, 2, ABPP);
        GetMD(psImage, pachHeader, 370, 1, PJUST);
    }
    else if (EQUAL(psFile->szVersion, "NITF02.00"))
    {
        nOffset = 0;
        GetMD(psImage, pachHeader, 2, 10, IID1);
        GetMD(psImage, pachHeader, 12, 14, IDATIM);
        GetMD(psImage, pachHeader, 26, 17, TGTID);
        GetMD(psImage, pachHeader, 43, 80, ITITLE);
        GetMD(psImage, pachHeader, 123, 1, ISCLAS);
        GetMD(psImage, pachHeader, 124, 40, ISCODE);
        GetMD(psImage, pachHeader, 164, 40, ISCTLH);
        GetMD(psImage, pachHeader, 204, 40, ISREL);
        GetMD(psImage, pachHeader, 244, 20, ISCAUT);
        GetMD(psImage, pachHeader, 264, 20, ISCTLN);
        GetMD(psImage, pachHeader, 284, 6, ISDWNG);

        if (STARTS_WITH_CI(pachHeader + 284, "999998"))
        {
            if (psSegInfo->nSegmentHeaderSize < 370 + 40 + 1)
                GOTO_header_too_small();
            GetMD(psImage, pachHeader, 290, 40, ISDEVT);
            nOffset += 40;
        }

        /* skip ENCRYPT - 1 character */
        GetMD(psImage, pachHeader, 291 + nOffset, 42, ISORCE);
        /* skip NROWS (8), and NCOLS (8) */
        GetMD(psImage, pachHeader, 349 + nOffset, 3, PVTYPE);
        GetMD(psImage, pachHeader, 352 + nOffset, 8, IREP);
        GetMD(psImage, pachHeader, 360 + nOffset, 8, ICAT);
        GetMD(psImage, pachHeader, 368 + nOffset, 2, ABPP);
        GetMD(psImage, pachHeader, 370 + nOffset, 1, PJUST);
    }

    /* -------------------------------------------------------------------- */
    /*      Does this header have the ISDEVT field?                         */
    /* -------------------------------------------------------------------- */
    nOffset = 333;

    if (STARTS_WITH_CI(psFile->szVersion, "NITF01.") ||
        STARTS_WITH_CI(pachHeader + 284, "999998"))
        nOffset += 40;

    /* -------------------------------------------------------------------- */
    /*      Read lots of header fields.                                     */
    /* -------------------------------------------------------------------- */
    if (!STARTS_WITH_CI(psFile->szVersion, "NITF01."))
    {
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 35 + 2)
            GOTO_header_too_small();

        psImage->nRows = atoi(NITFGetField(szTemp, pachHeader, nOffset, 8));
        psImage->nCols = atoi(NITFGetField(szTemp, pachHeader, nOffset + 8, 8));

        NITFTrimWhite(
            NITFGetField(psImage->szPVType, pachHeader, nOffset + 16, 3));
        NITFTrimWhite(
            NITFGetField(psImage->szIREP, pachHeader, nOffset + 19, 8));
        NITFTrimWhite(
            NITFGetField(psImage->szICAT, pachHeader, nOffset + 27, 8));
        psImage->nABPP =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 35, 2));
    }

    nOffset += 38;

    /* -------------------------------------------------------------------- */
    /*      Do we have IGEOLO information?  In NITF 2.0 (and 1.x) 'N' means */
    /*      no information, while in 2.1 this is indicated as ' ', and 'N'  */
    /*      means UTM (north).  So for 2.0 products we change 'N' to ' '    */
    /*      to conform to 2.1 conventions.                                  */
    /* -------------------------------------------------------------------- */
    if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 1)
        GOTO_header_too_small();

    GetMD(psImage, pachHeader, nOffset, 1, ICORDS);

    psImage->chICORDS = pachHeader[nOffset++];
    psImage->bHaveIGEOLO = FALSE;

    if ((STARTS_WITH_CI(psFile->szVersion, "NITF02.0") ||
         STARTS_WITH_CI(psFile->szVersion, "NITF01.")) &&
        psImage->chICORDS == 'N')
        psImage->chICORDS = ' ';

    /* -------------------------------------------------------------------- */
    /*      Read the image bounds.                                          */
    /* -------------------------------------------------------------------- */
    if (psImage->chICORDS != ' ')
    {
        int iCoord;

        psImage->bHaveIGEOLO = TRUE;
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 4 * 15)
            GOTO_header_too_small();

        GetMD(psImage, pachHeader, nOffset, 60, IGEOLO);

        psImage->bIsBoxCenterOfPixel = TRUE;
        for (iCoord = 0; iCoord < 4; iCoord++)
        {
            const char *pszCoordPair = pachHeader + nOffset + iCoord * 15;
            double *pdfXY = &(psImage->dfULX) + iCoord * 2;

            if (psImage->chICORDS == 'N' || psImage->chICORDS == 'S')
            {
                psImage->nZone = atoi(NITFGetField(szTemp, pszCoordPair, 0, 2));

                pdfXY[0] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 2, 6));
                pdfXY[1] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 8, 7));
            }
            else if (psImage->chICORDS == 'G' || psImage->chICORDS == 'C')
            {
                // It is critical to do the fetching of the D, M, S components
                // in 3 separate statements, otherwise if NITFGetField() is
                // defined in this compilation unit, the MSVC optimizer will
                // generate bad code, due to szTemp being overwritten before
                // being evaluated by CPLAtof() !
                pdfXY[1] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 0, 2));
                pdfXY[1] +=
                    CPLAtof(NITFGetField(szTemp, pszCoordPair, 2, 2)) / 60.0;
                pdfXY[1] +=
                    CPLAtof(NITFGetField(szTemp, pszCoordPair, 4, 2)) / 3600.0;
                if (pszCoordPair[6] == 's' || pszCoordPair[6] == 'S')
                    pdfXY[1] *= -1;

                // It is critical to do the fetching of the D, M, S components
                // in 3 separate statements, otherwise if NITFGetField() is
                // defined in this compilation unit, the MSVC optimizer will
                // generate bad code, due to szTemp being overwritten before
                // being evaluated by CPLAtof() !
                pdfXY[0] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 7, 3));
                pdfXY[0] +=
                    CPLAtof(NITFGetField(szTemp, pszCoordPair, 10, 2)) / 60.0;
                pdfXY[0] +=
                    CPLAtof(NITFGetField(szTemp, pszCoordPair, 12, 2)) / 3600.0;

                if (pszCoordPair[14] == 'w' || pszCoordPair[14] == 'W')
                    pdfXY[0] *= -1;
            }
            else if (psImage->chICORDS == 'D')
            { /* 'D' is Decimal Degrees */
                pdfXY[1] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 0, 7));
                pdfXY[0] = CPLAtof(NITFGetField(szTemp, pszCoordPair, 7, 8));
            }
            else if (psImage->chICORDS == 'U')
            {
                /* int err; */
                long nZone;
                char chHemisphere;
                NITFGetField(szTemp, pszCoordPair, 0, 15);

                CPLDebug("NITF", "IGEOLO = %15.15s", pszCoordPair);
                /* err = */ Convert_MGRS_To_UTM(szTemp, &nZone, &chHemisphere,
                                                pdfXY + 0, pdfXY + 1);

                if (chHemisphere == 'S')
                    nZone = -1 * nZone;

                if (psImage->nZone != 0 && psImage->nZone != -100)
                {
                    if (nZone != psImage->nZone)
                    {
                        CPLError(
                            CE_Warning, CPLE_AppDefined,
                            "Some IGEOLO points are in different UTM\n"
                            "zones, but this configuration isn't currently\n"
                            "supported by GDAL, ignoring IGEOLO.");
                        psImage->nZone = -100;
                    }
                }
                else if (psImage->nZone == 0)
                {
                    psImage->nZone = (int)nZone;
                }
            }
        }

        if (psImage->nZone == -100)
            psImage->nZone = 0;

        nOffset += 60;
    }
#undef GetMD

    /* -------------------------------------------------------------------- */
    /*      Should we reorient the IGEOLO points in an attempt to handle    */
    /*      files where they were written in the wrong order?               */
    /* -------------------------------------------------------------------- */
    if (psImage->bHaveIGEOLO)
        NITFPossibleIGEOLOReorientation(psImage);

    /* -------------------------------------------------------------------- */
    /*      Read the image comments.                                        */
    /* -------------------------------------------------------------------- */
    {
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 1)
            GOTO_header_too_small();

        nNICOM = atoi(NITFGetField(szTemp, pachHeader, nOffset++, 1));
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 80 * nNICOM)
            GOTO_header_too_small();

        char *pszICOM = (char *)CPLMalloc(nNICOM * 80 + 1);
        psImage->pszComments =
            CPLRecode(NITFGetField(pszICOM, pachHeader, nOffset, 80 * nNICOM),
                      CPL_ENC_ISO8859_1, CPL_ENC_UTF8);
        CPLFree(pszICOM);
        nOffset += nNICOM * 80;
    }

    /* -------------------------------------------------------------------- */
    /*      Read more stuff.                                                */
    /* -------------------------------------------------------------------- */
    if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 2)
        GOTO_header_too_small();

    NITFGetField(psImage->szIC, pachHeader, nOffset, 2);
    nOffset += 2;

    if (psImage->szIC[0] != 'N')
    {
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 4)
            GOTO_header_too_small();

        NITFGetField(psImage->szCOMRAT, pachHeader, nOffset, 4);
        nOffset += 4;
    }

    /* NBANDS */
    if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 1)
        GOTO_header_too_small();
    psImage->nBands = atoi(NITFGetField(szTemp, pachHeader, nOffset, 1));
    nOffset++;

    /* XBANDS */
    if (psImage->nBands == 0)
    {
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 5)
            GOTO_header_too_small();
        psImage->nBands = atoi(NITFGetField(szTemp, pachHeader, nOffset, 5));
        nOffset += 5;
    }

    if (psImage->nBands <= 0)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Invalid band number");
        NITFImageDeaccess(psImage);
        return NULL;
    }

    /* -------------------------------------------------------------------- */
    /*      Read per-band information.                                      */
    /* -------------------------------------------------------------------- */
    psImage->pasBandInfo = (NITFBandInfo *)VSI_CALLOC_VERBOSE(
        sizeof(NITFBandInfo), psImage->nBands);
    if (psImage->pasBandInfo == NULL)
    {
        NITFImageDeaccess(psImage);
        return NULL;
    }

    for (iBand = 0; iBand < psImage->nBands; iBand++)
    {
        NITFBandInfo *psBandInfo = psImage->pasBandInfo + iBand;
        int nLUTS;

        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 2 + 6 + 4 + 1 + 5)
            GOTO_header_too_small();

        NITFTrimWhite(
            NITFGetField(psBandInfo->szIREPBAND, pachHeader, nOffset, 2));
        nOffset += 2;

        NITFTrimWhite(
            NITFGetField(psBandInfo->szISUBCAT, pachHeader, nOffset, 6));
        nOffset += 6;

        nOffset += 4; /* Skip IFCn and IMFLTn */

        nLUTS = atoi(NITFGetField(szTemp, pachHeader, nOffset, 1));
        nOffset += 1;

        if (nLUTS == 0)
            continue;

        psBandInfo->nSignificantLUTEntries =
            atoi(NITFGetField(szTemp, pachHeader, nOffset, 5));
        nOffset += 5;

        if (psBandInfo->nSignificantLUTEntries < 0 ||
            psBandInfo->nSignificantLUTEntries > 256)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "LUT for band %d is corrupted : "
                     "nSignificantLUTEntries=%d. Truncating to 256",
                     iBand + 1, psBandInfo->nSignificantLUTEntries);
            psBandInfo->nSignificantLUTEntries = 256;
        }

        psBandInfo->nLUTLocation =
            nOffset + (int)psSegInfo->nSegmentHeaderStart;

        psBandInfo->pabyLUT = (unsigned char *)CPLCalloc(768, 1);

        if ((int)psSegInfo->nSegmentHeaderSize <
            nOffset + nLUTS * psBandInfo->nSignificantLUTEntries)
            GOTO_header_too_small();

        memcpy(psBandInfo->pabyLUT, pachHeader + nOffset,
               psBandInfo->nSignificantLUTEntries);
        nOffset += psBandInfo->nSignificantLUTEntries;

        if (nLUTS == 3)
        {
            memcpy(psBandInfo->pabyLUT + 256, pachHeader + nOffset,
                   psBandInfo->nSignificantLUTEntries);
            nOffset += psBandInfo->nSignificantLUTEntries;

            memcpy(psBandInfo->pabyLUT + 512, pachHeader + nOffset,
                   psBandInfo->nSignificantLUTEntries);
            nOffset += psBandInfo->nSignificantLUTEntries;
        }
        else if ((nLUTS == 2) && (STARTS_WITH_CI(psImage->szIREP, "MONO")) &&
                 ((STARTS_WITH_CI(psBandInfo->szIREPBAND, "M")) ||
                  (STARTS_WITH_CI(psBandInfo->szIREPBAND, "LU"))))
        {
            int iLUTEntry;
            double scale = 255.0 / 65535.0;
            unsigned char *pMSB = NULL;
            unsigned char *pLSB = NULL;
            unsigned char *p3rdLUT = NULL;
            unsigned char scaledVal = 0;
            unsigned short *pLUTVal = NULL;

            /* In this case, we have two LUTs. The first and second LUTs should
             * map respectively to the most */
            /* significant byte and the least significant byte of the 16 bit
             * values. */

            memcpy(psBandInfo->pabyLUT + 256, pachHeader + nOffset,
                   psBandInfo->nSignificantLUTEntries);
            nOffset += psBandInfo->nSignificantLUTEntries;

            pMSB = psBandInfo->pabyLUT;
            pLSB = psBandInfo->pabyLUT + 256;
            p3rdLUT = psBandInfo->pabyLUT + 512;
            /* E. Rouault: Why 255 and not 256 ? */
            pLUTVal = (unsigned short *)CPLMalloc(sizeof(short) * 255);

            for (iLUTEntry = 0; iLUTEntry < 255; ++iLUTEntry)
            {
                /* E. Rouault: I don't understand why the following logic is
                 * endianness dependent. */
                pLUTVal[iLUTEntry] = ((pMSB[iLUTEntry] << 8) | pLSB[iLUTEntry]);
#ifdef CPL_LSB
                pLUTVal[iLUTEntry] =
                    ((pLUTVal[iLUTEntry] >> 8) | (pLUTVal[iLUTEntry] << 8));
#endif
            }

            for (iLUTEntry = 0; iLUTEntry < 255; ++iLUTEntry)
            {
                scaledVal =
                    (unsigned char)ceil((double)(pLUTVal[iLUTEntry] * scale));

                pMSB[iLUTEntry] = scaledVal;
                pLSB[iLUTEntry] = scaledVal;
                p3rdLUT[iLUTEntry] = scaledVal;
            }

            CPLFree(pLUTVal);
        }
        else
        {
            /* morph greyscale lut into RGB LUT. */
            memcpy(psBandInfo->pabyLUT + 256, psBandInfo->pabyLUT, 256);
            memcpy(psBandInfo->pabyLUT + 512, psBandInfo->pabyLUT, 256);
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Some files (i.e. NSIF datasets) have truncated image              */
    /*      headers.  This has been observed with JPEG compressed           */
    /*      files.  In this case guess reasonable values for these          */
    /*      fields.                                                         */
    /* -------------------------------------------------------------------- */
    if (nOffset + 40 > (int)psSegInfo->nSegmentHeaderSize)
    {
        psImage->chIMODE = 'B';
        psImage->nBlocksPerRow = 1;
        psImage->nBlocksPerColumn = 1;
        psImage->nBlockWidth = psImage->nCols;
        psImage->nBlockHeight = psImage->nRows;
        psImage->nBitsPerSample = psImage->nABPP;
        psImage->nIDLVL = 0;
        psImage->nIALVL = 0;
        psImage->nILOCRow = 0;
        psImage->nILOCColumn = 0;
        psImage->szIMAG[0] = '\0';

        nOffset += 40;
    }

    /* -------------------------------------------------------------------- */
    /*      Read more header fields.                                        */
    /* -------------------------------------------------------------------- */
    else
    {
        psImage->chIMODE = pachHeader[nOffset + 1];

        psImage->nBlocksPerRow =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 2, 4));
        psImage->nBlocksPerColumn =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 6, 4));
        psImage->nBlockWidth =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 10, 4));
        psImage->nBlockHeight =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 14, 4));

        /* See MIL-STD-2500-C, paragraph 5.4.2.2-d (#3263) */
        if (psImage->nBlocksPerRow == 1 && psImage->nBlockWidth == 0)
        {
            psImage->nBlockWidth = psImage->nCols;
        }

        if (psImage->nBlocksPerColumn == 1 && psImage->nBlockHeight == 0)
        {
            psImage->nBlockHeight = psImage->nRows;
        }

        psImage->nBitsPerSample =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 18, 2));

        if (psImage->nABPP == 0)
            psImage->nABPP = psImage->nBitsPerSample;

        nOffset += 20;

        /* capture image inset information */

        psImage->nIDLVL =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 0, 3));
        psImage->nIALVL =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 3, 3));
        psImage->nILOCRow =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 6, 5));
        psImage->nILOCColumn =
            atoi(NITFGetField(szTemp, pachHeader, nOffset + 11, 5));

        memcpy(psImage->szIMAG, pachHeader + nOffset + 16, 4);
        psImage->szIMAG[4] = '\0';

        nOffset += 3;  /* IDLVL */
        nOffset += 3;  /* IALVL */
        nOffset += 10; /* ILOC */
        nOffset += 4;  /* IMAG */
    }

    if (psImage->nBitsPerSample <= 0 || psImage->nBlocksPerRow <= 0 ||
        psImage->nBlocksPerColumn <= 0 || psImage->nBlockWidth <= 0 ||
        psImage->nBlockHeight <= 0 ||
        psImage->nBlocksPerRow > INT_MAX / psImage->nBlockWidth ||
        psImage->nBlocksPerColumn > INT_MAX / psImage->nBlockHeight ||
        psImage->nCols > psImage->nBlocksPerRow * psImage->nBlockWidth ||
        psImage->nRows > psImage->nBlocksPerColumn * psImage->nBlockHeight ||
        psImage->nBlocksPerRow > INT_MAX / psImage->nBlocksPerColumn ||
        psImage->nBlocksPerRow * psImage->nBlocksPerColumn >
            INT_MAX / psImage->nBands)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Invalid values for block dimension/number");
        NITFImageDeaccess(psImage);
        return NULL;
    }

    if (psImage->nBlocksPerRow * psImage->nBlocksPerColumn * psImage->nBands >
        1000 * 1000)
    {
        // Sanity check to avoid allocating too much memory
        VSIFSeekL(psFile->fp, 0, SEEK_END);
        // This is really a very safe bound. A smarter check would taken
        // into account the block size as well and/or the size of an entry
        // in the offset table.
        if (VSIFTellL(psFile->fp) <
            (vsi_l_offset)(psImage->nBlocksPerRow) * psImage->nBlocksPerColumn)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "File is too small compared to the number of blocks");
            NITFImageDeaccess(psImage);
            return NULL;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Override nCols and nRows for NITF 1.1 (not sure why!)           */
    /* -------------------------------------------------------------------- */
    if (STARTS_WITH_CI(psFile->szVersion, "NITF01."))
    {
        psImage->nCols = psImage->nBlocksPerRow * psImage->nBlockWidth;
        psImage->nRows = psImage->nBlocksPerColumn * psImage->nBlockHeight;
    }

    /* -------------------------------------------------------------------- */
    /*      Read TREs if we have them.                                      */
    /* -------------------------------------------------------------------- */
    else if (nOffset + 10 <= (int)psSegInfo->nSegmentHeaderSize)
    {
        int nUserTREBytes, nExtendedTREBytes, nFirstTagUsedLength = 0;

        /* --------------------------------------------------------------------
         */
        /*      Are there user TRE bytes to skip? */
        /* --------------------------------------------------------------------
         */
        nUserTREBytes = atoi(NITFGetField(szTemp, pachHeader, nOffset, 5));
        nOffset += 5;

        if (nUserTREBytes > 3 + 11) /* Must have at least one tag */
        {
            if ((int)psSegInfo->nSegmentHeaderSize < nOffset + nUserTREBytes)
                GOTO_header_too_small();

            psImage->nTREBytes = nUserTREBytes - 3;
            psImage->pachTRE = (char *)CPLMalloc(psImage->nTREBytes);
            memcpy(psImage->pachTRE, pachHeader + nOffset + 3,
                   psImage->nTREBytes);

            nOffset += nUserTREBytes;

            sscanf(psImage->pachTRE + 6, "%*5d%n", &nFirstTagUsedLength);
            if (nFirstTagUsedLength != 5)
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Cannot read User TRE. First tag's length is invalid");
                CPLFree(psImage->pachTRE);
                psImage->nTREBytes = 0;
                psImage->pachTRE = NULL;
            }
        }
        else
        {
            psImage->nTREBytes = 0;
            psImage->pachTRE = NULL;

            if (nUserTREBytes > 0)
                nOffset += nUserTREBytes;
        }

        /* --------------------------------------------------------------------
         */
        /*      Are there managed TRE bytes to recognise? */
        /* --------------------------------------------------------------------
         */
        if ((int)psSegInfo->nSegmentHeaderSize < nOffset + 5)
            GOTO_header_too_small();
        nExtendedTREBytes = atoi(NITFGetField(szTemp, pachHeader, nOffset, 5));
        nOffset += 5;

        if (nExtendedTREBytes >= 3)
        {
            if ((int)psSegInfo->nSegmentHeaderSize <
                nOffset + nExtendedTREBytes)
                GOTO_header_too_small();

            psImage->nIXSOFLOffsetInSubfileHeader = nOffset;
            char szIXSOFL[4];
            memcpy(szIXSOFL, pachHeader + nOffset, 3);
            szIXSOFL[3] = 0;
            psImage->nIXSOFL = atoi(szIXSOFL);
            if (psImage->nIXSOFL != 0)
                psImage->papszMetadata = CSLSetNameValue(
                    psImage->papszMetadata, "NITF_IXSOFL", szIXSOFL);

            if (nExtendedTREBytes > 3)
            {
                psImage->pachTRE = (char *)CPLRealloc(
                    psImage->pachTRE,
                    psImage->nTREBytes + nExtendedTREBytes - 3);
                memcpy(psImage->pachTRE + psImage->nTREBytes,
                       pachHeader + nOffset + 3, nExtendedTREBytes - 3);

                psImage->nTREBytes += (nExtendedTREBytes - 3);
            }
            /*nOffset += nExtendedTREBytes;*/
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Is there a location table to load?                              */
    /* -------------------------------------------------------------------- */
    NITFLoadLocationTable(psImage);

    /* Fix bug #1744 */
    if (psImage->nBands == 1)
        NITFLoadColormapSubSection(psImage);

    /* -------------------------------------------------------------------- */
    /*      Setup some image access values.  Some of these may not apply    */
    /*      for compressed images, or band interleaved by block images.     */
    /* -------------------------------------------------------------------- */
    if (psImage->nBitsPerSample <= 8)
        psImage->nWordSize = 1;
    else if (psImage->nBitsPerSample <= 16)
        psImage->nWordSize = 2;
    else if (psImage->nBitsPerSample <= 32)
        psImage->nWordSize = 4;
    else
        psImage->nWordSize = psImage->nBitsPerSample / 8;
    if (psImage->chIMODE == 'S')
    {
        psImage->nPixelOffset = psImage->nWordSize;
        psImage->nLineOffset =
            ((GIntBig)psImage->nBlockWidth * psImage->nBitsPerSample) / 8;
        psImage->nBlockOffset = psImage->nLineOffset * psImage->nBlockHeight;
        psImage->nBandOffset = psImage->nBlockOffset * psImage->nBlocksPerRow *
                               psImage->nBlocksPerColumn;
    }
    else if (psImage->chIMODE == 'P')
    {
        psImage->nPixelOffset = (GIntBig)psImage->nWordSize * psImage->nBands;
        psImage->nLineOffset = ((GIntBig)psImage->nBlockWidth *
                                psImage->nBitsPerSample * psImage->nBands) /
                               8;
        psImage->nBandOffset = psImage->nWordSize;
        psImage->nBlockOffset = psImage->nLineOffset * psImage->nBlockHeight;
    }
    else if (psImage->chIMODE == 'R')
    {
        psImage->nPixelOffset = psImage->nWordSize;
        psImage->nBandOffset =
            ((GIntBig)psImage->nBlockWidth * psImage->nBitsPerSample) / 8;
        psImage->nLineOffset = psImage->nBandOffset * psImage->nBands;
        psImage->nBlockOffset = psImage->nLineOffset * psImage->nBlockHeight;
    }
    else /* if( psImage->chIMODE == 'B' ) */
    {
        psImage->nPixelOffset = psImage->nWordSize;
        psImage->nLineOffset =
            ((GIntBig)psImage->nBlockWidth * psImage->nBitsPerSample) / 8;
        psImage->nBandOffset = psImage->nBlockHeight * psImage->nLineOffset;
        psImage->nBlockOffset = psImage->nBandOffset * psImage->nBands;
    }

    /* -------------------------------------------------------------------- */
    /*      Setup block map.                                                */
    /* -------------------------------------------------------------------- */

    /* Int overflow already checked above */
    psImage->panBlockStart = (GUIntBig *)VSI_CALLOC_VERBOSE(
        (size_t)psImage->nBlocksPerRow * psImage->nBlocksPerColumn *
            psImage->nBands,
        sizeof(GUIntBig));
    if (psImage->panBlockStart == NULL)
    {
        NITFImageDeaccess(psImage);
        return NULL;
    }

    /* -------------------------------------------------------------------- */
    /*  Load subframe mask table if present (typically, for CADRG/CIB       */
    /*  images with IC=C4/M4)                                               */
    /* -------------------------------------------------------------------- */
    bool bBlockStartRead = false;
    NITFLoadSubframeMaskTable(psImage, &bBlockStartRead);

    /* -------------------------------------------------------------------- */
    /*      Offsets to VQ compressed tiles are based on a fixed block       */
    /*      size, and are offset from the spatial data location kept in     */
    /*      the location table ... which is generally not the beginning     */
    /*      of the image data segment.                                      */
    /* -------------------------------------------------------------------- */
    if (EQUAL(psImage->szIC, "C4"))
    {
        if (!bBlockStartRead)
        {
            GUIntBig nLocBase = psSegInfo->nSegmentStart;

            for (i = 0; i < psImage->nLocCount; i++)
            {
                if (psImage->pasLocations[i].nLocId ==
                    LID_SpatialDataSubsection)
                    nLocBase = psImage->pasLocations[i].nLocOffset;
            }

            if (nLocBase == psSegInfo->nSegmentStart)
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Failed to find spatial data location, guessing.");

            for (i = 0; i < psImage->nBlocksPerRow * psImage->nBlocksPerColumn;
                 i++)
                psImage->panBlockStart[i] = nLocBase + (GUIntBig)(6144) * i;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      If there is no block map, just compute directly assuming the    */
    /*      blocks start at the beginning of the image segment, and are     */
    /*      packed tightly with the IMODE organization.                     */
    /* -------------------------------------------------------------------- */
    else if (psImage->szIC[0] != 'M' && psImage->szIC[1] != 'M')
    {
        int iBlockX, iBlockY;

        for (iBlockY = 0; iBlockY < psImage->nBlocksPerColumn; iBlockY++)
        {
            for (iBlockX = 0; iBlockX < psImage->nBlocksPerRow; iBlockX++)
            {
                for (iBand = 0; iBand < psImage->nBands; iBand++)
                {
                    int iBlock;

                    iBlock = iBlockX + iBlockY * psImage->nBlocksPerRow +
                             iBand * psImage->nBlocksPerRow *
                                 psImage->nBlocksPerColumn;

                    psImage->panBlockStart[iBlock] =
                        psSegInfo->nSegmentStart +
                        ((iBlockX + iBlockY * psImage->nBlocksPerRow) *
                         psImage->nBlockOffset) +
                        (iBand * psImage->nBandOffset);
                }
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Otherwise we need to read the block map from the beginning      */
    /*      of the image segment.                                           */
    /* -------------------------------------------------------------------- */
    else
    {
        GUInt32 nIMDATOFF;
        GUInt16 nBMRLNTH, nTMRLNTH, nTPXCDLNTH;
        int nBlockCount;
        int bOK = TRUE;

        nBlockCount = psImage->nBlocksPerRow * psImage->nBlocksPerColumn *
                      psImage->nBands;

        CPLAssert(psImage->szIC[0] == 'M' || psImage->szIC[1] == 'M');

        bOK &= VSIFSeekL(psFile->fp, psSegInfo->nSegmentStart, SEEK_SET) == 0;
        bOK &= VSIFReadL(&nIMDATOFF, 1, 4, psFile->fp) == 4;
        bOK &= VSIFReadL(&nBMRLNTH, 1, 2, psFile->fp) == 2;
        bOK &= VSIFReadL(&nTMRLNTH, 1, 2, psFile->fp) == 2;
        bOK &= VSIFReadL(&nTPXCDLNTH, 1, 2, psFile->fp) == 2;

        CPL_MSBPTR32(&nIMDATOFF);
        CPL_MSBPTR16(&nBMRLNTH);
        CPL_MSBPTR16(&nTMRLNTH);
        CPL_MSBPTR16(&nTPXCDLNTH);

        if (nTPXCDLNTH == 8)
        {
            GByte byNodata;

            psImage->bNoDataSet = TRUE;
            bOK &= VSIFReadL(&byNodata, 1, 1, psFile->fp) == 1;
            psImage->nNoDataValue = byNodata;
        }
        else
            bOK &= VSIFSeekL(psFile->fp, (nTPXCDLNTH + 7) / 8, SEEK_CUR) == 0;

        if (nBMRLNTH == 4 && psImage->chIMODE == 'P')
        {
            int nStoredBlocks =
                psImage->nBlocksPerRow * psImage->nBlocksPerColumn;

            for (i = 0; bOK && i < nStoredBlocks; i++)
            {
                GUInt32 l_nOffset;
                bOK &= VSIFReadL(&l_nOffset, 4, 1, psFile->fp) == 1;
                CPL_MSBPTR32(&l_nOffset);
                psImage->panBlockStart[i] = l_nOffset;
                if (psImage->panBlockStart[i] != UINT_MAX)
                {
                    psImage->panBlockStart[i] +=
                        psSegInfo->nSegmentStart + nIMDATOFF;

                    for (iBand = 1; iBand < psImage->nBands; iBand++)
                    {
                        psImage->panBlockStart[i + iBand * nStoredBlocks] =
                            psImage->panBlockStart[i] +
                            iBand * psImage->nBandOffset;
                    }
                }
                else
                {
                    for (iBand = 1; iBand < psImage->nBands; iBand++)
                        psImage->panBlockStart[i + iBand * nStoredBlocks] =
                            UINT_MAX;
                }
            }
        }
        else if (nBMRLNTH == 4)
        {
            if (!bBlockStartRead)
            {
                for (i = 0; bOK && i < nBlockCount; i++)
                {
                    GUInt32 l_nOffset;
                    bOK &= VSIFReadL(&l_nOffset, 4, 1, psFile->fp) == 1;
                    CPL_MSBPTR32(&l_nOffset);
                    psImage->panBlockStart[i] = l_nOffset;
                    if (psImage->panBlockStart[i] != UINT_MAX)
                    {
                        psImage->panBlockStart[i] +=
                            psSegInfo->nSegmentStart + nIMDATOFF;
                    }
                }
            }
        }
        else
        {
            if (EQUAL(psImage->szIC, "M4"))
            {
                if (!bBlockStartRead)
                {
                    for (i = 0; i < nBlockCount; i++)
                        psImage->panBlockStart[i] = (GUIntBig)6144 * i +
                                                    psSegInfo->nSegmentStart +
                                                    nIMDATOFF;
                }
            }
            else if (EQUAL(psImage->szIC, "NM"))
            {
                int iBlockX, iBlockY;

                for (iBlockY = 0; iBlockY < psImage->nBlocksPerColumn;
                     iBlockY++)
                {
                    for (iBlockX = 0; iBlockX < psImage->nBlocksPerRow;
                         iBlockX++)
                    {
                        for (iBand = 0; iBand < psImage->nBands; iBand++)
                        {
                            int iBlock;

                            iBlock = iBlockX +
                                     iBlockY * psImage->nBlocksPerRow +
                                     iBand * psImage->nBlocksPerRow *
                                         psImage->nBlocksPerColumn;

                            psImage->panBlockStart[iBlock] =
                                psSegInfo->nSegmentStart + nIMDATOFF +
                                ((iBlockX + iBlockY * psImage->nBlocksPerRow) *
                                 psImage->nBlockOffset) +
                                (iBand * psImage->nBandOffset);
                        }
                    }
                }
            }
            else
            {
                CPLError(
                    CE_Warning, CPLE_AppDefined,
                    "Unsupported IC value '%s', image access will likely fail.",
                    psImage->szIC);
            }
        }
        if (!bOK)
        {
            CPLError(CE_Failure, CPLE_FileIO, "I/O error");
            NITFImageDeaccess(psImage);
            return NULL;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Bug #1751: Add a transparent color if there are none. Absent    */
    /*      subblocks will be then transparent.                             */
    /* -------------------------------------------------------------------- */
    if (!psImage->bNoDataSet && psImage->nBands == 1 &&
        psImage->nBitsPerSample == 8)
    {
        NITFBandInfo *psBandInfo = psImage->pasBandInfo;
        if (psBandInfo->nSignificantLUTEntries < 256 - 1 &&
            psBandInfo->pabyLUT != NULL)
        {
            if (psBandInfo->nSignificantLUTEntries == 217 &&
                psBandInfo->pabyLUT[216] == 0 &&
                psBandInfo->pabyLUT[256 + 216] == 0 &&
                psBandInfo->pabyLUT[512 + 216] == 0)
            {
                psImage->bNoDataSet = TRUE;
                psImage->nNoDataValue = psBandInfo->nSignificantLUTEntries - 1;
            }
            else
            {
                bool bHasMissingBlock = false;
                for (int iBlock = 0; !bHasMissingBlock &&
                                     iBlock < psImage->nBlocksPerRow *
                                                  psImage->nBlocksPerColumn;
                     ++iBlock)
                {
                    bHasMissingBlock =
                        (psImage->panBlockStart[iBlock] == UINT_MAX);
                }
                if (bHasMissingBlock)
                {
                    psBandInfo
                        ->pabyLUT[0 + psBandInfo->nSignificantLUTEntries] = 0;
                    psBandInfo
                        ->pabyLUT[256 + psBandInfo->nSignificantLUTEntries] = 0;
                    psBandInfo
                        ->pabyLUT[512 + psBandInfo->nSignificantLUTEntries] = 0;
                    psImage->bNoDataSet = TRUE;
                    psImage->nNoDataValue = psBandInfo->nSignificantLUTEntries;
                }
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*  We override the coordinates found in IGEOLO in case a BLOCKA is     */
    /*  present. According to the BLOCKA specification, it repeats earth    */
    /*  coordinates image corner locations described by IGEOLO in the NITF  */
    /*  image subheader, but provide higher precision.                      */
    /* -------------------------------------------------------------------- */

    NITFReadBLOCKA_GCPs(psImage);

    /* -------------------------------------------------------------------- */
    /*      We override the coordinates found in IGEOLO in case a GEOLOB is */
    /*      present.  It provides higher precision lat/long values.         */
    /* -------------------------------------------------------------------- */
    NITFReadGEOLOB(psImage);

    /* -------------------------------------------------------------------- */
    /*      If we have an RPF CoverageSectionSubheader, read the more       */
    /*      precise bounds from it.                                         */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId == LID_CoverageSectionSubheader)
        {
            double adfTarget[8];

            if (VSIFSeekL(psFile->fp, psImage->pasLocations[i].nLocOffset,
                          SEEK_SET) != 0 ||
                VSIFReadL(adfTarget, 8, 8, psFile->fp) != 8)
            {
                CPLError(CE_Failure, CPLE_FileIO, "I/O error");
                NITFImageDeaccess(psImage);
                return NULL;
            }

            for (i = 0; i < 8; i++)
                CPL_MSBPTR64((adfTarget + i));

            psImage->dfULX = adfTarget[1];
            psImage->dfULY = adfTarget[0];
            psImage->dfLLX = adfTarget[3];
            psImage->dfLLY = adfTarget[2];
            psImage->dfURX = adfTarget[5];
            psImage->dfURY = adfTarget[4];
            psImage->dfLRX = adfTarget[7];
            psImage->dfLRY = adfTarget[6];

            psImage->bIsBoxCenterOfPixel = FALSE;  // edge of pixel

            CPLDebug("NITF", "Got spatial info from CoverageSection");
            break;
        }
    }

    /* Bug #1750, #2135 and #3383 */
    /* Fix CADRG products like cjnc/cjncz01/000k1023.jn1 (and similar) from NIMA
     * GNCJNCN CDROM: */
    /* this product is crossing meridian 180deg and the upper and lower right
     * longitudes are negative  */
    /* while the upper and lower left longitudes are positive which causes
     * problems in OpenEV, etc... */
    /* So we are adjusting the upper and lower right longitudes by setting them
     * above +180 */
    /* Make this test only CADRG specific are there are other NITF profiles
     * where non north-up imagery */
    /* is valid */
    pszIID1 = CSLFetchNameValue(psImage->papszMetadata, "NITF_IID1");
    if ((psImage->chICORDS == 'G' || psImage->chICORDS == 'D') &&
        pszIID1 != NULL && EQUAL(pszIID1, "CADRG") &&
        (psImage->dfULX > psImage->dfURX && psImage->dfLLX > psImage->dfLRX &&
         psImage->dfULY > psImage->dfLLY && psImage->dfURY > psImage->dfLRY))
    {
        psImage->dfURX += 360;
        psImage->dfLRX += 360;
    }

    /* -------------------------------------------------------------------- */
    /*      Load RPF attribute metadata if we have it.                      */
    /* -------------------------------------------------------------------- */
    NITFLoadAttributeSection(psImage);

    /* -------------------------------------------------------------------- */
    /*      Are the VQ tables to load up?                                   */
    /* -------------------------------------------------------------------- */
    NITFLoadVQTables(psImage, TRUE);

    return psImage;

header_too_small:

    CPLError(CE_Failure, CPLE_AppDefined,
             "Image header too small (called from line %d)", nFaultyLine);
    NITFImageDeaccess(psImage);
    return NULL;
}

/************************************************************************/
/*                         NITFImageDeaccess()                          */
/************************************************************************/

void NITFImageDeaccess(NITFImage *psImage)

{
    int iBand;

    CPLAssert(psImage->psFile->pasSegmentInfo[psImage->iSegment].hAccess ==
              psImage);

    psImage->psFile->pasSegmentInfo[psImage->iSegment].hAccess = NULL;

    if (psImage->pasBandInfo)
    {
        for (iBand = 0; iBand < psImage->nBands; iBand++)
            CPLFree(psImage->pasBandInfo[iBand].pabyLUT);
    }
    CPLFree(psImage->pasBandInfo);
    CPLFree(psImage->panBlockStart);
    CPLFree(psImage->pszComments);
    CPLFree(psImage->pachHeader);
    CPLFree(psImage->pachTRE);
    CSLDestroy(psImage->papszMetadata);

    CPLFree(psImage->pasLocations);
    for (iBand = 0; iBand < 4; iBand++)
        CPLFree(psImage->apanVQLUT[iBand]);

    CPLFree(psImage);
}

/************************************************************************/
/*                        NITFUncompressVQTile()                        */
/*                                                                      */
/*      This code was derived from OSSIM which in turn derived it       */
/*      from OpenMap ... open source means sharing!                     */
/************************************************************************/

static void NITFUncompressVQTile(const NITFImage *psImage,
                                 const GByte *pabyVQBuf, GByte *pabyResult)

{
    int i, j, t, iSrcByte = 0;

    for (i = 0; i < 256; i += 4)
    {
        for (j = 0; j < 256; j += 8)
        {
            GUInt16 firstByte = pabyVQBuf[iSrcByte++];
            GUInt16 secondByte = pabyVQBuf[iSrcByte++];
            GUInt16 thirdByte = pabyVQBuf[iSrcByte++];

            /*
             * because dealing with half-bytes is hard, we
             * uncompress two 4x4 tiles at the same time. (a
             * 4x4 tile compressed is 12 bits )
             * this little code was grabbed from openmap software.
             */

            /* Get first 12-bit value as index into VQ table */

            GUInt16 val1 = (firstByte << 4) | (secondByte >> 4);

            /* Get second 12-bit value as index into VQ table*/

            GUInt16 val2 = ((secondByte & 0x000F) << 8) | thirdByte;

            for (t = 0; t < 4; ++t)
            {
                GByte *pabyTarget = pabyResult + (i + t) * 256 + j;

                memcpy(pabyTarget, psImage->apanVQLUT[t] + val1, 4);
                memcpy(pabyTarget + 4, psImage->apanVQLUT[t] + val2, 4);
            }
        } /* for j */
    } /* for i */
}

/************************************************************************/
/*                         NITFReadImageBlock()                         */
/************************************************************************/

int NITFReadImageBlock(NITFImage *psImage, int nBlockX, int nBlockY, int nBand,
                       void *pData)

{
    int nWrkBufSize;
    int iBaseBlock = nBlockX + nBlockY * psImage->nBlocksPerRow;
    int iFullBlock = iBaseBlock + (nBand - 1) * psImage->nBlocksPerRow *
                                      psImage->nBlocksPerColumn;

    /* -------------------------------------------------------------------- */
    /*      Special exit conditions.                                        */
    /* -------------------------------------------------------------------- */
    if (nBand == 0)
        return BLKREAD_FAIL;

    if (psImage->panBlockStart[iFullBlock] == UINT_MAX)
        return BLKREAD_NULL;

    /* -------------------------------------------------------------------- */
    /*      Special case for 1 bit data.  NITFRasterBand::IReadBlock()      */
    /*      already knows how to promote to byte.                           */
    /* -------------------------------------------------------------------- */
    if ((EQUAL(psImage->szIC, "NC") || EQUAL(psImage->szIC, "NM")) &&
        psImage->nBitsPerSample == 1)
    {
        if (nBlockX != 0 || nBlockY != 0)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "assert nBlockX == 0 && nBlockY == 0 failed\n");
            return BLKREAD_FAIL;
        }
        if (VSIFSeekL(psImage->psFile->fp,
                      psImage->panBlockStart[0] +
                          ((vsi_l_offset)psImage->nBlockWidth *
                               psImage->nBlockHeight +
                           7) /
                              8 * (nBand - 1),
                      SEEK_SET) == 0 &&
            VSIFReadL(pData,
                      (psImage->nBlockWidth * psImage->nBlockHeight + 7) / 8, 1,
                      psImage->psFile->fp) == 1)
        {
            return BLKREAD_OK;
        }
        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Figure out how big the working buffer will need to be.          */
    /* -------------------------------------------------------------------- */
    if (psImage->nBitsPerSample != psImage->nWordSize * 8)
        nWrkBufSize =
            (int)psImage->nLineOffset * (psImage->nBlockHeight - 1) +
            (psImage->nBitsPerSample * (psImage->nBlockWidth) + 7) / 8;
    else
        nWrkBufSize = (int)psImage->nLineOffset * (psImage->nBlockHeight - 1) +
                      (int)psImage->nPixelOffset * (psImage->nBlockWidth - 1) +
                      psImage->nWordSize;

    if (nWrkBufSize == 0)
        nWrkBufSize = (psImage->nBlockWidth * psImage->nBlockHeight *
                           psImage->nBitsPerSample +
                       7) /
                      8;

    /* -------------------------------------------------------------------- */
    /*      Can we do a direct read into our buffer?                        */
    /* -------------------------------------------------------------------- */
    if ((size_t)psImage->nWordSize == psImage->nPixelOffset &&
        (size_t)((psImage->nBitsPerSample * psImage->nBlockWidth + 7) / 8) ==
            psImage->nLineOffset &&
        psImage->szIC[0] != 'C' && psImage->szIC[0] != 'M' &&
        psImage->chIMODE != 'P')
    {
        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            (int)VSIFReadL(pData, 1, nWrkBufSize, psImage->psFile->fp) !=
                nWrkBufSize)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d byte block from " CPL_FRMT_GUIB ".",
                     nWrkBufSize, psImage->panBlockStart[iFullBlock]);
            return BLKREAD_FAIL;
        }
        else
        {
#ifdef CPL_LSB
            NITFSwapWords(psImage, pData,
                          psImage->nBlockWidth * psImage->nBlockHeight);
#endif

            return BLKREAD_OK;
        }
    }

    if (psImage->szIC[0] == 'N')
    {
        /* read all the data needed to get our requested band-block */
        if (psImage->nBitsPerSample != psImage->nWordSize * 8)
        {
            if (psImage->chIMODE == 'S' ||
                (psImage->chIMODE == 'B' && psImage->nBands == 1))
            {
                nWrkBufSize = ((psImage->nBlockWidth * psImage->nBlockHeight *
                                psImage->nBitsPerSample) +
                               7) /
                              8;
                if (VSIFSeekL(psImage->psFile->fp,
                              psImage->panBlockStart[iFullBlock],
                              SEEK_SET) != 0 ||
                    (int)VSIFReadL(pData, 1, nWrkBufSize,
                                   psImage->psFile->fp) != nWrkBufSize)
                {
                    CPLError(CE_Failure, CPLE_FileIO,
                             "Unable to read %d byte block from %d.",
                             (int)nWrkBufSize,
                             (int)psImage->panBlockStart[iFullBlock]);
                    return BLKREAD_FAIL;
                }

                return BLKREAD_OK;
            }
            else
            {
                CPLError(CE_Failure, CPLE_NotSupported,
                         "ABPP=%d and IMODE=%c not supported",
                         psImage->nBitsPerSample, psImage->chIMODE);
                return BLKREAD_FAIL;
            }
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Read the requested information into a temporary buffer and      */
    /*      pull out what we want.                                          */
    /* -------------------------------------------------------------------- */
    if (psImage->szIC[0] == 'N')
    {
        GByte *pabyWrkBuf = (GByte *)VSI_MALLOC_VERBOSE(nWrkBufSize);
        int iPixel, iLine;

        if (pabyWrkBuf == NULL)
        {
            return BLKREAD_FAIL;
        }

        /* read all the data needed to get our requested band-block */
        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            (int)VSIFReadL(pabyWrkBuf, 1, nWrkBufSize, psImage->psFile->fp) !=
                nWrkBufSize)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d byte block from " CPL_FRMT_GUIB ".",
                     nWrkBufSize, psImage->panBlockStart[iFullBlock]);
            CPLFree(pabyWrkBuf);
            return BLKREAD_FAIL;
        }

        for (iLine = 0; iLine < psImage->nBlockHeight; iLine++)
        {
            GByte *pabySrc, *pabyDst;

            pabySrc = pabyWrkBuf + iLine * psImage->nLineOffset;
            pabyDst = ((GByte *)pData) +
                      iLine * (psImage->nWordSize * psImage->nBlockWidth);

            for (iPixel = 0; iPixel < psImage->nBlockWidth; iPixel++)
            {
                memcpy(pabyDst + iPixel * psImage->nWordSize,
                       pabySrc + iPixel * psImage->nPixelOffset,
                       psImage->nWordSize);
            }
        }

#ifdef CPL_LSB
        NITFSwapWords(psImage, pData,
                      psImage->nBlockWidth * psImage->nBlockHeight);
#endif

        CPLFree(pabyWrkBuf);

        return BLKREAD_OK;
    }

    /* -------------------------------------------------------------------- */
    /*      Handle VQ compression.  The VQ compression basically keeps a    */
    /*      64x64 array of 12bit code words.  Each code word expands to     */
    /*      a predefined 4x4 8 bit per pixel pattern.                       */
    /* -------------------------------------------------------------------- */
    else if (EQUAL(psImage->szIC, "C4") || EQUAL(psImage->szIC, "M4"))
    {
        GByte abyVQCoded[6144];

        if (psImage->apanVQLUT[0] == NULL)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "File lacks VQ LUTs, unable to decode imagery.");
            return BLKREAD_FAIL;
        }
        if (psImage->nBlockWidth != 256 || psImage->nBlockHeight != 256)
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Invalid block dimension for VQ compressed data.");
            return BLKREAD_FAIL;
        }

        /* Read the codewords */
        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            VSIFReadL(abyVQCoded, 1, sizeof(abyVQCoded), psImage->psFile->fp) !=
                sizeof(abyVQCoded))
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d byte block from " CPL_FRMT_GUIB ".",
                     (int)sizeof(abyVQCoded),
                     psImage->panBlockStart[iFullBlock]);
            return BLKREAD_FAIL;
        }

        NITFUncompressVQTile(psImage, abyVQCoded, pData);

        return BLKREAD_OK;
    }

    /* -------------------------------------------------------------------- */
    /*      Handle ARIDPCM compression.                                     */
    /* -------------------------------------------------------------------- */
    else if (EQUAL(psImage->szIC, "C2") || EQUAL(psImage->szIC, "M2"))
    {
        GIntBig nSignedRawBytes;
        size_t nRawBytes;
        NITFSegmentInfo *psSegInfo;
        int success;
        GByte *pabyRawData;

        if (psImage->nBitsPerSample != 8)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Unsupported bits per sample value (%d) for C2/M2 compression",
                psImage->nBitsPerSample);
            return BLKREAD_FAIL;
        }

        if (iFullBlock < psImage->nBlocksPerRow * psImage->nBlocksPerColumn *
                                 psImage->nBands -
                             1)
        {
            nSignedRawBytes = (GIntBig)psImage->panBlockStart[iFullBlock + 1] -
                              (GIntBig)psImage->panBlockStart[iFullBlock];
        }
        else
        {
            psSegInfo = psImage->psFile->pasSegmentInfo + psImage->iSegment;
            nSignedRawBytes = (GIntBig)psSegInfo->nSegmentStart +
                              (GIntBig)psSegInfo->nSegmentSize -
                              (GIntBig)psImage->panBlockStart[iFullBlock];
        }
        if (nSignedRawBytes <= 0 || nSignedRawBytes > INT_MAX)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Invalid block size : " CPL_FRMT_GIB, nSignedRawBytes);
            return BLKREAD_FAIL;
        }

        nRawBytes = (size_t)nSignedRawBytes;
        pabyRawData = (GByte *)VSI_MALLOC_VERBOSE(nRawBytes);
        if (pabyRawData == NULL)
        {
            return BLKREAD_FAIL;
        }

        /* Read the codewords */
        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            VSIFReadL(pabyRawData, 1, nRawBytes, psImage->psFile->fp) !=
                nRawBytes)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d byte block from " CPL_FRMT_GUIB ".",
                     (int)nRawBytes, psImage->panBlockStart[iFullBlock]);
            CPLFree(pabyRawData);
            return BLKREAD_FAIL;
        }

        success =
            NITFUncompressARIDPCM(psImage, pabyRawData, (int)nRawBytes, pData);

        CPLFree(pabyRawData);

        if (success)
            return BLKREAD_OK;
        else
            return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Handle BILEVEL (C1) compression.                                */
    /* -------------------------------------------------------------------- */
    else if (EQUAL(psImage->szIC, "C1") || EQUAL(psImage->szIC, "M1"))
    {
#ifdef HAVE_TIFF
        GIntBig nSignedRawBytes;
        size_t nRawBytes;
        NITFSegmentInfo *psSegInfo;
        int success;
        GByte *pabyRawData;

        if (psImage->nBitsPerSample != 1)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Invalid bits per sample value (%d) for C1/M1 compression",
                     psImage->nBitsPerSample);
            return BLKREAD_FAIL;
        }

        if (iFullBlock < psImage->nBlocksPerRow * psImage->nBlocksPerColumn *
                                 psImage->nBands -
                             1)
        {
            nSignedRawBytes = (GIntBig)psImage->panBlockStart[iFullBlock + 1] -
                              (GIntBig)psImage->panBlockStart[iFullBlock];
        }
        else
        {
            psSegInfo = psImage->psFile->pasSegmentInfo + psImage->iSegment;
            nSignedRawBytes = (GIntBig)psSegInfo->nSegmentStart +
                              (GIntBig)psSegInfo->nSegmentSize -
                              (GIntBig)psImage->panBlockStart[iFullBlock];
        }
        if (nSignedRawBytes <= 0 || nSignedRawBytes > INT_MAX)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Invalid block size : " CPL_FRMT_GIB, nSignedRawBytes);
            return BLKREAD_FAIL;
        }

        nRawBytes = (size_t)nSignedRawBytes;
        pabyRawData = (GByte *)VSI_MALLOC_VERBOSE(nRawBytes);
        if (pabyRawData == NULL)
        {
            return BLKREAD_FAIL;
        }

        /* Read the codewords */
        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            VSIFReadL(pabyRawData, 1, nRawBytes, psImage->psFile->fp) !=
                nRawBytes)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d byte block from " CPL_FRMT_GUIB ".",
                     (int)nRawBytes, psImage->panBlockStart[iFullBlock]);
            CPLFree(pabyRawData);
            return BLKREAD_FAIL;
        }

        success =
            NITFUncompressBILEVEL(psImage, pabyRawData, (int)nRawBytes, pData);

        CPLFree(pabyRawData);

        if (success)
            return BLKREAD_OK;
        else
            return BLKREAD_FAIL;
#else
        CPLError(CE_Failure, CPLE_NotSupported,
                 "BILEVEL compression not supported because of lack of "
                 "TIFF support");
        return BLKREAD_FAIL;
#endif
    }

    /* -------------------------------------------------------------------- */
    /*      Report unsupported compression scheme(s).                       */
    /* -------------------------------------------------------------------- */
    else if (atoi(psImage->szIC + 1) > 0)
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Unsupported imagery compression format %s in NITF library.",
                 psImage->szIC);
        return BLKREAD_FAIL;
    }

    return BLKREAD_FAIL;
}

/************************************************************************/
/*                        NITFWriteImageBlock()                         */
/************************************************************************/

int NITFWriteImageBlock(NITFImage *psImage, int nBlockX, int nBlockY, int nBand,
                        void *pData)

{
    GUIntBig nWrkBufSize;
    int iBaseBlock = nBlockX + nBlockY * psImage->nBlocksPerRow;
    int iFullBlock = iBaseBlock + (nBand - 1) * psImage->nBlocksPerRow *
                                      psImage->nBlocksPerColumn;

    if (nBand == 0)
        return BLKREAD_FAIL;

    nWrkBufSize = psImage->nLineOffset * (psImage->nBlockHeight - 1) +
                  psImage->nPixelOffset * (psImage->nBlockWidth - 1) +
                  psImage->nWordSize;

    if (nWrkBufSize == 0)
        nWrkBufSize = ((GUIntBig)psImage->nBlockWidth * psImage->nBlockHeight *
                           psImage->nBitsPerSample +
                       7) /
                      8;

    /* -------------------------------------------------------------------- */
    /*      Can we do a direct read into our buffer?                        */
    /* -------------------------------------------------------------------- */
    if ((size_t)psImage->nWordSize == psImage->nPixelOffset &&
        (size_t)(psImage->nWordSize * psImage->nBlockWidth) ==
            psImage->nLineOffset &&
        psImage->szIC[0] != 'C' && psImage->szIC[0] != 'M')
    {
#ifdef CPL_LSB
        NITFSwapWords(psImage, pData,
                      psImage->nBlockWidth * psImage->nBlockHeight);
#endif

        if (VSIFSeekL(psImage->psFile->fp, psImage->panBlockStart[iFullBlock],
                      SEEK_SET) != 0 ||
            (GUIntBig)VSIFWriteL(pData, 1, (size_t)nWrkBufSize,
                                 psImage->psFile->fp) != nWrkBufSize)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to write " CPL_FRMT_GUIB
                     " byte block from " CPL_FRMT_GUIB ".",
                     nWrkBufSize, psImage->panBlockStart[iFullBlock]);
            return BLKREAD_FAIL;
        }
        else
        {
#ifdef CPL_LSB
            /* restore byte order to original */
            NITFSwapWords(psImage, pData,
                          psImage->nBlockWidth * psImage->nBlockHeight);
#endif

            return BLKREAD_OK;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Other forms not supported at this time.                         */
    /* -------------------------------------------------------------------- */
    CPLError(CE_Failure, CPLE_NotSupported,
             "Mapped, interleaved and compressed NITF forms not supported\n"
             "for writing at this time.");

    return BLKREAD_FAIL;
}

/************************************************************************/
/*                         NITFReadImageLine()                          */
/************************************************************************/

int NITFReadImageLine(NITFImage *psImage, int nLine, int nBand, void *pData)

{
    GUIntBig nLineOffsetInFile;
    size_t nLineSize;
    unsigned char *pabyLineBuf;

    if (nBand == 0)
        return BLKREAD_FAIL;

    if (psImage->nBlocksPerRow != 1 || psImage->nBlocksPerColumn != 1)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Scanline access not supported on tiled NITF files.");
        return BLKREAD_FAIL;
    }

    if (psImage->nBlockWidth < psImage->nCols)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "For scanline access, block width cannot be lesser than the "
                 "number of columns.");
        return BLKREAD_FAIL;
    }

    if (!EQUAL(psImage->szIC, "NC"))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Scanline access not supported on compressed NITF files.");
        return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Workout location and size of data in file.                      */
    /* -------------------------------------------------------------------- */
    nLineOffsetInFile = psImage->panBlockStart[0] +
                        psImage->nLineOffset * nLine +
                        psImage->nBandOffset * (nBand - 1);

    nLineSize = (size_t)psImage->nPixelOffset * (psImage->nBlockWidth - 1) +
                psImage->nWordSize;

    if (nLineSize == 0 || psImage->nWordSize * 8 != psImage->nBitsPerSample)
        nLineSize = (psImage->nBlockWidth * psImage->nBitsPerSample + 7) / 8;

    if (VSIFSeekL(psImage->psFile->fp, nLineOffsetInFile, SEEK_SET) != 0)
        return BLKREAD_FAIL;

    /* -------------------------------------------------------------------- */
    /*      Can we do a direct read into our buffer.                        */
    /* -------------------------------------------------------------------- */
    if ((psImage->nBitsPerSample % 8) != 0 ||
        ((size_t)psImage->nWordSize == psImage->nPixelOffset &&
         (size_t)(psImage->nWordSize * psImage->nBlockWidth) ==
             psImage->nLineOffset))
    {
        if (VSIFReadL(pData, 1, nLineSize, psImage->psFile->fp) != nLineSize)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Unable to read %d bytes for line %d.", (int)nLineSize,
                     nLine);
            return BLKREAD_FAIL;
        }

#ifdef CPL_LSB
        NITFSwapWords(psImage, pData, psImage->nBlockWidth);
#endif

        return BLKREAD_OK;
    }

    /* -------------------------------------------------------------------- */
    /*      Allocate a buffer for all the interleaved data, and read        */
    /*      it.                                                             */
    /* -------------------------------------------------------------------- */
    pabyLineBuf = (unsigned char *)VSI_MALLOC_VERBOSE(nLineSize);
    if (pabyLineBuf == NULL)
    {
        return BLKREAD_FAIL;
    }

    if (VSIFReadL(pabyLineBuf, 1, nLineSize, psImage->psFile->fp) != nLineSize)
    {
        CPLError(CE_Failure, CPLE_FileIO,
                 "Unable to read %d bytes for line %d.", (int)nLineSize, nLine);
        CPLFree(pabyLineBuf);
        return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Copy the desired data out of the interleaved buffer.            */
    /* -------------------------------------------------------------------- */
    {
        GByte *pabySrc, *pabyDst;
        int iPixel;

        pabySrc = pabyLineBuf;
        pabyDst = ((GByte *)pData);

        for (iPixel = 0; iPixel < psImage->nBlockWidth; iPixel++)
        {
            memcpy(pabyDst + iPixel * psImage->nWordSize,
                   pabySrc + iPixel * psImage->nPixelOffset,
                   psImage->nWordSize);
        }

#ifdef CPL_LSB
        NITFSwapWords(psImage, pabyDst, psImage->nBlockWidth);
#endif
    }

    CPLFree(pabyLineBuf);

    return BLKREAD_OK;
}

/************************************************************************/
/*                         NITFWriteImageLine()                         */
/************************************************************************/

int NITFWriteImageLine(NITFImage *psImage, int nLine, int nBand, void *pData)

{
    GUIntBig nLineOffsetInFile;
    size_t nLineSize;
    unsigned char *pabyLineBuf;

    if (nBand == 0)
        return BLKREAD_FAIL;

    if (psImage->nBlocksPerRow != 1 || psImage->nBlocksPerColumn != 1)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Scanline access not supported on tiled NITF files.");
        return BLKREAD_FAIL;
    }

    if (psImage->nBlockWidth < psImage->nCols)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "For scanline access, block width cannot be lesser than the "
                 "number of columns.");
        return BLKREAD_FAIL;
    }

    if (!EQUAL(psImage->szIC, "NC"))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Scanline access not supported on compressed NITF files.");
        return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Workout location and size of data in file.                      */
    /* -------------------------------------------------------------------- */
    nLineOffsetInFile = psImage->panBlockStart[0] +
                        psImage->nLineOffset * nLine +
                        psImage->nBandOffset * (nBand - 1);

    nLineSize = (size_t)psImage->nPixelOffset * (psImage->nBlockWidth - 1) +
                psImage->nWordSize;

    if (VSIFSeekL(psImage->psFile->fp, nLineOffsetInFile, SEEK_SET) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        return BLKREAD_FAIL;
    }

    /* -------------------------------------------------------------------- */
    /*      Can we do a direct write into our buffer.                       */
    /* -------------------------------------------------------------------- */
    if ((size_t)psImage->nWordSize == psImage->nPixelOffset &&
        (size_t)(psImage->nWordSize * psImage->nBlockWidth) ==
            psImage->nLineOffset)
    {
#ifdef CPL_LSB
        NITFSwapWords(psImage, pData, psImage->nBlockWidth);
#endif

        if (VSIFWriteL(pData, 1, nLineSize, psImage->psFile->fp) != nLineSize)
        {
            CPLError(CE_Failure, CPLE_FileIO, "I/O error");
            return BLKREAD_FAIL;
        }

#ifdef CPL_LSB
        NITFSwapWords(psImage, pData, psImage->nBlockWidth);
#endif

        return BLKREAD_OK;
    }

    /* -------------------------------------------------------------------- */
    /*      Allocate a buffer for all the interleaved data, and read        */
    /*      it.                                                             */
    /* -------------------------------------------------------------------- */
    pabyLineBuf = (unsigned char *)VSI_MALLOC_VERBOSE(nLineSize);
    if (pabyLineBuf == NULL)
    {
        return BLKREAD_FAIL;
    }

    if (VSIFReadL(pabyLineBuf, 1, nLineSize, psImage->psFile->fp) != nLineSize)
    {
        memset(pabyLineBuf, 0, nLineSize);
    }

    /* -------------------------------------------------------------------- */
    /*      Copy the desired data into the interleaved buffer.              */
    /* -------------------------------------------------------------------- */
    {
        GByte *pabySrc, *pabyDst;
        int iPixel;

        pabyDst = pabyLineBuf;
        pabySrc = ((GByte *)pData);

#ifdef CPL_LSB
        NITFSwapWords(psImage, pData, psImage->nBlockWidth);
#endif

        for (iPixel = 0; iPixel < psImage->nBlockWidth; iPixel++)
        {
            memcpy(pabyDst + iPixel * psImage->nPixelOffset,
                   pabySrc + iPixel * psImage->nWordSize, psImage->nWordSize);
        }

#ifdef CPL_LSB
        NITFSwapWords(psImage, pData, psImage->nBlockWidth);
#endif
    }

    /* -------------------------------------------------------------------- */
    /*      Write the results back out.                                     */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(psImage->psFile->fp, nLineOffsetInFile, SEEK_SET) != 0 ||
        VSIFWriteL(pabyLineBuf, 1, nLineSize, psImage->psFile->fp) != nLineSize)
    {
        CPLFree(pabyLineBuf);
        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
        return BLKREAD_FAIL;
    }
    CPLFree(pabyLineBuf);

    return BLKREAD_OK;
}

/************************************************************************/
/*                          NITFEncodeDMSLoc()                          */
/************************************************************************/

static void NITFEncodeDMSLoc(char *pszTarget, size_t nTargetLen, double dfValue,
                             const char *pszAxis)

{
    char chHemisphere;
    int nDegrees, nMinutes, nSeconds;

    if (EQUAL(pszAxis, "Lat"))
    {
        if (dfValue < 0.0)
            chHemisphere = 'S';
        else
            chHemisphere = 'N';
    }
    else
    {
        if (dfValue < 0.0)
            chHemisphere = 'W';
        else
            chHemisphere = 'E';
    }

    dfValue = fabs(dfValue);

    nDegrees = (int)dfValue;
    dfValue = (dfValue - nDegrees) * 60.0;

    nMinutes = (int)dfValue;
    dfValue = (dfValue - nMinutes) * 60.0;

    /* -------------------------------------------------------------------- */
    /*      Do careful rounding on seconds so that 59.9->60 is properly     */
    /*      rolled into minutes and degrees.                                */
    /* -------------------------------------------------------------------- */
    nSeconds = (int)(dfValue + 0.5);
    if (nSeconds == 60)
    {
        nSeconds = 0;
        nMinutes += 1;
        if (nMinutes == 60)
        {
            nMinutes = 0;
            nDegrees += 1;
        }
    }

    if (EQUAL(pszAxis, "Lat"))
        snprintf(pszTarget, nTargetLen, "%02d%02d%02d%c", nDegrees, nMinutes,
                 nSeconds, chHemisphere);
    else
        snprintf(pszTarget, nTargetLen, "%03d%02d%02d%c", nDegrees, nMinutes,
                 nSeconds, chHemisphere);
}

/************************************************************************/
/*                          NITFWriteIGEOLO()                           */
/************************************************************************/

/* Check that easting can be represented as a 6 character string */
#define CHECK_IGEOLO_UTM_X(name, x)                                            \
    if ((int)floor((x) + 0.5) <= -100000 || (int)floor((x) + 0.5) >= 1000000)  \
    {                                                                          \
        CPLError(CE_Failure, CPLE_AppDefined,                                  \
                 "Attempt to write UTM easting %s=%d which is outside of "     \
                 "valid range.",                                               \
                 name, (int)floor((x) + 0.5));                                 \
        return FALSE;                                                          \
    }

/* Check that northing can be represented as a 7 character string */
#define CHECK_IGEOLO_UTM_Y(name, y)                                            \
    if ((int)floor((y) + 0.5) <= -1000000 ||                                   \
        (int)floor((y) + 0.5) >= 10000000)                                     \
    {                                                                          \
        CPLError(CE_Failure, CPLE_AppDefined,                                  \
                 "Attempt to write UTM northing %s=%d which is outside of "    \
                 "valid range.",                                               \
                 name, (int)floor((y) + 0.5));                                 \
        return FALSE;                                                          \
    }

int NITFWriteIGEOLO(NITFImage *psImage, char chICORDS, int nZone, double dfULX,
                    double dfULY, double dfURX, double dfURY, double dfLRX,
                    double dfLRY, double dfLLX, double dfLLY)

{
    char szIGEOLO[61];

    /* -------------------------------------------------------------------- */
    /*      Do some checking.                                               */
    /* -------------------------------------------------------------------- */
    if (psImage->chICORDS == ' ')
    {
        CPLError(CE_Failure, CPLE_NotSupported,
                 "Apparently no space reserved for IGEOLO info in NITF file.\n"
                 "NITFWriteIGEOGLO() fails.");
        return FALSE;
    }

    if (EQUAL(psImage->psFile->szVersion, "NITF02.00"))
    {
        if (chICORDS == 'U')
        {
            // Could be done, but we don't need UTM for CADRG support
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Writing UTM MGRS coordinates for NITF02.00 is not "
                     "supported in this implementation.");
            return FALSE;
        }
        else if (chICORDS != 'G' && chICORDS != 'D')
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Invalid ICOORDS value (%c) for NITFWriteIGEOLO().",
                     chICORDS);
            return FALSE;
        }
    }
    else
    {
        if (chICORDS != 'G' && chICORDS != 'N' && chICORDS != 'S' &&
            chICORDS != 'D')
        {
            CPLError(CE_Failure, CPLE_NotSupported,
                     "Invalid ICOORDS value (%c) for NITFWriteIGEOLO().",
                     chICORDS);
            return FALSE;
        }
    }

    if (chICORDS == 'G' || chICORDS == 'D')
    {
        if (fabs(dfULX) <= 180 && fabs(dfLLX) <= 180 && dfURX > 180 &&
            dfLRX > 180)
        {
            dfURX -= 360;
            dfLRX -= 360;
        }
        else if (dfULX < -180 && dfLLX < -180 && fabs(dfURX) <= 180 &&
                 fabs(dfLRX) <= 180)
        {
            dfULX += 360;
            dfLLX += 360;
        }

        if (fabs(dfULX) > 180 || fabs(dfURX) > 180 || fabs(dfLRX) > 180 ||
            fabs(dfLLX) > 180 || fabs(dfULY) > 90 || fabs(dfURY) > 90 ||
            fabs(dfLRY) > 90 || fabs(dfLLY) > 90)
        {
            CPLError(
                CE_Failure, CPLE_AppDefined,
                "Attempt to write geographic bound outside of legal range.");
            return FALSE;
        }
    }

    /* -------------------------------------------------------------------- */
    /*      Format geographic coordinates in DMS                            */
    /* -------------------------------------------------------------------- */
    if (chICORDS == 'G')
    {
        NITFEncodeDMSLoc(szIGEOLO + 0, sizeof(szIGEOLO) - 0, dfULY, "Lat");
        NITFEncodeDMSLoc(szIGEOLO + 7, sizeof(szIGEOLO) - 7, dfULX, "Long");
        NITFEncodeDMSLoc(szIGEOLO + 15, sizeof(szIGEOLO) - 15, dfURY, "Lat");
        NITFEncodeDMSLoc(szIGEOLO + 22, sizeof(szIGEOLO) - 22, dfURX, "Long");
        NITFEncodeDMSLoc(szIGEOLO + 30, sizeof(szIGEOLO) - 30, dfLRY, "Lat");
        NITFEncodeDMSLoc(szIGEOLO + 37, sizeof(szIGEOLO) - 37, dfLRX, "Long");
        NITFEncodeDMSLoc(szIGEOLO + 45, sizeof(szIGEOLO) - 45, dfLLY, "Lat");
        NITFEncodeDMSLoc(szIGEOLO + 52, sizeof(szIGEOLO) - 52, dfLLX, "Long");
    }
    /* -------------------------------------------------------------------- */
    /*      Format geographic coordinates in decimal degrees                */
    /* -------------------------------------------------------------------- */
    else if (chICORDS == 'D')
    {
        CPLsnprintf(szIGEOLO + 0, sizeof(szIGEOLO), "%+#07.3f%+#08.3f", dfULY,
                    dfULX);
        CPLsnprintf(szIGEOLO + 15, sizeof(szIGEOLO) - 15, "%+#07.3f%+#08.3f",
                    dfURY, dfURX);
        CPLsnprintf(szIGEOLO + 30, sizeof(szIGEOLO) - 30, "%+#07.3f%+#08.3f",
                    dfLRY, dfLRX);
        CPLsnprintf(szIGEOLO + 45, sizeof(szIGEOLO) - 45, "%+#07.3f%+#08.3f",
                    dfLLY, dfLLX);
    }

    /* -------------------------------------------------------------------- */
    /*      Format UTM coordinates.                                         */
    /* -------------------------------------------------------------------- */
    else if (chICORDS == 'N' || chICORDS == 'S')
    {
        CHECK_IGEOLO_UTM_X("dfULX", dfULX);
        CHECK_IGEOLO_UTM_Y("dfULY", dfULY);
        CHECK_IGEOLO_UTM_X("dfURX", dfURX);
        CHECK_IGEOLO_UTM_Y("dfURY", dfURY);
        CHECK_IGEOLO_UTM_X("dfLRX", dfLRX);
        CHECK_IGEOLO_UTM_Y("dfLRY", dfLRY);
        CHECK_IGEOLO_UTM_X("dfLLX", dfLLX);
        CHECK_IGEOLO_UTM_Y("dfLLY", dfLLY);
        CPLsnprintf(szIGEOLO + 0, sizeof(szIGEOLO), "%02d%06d%07d", nZone,
                    (int)floor(dfULX + 0.5), (int)floor(dfULY + 0.5));
        CPLsnprintf(szIGEOLO + 15, sizeof(szIGEOLO) - 15, "%02d%06d%07d", nZone,
                    (int)floor(dfURX + 0.5), (int)floor(dfURY + 0.5));
        CPLsnprintf(szIGEOLO + 30, sizeof(szIGEOLO) - 30, "%02d%06d%07d", nZone,
                    (int)floor(dfLRX + 0.5), (int)floor(dfLRY + 0.5));
        CPLsnprintf(szIGEOLO + 45, sizeof(szIGEOLO) - 45, "%02d%06d%07d", nZone,
                    (int)floor(dfLLX + 0.5), (int)floor(dfLLY + 0.5));
    }

    /* -------------------------------------------------------------------- */
    /*      Write IGEOLO data to disk.                                      */
    /* -------------------------------------------------------------------- */
    int nOffset = 372;
    if (EQUAL(psImage->psFile->szVersion, "NITF02.00"))
    {
        // Read ISDWNG field;
        char szISDWNG[6 + 1] = {0};
        if (VSIFSeekL(psImage->psFile->fp,
                      psImage->psFile->pasSegmentInfo[psImage->iSegment]
                              .nSegmentHeaderStart +
                          284,
                      SEEK_SET) != 0 ||
            VSIFReadL(szISDWNG, 1, 6, psImage->psFile->fp) != 6)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "I/O Error writing IGEOLO segment.\n%s",
                     VSIStrerror(errno));
            return FALSE;
        }
        if (EQUAL(szISDWNG, "999998"))
            nOffset += 40;
    }
    if (VSIFSeekL(psImage->psFile->fp,
                  psImage->psFile->pasSegmentInfo[psImage->iSegment]
                          .nSegmentHeaderStart +
                      nOffset,
                  SEEK_SET) == 0 &&
        VSIFWriteL(szIGEOLO, 1, 60, psImage->psFile->fp) == 60)
    {
        return TRUE;
    }
    else
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "I/O Error writing IGEOLO segment.\n%s", VSIStrerror(errno));
        return FALSE;
    }
}

/************************************************************************/
/*                            NITFWriteLUT()                            */
/************************************************************************/

int NITFWriteLUT(NITFImage *psImage, int nBand, int nColors,
                 unsigned char *pabyLUT)

{
    NITFBandInfo *psBandInfo;
    int bSuccess = TRUE;

    if (nBand < 1 || nBand > psImage->nBands)
        return FALSE;

    psBandInfo = psImage->pasBandInfo + (nBand - 1);

    if (nColors > psBandInfo->nSignificantLUTEntries)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Unable to write all %d LUT entries, only able to write %d.",
                 nColors, psBandInfo->nSignificantLUTEntries);
        nColors = psBandInfo->nSignificantLUTEntries;
        bSuccess = FALSE;
    }

    bSuccess &=
        VSIFSeekL(psImage->psFile->fp, psBandInfo->nLUTLocation, SEEK_SET) == 0;
    bSuccess &=
        (int)VSIFWriteL(pabyLUT, 1, nColors, psImage->psFile->fp) == nColors;
    bSuccess &=
        VSIFSeekL(psImage->psFile->fp,
                  psBandInfo->nLUTLocation + psBandInfo->nSignificantLUTEntries,
                  SEEK_SET) == 0;
    bSuccess &= (int)VSIFWriteL(pabyLUT + 256, 1, nColors,
                                psImage->psFile->fp) == nColors;
    bSuccess &= VSIFSeekL(psImage->psFile->fp,
                          psBandInfo->nLUTLocation +
                              2 * psBandInfo->nSignificantLUTEntries,
                          SEEK_SET) == 0;
    bSuccess &= (int)VSIFWriteL(pabyLUT + 512, 1, nColors,
                                psImage->psFile->fp) == nColors;

    return bSuccess;
}

/************************************************************************/
/*                           NITFTrimWhite()                            */
/*                                                                      */
/*      Trim any white space off the white of the passed string in      */
/*      place.                                                          */
/************************************************************************/

char *NITFTrimWhite(char *pszTarget)

{
    int i;

    i = (int)strlen(pszTarget) - 1;
    while (i >= 0 && pszTarget[i] == ' ')
        pszTarget[i--] = '\0';

    return pszTarget;
}

/************************************************************************/
/*                           NITFSwapWords()                            */
/************************************************************************/

#ifdef CPL_LSB

static void NITFSwapWordsInternal(void *pData, int nWordSize, int nWordCount,
                                  int nWordSkip)

{
    int i;
    GByte *pabyData = (GByte *)pData;

    switch (nWordSize)
    {
        case 1:
            break;

        case 2:
            for (i = 0; i < nWordCount; i++)
            {
                GByte byTemp;

                byTemp = pabyData[0];
                pabyData[0] = pabyData[1];
                pabyData[1] = byTemp;

                pabyData += nWordSkip;
            }
            break;

        case 4:
            for (i = 0; i < nWordCount; i++)
            {
                GByte byTemp;

                byTemp = pabyData[0];
                pabyData[0] = pabyData[3];
                pabyData[3] = byTemp;

                byTemp = pabyData[1];
                pabyData[1] = pabyData[2];
                pabyData[2] = byTemp;

                pabyData += nWordSkip;
            }
            break;

        case 8:
            for (i = 0; i < nWordCount; i++)
            {
                GByte byTemp;

                byTemp = pabyData[0];
                pabyData[0] = pabyData[7];
                pabyData[7] = byTemp;

                byTemp = pabyData[1];
                pabyData[1] = pabyData[6];
                pabyData[6] = byTemp;

                byTemp = pabyData[2];
                pabyData[2] = pabyData[5];
                pabyData[5] = byTemp;

                byTemp = pabyData[3];
                pabyData[3] = pabyData[4];
                pabyData[4] = byTemp;

                pabyData += nWordSkip;
            }
            break;

        default:
            break;
    }
}

/* Swap real or complex types */
static void NITFSwapWords(NITFImage *psImage, void *pData, int nWordCount)

{
    if (psImage->nWordSize * 8 != psImage->nBitsPerSample)
    {
        // FIXME ?
        return;
    }

    if (EQUAL(psImage->szPVType, "C"))
    {
        /* According to
         * http://jitc.fhu.disa.mil/nitf/tag_reg/imagesubheader/pvtype.html */
        /* "C values shall be represented with the Real and Imaginary parts,
         * each represented */
        /* in IEEE 32 or 64-bit floating point representation (IEEE 754) and
         * appearing in */
        /* adjacent four or eight-byte blocks, first Real, then Imaginary" */
        NITFSwapWordsInternal(pData, psImage->nWordSize / 2, 2 * nWordCount,
                              psImage->nWordSize / 2);
    }
    else
    {
        NITFSwapWordsInternal(pData, psImage->nWordSize, nWordCount,
                              psImage->nWordSize);
    }
}

#endif /* def CPL_LSB */

/************************************************************************/
/*                           NITFReadCSEXRA()                           */
/*                                                                      */
/*      Read a CSEXRA TRE and return contents as metadata strings.      */
/************************************************************************/

char **NITFReadCSEXRA(NITFImage *psImage)

{
    return NITFGenericMetadataRead(NULL, NULL, psImage, "CSEXRA");
}

/************************************************************************/
/*                           NITFReadPIAIMC()                           */
/*                                                                      */
/*      Read a PIAIMC TRE and return contents as metadata strings.      */
/************************************************************************/

char **NITFReadPIAIMC(NITFImage *psImage)

{
    return NITFGenericMetadataRead(NULL, NULL, psImage, "PIAIMC");
}

/************************************************************************/
/*                    NITFFormatRPC00BCoefficient()                     */
/*                                                                      */
/*      Format coefficients like +X.XXXXXXE+X (12 bytes)                */
/************************************************************************/
static int NITFFormatRPC00BCoefficient(char *pszBuffer, double dfVal,
                                       int *pbPrecisionLoss)
{
    // We need 12 bytes + 2=3-1 bytes for MSVC potentially outputting exponents
    // with 3 digits + 1 terminating byte
    char szTemp[12 + 2 + 1];
#if defined(DEBUG) || defined(_WIN32)
    int nLen;
#endif

    if (fabs(dfVal) > 9.999999e9)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "Coefficient out of range: %g",
                 dfVal);
        return FALSE;
    }

    CPLsnprintf(szTemp, sizeof(szTemp), "%+.6E", dfVal);
#if defined(DEBUG) || defined(_WIN32)
    nLen = (int)strlen(szTemp);
    CPL_IGNORE_RET_VAL_INT(nLen);
#endif
    CPLAssert(szTemp[9] == 'E');
#ifdef _WIN32
    if (nLen == 14)  // Old MSVC versions: 3 digits for the exponent
    {
        if (szTemp[11] != DIGIT_ZERO || szTemp[12] != DIGIT_ZERO)
        {
            CPLError(CE_Warning, CPLE_AppDefined, "%g rounded to 0", dfVal);
            snprintf(pszBuffer, 12 + 1, "%s", "+0.000000E+0");
            if (pbPrecisionLoss)
                *pbPrecisionLoss = TRUE;
            return TRUE;
        }
        szTemp[11] = szTemp[13];
    }
    else  // behavior of the standard: 2 digits for the exponent
#endif
    {
        CPLAssert(nLen == 13);
        if (szTemp[11] != DIGIT_ZERO)
        {
            CPLError(CE_Warning, CPLE_AppDefined, "%g rounded to 0", dfVal);
            snprintf(pszBuffer, 12 + 1, "%s", "+0.000000E+0");
            if (pbPrecisionLoss)
                *pbPrecisionLoss = TRUE;
            return TRUE;
        }
        szTemp[11] = szTemp[12];
    }
    szTemp[12] = '\0';
    memcpy(pszBuffer, szTemp, strlen(szTemp) + 1);
    return TRUE;
}

/************************************************************************/
/*                   NITFFormatRPC00BFromMetadata()                     */
/*                                                                      */
/*      Format the content of a RPC00B TRE from RPC metadata            */
/************************************************************************/

char *NITFFormatRPC00BFromMetadata(CSLConstList papszRPC, int *pbPrecisionLoss)
{
    GDALRPCInfoV2 sRPC;
    char *pszRPC00B;
    double dfErrBIAS;
    double dfErrRAND;
    int nOffset;
    int nLength;
    int nRounded;
    int i;
    char szTemp[24];

    if (pbPrecisionLoss)
        *pbPrecisionLoss = FALSE;

    if (!GDALExtractRPCInfoV2(papszRPC, &sRPC))
        return NULL;

    pszRPC00B = (char *)CPLMalloc(1041 + 1);
    pszRPC00B[0] = '1'; /* success flag */
    nOffset = 1;

    dfErrBIAS = sRPC.dfERR_BIAS;
    if (dfErrBIAS == -1.0)  // value by default to indicate unknown
    {
        dfErrBIAS = 0.0;
    }
    else if (dfErrBIAS < 0)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Correcting ERR_BIAS from %f to 0", dfErrBIAS);
    }
    else if (dfErrBIAS > 9999.99)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "ERR_BIAS out of range. Clamping to 9999.99");
        dfErrBIAS = 9999.99;
    }
    nLength = 7;
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%07.2f", dfErrBIAS);
    nOffset += nLength;

    dfErrRAND = sRPC.dfERR_RAND;
    if (dfErrRAND == -1.0)  // value by default to indicate unknown
    {
        dfErrRAND = 0.0;
    }
    else if (dfErrRAND < 0)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Correcting ERR_RAND from %f to 0", dfErrRAND);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    else if (dfErrRAND > 9999.99)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "ERR_RAND out of range. Clamping to 9999.99");
        dfErrRAND = 9999.99;
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    nLength = 7;
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%07.2f", dfErrRAND);
    nOffset += nLength;

    nLength = 6;
    if (sRPC.dfLINE_OFF < 0 || sRPC.dfLINE_OFF >= 1e6)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LINE_OFF out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfLINE_OFF + 0.5);
    if (fabs(nRounded - sRPC.dfLINE_OFF) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LINE_OFF was rounded from %f to %d", sRPC.dfLINE_OFF,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%06d", nRounded);
    nOffset += nLength;

    nLength = 5;
    if (sRPC.dfSAMP_OFF < 0 || sRPC.dfSAMP_OFF >= 1e5)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "SAMP_OFF out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfSAMP_OFF + 0.5);
    if (fabs(nRounded - sRPC.dfSAMP_OFF) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "SAMP_OFF was rounded from %f to %d", sRPC.dfSAMP_OFF,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%05d", nRounded);
    nOffset += nLength;

    nLength = 8;
    if (fabs(sRPC.dfLAT_OFF) > 90)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LAT_OFF out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+08.4f", sRPC.dfLAT_OFF);
    if (fabs(sRPC.dfLAT_OFF -
             CPLAtof(NITFGetField(szTemp, pszRPC00B, nOffset, nLength))) > 1e-8)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LAT_OFF was rounded from %f to %s", sRPC.dfLAT_OFF, szTemp);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    nOffset += nLength;

    nLength = 9;
    if (fabs(sRPC.dfLONG_OFF) > 180)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LONG_OFF out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+09.4f", sRPC.dfLONG_OFF);
    if (fabs(sRPC.dfLONG_OFF -
             CPLAtof(NITFGetField(szTemp, pszRPC00B, nOffset, nLength))) > 1e-8)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LONG_OFF was rounded from %f to %s", sRPC.dfLONG_OFF, szTemp);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    nOffset += nLength;

    nLength = 5;
    if (fabs(sRPC.dfHEIGHT_OFF) > 9999)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "HEIGHT_OFF out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfHEIGHT_OFF + 0.5);
    if (fabs(nRounded - sRPC.dfHEIGHT_OFF) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "HEIGHT_OFF was rounded from %f to %d", sRPC.dfHEIGHT_OFF,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+05d", nRounded);
    nOffset += nLength;

    nLength = 6;
    if (sRPC.dfLINE_SCALE < 1 || sRPC.dfLINE_SCALE >= 999999)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LINE_SCALE out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfLINE_SCALE + 0.5);
    if (fabs(nRounded - sRPC.dfLINE_SCALE) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LINE_SCALE was rounded from %f to %d", sRPC.dfLINE_SCALE,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%06d", nRounded);
    nOffset += nLength;

    nLength = 5;
    if (sRPC.dfSAMP_SCALE < 1 || sRPC.dfSAMP_SCALE >= 99999)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "SAMP_SCALE out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfSAMP_SCALE + 0.5);
    if (fabs(nRounded - sRPC.dfSAMP_SCALE) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "SAMP_SCALE was rounded from %f to %d", sRPC.dfSAMP_SCALE,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%05d", nRounded);
    nOffset += nLength;

    nLength = 8;
    if (fabs(sRPC.dfLAT_SCALE) > 90)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LAT_SCALE out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+08.4f", sRPC.dfLAT_SCALE);
    if (fabs(sRPC.dfLAT_SCALE -
             CPLAtof(NITFGetField(szTemp, pszRPC00B, nOffset, nLength))) > 1e-8)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LAT_SCALE was rounded from %f to %s", sRPC.dfLAT_SCALE,
                 szTemp);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    nOffset += nLength;

    nLength = 9;
    if (fabs(sRPC.dfLONG_SCALE) > 180)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "LONG_SCALE out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+09.4f", sRPC.dfLONG_SCALE);
    if (fabs(sRPC.dfLONG_SCALE -
             CPLAtof(NITFGetField(szTemp, pszRPC00B, nOffset, nLength))) > 1e-8)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "LONG_SCALE was rounded from %f to %s", sRPC.dfLONG_SCALE,
                 szTemp);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    nOffset += nLength;

    nLength = 5;
    if (fabs(sRPC.dfHEIGHT_SCALE) > 9999)
    {
        CPLError(CE_Failure, CPLE_AppDefined, "HEIGHT_SCALE out of range.");
        CPLFree(pszRPC00B);
        return NULL;
    }
    nRounded = (int)floor(sRPC.dfHEIGHT_SCALE + 0.5);
    if (fabs(nRounded - sRPC.dfHEIGHT_SCALE) > 1e-2)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "HEIGHT_SCALE was rounded from %f to %d", sRPC.dfHEIGHT_SCALE,
                 nRounded);
        if (pbPrecisionLoss)
            *pbPrecisionLoss = TRUE;
    }
    CPLsnprintf(pszRPC00B + nOffset, nLength + 1, "%+05d", nRounded);
    nOffset += nLength;

    /* -------------------------------------------------------------------- */
    /*      Write coefficients.                                             */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < 20; i++)
    {
        if (!NITFFormatRPC00BCoefficient(pszRPC00B + nOffset,
                                         sRPC.adfLINE_NUM_COEFF[i],
                                         pbPrecisionLoss))
        {
            CPLFree(pszRPC00B);
            return NULL;
        }
        nOffset += 12;
    }
    for (i = 0; i < 20; i++)
    {
        if (!NITFFormatRPC00BCoefficient(pszRPC00B + nOffset,
                                         sRPC.adfLINE_DEN_COEFF[i],
                                         pbPrecisionLoss))
        {
            CPLFree(pszRPC00B);
            return NULL;
        }
        nOffset += 12;
    }
    for (i = 0; i < 20; i++)
    {
        if (!NITFFormatRPC00BCoefficient(pszRPC00B + nOffset,
                                         sRPC.adfSAMP_NUM_COEFF[i],
                                         pbPrecisionLoss))
        {
            CPLFree(pszRPC00B);
            return NULL;
        }
        nOffset += 12;
    }
    for (i = 0; i < 20; i++)
    {
        if (!NITFFormatRPC00BCoefficient(pszRPC00B + nOffset,
                                         sRPC.adfSAMP_DEN_COEFF[i],
                                         pbPrecisionLoss))
        {
            CPLFree(pszRPC00B);
            return NULL;
        }
        nOffset += 12;
    }

    CPLAssert(nOffset == 1041);
    pszRPC00B[nOffset] = '\0';
    CPLAssert(strlen(pszRPC00B) == 1041);
    CPLAssert(strchr(pszRPC00B, ' ') == NULL);

    return pszRPC00B;
}

/************************************************************************/
/*                           NITFReadRPC00B()                           */
/*                                                                      */
/*      Read an RPC00A or RPC00B structure if the TRE is available.     */
/*      RPC00A is remapped into RPC00B organization.                    */
/************************************************************************/

int NITFReadRPC00B(NITFImage *psImage, NITFRPC00BInfo *psRPC)

{
    const char *pachTRE;
    int bIsRPC00A = FALSE;
    int nTRESize;

    psRPC->SUCCESS = 0;

    /* -------------------------------------------------------------------- */
    /*      Do we have the TRE?                                             */
    /* -------------------------------------------------------------------- */
    pachTRE =
        NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "RPC00B", &nTRESize);

    if (pachTRE == NULL)
    {
        pachTRE = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "RPC00A",
                              &nTRESize);
        if (pachTRE)
            bIsRPC00A = TRUE;
    }

    if (pachTRE == NULL)
    {
        /* No RPC00 tag. Check to see if we have the IMASDA and IMRFCA
           tags (DPPDB data) before returning. */
        return NITFReadIMRFCA(psImage, psRPC);
    }

    if (nTRESize < 801 + 19 * 12 + 12)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read RPC00A/RPC00B TRE. Not enough bytes");
        return FALSE;
    }

    return NITFDeserializeRPC00B((const GByte *)pachTRE, psRPC, bIsRPC00A);
}

/************************************************************************/
/*                       NITFDeserializeRPC00B()                        */
/************************************************************************/

int NITFDeserializeRPC00B(const GByte *pabyTRE, NITFRPC00BInfo *psRPC,
                          int bIsRPC00A)
{
    const char *pachTRE = (const char *)pabyTRE;
    char szTemp[100];
    int i;
    static const int anRPC00AMap[] = /* See ticket #2040 */
        {0, 1, 2, 3, 4, 5, 6, 10, 7, 8, 9, 11, 14, 17, 12, 15, 18, 13, 16, 19};

    /* -------------------------------------------------------------------- */
    /*      Parse out field values.                                         */
    /* -------------------------------------------------------------------- */
    psRPC->SUCCESS = atoi(NITFGetField(szTemp, pachTRE, 0, 1));

    if (!psRPC->SUCCESS)
    {
        CPLError(CE_Warning, CPLE_AppDefined, "RPC Extension not Populated!");
    }

    psRPC->ERR_BIAS = CPLAtof(NITFGetField(szTemp, pachTRE, 1, 7));
    psRPC->ERR_RAND = CPLAtof(NITFGetField(szTemp, pachTRE, 8, 7));

    psRPC->LINE_OFF = CPLAtof(NITFGetField(szTemp, pachTRE, 15, 6));
    psRPC->SAMP_OFF = CPLAtof(NITFGetField(szTemp, pachTRE, 21, 5));
    psRPC->LAT_OFF = CPLAtof(NITFGetField(szTemp, pachTRE, 26, 8));
    psRPC->LONG_OFF = CPLAtof(NITFGetField(szTemp, pachTRE, 34, 9));
    psRPC->HEIGHT_OFF = CPLAtof(NITFGetField(szTemp, pachTRE, 43, 5));

    psRPC->LINE_SCALE = CPLAtof(NITFGetField(szTemp, pachTRE, 48, 6));
    psRPC->SAMP_SCALE = CPLAtof(NITFGetField(szTemp, pachTRE, 54, 5));
    psRPC->LAT_SCALE = CPLAtof(NITFGetField(szTemp, pachTRE, 59, 8));
    psRPC->LONG_SCALE = CPLAtof(NITFGetField(szTemp, pachTRE, 67, 9));
    psRPC->HEIGHT_SCALE = CPLAtof(NITFGetField(szTemp, pachTRE, 76, 5));

    /* -------------------------------------------------------------------- */
    /*      Parse out coefficients.                                         */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < 20; i++)
    {
        int iSrcCoef = i;

        if (bIsRPC00A)
            iSrcCoef = anRPC00AMap[i];

        psRPC->LINE_NUM_COEFF[i] =
            CPLAtof(NITFGetField(szTemp, pachTRE, 81 + iSrcCoef * 12, 12));
        psRPC->LINE_DEN_COEFF[i] =
            CPLAtof(NITFGetField(szTemp, pachTRE, 321 + iSrcCoef * 12, 12));
        psRPC->SAMP_NUM_COEFF[i] =
            CPLAtof(NITFGetField(szTemp, pachTRE, 561 + iSrcCoef * 12, 12));
        psRPC->SAMP_DEN_COEFF[i] =
            CPLAtof(NITFGetField(szTemp, pachTRE, 801 + iSrcCoef * 12, 12));
    }

    return TRUE;
}

/************************************************************************/
/*                           NITFReadICHIPB()                           */
/*                                                                      */
/*      Read an ICHIPB structure if the TRE is available.               */
/************************************************************************/

int NITFReadICHIPB(NITFImage *psImage, NITFICHIPBInfo *psICHIP)

{
    const char *pachTRE;
    char szTemp[32];
    int nTRESize;

    /* -------------------------------------------------------------------- */
    /*      Do we have the TRE?                                             */
    /* -------------------------------------------------------------------- */
    pachTRE =
        NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "ICHIPB", &nTRESize);

    if (pachTRE == NULL)
    {
        pachTRE = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "ICHIPA",
                              &nTRESize);
    }

    if (pachTRE == NULL)
    {
        return FALSE;
    }

    if (nTRESize < 2)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read ICHIPA/ICHIPB TRE. Not enough bytes");
        return FALSE;
    }
    /* -------------------------------------------------------------------- */
    /*      Parse out field values.                                         */
    /* -------------------------------------------------------------------- */
    psICHIP->XFRM_FLAG = atoi(NITFGetField(szTemp, pachTRE, 0, 2));

    if (psICHIP->XFRM_FLAG == 0)
    {
        if (nTRESize < 216 + 8)
        {
            CPLError(CE_Failure, CPLE_AppDefined,
                     "Cannot read ICHIPA/ICHIPB TRE. Not enough bytes");
            return FALSE;
        }

        psICHIP->SCALE_FACTOR = CPLAtof(NITFGetField(szTemp, pachTRE, 2, 10));
        psICHIP->ANAMORPH_CORR = atoi(NITFGetField(szTemp, pachTRE, 12, 2));
        psICHIP->SCANBLK_NUM = atoi(NITFGetField(szTemp, pachTRE, 14, 2));

        psICHIP->OP_ROW_11 = CPLAtof(NITFGetField(szTemp, pachTRE, 16, 12));
        psICHIP->OP_COL_11 = CPLAtof(NITFGetField(szTemp, pachTRE, 28, 12));

        psICHIP->OP_ROW_12 = CPLAtof(NITFGetField(szTemp, pachTRE, 40, 12));
        psICHIP->OP_COL_12 = CPLAtof(NITFGetField(szTemp, pachTRE, 52, 12));

        psICHIP->OP_ROW_21 = CPLAtof(NITFGetField(szTemp, pachTRE, 64, 12));
        psICHIP->OP_COL_21 = CPLAtof(NITFGetField(szTemp, pachTRE, 76, 12));

        psICHIP->OP_ROW_22 = CPLAtof(NITFGetField(szTemp, pachTRE, 88, 12));
        psICHIP->OP_COL_22 = CPLAtof(NITFGetField(szTemp, pachTRE, 100, 12));

        psICHIP->FI_ROW_11 = CPLAtof(NITFGetField(szTemp, pachTRE, 112, 12));
        psICHIP->FI_COL_11 = CPLAtof(NITFGetField(szTemp, pachTRE, 124, 12));

        psICHIP->FI_ROW_12 = CPLAtof(NITFGetField(szTemp, pachTRE, 136, 12));
        psICHIP->FI_COL_12 = CPLAtof(NITFGetField(szTemp, pachTRE, 148, 12));

        psICHIP->FI_ROW_21 = CPLAtof(NITFGetField(szTemp, pachTRE, 160, 12));
        psICHIP->FI_COL_21 = CPLAtof(NITFGetField(szTemp, pachTRE, 172, 12));

        psICHIP->FI_ROW_22 = CPLAtof(NITFGetField(szTemp, pachTRE, 184, 12));
        psICHIP->FI_COL_22 = CPLAtof(NITFGetField(szTemp, pachTRE, 196, 12));

        psICHIP->FI_ROW = atoi(NITFGetField(szTemp, pachTRE, 208, 8));
        psICHIP->FI_COL = atoi(NITFGetField(szTemp, pachTRE, 216, 8));
    }
    else
    {
        fprintf(stdout, "Chip is already de-warped?\n");
    }

    return TRUE;
}

/************************************************************************/
/*                           NITFReadUSE00A()                           */
/*                                                                      */
/*      Read a USE00A TRE and return contents as metadata strings.      */
/************************************************************************/

char **NITFReadUSE00A(NITFImage *psImage)

{
    return NITFGenericMetadataRead(NULL, NULL, psImage, "USE00A");
}

/************************************************************************/
/*                           NITFReadBLOCKA()                           */
/*                                                                      */
/*      Read a BLOCKA SDE and return contents as metadata strings.      */
/************************************************************************/

char **NITFReadBLOCKA(NITFImage *psImage)

{
    const char *pachTRE;
    int nTRESize;
    char **papszMD = NULL;
    int nBlockaCount = 0;
    char szTemp[128];

    while (TRUE)
    {
        /* --------------------------------------------------------------------
         */
        /*      Do we have the TRE? */
        /* --------------------------------------------------------------------
         */
        pachTRE = NITFFindTREByIndex(psImage->pachTRE, psImage->nTREBytes,
                                     "BLOCKA", nBlockaCount, &nTRESize);

        if (pachTRE == NULL)
            break;

        if (nTRESize != 123)
        {
            CPLError(CE_Warning, CPLE_AppDefined,
                     "BLOCKA TRE wrong size, ignoring.");
            break;
        }

        nBlockaCount++;

        /* --------------------------------------------------------------------
         */
        /*      Parse out field values. */
        /* --------------------------------------------------------------------
         */
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_BLOCK_INSTANCE_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 0, 2, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_N_GRAY_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 2, 5, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_L_LINES_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 7, 5, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_LAYOVER_ANGLE_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 12, 3, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_SHADOW_ANGLE_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 15, 3, szTemp);
        /* reserved: 16 */
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_FRLC_LOC_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 34, 21, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_LRLC_LOC_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 55, 21, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_LRFC_LOC_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 76, 21, szTemp);
        snprintf(szTemp, sizeof(szTemp), "NITF_BLOCKA_FRFC_LOC_%02d",
                 nBlockaCount);
        NITFExtractMetadata(&papszMD, pachTRE, 97, 21, szTemp);
        /* reserved: 5 -> 97 + 21 + 5 = 123 -> OK */
    }

    if (nBlockaCount > 0)
    {
        snprintf(szTemp, sizeof(szTemp), "%02d", nBlockaCount);
        papszMD = CSLSetNameValue(papszMD, "NITF_BLOCKA_BLOCK_COUNT", szTemp);
    }

    return papszMD;
}

/************************************************************************/
/*                           NITFGetGCP()                               */
/*                                                                      */
/* Reads a geographical coordinate (lat, long) from the provided        */
/* buffer.                                                              */
/************************************************************************/

void NITFGetGCP(const char *pachCoord, double *pdfXYs, int iCoord)
{
    char szTemp[128];

    // offset to selected coordinate.
    pdfXYs += 2 * iCoord;

    if (pachCoord[0] == 'N' || pachCoord[0] == 'n' || pachCoord[0] == 'S' ||
        pachCoord[0] == 's')
    {
        /* ------------------------------------------------------------ */
        /*                             0....+....1....+....2            */
        /* Coordinates are in the form Xddmmss.ssYdddmmss.ss:           */
        /* The format Xddmmss.cc represents degrees (00 to 89), minutes */
        /* (00 to 59), seconds (00 to 59), and hundredths of seconds    */
        /* (00 to 99) of latitude, with X = N for north or S for south, */
        /* and Ydddmmss.cc represents degrees (000 to 179), minutes     */
        /* (00 to 59), seconds (00 to 59), and hundredths of seconds    */
        /* (00 to 99) of longitude, with Y = E for east or W for west.  */
        /* ------------------------------------------------------------ */

        // It is critical to do the fetching of the D, M, S components
        // in 3 separate statements, otherwise if NITFGetField() is
        // defined in this compilation unit, the MSVC optimizer will
        // generate bad code, due to szTemp being overwritten before
        // being evaluated by CPLAtof() !
        pdfXYs[1] = CPLAtof(NITFGetField(szTemp, pachCoord, 1, 2));
        pdfXYs[1] += CPLAtof(NITFGetField(szTemp, pachCoord, 3, 2)) / 60.0;
        pdfXYs[1] += CPLAtof(NITFGetField(szTemp, pachCoord, 5, 5)) / 3600.0;

        if (pachCoord[0] == 's' || pachCoord[0] == 'S')
            pdfXYs[1] *= -1;

        // It is critical to do the fetching of the D, M, S components
        // in 3 separate statements, otherwise if NITFGetField() is
        // defined in this compilation unit, the MSVC optimizer will
        // generate bad code, due to szTemp being overwritten before
        // being evaluated by CPLAtof() !
        pdfXYs[0] = CPLAtof(NITFGetField(szTemp, pachCoord, 11, 3));
        pdfXYs[0] += CPLAtof(NITFGetField(szTemp, pachCoord, 14, 2)) / 60.0;
        pdfXYs[0] += CPLAtof(NITFGetField(szTemp, pachCoord, 16, 5)) / 3600.0;

        if (pachCoord[10] == 'w' || pachCoord[10] == 'W')
            pdfXYs[0] *= -1;
    }
    else
    {
        /* ------------------------------------------------------------ */
        /*                             0....+....1....+....2            */
        /* Coordinates are in the form ±dd.dddddd±ddd.dddddd:           */
        /* The format ±dd.dddddd indicates degrees of latitude (north   */
        /* is positive), and ±ddd.dddddd represents degrees of          */
        /* longitude (east is positive).                                */
        /* ------------------------------------------------------------ */

        pdfXYs[1] = CPLAtof(NITFGetField(szTemp, pachCoord, 0, 10));
        pdfXYs[0] = CPLAtof(NITFGetField(szTemp, pachCoord, 10, 11));
    }
}

/************************************************************************/
/*                           NITFReadBLOCKA_GCPs()                      */
/*                                                                      */
/* The BLOCKA repeat earth coordinates image corner locations described */
/* by IGEOLO in the NITF image subheader, but provide higher precision. */
/************************************************************************/

int NITFReadBLOCKA_GCPs(NITFImage *psImage)
{
    const char *pachTRE;
    int nTRESize;
    int nBlockaLines;
    char szTemp[128];

    /* -------------------------------------------------------------------- */
    /*      Do we have the TRE?                                             */
    /* -------------------------------------------------------------------- */
    pachTRE =
        NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "BLOCKA", &nTRESize);

    if (pachTRE == NULL)
        return FALSE;

    if (nTRESize != 123)
    {
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Parse out field values.                                         */
    /* -------------------------------------------------------------------- */

    /* ---------------------------------------------------------------- */
    /* Make sure the BLOCKA geo coordinates are set. Spaces indicate    */
    /* the value of a coordinate is unavailable or inapplicable.        */
    /* ---------------------------------------------------------------- */
    if (pachTRE[34] == ' ' || pachTRE[55] == ' ' || pachTRE[76] == ' ' ||
        pachTRE[97] == ' ')
    {
        return FALSE;
    }

    /* ---------------------------------------------------------------- */
    /* Extract the L_LINES field of BLOCKA and see if this instance     */
    /* covers the whole image. This is the case if L_LINES is equal to  */
    /* the no of rows of this image.                                    */
    /* We use the BLOCKA only in that case!                             */
    /* ---------------------------------------------------------------- */
    nBlockaLines = atoi(NITFGetField(szTemp, pachTRE, 7, 5));
    if (psImage->nRows != nBlockaLines)
    {
        return FALSE;
    }

    /* ---------------------------------------------------------------- */
    /* Note that the order of these coordinates is different from       */
    /* IGEOLO/NITFImage.                                                */
    /*                   IGEOLO            BLOCKA                       */
    /*                   0, 0              0, MaxCol                    */
    /*                   0, MaxCol         MaxRow, MaxCol               */
    /*                   MaxRow, MaxCol    MaxRow, 0                    */
    /*                   MaxRow, 0         0, 0                         */
    /* ---------------------------------------------------------------- */
    {
        double *pdfXYs = &(psImage->dfULX);

        NITFGetGCP(pachTRE + 34, pdfXYs, 1);
        NITFGetGCP(pachTRE + 55, pdfXYs, 2);
        NITFGetGCP(pachTRE + 76, pdfXYs, 3);
        NITFGetGCP(pachTRE + 97, pdfXYs, 0);

        psImage->bIsBoxCenterOfPixel = TRUE;
    }

    /* ---------------------------------------------------------------- */
    /* Regardless of the former value of ICORDS, the values are now in  */
    /* decimal degrees.                                                 */
    /* ---------------------------------------------------------------- */

    psImage->chICORDS = 'D';

    return TRUE;
}

/************************************************************************/
/*                        NITFReadGEOLOB()                              */
/*                                                                      */
/*      The GEOLOB contains high precision lat/long geotransform        */
/*      values.                                                         */
/************************************************************************/

static int NITFReadGEOLOB(NITFImage *psImage)
{
    const char *pachTRE;
    int nTRESize;
    char szTemp[128];

    /* -------------------------------------------------------------------- */
    /*      Do we have the TRE?                                             */
    /* -------------------------------------------------------------------- */
    pachTRE =
        NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "GEOLOB", &nTRESize);

    if (pachTRE == NULL)
        return FALSE;

    if (!CPLTestBoolean(CPLGetConfigOption("NITF_USEGEOLOB", "YES")))
    {
        CPLDebug("NITF", "GEOLOB available, but ignored by request.");
        return FALSE;
    }

    if (nTRESize != 48)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read GEOLOB TRE. Wrong size.");
        return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Parse out field values.                                         */
    /* -------------------------------------------------------------------- */
    {
        double dfARV = atoi(NITFGetField(szTemp, pachTRE, 0, 9));
        double dfBRV = atoi(NITFGetField(szTemp, pachTRE, 9, 9));

        double dfLSO = CPLAtof(NITFGetField(szTemp, pachTRE, 18, 15));
        double dfPSO = CPLAtof(NITFGetField(szTemp, pachTRE, 33, 15));

        double dfPixelWidth = 360.0 / dfARV;
        double dfPixelHeight = 360.0 / dfBRV;

        psImage->dfULX = dfLSO;
        psImage->dfURX = psImage->dfULX + psImage->nCols * dfPixelWidth;
        psImage->dfLLX = psImage->dfULX;
        psImage->dfLRX = psImage->dfURX;

        psImage->dfULY = dfPSO;
        psImage->dfURY = psImage->dfULY;
        psImage->dfLLY = psImage->dfULY - psImage->nRows * dfPixelHeight;
        psImage->dfLRY = psImage->dfLLY;

        psImage->bIsBoxCenterOfPixel = FALSE;  // GEOLOB is edge of pixel.
        psImage->chICORDS = 'G';

        CPLDebug("NITF", "IGEOLO bounds overridden by GEOLOB TRE.");
    }

    return TRUE;
}

/************************************************************************/
/*                      NITFLoadAttributeSection()                      */
/*                                                                      */
/*      Load metadata items from selected attributes in the RPF         */
/*      attributes subsection.  The items are defined in                */
/*      MIL-STD-2411-1 section 5.3.2.                                   */
/************************************************************************/

// MIL-STD-2411-1 section 5.3.2.
typedef enum
{
    RPFAttr_ASCII,
    RPFAttr_UINT,
    RPFAttr_REAL,
    RPFAttr_VARIABLE
} RPFAttributeType;

typedef struct
{
    int nAttrID;
    const char *pszGDALMetadataItem;
    int nParamID;
    RPFAttributeType eType;
    int nLength; /* bytes */
} RPFAttribute;

static const RPFAttribute asRPFAttributes[] = {
    {1, "CurrencyDate", 1, RPFAttr_ASCII, 8},
    {2, "ProductionDate", 1, RPFAttr_ASCII, 8},
    {3, "SignificantDate", 1, RPFAttr_ASCII, 8},
    {4, "DataSeriesDesignation", 1, RPFAttr_ASCII, 10},
    {4, "MapSeriesDesignation", 2, RPFAttr_ASCII, 8},
    {4, "OldHorizontalDatumCode", 3, RPFAttr_ASCII, 4},
    {4, "EditionIdentifier", 4, RPFAttr_ASCII, 7},
    {5, "ProjectionCode", 1, RPFAttr_ASCII, 2},
    {5, "ParameterA", 2, RPFAttr_REAL, 4},
    {5, "ParameterB", 3, RPFAttr_REAL, 4},
    {5, "ParameterC", 4, RPFAttr_REAL, 4},
    {5, "ParameterD", 5, RPFAttr_REAL, 4},
    {6, "VerticalDatumCode", 1, RPFAttr_ASCII, 4},
    {7, "HorizontalDatumCode", 1, RPFAttr_ASCII, 4},  // not in CADRG
    {8, "VerticalAbsoluteAccuracy", 1, RPFAttr_UINT, 4},
    {8, "VerticalAbsoluteAccuracyUnits", 2, RPFAttr_UINT, 2},
    {9, "HorizontalAbsoluteAccuracy", 1, RPFAttr_UINT, 4},
    {9, "HorizontalAbsoluteAccuracyUnits", 2, RPFAttr_UINT, 2},
    {10, "VerticalRelativeAccuracy", 1, RPFAttr_UINT, 4},
    {10, "VerticalRelativeAccuracyUnits", 2, RPFAttr_UINT, 2},
    {11, "HorizontalRelativeAccuracy", 1, RPFAttr_UINT, 4},
    {11, "HorizontalRelativeAccuracyUnits", 2, RPFAttr_UINT, 2},
    {12, "EllipsoidCode", 1, RPFAttr_ASCII, 3},
    {13, "SoundingDatum", 1, RPFAttr_ASCII, 4},
    {14, "NavigationSystemsCode", 1, RPFAttr_UINT, 2},
    {15, "GridCode", 1, RPFAttr_ASCII, 2},
    {16, "EasterlyAnnualMagneticChange", 1, RPFAttr_REAL, 4},
    {16, "EasterlyAnnualMagneticChangeUnits", 2, RPFAttr_UINT, 2},
    {17, "WesterlyAnnualMagneticChange", 1, RPFAttr_REAL, 4},
    {17, "WesterlyAnnualMagneticChangeUnits", 2, RPFAttr_UINT, 2},
    {18, "GridNorthMagneticAngle", 1, RPFAttr_REAL, 4},
    {18, "GridNorthMagneticAngleUnits", 2, RPFAttr_UINT, 2},
    {19, "GridConvergenceAngle", 1, RPFAttr_REAL, 4},
    {19, "GridConvergenceAngleUnits", 2, RPFAttr_UINT, 2},
    {20, "MaximumElevation", 1, RPFAttr_REAL, 8},
    {20, "MaximumElevationUnits", 2, RPFAttr_UINT, 2},
    {20, "MaximumElevationLatitude", 3, RPFAttr_REAL, 8},
    {20, "MaximumElevationLongitude", 4, RPFAttr_REAL, 8},
    {21, "LegendFileName", 1, RPFAttr_ASCII, 12},
    {22, "DataSource", 1, RPFAttr_ASCII, 12},
    {22, "GSD", 2, RPFAttr_UINT, 4},
    {23, "DataLevel", 1, RPFAttr_UINT, 2},
    // Following from MIL-PRF-89038_AMENDMENT-2.pdf
    {24, "NumberOfUpdates", 1, RPFAttr_UINT, 2},
    {24, "UpdateNumber", 2, RPFAttr_UINT, 2},
    {24, "UpdateDate", 3, RPFAttr_ASCII, 8},
    {24, "NumberOfSubframesImpacted", 4, RPFAttr_UINT, 2},
    {24, "ListOfSubframes", 5, RPFAttr_VARIABLE, 0 /* variable */},
    {24, "NumberOfCharactersInDescription", 6, RPFAttr_UINT, 2},
    {24, "ChangeDescription", 7, RPFAttr_ASCII, 0},  // given by previous attr
    {25, "ContourInterval", 1, RPFAttr_UINT, 2},
    {25, "ContourIntervalUnits", 2, RPFAttr_UINT, 2},
};

static void NITFLoadAttributeSection(NITFImage *psImage)

{
    GUInt32 nASHOffset = 0, /* nASHSize=0, */ nASSOffset = 0, nASSSize = 0,
            nNextOffset = 0;
    GInt16 nAttrCount;
    GByte *pabyAttributeSubsection;

    for (int i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId == LID_AttributeSectionSubheader)
        {
            nASHOffset = psImage->pasLocations[i].nLocOffset;
            /* nASHSize = psImage->pasLocations[i].nLocSize; */
        }
        else if (psImage->pasLocations[i].nLocId == LID_AttributeSubsection)
        {
            nASSOffset = psImage->pasLocations[i].nLocOffset;
            nASSSize = psImage->pasLocations[i].nLocSize;
        }
    }

    if (nASSOffset == 0 || nASHOffset == 0)
        return;

    /* -------------------------------------------------------------------- */
    /*      How many attribute records do we have?                          */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(psImage->psFile->fp, nASHOffset, SEEK_SET) != 0 ||
        VSIFReadL(&nAttrCount, 2, 1, psImage->psFile->fp) != 1)
        return;

    CPL_MSBPTR16(&nAttrCount);

    /* -------------------------------------------------------------------- */
    /*      nASSSize Hack                                                   */
    /* -------------------------------------------------------------------- */
    /* OK, now, as often with RPF/CADRG, here is the necessary dirty hack */
    /* -- Begin of lengthy explanation -- */
    /* A lot of CADRG files have a nASSSize value that reports a size */
    /* smaller than the genuine size of the attribute subsection in the */
    /* file, so if we trust the nASSSize value, we'll reject existing */
    /* attributes. This is for example the case for */
    /* http://download.osgeo.org/gdal/data/nitf/0000M033.GN3 */
    /* where nASSSize is reported to be 302 bytes for 52 attributes (which */
    /* is odd since 52 * 8 < 302), but a binary inspection of the attribute */
    /* subsection shows that the actual size is 608 bytes, which is also
     * confirmed*/
    /* by the fact that the next subsection (quite often
     * LID_ExplicitArealCoverageTable but not always) */
    /* begins right after. So if this next subsection is found and that the */
    /* difference in offset is larger than the original nASSSize, use it. */
    /* I have observed that nowhere in the NITF driver we make use of the
     * .nLocSize field */
    /* -- End of lengthy explanation -- */

    for (int i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocOffset > nASSOffset)
        {
            if (nNextOffset == 0 ||
                nNextOffset > psImage->pasLocations[i].nLocOffset)
                nNextOffset = psImage->pasLocations[i].nLocOffset;
        }
    }

    if (nNextOffset > 0 && nNextOffset - nASSOffset > nASSSize)
        nASSSize = nNextOffset - nASSOffset;

    /* -------------------------------------------------------------------- */
    /*      Be sure that the attribute subsection is large enough to        */
    /*      hold the offset table (otherwise NITFFetchAttribute could       */
    /*      read out of the buffer)                                         */
    /* -------------------------------------------------------------------- */
    if (nASSSize < (size_t)(8 * nAttrCount))
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Attribute subsection not large enough (%d bytes) to contain "
                 "%d attributes.",
                 nASSSize, nAttrCount);
        return;
    }

    /* -------------------------------------------------------------------- */
    /*      Load the attribute table.                                       */
    /* -------------------------------------------------------------------- */
    pabyAttributeSubsection = (GByte *)VSIMalloc(nASSSize);
    if (pabyAttributeSubsection == NULL)
    {
        CPLError(
            CE_Warning, CPLE_AppDefined,
            "Out of memory failure reading %d bytes of attribute subsection.",
            nASSSize);
        return;
    }

    if (VSIFSeekL(psImage->psFile->fp, nASSOffset, SEEK_SET) != 0 ||
        VSIFReadL(pabyAttributeSubsection, 1, nASSSize, psImage->psFile->fp) !=
            nASSSize)
    {
        CPLError(CE_Warning, CPLE_FileIO,
                 "I/O error when reading attribute subsection.");
        CPLFree(pabyAttributeSubsection);
        return;
    }

    /* -------------------------------------------------------------------- */
    /*      Scan the attribute offset table                                 */
    /* -------------------------------------------------------------------- */
    GByte abyBuffer[12];  // max size is for LegendFileName
    const int nRPFAttributeDictSize =
        (int)(sizeof(asRPFAttributes) / sizeof(asRPFAttributes[0]));
    const bool bShowAllAttributes =
        CPLTestBoolean(CPLGetConfigOption("RPF_SHOW_ALL_ATTRIBUTES", "NO"));
    for (int i = 0; i < nAttrCount; i++)
    {
        const GByte *pabyOffsetRec = pabyAttributeSubsection + i * 8;
        for (int iRPFAttrDict = 0; iRPFAttrDict < nRPFAttributeDictSize;
             iRPFAttrDict++)
        {
            const RPFAttribute *psAttr = &(asRPFAttributes[iRPFAttrDict]);
            const int nAttrID = pabyOffsetRec[0] * 256 + pabyOffsetRec[1];
            const int nParamID = pabyOffsetRec[2];
            if (nAttrID == psAttr->nAttrID && nParamID == psAttr->nParamID &&
                // By default only shows the first three attributes.
                (nAttrID <= 3 || bShowAllAttributes))
            {
                // const int nArealCoverageSeqNumber = pabyOffsetRec[3];

                GUInt32 nAttrOffset = 0;
                memcpy(&nAttrOffset, pabyOffsetRec + 4, 4);
                CPL_MSBPTR32(&nAttrOffset);

                const char *pszName = psAttr->pszGDALMetadataItem;
                const int nLength = psAttr->nLength;
                if (nLength != 0 && (GUInt32)nLength <= nASSSize &&
                    nAttrOffset <= nASSSize - nLength)
                {
                    memcpy(abyBuffer, pabyAttributeSubsection + nAttrOffset,
                           nLength);
                    if (psAttr->eType == RPFAttr_ASCII)
                    {
                        NITFExtractMetadata(&(psImage->papszMetadata),
                                            (char *)abyBuffer, 0, nLength,
                                            CPLSPrintf("NITF_RPF_%s", pszName));
                    }
                    else if (psAttr->eType == RPFAttr_UINT)
                    {
                        if (nLength == 2)
                        {
                            const uint16_t nVal =
                                abyBuffer[0] * 256 + abyBuffer[1];
                            psImage->papszMetadata = CSLSetNameValue(
                                psImage->papszMetadata,
                                CPLSPrintf("NITF_RPF_%s", pszName),
                                CPLSPrintf("%d", nVal));
                        }
                        else
                        {
                            CPLAssert(nLength == 4);
                            uint32_t nVal;
                            memcpy(&nVal, abyBuffer, sizeof(nVal));
                            CPL_MSBPTR32(&nVal);
                            psImage->papszMetadata = CSLSetNameValue(
                                psImage->papszMetadata,
                                CPLSPrintf("NITF_RPF_%s", pszName),
                                CPLSPrintf("%u", nVal));
                        }
                    }
                    else
                    {
                        CPLAssert(psAttr->eType == RPFAttr_REAL);
                        if (nLength == 4)
                        {
                            float fVal;
                            memcpy(&fVal, abyBuffer, sizeof(fVal));
                            CPL_MSBPTR32(&fVal);
                            psImage->papszMetadata = CSLSetNameValue(
                                psImage->papszMetadata,
                                CPLSPrintf("NITF_RPF_%s", pszName),
                                CPLSPrintf("%.8g", fVal));
                        }
                        else
                        {
                            CPLAssert(nLength == 8);
                            double dfVal;
                            memcpy(&dfVal, abyBuffer, sizeof(dfVal));
                            CPL_MSBPTR64(&dfVal);
                            psImage->papszMetadata = CSLSetNameValue(
                                psImage->papszMetadata,
                                CPLSPrintf("NITF_RPF_%s", pszName),
                                CPLSPrintf("%.17g", dfVal));
                        }
                    }
                }
                break;
            }
        }
    }

    CPLFree(pabyAttributeSubsection);
}

/************************************************************************/
/*                     NITFLoadColormapSubSection()                     */
/************************************************************************/

/* This function is directly inspired by function parse_clut coming from
   ogdi/driver/rpf/utils.c and placed under the following copyright */

/*
 ******************************************************************************
 * Copyright (C) 1995 Logiciels et Applications Scientifiques (L.A.S.) Inc
 * Permission to use, copy, modify and distribute this software and
 * its documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies, that
 * both the copyright notice and this permission notice appear in
 * supporting documentation, and that the name of L.A.S. Inc not be used
 * in advertising or publicity pertaining to distribution of the software
 * without specific, written prior permission. L.A.S. Inc. makes no
 * representations about the suitability of this software for any purpose.
 * It is provided "as is" without express or implied warranty.
 ******************************************************************************
 */

static void NITFLoadColormapSubSection(NITFImage *psImage)
{
    int nLocBaseColorGrayscaleSection = 0;
    int nLocBaseColormapSubSection = 0;
    /* int colorGrayscaleSectionSize = 0; */
    /* int colormapSubSectionSize = 0; */
    NITFFile *psFile = psImage->psFile;
    unsigned int i, j;
    unsigned char nOffsetRecs;
    NITFColormapRecord *colormapRecords;
    unsigned int colormapOffsetTableOffset;
    unsigned short offsetRecLen;
    int bOK = TRUE;

    NITFBandInfo *psBandInfo = psImage->pasBandInfo;

    for (i = 0; (int)i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId ==
            LID_ColorGrayscaleSectionSubheader)
        {
            nLocBaseColorGrayscaleSection = psImage->pasLocations[i].nLocOffset;
            /* colorGrayscaleSectionSize = psImage->pasLocations[i].nLocSize; */
        }
        else if (psImage->pasLocations[i].nLocId == LID_ColormapSubsection)
        {
            nLocBaseColormapSubSection = psImage->pasLocations[i].nLocOffset;
            /* colormapSubSectionSize = psImage->pasLocations[i].nLocSize; */
        }
    }
    if (nLocBaseColorGrayscaleSection == 0)
    {
        return;
    }
    if (nLocBaseColormapSubSection == 0)
    {
        return;
    }

    if (VSIFSeekL(psFile->fp, nLocBaseColorGrayscaleSection, SEEK_SET) != 0 ||
        VSIFReadL(&nOffsetRecs, 1, 1, psFile->fp) != 1)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Failed to seek to %d.",
                 nLocBaseColorGrayscaleSection);
        return;
    }

    if (VSIFSeekL(psFile->fp, nLocBaseColormapSubSection, SEEK_SET) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Failed to seek to %d.",
                 nLocBaseColormapSubSection);
        return;
    }

    colormapRecords = (NITFColormapRecord *)CPLMalloc(
        nOffsetRecs * sizeof(NITFColormapRecord));

    /* colormap offset table offset length */
    bOK &= VSIFReadL(&colormapOffsetTableOffset,
                     sizeof(colormapOffsetTableOffset), 1, psFile->fp) == 1;
    CPL_MSBPTR32(&colormapOffsetTableOffset);

    /* offset record length */
    bOK &= VSIFReadL(&offsetRecLen, sizeof(offsetRecLen), 1, psFile->fp) == 1;
    CPL_MSBPTR16(&offsetRecLen);

    for (i = 0; bOK && i < nOffsetRecs; i++)
    {
        bOK &=
            VSIFReadL(&colormapRecords[i].tableId,
                      sizeof(colormapRecords[i].tableId), 1, psFile->fp) == 1;
        CPL_MSBPTR16(&colormapRecords[i].tableId);

        bOK &=
            VSIFReadL(&colormapRecords[i].nRecords,
                      sizeof(colormapRecords[i].nRecords), 1, psFile->fp) == 1;
        CPL_MSBPTR32(&colormapRecords[i].nRecords);

        bOK &= VSIFReadL(&colormapRecords[i].elementLength,
                         sizeof(colormapRecords[i].elementLength), 1,
                         psFile->fp) == 1;

        bOK &= VSIFReadL(&colormapRecords[i].histogramRecordLength,
                         sizeof(colormapRecords[i].histogramRecordLength), 1,
                         psFile->fp) == 1;
        CPL_MSBPTR16(&colormapRecords[i].histogramRecordLength);

        bOK &= VSIFReadL(&colormapRecords[i].colorTableOffset,
                         sizeof(colormapRecords[i].colorTableOffset), 1,
                         psFile->fp) == 1;
        CPL_MSBPTR32(&colormapRecords[i].colorTableOffset);

        bOK &= VSIFReadL(&colormapRecords[i].histogramTableOffset,
                         sizeof(colormapRecords[i].histogramTableOffset), 1,
                         psFile->fp) == 1;
        CPL_MSBPTR32(&colormapRecords[i].histogramTableOffset);
    }

    for (i = 0; bOK && i < nOffsetRecs; i++)
    {
        vsi_l_offset nOffset = (vsi_l_offset)nLocBaseColormapSubSection +
                               colormapRecords[i].colorTableOffset;
        if (VSIFSeekL(psFile->fp, nOffset, SEEK_SET) != 0)
        {
            CPLError(CE_Failure, CPLE_FileIO,
                     "Failed to seek to " CPL_FRMT_GUIB ".", nOffset);
            CPLFree(colormapRecords);
            return;
        }

        /* This test is very CADRG specific. See MIL-C-89038, paragraph 3.12.5.a
         */
        if (i == 0 && colormapRecords[i].tableId == 2 &&
            colormapRecords[i].elementLength == 4 &&
            colormapRecords[i].nRecords == 216) /* read, use colortable */
        {
            GByte *rgbm = (GByte *)CPLMalloc(colormapRecords[i].nRecords * 4);
            if (VSIFReadL(rgbm, 1, colormapRecords[i].nRecords * 4,
                          psFile->fp) != colormapRecords[i].nRecords * 4)
            {
                CPLError(CE_Failure, CPLE_FileIO,
                         "Failed to read %d byte rgbm.",
                         colormapRecords[i].nRecords * 4);
                CPLFree(rgbm);
                CPLFree(colormapRecords);
                return;
            }
            for (j = 0; j < colormapRecords[i].nRecords; j++)
            {
                psBandInfo->pabyLUT[j] = rgbm[4 * j];
                psBandInfo->pabyLUT[j + 256] = rgbm[4 * j + 1];
                psBandInfo->pabyLUT[j + 512] = rgbm[4 * j + 2];
            }
            CPLFree(rgbm);
        }
    }

    CPLFree(colormapRecords);
}

/************************************************************************/
/*                     NITFLoadSubframeMaskTable()                      */
/************************************************************************/

/* Fixes bug #913 */
static void NITFLoadSubframeMaskTable(NITFImage *psImage,
                                      bool *pbBlockStartRead)
{
    int i;
    NITFFile *psFile = psImage->psFile;
    NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + psImage->iSegment;
    vsi_l_offset nLocBaseSpatialDataSubsection = psSegInfo->nSegmentStart;
    GUInt32 nLocBaseMaskSubsection = 0;
    vsi_l_offset nLocImageDescriptionSubheader = 0;
    GUInt16 subframeSequenceRecordLength, transparencySequenceRecordLength,
        transparencyOutputPixelCodeLength;
    bool bOK = true;

    for (i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId == LID_SpatialDataSubsection)
        {
            nLocBaseSpatialDataSubsection = psImage->pasLocations[i].nLocOffset;
        }
        else if (psImage->pasLocations[i].nLocId == LID_MaskSubsection)
        {
            nLocBaseMaskSubsection = psImage->pasLocations[i].nLocOffset;
        }
        else if (psImage->pasLocations[i].nLocId ==
                 LID_ImageDescriptionSubheader)
        {
            nLocImageDescriptionSubheader = psImage->pasLocations[i].nLocOffset;
        }
    }
    if (nLocBaseMaskSubsection == 0)
    {
        // fprintf(stderr, "nLocBase(LID_MaskSubsection) == 0\n");
        return;
    }

    // Reasonable default if ImageDescriptionSubheader is missing assuming
    // the offset follow immediately the header of MaskSubsection which they
    // generally do except for product cjga/cjgaz01/0105f033.ja1, which has
    // however an explicit correct value for SUBFRAME_MASK_TABLE_OFFSET
    GUInt32 nSUBFRAME_MASK_TABLE_OFFSET = 6;
    if (nLocImageDescriptionSubheader > 0)
    {
        if (VSIFSeekL(psFile->fp, nLocImageDescriptionSubheader + 20,
                      SEEK_SET) != 0)
        {
            CPLError(CE_Failure, CPLE_FileIO, "Failed to seek to %" PRIu64,
                     ((uint64_t)nLocImageDescriptionSubheader) + 20);
            return;
        }

        bOK &=
            VSIFReadL(&nSUBFRAME_MASK_TABLE_OFFSET,
                      sizeof(nSUBFRAME_MASK_TABLE_OFFSET), 1, psFile->fp) == 1;
        CPL_MSBPTR32(&nSUBFRAME_MASK_TABLE_OFFSET);
    }

    // fprintf(stderr, "nLocBaseMaskSubsection = %d\n", nLocBaseMaskSubsection);
    if (VSIFSeekL(psFile->fp, nLocBaseMaskSubsection, SEEK_SET) != 0)
    {
        CPLError(CE_Failure, CPLE_FileIO, "Failed to seek to %u",
                 nLocBaseMaskSubsection);
        return;
    }

    bOK &= VSIFReadL(&subframeSequenceRecordLength,
                     sizeof(subframeSequenceRecordLength), 1, psFile->fp) == 1;
    CPL_MSBPTR16(&subframeSequenceRecordLength);

    bOK &=
        VSIFReadL(&transparencySequenceRecordLength,
                  sizeof(transparencySequenceRecordLength), 1, psFile->fp) == 1;
    CPL_MSBPTR16(&transparencySequenceRecordLength);

    /* in bits */
    bOK &= VSIFReadL(&transparencyOutputPixelCodeLength,
                     sizeof(transparencyOutputPixelCodeLength), 1,
                     psFile->fp) == 1;
    CPL_MSBPTR16(&transparencyOutputPixelCodeLength);

    // fprintf(stderr, "transparencyOutputPixelCodeLength=%d\n",
    // transparencyOutputPixelCodeLength);

    if (transparencyOutputPixelCodeLength == 8)
    {
        GByte byNodata;

        if (bOK && VSIFReadL(&byNodata, 1, 1, psFile->fp) == 1)
        {
            psImage->bNoDataSet = TRUE;
            psImage->nNoDataValue = byNodata;
        }
    }
    else
    {
        bOK &=
            VSIFSeekL(psFile->fp, (transparencyOutputPixelCodeLength + 7) / 8,
                      SEEK_CUR) == 0;
    }

    /* Fix for rpf/cjnc/cjncz01/0001f023.jn1 */
    if (!bOK || subframeSequenceRecordLength != 4 ||
        nSUBFRAME_MASK_TABLE_OFFSET == UINT_MAX)
    {
        // fprintf(stderr, "subframeSequenceRecordLength=%d\n",
        // subframeSequenceRecordLength);
        return;
    }
    if (nSUBFRAME_MASK_TABLE_OFFSET < 6)
    {
        CPLError(CE_Warning, CPLE_AppDefined,
                 "Invalid value for SUBFRAME_MASK_TABLE_OFFSET: %u. Ignoring "
                 "block start table",
                 nSUBFRAME_MASK_TABLE_OFFSET);
        return;
    }

    bOK &= (VSIFSeekL(psFile->fp,
                      nLocBaseMaskSubsection + nSUBFRAME_MASK_TABLE_OFFSET,
                      SEEK_SET) == 0);

    for (i = 0; bOK && i < psImage->nBlocksPerRow * psImage->nBlocksPerColumn;
         i++)
    {
        unsigned int offset;
        bOK = VSIFReadL(&offset, sizeof(offset), 1, psFile->fp) == 1;
        CPL_MSBPTR32(&offset);
        // fprintf(stderr, "%u : %d\n", i, offset);
        if (!bOK || offset == UINT_MAX)
            psImage->panBlockStart[i] = UINT_MAX;
        else
            psImage->panBlockStart[i] = nLocBaseSpatialDataSubsection + offset;
    }

    *pbBlockStartRead = bOK;
}

static GUInt16 NITFReadMSBGUInt16(VSILFILE *fp, int *pbSuccess)
{
    GUInt16 nVal;
    if (VSIFReadL(&nVal, 1, sizeof(nVal), fp) != sizeof(nVal))
    {
        *pbSuccess = FALSE;
        return 0;
    }
    CPL_MSBPTR16(&nVal);
    return nVal;
}

static GUInt32 NITFReadMSBGUInt32(VSILFILE *fp, int *pbSuccess)
{
    GUInt32 nVal;
    if (VSIFReadL(&nVal, 1, sizeof(nVal), fp) != sizeof(nVal))
    {
        *pbSuccess = FALSE;
        return 0;
    }
    CPL_MSBPTR32(&nVal);
    return nVal;
}

/************************************************************************/
/*                      NITFReadRPFLocationTable()                      */
/************************************************************************/

NITFLocation *NITFReadRPFLocationTable(VSILFILE *fp, int *pnLocCount)
{
    /* GUInt16 nLocSectionLength; */
    GUInt32 nLocSectionOffset;
    GUInt16 iLoc;
    GUInt16 nLocCount;
    GUInt16 nLocRecordLength;
    /* GUInt32 nLocComponentAggregateLength; */
    NITFLocation *pasLocations = NULL;
    int bSuccess;
    GUIntBig nCurOffset;

    if (fp == NULL || pnLocCount == NULL)
        return NULL;

    *pnLocCount = 0;

    nCurOffset = VSIFTellL(fp);

    bSuccess = TRUE;
    /* nLocSectionLength = */ NITFReadMSBGUInt16(fp, &bSuccess);
    nLocSectionOffset = NITFReadMSBGUInt32(fp, &bSuccess);
    if (nLocSectionOffset != 14)
    {
        CPLDebug("NITF", "Unusual location section offset : %d",
                 nLocSectionOffset);
    }

    nLocCount = NITFReadMSBGUInt16(fp, &bSuccess);

    if (!bSuccess || nLocCount == 0)
    {
        return NULL;
    }

    nLocRecordLength = NITFReadMSBGUInt16(fp, &bSuccess);
    if (nLocRecordLength != 10)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Did not get expected record length : %d", nLocRecordLength);
        return NULL;
    }

    /* nLocComponentAggregateLength = */ NITFReadMSBGUInt32(fp, &bSuccess);

    bSuccess = VSIFSeekL(fp, nCurOffset + nLocSectionOffset, SEEK_SET) == 0;

    pasLocations =
        (NITFLocation *)VSI_CALLOC_VERBOSE(sizeof(NITFLocation), nLocCount);
    if (pasLocations == NULL)
    {
        return NULL;
    }

    /* -------------------------------------------------------------------- */
    /*      Process the locations.                                          */
    /* -------------------------------------------------------------------- */
    for (iLoc = 0; bSuccess && iLoc < nLocCount; iLoc++)
    {
        pasLocations[iLoc].nLocId = NITFReadMSBGUInt16(fp, &bSuccess);
        pasLocations[iLoc].nLocSize = NITFReadMSBGUInt32(fp, &bSuccess);
        pasLocations[iLoc].nLocOffset = NITFReadMSBGUInt32(fp, &bSuccess);
        CPLDebugOnly("NITF", "Location[%d]: id=%d, size=%u, offset=%u", iLoc,
                     pasLocations[iLoc].nLocId, pasLocations[iLoc].nLocSize,
                     pasLocations[iLoc].nLocOffset);
    }

    if (!bSuccess)
    {
        CPLFree(pasLocations);
        return NULL;
    }

    *pnLocCount = nLocCount;
    return pasLocations;
}

/************************************************************************/
/*                       NITFLoadLocationTable()                        */
/************************************************************************/

static void NITFLoadLocationTable(NITFImage *psImage)

{
    /* -------------------------------------------------------------------- */
    /*      Get the location table out of the RPFIMG TRE on the image.      */
    /* -------------------------------------------------------------------- */
    const char *pszTRE;
    GUInt32 nHeaderOffset = 0;
    int i;
    int nTRESize;
    char szTempFileName[256];
    VSILFILE *fpTemp;

    pszTRE =
        NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "RPFIMG", &nTRESize);
    if (pszTRE == NULL)
        return;

    snprintf(szTempFileName, sizeof(szTempFileName), "%s",
             VSIMemGenerateHiddenFilename("nitf_tre"));
    fpTemp =
        VSIFileFromMemBuffer(szTempFileName, (GByte *)pszTRE, nTRESize, FALSE);
    psImage->pasLocations =
        NITFReadRPFLocationTable(fpTemp, &psImage->nLocCount);
    CPL_IGNORE_RET_VAL_INT(VSIFCloseL(fpTemp));
    VSIUnlink(szTempFileName);

    if (psImage->nLocCount == 0)
        return;

    /* -------------------------------------------------------------------- */
    /*      It seems that sometimes (at least for bug #1313 and #1714)      */
    /*      the RPF headers are improperly placed.  We check by looking     */
    /*      to see if the RPFHDR is where it should be.  If not, we         */
    /*      disregard the location table.                                   */
    /*                                                                      */
    /*      The NITF21_CGM_ANNO_Uncompressed_unmasked.ntf sample data       */
    /*      file (see gdal data downloads) is an example of this.           */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId == LID_HeaderComponent)
        {
            nHeaderOffset = psImage->pasLocations[i].nLocOffset;
            break;
        }
    }

    if (nHeaderOffset > 11)
    {
        char achHeaderChunk[1000];

        if (VSIFSeekL(psImage->psFile->fp, nHeaderOffset - 11, SEEK_SET) != 0 ||
            VSIFReadL(achHeaderChunk, sizeof(achHeaderChunk), 1,
                      psImage->psFile->fp) != 1)
        {
            CPLFree(psImage->pasLocations);
            psImage->pasLocations = NULL;
            psImage->nLocCount = 0;
            return;
        }

        /* You can define NITF_DISABLE_RPF_LOCATION_TABLE_SANITY_TESTS to TRUE
         */
        /* to blindly trust the RPF location table even if it doesn't look */
        /* sane. Necessary for dataset attached to
         * http://trac.osgeo.org/gdal/ticket/3930 */
        if (!STARTS_WITH_CI(achHeaderChunk, "RPFHDR") &&
            !CPLTestBoolean(CPLGetConfigOption(
                "NITF_DISABLE_RPF_LOCATION_TABLE_SANITY_TESTS", "FALSE")))
        {
            /* Image of http://trac.osgeo.org/gdal/ticket/3848 has incorrect */
            /* RPFHDR offset, but all other locations are correct... */
            /* So if we find LID_CoverageSectionSubheader and
             * LID_CompressionLookupSubsection */
            /* we check whether their content is valid. */
            int bFoundValidLocation = FALSE;
            for (i = 0; i < psImage->nLocCount; i++)
            {
                if (psImage->pasLocations[i].nLocId ==
                        LID_CoverageSectionSubheader &&
                    (psImage->chICORDS == 'G' || psImage->chICORDS == 'D'))
                {
                    /* Does that look like valid latitude/longitude values ? */
                    /* We test that they are close enough from the values of the
                     * IGEOLO record */
                    double adfTarget[8];

                    if (VSIFSeekL(psImage->psFile->fp,
                                  psImage->pasLocations[i].nLocOffset,
                                  SEEK_SET) != 0 ||
                        VSIFReadL(adfTarget, 8, 8, psImage->psFile->fp) != 8)
                    {
                        CPLFree(psImage->pasLocations);
                        psImage->pasLocations = NULL;
                        psImage->nLocCount = 0;
                        return;
                    }
                    for (i = 0; i < 8; i++)
                        CPL_MSBPTR64((adfTarget + i));

                    if (fabs(psImage->dfULX - adfTarget[1]) < 0.1 &&
                        fabs(psImage->dfULY - adfTarget[0]) < 0.1 &&
                        fabs(psImage->dfLLX - adfTarget[3]) < 0.1 &&
                        fabs(psImage->dfLLY - adfTarget[2]) < 0.1 &&
                        fabs(psImage->dfURX - adfTarget[5]) < 0.1 &&
                        fabs(psImage->dfURY - adfTarget[4]) < 0.1 &&
                        fabs(psImage->dfLRX - adfTarget[7]) < 0.1 &&
                        fabs(psImage->dfLRY - adfTarget[6]) < 0.1)
                    {
                        bFoundValidLocation = TRUE;
                    }
                    else
                    {
                        CPLDebug("NITF", "The CoverageSectionSubheader content "
                                         "isn't consistent");
                        bFoundValidLocation = FALSE;
                        break;
                    }
                }
                else if (psImage->pasLocations[i].nLocId ==
                         LID_CompressionLookupSubsection)
                {
                    if (NITFLoadVQTables(psImage, FALSE))
                    {
                        bFoundValidLocation = TRUE;
                    }
                    else
                    {
                        CPLDebug("NITF",
                                 "The VQ tables content aren't consistent");
                        bFoundValidLocation = FALSE;
                        break;
                    }
                }
            }
            if (bFoundValidLocation)
            {
                CPLDebug("NITF", "RPFHDR is not correctly placed, but other "
                                 "locations seem correct. Going on...");
            }
            else
            {
                CPLError(CE_Warning, CPLE_AppDefined,
                         "Ignoring NITF RPF Location table since it seems to "
                         "be corrupt.");
                CPLFree(psImage->pasLocations);
                psImage->pasLocations = NULL;
                psImage->nLocCount = 0;
            }
        }
    }
}

/************************************************************************/
/*                          NITFLoadVQTables()                          */
/************************************************************************/

static int NITFLoadVQTables(NITFImage *psImage, int bTryGuessingOffset)

{
    int i;
    GUInt32 nVQOffset = 0 /*, nVQSize=0 */;
    GByte abyTestChunk[1000];
    const GByte abySignature[6] = {0x00, 0x00, 0x00, 0x06, 0x00, 0x0E};

    /* -------------------------------------------------------------------- */
    /*      Do we already have the VQ tables?                               */
    /* -------------------------------------------------------------------- */
    if (psImage->apanVQLUT[0] != NULL)
        return TRUE;

    /* -------------------------------------------------------------------- */
    /*      Do we have the location information?                            */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < psImage->nLocCount; i++)
    {
        if (psImage->pasLocations[i].nLocId == LID_CompressionLookupSubsection)
        {
            nVQOffset = psImage->pasLocations[i].nLocOffset;
            /* nVQSize = psImage->pasLocations[i].nLocSize; */
        }
    }

    if (nVQOffset == 0)
        return FALSE;

    /* -------------------------------------------------------------------- */
    /*      Does it look like we have the tables properly identified?       */
    /* -------------------------------------------------------------------- */
    if (VSIFSeekL(psImage->psFile->fp, nVQOffset, SEEK_SET) != 0 ||
        VSIFReadL(abyTestChunk, sizeof(abyTestChunk), 1, psImage->psFile->fp) !=
            1)
    {
        return FALSE;
    }

    if (memcmp(abyTestChunk, abySignature, sizeof(abySignature)) != 0)
    {
        int bFoundSignature = FALSE;
        if (!bTryGuessingOffset)
            return FALSE;

        for (i = 0; (size_t)i < sizeof(abyTestChunk) - sizeof(abySignature);
             i++)
        {
            if (memcmp(abyTestChunk + i, abySignature, sizeof(abySignature)) ==
                0)
            {
                bFoundSignature = TRUE;
                nVQOffset += i;
                CPLDebug("NITF",
                         "VQ CompressionLookupSubsection offsets off by %d "
                         "bytes, adjusting accordingly.",
                         i);
                break;
            }
        }
        if (!bFoundSignature)
            return FALSE;
    }

    /* -------------------------------------------------------------------- */
    /*      Load the tables.                                                */
    /* -------------------------------------------------------------------- */
    for (i = 0; i < 4; i++)
    {
        GUInt32 nVQVector;
        int bOK;

        psImage->apanVQLUT[i] = (GUInt32 *)CPLCalloc(4096, sizeof(GUInt32));

        bOK = VSIFSeekL(psImage->psFile->fp, nVQOffset + 6 + i * 14 + 10,
                        SEEK_SET) == 0;
        bOK &= VSIFReadL(&nVQVector, 1, 4, psImage->psFile->fp) == 4;
        nVQVector = CPL_MSBWORD32(nVQVector);

        bOK &= VSIFSeekL(psImage->psFile->fp,
                         (vsi_l_offset)(nVQOffset) + nVQVector, SEEK_SET) == 0;
        bOK &= VSIFReadL(psImage->apanVQLUT[i], 4, 4096, psImage->psFile->fp) ==
               4096;
        if (!bOK)
        {
            for (i = 0; i < 4; i++)
            {
                CPLFree(psImage->apanVQLUT[i]);
                psImage->apanVQLUT[i] = NULL;
            }
            return FALSE;
        }
    }

    return TRUE;
}

/************************************************************************/
/*                           NITFReadSTDIDC()                           */
/*                                                                      */
/*      Read a STDIDC TRE and return contents as metadata strings.      */
/************************************************************************/

char **NITFReadSTDIDC(NITFImage *psImage)

{
    return NITFGenericMetadataRead(NULL, NULL, psImage, "STDIDC");
}

/************************************************************************/
/*                         NITFRPCGeoToImage()                          */
/************************************************************************/

int NITFRPCGeoToImage(NITFRPC00BInfo *psRPC, double dfLong, double dfLat,
                      double dfHeight, double *pdfPixel, double *pdfLine)

{
    double dfLineNumerator, dfLineDenominator, dfPixelNumerator,
        dfPixelDenominator;
    double dfPolyTerm[20];
    double *pdfPolyTerm = dfPolyTerm;
    int i;

    /* -------------------------------------------------------------------- */
    /*      Normalize Lat/Long position.                                    */
    /* -------------------------------------------------------------------- */
    dfLong = (dfLong - psRPC->LONG_OFF) / psRPC->LONG_SCALE;
    dfLat = (dfLat - psRPC->LAT_OFF) / psRPC->LAT_SCALE;
    dfHeight = (dfHeight - psRPC->HEIGHT_OFF) / psRPC->HEIGHT_SCALE;

    /* -------------------------------------------------------------------- */
    /*      Compute the 20 terms.                                           */
    /* -------------------------------------------------------------------- */

    pdfPolyTerm[0] = 1.0; /* workaround cppcheck false positive */
    dfPolyTerm[1] = dfLong;
    dfPolyTerm[2] = dfLat;
    dfPolyTerm[3] = dfHeight;
    dfPolyTerm[4] = dfLong * dfLat;
    dfPolyTerm[5] = dfLong * dfHeight;
    dfPolyTerm[6] = dfLat * dfHeight;
    dfPolyTerm[7] = dfLong * dfLong;
    dfPolyTerm[8] = dfLat * dfLat;
    dfPolyTerm[9] = dfHeight * dfHeight;

    dfPolyTerm[10] = dfLong * dfLat * dfHeight;
    dfPolyTerm[11] = dfLong * dfLong * dfLong;
    dfPolyTerm[12] = dfLong * dfLat * dfLat;
    dfPolyTerm[13] = dfLong * dfHeight * dfHeight;
    dfPolyTerm[14] = dfLong * dfLong * dfLat;
    dfPolyTerm[15] = dfLat * dfLat * dfLat;
    dfPolyTerm[16] = dfLat * dfHeight * dfHeight;
    dfPolyTerm[17] = dfLong * dfLong * dfHeight;
    dfPolyTerm[18] = dfLat * dfLat * dfHeight;
    dfPolyTerm[19] = dfHeight * dfHeight * dfHeight;

    /* -------------------------------------------------------------------- */
    /*      Compute numerator and denominator sums.                         */
    /* -------------------------------------------------------------------- */
    dfPixelNumerator = 0.0;
    dfPixelDenominator = 0.0;
    dfLineNumerator = 0.0;
    dfLineDenominator = 0.0;

    for (i = 0; i < 20; i++)
    {
        dfPixelNumerator += psRPC->SAMP_NUM_COEFF[i] * dfPolyTerm[i];
        dfPixelDenominator += psRPC->SAMP_DEN_COEFF[i] * dfPolyTerm[i];
        dfLineNumerator += psRPC->LINE_NUM_COEFF[i] * dfPolyTerm[i];
        dfLineDenominator += psRPC->LINE_DEN_COEFF[i] * dfPolyTerm[i];
    }

    /* -------------------------------------------------------------------- */
    /*      Compute normalized pixel and line values.                       */
    /* -------------------------------------------------------------------- */
    *pdfPixel = dfPixelNumerator / dfPixelDenominator;
    *pdfLine = dfLineNumerator / dfLineDenominator;

    /* -------------------------------------------------------------------- */
    /*      Denormalize.                                                    */
    /* -------------------------------------------------------------------- */
    *pdfPixel = *pdfPixel * psRPC->SAMP_SCALE + psRPC->SAMP_OFF;
    *pdfLine = *pdfLine * psRPC->LINE_SCALE + psRPC->LINE_OFF;

    return TRUE;
}

/************************************************************************/
/*                         NITFIHFieldOffset()                          */
/*                                                                      */
/*      Find the file offset for the beginning of a particular field    */
/*      in this image header.  Only implemented for selected fields.    */
/************************************************************************/

GUIntBig NITFIHFieldOffset(NITFImage *psImage, const char *pszFieldName)

{
    char szTemp[128];
    int nNICOM;
    GUIntBig nWrkOffset;
    GUIntBig nIMOffset =
        psImage->psFile->pasSegmentInfo[psImage->iSegment].nSegmentHeaderStart;

    // We only support files we created.
    if (!STARTS_WITH_CI(psImage->psFile->szVersion, "NITF02.1"))
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "NITFIHFieldOffset() only works with NITF 2.1 images");
        return 0;
    }

    if (EQUAL(pszFieldName, "IM"))
        return nIMOffset;

    if (EQUAL(pszFieldName, "PJUST"))
        return nIMOffset + 370;

    if (EQUAL(pszFieldName, "ICORDS"))
        return nIMOffset + 371;

    if (EQUAL(pszFieldName, "IGEOLO"))
    {
        if (!psImage->bHaveIGEOLO)
            return 0;
        else
            return nIMOffset + 372;
    }

    /* -------------------------------------------------------------------- */
    /*      Keep working offset from here on in since everything else is    */
    /*      variable.                                                       */
    /* -------------------------------------------------------------------- */
    nWrkOffset = 372 + nIMOffset;

    if (psImage->bHaveIGEOLO)
        nWrkOffset += 60;

    /* -------------------------------------------------------------------- */
    /*      Comments.                                                       */
    /* -------------------------------------------------------------------- */
    nNICOM = atoi(NITFGetField(szTemp, psImage->pachHeader,
                               (int)(nWrkOffset - nIMOffset), 1));

    if (EQUAL(pszFieldName, "NICOM"))
        return nWrkOffset;

    nWrkOffset++;

    if (EQUAL(pszFieldName, "ICOM"))
        return nWrkOffset;

    nWrkOffset += 80 * nNICOM;

    /* -------------------------------------------------------------------- */
    /*      IC                                                              */
    /* -------------------------------------------------------------------- */
    if (EQUAL(pszFieldName, "IC"))
        return nWrkOffset;

    nWrkOffset += 2;

    /* -------------------------------------------------------------------- */
    /*      COMRAT                                                          */
    /* -------------------------------------------------------------------- */

    if (psImage->szIC[0] != 'N')
    {
        if (EQUAL(pszFieldName, "COMRAT"))
            return nWrkOffset;
        nWrkOffset += 4;
    }

    /* -------------------------------------------------------------------- */
    /*      NBANDS                                                          */
    /* -------------------------------------------------------------------- */
    if (EQUAL(pszFieldName, "NBANDS"))
        return nWrkOffset;

    nWrkOffset += 1;

    /* -------------------------------------------------------------------- */
    /*      XBANDS                                                          */
    /* -------------------------------------------------------------------- */
    if (EQUAL(pszFieldName, "XBANDS"))
        return nWrkOffset;

    if (psImage->nBands > 9)
        nWrkOffset += 5;

    /* -------------------------------------------------------------------- */
    /*      IREPBAND                                                        */
    /* -------------------------------------------------------------------- */
    if (EQUAL(pszFieldName, "IREPBAND"))
        return nWrkOffset;

    //    nWrkOffset += 2 * psImage->nBands;

    return 0;
}

/************************************************************************/
/*                        NITFDoLinesIntersect()                        */
/************************************************************************/

static int NITFDoLinesIntersect(double dfL1X1, double dfL1Y1, double dfL1X2,
                                double dfL1Y2, double dfL2X1, double dfL2Y1,
                                double dfL2X2, double dfL2Y2)

{
    double dfL1M, dfL1B, dfL2M, dfL2B;

    if (dfL1X1 == dfL1X2)
    {
        dfL1M = 1e10;
        dfL1B = 0.0;
    }
    else
    {
        dfL1M = (dfL1Y2 - dfL1Y1) / (dfL1X2 - dfL1X1);
        dfL1B = dfL1Y2 - dfL1M * dfL1X2;
    }

    if (dfL2X1 == dfL2X2)
    {
        dfL2M = 1e10;
        dfL2B = 0.0;
    }
    else
    {
        dfL2M = (dfL2Y2 - dfL2Y1) / (dfL2X2 - dfL2X1);
        dfL2B = dfL2Y2 - dfL2M * dfL2X2;
    }

    if (dfL2M == dfL1M)
    {
        // parallel .. no meaningful intersection.
        return FALSE;
    }
    else
    {
        double dfX /*, dfY*/;

        dfX = (dfL2B - dfL1B) / (dfL1M - dfL2M);
        /* dfY = dfL2M * dfX + dfL2B; */

        /*
        ** Is this intersection on the line between
        ** our corner points or "out somewhere" else?
        */
        return ((dfX >= dfL1X1 && dfX <= dfL1X2) ||
                (dfX >= dfL1X2 && dfX <= dfL1X1)) &&
               ((dfX >= dfL2X1 && dfX <= dfL2X2) ||
                (dfX >= dfL2X2 && dfX <= dfL2X1));
    }
}

/************************************************************************/
/*                  NITFPossibleIGEOLOReorientation()                   */
/************************************************************************/

static void NITFPossibleIGEOLOReorientation(NITFImage *psImage)

{
/* -------------------------------------------------------------------- */
/*      Check whether the vector from top left to bottom left           */
/*      intersects the line from top right to bottom right.  If this    */
/*      is true, then we believe the corner coordinate order was        */
/*      written improperly.                                             */
/* -------------------------------------------------------------------- */
#if 1
    if (!NITFDoLinesIntersect(psImage->dfULX, psImage->dfULY, psImage->dfLLX,
                              psImage->dfLLY, psImage->dfURX, psImage->dfURY,
                              psImage->dfLRX, psImage->dfLRY))
        return;
    else
        CPLDebug("NITF", "It appears the IGEOLO corner coordinates were "
                         "written improperly!");
#endif

    /* -------------------------------------------------------------------- */
    /*      Divide the lat/long extents of this image into four             */
    /*      quadrants and assign the corners based on which point falls     */
    /*      into which quadrant.  This is intended to correct images        */
    /*      with the corner points written improperly.  Unfortunately it    */
    /*      also breaks images which are mirrored, or rotated more than     */
    /*      90 degrees from simple north up.                                */
    /* -------------------------------------------------------------------- */
    {

        double dfXMax = CPL_MAX(CPL_MAX(psImage->dfULX, psImage->dfURX),
                                CPL_MAX(psImage->dfLRX, psImage->dfLLX));
        double dfXMin = CPL_MIN(CPL_MIN(psImage->dfULX, psImage->dfURX),
                                CPL_MIN(psImage->dfLRX, psImage->dfLLX));
        double dfYMax = CPL_MAX(CPL_MAX(psImage->dfULY, psImage->dfURY),
                                CPL_MAX(psImage->dfLRY, psImage->dfLLY));
        double dfYMin = CPL_MIN(CPL_MIN(psImage->dfULY, psImage->dfURY),
                                CPL_MIN(psImage->dfLRY, psImage->dfLLY));
        double dfXPivot = (dfXMax + dfXMin) * 0.5;
        double dfYPivot = (dfYMax + dfYMin) * 0.5;

        double dfNewULX = 0., dfNewULY = 0., dfNewURX = 0., dfNewURY = 0.,
               dfNewLLX = 0., dfNewLLY = 0., dfNewLRX = 0., dfNewLRY = 0.;
        int bGotUL = FALSE, bGotUR = FALSE, bGotLL = FALSE, bGotLR = FALSE;
        int iCoord, bChange = FALSE;

        for (iCoord = 0; iCoord < 4; iCoord++)
        {
            double *pdfXY = &(psImage->dfULX) + iCoord * 2;

            if (pdfXY[0] < dfXPivot && pdfXY[1] < dfYPivot)
            {
                bGotLL = TRUE;
                dfNewLLX = pdfXY[0];
                dfNewLLY = pdfXY[1];
                bChange |= iCoord != 3;
            }
            else if (pdfXY[0] > dfXPivot && pdfXY[1] < dfYPivot)
            {
                bGotLR = TRUE;
                dfNewLRX = pdfXY[0];
                dfNewLRY = pdfXY[1];
                bChange |= iCoord != 2;
            }
            else if (pdfXY[0] > dfXPivot && pdfXY[1] > dfYPivot)
            {
                bGotUR = TRUE;
                dfNewURX = pdfXY[0];
                dfNewURY = pdfXY[1];
                bChange |= iCoord != 1;
            }
            else
            {
                bGotUL = TRUE;
                dfNewULX = pdfXY[0];
                dfNewULY = pdfXY[1];
                bChange |= iCoord != 0;
            }
        }

        if (!bGotUL || !bGotUR || !bGotLL || !bGotLR)
        {
            CPLDebug("NITF", "Unable to reorient corner points sensibly in "
                             "NITFPossibleIGEOLOReorganization(), discarding "
                             "IGEOLO locations.");
            psImage->bHaveIGEOLO = FALSE;
            return;
        }

        if (!bChange)
            return;

        psImage->dfULX = dfNewULX;
        psImage->dfULY = dfNewULY;
        psImage->dfURX = dfNewURX;
        psImage->dfURY = dfNewURY;
        psImage->dfLRX = dfNewLRX;
        psImage->dfLRY = dfNewLRY;
        psImage->dfLLX = dfNewLLX;
        psImage->dfLLY = dfNewLLY;

        CPLDebug("NITF", "IGEOLO corners have been reoriented by "
                         "NITFPossibleIGEOLOReorientation().");
    }
}

/************************************************************************/
/*                           NITFReadIMRFCA()                           */
/*                                                                      */
/*      Read DPPDB IMRFCA TRE (and the associated IMASDA TRE) if it is  */
/*      available. IMRFCA RPC coefficients are remapped into RPC00B     */
/*      organization.                                                   */
/*      See table 68 for IMASDA and table 69 for IMRFCA in              */
/*      http://earth-info.nga.mil/publications/specs/printed/89034/89034DPPDB.pdf
 */
/************************************************************************/
int NITFReadIMRFCA(NITFImage *psImage, NITFRPC00BInfo *psRPC)
{
    char szTemp[100];
    const char *pachTreIMASDA = NULL;
    const char *pachTreIMRFCA = NULL;
    double dfTolerance = 1.0e-10;
    int count = 0;
    int nTreIMASDASize = 0;
    int nTreIMRFCASize = 0;

    if ((psImage == NULL) || (psRPC == NULL))
        return FALSE;

    /* Check to see if we have the IMASDA and IMRFCA tag (DPPDB data). */

    pachTreIMASDA = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "IMASDA",
                                &nTreIMASDASize);
    pachTreIMRFCA = NITFFindTRE(psImage->pachTRE, psImage->nTREBytes, "IMRFCA",
                                &nTreIMRFCASize);

    if ((pachTreIMASDA == NULL) || (pachTreIMRFCA == NULL))
        return FALSE;

    if (nTreIMASDASize < 242 || nTreIMRFCASize < 1760)
    {
        CPLError(CE_Failure, CPLE_AppDefined,
                 "Cannot read DPPDB IMASDA/IMRFCA TREs; not enough bytes.");

        return FALSE;
    }

    /* Parse out the field values. */

    /* Set the errors to 0.0 for now. */

    psRPC->ERR_BIAS = 0.0;
    psRPC->ERR_RAND = 0.0;

    psRPC->LONG_OFF = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 0, 22));
    psRPC->LAT_OFF = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 22, 22));
    psRPC->HEIGHT_OFF = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 44, 22));
    psRPC->LONG_SCALE = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 66, 22));
    psRPC->LAT_SCALE = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 88, 22));
    psRPC->HEIGHT_SCALE = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 110, 22));
    psRPC->SAMP_OFF = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 132, 22));
    psRPC->LINE_OFF = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 154, 22));
    psRPC->SAMP_SCALE = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 176, 22));
    psRPC->LINE_SCALE = CPLAtof(NITFGetField(szTemp, pachTreIMASDA, 198, 22));

    if (psRPC->HEIGHT_SCALE == 0.0)
        psRPC->HEIGHT_SCALE = dfTolerance;
    if (psRPC->LAT_SCALE == 0.0)
        psRPC->LAT_SCALE = dfTolerance;
    if (psRPC->LINE_SCALE == 0.0)
        psRPC->LINE_SCALE = dfTolerance;
    if (psRPC->LONG_SCALE == 0.0)
        psRPC->LONG_SCALE = dfTolerance;
    if (psRPC->SAMP_SCALE == 0.0)
        psRPC->SAMP_SCALE = dfTolerance;

    psRPC->HEIGHT_SCALE = 1.0 / psRPC->HEIGHT_SCALE;
    psRPC->LAT_SCALE = 1.0 / psRPC->LAT_SCALE;
    psRPC->LINE_SCALE = 1.0 / psRPC->LINE_SCALE;
    psRPC->LONG_SCALE = 1.0 / psRPC->LONG_SCALE;
    psRPC->SAMP_SCALE = 1.0 / psRPC->SAMP_SCALE;

    /* Parse out the RPC coefficients. */

    for (count = 0; count < 20; ++count)
    {
        psRPC->SAMP_NUM_COEFF[count] =
            CPLAtof(NITFGetField(szTemp, pachTreIMRFCA, count * 22, 22));
        psRPC->SAMP_DEN_COEFF[count] =
            CPLAtof(NITFGetField(szTemp, pachTreIMRFCA, 440 + count * 22, 22));

        psRPC->LINE_NUM_COEFF[count] =
            CPLAtof(NITFGetField(szTemp, pachTreIMRFCA, 880 + count * 22, 22));
        psRPC->LINE_DEN_COEFF[count] =
            CPLAtof(NITFGetField(szTemp, pachTreIMRFCA, 1320 + count * 22, 22));
    }

    psRPC->SUCCESS = 1;

    return TRUE;
}
