/******************************************************************************
 * $Id: gdal_rat.cpp,v 1.10 2006/10/24 18:49:22 fwarmerdam Exp $
 *
 * Project:  GDAL Core
 * Purpose:  Implementation of GDALRasterAttributeTable and related classes.
 * Author:   Frank Warmerdam, warmerdam@pobox.com
 *
 ******************************************************************************
 * Copyright (c) 2005, Frank Warmerdam
 *
 * 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.
 ******************************************************************************
 *
 * $Log: gdal_rat.cpp,v $
 * Revision 1.10  2006/10/24 18:49:22  fwarmerdam
 * avoid using CPLString in arguments and return from RAT API
 *
 * Revision 1.9  2006/10/23 17:25:31  fwarmerdam
 * Fixed problem with <F> element has no text child because it is empty.
 *
 * Revision 1.8  2006/10/07 02:00:38  fwarmerdam
 * added RAT translation to ColorTable, and a few other fixes
 *
 * Revision 1.7  2006/02/13 17:22:54  fwarmerdam
 * Avoid large amounts of linked list walking adding rows when
 * serializing RAT.
 *
 * Revision 1.6  2005/11/01 22:16:36  fwarmerdam
 * fix comments (bug 985)
 *
 * Revision 1.5  2005/09/29 00:50:36  fwarmerdam
 * Added doc note.
 *
 * Revision 1.4  2005/09/28 21:29:30  fwarmerdam
 * added RAT documentation
 *
 * Revision 1.3  2005/09/27 22:12:44  fwarmerdam
 * fixed bug creating string columns
 *
 * Revision 1.2  2005/09/27 14:05:47  fwarmerdam
 * fixed various C wrappers returning void
 *
 * Revision 1.1  2005/09/24 19:00:53  fwarmerdam
 * New
 *
 */

#include "gdal_priv.h"
#include "gdal_rat.h"

CPL_CVSID("$Id: gdal_rat.cpp,v 1.10 2006/10/24 18:49:22 fwarmerdam Exp $");

/**
 * \class GDALRasterAttributeTable
 *
 * The GDALRasterAttributeTable (or RAT) class is used to encapsulate a table
 * used to provide attribute information about pixel values.  Each row
 * in the table applies to a range of pixel values (or a single value in
 * some cases), and might have attributes such as the histogram count for
 * that range, the color pixels of that range should be drawn names of classes
 * or any other generic information. 
 *
 * Raster attribute tables can be used to represent histograms, color tables,
 * and classification information.  
 *
 * Each column in a raster attribute table has a name, a type (integer,
 * floating point or string), and a GDALRATFieldUsage.  The usage distinguishes 
 * columns with particular understood purposes (such as color, histogram 
 * count, name) and columns that have specific purposes not understood by 
 * the library (long label, suitability_for_growing_wheat, etc).  
 *
 * In the general case each row as a column indicating the minimum pixel
 * values falling into that category, and a column indicating the maximum
 * pixel value.  These are indicated with usage values of GFU_Min, and
 * GFU_Max.  In other cases where each row is a discrete pixel value, one
 * column of usage GFU_MinMax can be used.  
 * 
 * In other cases all the categories are of equal size and regularly spaced 
 * and the categorization information can be determine just by knowing the
 * value at which the categories start, and the size of a category.  This
 * is called "Linear Binning" and the information is kept specially on 
 * the raster attribute table as a whole.
 *
 * RATs are normally associated with GDALRasterBands and be be queried
 * using the GDALRasterBand::GetDefaultRAT() method.  
 */

/************************************************************************/
/*                      GDALRasterAttributeTable()                      */
/*                                                                      */
/*      Simple initialization constructor.                              */
/************************************************************************/

//! Construct empty table.

GDALRasterAttributeTable::GDALRasterAttributeTable()

{
    bColumnsAnalysed = FALSE;
    nMinCol = -1;
    nMaxCol = -1;
    bLinearBinning = FALSE;
    dfRow0Min = -0.5;
    dfBinSize = 1.0;
    nRowCount = 0;
}

/************************************************************************/
/*                   GDALCreateRasterAttributeTable()                   */
/************************************************************************/

GDALRasterAttributeTableH CPL_STDCALL GDALCreateRasterAttributeTable()

{
    return (GDALRasterAttributeTableH) (new GDALRasterAttributeTable());
}

/************************************************************************/
/*                      GDALRasterAttributeTable()                      */
/************************************************************************/

//! Copy constructor.

GDALRasterAttributeTable::GDALRasterAttributeTable( 
    const GDALRasterAttributeTable &oOther )

{
    // We have tried to be careful to allow wholesale assignment
    *this = oOther;
}

/************************************************************************/
/*                     ~GDALRasterAttributeTable()                      */
/*                                                                      */
/*      All magic done by magic by the container destructors.           */
/************************************************************************/

GDALRasterAttributeTable::~GDALRasterAttributeTable()

{
}

/************************************************************************/
/*                  GDALDestroyRasterAttributeTable()                   */
/************************************************************************/

void CPL_STDCALL 
GDALDestroyRasterAttributeTable( GDALRasterAttributeTableH hRAT )

{
    if( hRAT != NULL )
        delete (GDALRasterAttributeTable *) hRAT;
}

/************************************************************************/
/*                           AnalyseColumns()                           */
/*                                                                      */
/*      Internal method to work out which column to use for various     */
/*      tasks.                                                          */
/************************************************************************/

void GDALRasterAttributeTable::AnalyseColumns()

{
    bColumnsAnalysed = TRUE;

    nMinCol = GetColOfUsage( GFU_Min );
    if( nMinCol == -1 )
        nMinCol = GetColOfUsage( GFU_MinMax );

    nMaxCol = GetColOfUsage( GFU_Max );
    if( nMaxCol == -1 )
        nMaxCol = GetColOfUsage( GFU_MinMax );
}

/************************************************************************/
/*                           GetColumnCount()                           */
/************************************************************************/

/**
 * \brief Fetch table column count.
 *
 * This method is the same as the C function GDALRATGetColumnCount().
 *
 * @return the number of columns.
 */

int GDALRasterAttributeTable::GetColumnCount() const

{
    return aoFields.size();
}

/************************************************************************/
/*                       GDALRATGetColumnCount()                        */
/************************************************************************/

int CPL_STDCALL GDALRATGetColumnCount( GDALRasterAttributeTableH hRAT )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetColumnCount();
}

/************************************************************************/
/*                            GetNameOfCol()                            */
/************************************************************************/

/**
 * \brief Fetch name of indicated column.
 *
 * This method is the same as the C function GDALRATGetNameOfCol(), except
 * that the C function returns "const char *".
 *
 * @param iCol the column index (zero based). 
 *
 * @return the column name or an empty string for invalid column numbers.
 */

const char *GDALRasterAttributeTable::GetNameOfCol( int iCol ) const

{
    if( iCol < 0 || iCol >= (int) aoFields.size() )
        return "";

    else
        return aoFields[iCol].sName;
}

/************************************************************************/
/*                        GDALRATGetNameOfCol()                         */
/************************************************************************/

const char *CPL_STDCALL GDALRATGetNameOfCol( GDALRasterAttributeTableH hRAT,
                                             int iCol )

{
    // we don't just wrap the normal operator because we don't want to
    // return a temporary string, we want to return a pointer to the
    // internal column name. 

    GDALRasterAttributeTable *poRAT = (GDALRasterAttributeTable *) hRAT;

    if( iCol < 0 || iCol >= (int) poRAT->aoFields.size() )
        return "";

    else
        return poRAT->aoFields[iCol].sName.c_str();
}

/************************************************************************/
/*                           GetUsageOfCol()                            */
/************************************************************************/

/**
 * \brief Fetch column usage value. 
 *
 * This method is the same as the C function GDALRATGetUsageOfCol().
 *
 * @param iCol the column index (zero based).
 *
 * @return the column usage, or GFU_Generic for improper column numbers.
 */

GDALRATFieldUsage GDALRasterAttributeTable::GetUsageOfCol( int iCol ) const

{
    if( iCol < 0 || iCol >= (int) aoFields.size() )
        return GFU_Generic;

    else
        return aoFields[iCol].eUsage;
}

/************************************************************************/
/*                        GDALRATGetUsageOfCol()                        */
/************************************************************************/

GDALRATFieldUsage CPL_STDCALL 
GDALRATGetUsageOfCol( GDALRasterAttributeTableH hRAT, int iCol )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetUsageOfCol( iCol );
}

/************************************************************************/
/*                            GetTypeOfCol()                            */
/************************************************************************/

/**
 * \brief Fetch color type.
 *
 * This method is the same as the C function GDALRATGetTypeOfCol().
 *
 * @param iCol the column index (zero based).
 *
 * @return color type or GFT_Integer if the column index is illegal.
 */

GDALRATFieldType GDALRasterAttributeTable::GetTypeOfCol( int iCol ) const

{
    if( iCol < 0 || iCol >= (int) aoFields.size() )
        return GFT_Integer;

    else
        return aoFields[iCol].eType;
}

/************************************************************************/
/*                        GDALRATGetTypeOfCol()                         */
/************************************************************************/

GDALRATFieldType CPL_STDCALL 
GDALRATGetTypeOfCol( GDALRasterAttributeTableH hRAT, int iCol )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetTypeOfCol( iCol );
}

/************************************************************************/
/*                           GetColOfUsage()                            */
/************************************************************************/

/**
 * \brief Fetch column index for given usage.
 *
 * Returns the index of the first column of the requested usage type, or -1 
 * if no match is found. 
 *
 * This method is the same as the C function GDALRATGetUsageOfCol().
 *
 * @param eUsage usage type to search for.
 *
 * @return column index, or -1 on failure. 
 */

int GDALRasterAttributeTable::GetColOfUsage( GDALRATFieldUsage eUsage ) const

{
    unsigned int i;

    for( i = 0; i < aoFields.size(); i++ )
    {
        if( aoFields[i].eUsage == eUsage )
            return i;
    }

    return -1;
}

/************************************************************************/
/*                        GDALRATGetColOfUsage()                        */
/************************************************************************/

int CPL_STDCALL 
GDALRATGetColOfUsage( GDALRasterAttributeTableH hRAT, 
                      GDALRATFieldUsage eUsage )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetColOfUsage( eUsage );
}

/************************************************************************/
/*                            GetRowCount()                             */
/************************************************************************/

/**
 * \brief Fetch row count.
 * 
 * This method is the same as the C function GDALRATGetRowCount().
 *
 * @return the number of rows. 
 */

int GDALRasterAttributeTable::GetRowCount() const

{
    return (int) nRowCount;
}

/************************************************************************/
/*                        GDALRATGetUsageOfCol()                        */
/************************************************************************/

int CPL_STDCALL 
GDALRATGetRowCount( GDALRasterAttributeTableH hRAT )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetRowCount();
}

/************************************************************************/
/*                          GetValueAsString()                          */
/************************************************************************/

/**
 * \brief Fetch field value as a string.
 *
 * The value of the requested column in the requested row is returned
 * as a string.  If the field is numeric, it is formatted as a string
 * using default rules, so some precision may be lost.
 *
 * This method is the same as the C function GDALRATGetValueAsString(), 
 * except it returns a "const char *" result.
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * 
 * @return field value
 */

const char *
GDALRasterAttributeTable::GetValueAsString( int iRow, int iField ) const

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return "";
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return "";
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
      {
          ((GDALRasterAttributeTable *) this)->
              osWorkingResult.Printf( "%d", aoFields[iField].anValues[iRow] );
          return osWorkingResult;
      }

      case GFT_Real:
      {
          ((GDALRasterAttributeTable *) this)->
              osWorkingResult.Printf( "%.16g", aoFields[iField].adfValues[iRow]);
          return osWorkingResult;
      }

      case GFT_String:
      {
          return aoFields[iField].aosValues[iRow];
      }
    }

    return "";
}

/************************************************************************/
/*                      GDALRATGetValueAsString()                       */
/************************************************************************/

const char * CPL_STDCALL 
GDALRATGetValueAsString( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    GDALRasterAttributeTable *poRAT = (GDALRasterAttributeTable *) hRAT;

    poRAT->osWorkingResult = poRAT->GetValueAsString( iRow, iField );
    
    return poRAT->osWorkingResult.c_str();
}

/************************************************************************/
/*                           GetValueAsInt()                            */
/************************************************************************/

/**
 * \brief Fetch field value as a integer.
 *
 * The value of the requested column in the requested row is returned
 * as an integer.  Non-integer fields will be converted to integer with
 * the possibility of data loss.
 *
 * This method is the same as the C function GDALRATGetValueAsInt().
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * 
 * @return field value
 */

int 
GDALRasterAttributeTable::GetValueAsInt( int iRow, int iField ) const

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return 0;
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return 0;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        return aoFields[iField].anValues[iRow];

      case GFT_Real:
        return (int)  aoFields[iField].adfValues[iRow];

      case GFT_String:
        return atoi( aoFields[iField].aosValues[iRow].c_str() );
    }

    return 0;
}

/************************************************************************/
/*                        GDALRATGetValueAsInt()                        */
/************************************************************************/

int CPL_STDCALL 
GDALRATGetValueAsInt( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetValueAsInt( iRow, iField );
}

/************************************************************************/
/*                          GetValueAsDouble()                          */
/************************************************************************/

/**
 * \brief Fetch field value as a double.
 *
 * The value of the requested column in the requested row is returned
 * as a double.   Non double fields will be converted to double with
 * the possibility of data loss.
 * 
 * This method is the same as the C function GDALRATGetValueAsDouble().
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * 
 * @return field value
 */

double
GDALRasterAttributeTable::GetValueAsDouble( int iRow, int iField ) const

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return 0;
    }

    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return 0;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        return aoFields[iField].anValues[iRow];

      case GFT_Real:
        return aoFields[iField].adfValues[iRow];

      case GFT_String:
        return atof( aoFields[iField].aosValues[iRow].c_str() );
    }

    return 0;
}

/************************************************************************/
/*                      GDALRATGetValueAsDouble()                       */
/************************************************************************/

double CPL_STDCALL 
GDALRATGetValueAsDouble( GDALRasterAttributeTableH hRAT, int iRow, int iField )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetValueAsDouble(iRow,iField);
}

/************************************************************************/
/*                            SetRowCount()                             */
/************************************************************************/

/**
 * \brief Set row count.
 *
 * Resizes the table to include the indicated number of rows.  Newly created
 * rows will be initialized to their default values - "" for strings, 
 * and zero for numeric fields. 
 *
 * This method is the same as the C function GDALRATSetRowCount().
 *
 * @param nNewCount the new number of rows.
 */

void GDALRasterAttributeTable::SetRowCount( int nNewCount )

{
    if( nNewCount == nRowCount )
        return;

    unsigned int iField;
    for( iField = 0; iField < aoFields.size(); iField++ )
    {
        switch( aoFields[iField].eType )
        {
          case GFT_Integer:
            aoFields[iField].anValues.resize( nNewCount );
            break;

          case GFT_Real:
            aoFields[iField].adfValues.resize( nNewCount );
            break;

          case GFT_String:
            aoFields[iField].aosValues.resize( nNewCount );
            break;
        }
    }

    nRowCount = nNewCount;
}

/************************************************************************/
/*                         GDALRATSetRowCount()                         */
/************************************************************************/

void CPL_STDCALL 
GDALRATSetRowCount( GDALRasterAttributeTableH hRAT, int nNewCount )

{
    ((GDALRasterAttributeTable *) hRAT)->SetRowCount( nNewCount );
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

/**
 * \brief Set field value from string.
 *
 * The indicated field (column) on the indicated row is set from the
 * passed value.  The value will be automatically converted for other field
 * types, with a possible loss of precision.
 *
 * This method is the same as the C function GDALRATSetValueAsString().
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * @param pszValue the value to assign.
 */

void GDALRasterAttributeTable::SetValue( int iRow, int iField, 
                                         const char *pszValue )

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount+1 );
    
    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = atoi(pszValue);
        break;
        
      case GFT_Real:
        aoFields[iField].adfValues[iRow] = atof(pszValue);
        break;
        
      case GFT_String:
        aoFields[iField].aosValues[iRow] = pszValue;
        break;
    }
}

/************************************************************************/
/*                      GDALRATSetValueAsString()                       */
/************************************************************************/

void CPL_STDCALL 
GDALRATSetValueAsString( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                         const char *pszValue )

{
    ((GDALRasterAttributeTable *) hRAT)->SetValue( iRow, iField, pszValue );
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

/**
 * \brief Set field value from integer.
 *
 * The indicated field (column) on the indicated row is set from the
 * passed value.  The value will be automatically converted for other field
 * types, with a possible loss of precision.
 *
 * This method is the same as the C function GDALRATSetValueAsInteger().
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * @param nValue the value to assign.
 */

void GDALRasterAttributeTable::SetValue( int iRow, int iField, 
                                         int nValue )

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount+1 );
    
    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = nValue;
        break;
        
      case GFT_Real:
        aoFields[iField].adfValues[iRow] = nValue;
        break;
        
      case GFT_String:
      {
          char szValue[100];

          sprintf( szValue, "%d", nValue );
          aoFields[iField].aosValues[iRow] = szValue;
      }
      break;
    }
}

/************************************************************************/
/*                        GDALRATSetValueAsInt()                        */
/************************************************************************/

void CPL_STDCALL 
GDALRATSetValueAsInt( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                      int nValue )

{
    ((GDALRasterAttributeTable *) hRAT)->SetValue( iRow, iField, nValue);
}

/************************************************************************/
/*                              SetValue()                              */
/************************************************************************/

/**
 * \brief Set field value from double.
 *
 * The indicated field (column) on the indicated row is set from the
 * passed value.  The value will be automatically converted for other field
 * types, with a possible loss of precision.
 *
 * This method is the same as the C function GDALRATSetValueAsDouble().
 *
 * @param iRow row to fetch (zero based).
 * @param iField column to fetch (zero based).
 * @param dfValue the value to assign.
 */

void GDALRasterAttributeTable::SetValue( int iRow, int iField, 
                                         double dfValue )

{
    if( iField < 0 || iField >= (int) aoFields.size() )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iField (%d) out of range.", iField );

        return;
    }

    if( iRow == nRowCount )
        SetRowCount( nRowCount+1 );
    
    if( iRow < 0 || iRow >= nRowCount )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "iRow (%d) out of range.", iRow );

        return;
    }

    switch( aoFields[iField].eType )
    {
      case GFT_Integer:
        aoFields[iField].anValues[iRow] = (int) dfValue;
        break;
        
      case GFT_Real:
        aoFields[iField].adfValues[iRow] = dfValue;
        break;
        
      case GFT_String:
      {
          char szValue[100];

          sprintf( szValue, "%.15g", dfValue );
          aoFields[iField].aosValues[iRow] = szValue;
      }
      break;
    }
}

/************************************************************************/
/*                      GDALRATSetValueAsDouble()                       */
/************************************************************************/

void CPL_STDCALL 
GDALRATSetValueAsDouble( GDALRasterAttributeTableH hRAT, int iRow, int iField,
                         double dfValue )

{
    ((GDALRasterAttributeTable *) hRAT)->SetValue( iRow, iField, dfValue );
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/************************************************************************/

/**
 * \brief Get row for pixel value.
 *
 * Given a raw pixel value, the raster attribute table is scanned to 
 * determine which row in the table applies to the pixel value.  The
 * row index is returned. 
 *
 * This method is the same as the C function GDALRATGetRowOfValue().
 *
 * @param dfValue the pixel value. 
 *
 * @return the row index or -1 if no row is appropriate. 
 */

int GDALRasterAttributeTable::GetRowOfValue( double dfValue ) const

{
/* -------------------------------------------------------------------- */
/*      Handle case of regular binning.                                 */
/* -------------------------------------------------------------------- */
    if( bLinearBinning )
    {
        int iBin = (int) floor((dfValue - dfRow0Min) / dfBinSize);
        if( iBin < 0 || iBin >= nRowCount )
            return -1;
        else
            return iBin;
    }

/* -------------------------------------------------------------------- */
/*      Do we have any information?                                     */
/* -------------------------------------------------------------------- */
    const GDALRasterAttributeField *poMin, *poMax;

    if( !bColumnsAnalysed )
        ((GDALRasterAttributeTable *) this)->AnalyseColumns();

    if( nMinCol == -1 && nMaxCol == -1 )
        return -1;

    if( nMinCol != -1 )
        poMin = &(aoFields[nMinCol]);
    else
        poMin = NULL;
    
    if( nMaxCol != -1 )
        poMax = &(aoFields[nMaxCol]);
    else
        poMax = NULL;
    
/* -------------------------------------------------------------------- */
/*      Search through rows for match.                                  */
/* -------------------------------------------------------------------- */
    int   iRow;

    for( iRow = 0; iRow < nRowCount; iRow++ )
    {
        if( poMin != NULL )
        {
            if( poMin->eType == GFT_Integer )
            {
                while( iRow < nRowCount && dfValue < poMin->anValues[iRow] ) 
                    iRow++;
            }
            else if( poMin->eType == GFT_Real )
            {
                while( iRow < nRowCount && dfValue < poMin->adfValues[iRow] )
                    iRow++;
            }

            if( iRow == nRowCount )
                break;
        }

        if( poMax != NULL )
        {
            if( (poMax->eType == GFT_Integer 
                 && dfValue > poMax->anValues[iRow] ) 
                || (poMax->eType == GFT_Real 
                    && dfValue > poMax->adfValues[iRow] ) )
                continue;
        }

        return iRow;
    }

    return -1;
}

/************************************************************************/
/*                           GetRowOfValue()                            */
/*                                                                      */
/*      Int arg for now just converted to double.  Perhaps we will      */
/*      handle this in a special way some day?                          */
/************************************************************************/

int GDALRasterAttributeTable::GetRowOfValue( int nValue ) const

{
    return GetRowOfValue( (double) nValue );
}

/************************************************************************/
/*                        GDALRATGetRowOfValue()                        */
/************************************************************************/

int CPL_STDCALL 
GDALRATGetRowOfValue( GDALRasterAttributeTableH hRAT, double dfValue )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetRowOfValue( dfValue );
}

/************************************************************************/
/*                          SetLinearBinning()                          */
/************************************************************************/

/**
 * \brief Set linear binning information.
 *
 * For RATs with equal sized categories (in pixel value space) that are
 * evenly spaced, this method may be used to associate the linear binning
 * information with the table.
 *
 * This method is the same as the C function GDALRATSetLinearBinning().
 *
 * @param dfRow0MinIn the lower bound (pixel value) of the first category.
 * @param dfBinSizeIn the width of each category (in pixel value units). 
 *
 * @return CE_None on success or CE_Failure on failure.
 */

CPLErr GDALRasterAttributeTable::SetLinearBinning( double dfRow0MinIn, 
                                                   double dfBinSizeIn )

{
    bLinearBinning = TRUE;
    dfRow0Min = dfRow0MinIn;
    dfBinSize = dfBinSizeIn;

    return CE_None;
}

/************************************************************************/
/*                      GDALRATSetLinearBinning()                       */
/************************************************************************/

CPLErr CPL_STDCALL 
GDALRATSetLinearBinning( GDALRasterAttributeTableH hRAT, 
                         double dfRow0Min, double dfBinSize )

{
    return ((GDALRasterAttributeTable *) hRAT)->SetLinearBinning(
        dfRow0Min, dfBinSize );
}

/************************************************************************/
/*                          GetLinearBinning()                          */
/************************************************************************/

/**
 * \brief Get linear binning information.
 *
 * Returns linear binning information if any is associated with the RAT.
 *
 * This method is the same as the C function GDALRATGetLinearBinning().
 *
 * @param pdfRow0MinIn (out) the lower bound (pixel value) of the first category.
 * @param pdfBinSizeIn (out) the width of each category (in pixel value units).
 *
 * @return TRUE if linear binning information exists or FALSE if there is none.
 */

int GDALRasterAttributeTable::GetLinearBinning( double *pdfRow0Min,
                                                double *pdfBinSize ) const

{
    if( !bLinearBinning )
        return FALSE;

    *pdfRow0Min = dfRow0Min;
    *pdfBinSize = dfBinSize;

    return TRUE;
}

/************************************************************************/
/*                      GDALRATGetLinearBinning()                       */
/************************************************************************/

int CPL_STDCALL 
GDALRATGetLinearBinning( GDALRasterAttributeTableH hRAT, 
                         double *pdfRow0Min, double *pdfBinSize )

{
    return ((GDALRasterAttributeTable *) hRAT)->GetLinearBinning(
        pdfRow0Min, pdfBinSize );
}

/************************************************************************/
/*                            CreateColumn()                            */
/************************************************************************/

/**
 * \brief Create new column.
 *
 * If the table already has rows, all row values for the new column will
 * be initialized to the default value ("", or zero).  The new column is
 * always created as the last column, can will be column (field) 
 * "GetColumnCount()-1" after CreateColumn() has completed successfully.
 * 
 * This method is the same as the C function GDALRATCreateColumn().
 *
 * @param pszFieldName the name of the field to create.
 * @param eFieldType the field type (integer, double or string).
 * @param eFieldUsage the field usage, GFU_Generic if not known.
 *
 * @return CE_None on success or CE_Failure if something goes wrong.
 */

CPLErr GDALRasterAttributeTable::CreateColumn( const char *pszFieldName, 
                                               GDALRATFieldType eFieldType,
                                               GDALRATFieldUsage eFieldUsage )

{
    int iNewField = aoFields.size();

    aoFields.resize( iNewField+1 );

    aoFields[iNewField].sName = pszFieldName;
    aoFields[iNewField].eType = eFieldType;
    aoFields[iNewField].eUsage = eFieldUsage;

    if( eFieldType == GFT_Integer )
        aoFields[iNewField].anValues.resize( nRowCount );
    else if( eFieldType == GFT_Real )
        aoFields[iNewField].adfValues.resize( nRowCount );
    else if( eFieldType == GFT_String )
        aoFields[iNewField].aosValues.resize( nRowCount );

    return CE_None;
}

/************************************************************************/
/*                        GDALRATCreateColumn()                         */
/************************************************************************/

CPLErr CPL_STDCALL GDALRATCreateColumn( GDALRasterAttributeTableH hRAT, 
                                        const char *pszFieldName, 
                                        GDALRATFieldType eFieldType,
                                        GDALRATFieldUsage eFieldUsage )

{
    return ((GDALRasterAttributeTable *) hRAT)->CreateColumn( pszFieldName, 
                                                              eFieldType,
                                                              eFieldUsage );
}

/************************************************************************/
/*                      InitializeFromColorTable()                      */
/************************************************************************/

/**
 * \brief Initialize from color table.
 *
 * This method will setup a whole raster attribute table based on the
 * contents of the passed color table.  The Value (GFU_MinMax), 
 * Red (GFU_Red), Green (GFU_Green), Blue (GFU_Blue), and Alpha (GFU_Alpha)
 * fields are created, and a row is set for each entry in the color table. 
 * 
 * The raster attribute table must be empty before calling 
 * InitializeFromColorTable(). 
 *
 * The Value fields are set based on the implicit assumption with color
 * tables that entry 0 applies to pixel value 0, 1 to 1, etc. 
 *
 * This method is the same as the C function GDALRATInitializeFromColorTable().
 *
 * @param poTable the color table to copy from.
 *
 * @param CE_None on success or CE_Failure if something goes wrong.
 */

CPLErr GDALRasterAttributeTable::InitializeFromColorTable( 
    const GDALColorTable *poTable )

{
    int iRow;

    if( GetRowCount() > 0 || GetColumnCount() > 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined, 
                  "Raster Attribute Table not empty in InitializeFromColorTable()" );
        return CE_Failure;
    }

    SetLinearBinning( 0.0, 1.0 );
    CreateColumn( "Value", GFT_Integer, GFU_MinMax );
    CreateColumn( "Red", GFT_Integer, GFU_Red );
    CreateColumn( "Green", GFT_Integer, GFU_Green );
    CreateColumn( "Blue", GFT_Integer, GFU_Blue );
    CreateColumn( "Alpha", GFT_Integer, GFU_Alpha );

    SetRowCount( poTable->GetColorEntryCount() );

    for( iRow = 0; iRow < poTable->GetColorEntryCount(); iRow++ )
    {
        GDALColorEntry sEntry;

        poTable->GetColorEntryAsRGB( iRow, &sEntry );

        SetValue( iRow, 0, iRow );
        SetValue( iRow, 1, sEntry.c1 );
        SetValue( iRow, 2, sEntry.c2 );
        SetValue( iRow, 3, sEntry.c3 );
        SetValue( iRow, 4, sEntry.c4 );
    }

    return CE_None;
}

/************************************************************************/
/*                  GDALRATInitializeFromColorTable()                   */
/************************************************************************/

CPLErr CPL_STDCALL 
GDALRATInitializeFromColorTable( GDALRasterAttributeTableH hRAT,
                                 GDALColorTableH hCT )
                                 

{
    return ((GDALRasterAttributeTable *) hRAT)->
        InitializeFromColorTable( (GDALColorTable *) hCT );
}

/************************************************************************/
/*                       TranslateToColorTable()                        */
/************************************************************************/

/**
 * \brief Translate to a color table.
 *
 * This method will attempt to create a corresponding GDALColorTable from
 * this raster attribute table. 
 * 
 * This method is the same as the C function GDALRATTranslateToColorTable().
 *
 * @param nEntryCount The number of entries to produce (0 to nEntryCount-1), or -1 to auto-determine the number of entries.
 *
 * @return the generated color table or NULL on failure.
 */

GDALColorTable *GDALRasterAttributeTable::TranslateToColorTable( 
    int nEntryCount )

{
/* -------------------------------------------------------------------- */
/*      Establish which fields are red, green, blue and alpha.          */
/* -------------------------------------------------------------------- */
    int iRed, iGreen, iBlue, iAlpha;

    iRed = GetColOfUsage( GFU_Red );
    iGreen = GetColOfUsage( GFU_Green );
    iBlue = GetColOfUsage( GFU_Blue );
    iAlpha = GetColOfUsage( GFU_Alpha );

    if( iRed == -1 || iGreen == -1 || iBlue == -1 )
        return NULL;

/* -------------------------------------------------------------------- */
/*      If we aren't given an explicit number of values to scan for,    */
/*      search for the maximum "max" value.                             */
/* -------------------------------------------------------------------- */
    if( nEntryCount == -1 )
    {
        int  iRow, nRowCount = GetRowCount();
        int  iMaxCol;

        iMaxCol = GetColOfUsage( GFU_Max );
        if( iMaxCol == -1 )
            GetColOfUsage( GFU_MinMax );

        if( iMaxCol == -1 || nRowCount == 0 )
            return NULL;
    
        for( iRow = 0; iRow < nRowCount; iRow++ )
            nEntryCount = MAX(nEntryCount,GetValueAsInt(iRow,iMaxCol)+1);

        if( nEntryCount < 0 )
            return NULL;

        // restrict our number of entries to something vaguely sensible
        nEntryCount = MIN(65535,nEntryCount);
    }

/* -------------------------------------------------------------------- */
/*      Assign values to color table.                                   */
/* -------------------------------------------------------------------- */
    GDALColorTable *poCT = new GDALColorTable();
    int iEntry;

    for( iEntry = 0; iEntry < nEntryCount; iEntry++ )
    {
        GDALColorEntry sColor;
        int iRow = GetRowOfValue( iEntry );

        if( iRow == -1 )
        {
            sColor.c1 = sColor.c2 = sColor.c3 = sColor.c4 = 0;
        }
        else
        {
            sColor.c1 = GetValueAsInt( iRow, iRed );
            sColor.c2 = GetValueAsInt( iRow, iGreen );
            sColor.c3 = GetValueAsInt( iRow, iBlue );
            if( iAlpha == -1 )
                sColor.c4 = 255;
            else
                sColor.c4 = GetValueAsInt( iRow, iAlpha );
        }
        
        poCT->SetColorEntry( iEntry, &sColor );
    }

    return poCT;
}

/************************************************************************/
/*                  GDALRATInitializeFromColorTable()                   */
/************************************************************************/

GDALColorTableH CPL_STDCALL 
GDALRATTranslateToColorTable( GDALRasterAttributeTableH hRAT,
                              int nEntryCount )
                                 

{
    return ((GDALRasterAttributeTable *) hRAT)->
        TranslateToColorTable( nEntryCount );
}

/************************************************************************/
/*                              XMLInit()                               */
/************************************************************************/

CPLErr GDALRasterAttributeTable::XMLInit( CPLXMLNode *psTree, 
                                          const char * /*pszVRTPath*/ )

{
    CPLAssert( GetRowCount() == 0 && GetColumnCount() == 0 );
    
/* -------------------------------------------------------------------- */
/*      Linear binning.                                                 */
/* -------------------------------------------------------------------- */
    if( CPLGetXMLValue( psTree, "Row0Min", NULL ) 
        && CPLGetXMLValue( psTree, "BinSize", NULL ) )
    {
        SetLinearBinning( atof(CPLGetXMLValue( psTree, "Row0Min","" )), 
                          atof(CPLGetXMLValue( psTree, "BinSize","" )) );
    }

/* -------------------------------------------------------------------- */
/*      Column definitions                                              */
/* -------------------------------------------------------------------- */
    CPLXMLNode *psChild;

    for( psChild = psTree->psChild; psChild != NULL; psChild = psChild->psNext)
    {
        if( psChild->eType == CXT_Element 
            && EQUAL(psChild->pszValue,"FieldDefn") )
        {
            CreateColumn( 
              CPLGetXMLValue( psChild, "Name", "" ), 
              (GDALRATFieldType) atoi(CPLGetXMLValue( psChild, "Type", "1" )),
              (GDALRATFieldUsage) atoi(CPLGetXMLValue( psChild, "Usage","0")));
        }
    }
    
/* -------------------------------------------------------------------- */
/*      Row data.                                                       */
/* -------------------------------------------------------------------- */
    for( psChild = psTree->psChild; psChild != NULL; psChild = psChild->psNext)
    {
        if( psChild->eType == CXT_Element 
            && EQUAL(psChild->pszValue,"Row") )
        {
            int iRow = atoi(CPLGetXMLValue(psChild,"index","0"));
            int iField = 0;
            CPLXMLNode *psF;

            for( psF = psChild->psChild; psF != NULL; psF = psF->psNext )
            {
                if( psF->eType != CXT_Element || !EQUAL(psF->pszValue,"F") )
                    continue;

                if( psF->psChild != NULL && psF->psChild->eType == CXT_Text )
                    SetValue( iRow, iField++, psF->psChild->pszValue );
                else
                    SetValue( iRow, iField++, "" );
            }
        }
    }

    return CE_None;
}

/************************************************************************/
/*                             Serialize()                              */
/************************************************************************/

CPLXMLNode *GDALRasterAttributeTable::Serialize() const

{
    CPLXMLNode *psTree = NULL;
    CPLXMLNode *psRow = NULL;

    psTree = CPLCreateXMLNode( NULL, CXT_Element, "GDALRasterAttributeTable" );

/* -------------------------------------------------------------------- */
/*      Add attributes with regular binning info if appropriate.        */
/* -------------------------------------------------------------------- */
    char szValue[128];

    if( bLinearBinning )
    {
        sprintf( szValue, "%.16g", dfRow0Min );
        CPLCreateXMLNode( 
            CPLCreateXMLNode( psTree, CXT_Attribute, "Row0Min" ), 
            CXT_Text, szValue );

        sprintf( szValue, "%.16g", dfBinSize );
        CPLCreateXMLNode( 
            CPLCreateXMLNode( psTree, CXT_Attribute, "BinSize" ), 
            CXT_Text, szValue );
    }

/* -------------------------------------------------------------------- */
/*      Define each column.                                             */
/* -------------------------------------------------------------------- */
    int iCol;

    for( iCol = 0; iCol < (int) aoFields.size(); iCol++ )
    {
        CPLXMLNode *psCol;

        psCol = CPLCreateXMLNode( psTree, CXT_Element, "FieldDefn" );
        
        sprintf( szValue, "%d", iCol );
        CPLCreateXMLNode( 
            CPLCreateXMLNode( psCol, CXT_Attribute, "index" ), 
            CXT_Text, szValue );

        CPLCreateXMLElementAndValue( psCol, "Name", 
                                     aoFields[iCol].sName.c_str() );

        sprintf( szValue, "%d", (int) aoFields[iCol].eType );
        CPLCreateXMLElementAndValue( psCol, "Type", szValue );

        sprintf( szValue, "%d", (int) aoFields[iCol].eUsage );
        CPLCreateXMLElementAndValue( psCol, "Usage", szValue );
    }

/* -------------------------------------------------------------------- */
/*      Write out each row.                                             */
/* -------------------------------------------------------------------- */
    int iRow;
    CPLXMLNode *psTail = NULL;

    for( iRow = 0; iRow < nRowCount; iRow++ )
    {
        psRow = CPLCreateXMLNode( NULL, CXT_Element, "Row" );
        if( psTail == NULL )
            CPLAddXMLChild( psTree, psRow );
        else
            psTail->psNext = psRow;
        psTail = psRow;

        sprintf( szValue, "%d", iRow );
        CPLCreateXMLNode( 
            CPLCreateXMLNode( psRow, CXT_Attribute, "index" ), 
            CXT_Text, szValue );

        for( iCol = 0; iCol < (int) aoFields.size(); iCol++ )
        {
            const char *pszValue = szValue;

            if( aoFields[iCol].eType == GFT_Integer )
                sprintf( szValue, "%d", aoFields[iCol].anValues[iRow] );
            else if( aoFields[iCol].eType == GFT_Real )
                sprintf( szValue, "%.16g", aoFields[iCol].adfValues[iRow] );
            else
                pszValue = aoFields[iCol].aosValues[iRow].c_str();

            CPLCreateXMLElementAndValue( psRow, "F", pszValue );
        }
    }

    return psTree;
}

/************************************************************************/
/*                            DumpReadable()                            */
/************************************************************************/

/**
 * \brief Dump RAT in readable form.
 *
 * Currently the readable form is the XML encoding ... only barely 
 * readable. 
 *
 * This method is the same as the C function GDALRATDumpReadable().
 *
 * @param fp file to dump to or NULL for stdout. 
 */

void GDALRasterAttributeTable::DumpReadable( FILE * fp )

{
    CPLXMLNode *psTree = Serialize();
    char *pszXMLText = CPLSerializeXMLTree( psTree );

    CPLDestroyXMLNode( psTree );

    if( fp == NULL )
        fp = stdout;

    fprintf( fp, "%s\n", pszXMLText );

    CPLFree( pszXMLText );
}

/************************************************************************/
/*                        GDALRATDumpReadable()                         */
/************************************************************************/

void CPL_STDCALL 
GDALRATDumpReadable( GDALRasterAttributeTableH hRAT, FILE *fp )

{
    ((GDALRasterAttributeTable *) hRAT)->DumpReadable( fp );
}

/************************************************************************/
/*                               Clone()                                */
/************************************************************************/

/**
 * \brief Copy Raster Attribute Table
 *
 * Creates a new copy of an existing raster attribute table.  The new copy
 * becomes the responsibility of the caller to destroy.
 *
 * This method is the same as the C function GDALRATClone().
 *
 * @return new copy of the RAT. 
 */

GDALRasterAttributeTable *GDALRasterAttributeTable::Clone() const

{
    return new GDALRasterAttributeTable( *this );
}

/************************************************************************/
/*                            GDALRATClone()                            */
/************************************************************************/

GDALRasterAttributeTableH CPL_STDCALL 
GDALRATClone( GDALRasterAttributeTableH hRAT )

{
    return ((GDALRasterAttributeTable *) hRAT)->Clone();
}
