/******************************************************************************
 * $Id$
 *
 * 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>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

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


CPL_INLINE static void CPL_IGNORE_RET_VAL_INT(CPL_UNUSED int unused) {}

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 );
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;
    int        bGotWrongOffset = FALSE;

/* -------------------------------------------------------------------- */
/*      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 FSDEVT 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' )
            {
                pdfXY[1] =
                    CPLAtof(NITFGetField( szTemp, pszCoordPair, 0, 2 ))
                  + CPLAtof(NITFGetField( szTemp, pszCoordPair, 2, 2 )) / 60.0
                  + CPLAtof(NITFGetField( szTemp, pszCoordPair, 4, 2 )) / 3600.0;
                if( pszCoordPair[6] == 's' || pszCoordPair[6] == 'S' )
                    pdfXY[1] *= -1;

                pdfXY[0] =
                    CPLAtof(NITFGetField( szTemp, pszCoordPair, 7, 3 ))
                  + CPLAtof(NITFGetField( szTemp, pszCoordPair,10, 2 )) / 60.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;
    }

/* -------------------------------------------------------------------- */
/*      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) < (unsigned)(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 = 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( psImage->nBlocksPerRow * psImage->nBlocksPerColumn
                   * psImage->nBands, sizeof(GUIntBig) );
    if (psImage->panBlockStart == NULL)
    {
        NITFImageDeaccess(psImage);
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      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") )
    {
        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 )
        {
            int isM4 = EQUAL(psImage->szIC,"M4");
            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 )
                {
                    if (isM4 && (psImage->panBlockStart[i] % 6144) != 0)
                    {
                        break;
                    }
                    psImage->panBlockStart[i]
                        += psSegInfo->nSegmentStart + nIMDATOFF;
                }
            }
            /* This is a fix for a problem with rpf/cjga/cjgaz01/0105f033.ja1 and */
            /* rpf/cjga/cjgaz03/0034t0b3.ja3 CADRG products (bug 1754). */
            /* These products have the strange particularity that their block start table begins */
            /* one byte after its theoretical beginning, for an unknown reason */
            /* We detect this situation when the block start offset is not a multiple of 6144 */
            /* Hopefully there's something in the NITF/CADRG standard that can account for it,  */
            /* but I've not found it */
            if (isM4 && i != nBlockCount)
            {
                bGotWrongOffset = TRUE;
                CPLError( CE_Warning, CPLE_AppDefined,
                          "Block start for block %d is wrong. Retrying with one extra byte shift...", i);
                bOK &= VSIFSeekL( psFile->fp, psSegInfo->nSegmentStart +
                                       4 + /* nIMDATOFF */
                                       2 + /* nBMRLNTH */
                                       2 + /* nTMRLNTH */
                                       2 + /* nTPXCDLNTH */
                                       (nTPXCDLNTH+7)/8 +
                                       1, /* MAGIC here ! One byte shift... */
                            SEEK_SET ) == 0;

                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 )
                    {
                        if ((psImage->panBlockStart[i] % 6144) != 0)
                        {
                            CPLError( CE_Warning, CPLE_AppDefined,
                                      "Block start for block %d is still wrong. Display will be wrong.", i );
                            break;
                        }
                        psImage->panBlockStart[i]
                            += psSegInfo->nSegmentStart + nIMDATOFF;
                    }
                }
            }
        }
        else
        {
            if( EQUAL(psImage->szIC,"M4") )
            {
                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;
        }
    }


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

/* -------------------------------------------------------------------- */
/*      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
            {
                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( NITFImage *psImage,
                                  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] +
                    (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") )
    {
        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;
    }

/* -------------------------------------------------------------------- */
/*      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( chICORDS != 'G' && chICORDS != 'N' && chICORDS != 'S' && chICORDS != 'D')
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "Invalid ICOORDS value (%c) for NITFWriteIGEOLO().", chICORDS );
        return FALSE;
    }

/* -------------------------------------------------------------------- */
/*      Format geographic coordinates in DMS                            */
/* -------------------------------------------------------------------- */
    if( chICORDS == 'G' )
    {
        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;
        }

        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' )
    {
        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;
        }

        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.                                      */
/* -------------------------------------------------------------------- */
    if( VSIFSeekL( psImage->psFile->fp,
                  psImage->psFile->pasSegmentInfo[psImage->iSegment].nSegmentHeaderStart + 372, 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( char** 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.  */
        /* ------------------------------------------------------------ */

        pdfXYs[1] =
            CPLAtof(NITFGetField( szTemp, pachCoord, 1, 2 ))
          + CPLAtof(NITFGetField( szTemp, pachCoord, 3, 2 )) / 60.0
          + CPLAtof(NITFGetField( szTemp, pachCoord, 5, 5 )) / 3600.0;

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

        pdfXYs[0] =
            CPLAtof(NITFGetField( szTemp, pachCoord,11, 3 ))
          + CPLAtof(NITFGetField( szTemp, pachCoord,14, 2 )) / 60.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;
}

/************************************************************************/
/*                         NITFFetchAttribute()                         */
/*                                                                      */
/*      Load one attribute given the attribute id, and the parameter    */
/*      id and the number of bytes to fetch.                            */
/************************************************************************/

static int NITFFetchAttribute( GByte *pabyAttributeSubsection,
                               GUInt32 nASSSize, int nAttrCount,
                               int nAttrID, int nParamID, GUInt32 nBytesToFetch,
                               GByte *pabyBuffer )

{
    int i;
    GUInt32 nAttrOffset = 0;

/* -------------------------------------------------------------------- */
/*      Scan the attribute offset table                                 */
/* -------------------------------------------------------------------- */
    for( i = 0; i < nAttrCount; i++ )
    {
        GByte *pabyOffsetRec = i*8 + pabyAttributeSubsection;

        if( (pabyOffsetRec[0] * 256 + pabyOffsetRec[1]) == nAttrID
            && pabyOffsetRec[2] == nParamID )
        {
            memcpy( &nAttrOffset, pabyOffsetRec+4, 4 );
            CPL_MSBPTR32( &nAttrOffset );
            break;
        }
    }

/* -------------------------------------------------------------------- */
/*      Extract the attribute value.                                    */
/* -------------------------------------------------------------------- */
    if( nAttrOffset == 0 )
        return FALSE;

    if( nAttrOffset + nBytesToFetch > nASSSize )
        return FALSE;

    memcpy( pabyBuffer, pabyAttributeSubsection + nAttrOffset, nBytesToFetch );
    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.                                   */
/************************************************************************/

static void NITFLoadAttributeSection( NITFImage *psImage )

{
    int i;
    GUInt32 nASHOffset=0, /* nASHSize=0, */ nASSOffset=0, nASSSize=0, nNextOffset=0;
    GInt16 nAttrCount;
    GByte *pabyAttributeSubsection;
    GByte abyBuffer[128];

    for( 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( 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 for some particular attributes we would like.              */
/* -------------------------------------------------------------------- */
    if( NITFFetchAttribute( pabyAttributeSubsection, nASSSize, nAttrCount,
                            1, 1, 8, abyBuffer ) )
        NITFExtractMetadata( &(psImage->papszMetadata), (char*)abyBuffer, 0, 8,
                             "NITF_RPF_CurrencyDate" );
    if( NITFFetchAttribute( pabyAttributeSubsection, nASSSize, nAttrCount,
                            2, 1, 8, abyBuffer ) )
        NITFExtractMetadata( &(psImage->papszMetadata), (char*)abyBuffer, 0, 8,
                             "NITF_RPF_ProductionDate" );
    if( NITFFetchAttribute( pabyAttributeSubsection, nASSSize, nAttrCount,
                            3, 1, 8, abyBuffer ) )
        NITFExtractMetadata( &(psImage->papszMetadata), (char*)abyBuffer, 0, 8,
                             "NITF_RPF_SignificantDate" );

    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 )
{
    int i;
    NITFFile *psFile = psImage->psFile;
    NITFSegmentInfo *psSegInfo = psFile->pasSegmentInfo + psImage->iSegment;
    GUIntBig  nLocBaseSpatialDataSubsection = psSegInfo->nSegmentStart;
    GUInt32  nLocBaseMaskSubsection = 0;
    GUInt16 subframeSequenceRecordLength, transparencySequenceRecordLength, transparencyOutputPixelCodeLength;
    int 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;
        }
    }
    if (nLocBaseMaskSubsection == 0)
    {
        //fprintf(stderr, "nLocBase(LID_MaskSubsection) == 0\n");
        return;
    }

    //fprintf(stderr, "nLocBaseMaskSubsection = %d\n", nLocBaseMaskSubsection);
    if( VSIFSeekL( psFile->fp, nLocBaseMaskSubsection,
                    SEEK_SET ) != 0  )
    {
        CPLError( CE_Failure, CPLE_FileIO,
                "Failed to seek to %d.",
                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)
    {
      //fprintf(stderr, "subframeSequenceRecordLength=%d\n", subframeSequenceRecordLength);
      return;
    }

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


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);
    }

    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[32];
    VSILFILE* fpTemp;

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

    snprintf(szTempFileName, sizeof(szTempFileName), "/vsimem/%p", pszTRE);
    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 = MAX(MAX(psImage->dfULX,psImage->dfURX),
                            MAX(psImage->dfLRX,psImage->dfLLX));
        double dfXMin = MIN(MIN(psImage->dfULX,psImage->dfURX),
                            MIN(psImage->dfLRX,psImage->dfLLX));
        double dfYMax = MAX(MAX(psImage->dfULY,psImage->dfURY),
                            MAX(psImage->dfLRY,psImage->dfLLY));
        double dfYMin = MIN(MIN(psImage->dfULY,psImage->dfURY),
                            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;
}
