/******************************************************************************
 * $Id: irisdataset.cpp 33720 2016-03-15 00:39:53Z goatbar $
 *
 * Project:  IRIS Reader
 * Purpose:  All code for IRIS format Reader
 * Author:   Roger Veciana, rveciana@gmail.com
 *           Portions are adapted from code copyright (C) 2005-2012
 *           Chris Veness under a CC-BY 3.0 licence
 *
 ******************************************************************************
 * Copyright (c) 2012, Roger Veciana <rveciana@gmail.com>
 * Copyright (c) 2012-2013, Even Rouault <even dot rouault at mines-paris dot org>
 *
 * 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.
 ****************************************************************************/

#ifndef DEG2RAD
#  define DEG2RAD (M_PI/180.0)
#endif

#ifndef RAD2DEG
#  define RAD2DEG (180.0/M_PI)
#endif

#include "gdal_frmts.h"
#include "gdal_pam.h"
#include "ogr_spatialref.h"

#include <sstream>

CPL_CVSID("$Id: irisdataset.cpp 33720 2016-03-15 00:39:53Z goatbar $");

#define ARRAY_ELEMENT_COUNT(x) ((sizeof(x))/sizeof(x[0]))

/************************************************************************/
/* ==================================================================== */
/*                                  IRISDataset                         */
/* ==================================================================== */
/************************************************************************/

class IRISRasterBand;

class IRISDataset : public GDALPamDataset
{
    friend class IRISRasterBand;

    VSILFILE              *fp;
    GByte                 abyHeader[640];
    int                   bNoDataSet;
    double                dfNoDataValue;
    static const char* const   aszProductNames[];
    static const char* const   aszDataTypeCodes[];
    static const char* const   aszDataTypes[];
    static const char* const   aszProjections[];
    unsigned short        nProductCode;
    unsigned short        nDataTypeCode;
    unsigned char         nProjectionCode;
    float                 fNyquistVelocity;
    char*                 pszSRS_WKT;
    double                adfGeoTransform[6];
    int                   bHasLoadedProjection;
    void                  LoadProjection();
    std::pair <double,double> GeodesicCalculation(float fLat, float fLon, float fAngle, float fDist, float fEquatorialRadius, float fPolarRadius, float fFlattening);

public:
    IRISDataset();
    ~IRISDataset();

    static GDALDataset *Open( GDALOpenInfo * );
    static int Identify( GDALOpenInfo * );

    CPLErr 	GetGeoTransform( double * padfTransform );
    const char *GetProjectionRef();
};

const char* const IRISDataset::aszProductNames[]= {
    "", "PPI", "RHI", "CAPPI", "CROSS", "TOPS", "TRACK", "RAIN1", "RAINN",
    "VVP", "VIL", "SHEAR", "WARN", "CATCH", "RTI", "RAW", "MAX", "USER",
    "USERV", "OTHER", "STATUS", "SLINE", "WIND", "BEAM", "TEXT", "FCAST",
    "NDOP", "IMAGE", "COMP", "TDWR", "GAGE", "DWELL", "SRI", "BASE", "HMAX"};

const char* const IRISDataset::aszDataTypeCodes[]={
    "XHDR", "DBT" ,"dBZ", "VEL", "WIDTH", "ZDR", "ORAIN", "dBZC", "DBT2",
    "dBZ2", "VEL2", "WIDTH2", "ZDR2", "RAINRATE2", "KDP", "KDP2", "PHIDP",
    "VELC", "SQI", "RHOHV", "RHOHV2", "dBZC2", "VELC2", "SQI2", "PHIDP2",
    "LDRH", "LDRH2", "LDRV", "LDRV2", "FLAGS", "FLAGS2", "FLOAT32", "HEIGHT",
    "VIL2", "NULL", "SHEAR", "DIVERGE2", "FLIQUID2", "USER", "OTHER", "DEFORM2",
    "VVEL2", "HVEL2", "HDIR2", "AXDIL2", "TIME2", "RHOH", "RHOH2", "RHOV",
    "RHOV2", "PHIH", "PHIH2", "PHIV", "PHIV2", "USER2", "HCLASS", "HCLASS2",
    "ZDRC", "ZDRC2", "TEMPERATURE16", "VIR16", "DBTV8", "DBTV16", "DBZV8",
    "DBZV16", "SNR8", "SNR16", "ALBEDO8", "ALBEDO16", "VILD16", "TURB16"};
const char* const IRISDataset::aszDataTypes[]={
    "Extended Headers","Total H power (1 byte)","Clutter Corrected H reflectivity (1 byte)",
    "Velocity (1 byte)","Width (1 byte)","Differential reflectivity (1 byte)",
    "Old Rainfall rate (stored as dBZ)","Fully corrected reflectivity (1 byte)",
    "Uncorrected reflectivity (2 byte)","Corrected reflectivity (2 byte)",
    "Velocity (2 byte)","Width (2 byte)","Differential reflectivity (2 byte)",
    "Rainfall rate (2 byte)","Kdp (specific differential phase)(1 byte)",
    "Kdp (specific differential phase)(2 byte)","PHIdp (differential phase)(1 byte)",
    "Corrected Velocity (1 byte)","SQI (1 byte)","RhoHV(0) (1 byte)","RhoHV(0) (2 byte)",
    "Fully corrected reflectivity (2 byte)","Corrected Velocity (2 byte)","SQI (2 byte)",
    "PHIdp (differential phase)(2 byte)","LDR H to V (1 byte)","LDR H to V (2 byte)",
    "LDR V to H (1 byte)","LDR V to H (2 byte)","Individual flag bits for each bin","",
    "Test of floating format", "Height (1/10 km) (1 byte)", "Linear liquid (.001mm) (2 byte)",
    "Data type is not applicable", "Wind Shear (1 byte)", "Divergence (.001 10**-4) (2-byte)",
    "Floated liquid (2 byte)", "User type, unspecified data (1 byte)",
    "Unspecified data, no color legend", "Deformation (.001 10**-4) (2-byte)",
    "Vertical velocity (.01 m/s) (2-byte)", "Horizontal velocity (.01 m/s) (2-byte)",
    "Horizontal wind direction (.1 degree) (2-byte)", "Axis of Dillitation (.1 degree) (2-byte)",
    "Time of data (seconds) (2-byte)", "Rho H to V (1 byte)", "Rho H to V (2 byte)",
    "Rho V to H (1 byte)", "Rho V to H (2 byte)", "Phi H to V (1 byte)", "Phi H to V (2 byte)",
    "Phi V to H (1 byte)", "Phi V to H (2 byte)", "User type, unspecified data (2 byte)",
    "Hydrometeor class (1 byte)", "Hydrometeor class (2 byte)", "Corrected Differential reflectivity (1 byte)",
    "Corrected Differential reflectivity (2 byte)", "Temperature (2 byte)",
    "Vertically Integrated Reflectivity (2 byte)", "Total V Power (1 byte)", "Total V Power (2 byte)",
    "Clutter Corrected V Reflectivity (1 byte)", "Clutter Corrected V Reflectivity (2 byte)",
    "Signal to Noise ratio (1 byte)", "Signal to Noise ratio (2 byte)", "Albedo (1 byte)",
    "Albedo (2 byte)", "VIL Density (2 byte)", "Turbulence (2 byte)"};
const char* const IRISDataset::aszProjections[]={
    "Azimutal equidistant","Mercator","Polar Stereographic","UTM",
    // FIXME: is it a typo here or in IRIS itself: Perspective or Prespective ?
    "Prespective from geosync","Equidistant cylindrical","Gnomonic",
    "Gauss conformal","Lambert conformal conic"};

/************************************************************************/
/* ==================================================================== */
/*                            IRISRasterBand                            */
/* ==================================================================== */
/************************************************************************/

class IRISRasterBand : public GDALPamRasterBand
{
    friend class IRISDataset;

    unsigned char*        pszRecord;
    int                   bBufferAllocFailed;

public:
    IRISRasterBand( IRISDataset *, int );
    ~IRISRasterBand();

    virtual CPLErr IReadBlock( int, int, void * );

    virtual double          GetNoDataValue( int * );
    virtual CPLErr          SetNoDataValue( double );
};


/************************************************************************/
/*                           IRISRasterBand()                           */
/************************************************************************/

IRISRasterBand::IRISRasterBand( IRISDataset *poDSIn, int nBandIn )
{
    this->poDS = poDSIn;
    this->nBand = nBandIn;
    eDataType = GDT_Float32;
    nBlockXSize = poDS->GetRasterXSize();
    nBlockYSize = 1;
    pszRecord = NULL;
    bBufferAllocFailed = FALSE;
}

IRISRasterBand::~IRISRasterBand()
{
    VSIFree(pszRecord);
}

/************************************************************************/
/*                             IReadBlock()                             */
/************************************************************************/

CPLErr IRISRasterBand::IReadBlock( CPL_UNUSED int nBlockXOff,
                                   int nBlockYOff,
                                   void * pImage )

{
    IRISDataset *poGDS = (IRISDataset *) poDS;

    //Every product type has it's own size. TODO: Move it like dataType
    int nDataLength = 1;
    if(poGDS->nDataTypeCode == 2){nDataLength=1;}
    else if(poGDS->nDataTypeCode == 37){nDataLength=2;}
    else if(poGDS->nDataTypeCode == 33){nDataLength=2;}
    else if(poGDS->nDataTypeCode == 32){nDataLength=1;}

    int i;
    //We allocate space for storing a record:
    if (pszRecord == NULL)
    {
        if (bBufferAllocFailed)
            return CE_Failure;

        pszRecord = (unsigned char *) VSI_MALLOC_VERBOSE(nBlockXSize*nDataLength);

        if (pszRecord == NULL)
        {
            bBufferAllocFailed = TRUE;
            return CE_Failure;
        }
    }

    //Prepare to read (640 is the header size in bytes) and read (the y axis in the IRIS files in the inverse direction)
    //The previous bands are also added as an offset

    VSIFSeekL( poGDS->fp, 640 + (vsi_l_offset)nDataLength*poGDS->GetRasterXSize()*poGDS->GetRasterYSize()*(this->nBand-1) +
                                (vsi_l_offset)nBlockXSize*nDataLength*(poGDS->GetRasterYSize()-1-nBlockYOff), SEEK_SET );

    if( (int)VSIFReadL( pszRecord, nBlockXSize*nDataLength, 1, poGDS->fp ) != 1 )
        return CE_Failure;

    //If datatype is dbZ or dBT:
    //See point 3.3.3 at page 3.33 of the manual
    if(poGDS->nDataTypeCode == 2 || poGDS->nDataTypeCode == 1){
        float fVal;
        for (i=0;i<nBlockXSize;i++){
            fVal = (((float) *(pszRecord+i*nDataLength)) -64)/2.0f;
            if (fVal == 95.5f)
                fVal = -9999.0f;
            ((float *) pImage)[i] = fVal;
        }
    //If datatype is dbZ2 or dBT2:
    //See point 3.3.4 at page 3.33 of the manual
    } else if(poGDS->nDataTypeCode == 8 || poGDS->nDataTypeCode == 9){
        float fVal;
        for (i=0;i<nBlockXSize;i++){
            fVal = (((float) CPL_LSBUINT16PTR(pszRecord+i*nDataLength)) - 32768)/100.0f;
            if (fVal == 327.67f)
                fVal = -9999.0f;
            ((float *) pImage)[i] = fVal;
        }
    //Fliquid2 (Rain1 & Rainn products)
    //See point 3.3.11 at page 3.43 of the manual
    } else if(poGDS->nDataTypeCode == 37){
        unsigned short nVal, nExp, nMantissa;
        float fVal2=0;
        for (i=0;i<nBlockXSize;i++){
            nVal = CPL_LSBUINT16PTR(pszRecord+i*nDataLength);
            nExp = nVal>>12;
            nMantissa = nVal - (nExp<<12);
            if (nVal == 65535)
                fVal2 = -9999.0f;
            else if (nExp == 0)
                fVal2 = (float) nMantissa / 1000.0f;
            else
                fVal2 = (float)((nMantissa+4096)<<(nExp-1))/1000.0f;
            ((float *) pImage)[i] = fVal2;
        }
    //VIL2 (VIL products)
    //See point 3.3.41 at page 3.54 of the manual
    } else if(poGDS->nDataTypeCode == 33){
        float fVal;
        for (i=0;i<nBlockXSize;i++){
            fVal = (float) CPL_LSBUINT16PTR(pszRecord+i*nDataLength);
            if (fVal == 65535.0f)
                ((float *) pImage)[i] = -9999.0f;
            else if (fVal == 0)
                ((float *) pImage)[i] = -1.0f;
            else
                ((float *) pImage)[i] = (fVal-1)/1000.0f;
        }
    //HEIGHT (TOPS products)
    //See point 3.3.14 at page 3.46 of the manual
    } else if(poGDS->nDataTypeCode == 32){
        unsigned char nVal;
        for (i=0;i<nBlockXSize;i++){
            nVal =  *(pszRecord+i*nDataLength) ;
            if (nVal == 255)
                ((float *) pImage)[i] = -9999.0f;
            else if (nVal == 0)
                ((float *) pImage)[i] = -1.0f;
            else
                ((float *) pImage)[i] = ((float) nVal - 1) / 10.0f;
        }
    //VEL (Velocity 1-Byte in PPI & others)
    //See point 3.3.37 at page 3.53 of the manual
    } else if(poGDS->nDataTypeCode == 3){
          float fVal;
        for (i=0;i<nBlockXSize;i++){
            fVal = (float) *(pszRecord+i*nDataLength);
            if (fVal == 0.0f)
                fVal = -9997.0f;
            else if(fVal == 1.0f)
                fVal = -9998.0f;
            else if(fVal == 255.0f)
                fVal = -9999.0f;
            else
                fVal = poGDS->fNyquistVelocity * (fVal - 128.0f)/127.0f;
            ((float *) pImage)[i] = fVal;
        }
    //SHEAR (1-Byte Shear)
    //See point 3.3.23 at page 3.39 of the manual
    } else if(poGDS->nDataTypeCode == 35){
        float fVal;
        for (i=0;i<nBlockXSize;i++){
            fVal = (float) *(pszRecord+i*nDataLength);
            if (fVal == 0.0f)
                fVal = -9998.0f;
            else if (fVal == 255.0f)
                fVal = -9999.0f;
            else
                fVal = (fVal - 128.0f) * 0.2f;
            ((float *) pImage)[i] = fVal;
        }

    }

    return CE_None;
}

/************************************************************************/
/*                           SetNoDataValue()                           */
/************************************************************************/

CPLErr IRISRasterBand::SetNoDataValue( double dfNoData )

{
    IRISDataset *poGDS = (IRISDataset *) poDS;
   // if( poGDS->bNoDataSet && poGDS->dfNoDataValue == dfNoData )
   //   return CE_None;


    poGDS->bNoDataSet = TRUE;
    poGDS->dfNoDataValue = dfNoData;

    return CE_None;
}

/************************************************************************/
/*                           GetNoDataValue()                           */
/************************************************************************/

double IRISRasterBand::GetNoDataValue( int * pbSuccess )

{
    IRISDataset *poGDS = (IRISDataset *) poDS;


    if( poGDS->bNoDataSet )
    {
        if( pbSuccess )
            *pbSuccess = TRUE;

        return poGDS->dfNoDataValue;
    }

    return GDALPamRasterBand::GetNoDataValue( pbSuccess );
}


/************************************************************************/
/* ==================================================================== */
/*                              IRISDataset                             */
/* ==================================================================== */
/************************************************************************/

/************************************************************************/
/*                            IRISDataset()                             */
/************************************************************************/

IRISDataset::IRISDataset()
    : fp(NULL), bNoDataSet(0), dfNoDataValue(0.0), nProductCode(0),
      nDataTypeCode(0), nProjectionCode(0), fNyquistVelocity(0.0),
      pszSRS_WKT(NULL), bHasLoadedProjection(FALSE)
{
    adfGeoTransform[0] = 0.0;
    adfGeoTransform[1] = 1.0;
    adfGeoTransform[2] = 0.0;
    adfGeoTransform[3] = 0.0;
    adfGeoTransform[4] = 0.0;
    adfGeoTransform[5] = 1.0;
}

/************************************************************************/
/*                           ~IRISDataset()                             */
/************************************************************************/

IRISDataset::~IRISDataset()

{
    FlushCache();
    if( fp != NULL )
        VSIFCloseL( fp );
    CPLFree( pszSRS_WKT );
}

/************************************************************************/
/*           Calculates the projection and Geotransform                 */
/************************************************************************/
void IRISDataset::LoadProjection()
{
    bHasLoadedProjection = TRUE;
    float fEquatorialRadius = float( (CPL_LSBUINT32PTR (abyHeader+220+320+12)))/100; //They give it in cm
    float fInvFlattening = float( (CPL_LSBUINT32PTR (abyHeader+224+320+12)))/1000000; //Point 3.2.27 pag 3-15
    float fFlattening;
    float fPolarRadius;

    if(fEquatorialRadius == 0){ // if Radius is 0, change to 6371000 Point 3.2.27 pag 3-15 (old IRIS versions)
        fEquatorialRadius = 6371000;
        fPolarRadius = fEquatorialRadius;
        fInvFlattening = 0;
        fFlattening = 0;
    } else {
        if (fInvFlattening == 0){ //When inverse flattening is infinite, they use 0
            fFlattening = 0;
            fPolarRadius = fEquatorialRadius;
        } else {
            fFlattening = 1/fInvFlattening;
            fPolarRadius = fEquatorialRadius * (1-fFlattening);
        }
    }

    float fCenterLon = 360 * float((CPL_LSBUINT32PTR (abyHeader+112+320+12))) / 4294967295LL;
    float fCenterLat = 360 * float((CPL_LSBUINT32PTR (abyHeader+108+320+12))) / 4294967295LL;

    float fProjRefLon = 360 * float((CPL_LSBUINT32PTR (abyHeader+244+320+12))) / 4294967295LL;
    float fProjRefLat = 360 * float((CPL_LSBUINT32PTR (abyHeader+240+320+12))) / 4294967295LL;

    float fRadarLocX, fRadarLocY, fScaleX, fScaleY;

    fRadarLocX = float (CPL_LSBSINT32PTR (abyHeader + 112 + 12 )) / 1000;
    fRadarLocY = float (CPL_LSBSINT32PTR (abyHeader + 116 + 12 )) / 1000;

    fScaleX = float (CPL_LSBSINT32PTR (abyHeader + 88 + 12 )) / 100;
    fScaleY = float (CPL_LSBSINT32PTR (abyHeader + 92 + 12 )) / 100;

    OGRSpatialReference oSRSOut;

    ////MERCATOR PROJECTION
    if(EQUAL(aszProjections[nProjectionCode],"Mercator")){
        OGRCoordinateTransformation *poTransform = NULL;
        OGRSpatialReference oSRSLatLon;

        oSRSOut.SetGeogCS("unnamed ellipse",
                        "unknown",
                        "unnamed",
                        fEquatorialRadius, fInvFlattening,
                        "Greenwich", 0.0,
                        "degree", 0.0174532925199433);

        oSRSOut.SetMercator(fProjRefLat,fProjRefLon,1,0,0);
	oSRSOut.exportToWkt(&pszSRS_WKT);

        //The center coordinates are given in LatLon on the defined ellipsoid. Necessary to calculate geotransform.

        oSRSLatLon.SetGeogCS("unnamed ellipse",
                        "unknown",
                        "unnamed",
                        fEquatorialRadius, fInvFlattening,
                        "Greenwich", 0.0,
                        "degree", 0.0174532925199433);

        poTransform = OGRCreateCoordinateTransformation( &oSRSLatLon,
                                                  &oSRSOut );
        std::pair <double,double> oPositionX2 = GeodesicCalculation(fCenterLat, fCenterLon, 90, fScaleX, fEquatorialRadius, fPolarRadius, fFlattening);
        std::pair <double,double> oPositionY2 = GeodesicCalculation(fCenterLat, fCenterLon, 0, fScaleY, fEquatorialRadius, fPolarRadius, fFlattening);

        double dfLon2, dfLat2;
        dfLon2 = oPositionX2.first;
        dfLat2 = oPositionY2.second;
        double dfX, dfY, dfX2, dfY2;
        dfX = fCenterLon ;
        dfY = fCenterLat ;
        dfX2 = dfLon2;
        dfY2 = dfLat2;

        if( poTransform == NULL || !poTransform->Transform( 1, &dfX, &dfY ) )
             CPLError( CE_Failure, CPLE_None, "Transformation Failed\n" );

        if( poTransform == NULL || !poTransform->Transform( 1, &dfX2, &dfY2 ) )
             CPLError( CE_Failure, CPLE_None, "Transformation Failed\n" );

        adfGeoTransform[0] = dfX - (fRadarLocX * (dfX2 - dfX));
        adfGeoTransform[1] = dfX2 - dfX;
        adfGeoTransform[2] = 0.0;
        adfGeoTransform[3] = dfY + (fRadarLocY * (dfY2 - dfY));
        adfGeoTransform[4] = 0.0;
        adfGeoTransform[5] = -1*(dfY2 - dfY);

        delete poTransform;

    }else if(EQUAL(aszProjections[nProjectionCode],"Azimutal equidistant")){

        oSRSOut.SetGeogCS("unnamed ellipse",
                        "unknown",
                        "unnamed",
                        fEquatorialRadius, fInvFlattening,
                        "Greenwich", 0.0,
                        "degree", 0.0174532925199433);
        oSRSOut.SetAE(fProjRefLat,fProjRefLon,0,0);
        oSRSOut.exportToWkt(&pszSRS_WKT) ;
        adfGeoTransform[0] = -1*(fRadarLocX*fScaleX);
        adfGeoTransform[1] = fScaleX;
        adfGeoTransform[2] = 0.0;
        adfGeoTransform[3] = fRadarLocY*fScaleY;
        adfGeoTransform[4] = 0.0;
        adfGeoTransform[5] = -1*fScaleY;
    //When the projection is different from Mercator or Azimutal equidistant, we set a standard geotransform
    } else {
        adfGeoTransform[0] = -1*(fRadarLocX*fScaleX);
        adfGeoTransform[1] = fScaleX;
        adfGeoTransform[2] = 0.0;
        adfGeoTransform[3] = fRadarLocY*fScaleY;
        adfGeoTransform[4] = 0.0;
        adfGeoTransform[5] = -1*fScaleY;
    }
}

/******************************************************************************/
/* The geotransform in Mercator projection must be calculated transforming    */
/* distance to degrees over the ellipsoid, using Vincenty's formula.          */
/* The following method is ported from a version for Javascript by Chris      */
/* Veness distributed under a CC-BY 3.0 licence, whose conditions is that the */
/* following copyright notice is retained as well as the link to :            */
/* http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html         */
/******************************************************************************/

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */
/* Vincenty Direct Solution of Geodesics on the Ellipsoid (c) Chris Veness 2005-2012              */
/*                                                                                                */
/* from: Vincenty direct formula - T Vincenty, "Direct and Inverse Solutions of Geodesics on the  */
/*       Ellipsoid with application of nested equations", Survey Review, vol XXII no 176, 1975    */
/*       http://www.ngs.noaa.gov/PUBS_LIB/inverse.pdf                                             */
/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  */

std::pair <double,double> IRISDataset::GeodesicCalculation(float fLat, float fLon, float fAngle, float fDist, float fEquatorialRadius, float fPolarRadius, float fFlattening)
{
    std::pair <double,double> oOutput;
    double dfAlpha1 = DEG2RAD * fAngle;
    double dfSinAlpha1 = sin(dfAlpha1);
    double dfCosAlpha1 = cos(dfAlpha1);

    double dfTanU1 = (1-fFlattening) * tan(fLat*DEG2RAD);
    double dfCosU1 = 1 / sqrt((1 + dfTanU1*dfTanU1));
    double dfSinU1 = dfTanU1*dfCosU1;

    double dfSigma1 = atan2(dfTanU1, dfCosAlpha1);
    double dfSinAlpha = dfCosU1 * dfSinAlpha1;
    double dfCosSqAlpha = 1 - dfSinAlpha*dfSinAlpha;
    double dfUSq = dfCosSqAlpha * (fEquatorialRadius*fEquatorialRadius - fPolarRadius*fPolarRadius) / (fPolarRadius*fPolarRadius);
    double dfA = 1 + dfUSq/16384*(4096+dfUSq*(-768+dfUSq*(320-175*dfUSq)));
    double dfB = dfUSq/1024 * (256+dfUSq*(-128+dfUSq*(74-47*dfUSq)));

    double dfSigma = fDist / (fPolarRadius*dfA);
    double dfSigmaP = 2*M_PI;

    double dfSinSigma = 0.0;
    double dfCosSigma = 0.0;
    double dfCos2SigmaM = 0.0;
    double dfDeltaSigma;

    while (fabs(dfSigma-dfSigmaP) > 1e-12) {
        dfCos2SigmaM = cos(2*dfSigma1 + dfSigma);
        dfSinSigma = sin(dfSigma);
        dfCosSigma = cos(dfSigma);
        dfDeltaSigma = dfB*dfSinSigma*(dfCos2SigmaM+dfB/4*(dfCosSigma*(-1+2*dfCos2SigmaM*dfCos2SigmaM)-
          dfB/6*dfCos2SigmaM*(-3+4*dfSinSigma*dfSinSigma)*(-3+4*dfCos2SigmaM*dfCos2SigmaM)));
        dfSigmaP = dfSigma;
        dfSigma = fDist / (fPolarRadius*dfA) + dfDeltaSigma;
    }

    double dfTmp = dfSinU1*dfSinSigma - dfCosU1*dfCosSigma*dfCosAlpha1;
    double dfLat2 = atan2(dfSinU1*dfCosSigma + dfCosU1*dfSinSigma*dfCosAlpha1,
      (1-fFlattening)*sqrt(dfSinAlpha*dfSinAlpha + dfTmp*dfTmp));
    double dfLambda = atan2(dfSinSigma*dfSinAlpha1, dfCosU1*dfCosSigma - dfSinU1*dfSinSigma*dfCosAlpha1);
    double dfC = fFlattening/16*dfCosSqAlpha*(4+fFlattening*(4-3*dfCosSqAlpha));
    double dfL = dfLambda - (1-dfC) * fFlattening * dfSinAlpha *
      (dfSigma + dfC*dfSinSigma*(dfCos2SigmaM+dfC*dfCosSigma*(-1+2*dfCos2SigmaM*dfCos2SigmaM)));
    double dfLon2 = fLon*DEG2RAD+dfL;
    if (dfLon2 > M_PI)
        dfLon2 = dfLon2 - 2*M_PI;
    if (dfLon2 < -1*M_PI)
        dfLon2 = dfLon2 + 2*M_PI;
    oOutput.first = dfLon2*RAD2DEG;
    oOutput.second = dfLat2*RAD2DEG;

    return oOutput;
}

/************************************************************************/
/*                          GetGeoTransform()                           */
/************************************************************************/

CPLErr IRISDataset::GetGeoTransform( double * padfTransform )

{
    if (!bHasLoadedProjection)
        LoadProjection();
    memcpy( padfTransform, adfGeoTransform, sizeof(double)*6 );
    return CE_None;
}

/************************************************************************/
/*                          GetProjectionRef()                          */
/************************************************************************/

const char *IRISDataset::GetProjectionRef(){
    if (!bHasLoadedProjection)
        LoadProjection();
    return pszSRS_WKT;
}

/************************************************************************/
/*                              Identify()                              */
/************************************************************************/

int IRISDataset::Identify( GDALOpenInfo * poOpenInfo )

{

/* -------------------------------------------------------------------- */
/*      Confirm that the file is an IRIS file                           */
/* -------------------------------------------------------------------- */
    // TODO: Translate to English.
    //Si no el posem, peta al fer el translate, quan s'obre Identify des de GDALIdentifyDriver
    if( poOpenInfo->nHeaderBytes < 640 )
        return FALSE;


    short nId1 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader);
    short nId2 = CPL_LSBSINT16PTR(poOpenInfo->pabyHeader+12);
    unsigned short nType = CPL_LSBUINT16PTR (poOpenInfo->pabyHeader+24);

    /*Check if the two headers are 27 (product hdr) & 26 (product configuration), and the product type is in the range 1 -> 34*/
    if( !(nId1 == 27 && nId2 == 26 && nType > 0 && nType < 35) )
        return FALSE;

    return TRUE;
}

/************************************************************************/
/*                             FillString()                             */
/************************************************************************/

static void FillString(char* szBuffer, size_t nBufferSize, void* pSrcBuffer)
{
    for(size_t i = 0; i < nBufferSize - 1; i++)
        szBuffer[i] = ((char*)pSrcBuffer)[i];
    szBuffer[nBufferSize-1] = '\0';
}

/************************************************************************/
/*                                Open()                                */
/************************************************************************/

GDALDataset *IRISDataset::Open( GDALOpenInfo * poOpenInfo )

{
    if (!Identify(poOpenInfo))
        return NULL;
/* -------------------------------------------------------------------- */
/*      Confirm the requested access is supported.                      */
/* -------------------------------------------------------------------- */
    if( poOpenInfo->eAccess == GA_Update )
    {
        CPLError( CE_Failure, CPLE_NotSupported,
                  "The IRIS driver does not support update access to existing"
                  " datasets.\n" );
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Create a corresponding GDALDataset.                             */
/* -------------------------------------------------------------------- */
    IRISDataset *poDS = new IRISDataset();

    poDS->fp = VSIFOpenL( poOpenInfo->pszFilename, "rb" );
    if (poDS->fp == NULL)
    {
        delete poDS;
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Read the header.                                                */
/* -------------------------------------------------------------------- */
    VSIFReadL( poDS->abyHeader, 1, 640, poDS->fp );
    int nXSize = CPL_LSBSINT32PTR(poDS->abyHeader+100+12);
    int nYSize = CPL_LSBSINT32PTR(poDS->abyHeader+104+12);
    int nNumBands = CPL_LSBSINT32PTR(poDS->abyHeader+108+12);

    poDS->nRasterXSize = nXSize;

    poDS->nRasterYSize = nYSize;
    if  (poDS->nRasterXSize <= 0 || poDS->nRasterYSize <= 0 )
    {
        CPLError( CE_Failure, CPLE_AppDefined,
                  "Invalid dimensions : %d x %d",
                  poDS->nRasterXSize, poDS->nRasterYSize);
        delete poDS;
        return NULL;
    }

    if( !GDALCheckBandCount(nNumBands, TRUE) )
    {
        delete poDS;
        return NULL;
    }

/* -------------------------------------------------------------------- */
/*      Setting the Metadata                                            */
/* -------------------------------------------------------------------- */
    //See point 3.2.26 at page 3.12 of the manual
    poDS->nProductCode = CPL_LSBUINT16PTR (poDS->abyHeader+12+12);
    poDS->SetMetadataItem( "PRODUCT_ID", CPLString().Printf("%d", poDS->nProductCode ));
    if( poDS->nProductCode >= ARRAY_ELEMENT_COUNT(poDS->aszProductNames) )
    {
        delete poDS;
        return NULL;
    }

    poDS->SetMetadataItem( "PRODUCT",poDS->aszProductNames[poDS->nProductCode]);

    poDS->nDataTypeCode = CPL_LSBUINT16PTR (poDS->abyHeader+130+12);
    if( poDS->nDataTypeCode >= ARRAY_ELEMENT_COUNT(poDS->aszDataTypeCodes) )
    {
        delete poDS;
        return NULL;
    }
    poDS->SetMetadataItem( "DATA_TYPE_CODE",poDS->aszDataTypeCodes[poDS->nDataTypeCode]);

    if( poDS->nDataTypeCode >= ARRAY_ELEMENT_COUNT(poDS->aszDataTypes) )
    {
        delete poDS;
        return NULL;
    }
    poDS->SetMetadataItem( "DATA_TYPE",poDS->aszDataTypes[poDS->nDataTypeCode]);

    unsigned short nDataTypeInputCode = CPL_LSBUINT16PTR (poDS->abyHeader+144+12);
    if( nDataTypeInputCode >= ARRAY_ELEMENT_COUNT(poDS->aszDataTypeCodes) )
    {
        delete poDS;
        return NULL;
    }
    poDS->SetMetadataItem( "DATA_TYPE_INPUT_CODE",poDS->aszDataTypeCodes[nDataTypeInputCode]);

    unsigned short nDataTypeInput = CPL_LSBUINT16PTR (poDS->abyHeader+144+12);
    if( nDataTypeInput >= ARRAY_ELEMENT_COUNT(poDS->aszDataTypes) )
    {
        delete poDS;
        return NULL;
    }
    poDS->SetMetadataItem( "DATA_TYPE_INPUT",poDS->aszDataTypes[nDataTypeInput]);

    poDS->nProjectionCode = * (unsigned char *) (poDS->abyHeader+146+12);
    if( poDS->nProjectionCode >= ARRAY_ELEMENT_COUNT(poDS->aszProjections) )
    {
        delete poDS;
        return NULL;
    }

    ////TIMES
    int nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader+20+12);

    int nHour =  (nSeconds - (nSeconds%3600)) /3600;
    int nMinute = ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600)%60)/ 60;
    int nSecond = nSeconds - nHour * 3600 - nMinute * 60;

    short nYear = CPL_LSBSINT16PTR(poDS->abyHeader+26+12);
    short nMonth = CPL_LSBSINT16PTR(poDS->abyHeader+28+12);
    short nDay = CPL_LSBSINT16PTR(poDS->abyHeader+30+12);

    poDS->SetMetadataItem( "TIME_PRODUCT_GENERATED", CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d", nYear, nMonth, nDay, nHour, nMinute, nSecond ) );

    nSeconds = CPL_LSBSINT32PTR(poDS->abyHeader+32+12);

    nHour =  (nSeconds - (nSeconds%3600)) /3600;
    nMinute = ((nSeconds - nHour * 3600) - (nSeconds - nHour * 3600)%60)/ 60;
    nSecond = nSeconds - nHour * 3600 - nMinute * 60;

    nYear = CPL_LSBSINT16PTR(poDS->abyHeader+26+12);
    nMonth = CPL_LSBSINT16PTR(poDS->abyHeader+28+12);
    nDay = CPL_LSBSINT16PTR(poDS->abyHeader+30+12);

    poDS->SetMetadataItem( "TIME_INPUT_INGEST_SWEEP", CPLString().Printf("%d-%02d-%02d %02d:%02d:%02d", nYear, nMonth, nDay, nHour, nMinute, nSecond ) );

    ///Site and task information

    char szSiteName[17] = ""; //Must have one extra char for string end!
    char szVersionName[9] = "";

    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader+320+12);
    FillString(szVersionName, sizeof(szVersionName), poDS->abyHeader+16+320+12);
    poDS->SetMetadataItem( "PRODUCT_SITE_NAME",szSiteName);
    poDS->SetMetadataItem( "PRODUCT_SITE_IRIS_VERSION",szVersionName);

    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader+90+320+12);
    FillString(szVersionName, sizeof(szVersionName), poDS->abyHeader+24+320+12);
    poDS->SetMetadataItem( "INGEST_SITE_NAME",szSiteName);
    poDS->SetMetadataItem( "INGEST_SITE_IRIS_VERSION",szVersionName);

    FillString(szSiteName, sizeof(szSiteName), poDS->abyHeader+74+320+12);
    poDS->SetMetadataItem( "INGEST_HARDWARE_NAME",szSiteName);

    char szConfigFile[13] = "";
    FillString(szConfigFile, sizeof(szConfigFile), poDS->abyHeader+62+12);
    poDS->SetMetadataItem( "PRODUCT_CONFIGURATION_NAME",szConfigFile);

    char szTaskName[13] = "";
    FillString(szTaskName, sizeof(szTaskName), poDS->abyHeader+74+12);
    poDS->SetMetadataItem( "TASK_NAME",szTaskName);

    const short nRadarHeight = CPL_LSBSINT16PTR(poDS->abyHeader+284+320+12);
    poDS->SetMetadataItem( "RADAR_HEIGHT",CPLString().Printf("%d m",nRadarHeight));
    const short nGroundHeight = CPL_LSBSINT16PTR(poDS->abyHeader+118+320+12);
    poDS->SetMetadataItem( "GROUND_HEIGHT",CPLString().Printf("%d m",nRadarHeight-nGroundHeight)); //Ground height over the sea level

    unsigned short nFlags = CPL_LSBUINT16PTR (poDS->abyHeader+86+12);
    //Get eleventh bit
    nFlags=nFlags<<4;
    nFlags=nFlags>>15;
    if (nFlags == 1){
        poDS->SetMetadataItem( "COMPOSITED_PRODUCT","YES");
        unsigned int compositedMask = CPL_LSBUINT32PTR (poDS->abyHeader+232+320+12);
        poDS->SetMetadataItem( "COMPOSITED_PRODUCT_MASK",CPLString().Printf("0x%08x",compositedMask));
    } else{
        poDS->SetMetadataItem( "COMPOSITED_PRODUCT","NO");
    }

    //Wave values
    poDS->SetMetadataItem( "PRF",CPLString().Printf("%d Hz",CPL_LSBSINT32PTR(poDS->abyHeader+120+320+12)));
    poDS->SetMetadataItem( "WAVELENGTH",CPLString().Printf("%4.2f cm",(float) CPL_LSBSINT32PTR(poDS->abyHeader+148+320+12)/100));
    const unsigned short nPolarizationType = CPL_LSBUINT16PTR (poDS->abyHeader+172+320+12);
    float fNyquist = (CPL_LSBSINT32PTR(poDS->abyHeader+120+320+12))*((float) CPL_LSBSINT32PTR(poDS->abyHeader+148+320+12)/10000)/4; //See section 3.3.37 & 3.2.54
    if (nPolarizationType == 1)
        fNyquist = fNyquist * 2;
    else if(nPolarizationType == 2)
        fNyquist = fNyquist * 3;
    else if(nPolarizationType == 3)
        fNyquist = fNyquist * 4;
    poDS->fNyquistVelocity = fNyquist;
    poDS->SetMetadataItem( "NYQUIST_VELOCITY",CPLString().Printf("%.2f m/s",fNyquist));

    // Product dependent metadata (stored in 80 bytes from 162 bytes
    // at the product header) See point 3.2.30 at page 3.19 of the
    // manual.
    //See point 3.2.25 at page 3.12 of the manual.
    if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"PPI")){
        //Degrees = 360 * (Binary Angle)*2^N
        //float fElevation = 360 * float((CPL_LSBUINT16PTR (poDS->abyHeader+164+12))) / 65536;
        float fElevation = 360 * float((CPL_LSBSINT16PTR (poDS->abyHeader+164+12))) / 65536;

        poDS->SetMetadataItem( "PPI_ELEVATION_ANGLE",CPLString().Printf("%f",fElevation));
        if (EQUAL(poDS->aszDataTypeCodes[poDS->nDataTypeCode],"dBZ"))
            poDS->SetMetadataItem( "DATA_TYPE_UNITS","dBZ");
        else
            poDS->SetMetadataItem( "DATA_TYPE_UNITS","m/s");
        //See point 3.2.2 at page 3.2 of the manual
    } else if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"CAPPI")){
        const float fElevation = ((float) CPL_LSBSINT32PTR(poDS->abyHeader+4+164+12))/100;
        poDS->SetMetadataItem( "CAPPI_BOTTOM_HEIGHT",CPLString().Printf("%.1f m",fElevation));
        const float fAzimuthSmoothingForShear = 360 * float((CPL_LSBUINT16PTR (poDS->abyHeader+10+164+12))) / 65536;
        poDS->SetMetadataItem( "AZIMUTH_SMOOTHING_FOR_SHEAR" ,CPLString().Printf("%.1f", fAzimuthSmoothingForShear));
        const unsigned int  nMaxAgeVVPCorrection = CPL_LSBUINT32PTR (poDS->abyHeader+24+164+12);
        poDS->SetMetadataItem( "MAX_AGE_FOR_SHEAR_VVP_CORRECTION" ,CPLString().Printf("%d s", nMaxAgeVVPCorrection));
        if (EQUAL(poDS->aszDataTypeCodes[poDS->nDataTypeCode],"dBZ"))
            poDS->SetMetadataItem( "DATA_TYPE_UNITS","dBZ");
        else
            poDS->SetMetadataItem( "DATA_TYPE_UNITS","m/s");
        //See point 3.2.32 at page 3.19 of the manual
    } else if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"RAIN1") || EQUAL(poDS->aszProductNames[poDS->nProductCode],"RAINN")){
        const short nNumProducts = CPL_LSBSINT16PTR(poDS->abyHeader+170+320+12);
        poDS->SetMetadataItem( "NUM_FILES_USED",CPLString().Printf("%d",nNumProducts));

        const float fMinZAcum= (float)((CPL_LSBUINT32PTR (poDS->abyHeader+164+12))-32768)/1000;
        poDS->SetMetadataItem( "MINIMUM_Z_TO_ACUMULATE",CPLString().Printf("%f",fMinZAcum));

        const unsigned short nSecondsOfAccumulation = CPL_LSBUINT16PTR (poDS->abyHeader+6+164+12);
        poDS->SetMetadataItem( "SECONDS_OF_ACCUMULATION",CPLString().Printf("%d s",nSecondsOfAccumulation));

        const unsigned int nSpanInputFiles = CPL_LSBUINT32PTR (poDS->abyHeader+24+164+12);
        poDS->SetMetadataItem( "SPAN_OF_INPUT_FILES",CPLString().Printf("%d s",nSpanInputFiles));
        poDS->SetMetadataItem( "DATA_TYPE_UNITS","mm");

        char szInputProductName[13] = "";
        for(int k=0; k<12;k++)
            szInputProductName[k] = * (char *) (poDS->abyHeader+k+12+164+12);
        poDS->SetMetadataItem( "INPUT_PRODUCT_NAME",CPLString().Printf("%s",szInputProductName));

        if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"RAINN"))
             poDS->SetMetadataItem( "NUM_HOURS_ACCUMULATE",CPLString().Printf("%d",CPL_LSBUINT16PTR (poDS->abyHeader+10+164+12)));

    //See point 3.2.73 at page 3.36 of the manual
    } else if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"VIL")){
        const float fBottomHeightInterval = (float) CPL_LSBSINT32PTR(poDS->abyHeader+4+164+12) / 100;
        // TYPO in metadata key: FIXME ?
        poDS->SetMetadataItem( "BOTTOM_OF_HEIGTH_INTERVAL",CPLString().Printf("%.1f m",fBottomHeightInterval));
        const float fTopHeightInterval = (float) CPL_LSBSINT32PTR(poDS->abyHeader+8+164+12) / 100;
        // TYPO in metadata key: FIXME ?
        poDS->SetMetadataItem( "TOP_OF_HEIGTH_INTERVAL",CPLString().Printf("%.1f m",fTopHeightInterval));
        poDS->SetMetadataItem( "VIL_DENSITY_NOT_AVAILABLE_VALUE","-1");
        poDS->SetMetadataItem( "DATA_TYPE_UNITS","mm");
    //See point 3.2.68 at page 3.36 of the manual
    } else if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"TOPS")){
        const float fZThreshold = (float) CPL_LSBSINT16PTR(poDS->abyHeader+4+164+12) / 16;
        poDS->SetMetadataItem( "Z_THRESHOLD",CPLString().Printf("%.1f dBZ",fZThreshold));
        poDS->SetMetadataItem( "ECHO_TOPS_NOT_AVAILABLE_VALUE","-1");
        poDS->SetMetadataItem( "DATA_TYPE_UNITS","km");
    //See point 3.2.20 at page 3.10 of the manual
    } else if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"MAX")){
        const float fBottomInterval = (float) CPL_LSBSINT32PTR(poDS->abyHeader+4+164+12) / 100;
        poDS->SetMetadataItem( "BOTTOM_OF_INTERVAL",CPLString().Printf("%.1f m",fBottomInterval));
        const float fTopInterval = (float) CPL_LSBSINT32PTR(poDS->abyHeader+8+164+12) / 100;
        poDS->SetMetadataItem( "TOP_OF_INTERVAL",CPLString().Printf("%.1f m",fTopInterval));
        const int nNumPixelsSidePanels = CPL_LSBSINT32PTR(poDS->abyHeader+12+164+12);
        poDS->SetMetadataItem( "NUM_PIXELS_SIDE_PANELS",CPLString().Printf("%d",nNumPixelsSidePanels));
        const short nHorizontalSmootherSidePanels = CPL_LSBSINT16PTR(poDS->abyHeader+16+164+12);
        poDS->SetMetadataItem( "HORIZONTAL_SMOOTHER_SIDE_PANELS",CPLString().Printf("%d",nHorizontalSmootherSidePanels));
        const short nVerticalSmootherSidePanels = CPL_LSBSINT16PTR(poDS->abyHeader+18+164+12);
        poDS->SetMetadataItem( "VERTICAL_SMOOTHER_SIDE_PANELS",CPLString().Printf("%d",nVerticalSmootherSidePanels));
    }

/* -------------------------------------------------------------------- */
/*      Create band information objects.                                */
/* -------------------------------------------------------------------- */
    /* coverity[tainted_data] */
    for (int iBandNum = 1; iBandNum <= nNumBands; iBandNum++) {
        poDS->SetBand( iBandNum, new IRISRasterBand( poDS, iBandNum ));

        poDS->GetRasterBand(iBandNum)->SetNoDataValue(-9999);
        //Calculating the band height to include it in the band metadata. Only for the CAPPI product
        if (EQUAL(poDS->aszProductNames[poDS->nProductCode],"CAPPI")){
            const float fScaleZ = float (CPL_LSBSINT32PTR (poDS->abyHeader + 96 + 12 )) / 100;
            const float fOffset = ((float) CPL_LSBSINT32PTR(poDS->abyHeader+4+164+12))/100;

            poDS->GetRasterBand(iBandNum)->SetMetadataItem("height",CPLString().Printf("%.0f m",fOffset + fScaleZ*(iBandNum-1)));
        }
    }
/* -------------------------------------------------------------------- */
/*      Initialize any PAM information.                                 */
/* -------------------------------------------------------------------- */
    poDS->SetDescription( poOpenInfo->pszFilename );
    poDS->TryLoadXML();

/* -------------------------------------------------------------------- */
/*      Check for overviews.                                            */
/* -------------------------------------------------------------------- */
    poDS->oOvManager.Initialize( poDS, poOpenInfo->pszFilename );

    return( poDS );
}

/************************************************************************/
/*                          GDALRegister_IRIS()                         */
/************************************************************************/

void GDALRegister_IRIS()

{
    if( GDALGetDriverByName( "IRIS" ) != NULL )
        return;

    GDALDriver *poDriver = new GDALDriver();

    poDriver->SetDescription( "IRIS" );
    poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
    poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
                               "IRIS data (.PPI, .CAPPi etc)" );
    poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC, "frmt_various.html#IRIS" );
    poDriver->SetMetadataItem( GDAL_DMD_EXTENSION, "ppi" );
    poDriver->SetMetadataItem( GDAL_DCAP_VIRTUALIO, "YES" );

    poDriver->pfnOpen = IRISDataset::Open;
    poDriver->pfnIdentify = IRISDataset::Identify;

    GetGDALDriverManager()->RegisterDriver( poDriver );
}
