/******************************************************************************
 *
 * Name:     georaster_wrapper.cpp
 * Project:  Oracle Spatial GeoRaster Driver
 * Purpose:  Implement GeoRasterWrapper methods
 * Author:   Ivan Lucena [ivan.lucena at oracle.com]
 *
 ******************************************************************************
 * Copyright (c) 2008, Ivan Lucena
 *
 * 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 <string.h>

#include "georaster_priv.h"
#include "cpl_error.h"
#include "cpl_string.h"
#include "cpl_minixml.h"

CPL_CVSID("$Id: georaster_wrapper.cpp 499f2eb9c7e58d30cb5a11ce5d88589a44a4df7b 2018-10-19 11:35:28 -0700 Fengting Chen $")

//  ---------------------------------------------------------------------------
//                                                           GeoRasterWrapper()
//  ---------------------------------------------------------------------------

GeoRasterWrapper::GeoRasterWrapper() :
    sPyramidResampling  ( "NN" ),
    sCompressionType    ( "NONE" ),
    sInterleaving       ( "BSQ" )
{
    nRasterId           = -1;
    phMetadata          = nullptr;
    nRasterRows         = 0;
    nRasterColumns      = 0;
    nRasterBands        = 0;
    nRowBlockSize       = 0;
    nColumnBlockSize    = 0;
    nBandBlockSize      = 0;
    nTotalColumnBlocks  = 0;
    nTotalRowBlocks     = 0;
    nTotalBandBlocks    = 0;
    nCellSizeBits       = 0;
    nGDALCellBytes      = 0;
    dfXCoefficient[0]   = 1.0;
    dfXCoefficient[1]   = 0.0;
    dfXCoefficient[2]   = 0.0;
    dfYCoefficient[0]   = 0.0;
    dfYCoefficient[1]   = 1.0;
    dfYCoefficient[2]   = 0.0;
    nCompressQuality    = 75;
    bGenPyramid         = false;
    nPyramidLevels      = 0;
    pahLocator          = nullptr;
    pabyBlockBuf        = nullptr;
    pabyCompressBuf     = nullptr;
    bIsReferenced       = false;
    poBlockStmt         = nullptr;
    nCacheBlockId       = -1;
    nCurrentLevel       = -1;
    pahLevels           = nullptr;
    nLevelOffset        = 0L;
    bUpdate             = false;
    bInitializeIO       = false;
    bFlushMetadata      = false;
    nSRID               = DEFAULT_CRS;;
    nExtentSRID         = 0;
    bGenSpatialExtent    = false;
    bCreateObjectTable  = false;
    nPyramidMaxLevel    = 0;
    nBlockCount         = 0L;
    nGDALBlockBytes     = 0L;
    sDInfo.global_state = 0;
    sCInfo.global_state = 0;
    bHasBitmapMask      = false;
    nBlockBytes         = 0L;
    bFlushBlock         = false;
    nFlushBlockSize     = 0L;
    bUniqueFound        = false;
    psNoDataList        = nullptr;
    bWriteOnly          = false;
    bBlocking           = true;
    bAutoBlocking       = false;
    eModelCoordLocation = MCL_DEFAULT;
    phRPC               = nullptr;
    poConnection        = nullptr;
    iDefaultRedBand     = 0;
    iDefaultGreenBand   = 0;
    iDefaultBlueBand    = 0;
    anULTCoordinate[0]  = 0;
    anULTCoordinate[1]  = 0;
    anULTCoordinate[2]  = 0;
    pasGCPList          = nullptr;
    nGCPCount           = 0;
    bFlushGCP           = false;
}

//  ---------------------------------------------------------------------------
//                                                           GeoRasterDataset()
//  ---------------------------------------------------------------------------

GeoRasterWrapper::~GeoRasterWrapper()
{
    FlushMetadata();

    if( pahLocator && nBlockCount )
    {
        OWStatement::Free( pahLocator, static_cast<int>(nBlockCount) );
    }

    CPLFree( pahLocator );
    CPLFree( pabyBlockBuf );
    CPLFree( pabyCompressBuf );
    CPLFree( pahLevels );

    if( bFlushGCP )
    {
        FlushGCP();
        GDALDeinitGCPs( nGCPCount, pasGCPList );
        CPLFree( pasGCPList );
        pasGCPList = nullptr;
        nGCPCount = 0;
    }

    if( CPLListCount( psNoDataList ) )
    {
        CPLList* psList = nullptr;

        for( psList = psNoDataList; psList ; psList = psList->psNext )
        {
            CPLFree( psList->pData );
        }

        CPLListDestroy( psNoDataList );
    }

    if( poBlockStmt )
    {
        delete poBlockStmt;
    }

    CPLDestroyXMLNode( phMetadata );

    if( sDInfo.global_state )
    {
        jpeg_destroy_decompress( &sDInfo );
    }

    if( sCInfo.global_state )
    {
        jpeg_destroy_compress( &sCInfo );
    }

    if( poConnection )
    {
        delete poConnection;
    }

    if( phRPC )
    {
        CPLFree( phRPC );
    }
}

//  ---------------------------------------------------------------------------
//                                                         ParseIdentificator()
//  ---------------------------------------------------------------------------
//
//  StringID:
//      {georaster,geor}:<name>{/,,}<password>{/,@}<db>,<tab>,<col>,<where>
//      {georaster,geor}:<name>{/,,}<password>{/,@}<db>,<rdt>,<rid>
//
//  ---------------------------------------------------------------------------

char** GeoRasterWrapper::ParseIdentificator( const char* pszStringID )
{

    char* pszStartPos = (char*) strstr( pszStringID, ":" ) + 1;

    char** papszParam = CSLTokenizeString2( pszStartPos, ",@",
                            CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS |
                            CSLT_STRIPLEADSPACES | CSLT_STRIPENDSPACES );

    //  -------------------------------------------------------------------
    //  The "/" should not be catch on the previous parser
    //  -------------------------------------------------------------------

    if( CSLCount( papszParam ) > 0 )
    {
        char** papszFirst2 = CSLTokenizeString2( papszParam[0], "/",
                             CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS );
        if( CSLCount( papszFirst2 ) == 2 )
        {
            papszParam = CSLInsertStrings( papszParam, 0, papszFirst2 );
            papszParam = CSLRemoveStrings( papszParam, 2, 1, nullptr );
        }
        CSLDestroy( papszFirst2 );
    }

    return papszParam;
}

//  ---------------------------------------------------------------------------
//                                                                       Open()
//  ---------------------------------------------------------------------------

GeoRasterWrapper* GeoRasterWrapper::Open( const char* pszStringId, bool bUpdate )
{
    char** papszParam = ParseIdentificator( pszStringId );

    //  ---------------------------------------------------------------
    //  Validate identificator
    //  ---------------------------------------------------------------

    int nArgc = CSLCount( papszParam );

    for( ; nArgc < 3; nArgc++ )
    {
        papszParam = CSLAddString( papszParam, "" );
    }

    //  ---------------------------------------------------------------
    //  Create a GeoRasterWrapper object
    //  ---------------------------------------------------------------

    GeoRasterWrapper* poGRW = new GeoRasterWrapper();

    if( ! poGRW )
    {
        return nullptr;
    }

    poGRW->bUpdate = bUpdate;

    //  ---------------------------------------------------------------
    //  Get a connection with Oracle server
    //  ---------------------------------------------------------------

    if( strlen( papszParam[0] ) == 0 &&
        strlen( papszParam[1] ) == 0 &&
        strlen( papszParam[2] ) == 0 )
    {
        /* In an external procedure environment, before opening any
         * dataset, the caller must pass the with_context as an
         * string metadata item OCI_CONTEXT_PTR to the driver. */ 

        OCIExtProcContext* with_context = nullptr;

        const char* pszContext = GDALGetMetadataItem(
                                           GDALGetDriverByName("GEORASTER"), 
                                          "OCI_CONTEXT_PTR", nullptr );

        if( pszContext )
        {
            sscanf( pszContext, "%p", &with_context );

            poGRW->poConnection = new OWConnection( with_context );
        }
    }
    else
    {
        poGRW->poConnection = new OWConnection( papszParam[0],
                                                papszParam[1],
                                                papszParam[2] );
    }

    if( ! poGRW->poConnection || 
        ! poGRW->poConnection->Succeeded() )
    {
        CSLDestroy( papszParam );
        delete poGRW;
        return nullptr;
    }

    //  -------------------------------------------------------------------
    //  Extract schema name
    //  -------------------------------------------------------------------

    if( poGRW->poConnection->IsExtProc() )
    {
        poGRW->sOwner  = poGRW->poConnection->GetExtProcUser();
        poGRW->sSchema = poGRW->poConnection->GetExtProcSchema();
    }
    else
    {
        char** papszSchema = CSLTokenizeString2( papszParam[3], ".",
                                CSLT_HONOURSTRINGS | CSLT_ALLOWEMPTYTOKENS );

        if( CSLCount( papszSchema ) == 2 )
        {
            poGRW->sOwner  = papszSchema[0];
            poGRW->sSchema = CPLSPrintf( "%s.", poGRW->sOwner.c_str() );

            papszParam = CSLRemoveStrings( papszParam, 3, 1, nullptr );

            if( ! EQUAL( papszSchema[1], "" ) )
            {
                papszParam = CSLInsertString( papszParam, 3, papszSchema[1] );
            }

            nArgc = CSLCount( papszParam );
        }
        else
        {
            poGRW->sSchema = "";
            poGRW->sOwner  = poGRW->poConnection->GetUser();
        }

        CSLDestroy( papszSchema );
    }

    //  -------------------------------------------------------------------
    //  Assign parameters from Identification string
    //  -------------------------------------------------------------------

    switch( nArgc )
    {
    case 6 :
        poGRW->sTable   = papszParam[3];
        poGRW->sColumn  = papszParam[4];
        poGRW->sWhere   = papszParam[5];
        break;
    case 5 :
        if( OWIsNumeric( papszParam[4] ) )
        {
            poGRW->sDataTable   = papszParam[3];
            poGRW->nRasterId    = (long long) CPLAtoGIntBig( papszParam[4]);
            break;
        }
        else
        {
            poGRW->sTable   = papszParam[3];
            poGRW->sColumn  = papszParam[4];
            return poGRW;
        }
    case 4 :
        poGRW->sTable   = papszParam[3];
        return poGRW;
    default :
        return poGRW;
    }

    CSLDestroy( papszParam );

    //  -------------------------------------------------------------------
    //  Query all the basic information at once to reduce round trips
    //  -------------------------------------------------------------------

    char szOwner[OWCODE];
    char szTable[OWCODE];
    char szColumn[OWTEXT];
    char szDataTable[OWCODE];
    char szWhere[OWTEXT];
    long long nRasterId = -1;
    OCILobLocator* phLocator = nullptr;

    szOwner[0]     = '\0';
    szTable[0]     = '\0';
    szColumn[0]    = '\0';
    szDataTable[0] = '\0';
    szWhere[0]     = '\0';

    if( ! poGRW->sOwner.empty() )
    {
      strcpy( szOwner, poGRW->sOwner.c_str() );
    }

    if( ! poGRW->sTable.empty() )
    {
      strcpy( szTable, poGRW->sTable.c_str() );
    }

    if( ! poGRW->sColumn.empty() )
    {
      strcpy( szColumn, poGRW->sColumn.c_str() );
    }

    if( ! poGRW->sDataTable.empty() )
    {
      strcpy( szDataTable, poGRW->sDataTable.c_str() );
    }

    nRasterId = poGRW->nRasterId;

    if( ! poGRW->sWhere.empty() )
    {
      strcpy( szWhere, poGRW->sWhere.c_str() );
    }

    OWStatement* poStmt = poGRW->poConnection->CreateStatement(
      "BEGIN\n"
      "\n"
      "    IF :datatable IS NOT NULL AND :rasterid IS NOT NULL THEN\n"
      "\n"
      "      EXECUTE IMMEDIATE\n"
      "        'SELECT OWNER, TABLE_NAME, COLUMN_NAME\n"
      "         FROM   ALL_SDO_GEOR_SYSDATA\n"
      "         WHERE  RDT_TABLE_NAME = UPPER(:1)\n"
      "           AND  RASTER_ID = :2'\n"
      "        INTO  :owner, :table, :column\n"
      "        USING :datatable, :rasterid;\n"
      "\n"
      "      EXECUTE IMMEDIATE\n"
      "        'SELECT T.'||:column||'.METADATA.getClobVal()\n"
      "         FROM   '||:owner||'.'||:table||' T\n"
      "         WHERE  T.'||:column||'.RASTERDATATABLE = UPPER(:1)\n"
      "           AND  T.'||:column||'.RASTERID = :2'\n"
      "        INTO  :metadata\n"
      "        USING :datatable, :rasterid;\n"
      "      :counter := 1;\n"
      "\n"
      "    ELSE\n"
      "\n"
      "      EXECUTE IMMEDIATE\n"
      "        'SELECT T.'||:column||'.RASTERDATATABLE,\n"
      "                T.'||:column||'.RASTERID,\n"
      "                T.'||:column||'.METADATA.getClobVal()\n"
      "         FROM  '||:owner||'.'||:table||' T\n"
      "         WHERE '||:where\n"
      "        INTO  :datatable, :rasterid, :metadata;\n"
      "      :counter := 1;\n"
      "\n"
      "    END IF;\n"
      "\n"
      "  EXCEPTION\n"
      "    WHEN no_data_found THEN :counter := 0;\n"
      "    WHEN too_many_rows THEN :counter := 2;\n"
      "END;" );

    int nCounter = 0;

    poStmt->BindName( ":datatable", szDataTable );
    poStmt->BindName( ":rasterid", &nRasterId );
    poStmt->BindName( ":owner", szOwner );
    poStmt->BindName( ":table", szTable );
    poStmt->BindName( ":column", szColumn );
    poStmt->BindName( ":where", szWhere );
    poStmt->BindName( ":counter", &nCounter );
    poStmt->BindName( ":metadata", &phLocator );

    CPLErrorReset();

    if( ! poStmt->Execute() )
    {
        delete poStmt;
        delete poGRW;
        return nullptr;
    }

    if( nCounter < 1 )
    {
        delete poStmt;
        delete poGRW;
        return nullptr;
    }

    poGRW->sSchema  = CPLSPrintf( "%s.", szOwner );
    poGRW->sOwner   = szOwner;
    poGRW->sTable   = szTable;
    poGRW->sColumn  = szColumn;

    if( nCounter == 1 )
    {
        poGRW->bUniqueFound = true;
    }
    else
    {
        poGRW->bUniqueFound = false;

        delete poStmt;
        return poGRW;
    }

    poGRW->sDataTable   = szDataTable;
    poGRW->nRasterId    = nRasterId;
    poGRW->sWhere       = CPLSPrintf(
        "T.%s.RASTERDATATABLE = UPPER('%s') AND T.%s.RASTERID = %lld",
        poGRW->sColumn.c_str(),
        poGRW->sDataTable.c_str(),
        poGRW->sColumn.c_str(),
        poGRW->nRasterId );

    //  -------------------------------------------------------------------
    //  Read Metadata XML in text
    //  -------------------------------------------------------------------

    CPLPushErrorHandler( CPLQuietErrorHandler );

    char* pszXML = poStmt->ReadCLob( phLocator );

    CPLPopErrorHandler();

    if( pszXML )
    {
        //  -----------------------------------------------------------
        //  Get basic information from xml metadata
        //  -----------------------------------------------------------

        poGRW->phMetadata = CPLParseXMLString( pszXML );
        poGRW->GetRasterInfo();
        poGRW->GetSpatialReference();
    }
    else
    {
        poGRW->sDataTable = "";
        poGRW->nRasterId  = 0;
    }

    //  -------------------------------------------------------------------
    //  Clean up
    //  -------------------------------------------------------------------

    poStmt->FreeLob(phLocator);
    CPLFree( pszXML );
    delete poStmt;

    //  -------------------------------------------------------------------
    //  Return a GeoRasterWrapper object
    //  -------------------------------------------------------------------

    return poGRW;
}

//  ---------------------------------------------------------------------------
//                                                                     Create()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::Create( char* pszDescription,
                               char* pszInsert,
                               bool bUpdateIn )
{
    CPLString sValues;
    CPLString sFormat;

    if( sTable.empty() ||
        sColumn.empty() )
    {
        return false;
    }

    //  -------------------------------------------------------------------
    //  Parse RDT/RID from the current szValues
    //  -------------------------------------------------------------------

    char szRDT[OWNAME];
    char szRID[OWCODE];

    if( ! sDataTable.empty() )
    {
        strcpy( szRDT, CPLSPrintf( "'%s'", sDataTable.c_str() ) );
    }
    else
    {
        strcpy( szRDT, OWParseSDO_GEOR_INIT( sValues.c_str(), 1 ) );
    }

    if ( nRasterId > 0 )
    {
        strcpy( szRID, CPLSPrintf( "%lld", nRasterId ) );
    }
    else
    {
        strcpy( szRID, OWParseSDO_GEOR_INIT( sValues.c_str(), 2 ) );

        if ( EQUAL( szRID, "" ) )
        {
            strcpy( szRID, "NULL" );
        }
    }

    //  -------------------------------------------------------------------
    //  Description parameters
    //  -------------------------------------------------------------------

    char szDescription[OWTEXT];

    if( bUpdateIn == false )
    {

        if ( pszDescription  )
        {
            strcpy( szDescription, pszDescription );
        }
        else
        {
            strcpy( szDescription, CPLSPrintf(
                "(%s MDSYS.SDO_GEORASTER)", sColumn.c_str() ) );
        }

        //  ---------------------------------------------------------------
        //  Insert parameters
        //  ---------------------------------------------------------------

        if( pszInsert )
        {
            sValues = pszInsert;

            if( pszInsert[0] == '(' && sValues.ifind( "VALUES" ) == std::string::npos )
            {
                sValues = CPLSPrintf( "VALUES %s", pszInsert );
            }
        }
        else
        {
            sValues = CPLSPrintf( "VALUES (SDO_GEOR.INIT(%s,%s))", szRDT, szRID );
        }
    }

    //  -----------------------------------------------------------
    //  Storage parameters
    //  -----------------------------------------------------------

    nColumnBlockSize = nColumnBlockSize == 0 ? DEFAULT_BLOCK_COLUMNS : nColumnBlockSize;
    nRowBlockSize    = nRowBlockSize    == 0 ? DEFAULT_BLOCK_ROWS    : nRowBlockSize;
    nBandBlockSize   = nBandBlockSize   == 0 ? 1 : nBandBlockSize;

    //  -----------------------------------------------------------
    //  Blocking storage parameters
    //  -----------------------------------------------------------

    CPLString sBlocking;

    if( bBlocking == true )
    {
        if( bAutoBlocking == true )
        {
            int nBlockXSize = nColumnBlockSize;
            int nBlockYSize = nRowBlockSize;
            int nBlockBSize = nBandBlockSize;

            OWStatement* poStmt = poConnection->CreateStatement(
                "DECLARE\n"
                "  dimensionSize    sdo_number_array;\n"
                "  blockSize        sdo_number_array;\n"
                "BEGIN\n"
                "  dimensionSize := sdo_number_array(:1, :2, :3);\n"
                "  blockSize     := sdo_number_array(:4, :5, :6);\n"
                "  sdo_geor_utl.calcOptimizedBlockSize(dimensionSize,blockSize);\n"
                "  :4 := blockSize(1);\n"
                "  :5 := blockSize(2);\n"
                "  :6 := blockSize(3);\n"
                "END;" );

            poStmt->Bind( &nRasterColumns );
            poStmt->Bind( &nRasterRows );
            poStmt->Bind( &nRasterBands );
            poStmt->Bind( &nBlockXSize );
            poStmt->Bind( &nBlockYSize );
            poStmt->Bind( &nBlockBSize );

            if( poStmt->Execute() )
            {
                nColumnBlockSize = nBlockXSize;
                nRowBlockSize = nBlockYSize;
                nBandBlockSize = nBlockBSize;
            }

            delete poStmt;
        }

        if( nRasterBands == 1 )
        {
            sBlocking = CPLSPrintf(
                "blockSize=(%d, %d)",
                nRowBlockSize,
                nColumnBlockSize );
        }
        else
        {
            sBlocking = CPLSPrintf(
                "blockSize=(%d, %d, %d)",
                nRowBlockSize,
                nColumnBlockSize,
                nBandBlockSize );
        }
    }
    else
    {
        sBlocking = "blocking=FALSE";

        nColumnBlockSize = nRasterColumns;
        nRowBlockSize = nRasterRows;
        nBandBlockSize = nRasterBands;
    }

    //  -----------------------------------------------------------
    //  Complete format parameters
    //  -----------------------------------------------------------

    if( poConnection->GetVersion() > 10 )
    {
        if( nRasterBands == 1 )
        {
            sFormat = CPLSPrintf(
                "20001, '"
                "dimSize=(%d,%d) ",
                nRasterRows, nRasterColumns );
        }
        else
        {
            sFormat = CPLSPrintf(
                "21001, '"
                "dimSize=(%d,%d,%d) ",
                nRasterRows, nRasterColumns, nRasterBands );
        }

        if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
        {
            sFormat.append( CPLSPrintf(
                    "%s "
                    "cellDepth=%s "
                    "interleaving=%s "
                    "compression=%s "
                    "quality=%d'",
                    sBlocking.c_str(),
                    sCellDepth.c_str(),
                    sInterleaving.c_str(),
                    sCompressionType.c_str(),
                    nCompressQuality) );
        }
        else
        {
            sFormat.append( CPLSPrintf(
                    "%s "
                    "cellDepth=%s "
                    "interleaving=%s "
                    "compression=%s'",
                    sBlocking.c_str(),
                    sCellDepth.c_str(),
                    sInterleaving.c_str(),
                    sCompressionType.c_str() ) );
        }
    }
    else
    {
        //  -------------------------------------------------------
        //  For versions 10g or older
        //  -------------------------------------------------------

        sFormat = CPLSPrintf(
            "%s "
            "cellDepth=%s "
            "interleaving=%s "
            "pyramid=FALSE "
            "compression=NONE",
            sBlocking.c_str(),
            sCellDepth.c_str(),
            sInterleaving.c_str() );
    }

    nTotalColumnBlocks = (int)
        ( ( nRasterColumns + nColumnBlockSize - 1 ) / nColumnBlockSize );

    nTotalRowBlocks = (int)
        ( ( nRasterRows + nRowBlockSize - 1 ) / nRowBlockSize );

    nTotalBandBlocks = (int)
        ( ( nRasterBands + nBandBlockSize - 1) / nBandBlockSize );

    //  -------------------------------------------------------------------
    //  Create Georaster Table if needed
    //  -------------------------------------------------------------------

    OWStatement* poStmt;

    if( ! bUpdateIn )
    {
        poStmt = poConnection->CreateStatement( CPLSPrintf(
            "DECLARE\n"
            "  TAB VARCHAR2(68)  := UPPER('%s');\n"
            "  COL VARCHAR2(68)  := UPPER('%s');\n"
            "  OWN VARCHAR2(68)  := UPPER('%s');\n"
            "  CNT NUMBER        := 0;\n"
            "BEGIN\n"
            "  EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ALL_TABLES\n"
            "    WHERE TABLE_NAME = :1 AND OWNER = UPPER(:2)'\n"
            "      INTO CNT USING TAB, OWN;\n"
            "\n"
            "  IF CNT = 0 THEN\n"
            "    EXECUTE IMMEDIATE 'CREATE TABLE %s%s %s';\n"
            "    SDO_GEOR_UTL.createDMLTrigger( TAB,  COL );\n"
            "  END IF;\n"
            "END;",
                sTable.c_str(),
                sColumn.c_str(),
                sOwner.c_str(),
                sSchema.c_str(),
                sTable.c_str(),
                szDescription ) );

        if( ! poStmt->Execute() )
        {
            delete ( poStmt );
            return false;
        }

        delete poStmt;
    }

    //  -----------------------------------------------------------
    //  Prepare UPDATE or INSERT command
    //  -----------------------------------------------------------

    CPLString sCommand;

    if( bUpdateIn )
    {
        sCommand = CPLSPrintf(
            "SELECT %s INTO GR1 FROM %s%s T WHERE %s FOR UPDATE;",
            sColumn.c_str(),
            sSchema.c_str(),
            sTable.c_str(),
            sWhere.c_str() );
    }
    else
    {
        sCommand = CPLSPrintf(
            "INSERT INTO %s%s %s RETURNING %s INTO GR1;",
            sSchema.c_str(),
            sTable.c_str(),
            sValues.c_str(),
            sColumn.c_str() );
    }

    //  -----------------------------------------------------------
    //  Create RTD if needed and insert/update GeoRaster
    //  -----------------------------------------------------------

    char szBindRDT[OWNAME];
    long long nBindRID = 0;
    szBindRDT[0] = '\0';

    CPLString sObjectTable;
    CPLString sSecureFile;

    // For version > 11 create RDT as relational table by default,
    // if it is not specified by create-option OBJECTTABLE=TRUE

    if( poConnection->GetVersion() <= 11 || bCreateObjectTable )
    {
        sObjectTable = "OF MDSYS.SDO_RASTER\n      (";
    }
    else
    {
        sObjectTable = CPLSPrintf("(\n"
                       "      RASTERID           NUMBER,\n"
                       "      PYRAMIDLEVEL       NUMBER,\n"
                       "      BANDBLOCKNUMBER    NUMBER,\n"
                       "      ROWBLOCKNUMBER     NUMBER,\n"
                       "      COLUMNBLOCKNUMBER  NUMBER,\n"
                       "      BLOCKMBR           SDO_GEOMETRY,\n"
                       "      RASTERBLOCK        BLOB,\n"
                       "      CONSTRAINT '||:rdt||'_RDT_PK ");
    }

    // For version >= 11 create RDT rasterBlock as securefile

    if( poConnection->GetVersion() >= 11 )
    {
        sSecureFile = "SECUREFILE(CACHE)";
    }
    else
    {
        sSecureFile = "(NOCACHE NOLOGGING)";
    }

    if( poConnection->GetVersion() > 10 )
    {
        poStmt = poConnection->CreateStatement( CPLSPrintf(
            "DECLARE\n"
            "  TAB  VARCHAR2(68)    := UPPER('%s');\n"
            "  COL  VARCHAR2(68)    := UPPER('%s');\n"
            "  OWN  VARCHAR2(68)    := UPPER('%s');\n"
            "  CNT  NUMBER          := 0;\n"
            "  GR1  SDO_GEORASTER   := NULL;\n"
            "BEGIN\n"
            "\n"
            "  %s\n"
            "\n"
            "  GR1.spatialExtent := NULL;\n"
            "\n"
            "  SELECT GR1.RASTERDATATABLE INTO :rdt FROM DUAL;\n"
            "  SELECT GR1.RASTERID        INTO :rid FROM DUAL;\n"
            "\n"
            "  EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ALL_OBJECT_TABLES\n"
            "    WHERE TABLE_NAME = :1 AND OWNER = UPPER(:2)'\n"
            "      INTO CNT USING :rdt, OWN;\n"
            "\n"
            "  IF CNT = 0 THEN\n"
            "    EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ALL_TABLES\n"
            "      WHERE TABLE_NAME = :1 AND OWNER = UPPER(:2)'\n"
            "        INTO CNT USING :rdt, OWN;\n"
            "  END IF;\n"
            "\n"
            "  IF CNT = 0 THEN\n"
            "    EXECUTE IMMEDIATE 'CREATE TABLE %s'||:rdt||' %s"
            "PRIMARY KEY (RASTERID, PYRAMIDLEVEL,\n"
            "      BANDBLOCKNUMBER, ROWBLOCKNUMBER, COLUMNBLOCKNUMBER))\n"
            "      LOB(RASTERBLOCK) STORE AS %s';\n"
            "  END IF;\n"
            "\n"
            "  SDO_GEOR.createTemplate(GR1, %s, null, 'TRUE');\n"
            "\n"
            "  UPDATE %s%s T SET %s = GR1 WHERE\n"
            "    T.%s.RasterDataTable = :rdt AND\n"
            "    T.%s.RasterId = :rid;\n"
            "\n"
            "  EXECUTE IMMEDIATE\n"
            "    'SELECT T.%s.METADATA.getClobVal()\n"
            "     FROM   %s%s T\n"
            "     WHERE  T.%s.RASTERDATATABLE = UPPER(:1)\n"
            "       AND  T.%s.RASTERID = :2'\n"
            "      INTO  :metadata\n"
            "     USING  :rdt, :rid;\n"
            "\n"
            "END;\n",
                sTable.c_str(),
                sColumn.c_str(),
                sOwner.c_str(),
                sCommand.c_str(),
                sSchema.c_str(),
                sObjectTable.c_str(),
                sSecureFile.c_str(),
                sFormat.c_str(),
                sSchema.c_str(),
                sTable.c_str(),
                sColumn.c_str(),
                sColumn.c_str(),
                sColumn.c_str(),
                sColumn.c_str(),
                sSchema.c_str(),
                sTable.c_str(),
                sColumn.c_str(),
                sColumn.c_str() ) );

        OCILobLocator* phLocator = nullptr;

        poStmt->BindName( ":metadata", &phLocator );
        poStmt->BindName( ":rdt", szBindRDT );
        poStmt->BindName( ":rid", &nBindRID );

        CPLErrorReset();

        if( ! poStmt->Execute() )
        {
            delete poStmt;
            return false;
        }

        sDataTable = szBindRDT;
        nRasterId  = nBindRID;

        poStmt->FreeLob(phLocator);

        delete poStmt;

        return true;
    }

    //  -----------------------------------------------------------
    //  Procedure for Server version older than 11
    //  -----------------------------------------------------------

    char szCreateBlank[OWTEXT];

    if( nRasterBands == 1 )
    {
        strcpy( szCreateBlank, CPLSPrintf( "SDO_GEOR.createBlank(20001, "
            "SDO_NUMBER_ARRAY(0, 0), "
            "SDO_NUMBER_ARRAY(%d, %d), 0, :rdt, :rid)",
            nRasterRows, nRasterColumns ) );
    }
    else
    {
        strcpy( szCreateBlank, CPLSPrintf( "SDO_GEOR.createBlank(21001, "
            "SDO_NUMBER_ARRAY(0, 0, 0), "
            "SDO_NUMBER_ARRAY(%d, %d, %d), 0, :rdt, :rid)",
            nRasterRows, nRasterColumns, nRasterBands ) );
    }

    poStmt = poConnection->CreateStatement( CPLSPrintf(
        "DECLARE\n"
        "  W    NUMBER          := :1;\n"
        "  H    NUMBER          := :2;\n"
        "  BB   NUMBER          := :3;\n"
        "  RB   NUMBER          := :4;\n"
        "  CB   NUMBER          := :5;\n"
        "  OWN  VARCHAR2(68)    := UPPER('%s');\n"
        "  X    NUMBER          := 0;\n"
        "  Y    NUMBER          := 0;\n"
        "  CNT  NUMBER          := 0;\n"
        "  GR1  SDO_GEORASTER   := NULL;\n"
        "  GR2  SDO_GEORASTER   := NULL;\n"
        "  STM  VARCHAR2(1024)  := '';\n"
        "BEGIN\n"
        "\n"
        "  %s\n"
        "\n"
        "  SELECT GR1.RASTERDATATABLE INTO :rdt FROM DUAL;\n"
        "  SELECT GR1.RASTERID        INTO :rid FROM DUAL;\n"
        "\n"
        "  SELECT %s INTO GR2 FROM %s%s T WHERE"
        " T.%s.RasterDataTable = :rdt AND"
        " T.%s.RasterId = :rid FOR UPDATE;\n"
        "\n"
        "  GR1 := %s;\n"
        "\n"
        "  SDO_GEOR.changeFormatCopy(GR1, '%s', GR2);\n"
        "\n"
        "  UPDATE %s%s T SET %s = GR2 WHERE"
        " T.%s.RasterDataTable = :rdt AND"
        " T.%s.RasterId = :rid;\n"
        "\n"
        "  EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM ALL_OBJECT_TABLES\n"
        "    WHERE TABLE_NAME = :1 AND OWNER = UPPER(:2)'\n"
        "      INTO CNT USING :rdt, OWN;\n"
        "\n"
        "  IF CNT = 0 THEN\n"
        "    EXECUTE IMMEDIATE 'CREATE TABLE %s'||:rdt||' OF MDSYS.SDO_RASTER\n"
        "      (PRIMARY KEY (RASTERID, PYRAMIDLEVEL, BANDBLOCKNUMBER,\n"
        "      ROWBLOCKNUMBER, COLUMNBLOCKNUMBER))\n"
        "      LOB(RASTERBLOCK) STORE AS (NOCACHE NOLOGGING)';\n"
        "  ELSE\n"
        "    EXECUTE IMMEDIATE 'DELETE FROM %s'||:rdt||' WHERE RASTERID ='||:rid||' ';\n"
        "  END IF;\n"
        "\n"
        "  STM := 'INSERT INTO %s'||:rdt||' VALUES (:1,0,:2-1,:3-1,:4-1,\n"
        "    SDO_GEOMETRY(2003, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 3),\n"
        "    SDO_ORDINATE_ARRAY(:5,:6,:7-1,:8-1)), EMPTY_BLOB() )';\n\n"
        "  FOR b IN 1..BB LOOP\n"
        "    Y := 0;\n"
        "    FOR r IN 1..RB LOOP\n"
        "      X := 0;\n"
        "      FOR c IN 1..CB LOOP\n"
        "        EXECUTE IMMEDIATE STM USING :rid, b, r, c, Y, X, (Y+H), (X+W);\n"
        "        X := X + W;\n"
        "      END LOOP;\n"
        "      Y := Y + H;\n"
        "    END LOOP;\n"
        "  END LOOP;\n"
        "\n"
        "  SDO_GEOR.georeference(GR1, %d, %d,"
        " SDO_NUMBER_ARRAY(1.0, 0.0, 0.0),"
        " SDO_NUMBER_ARRAY(0.0, 1.0, 0.0));\n"
        "\n"
        "  UPDATE %s%s T SET %s = GR1 WHERE"
        " T.%s.RasterDataTable = :rdt AND"
        " T.%s.RasterId = :rid;\n"
        "\n"
        "END;",
            sOwner.c_str(),
            sCommand.c_str(),
            sColumn.c_str(), sSchema.c_str(), sTable.c_str(),
            sColumn.c_str(), sColumn.c_str(),
            szCreateBlank,
            sFormat.c_str(),
            sSchema.c_str(), sTable.c_str(),
            sColumn.c_str(), sColumn.c_str(), sColumn.c_str(),
            sSchema.c_str(), sSchema.c_str(), sSchema.c_str(),
            DEFAULT_CRS, MCL_DEFAULT,
            sSchema.c_str(), sTable.c_str(),
            sColumn.c_str(), sColumn.c_str(), sColumn.c_str() ) );

    poStmt->Bind( &nColumnBlockSize );
    poStmt->Bind( &nRowBlockSize );
    poStmt->Bind( &nTotalBandBlocks );
    poStmt->Bind( &nTotalRowBlocks );
    poStmt->Bind( &nTotalColumnBlocks );

    poStmt->BindName( ":rdt", szBindRDT );
    poStmt->BindName( ":rid", &nBindRID );

    if( ! poStmt->Execute() )
    {
        delete poStmt;
        return false;
    }

    sDataTable = szBindRDT;
    nRasterId  = nBindRID;

    delete poStmt;

    return true;
}

//  ---------------------------------------------------------------------------
//                                                         PrepareToOverwrite()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::PrepareToOverwrite( void )
{
    nTotalColumnBlocks  = 0;
    nTotalRowBlocks     = 0;
    nTotalBandBlocks    = 0;
    if( sscanf( sCellDepth.c_str(), "%dBIT", &nCellSizeBits ) )
    {
        nGDALCellBytes   = GDALGetDataTypeSize(
                          OWGetDataType( sCellDepth.c_str() ) ) / 8;
    }
    else
    {
        nGDALCellBytes   = 1;
    }
    dfXCoefficient[0]   = 1.0;
    dfXCoefficient[1]   = 0.0;
    dfXCoefficient[2]   = 0.0;
    dfYCoefficient[0]   = 0.0;
    dfYCoefficient[1]   = 1.0;
    dfYCoefficient[2]   = 0.0;
    sCompressionType    = "NONE";
    nCompressQuality    = 75;
    bGenPyramid         = false;
    nPyramidLevels      = 0;
    sPyramidResampling  = "NN";
    bIsReferenced       = false;
    nCacheBlockId       = -1;
    nCurrentLevel       = -1;
    pahLevels           = nullptr;
    nLevelOffset        = 0L;
    sInterleaving       = "BSQ";
    bUpdate             = false;
    bInitializeIO       = false;
    bFlushMetadata      = false;
    nSRID               = DEFAULT_CRS;;
    nExtentSRID         = 0;
    bGenSpatialExtent    = false;
    bCreateObjectTable  = false;
    nPyramidMaxLevel    = 0;
    nBlockCount         = 0L;
    sDInfo.global_state = 0;
    sCInfo.global_state = 0;
    bHasBitmapMask      = false;
    bWriteOnly          = false;
    bBlocking           = true;
    bAutoBlocking       = false;
    eModelCoordLocation = MCL_DEFAULT;
    bFlushBlock         = false;
    nFlushBlockSize     = 0L;
    phRPC               = nullptr;
}

//  ---------------------------------------------------------------------------
//                                                                     Delete()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::Delete( void )
{
    if( ! bUniqueFound )
    {
        return false;
    }

    OWStatement* poStmt = poConnection->CreateStatement( CPLSPrintf(
      "UPDATE %s%s T SET %s = NULL WHERE %s\n",
            sSchema.c_str(),
            sTable.c_str(),
            sColumn.c_str(),
            sWhere.c_str() ) );

    bool bReturn = poStmt->Execute();

    delete poStmt;

    return bReturn;
}

//  ---------------------------------------------------------------------------
//                                                            SetGeoReference()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::SetGeoReference( long long nSRIDIn )
{
    nSRID = nSRIDIn;

    bIsReferenced = true;

    bFlushMetadata = true;
}

//  ---------------------------------------------------------------------------
//                                                              GetRasterInfo()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::GetRasterInfo( void )
{
    //  -------------------------------------------------------------------
    //  Get dimensions
    //  -------------------------------------------------------------------

    int nCount      = atoi( CPLGetXMLValue( phMetadata,
                  "rasterInfo.totalDimensions", "0" ) );
    CPLXMLNode* phDimSize   = CPLGetXMLNode( phMetadata, "rasterInfo.dimensionSize" );

    int i = 0;

    for( i = 0; i < nCount; i++ )
    {
        const char* pszType = CPLGetXMLValue( phDimSize, "type", "0" );

        if( EQUAL( pszType, "ROW" ) )
        {
            nRasterRows = atoi( CPLGetXMLValue( phDimSize, "size", "0" ) );
        }

        if( EQUAL( pszType, "COLUMN" ) )
        {
            nRasterColumns = atoi( CPLGetXMLValue( phDimSize, "size", "0" ) );
        }

        if( EQUAL( pszType, "BAND" ) )
        {
            nRasterBands = atoi( CPLGetXMLValue( phDimSize, "size", "0" ) );
        }

        phDimSize = phDimSize->psNext;
    }

    if( nRasterBands == 0 )
    {
        nRasterBands = 1;
    }

    //  -------------------------------------------------------------------
    //  Load NoData Values
    //  -------------------------------------------------------------------

    LoadNoDataValues();

    //  -------------------------------------------------------------------
    //  Get ULTCoordinate values
    //  -------------------------------------------------------------------

    anULTCoordinate[0] = atoi(CPLGetXMLValue(
            phMetadata, "rasterInfo.ULTCoordinate.column", "0"));

    anULTCoordinate[1] = atoi(CPLGetXMLValue(
            phMetadata, "rasterInfo.ULTCoordinate.row", "0"));

    anULTCoordinate[2] = atoi(CPLGetXMLValue(
            phMetadata, "rasterInfo.ULTCoordinate.band", "0"));

    //  -------------------------------------------------------------------
    //  Get Interleaving mode
    //  -------------------------------------------------------------------

    sInterleaving = CPLGetXMLValue( phMetadata,
        "rasterInfo.interleaving", "BSQ" );

    //  -------------------------------------------------------------------
    //  Get blocking
    //  -------------------------------------------------------------------

    nRowBlockSize       = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.rowBlockSize",
                            CPLSPrintf( "%d", nRasterRows ) ) );

    nColumnBlockSize    = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.columnBlockSize",
                            CPLSPrintf( "%d", nRasterColumns ) ) );

    nBandBlockSize      = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.bandBlockSize",
                            CPLSPrintf( "%d", nRasterBands ) ) );

    nTotalColumnBlocks  = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.totalColumnBlocks","1") );

    nTotalRowBlocks     = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.totalRowBlocks", "1" ) );

    nTotalBandBlocks    = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.blocking.totalBandBlocks", "1" ) );

    if( nBandBlockSize == -1 )
    {
       nBandBlockSize = nRasterBands;
    }

    //  -------------------------------------------------------------------
    //  Get data type
    //  -------------------------------------------------------------------

    sCellDepth = CPLGetXMLValue( phMetadata, "rasterInfo.cellDepth", "8BIT_U" );

    if( sscanf( sCellDepth.c_str(), "%dBIT", &nCellSizeBits ) )
    {
        nGDALCellBytes   = GDALGetDataTypeSize(
            OWGetDataType( sCellDepth.c_str() ) ) / 8;
    }
    else
    {
        nGDALCellBytes   = 1;
    }

    sCompressionType  = CPLGetXMLValue( phMetadata,
        "rasterInfo.compression.type", "NONE" );

    if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
    {
        nCompressQuality = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.compression.quality", "75" ) );
    }

    if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
    {
        sInterleaving = "BIP";
    }

    //  -------------------------------------------------------------------
    //  Get default RGB Bands
    //  -------------------------------------------------------------------

    iDefaultRedBand     = atoi( CPLGetXMLValue( phMetadata,
                            "objectInfo.defaultRed", "-1" ) );

    iDefaultGreenBand   = atoi( CPLGetXMLValue( phMetadata,
                            "objectInfo.defaultGreen", "-1" ) );

    iDefaultBlueBand    = atoi( CPLGetXMLValue( phMetadata,
                            "objectInfo.defaultBlue", "-1" ) );

    //  -------------------------------------------------------------------
    //  Get Pyramid details
    //  -------------------------------------------------------------------

    char szPyramidType[OWCODE];

    strcpy( szPyramidType, CPLGetXMLValue( phMetadata,
                            "rasterInfo.pyramid.type", "None" ) );

    if( EQUAL( szPyramidType, "DECREASE" ) )
    {
        nPyramidMaxLevel = atoi( CPLGetXMLValue( phMetadata,
                            "rasterInfo.pyramid.maxLevel", "0" ) );
    }

    //  -------------------------------------------------------------------
    //  Check for RPCs
    //  -------------------------------------------------------------------

    CPLXMLNode* phModelType = CPLGetXMLNode( phMetadata,
        "spatialReferenceInfo.modelType");

    for( int n = 1 ; phModelType ; phModelType = phModelType->psNext, n++ )
    {
        const char* pszModelType = CPLGetXMLValue( phModelType, ".", "None" );

        if( EQUAL( pszModelType, "StoredFunction" ) )
        {
            GetGCP();
        }

        if( EQUAL( pszModelType, "FunctionalFitting" ) )
        {
            GetRPC();
        }
    }

    //  -------------------------------------------------------------------
    //  Prepare to get Spatial reference system
    //  -------------------------------------------------------------------

    bIsReferenced       = EQUAL( "TRUE", CPLGetXMLValue( phMetadata,
                            "spatialReferenceInfo.isReferenced", "FALSE" ) );

    nSRID               = (long long) CPLAtoGIntBig( CPLGetXMLValue( phMetadata,
                            "spatialReferenceInfo.SRID", "0" ) );

    if( bIsReferenced == false || nSRID == 0 || nSRID == UNKNOWN_CRS )
    {
        return;
    }

}

//  ---------------------------------------------------------------------------
//                                                                QueryWKText()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::QueryWKText()
{
    char* pszWKText = (char*) VSI_MALLOC2_VERBOSE( sizeof(char), 3 * OWTEXT);
    char* pszAuthority = (char*) VSI_MALLOC2_VERBOSE( sizeof(char), OWTEXT);

    OWStatement* poStmt = poConnection->CreateStatement(
        "select wktext, auth_name from mdsys.cs_srs "
        "where srid = :1 and wktext is not null" );

    poStmt->Bind( &nSRID );
    poStmt->Define( pszWKText, 3 * OWTEXT );
    poStmt->Define( pszAuthority, OWTEXT );

    if( poStmt->Execute() )
    {
        sWKText = pszWKText;
        sAuthority = pszAuthority;
    }

    CPLFree( pszWKText );
    CPLFree( pszAuthority );

    delete poStmt;
}

//  ---------------------------------------------------------------------------
//                                                              GetStatistics()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::GetStatistics( int nBand,
                                      char* pszMin,
                                      char* pszMax,
                                      char* pszMean,
                                      char* pszMedian,
                                      char* pszMode,
                                      char* pszStdDev,
                                      char* pszSampling )
{
    int n = 1;

    CPLXMLNode *phSubLayer = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( n = 1 ; phSubLayer ; phSubLayer = phSubLayer->psNext, n++ )
    {
        if( n == nBand && CPLGetXMLNode( phSubLayer, "statisticDataset" ) )
        {
            strncpy( pszSampling, CPLGetXMLValue( phSubLayer,
                "statisticDataset.samplingFactor",  "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszMin, CPLGetXMLValue( phSubLayer,
                "statisticDataset.MIN",  "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszMax, CPLGetXMLValue( phSubLayer,
                "statisticDataset.MAX",  "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszMean, CPLGetXMLValue( phSubLayer,
                "statisticDataset.MEAN", "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszMedian, CPLGetXMLValue( phSubLayer,
                "statisticDataset.MEDIAN", "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszMode, CPLGetXMLValue( phSubLayer,
                "statisticDataset.MODEVALUE", "0.0" ), MAX_DOUBLE_STR_REP );
            strncpy( pszStdDev, CPLGetXMLValue( phSubLayer,
                "statisticDataset.STD",  "0.0" ), MAX_DOUBLE_STR_REP );
            return true;
        }
    }
    return false;
}

//  ---------------------------------------------------------------------------
//                                                              SetStatistics()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::SetStatistics( int nBand,
                                      const char* pszMin,
                                      const char* pszMax,
                                      const char* pszMean,
                                      const char* pszMedian,
                                      const char* pszMode,
                                      const char* pszStdDev,
                                      const char* pszSampling )
{
    InitializeLayersNode();

    bFlushMetadata = true;

    int n = 1;

    CPLXMLNode* phSubLayer = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( n = 1 ; phSubLayer ; phSubLayer = phSubLayer->psNext, n++ )
    {
        if( n != nBand )
        {
            continue;
        }

        CPLXMLNode* psSDaset = CPLGetXMLNode( phSubLayer, "statisticDataset" );

        if( psSDaset != nullptr )
        {
            CPLRemoveXMLChild( phSubLayer, psSDaset );
            CPLDestroyXMLNode( psSDaset );
        }

        psSDaset = CPLCreateXMLNode(phSubLayer,CXT_Element,"statisticDataset");

        CPLCreateXMLElementAndValue(psSDaset,"samplingFactor", pszSampling );
        CPLCreateXMLElementAndValue(psSDaset,"MIN", pszMin );
        CPLCreateXMLElementAndValue(psSDaset,"MAX", pszMax );
        CPLCreateXMLElementAndValue(psSDaset,"MEAN", pszMean );
        CPLCreateXMLElementAndValue(psSDaset,"MEDIAN", pszMedian );
        CPLCreateXMLElementAndValue(psSDaset,"MODEVALUE", pszMode );
        CPLCreateXMLElementAndValue(psSDaset,"STD", pszStdDev );

        return true;
    }
    return false;
}

//  ---------------------------------------------------------------------------
//                                                              HasColorTable()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::HasColorMap( int nBand )
{
    int n = 1;

    CPLXMLNode *psLayers = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( ; psLayers; psLayers = psLayers->psNext, n++ )
    {
        if( n == nBand )
        {
            if( CPLGetXMLNode( psLayers, "colorMap.colors" ) )
            {
                return true;
            }
        }
    }

    return false;
}

//  ---------------------------------------------------------------------------
//                                                        InitializeLayersNode()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::InitializeLayersNode()
{
    CPLXMLNode *pslInfo  = CPLGetXMLNode( phMetadata, "layerInfo" );

    int n = 1;

    for( n = 0 ; n < nRasterBands; n++ )
    {
        CPLXMLNode *psSLayer = CPLGetXMLNode( pslInfo,    "subLayer" );

        if( psSLayer == nullptr )
        {
            psSLayer = CPLCreateXMLNode( pslInfo, CXT_Element, "subLayer" );

            CPLCreateXMLElementAndValue( psSLayer, "layerNumber",
                CPLSPrintf( "%d", n + 1 ) );

            CPLCreateXMLElementAndValue( psSLayer, "layerDimensionOrdinate",
                CPLSPrintf( "%d", n ) );

            CPLCreateXMLElementAndValue( psSLayer, "layerID", "" );
        }
    }
}

//  ---------------------------------------------------------------------------
//                                                              GetColorTable()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::GetColorMap( int nBand, GDALColorTable* poCT )
{
    GDALColorEntry oEntry;

    int n = 1;

    CPLXMLNode* psLayers = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( ; psLayers; psLayers = psLayers->psNext, n++ )
    {
        if( n != nBand )
        {
            continue;
        }

        CPLXMLNode* psColors = CPLGetXMLNode( psLayers, "colorMap.colors.cell" );

        int iColor = 0;

        for(  ; psColors; psColors = psColors->psNext )
        {
            iColor    = (short) atoi( CPLGetXMLValue( psColors, "value","0"));
            oEntry.c1 = (short) atoi( CPLGetXMLValue( psColors, "red",  "0"));
            oEntry.c2 = (short) atoi( CPLGetXMLValue( psColors, "green","0"));
            oEntry.c3 = (short) atoi( CPLGetXMLValue( psColors, "blue", "0"));
            oEntry.c4 = (short) atoi( CPLGetXMLValue( psColors, "alpha","0"));
            poCT->SetColorEntry( iColor, &oEntry );
        }
        break;
    }
}

//  ---------------------------------------------------------------------------
//                                                              SetColorTable()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::SetColorMap( int nBand, GDALColorTable* poCT )
{
    InitializeLayersNode();

    bFlushMetadata = true;

    GDALColorEntry oEntry;

    int n = 1;

    CPLXMLNode* phSubLayer = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( n = 1 ; phSubLayer ; phSubLayer = phSubLayer->psNext, n++ )
    {
        if( n != nBand )
        {
            continue;
        }

        CPLXMLNode* psCMap = CPLGetXMLNode( phSubLayer, "colorMap" );

        if( psCMap != nullptr )
        {
            CPLRemoveXMLChild( phSubLayer, psCMap );
            CPLDestroyXMLNode( psCMap );
        }

        psCMap = CPLCreateXMLNode( phSubLayer, CXT_Element, "colorMap" );

        CPLXMLNode* psColor = CPLCreateXMLNode( psCMap, CXT_Element, "colors" );

        // ------------------------------------------------
        // Clean existing colors entry (RGB color table)
        // ------------------------------------------------

        if( psColor != nullptr )
        {
            CPLRemoveXMLChild( psCMap, psColor );
            CPLDestroyXMLNode( psColor );
        }

        psColor = CPLCreateXMLNode( psCMap, CXT_Element, "colors" );

        int iColor = 0;

        int nCount = 0;

        switch( nCellSizeBits )
        {
            case 1 :
                nCount = 2;
                break;
            case 2 :
                nCount = 4;
                break;
            case 4:
                nCount = 16;
                break;
            default:
                nCount = poCT->GetColorEntryCount();
        }

        for( iColor = 0; iColor < nCount; iColor++ )
        {
            poCT->GetColorEntryAsRGB( iColor, &oEntry );

            CPLXMLNode* psCell = CPLCreateXMLNode( psColor, CXT_Element, "cell" );

            CPLSetXMLValue( psCell, "#value", CPLSPrintf("%d", iColor) );
            CPLSetXMLValue( psCell, "#blue",  CPLSPrintf("%d", oEntry.c3) );
            CPLSetXMLValue( psCell, "#red",   CPLSPrintf("%d", oEntry.c1) );
            CPLSetXMLValue( psCell, "#green", CPLSPrintf("%d", oEntry.c2) );
            CPLSetXMLValue( psCell, "#alpha", CPLSPrintf("%d", oEntry.c4) );
        }
    }
}

//  ---------------------------------------------------------------------------
//                                                               InitializeIO()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::InitializeIO( void )
{
    bInitializeIO = true;

    // --------------------------------------------------------------------
    // Initialize Pyramid level details
    // --------------------------------------------------------------------

    pahLevels = (hLevelDetails*) CPLCalloc( sizeof(hLevelDetails),
                                            nPyramidMaxLevel + 1 );

    // --------------------------------------------------------------------
    // Calculate number and size of the blocks in level zero
    // --------------------------------------------------------------------

    nBlockCount = (unsigned long) ( nTotalColumnBlocks * nTotalRowBlocks * nTotalBandBlocks );
    nBlockBytes = (unsigned long) ( nColumnBlockSize * nRowBlockSize * nBandBlockSize *
                                    nCellSizeBits / 8L );
    nGDALBlockBytes = (unsigned long) ( nColumnBlockSize * nRowBlockSize * nGDALCellBytes );

    pahLevels[0].nColumnBlockSize   = nColumnBlockSize;
    pahLevels[0].nRowBlockSize      = nRowBlockSize;
    pahLevels[0].nTotalColumnBlocks = nTotalColumnBlocks;
    pahLevels[0].nTotalRowBlocks    = nTotalRowBlocks;
    pahLevels[0].nBlockCount        = nBlockCount;
    pahLevels[0].nBlockBytes        = nBlockBytes;
    pahLevels[0].nGDALBlockBytes    = nGDALBlockBytes;
    pahLevels[0].nOffset            = 0L;

    // --------------------------------------------------------------------
    // Calculate number and size of the blocks in Pyramid levels
    // --------------------------------------------------------------------

    int iLevel = 1;

    for( iLevel = 1; iLevel <= nPyramidMaxLevel; iLevel++ )
    {
        int nRBS = nRowBlockSize;
        int nCBS = nColumnBlockSize;
        int nTCB;
        int nTRB;

        // --------------------------------------------------------------------
        // Calculate the actual size of a lower resolution block
        // --------------------------------------------------------------------

        double dfScale = pow( (double) 2.0, (double) iLevel );

        int nXSize  = (int) floor( (double) nRasterColumns / dfScale );
        int nYSize  = (int) floor( (double) nRasterRows / dfScale );
        int nXBlock = (int) floor( (double) nCBS / 2.0 );
        int nYBlock = (int) floor( (double) nRBS / 2.0 );

        if( nXSize <= nXBlock && nYSize <= nYBlock )
        {
            // ------------------------------------------------------------
            // Calculate the size of the single small blocks
            // ------------------------------------------------------------

            nCBS = nXSize;
            nRBS = nYSize;
            nTCB = 1;
            nTRB = 1;
        }
        else
        {
            // ------------------------------------------------------------
            // Recalculate blocks quantity
            // ------------------------------------------------------------

            nTCB = (int) ceil( (double) nXSize / nCBS );
            nTRB = (int) ceil( (double) nYSize / nRBS );
        }

        // --------------------------------------------------------------------
        // Save level datails
        // --------------------------------------------------------------------

        pahLevels[iLevel].nColumnBlockSize   = nCBS;
        pahLevels[iLevel].nRowBlockSize      = nRBS;
        pahLevels[iLevel].nTotalColumnBlocks = nTCB;
        pahLevels[iLevel].nTotalRowBlocks    = nTRB;
        pahLevels[iLevel].nBlockCount        = (unsigned long ) ( nTCB * nTRB * nTotalBandBlocks );
        pahLevels[iLevel].nBlockBytes        = (unsigned long ) ( nCBS * nRBS * nBandBlockSize *
                                                                  nCellSizeBits / 8L );
        pahLevels[iLevel].nGDALBlockBytes    = (unsigned long ) ( nCBS * nRBS * nGDALCellBytes );
        pahLevels[iLevel].nOffset            = 0L;
    }

    // --------------------------------------------------------------------
    // Calculate total row count and level's offsets
    // --------------------------------------------------------------------

    nBlockCount = 0L;

    for( iLevel = 0; iLevel <= nPyramidMaxLevel; iLevel++ )
    {
        pahLevels[iLevel].nOffset = nBlockCount;
        nBlockCount += pahLevels[iLevel].nBlockCount;
    }

    // --------------------------------------------------------------------
    // Allocate buffer for one raster block
    // --------------------------------------------------------------------

    long nMaxBufferSize = MAX( nBlockBytes, nGDALBlockBytes );

    pabyBlockBuf = (GByte*) VSI_MALLOC_VERBOSE( nMaxBufferSize );

    if ( pabyBlockBuf == nullptr )
    {
        return false;
    }

    // --------------------------------------------------------------------
    // Allocate buffer for one compressed raster block
    // --------------------------------------------------------------------

    if( bUpdate && ! EQUAL( sCompressionType.c_str(), "NONE") )
    {
        pabyCompressBuf = (GByte*) VSI_MALLOC_VERBOSE( nMaxBufferSize );

        if ( pabyCompressBuf == nullptr )
        {
            return false;
        }
    }

    // --------------------------------------------------------------------
    // Allocate array of LOB Locators
    // --------------------------------------------------------------------

    pahLocator = (OCILobLocator**) VSI_MALLOC_VERBOSE( sizeof(void*) * nBlockCount );

    if ( pahLocator == nullptr )
    {
        return false;
    }

    //  --------------------------------------------------------------------
    //  Issue a statement to load the locators
    //  --------------------------------------------------------------------

    const char* pszUpdate = "";

    if( bUpdate )
    {
        pszUpdate = CPLStrdup( "\nFOR UPDATE" );
    }

    poBlockStmt = poConnection->CreateStatement( CPLSPrintf(
        "SELECT RASTERBLOCK\n"
        "FROM   %s%s\n"
        "WHERE  RASTERID = :1\n"
        "ORDER BY\n"
        "       PYRAMIDLEVEL ASC,\n"
        "       BANDBLOCKNUMBER ASC,\n"
        "       ROWBLOCKNUMBER ASC,\n"
        "       COLUMNBLOCKNUMBER ASC%s",
        sSchema.c_str(),
        sDataTable.c_str(),
        pszUpdate ) );

    poBlockStmt->Bind( &nRasterId );
    poBlockStmt->Define( pahLocator, nBlockCount );

    if( ! poBlockStmt->Execute( static_cast<int>(nBlockCount) ) )
    {
        return false;
    }

    return true;
}

//  ---------------------------------------------------------------------------
//                                                            InitializeLevel()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::InitializeLevel( int nLevel )
{
    nCurrentLevel       = nLevel;
    nColumnBlockSize    = pahLevels[nLevel].nColumnBlockSize;
    nRowBlockSize       = pahLevels[nLevel].nRowBlockSize;
    nTotalColumnBlocks  = pahLevels[nLevel].nTotalColumnBlocks;
    nTotalRowBlocks     = pahLevels[nLevel].nTotalRowBlocks;
    nBlockBytes         = pahLevels[nLevel].nBlockBytes;
    nGDALBlockBytes     = pahLevels[nLevel].nGDALBlockBytes;
    nLevelOffset        = pahLevels[nLevel].nOffset;
}

//  ---------------------------------------------------------------------------
//                                                               GetDataBlock()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::GetDataBlock( int nBand,
                                     int nLevel,
                                     int nXOffset,
                                     int nYOffset,
                                     void* pData )
{
    if( ! bInitializeIO )
    {
        if( InitializeIO() == false )
        {
            return false;
        }
    }

    if( nCurrentLevel != nLevel )
    {
        InitializeLevel( nLevel );
    }

    long nBlock = GetBlockNumber( nBand, nXOffset, nYOffset );

    CPLDebug( "Read  ",
              "Block = %4ld Size = %7ld Band = %d Level = %d X = %d Y = %d",
              nBlock, nBlockBytes, nBand, nLevel, nXOffset, nYOffset );

    if( nCacheBlockId != nBlock )
    {
        if ( bFlushBlock )
        {
            if( ! FlushBlock( nCacheBlockId ) )
            {
                return false;
            }
        }

        nCacheBlockId = nBlock;

        unsigned long nBytesRead = 0;

        nBytesRead = poBlockStmt->ReadBlob( pahLocator[nBlock],
                                            pabyBlockBuf,
                                            nBlockBytes );

        CPLDebug( "Load  ", "Block = %4ld Size = %7ld", nBlock, nBlockBytes );

        if( nBytesRead == 0 )
        {
            memset( pData, 0, nGDALBlockBytes );
            return true;
        }

        if( nBytesRead < nBlockBytes &&
            EQUAL( sCompressionType.c_str(), "NONE") )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                "BLOB size (%ld) is smaller than expected (%ld) !",
                nBytesRead,  nBlockBytes );
            memset( pData, 0, nGDALBlockBytes );
            return true;
        }

        if( nBytesRead > nBlockBytes )
        {
            CPLError( CE_Warning, CPLE_AppDefined,
                "BLOB size (%ld) is bigger than expected (%ld) !",
                nBytesRead,  nBlockBytes );
            memset( pData, 0, nGDALBlockBytes );
            return true;
        }

        //  ----------------------------------------------------------------
        //  GeoRaster BLOB is always Big endian
        //  ----------------------------------------------------------------

#ifndef CPL_MSB
        if( nCellSizeBits > 8 &&
            EQUAL( sCompressionType.c_str(), "DEFLATE") == false )
        {
            int nWordSize  = nCellSizeBits / 8;
            int nWordCount = nColumnBlockSize * nRowBlockSize * nBandBlockSize;
            GDALSwapWords( pabyBlockBuf, nWordSize, nWordCount, nWordSize );
        }
#endif

        //  ----------------------------------------------------------------
        //  Uncompress
        //  ----------------------------------------------------------------

        if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
        {
            UncompressJpeg( nBytesRead );
        }
        else if ( EQUAL( sCompressionType.c_str(), "DEFLATE" ) )
        {
            UncompressDeflate( nBytesRead );
#ifndef CPL_MSB
            if( nCellSizeBits > 8 )
            {
                int nWordSize  = nCellSizeBits / 8;
                int nWordCount = nColumnBlockSize * nRowBlockSize * nBandBlockSize;
                GDALSwapWords( pabyBlockBuf, nWordSize, nWordCount, nWordSize );
            }
#endif
        }

        //  ----------------------------------------------------------------
        //  Unpack NBits
        //  ----------------------------------------------------------------

        if( nCellSizeBits < 8 || nLevel == DEFAULT_BMP_MASK )
        {
            UnpackNBits( pabyBlockBuf );
        }
    }

    //  --------------------------------------------------------------------
    //  Uninterleaving, extract band from block buffer
    //  --------------------------------------------------------------------

    int nStart = ( nBand - 1 ) % nBandBlockSize;

    if( EQUAL( sInterleaving.c_str(), "BSQ" ) || nBandBlockSize == 1 )
    {
        nStart *= nGDALBlockBytes;

        memcpy( pData, &pabyBlockBuf[nStart], nGDALBlockBytes );
    }
    else
    {
        int nIncr   = nBandBlockSize * nGDALCellBytes;
        int nSize   = nGDALCellBytes;

        if( EQUAL( sInterleaving.c_str(), "BIL" ) )
        {
            nStart  *= nColumnBlockSize;
            nIncr   *= nColumnBlockSize;
            nSize   *= nColumnBlockSize;
        }

        GByte* pabyData = (GByte*) pData;

        unsigned long ii = 0;
        unsigned long jj = nStart * nGDALCellBytes;

        for( ii = 0; ii < nGDALBlockBytes; ii += nSize, jj += nIncr )
        {
            memcpy( &pabyData[ii], &pabyBlockBuf[jj], nSize );
        }
    }

    return true;
}

//  ---------------------------------------------------------------------------
//                                                               SetDataBlock()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::SetDataBlock( int nBand,
                                     int nLevel,
                                     int nXOffset,
                                     int nYOffset,
                                     void* pData )
{
#ifndef CPL_MSB
    if( nCellSizeBits > 8 )
    {
        int nWordSize  = nCellSizeBits / 8;
        int nWordCount = nColumnBlockSize * nRowBlockSize;
        GDALSwapWords( pData, nWordSize, nWordCount, nWordSize );
    }
#endif

    if( ! bInitializeIO )
    {
        if( InitializeIO() == false )
        {
            return false;
        }
    }

    if( nCurrentLevel != nLevel )
    {
        InitializeLevel( nLevel );
    }

    long nBlock = GetBlockNumber( nBand, nXOffset, nYOffset );

    CPLDebug( "Write ",
              "Block = %4ld Size = %7ld Band = %d Level = %d X = %d Y = %d",
              nBlock, nBlockBytes, nBand, nLevel, nXOffset, nYOffset );

    //  --------------------------------------------------------------------
    //  Flush previous block
    //  --------------------------------------------------------------------

    if( nCacheBlockId != nBlock && bFlushBlock )
    {
        if( ! FlushBlock( nCacheBlockId ) )
        {
            return false;
        }
    }

    //  --------------------------------------------------------------------
    //  Re-load interleaved block
    //  --------------------------------------------------------------------

    if( nBandBlockSize > 1 && bWriteOnly == false && nCacheBlockId != nBlock )
    {
        nCacheBlockId = nBlock;

        unsigned long nBytesRead = 0;

        nBytesRead = poBlockStmt->ReadBlob( pahLocator[nBlock],
                                            pabyBlockBuf,
                                            static_cast<unsigned long>(nBlockBytes) );

        CPLDebug( "Reload", "Block = %4ld Size = %7ld", nBlock, nBlockBytes );

        if( nBytesRead == 0 )
        {
            memset( pabyBlockBuf, 0, nBlockBytes );
        }
        else
        {
            //  ------------------------------------------------------------
            //  Uncompress
            //  ------------------------------------------------------------

            if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
            {
                UncompressJpeg( nBytesRead );
            }
            else if ( EQUAL( sCompressionType.c_str(), "DEFLATE" ) )
            {
                UncompressDeflate( nBytesRead );
            }

            //  ------------------------------------------------------------
            //  Unpack NBits
            //  ------------------------------------------------------------

            if( nCellSizeBits < 8 || nLevel == DEFAULT_BMP_MASK )
            {
                UnpackNBits( pabyBlockBuf );
            }
        }
    }

    GByte *pabyInBuf = (GByte *) pData;

    //  --------------------------------------------------------------------
    //  Interleave
    //  --------------------------------------------------------------------

    int nStart = ( nBand - 1 ) % nBandBlockSize;

    if( EQUAL( sInterleaving.c_str(), "BSQ" ) || nBandBlockSize == 1 )
    {
        nStart *= nGDALBlockBytes;

        memcpy( &pabyBlockBuf[nStart], pabyInBuf, nGDALBlockBytes );
    }
    else
    {
        int nIncr   = nBandBlockSize * nGDALCellBytes;
        int nSize   = nGDALCellBytes;

        if( EQUAL( sInterleaving.c_str(), "BIL" ) )
        {
            nStart  *= nColumnBlockSize;
            nIncr   *= nColumnBlockSize;
            nSize   *= nColumnBlockSize;
        }

        unsigned long ii = 0;
        unsigned long jj = nStart * nGDALCellBytes;

        for( ii = 0; ii < nGDALBlockBytes; ii += nSize, jj += nIncr )
        {
            memcpy( &pabyBlockBuf[jj], &pabyInBuf[ii], nSize );
        }
    }

    //  --------------------------------------------------------------------
    //  Flag the flush block
    //  --------------------------------------------------------------------

    nCacheBlockId   = nBlock;
    bFlushBlock     = true;
    nFlushBlockSize = nBlockBytes;

    return true;
}

//  ---------------------------------------------------------------------------
//                                                                 FlushBlock()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::FlushBlock( long nCacheBlock )
{
    GByte* pabyFlushBuffer = (GByte *) pabyBlockBuf;

    //  --------------------------------------------------------------------
    //  Pack bits ( inside pabyOutBuf )
    //  --------------------------------------------------------------------

    if( nCellSizeBits < 8 || nCurrentLevel == DEFAULT_BMP_MASK )
    {
        PackNBits( pabyFlushBuffer );
    }

    //  --------------------------------------------------------------------
    //  Compress ( from pabyBlockBuf to pabyBlockBuf2 )
    //  --------------------------------------------------------------------

    if( ! EQUAL( sCompressionType.c_str(), "NONE" ) )
    {
        if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
        {
            nFlushBlockSize = CompressJpeg();
        }
        else if ( EQUAL( sCompressionType.c_str(), "DEFLATE" ) )
        {
            nFlushBlockSize = CompressDeflate();
        }

        pabyFlushBuffer = pabyCompressBuf;
    }

    //  --------------------------------------------------------------------
    //  Write BLOB
    //  --------------------------------------------------------------------

    CPLDebug( "Flush ", "Block = %4ld Size = %7ld", nCacheBlock,
              nFlushBlockSize );

    if( ! poBlockStmt->WriteBlob( pahLocator[nCacheBlock],
                                  pabyFlushBuffer,
                                  nFlushBlockSize ) )
    {
        return false;
    }

    bFlushBlock     = false;
    bFlushMetadata  = true;
    nFlushBlockSize = nBlockBytes;

    return true;
}

//  ---------------------------------------------------------------------------
//                                                           LoadNoDataValues()
//  ---------------------------------------------------------------------------
static
CPLList* AddToNoDataList( CPLXMLNode* phNode, int nNumber, CPLList* poList )
{
    CPLXMLNode* psChild = phNode->psChild;

    const char* pszMin = nullptr;
    const char* pszMax = nullptr;

    for( ; psChild ; psChild = psChild->psNext )
    {
        if( EQUAL( psChild->pszValue, "value" ) )
        {
            pszMin = CPLGetXMLValue( psChild, nullptr, "NONE" );
            pszMax = pszMin;
        }
        else if ( EQUAL( psChild->pszValue, "range" ) )
        {
            pszMin = CPLGetXMLValue( psChild, "min", "NONE" );
            pszMax = CPLGetXMLValue( psChild, "max", "NONE" );
        }
        else
        {
            continue;
        }

        hNoDataItem* poItem = (hNoDataItem*) CPLMalloc( sizeof( hNoDataItem ) );

        poItem->nBand = nNumber;
        poItem->dfLower = CPLAtof( pszMin );
        poItem->dfUpper = CPLAtof( pszMax );

        poList = CPLListAppend( poList, poItem );
    }

    return poList;
}

void GeoRasterWrapper::LoadNoDataValues( void )
{
    CPLListDestroy( psNoDataList );

    CPLXMLNode* phLayerInfo = CPLGetXMLNode( phMetadata, "layerInfo" );

    if( phLayerInfo == nullptr )
    {
        return;
    }

    //  -------------------------------------------------------------------
    //  Load NoDatas from list of values and list of value ranges
    //  -------------------------------------------------------------------

    CPLXMLNode* phObjNoData = CPLGetXMLNode( phLayerInfo, "objectLayer.NODATA" );

    for( ; phObjNoData ; phObjNoData = phObjNoData->psNext )
    {
        psNoDataList = AddToNoDataList( phObjNoData, 0, psNoDataList );
    }

    CPLXMLNode* phSubLayer = CPLGetXMLNode( phLayerInfo, "subLayer" );

    for( ; phSubLayer ; phSubLayer = phSubLayer->psNext )
    {
        int nNumber = atoi( CPLGetXMLValue( phSubLayer, "layerNumber", "-1") );

        CPLXMLNode* phSubNoData = CPLGetXMLNode( phSubLayer, "NODATA" );

        if( phSubNoData )
        {
            psNoDataList = AddToNoDataList( phSubNoData, nNumber, psNoDataList );
        }
    }
}

//  ---------------------------------------------------------------------------
//                                                        GetSpatialReference()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::GetSpatialReference()
{
    int i;

    CPLXMLNode* phSRSInfo = CPLGetXMLNode( phMetadata, "spatialReferenceInfo" );
    
    if( phSRSInfo == nullptr )
    {
        return;
    }
    
    const char* pszMCL = CPLGetXMLValue( phSRSInfo, "modelCoordinateLocation", 
                                                    "CENTER" );
    
    if( EQUAL( pszMCL, "CENTER" ) )
    {
      eModelCoordLocation = MCL_CENTER;
    }
    else
    {
      eModelCoordLocation = MCL_UPPERLEFT;
    }

    const char* pszModelType = CPLGetXMLValue( phSRSInfo, "modelType", "None" );
    
    if( EQUAL( pszModelType, "FunctionalFitting" ) == false )
    {
        return;
    }

    CPLXMLNode* phPolyModel = CPLGetXMLNode( phSRSInfo, "polynomialModel" );

    if ( phPolyModel == nullptr )
    {
        return;
    }

    CPLXMLNode* phPolynomial = CPLGetXMLNode( phPolyModel, "pPolynomial" );

    if ( phPolynomial == nullptr )
    {
        return;
    }

    int nNumCoeff = atoi( CPLGetXMLValue( phPolynomial, "nCoefficients", "0" ));

    if ( nNumCoeff != 3 ) 
    {
        return;
    }

    const char* pszPolyCoeff = CPLGetXMLValue(phPolynomial,
                                              "polynomialCoefficients", "None");

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        return;
    }

    char** papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", 
                                           CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) < 3 )
    {
        return;
    }

    double adfPCoef[3];
    
    for( i = 0; i < 3; i++ )
    {
        adfPCoef[i] = CPLAtof( papszCeoff[i] );
    }

    phPolynomial = CPLGetXMLNode( phPolyModel, "rPolynomial" );

    if ( phPolynomial == nullptr )
    {
        return;
    }

    pszPolyCoeff = CPLGetXMLValue( phPolynomial, "polynomialCoefficients", "None" );

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        return;
    }

    papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) < 3 )
    {
        return;
    }
    
    double adfRCoef[3];

    for( i = 0; i < 3; i++ )
    {
        adfRCoef[i] = CPLAtof( papszCeoff[i] );
    }

    //  -------------------------------------------------------------------
    //  Inverse the transformation matrix
    //  -------------------------------------------------------------------

    double adfVal[6] = {1.0, 0.0, 0.0, 1.0, 0.0, 0.0};

    double dfDet = adfRCoef[1] * adfPCoef[2] - adfRCoef[2] * adfPCoef[1];
   
    if( CPLIsEqual( dfDet, 0.0 ) )
    {
        dfDet = 0.0000000001; // to avoid divide by zero
        CPLError( CE_Warning, CPLE_AppDefined, "Determinant is ZERO!!!");
    }

    adfVal[0] =   adfPCoef[2] / dfDet;
    adfVal[1] =  -adfRCoef[2] / dfDet;
    adfVal[2] = -(adfRCoef[0] * adfPCoef[2] - adfPCoef[0] * adfRCoef[2]) / dfDet;
    adfVal[3] =  -adfPCoef[1] / dfDet;
    adfVal[4] =   adfRCoef[1] / dfDet;
    adfVal[5] =  (adfRCoef[0] * adfPCoef[1] - adfPCoef[0] * adfRCoef[1]) / dfDet;

    //  -------------------------------------------------------------------
    //  Adjust Model Coordinate Location
    //  -------------------------------------------------------------------
    
    if ( eModelCoordLocation == MCL_CENTER )
    {
       adfVal[2] -= ( adfVal[0] / 2.0 );
       adfVal[5] -= ( adfVal[4] / 2.0 );
    }    

    dfXCoefficient[0] = adfVal[0];
    dfXCoefficient[1] = adfVal[1];
    dfXCoefficient[2] = adfVal[2];
    dfYCoefficient[0] = adfVal[3];
    dfYCoefficient[1] = adfVal[4];
    dfYCoefficient[2] = adfVal[5];
    
    //  -------------------------------------------------------------------
    //  Apply ULTCoordinate
    //  -------------------------------------------------------------------

    dfXCoefficient[2] += ( anULTCoordinate[0] * dfXCoefficient[0] );

    dfYCoefficient[2] += ( anULTCoordinate[1] * dfYCoefficient[1] );
}

//  ---------------------------------------------------------------------------
//                                                                     GetGCP()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::GetGCP()
{
    CPLXMLNode* psGCP = CPLGetXMLNode( phMetadata, 
                "spatialReferenceInfo.gcpGeoreferenceModel.gcp" );

    CPLXMLNode* psFirst = psGCP;

    if( nGCPCount > 0 && pasGCPList )
    {
        GDALDeinitGCPs( nGCPCount, pasGCPList );
        CPLFree( pasGCPList );
    }

    pasGCPList = nullptr;
    nGCPCount = 0;

    for( int i = 1 ; psGCP ; psGCP = psGCP->psNext, i++ )
    {
        if( ! EQUAL( CPLGetXMLValue( psGCP, "type",  "" ), "ControlPoint" ) )
        {
            continue;
        }
        nGCPCount++;
    }

    pasGCPList = static_cast<GDAL_GCP *>(
            CPLCalloc(sizeof(GDAL_GCP), nGCPCount) );

    psGCP = psFirst;

    for( int i = 0 ; psGCP ; psGCP = psGCP->psNext, i++ )
    {
        if( ! EQUAL( CPLGetXMLValue( psGCP, "type",  "" ), "ControlPoint" ) )
        {
            continue;
        }
        pasGCPList[i].pszId    = CPLStrdup( CPLGetXMLValue( psGCP, "ID", "" ) );
        pasGCPList[i].pszInfo  = CPLStrdup( "" );
        pasGCPList[i].dfGCPLine  = CPLAtof( CPLGetXMLValue( psGCP, "row", "0.0" ) );
        pasGCPList[i].dfGCPPixel = CPLAtof( CPLGetXMLValue( psGCP, "column", "0.0" ) );
        pasGCPList[i].dfGCPX     = CPLAtof( CPLGetXMLValue( psGCP, "X", "0.0" ) );
        pasGCPList[i].dfGCPY     = CPLAtof( CPLGetXMLValue( psGCP, "Y", "0.0" ) );
        pasGCPList[i].dfGCPZ     = CPLAtof( CPLGetXMLValue( psGCP, "Z", "0.0" ));
    }
}

//  ---------------------------------------------------------------------------
//                                                                     SetGCP()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::SetGCP( int nGCPCountIn, const GDAL_GCP *pasGCPListIn)
{
    if( nGCPCount > 0 )
    {
        GDALDeinitGCPs( nGCPCount, pasGCPList );
        CPLFree( pasGCPList );
    }

    nGCPCount  = nGCPCountIn;
    pasGCPList = GDALDuplicateGCPs(nGCPCount, pasGCPListIn);
    bFlushGCP  = true;
}

void GeoRasterWrapper::FlushGCP()
{
    const char* pszStatement = CPLSPrintf(
        "DECLARE\n"
        "  gr   sdo_georaster;\n"
        "BEGIN\n"
        "  SELECT %s INTO gr FROM %s%s t WHERE %s FOR UPDATE;\n",
            sColumn.c_str(),
            sSchema.c_str(),
            sTable.c_str(),
            sWhere.c_str());

    int nDimns = 2;

    for( int iGCP = 0; iGCP < nGCPCount; ++iGCP )
    {
        if( pasGCPList[iGCP].dfGCPZ != 0.0 )
        {
            nDimns = 3;
            break;
        }
    }

    if ( nDimns == 3 )
    {
        pszStatement = CPLSPrintf(
            "%s"
            "  sdo_geor.setControlPoint(gr,\n"
            "    SDO_GEOR_GCP(TO_CHAR(:1), '', 1,\n"
            "      2, sdo_number_array(:2, :3),\n"
            "      3, sdo_number_array(:4, :5, :6),\n"
            "      NULL, NULL));\n",
            pszStatement);
    }
    else
    {
        pszStatement = CPLSPrintf(
            "%s"
            "  sdo_geor.setControlPoint(gr,\n"
            "    SDO_GEOR_GCP(TO_CHAR(:1), '', 1,\n"
            "      2, sdo_number_array(:2, :3),\n"
            "      2, sdo_number_array(:4, :5),\n"
            "      NULL, NULL));\n",
            pszStatement);
    }

    pszStatement = CPLSPrintf(
            "%s"
            "  UPDATE %s%s t SET %s = gr WHERE %s;\n"
            "END;\n",
            pszStatement,
            sSchema.c_str(),
            sTable.c_str(),
            sColumn.c_str(),
            sWhere.c_str());

    OWStatement* poStmt = poConnection->CreateStatement( pszStatement );

    long   lBindN = 0L;
    double dBindL = 0.0;
    double dBindP = 0.0;
    double dBindX = 0.0;
    double dBindY = 0.0;
    double dBindZ = 0.0;

    poStmt->Bind( &lBindN );
    poStmt->Bind( &dBindL );
    poStmt->Bind( &dBindP );
    poStmt->Bind( &dBindX );
    poStmt->Bind( &dBindY );

    if ( nDimns == 3 )
    {
        poStmt->Bind( &dBindZ );
    }

    for( int iGCP = 0; iGCP < nGCPCount; ++iGCP )
    {

        // Assign bound variables

        lBindN = iGCP + 1;
        dBindL = pasGCPList[iGCP].dfGCPLine;
        dBindP = pasGCPList[iGCP].dfGCPPixel;
        dBindX = pasGCPList[iGCP].dfGCPX;
        dBindY = pasGCPList[iGCP].dfGCPY;
        dBindZ = nDimns == 3 ? pasGCPList[iGCP].dfGCPZ : 0.0;

        // To avoid cppcheck false positive complaints about unreadVariable
        CPL_IGNORE_RET_VAL(lBindN);
        CPL_IGNORE_RET_VAL(dBindL);
        CPL_IGNORE_RET_VAL(dBindP);
        CPL_IGNORE_RET_VAL(dBindX);
        CPL_IGNORE_RET_VAL(dBindY);
        CPL_IGNORE_RET_VAL(dBindZ);

        // Consume bound values

        if( ! poStmt->Execute() )
        {
            CPLError( CE_Failure, CPLE_AppDefined, "Error loading GCP.");
        }
    }

    delete poStmt;
}

//  ---------------------------------------------------------------------------
//                                                                     GetRPC()
//  ---------------------------------------------------------------------------

/* This is the order for storing 20 coefficients in GeoRaster Metadata */

constexpr int anOrder[] = {
    1, 2, 8, 12, 3, 5, 15, 9, 13, 16, 4, 6, 18, 7, 11, 19, 10, 14, 17, 20
};

void GeoRasterWrapper::GetRPC()
{
    int i;

    CPLXMLNode* phSRSInfo = CPLGetXMLNode( phMetadata,
                                           "spatialReferenceInfo" );

    if( phSRSInfo == nullptr )
    {
        return;
    }

    CPLXMLNode* phPolyModel = CPLGetXMLNode( phSRSInfo, "polynomialModel" );

    if ( phPolyModel == nullptr )
    {
        return;
    }

    // pPolynomial refers to LINE_NUM

    CPLXMLNode* phPolynomial = CPLGetXMLNode( phPolyModel, "pPolynomial" );

    if ( phPolynomial == nullptr )
    {
        return;
    }

    int nNumCoeff = atoi( CPLGetXMLValue( phPolynomial, "nCoefficients", "0" ) );

    if ( nNumCoeff != 20 )
    {
        return;
    }

    const char* pszPolyCoeff = CPLGetXMLValue( phPolynomial, "polynomialCoefficients", "None" );

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        return;
    }

    char** papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) != 20 )
    {
        return;
    }

    phRPC = (GDALRPCInfo*) VSIMalloc( sizeof(GDALRPCInfo) );

    phRPC->dfLINE_OFF     = CPLAtof( CPLGetXMLValue( phPolyModel, "rowOff", "0" ) );
    phRPC->dfSAMP_OFF     = CPLAtof( CPLGetXMLValue( phPolyModel, "columnOff", "0" ) );
    phRPC->dfLONG_OFF     = CPLAtof( CPLGetXMLValue( phPolyModel, "xOff", "0" ) );
    phRPC->dfLAT_OFF      = CPLAtof( CPLGetXMLValue( phPolyModel, "yOff", "0" ) );
    phRPC->dfHEIGHT_OFF   = CPLAtof( CPLGetXMLValue( phPolyModel, "zOff", "0" ) );

    phRPC->dfLINE_SCALE   = CPLAtof( CPLGetXMLValue( phPolyModel, "rowScale", "0" ) );
    phRPC->dfSAMP_SCALE   = CPLAtof( CPLGetXMLValue( phPolyModel, "columnScale", "0" ) );
    phRPC->dfLONG_SCALE   = CPLAtof( CPLGetXMLValue( phPolyModel, "xScale", "0" ) );
    phRPC->dfLAT_SCALE    = CPLAtof( CPLGetXMLValue( phPolyModel, "yScale", "0" ) );
    phRPC->dfHEIGHT_SCALE = CPLAtof( CPLGetXMLValue( phPolyModel, "zScale", "0" ) );

    for( i = 0; i < 20; i++ )
    {
        phRPC->adfLINE_NUM_COEFF[anOrder[i] - 1] = CPLAtof( papszCeoff[i] );
    }

    // qPolynomial refers to LINE_DEN

    phPolynomial = CPLGetXMLNode( phPolyModel, "qPolynomial" );

    if ( phPolynomial == nullptr )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    pszPolyCoeff = CPLGetXMLValue( phPolynomial, "polynomialCoefficients", "None" );

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) != 20 )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    for( i = 0; i < 20; i++ )
    {
        phRPC->adfLINE_DEN_COEFF[anOrder[i] - 1] = CPLAtof( papszCeoff[i] );
    }

    // rPolynomial refers to SAMP_NUM

    phPolynomial = CPLGetXMLNode( phPolyModel, "rPolynomial" );

    if ( phPolynomial == nullptr )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    pszPolyCoeff = CPLGetXMLValue( phPolynomial, "polynomialCoefficients", "None" );

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) != 20 )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    for( i = 0; i < 20; i++ )
    {
        phRPC->adfSAMP_NUM_COEFF[anOrder[i] - 1] = CPLAtof( papszCeoff[i] );
    }

    // sPolynomial refers to SAMP_DEN

    phPolynomial = CPLGetXMLNode( phPolyModel, "sPolynomial" );

    if ( phPolynomial == nullptr )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    pszPolyCoeff = CPLGetXMLValue( phPolynomial, "polynomialCoefficients", "None" );

    if ( EQUAL( pszPolyCoeff, "None" ) )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    papszCeoff = CSLTokenizeString2( pszPolyCoeff, " ", CSLT_STRIPLEADSPACES );

    if( CSLCount( papszCeoff ) != 20 )
    {
        CPLFree( phRPC );
        phRPC = nullptr;
        return;
    }

    for( i = 0; i < 20; i++ )
    {
        phRPC->adfSAMP_DEN_COEFF[anOrder[i] - 1] = CPLAtof( papszCeoff[i] );
    }
}

//  ---------------------------------------------------------------------------
//                                                                     SetRPC()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::SetRPC()
{
    //  -------------------------------------------------------------------
    //  Remove "layerInfo" tree
    //  -------------------------------------------------------------------

    CPLXMLNode* phLayerInfo = CPLGetXMLNode( phMetadata, "layerInfo" );
    CPLXMLNode* phClone = nullptr;

    if( phLayerInfo )
    {
        phClone = CPLCloneXMLTree( phLayerInfo );
        CPLRemoveXMLChild( phMetadata, phLayerInfo );
    }

    //  -------------------------------------------------------------------
    //  Start loading the RPC to "spatialReferenceInfo" tree
    //  -------------------------------------------------------------------

    int i = 0;
    CPLString osField, osMultiField;
    CPLXMLNode* phPolynomial = nullptr;

    CPLXMLNode* phSRSInfo = CPLGetXMLNode( phMetadata,
                                           "spatialReferenceInfo" );

    if( ! phSRSInfo )
    {
        phSRSInfo = CPLCreateXMLNode( phMetadata, CXT_Element,
                                      "spatialReferenceInfo" );
    }
    else
    {
        CPLXMLNode* phNode = CPLGetXMLNode( phSRSInfo, "isReferenced" );
        if( phNode )
        {
            CPLRemoveXMLChild( phSRSInfo, phNode );
        }

        phNode = CPLGetXMLNode( phSRSInfo, "SRID" );
        if( phNode )
        {
            CPLRemoveXMLChild( phSRSInfo, phNode );
        }

        phNode = CPLGetXMLNode( phSRSInfo, "modelCoordinateLocation" );
        if( phNode )
        {
            CPLRemoveXMLChild( phSRSInfo, phNode );
        }

        phNode = CPLGetXMLNode( phSRSInfo, "modelType" );
        if( phNode )
        {
            CPLRemoveXMLChild( phSRSInfo, phNode );
        }

        phNode = CPLGetXMLNode( phSRSInfo, "polynomialModel" );
        if( phNode )
        {
            CPLRemoveXMLChild( phSRSInfo, phNode );
        }
    }

    CPLCreateXMLElementAndValue( phSRSInfo, "isReferenced", "true" );
    CPLCreateXMLElementAndValue( phSRSInfo, "SRID", "4327" );
    CPLCreateXMLElementAndValue( phSRSInfo, "modelCoordinateLocation",
                                            "CENTER" );
    CPLCreateXMLElementAndValue( phSRSInfo, "modelType", "FunctionalFitting" );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#rowOff",
                                    CPLSPrintf( "%.15g", phRPC->dfLINE_OFF ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#columnOff",
                                    CPLSPrintf( "%.15g", phRPC->dfSAMP_OFF ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#xOff",
                                    CPLSPrintf( "%.15g", phRPC->dfLONG_OFF ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#yOff",
                                    CPLSPrintf( "%.15g", phRPC->dfLAT_OFF ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#zOff",
                                    CPLSPrintf( "%.15g", phRPC->dfHEIGHT_OFF ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#rowScale",
                                    CPLSPrintf( "%.15g", phRPC->dfLINE_SCALE ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#columnScale",
                                    CPLSPrintf( "%.15g", phRPC->dfSAMP_SCALE ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#xScale",
                                    CPLSPrintf( "%.15g", phRPC->dfLONG_SCALE ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#yScale",
                                    CPLSPrintf( "%.15g", phRPC->dfLAT_SCALE ) );
    CPLSetXMLValue( phSRSInfo, "polynomialModel.#zScale",
                                    CPLSPrintf( "%.15g", phRPC->dfHEIGHT_SCALE ) );
    CPLXMLNode*     phPloyModel = CPLGetXMLNode( phSRSInfo, "polynomialModel" );

    // pPolynomial refers to LINE_NUM

    CPLSetXMLValue( phPloyModel, "pPolynomial.#pType",         "1" );
    CPLSetXMLValue( phPloyModel, "pPolynomial.#nVars",         "3" );
    CPLSetXMLValue( phPloyModel, "pPolynomial.#order",         "3" );
    CPLSetXMLValue( phPloyModel, "pPolynomial.#nCoefficients", "20" );
    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", phRPC->adfLINE_NUM_COEFF[anOrder[i] - 1] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    phPolynomial = CPLGetXMLNode( phPloyModel, "pPolynomial" );
    CPLCreateXMLElementAndValue( phPolynomial, "polynomialCoefficients",
                                 osMultiField );

    // qPolynomial refers to LINE_DEN

    CPLSetXMLValue( phPloyModel, "qPolynomial.#pType",         "1" );
    CPLSetXMLValue( phPloyModel, "qPolynomial.#nVars",         "3" );
    CPLSetXMLValue( phPloyModel, "qPolynomial.#order",         "3" );
    CPLSetXMLValue( phPloyModel, "qPolynomial.#nCoefficients", "20" );
    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", phRPC->adfLINE_DEN_COEFF[anOrder[i] - 1] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    phPolynomial = CPLGetXMLNode( phPloyModel, "qPolynomial" );
    CPLCreateXMLElementAndValue( phPolynomial, "polynomialCoefficients",
                                 osMultiField );

    // rPolynomial refers to SAMP_NUM

    CPLSetXMLValue( phPloyModel, "rPolynomial.#pType",         "1" );
    CPLSetXMLValue( phPloyModel, "rPolynomial.#nVars",         "3" );
    CPLSetXMLValue( phPloyModel, "rPolynomial.#order",         "3" );
    CPLSetXMLValue( phPloyModel, "rPolynomial.#nCoefficients", "20" );
    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", phRPC->adfSAMP_NUM_COEFF[anOrder[i] - 1] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    phPolynomial = CPLGetXMLNode( phPloyModel, "rPolynomial" );
    CPLCreateXMLElementAndValue( phPolynomial, "polynomialCoefficients",
                                 osMultiField );

    // sPolynomial refers to SAMP_DEN

    CPLSetXMLValue( phPloyModel, "sPolynomial.#pType",         "1" );
    CPLSetXMLValue( phPloyModel, "sPolynomial.#nVars",         "3" );
    CPLSetXMLValue( phPloyModel, "sPolynomial.#order",         "3" );
    CPLSetXMLValue( phPloyModel, "sPolynomial.#nCoefficients", "20" );
    for( i = 0; i < 20; i++ )
    {
        osField.Printf( "%.15g", phRPC->adfSAMP_DEN_COEFF[anOrder[i] - 1] );
        if( i > 0 )
            osMultiField += " ";
        else
            osMultiField = "";
        osMultiField += osField;
    }
    phPolynomial = CPLGetXMLNode( phPloyModel, "sPolynomial" );
    CPLCreateXMLElementAndValue( phPolynomial, "polynomialCoefficients",
                                 osMultiField );

    //  -------------------------------------------------------------------
    //  Add "layerInfo" tree back
    //  -------------------------------------------------------------------

    CPLAddXMLChild( phMetadata, phClone );
}

//  ---------------------------------------------------------------------------
//                                                                 SetMaxLeve()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::SetMaxLevel( int nLevels )
{
    CPLXMLNode* psNode = CPLGetXMLNode( phMetadata, "rasterInfo.pyramid" );

    if( psNode )
    {
        CPLSetXMLValue( psNode, "type", "DECREASE" );
        CPLSetXMLValue( psNode, "maxLevel", CPLSPrintf( "%d", nLevels ) );
    }
}

//  ---------------------------------------------------------------------------
//                                                                  GetNoData()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::GetNoData( int nLayer, double* pdfNoDataValue )
{
    if( psNoDataList == nullptr || CPLListCount( psNoDataList ) == 0 )
    {
        return false;
    }

    //  -------------------------------------------------------------------
    //  Get only single value NoData, no list of values or value ranges
    //  -------------------------------------------------------------------

    int nCount = 0;
    double dfValue = 0.0;

    CPLList* psList = nullptr;

    //  -------------------------------------------------------------------
    //  Process Object Layer values
    //  -------------------------------------------------------------------

    for( psList = psNoDataList; psList ; psList = psList->psNext )
    {
        hNoDataItem* phItem = (hNoDataItem*) psList->pData;

        if( phItem->nBand == 0 )
        {
            if( phItem->dfLower == phItem->dfUpper )
            {
                dfValue = phItem->dfLower;
                nCount++;
            }
            else
            {
                return false; // value range
            }
        }
    }

    //  -------------------------------------------------------------------
    //  Values from the Object Layer override values from the layers
    //  -------------------------------------------------------------------

    if( nCount == 1 )
    {
        *pdfNoDataValue = dfValue;
        return true;
    }

    //  -------------------------------------------------------------------
    //  Process SubLayer values
    //  -------------------------------------------------------------------

    for( psList = psNoDataList; psList ; psList = psList->psNext )
    {
        hNoDataItem* phItem = (hNoDataItem*) psList->pData;

        if( phItem->nBand == nLayer )
        {
            if( phItem->dfLower == phItem->dfUpper )
            {
                dfValue = phItem->dfLower;
                nCount++;
            }
            else
            {
                return false; // value range
            }
        }
    }

    if( nCount == 1 )
    {
        *pdfNoDataValue = dfValue;
        return true;
    }

    return false;
}

//  ---------------------------------------------------------------------------
//                                                             SetNoDataValue()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::SetNoData( int nLayer, const char* pszValue )
{
    // ------------------------------------------------------------
    //  Set one NoData per dataset on "rasterInfo" section (10g)
    // ------------------------------------------------------------

    if( poConnection->GetVersion() < 11 )
    {
        CPLXMLNode* psRInfo = CPLGetXMLNode( phMetadata, "rasterInfo" );
        CPLXMLNode* psNData = CPLSearchXMLNode( psRInfo, "NODATA" );

        if( psNData == nullptr )
        {
            psNData = CPLCreateXMLNode( nullptr, CXT_Element, "NODATA" );

            CPLXMLNode* psCDepth = CPLGetXMLNode( psRInfo, "cellDepth" );

            CPLXMLNode* psPointer = psCDepth->psNext;
            psCDepth->psNext = psNData;
            psNData->psNext = psPointer;
        }

        CPLSetXMLValue( psRInfo, "NODATA", pszValue );
        bFlushMetadata = true;
        return true;
    }

    // ------------------------------------------------------------
    //  Add NoData for all bands (layer=0) or for a specific band
    // ------------------------------------------------------------

    char szRDT[OWCODE];
    char szNoData[OWTEXT];

    strcpy( szRDT, sDataTable.c_str() );
    strcpy( szNoData, pszValue );

    long long  nRID = nRasterId;

    // ------------------------------------------------------------
    //  Write the in memory XML metadata to avoid losing other changes.
    // ------------------------------------------------------------

    char* pszMetadata = CPLSerializeXMLTree( phMetadata );

    if( pszMetadata == nullptr )
    {
        return false;
    }

    OCILobLocator* phLocatorR = nullptr;
    OCILobLocator* phLocatorW = nullptr;

    OWStatement* poStmt = poConnection->CreateStatement( CPLSPrintf(
        "DECLARE\n"
        "  GR1 sdo_georaster;\n"
        "BEGIN\n"
        "  SELECT %s INTO GR1 FROM %s%s T WHERE %s FOR UPDATE;\n"
        "\n"
        "  GR1.metadata := sys.xmltype.createxml(:1);\n"
        "\n"
        "  SDO_GEOR.addNODATA( GR1, :2, :3 );\n"
        "\n"
        "  UPDATE %s%s T SET %s = GR1 WHERE %s;\n"
        "\n"
        "  EXECUTE IMMEDIATE\n"
        "    'SELECT T.%s.METADATA.getClobVal() FROM %s%s T \n"
        "     WHERE  T.%s.RASTERDATATABLE = UPPER(:1)\n"
        "       AND  T.%s.RASTERID = :2'\n"
        "    INTO :metadata USING :rdt, :rid;\n"
        "END;",
            sColumn.c_str(), sSchema.c_str(), sTable.c_str(), sWhere.c_str(),
            sSchema.c_str(), sTable.c_str(), sColumn.c_str(), sWhere.c_str(),
            sColumn.c_str(), sSchema.c_str(), sTable.c_str(),
            sColumn.c_str(),
            sColumn.c_str() ) );

    poStmt->WriteCLob( &phLocatorW, pszMetadata );

    poStmt->Bind( &phLocatorW );
    poStmt->Bind( &nLayer );
    poStmt->Bind( szNoData );
    poStmt->BindName( ":metadata", &phLocatorR );
    poStmt->BindName( ":rdt", szRDT );
    poStmt->BindName( ":rid", &nRID );

    CPLFree( pszMetadata );

    if( ! poStmt->Execute() )
    {
        poStmt->FreeLob(phLocatorR);
        poStmt->FreeLob(phLocatorW);
        delete poStmt;
        return false;
    }

    poStmt->FreeLob(phLocatorW);

    // ------------------------------------------------------------
    //  Read the XML metadata from db to memory with nodata updates
    // ------------------------------------------------------------

    char* pszXML = poStmt->ReadCLob( phLocatorR );

    if( pszXML )
    {
        CPLDestroyXMLNode( phMetadata );
        phMetadata = CPLParseXMLString( pszXML );
        CPLFree( pszXML );
    }

    poStmt->FreeLob(phLocatorR);

    bFlushMetadata = true;
    delete poStmt;
    return false;
}

//  ---------------------------------------------------------------------------
//                                                                     SetVAT()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::SetVAT( int nBand, const char* pszName )
{
    InitializeLayersNode();

    bFlushMetadata = true;

    int n = 1;

    CPLXMLNode* psLayers = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    for( ; psLayers; psLayers = psLayers->psNext, n++ )
    {
        if( n != nBand )
        {
            continue;
        }

        CPLXMLNode* psVAT = CPLGetXMLNode( psLayers, "vatTableName" );

        if( psVAT != nullptr )
        {
            CPLRemoveXMLChild( psLayers, psVAT );
            CPLDestroyXMLNode( psVAT );
        }

        CPLCreateXMLElementAndValue(psLayers, "vatTableName", pszName );

        // ------------------------------------------------------------
        // To be updated at Flush Metadata in SDO_GEOR.setVAT()
        // ------------------------------------------------------------

        sValueAttributeTab = pszName;

        return true;
    }

    return false;
}

//  ---------------------------------------------------------------------------
//                                                                     GetVAT()
//  ---------------------------------------------------------------------------

char* GeoRasterWrapper::GetVAT( int nBand )
{
    CPLXMLNode* psLayers = CPLGetXMLNode( phMetadata, "layerInfo.subLayer" );

    if( psLayers == nullptr )
    {
        return nullptr;
    }

    char* pszTablename = nullptr;

    int n = 1;

    for( ; psLayers; psLayers = psLayers->psNext, n++ )
    {
        if( n != nBand )
        {
            continue;
        }

        CPLXMLNode* psVAT = CPLGetXMLNode( psLayers, "vatTableName" );

        if( psVAT != nullptr )
        {
            pszTablename = CPLStrdup(
                CPLGetXMLValue( psLayers, "vatTableName", "" ) );
        }

        break;
    }

    return pszTablename;
}

//  ---------------------------------------------------------------------------
//                                                              FlushMetadata()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::FlushMetadata()
{
    if( bFlushBlock )
    {
        FlushBlock( nCacheBlockId );
    }

    if( ! bFlushMetadata )
    {
        return true;
    }

    bFlushMetadata = false;

    //  --------------------------------------------------------------------
    //  Change the isBlank setting left by SDO_GEOR.createBlank() to 'false'
    //  --------------------------------------------------------------------

    CPLXMLNode* psOInfo = CPLGetXMLNode( phMetadata, "objectInfo" );

    CPLSetXMLValue( psOInfo,  "isBlank", "false" );

    CPLXMLNode* psNode  = CPLGetXMLNode( psOInfo, "blankCellValue" );

    if( psNode != nullptr )
    {
        CPLRemoveXMLChild( psOInfo, psNode );
        CPLDestroyXMLNode( psNode );
    }

    const char* pszRed   = "1";
    const char* pszGreen = "1";
    const char* pszBlue  = "1";

    if( ( nRasterBands > 2 ) &&
        ( ! HasColorMap( 1 ) ) &&
        ( ! HasColorMap( 2 ) ) &&
        ( ! HasColorMap( 3 ) ) )
    {
        pszRed   = "1";
        pszGreen = "2";
        pszBlue  = "3";
    }

    psNode = CPLGetXMLNode( psOInfo, "defaultRed" );
    if( psNode )
    {
        CPLRemoveXMLChild( psOInfo, psNode );
        CPLDestroyXMLNode( psNode );
    }
    CPLCreateXMLElementAndValue( psOInfo, "defaultRed",   pszRed );

    psNode = CPLGetXMLNode( psOInfo, "defaultGreen" );
    if( psNode )
    {
        CPLRemoveXMLChild( psOInfo, psNode );
        CPLDestroyXMLNode( psNode );
    }
    CPLCreateXMLElementAndValue( psOInfo, "defaultGreen",   pszGreen );

    psNode = CPLGetXMLNode( psOInfo, "defaultBlue" );
    if( psNode )
    {
        CPLRemoveXMLChild( psOInfo, psNode );
        CPLDestroyXMLNode( psNode );
    }
    CPLCreateXMLElementAndValue( psOInfo, "defaultBlue",   pszBlue );

    //  --------------------------------------------------------------------
    //  Set compression
    //  --------------------------------------------------------------------

    psNode = CPLGetXMLNode( phMetadata, "rasterInfo.compression" );

    if( psNode )
    {
        CPLSetXMLValue( psNode, "type", sCompressionType.c_str() );

        if( STARTS_WITH_CI(sCompressionType.c_str(), "JPEG") )
        {
            CPLSetXMLValue( psNode, "quality",
                CPLSPrintf( "%d", nCompressQuality ) );
        }
    }

    //  --------------------------------------------------------------------
    //  Update BitmapMask info
    //  --------------------------------------------------------------------

    if( bHasBitmapMask )
    {
        CPLXMLNode* psLayers = CPLGetXMLNode( phMetadata, "layerInfo" );

        if( psLayers )
        {
            CPLCreateXMLElementAndValue( psLayers, "bitmapMask", "true" );
        }
    }

    //  --------------------------------------------------------------------
    //  Update the Metadata directly from the XML text
    //  --------------------------------------------------------------------

    double dfXCoef[3];
    double dfYCoef[3];
    int nMLC;

    dfXCoef[0] = dfXCoefficient[0];
    dfXCoef[1] = dfXCoefficient[1];
    dfXCoef[2] = dfXCoefficient[2];

    dfYCoef[0] = dfYCoefficient[0];
    dfYCoef[1] = dfYCoefficient[1];
    dfYCoef[2] = dfYCoefficient[2];

    if ( eModelCoordLocation == MCL_CENTER )
    {
        dfXCoef[2] += ( dfXCoefficient[0] / 2.0 );
        dfYCoef[2] += ( dfYCoefficient[1] / 2.0 );
        nMLC = MCL_CENTER;
    }
    else
    {
        nMLC = MCL_UPPERLEFT;
    }

    if( phRPC )
    {
        SetRPC();
        nSRID = NO_CRS;
    }

    //  --------------------------------------------------------------------
    //  Serialize XML metadata to plain text
    //  --------------------------------------------------------------------

    char* pszMetadata = CPLSerializeXMLTree( phMetadata );

    if( pszMetadata == nullptr )
    {
        return false;
    }

    //  --------------------------------------------------------------------
    //  Search for existing Spatial Index SRID
    //  --------------------------------------------------------------------

    OWStatement* poStmt = (OWStatement*) nullptr;

    if ( nSRID != UNKNOWN_CRS && bGenSpatialExtent )
    {
        long long nIdxSRID = -1;

        poStmt = poConnection->CreateStatement( CPLSPrintf(
            "DECLARE\n"
            "  IDX_SRID NUMBER;\n"
            "BEGIN\n"
            "  BEGIN\n"
            "    EXECUTE IMMEDIATE \n"
            "         'SELECT SRID FROM ALL_SDO_GEOM_METADATA ' || \n"
            "         'WHERE OWNER = ''%s'' AND ' || \n"
            "         'TABLE_NAME  = ''%s'' AND ' || \n"
            "         'COLUMN_NAME = ''%s'' || ''.SPATIALEXTENT'' '\n"
            "      INTO IDX_SRID;\n"
            "  EXCEPTION\n"
            "    WHEN NO_DATA_FOUND THEN\n"
            "      IDX_SRID := -1;\n"
            "  END;\n"
            "  :idx_srid := IDX_SRID;\n"
            "END;",
                sOwner.c_str(),
                sTable.c_str(),
                sColumn.c_str()));

        poStmt->BindName( ":idx_srid", &nIdxSRID );

        if( ! poStmt->Execute() )
        {
            nIdxSRID = -1;
        }

        if ( nIdxSRID != -1 )
        {
            if ( nExtentSRID == 0 )
            {
                nExtentSRID = nIdxSRID; 
            }
            else
            {
                if ( nExtentSRID != nIdxSRID )
                {
                    CPLError( CE_Warning, CPLE_AppDefined,
                      "Cannot generate spatialExtent, "
                      "Spatial Index SRID is (%lld)",
                      nIdxSRID );

                    nExtentSRID = 0;
                }
            }
        }

        delete poStmt;

        CPLDebug("GEOR","nIdxSRID    = %lld",nIdxSRID);
    }
    else
    {
        nExtentSRID = 0;
    }

    if ( nSRID == 0 || nSRID == UNKNOWN_CRS )
    {
        nExtentSRID = 0;
    }

    CPLDebug("GEOR","nExtentSRID = %lld",nExtentSRID);
    CPLDebug("GEOR","nSRID       = %lld",nSRID);

    //  --------------------------------------------------------------------
    //  Update GeoRaster Metadata
    //  --------------------------------------------------------------------

    int nException = 0;

    OCILobLocator* phLocator = nullptr;

    poStmt = poConnection->CreateStatement( CPLSPrintf(
        "DECLARE\n"
        "  GR1      sdo_georaster;\n"
        "  GM1      sdo_geometry;\n"
        "  SRID     number  := :1;\n"
        "  EXT_SRID number  := :2;\n"
        "  VAT      varchar2(128);\n"
        "BEGIN\n"
        "\n"
        "  SELECT %s INTO GR1 FROM %s%s T WHERE %s FOR UPDATE;\n"
        "\n"
        "  GR1.metadata := sys.xmltype.createxml(:3);\n"
        "\n"
        "  IF SRID != 0 THEN\n"
        "    SDO_GEOR.georeference( GR1, SRID, :4, \n"
        "      SDO_NUMBER_ARRAY(:5, :6, :7), SDO_NUMBER_ARRAY(:8, :9, :10));\n"
        "  END IF;\n"
        "\n"
        "  IF EXT_SRID = 0 THEN\n"
        "    GM1 := NULL;\n"
        "  ELSE\n"
        "    GM1 := SDO_GEOR.generateSpatialExtent( GR1 );\n"
        "    IF EXT_SRID != SRID THEN\n"
        "      GM1 := SDO_CS.transform( GM1, EXT_SRID );\n"
        "    END IF;\n"
        "  END IF;\n"
        "\n"
        "  GR1.spatialExtent := GM1;\n"
        "\n"
        "  VAT := '%s';\n"
        "  IF VAT != '' THEN\n"
        "    SDO_GEOR.setVAT(GR1, 1, VAT);\n"
        "  END IF;\n"
        "\n"
        "  BEGIN\n"
        "    UPDATE %s%s T SET %s = GR1\n"
        "    WHERE %s;\n"
        "  EXCEPTION\n"
        "    WHEN OTHERS THEN\n"
        "      :except := SQLCODE;\n"
        "  END;\n"
        "\n"
        "  COMMIT;\n"
        "END;",
            sColumn.c_str(),
            sSchema.c_str(),
            sTable.c_str(),
            sWhere.c_str(),
            sValueAttributeTab.c_str(),
            sSchema.c_str(),
            sTable.c_str(),
            sColumn.c_str(),
            sWhere.c_str() ) );

    poStmt->WriteCLob( &phLocator, pszMetadata );

    poStmt->Bind( &nSRID );
    poStmt->Bind( &nExtentSRID );
    poStmt->Bind( &phLocator );
    poStmt->Bind( &nMLC );
    poStmt->Bind( &dfXCoef[0] );
    poStmt->Bind( &dfXCoef[1] );
    poStmt->Bind( &dfXCoef[2] );
    poStmt->Bind( &dfYCoef[0] );
    poStmt->Bind( &dfYCoef[1] );
    poStmt->Bind( &dfYCoef[2] );
    poStmt->BindName( ":except", &nException );

    CPLFree( pszMetadata );

    if( ! poStmt->Execute() )
    {
        poStmt->FreeLob(phLocator);
        delete poStmt;
        return false;
    }

    poStmt->FreeLob(phLocator);

    delete poStmt;

    if( nException )
    {
        CPLError( CE_Warning, CPLE_AppDefined,
            "Fail to update GeoRaster Metadata (ORA-%d) ", nException );
    }

    if (bGenPyramid)
    {
        if (GeneratePyramid( nPyramidLevels, sPyramidResampling.c_str(), true ))
        {
            CPLDebug("GEOR", "Generated pyramid successfully.");
        }
        else
        {
            CPLError( CE_Warning, CPLE_AppDefined, "Error generating pyramid!");
        }
    }

    return true;
}

//  ---------------------------------------------------------------------------
//                                                            GeneratePyramid()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::GeneratePyramid( int nLevels,
                                        const char* pszResampling,
                                        bool bInternal )
{
    nPyramidMaxLevel = nLevels;

    if( bInternal )
    {
        CPLString sLevels = "";

        if (nLevels > 0)
        {
            sLevels = CPLSPrintf("rlevel=%d", nLevels);
        }

        OWStatement* poStmt = poConnection->CreateStatement( CPLSPrintf(
            "DECLARE\n"
            "  gr sdo_georaster;\n"
            "BEGIN\n"
            "  SELECT %s INTO gr FROM %s t WHERE %s FOR UPDATE;\n"
            "  sdo_geor.generatePyramid(gr, '%s resampling=%s');\n"
            "  UPDATE %s t SET %s = gr WHERE %s;\n"
            "  COMMIT;\n"
            "END;\n",
                sColumn.c_str(),
                sTable.c_str(),
                sWhere.c_str(),
                sLevels.c_str(),
                pszResampling,
                sTable.c_str(),
                sColumn.c_str(),
                sWhere.c_str() ) );

        if( poStmt->Execute() )
        {
            delete poStmt;
            return true;
        }

        delete poStmt;
        return false;
    }

    //  -----------------------------------------------------------
    //  Create rows for pyramid levels
    //  -----------------------------------------------------------

    OWStatement* poStmt = poConnection->CreateStatement(
        "DECLARE\n"
        "  SCL  NUMBER         := 0;\n"
        "  RC   NUMBER         := 0;\n"
        "  RR   NUMBER         := 0;\n"
        "  CBS2 NUMBER         := 0;\n"
        "  RBS2 NUMBER         := 0;\n"
        "  TBB  NUMBER         := 0;\n"
        "  TRB  NUMBER         := 0;\n"
        "  TCB  NUMBER         := 0;\n"
        "  X    NUMBER         := 0;\n"
        "  Y    NUMBER         := 0;\n"
        "  W    NUMBER         := 0;\n"
        "  H    NUMBER         := 0;\n"
        "  STM  VARCHAR2(1024) := '';\n"
        "BEGIN\n"
        "  EXECUTE IMMEDIATE 'DELETE FROM '||:rdt||' \n"
        "    WHERE RASTERID = '||:rid||' AND PYRAMIDLEVEL > 0';\n"
        "  STM := 'INSERT INTO '||:rdt||' VALUES (:1, :2, :3-1, :4-1, :5-1 ,\n"
        "    SDO_GEOMETRY(2003, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 3),\n"
        "    SDO_ORDINATE_ARRAY(:6, :7, :8-1, :9-1)), EMPTY_BLOB() )';\n"
        "  TBB  := :TotalBandBlocks;\n"
        "  RBS2 := floor(:RowBlockSize / 2);\n"
        "  CBS2 := floor(:ColumnBlockSize / 2);\n"
        "  FOR l IN 1..:level LOOP\n"
        "    SCL := 2 ** l;\n"
        "    RR  := floor(:RasterRows / SCL);\n"
        "    RC  := floor(:RasterColumns / SCL);\n"
        "    IF (RC <= CBS2) OR (RR <= RBS2) THEN\n"
        "      H   := RR;\n"
        "      W   := RC;\n"
        "    ELSE\n"
        "      H   := :RowBlockSize;\n"
        "      W   := :ColumnBlockSize;\n"
        "    END IF;\n"
        "    TRB := greatest(1, ceil( ceil(:RasterColumns / :ColumnBlockSize) / SCL));\n"
        "    TCB := greatest(1, ceil( ceil(:RasterRows / :RowBlockSize) / SCL));\n"
        "    FOR b IN 1..TBB LOOP\n"
        "      Y := 0;\n"
        "      FOR r IN 1..TCB LOOP\n"
        "        X := 0;\n"
        "        FOR c IN 1..TRB LOOP\n"
        "          EXECUTE IMMEDIATE STM USING :rid, l, b, r, c, Y, X, (Y+H), (X+W);\n"
        "          X := X + W;\n"
        "        END LOOP;\n"
        "        Y := Y + H;\n"
        "      END LOOP;\n"
        "    END LOOP;\n"
        "  END LOOP;\n"
        "  COMMIT;\n"
        "END;" );

    const char* pszDataTable = sDataTable.c_str();

    poStmt->BindName( ":rdt",             (char*) pszDataTable );
    poStmt->BindName( ":rid",             &nRasterId );
    poStmt->BindName( ":level",           &nLevels );
    poStmt->BindName( ":TotalBandBlocks", &nTotalBandBlocks );
    poStmt->BindName( ":RowBlockSize",    &nRowBlockSize );
    poStmt->BindName( ":ColumnBlockSize", &nColumnBlockSize );
    poStmt->BindName( ":RasterRows",      &nRasterRows );
    poStmt->BindName( ":RasterColumns",   &nRasterColumns );

    if( ! poStmt->Execute() )
    {
        delete poStmt;
        return false;
    }

    CPLXMLNode* psNode = CPLGetXMLNode( phMetadata, "rasterInfo.pyramid" );

    if( psNode )
    {
        CPLSetXMLValue( psNode, "type", "DECREASE" );
        CPLSetXMLValue( psNode, "resampling", pszResampling );
        CPLSetXMLValue( psNode, "maxLevel", CPLSPrintf( "%d", nLevels ) );
    }

    bFlushMetadata = true;

    return true;
}

//  ---------------------------------------------------------------------------
//                                                            GeneratePyramid()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::DeletePyramid()
{
    OWStatement* poStmt = poConnection->CreateStatement( CPLSPrintf(
        "DECLARE\n"
        "  gr sdo_georaster;\n"
        "BEGIN\n"
        "  SELECT %s INTO gr FROM %s t WHERE %s FOR UPDATE;\n"
        "  sdo_geor.deletePyramid(gr);\n"
        "  UPDATE %s t SET %s = gr WHERE %s;\n"
        "  COMMIT;\n"
        "END;\n",
            sColumn.c_str(),
            sTable.c_str(),
            sWhere.c_str(),
            sTable.c_str(),
            sColumn.c_str(),
            sWhere.c_str() ) );

    poStmt->Execute();

    delete poStmt;
    return false;
}

//  ---------------------------------------------------------------------------
//                                                           CreateBitmapMask()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::InitializeMask( int nLevel,
                                       int nBlockColumns,
                                       int nBlockRows,
                                       int nColumnBlocks,
                                       int nRowBlocks,
                                       int nBandBlocks )
{
    //  -----------------------------------------------------------
    //  Create rows for the bitmap mask
    //  -----------------------------------------------------------

    OWStatement* poStmt = poConnection->CreateStatement(
        "DECLARE\n"
        "  W    NUMBER          := :1;\n"
        "  H    NUMBER          := :2;\n"
        "  BB   NUMBER          := :3;\n"
        "  RB   NUMBER          := :4;\n"
        "  CB   NUMBER          := :5;\n"
        "  X    NUMBER          := 0;\n"
        "  Y    NUMBER          := 0;\n"
        "  STM  VARCHAR2(1024)  := '';\n"
        "BEGIN\n"
        "\n"
        "  EXECUTE IMMEDIATE 'DELETE FROM '||:rdt||' \n"
        "    WHERE RASTERID = '||:rid||' AND PYRAMIDLEVEL = '||:lev||' ';\n"
        "\n"
        "  STM := 'INSERT INTO '||:rdt||' VALUES (:1, :lev, :2-1, :3-1, :4-1 ,\n"
        "    SDO_GEOMETRY(2003, NULL, NULL, SDO_ELEM_INFO_ARRAY(1, 1003, 3),\n"
        "    SDO_ORDINATE_ARRAY(:5, :6, :7-1, :8-1)), EMPTY_BLOB() )';\n"
        "\n"
        "  FOR b IN 1..BB LOOP\n"
        "    Y := 0;\n"
        "    FOR r IN 1..RB LOOP\n"
        "      X := 0;\n"
        "      FOR c IN 1..CB LOOP\n"
        "        EXECUTE IMMEDIATE STM USING :rid, b, r, c, Y, X, (Y+H), (X+W);\n"
        "        X := X + W;\n"
        "      END LOOP;\n"
        "      Y := Y + H;\n"
        "    END LOOP;\n"
        "  END LOOP;\n"
        "END;" );

    char pszDataTable[OWNAME];

    poStmt->Bind( &nBlockColumns );
    poStmt->Bind( &nBlockRows );
    poStmt->Bind( &nBandBlocks );
    poStmt->Bind( &nRowBlocks );
    poStmt->Bind( &nColumnBlocks );
    poStmt->BindName( ":rdt", pszDataTable );
    poStmt->BindName( ":rid", &nRasterId );
    poStmt->BindName( ":lev", &nLevel );

    if( ! poStmt->Execute() )
    {
        delete poStmt;
        return false;
    }

    return true;
}

//  ---------------------------------------------------------------------------
//                                                                UnpackNBits()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::UnpackNBits( GByte* pabyData )
{
    int nPixCount = nColumnBlockSize * nRowBlockSize * nBandBlockSize;

    if( EQUAL( sCellDepth.c_str(), "4BIT" ) )
    {
        for( int ii = nPixCount - 2; ii >= 0; ii -= 2 )
        {
            int k = ii >> 1;
            pabyData[ii+1] = (pabyData[k]     ) & 0xf;
            pabyData[ii]   = (pabyData[k] >> 4) & 0xf;
        }
    }
    else if( EQUAL( sCellDepth.c_str(), "2BIT" ) )
    {
        for( int ii = nPixCount - 4; ii >= 0; ii -= 4 )
        {
            int k = ii >> 2;
            pabyData[ii+3] = (pabyData[k]     ) & 0x3;
            pabyData[ii+2] = (pabyData[k] >> 2) & 0x3;
            pabyData[ii+1] = (pabyData[k] >> 4) & 0x3;
            pabyData[ii]   = (pabyData[k] >> 6) & 0x3;
        }
    }
    else
    {
        for( int ii = nPixCount - 1; ii >= 0; ii-- )
        {
            if( ( pabyData[ii>>3] & ( 128 >> (ii & 0x7) ) ) )
                pabyData[ii] = 1;
            else
                pabyData[ii] = 0;
        }
    }
}

//  ---------------------------------------------------------------------------
//                                                                  PackNBits()
//  ---------------------------------------------------------------------------

void GeoRasterWrapper::PackNBits( GByte* pabyData ) const
{
    int nPixCount = nBandBlockSize * nRowBlockSize * nColumnBlockSize;

    GByte* pabyBuffer = (GByte*) VSI_MALLOC_VERBOSE( nPixCount * sizeof(GByte*) );

    if( pabyBuffer == nullptr )
    {
        return;
    }

    if( nCellSizeBits == 4 )
    {
        for( int ii = 0; ii < nPixCount - 1; ii += 2 )
        {
            int k = ii >> 1;
            pabyBuffer[k] =
                  ((((GByte *) pabyData)[ii+1] & 0xf)     )
                | ((((GByte *) pabyData)[ii]   & 0xf) << 4);
        }
    }
    else if( nCellSizeBits == 2 )
    {
        for( int ii = 0; ii < nPixCount - 3; ii += 4 )
        {
            int k = ii >> 2;
            pabyBuffer[k] =
                  ((((GByte *) pabyData)[ii+3] & 0x3)     )
                | ((((GByte *) pabyData)[ii+2] & 0x3) << 2)
                | ((((GByte *) pabyData)[ii+1] & 0x3) << 4)
                | ((((GByte *) pabyData)[ii]   & 0x3) << 6);
        }
    }
    else
    {
        for( int ii = 0; ii < nPixCount - 7; ii += 8 )
        {
            int k = ii >> 3;
            pabyBuffer[k] =
                  ((((GByte *) pabyData)[ii+7] & 0x1)     )
                | ((((GByte *) pabyData)[ii+6] & 0x1) << 1)
                | ((((GByte *) pabyData)[ii+5] & 0x1) << 2)
                | ((((GByte *) pabyData)[ii+4] & 0x1) << 3)
                | ((((GByte *) pabyData)[ii+3] & 0x1) << 4)
                | ((((GByte *) pabyData)[ii+2] & 0x1) << 5)
                | ((((GByte *) pabyData)[ii+1] & 0x1) << 6)
                | ((((GByte *) pabyData)[ii]   & 0x1) << 7);
        }
    }

    memcpy( pabyData, pabyBuffer, nPixCount );

    CPLFree( pabyBuffer );
}

//  ---------------------------------------------------------------------------
//                                                             UncompressJpeg()
//  ---------------------------------------------------------------------------

const static int K2Chrominance[64] =
{
    17, 18, 24, 47, 99, 99, 99, 99,
    18, 21, 26, 66, 99, 99, 99, 99,
    24, 26, 56, 99, 99, 99, 99, 99,
    47, 66, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99,
    99, 99, 99, 99, 99, 99, 99, 99
};

constexpr int AC_BITS[16] =
{
    0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 119
};

constexpr int AC_HUFFVAL[256] =
{
      0,   1,   2,   3,  17,   4,   5,  33,  49,   6,  18,
     65,  81,   7,  97, 113,  19,  34,  50, 129,   8,  20,
     66, 145, 161, 177, 193,   9,  35,  51,  82, 240,  21,
     98, 114, 209,  10,  22,  36,  52, 225,  37, 241,  23,
     24,  25,  26,  38,  39,  40,  41,  42,  53,  54,  55,
     56,  57,  58,  67,  68,  69,  70,  71,  72,  73,  74,
     83,  84,  85,  86,  87,  88,  89,  90,  99, 100, 101,
    102, 103, 104, 105, 106, 115, 116, 117, 118, 119, 120,
    121, 122, 130, 131, 132, 133, 134, 135, 136, 137, 138,
    146, 147, 148, 149, 150, 151, 152, 153, 154, 162, 163,
    164, 165, 166, 167, 168, 169, 170, 178, 179, 180, 181,
    182, 183, 184, 185, 186, 194, 195, 196, 197, 198, 199,
    200, 201, 202, 210, 211, 212, 213, 214, 215, 216, 217,
    218, 226, 227, 228, 229, 230, 231, 232, 233, 234, 242,
    243, 244, 245, 246, 247, 248, 249, 250
};

constexpr int DC_BITS[16] =
{
    0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0
};

constexpr int DC_HUFFVAL[256] =
{
    0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
};

/***
 *
 * Load the tables based on the Java's JAI default values.
 *
 * JPEGQTable.K2Chrominance.getScaledInstance()
 * JPEGHuffmanTable.StdACChrominance
 * JPEGHuffmanTable.StdDCChrominance
 *
 ***/
static
void JPEG_LoadTables( JQUANT_TBL* hquant_tbl_ptr,
                      JHUFF_TBL* huff_ac_ptr,
                      JHUFF_TBL* huff_dc_ptr,
                      unsigned int nQuality )
{
    int i = 0;
    float fscale_factor;

    //  --------------------------------------------------------------------
    //  Scale Quantization table based on quality
    //  --------------------------------------------------------------------

    fscale_factor = (float) jpeg_quality_scaling( nQuality ) / (float) 100.0;

    for ( i = 0; i < 64; i++ )
    {
        UINT16 temp = (UINT16) floor( K2Chrominance[i] * fscale_factor + 0.5 );
        if ( temp <= 0 )
            temp = 1;
        if ( temp > 255 )
            temp = 255;
        hquant_tbl_ptr->quantval[i] = (UINT16) temp;
    }

    //  --------------------------------------------------------------------
    //  Load AC huffman table
    //  --------------------------------------------------------------------

    for ( i = 1; i <= 16; i++ )
    {
        /* counts[i] is number of Huffman codes of length i bits, i=1..16 */
        huff_ac_ptr->bits[i] = (UINT8) AC_BITS[i-1];
    }

    for ( i = 0; i < 256; i++ )
    {
        /* symbols[] is the list of Huffman symbols, in code-length order */
        huff_ac_ptr->huffval[i] = (UINT8) AC_HUFFVAL[i];
    }

    //  --------------------------------------------------------------------
    //  Load DC huffman table
    //  --------------------------------------------------------------------

    for ( i = 1; i <= 16; i++ )
    {
        /* counts[i] is number of Huffman codes of length i bits, i=1..16 */
        huff_dc_ptr->bits[i] = (UINT8) DC_BITS[i-1];
    }

    for ( i = 0; i < 256; i++ )
    {
        /* symbols[] is the list of Huffman symbols, in code-length order */
        huff_dc_ptr->huffval[i] = (UINT8) DC_HUFFVAL[i];
    }
}

void GeoRasterWrapper::UncompressJpeg( unsigned long nInSize )
{
    //  --------------------------------------------------------------------
    //  Load JPEG in a virtual file
    //  --------------------------------------------------------------------

    const char* pszMemFile = CPLSPrintf( "/vsimem/geor_%p.jpg", pabyBlockBuf );

    VSILFILE *fpImage = VSIFOpenL( pszMemFile, "wb" );
    VSIFWriteL( pabyBlockBuf, nInSize, 1, fpImage );
    VSIFCloseL( fpImage );

    fpImage = VSIFOpenL( pszMemFile, "rb" );

    //  --------------------------------------------------------------------
    //  Initialize decompressor
    //  --------------------------------------------------------------------

    if( ! sDInfo.global_state )
    {
        sDInfo.err = jpeg_std_error( &sJErr );
        jpeg_create_decompress( &sDInfo );

        // -----------------------------------------------------------------
        // Load table for abbreviated JPEG-B
        // -----------------------------------------------------------------

        int nComponentsToLoad = -1; /* doesn't load any table */

        if( EQUAL( sCompressionType.c_str(), "JPEG-B") )
        {
            nComponentsToLoad = nBandBlockSize;
        }

        for( int n = 0; n < nComponentsToLoad; n++ )
        {
            sDInfo.quant_tbl_ptrs[n] =
                jpeg_alloc_quant_table( (j_common_ptr) &sDInfo );
            sDInfo.ac_huff_tbl_ptrs[n] =
                jpeg_alloc_huff_table( (j_common_ptr) &sDInfo );
            sDInfo.dc_huff_tbl_ptrs[n] =
                jpeg_alloc_huff_table( (j_common_ptr) &sDInfo );

            JPEG_LoadTables( sDInfo.quant_tbl_ptrs[n],
                             sDInfo.ac_huff_tbl_ptrs[n],
                             sDInfo.dc_huff_tbl_ptrs[n],
                             nCompressQuality );
        }
    }

    jpeg_vsiio_src( &sDInfo, fpImage );
    jpeg_read_header( &sDInfo, TRUE );

    sDInfo.out_color_space = ( nBandBlockSize == 1 ? JCS_GRAYSCALE : JCS_RGB );

    jpeg_start_decompress( &sDInfo );

    GByte* pabyScanline = pabyBlockBuf;

    for( int iLine = 0; iLine < nRowBlockSize; iLine++ )
    {
        JSAMPLE* ppSamples = (JSAMPLE*) pabyScanline;
        jpeg_read_scanlines( &sDInfo, &ppSamples, 1 );
        pabyScanline += ( nColumnBlockSize * nBandBlockSize );
    }

    jpeg_finish_decompress( &sDInfo );

    VSIFCloseL( fpImage );

    VSIUnlink( pszMemFile );
}

//  ---------------------------------------------------------------------------
//                                                               CompressJpeg()
//  ---------------------------------------------------------------------------

unsigned long GeoRasterWrapper::CompressJpeg( void )
{
    //  --------------------------------------------------------------------
    //  Load JPEG in a virtual file
    //  --------------------------------------------------------------------

    const char* pszMemFile = CPLSPrintf( "/vsimem/geor_%p.jpg", pabyBlockBuf );

    VSILFILE *fpImage = VSIFOpenL( pszMemFile, "wb" );

    bool write_all_tables = TRUE;

    if( EQUAL( sCompressionType.c_str(), "JPEG-B") )
    {
        write_all_tables = FALSE;
    }

    //  --------------------------------------------------------------------
    //  Initialize compressor
    //  --------------------------------------------------------------------

    if( ! sCInfo.global_state )
    {
        sCInfo.err = jpeg_std_error( &sJErr );
        jpeg_create_compress( &sCInfo );

        jpeg_vsiio_dest( &sCInfo, fpImage );

        sCInfo.image_width = nColumnBlockSize;
        sCInfo.image_height = nRowBlockSize;
        sCInfo.input_components = nBandBlockSize;
        sCInfo.in_color_space = (nBandBlockSize == 1 ? JCS_GRAYSCALE : JCS_RGB);
        jpeg_set_defaults( &sCInfo );
        sCInfo.JFIF_major_version = 1;
        sCInfo.JFIF_minor_version = 2;
        jpeg_set_quality( &sCInfo, nCompressQuality, TRUE );

        // -----------------------------------------------------------------
        // Load table for abbreviated JPEG-B
        // -----------------------------------------------------------------

        int nComponentsToLoad = -1; /* doesn't load any table */

        if( EQUAL( sCompressionType.c_str(), "JPEG-B") )
        {
            nComponentsToLoad = nBandBlockSize;
        }

        for( int n = 0; n < nComponentsToLoad; n++ )
        {
            sCInfo.quant_tbl_ptrs[n] =
                jpeg_alloc_quant_table( (j_common_ptr) &sCInfo );
            sCInfo.ac_huff_tbl_ptrs[n] =
                jpeg_alloc_huff_table( (j_common_ptr) &sCInfo );
            sCInfo.dc_huff_tbl_ptrs[n] =
                jpeg_alloc_huff_table( (j_common_ptr) &sCInfo );

            JPEG_LoadTables( sCInfo.quant_tbl_ptrs[n],
                             sCInfo.ac_huff_tbl_ptrs[n],
                             sCInfo.dc_huff_tbl_ptrs[n],
                             nCompressQuality );
        }
    }
    else
    {
        jpeg_vsiio_dest( &sCInfo, fpImage );
    }

    jpeg_suppress_tables( &sCInfo, ! write_all_tables );
    jpeg_start_compress( &sCInfo, write_all_tables );

    GByte* pabyScanline = pabyBlockBuf;

    for( int iLine = 0; iLine < nRowBlockSize; iLine++ )
    {
        JSAMPLE* ppSamples = (JSAMPLE*) pabyScanline;
        jpeg_write_scanlines( &sCInfo, &ppSamples, 1 );
        pabyScanline += ( nColumnBlockSize * nBandBlockSize );
    }

    jpeg_finish_compress( &sCInfo );

    VSIFCloseL( fpImage );

    fpImage = VSIFOpenL( pszMemFile, "rb" );
    size_t nSize = VSIFReadL( pabyCompressBuf, 1, nBlockBytes, fpImage );
    VSIFCloseL( fpImage );

    VSIUnlink( pszMemFile );

    return (unsigned long) nSize;
}

//  ---------------------------------------------------------------------------
//                                                          UncompressDeflate()
//  ---------------------------------------------------------------------------

bool GeoRasterWrapper::UncompressDeflate( unsigned long nBufferSize )
{
    GByte* pabyBuf = (GByte*) VSI_MALLOC_VERBOSE( nBufferSize );

    if( pabyBuf == nullptr )
    {
        return false;
    }

    memcpy( pabyBuf, pabyBlockBuf, nBufferSize );

    // Call ZLib uncompress

    unsigned long nDestLen = nBlockBytes;

    int nRet = uncompress( pabyBlockBuf, &nDestLen, pabyBuf, nBufferSize );

    CPLFree( pabyBuf );

    if( nRet != Z_OK )
    {
        CPLError( CE_Failure, CPLE_AppDefined, "ZLib return code (%d)", nRet );
        return false;
    }

    if( nDestLen != nBlockBytes )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
            "ZLib decompressed buffer size (%ld) expected (%ld)", nDestLen, nBlockBytes );
        return false;
    }

    return true;
}

//  ---------------------------------------------------------------------------
//                                                            CompressDeflate()
//  ---------------------------------------------------------------------------

unsigned long GeoRasterWrapper::CompressDeflate( void )
{
    unsigned long nLen = ((unsigned long)(nBlockBytes * 1.1)) + 12;

    GByte* pabyBuf = (GByte*) VSI_MALLOC_VERBOSE( nBlockBytes );

    if( pabyBuf == nullptr )
    {
        return 0;
    }

    memcpy( pabyBuf, pabyBlockBuf, nBlockBytes );

    // Call ZLib compress

    int nRet = compress( pabyCompressBuf, &nLen, pabyBuf, nBlockBytes );

    CPLFree( pabyBuf );

    if( nRet != Z_OK )
    {
        CPLError( CE_Failure, CPLE_AppDefined, "ZLib return code (%d)", nRet );
        return 0;
    }

    return nLen;
}
