/***************************** LICENSE START ***********************************

 Copyright 2012 ECMWF and INPE. This software is distributed under the terms
 of the Apache License version 2.0. In applying this license, ECMWF does not
 waive the privileges and immunities granted to it by virtue of its status as
 an Intergovernmental Organization or submit itself to any jurisdiction.

 ***************************** LICENSE END *************************************/

// MvGrid.cc,    vk  Aug-98

#include "inc_iostream.h"
#include <math.h>
#include <ctype.h>
#include "MvGrid.h"
#include "MvException.h"
#include "proj_braz.hpp"

// define this if you want to use a version of MvLatLonGrid::interpolatePoint()
// which uses built_in grib_api functions to get the nearest 4 points.
// Note that the grib_api version is probably slower (not tested), and it
// chooses different points when the point to be interpolated lies on
// a grid line. Still, it's interesting for comparison.

#undef USE_GRIB_API_FOR_INTERPOLATE


GaussianLatitudes MvGaussianGridBase::gLatitudes_;  // global variable for the class


const long   cFirstLatInd       =  3; //--  4 in Fortran
const long   cScanModeInd       = 10; //-- 11 in Fortran (CHECK if new classes added!!!)

const double cFullGlobeLimit    = 359.9;
const double cGridEpsilon       = 5e-5;    //-- 0.05 millidegrees
const double cToRadians         = M_PI/180.0;
const double cToDegrees         = 180.0/M_PI;

const double cEarthRadius       = 6371200.0;   //-- MARS code
//const double cEarthRadius       = 6367470.0;   //-- WMO, for GRIB headers
//const double cEarthRadius       = 6371221.3;   //-- NCAR Fortran routines


//-- temporary function:
void grib_api_port_missing( const char* msg )
{
   cout << ">>>>\n"
        << ">>> GRIB_API port not done for: \n"
        << ">> " << msg << "\n"
        << ">  " << endl;
   marslog(LOG_INFO,"grib_api missing: %s",msg);
}

//-- temporary function:
void nontested_grib_api_port( const char* msg )
{
   marslog(LOG_INFO,"not-fully-tested grib_api mod: %s",msg);
}


//===================================================================
//
// MvGridBase:
// ----------

MvGridBase::MvGridBase( field* myfield, field_state oldState, bool memoryToBeReleased )
 : field_( myfield )
 , vertCoord_( 0 )
 , vertCoordPairCount_( 0 )
 , gridType_( "NA" )
 , horisLines_( 0 )
 , horisPoints_( 0 )
 , horisPointCount_( 0 )
 , currentIndex_( 0 )
 , currentLaty_( 0.0 )
 , currentLonx_( 0.0 )
 , dx_( 0 )
 , oldState_( oldState )
 , memoryToBeReleased_( memoryToBeReleased )
{
   field_->refcnt++;


   // Internally, libMars deletes a field's request after decrementing its request's
   // reference. But the requests' references are not incremented by most
   // procedures, but they are when copying a field to another because a copy
   // is not made, just a pointer to the same request. If a copy has been made, then
   // the ref counter for the request is now '1'. If both fields which use the same
   // request are destroyed, then the first one sets the ref count to '0' and
   // deletes the request memory. The second sets the ref count to '1' and tries
   // to delete the request memory again! Therefore, we should increment the ref
   // count here. As far as the current MARS code is concerned, there is no
   // difference between a ref count of '0' and '1' - both are decremented
   // and then tested: "if(g->refcnt <= 0)" - both pass this test. So this
   // modification only affects the case when a copy of a field has been made,
   // causing the ref count to be 2.

   if (field_->r !=0) field_->r->refcnt++;
}
//------------------------------------------------------

MvGridBase::~MvGridBase()
{
  if( field_ && memoryToBeReleased_ )
    {
//      if( memoryToBeReleased_ )
//	{
//	  release_field( field_ );
//	}

      bool restore_field_state = (field_->refcnt > 1);  // see comment below, and note that free_field will decrement field_->refcnt
      free_field( field_ );


      // it's possible that this will not have actually freed the field, for instance
      // if the underlying field_ is used in another MvGrid. We check the reference counter
      // because if it is <=0, then libMars/free_field() will have released the memory,
      // but otherwise it will not have. In this case, we should reset the shape
      // of the field to what it was before we instantiated this MvGrid.

      if(restore_field_state)
        set_field_state( field_, oldState_ );
    }

  if( vertCoord_ )
     delete [] vertCoord_;

  //  cout << "MvGridBase::~MvGridBase(): currentIndex_ = " << currentIndex_ << endl;
}
//------------------------------------------------------
long
MvGridBase::getLong( const char* name, bool throwOnError ) const
{
   long val = -999999999;
   int  err = grib_get_long( field_->handle, name, &val );
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_get_long: error getting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       if( throwOnError )
            throw MvException( s.c_str() );
     }

   return val;
}
//------------------------------------------------------
long
MvGridBase::getLongArray( const char* name, long **lvals, bool throwOnError ) const
{
   size_t numVals = 0;

   int err1 = grib_get_size(field_->handle, name, &numVals);
   int err2 = 1;

   if (!err1 && numVals != 0)
   {
        *lvals = (long *) malloc (numVals * sizeof(long));
        err2 = grib_get_long_array (field_->handle, name, *lvals,  &numVals);
   }

   if( err1 || err2 )
   {
       string ge = grib_get_error_message (err1 ? err1 : err2);
       string s("grib_get_long_array: error getting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       if( throwOnError )
            throw MvException( s.c_str() );
   }

   return numVals;
}
//------------------------------------------------------
bool
MvGridBase::setLong( const char* name, long val )
{
   int  err = grib_set_long( field_->handle, name, val );
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_set_long: error setting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       //throw MvException( s.c_str() );
       return false;
     }

   return true;
}
//------------------------------------------------------
double
MvGridBase::getDouble( const char* name, bool throwOnError ) const
{
   double val = DBL_MAX;
   int    err = grib_get_double( field_->handle, name, &val );
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_get_double: error getting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       if( throwOnError )
            throw MvException( s.c_str() );
     }

   return val;
}
//------------------------------------------------------
long
MvGridBase::getDoubleArray( const char* name, double **dvals, bool throwOnError ) const
{
   size_t numVals = 0;

   int err1 = grib_get_size(field_->handle, name, &numVals);
   int err2 = 1;

   if (!err1 && numVals != 0)
   {
        *dvals = (double *) malloc (numVals * sizeof(double));
        err2 = grib_get_double_array (field_->handle, name, *dvals,  &numVals);
   }

   if( err1 || err2 )
   {
       string ge = grib_get_error_message (err1 ? err1 : err2);
       string s("grib_get_double_array: error getting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       if( throwOnError )
            throw MvException( s.c_str() );
   }

   return numVals;
}
//------------------------------------------------------
bool
MvGridBase::setDouble( const char* name, double val )
{
   int  err = grib_set_double( field_->handle, name, val );
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_set_double: error setting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       //throw MvException( s.c_str() );
       return false;
     }

   return true;
}
//------------------------------------------------------
bool
MvGridBase::setString( const char* name, string &val )
{
   size_t len = val.size();
   int  err = grib_set_string( field_->handle, name, val.c_str(), &len);
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_set_string: error setting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       //throw MvException( s.c_str() );
       return false;
     }

   return true;
}
//------------------------------------------------------string
string
MvGridBase::getString( const char* name, bool throwOnError ) const
{
   string val("NA");

   const size_t cMaxBuf = 99;
   char strbuf[ cMaxBuf+1 ];
   size_t slen = cMaxBuf;

   int  err = grib_get_string( field_->handle, name, strbuf, &slen);
   if( err )
     {
       string ge = grib_get_error_message (err);
       string s("grib_get_string: error getting ");
       s = s + name + " (" + ge + ")";
       marslog( LOG_WARN, "%s", s.c_str() );
       cerr << ">>>>>-------------------------------------------------\n"
            << ">>>>\n"
            << ">>>  " << s <<"\n"
	    << ">>\n"
	    << ">" << endl;
       if( throwOnError )
            throw MvException( s.c_str() );
     }
   else
     val = strbuf;

   return val;
}
//------------------------------------------------------
long
MvGridBase::vertCoordCoefPairCount()
{
   if( vertCoordPairCount_ == 0 )
      vertCoordPairCount_ = getLong("numberOfVerticalCoordinateValues") / 2;

   return vertCoordPairCount_;
}
//------------------------------------------------------
bool
MvGridBase::vertCoordCoefs( double& C1, double& C2, int level)
{
   C1 = C2 = 0;

   int nvccp = (int)vertCoordCoefPairCount();
   if( level > nvccp )
    {
      marslog( LOG_EROR
             , "MvGridBase::vertCoordCoefs: not enough coefficients in GRIB header for level %d"
             , level );
      return false;
    }

   if( ! vertCoord_ )                        //-- get vertical coordinate coefficients
    {
      size_t nvc_st = (size_t)(nvccp*2);
      vertCoord_ = new double[nvc_st];
      int   err  = grib_get_double_array( field_->handle, "pv", vertCoord_, &nvc_st );
      if( err )
       {
         marslog(LOG_EROR, "MvGridBase::vertCoordCoefs: unable to access coefficients");
         return false;
       }
    }

   C1 = vertCoord_[ level ];
   C2 = vertCoord_[ level + nvccp ];

   return true;
}

//------------------------------------------------------
#if 0
bool
MvGridBase::getValues()
{
   if( values_ )                              //-- got them already
      return true;

   if( ! field_ )                              //-- not possible!
      return false;

   size_t  values_length = 0;
   grib_get_size( field_->handle, "values", &values_length );
   values_ = new double[ values_length*sizeof(double) ];

   int ret = grib_get_double_array( field_->handle, "values", values_, &values_length );

   if( ret != GRIB_SUCCESS )
     {
        marslog(LOG_EROR, "MvGridBase::getValues(): grib_get_double_array() failed!");
        return false;
     }

   return true;
}
#endif

//------------------------------------------------------
double
MvGridBase::valueAt( int i ) const
{
    if (field_)
    {
        // if the field is not currently expanded in memory, then use grib_api's
        // function to retrieve the value we want

        if (field_->shape != expand_mem)
        {
            double val;

            if (grib_get_double_element(field_->handle, "values", i, &val) == 0)
                return val;
            else
                return mars.grib_missing_value;
        }
        else
        {
            // just index into our array of pre-loaded values

            return field_->values[i];
        }
    }
    else
        return mars.grib_missing_value;
}


//------------------------------------------------------
double
MvGridBase::operator[](int index)
{
    return valueAt(index);
}

//------------------------------------------------------

bool MvGridBase::isEqual( const MvGridBase* mygrid ) const
{
   if( ! ( field_ && mygrid->field_ ) )
      return false;              //-- must contain fields

   if( gridType_ != mygrid->gridType_ )
      return false;              //-- must have same representation

   if( length() != mygrid->length() )
      return false;              //-- must have same size

   //-- WARNING:
   //-- cScanModeInd is the same for all so far implemented grid types (011025/vk)
   //--
   long scanModI1 = getLong( "iScansNegatively" );
   long scanModJ1 = getLong( "jScansPositively" );
   long scanModI2 = mygrid->getLong( "iScansNegatively" );
   long scanModJ2 = mygrid->getLong( "jScansPositively" );
   if ((scanModI1 != scanModI2) || (scanModJ1 != scanModJ2))
      return false;              //-- must have same scanning mode

   return true;
}
//------------------------------------------------------

MvGridPoint MvGridBase::nearestGridpoint( double lat_y, double lon_x )
{
  //--
  //--  This is a generic non-optimised method!
  //--  Derived classes should provide optimised solutions.
  //--

   if( ! field_ )
      return cMissingPoint;	//-- must be valid message

   MvLocation myPoint( lat_y, lon_x );

   init();
   MvGridPoint myCurrentPoint = gridPoint();
   MvGridPoint myClosestPoint = myCurrentPoint;
   double      myClosestDistance = myCurrentPoint.loc_.distanceInMeters( myPoint );

   for( int p=1; p<length(); ++p )
   {
      advance();
      myCurrentPoint = gridPoint();
      double myDistance = myCurrentPoint.loc_.distanceInMeters( myPoint );
      if( myDistance < myClosestDistance )
      {
         myClosestPoint = myCurrentPoint;
         myClosestDistance = myDistance;
      }
   }

/*
   cout << "INFO: "
        << MvLocation(lat_y,lon_x)
        << ": nearest grid point at "
	<< myClosestPoint.loc_
	<< ", distance "
	<< ( (int)(myClosestDistance/100.0) / 10.0 )  //-- xx.x km
	<< " km"
	<< endl;
*/
   return myClosestPoint;
}
//------------------------------------------------------

double MvGridBase::interpolatePoint( double lat_y, double lon_x )
{
  //--
  //--  This is a generic Quick&Dirty approximation!
  //--  Derived classes should provide accurate solutions.
  //--

  marslog( LOG_INFO, "Nearest grid point used (no interpolation for this grid type)" );
  return nearestGridpoint( lat_y, lon_x ).value_;
}
//------------------------------------------------------

bool MvGridBase::getMatrixNN( double, double, MvMatrix& )
{
  marslog( LOG_INFO, "Function GetMatrixNN not implemented for this grid type" );

  return false;
}
//------------------------------------------------------

void MvGridBase::init()
{
   //-- to access values (via MARS interface) msg needs to be expanded
   // Aug 2008 - no, MvGrid is no longer just for accessing grid values
   // - don't *assume* that we want the values at all now.
   //ir set_field_state( field_, expand_mem );

   currentIndex_ = 0;
   currentLaty_  = firstLatY();
   currentLonx_  = firstLonX();
}
//------------------------------------------------------

bool
MvGridBase::ScanModeCheck( double first, double last, double step ) const
{
  if( first > last && step < 0 )
    return true;
  if( first < last && step > 0 )
    return true;

  marslog( LOG_EROR
	 , "Ambiguous grid definition: first=%g, last=%g, step=%g"
	 , first
	 , last
	 , step
	 );
  return false;
}
//------------------------------------------------------

double MvGridBase::weight() const
{
   return cos( lat_y() * cToRadians ) / horisPoints_;
}
//------------------------------------------------------
//---  averageCalc - calculate averages within given area,
//--   either NS or EW directions, into a vector with given
//-    grid interval
//
vector<double>
MvGridBase::averageCalc(bool isEW, double N, double W, double S, double E, double grid)
{
  const double cEps = 0.01;

  if( ! field_ )
    throw MvException("averageCalc: GRIB problems");

  if( grid <= 0 )
    {
      grid = 1;
      marslog(LOG_INFO, "average_xx: grid interval negative or zero, set to 1");
    }

  double dif        = ( E - W ) - 360.0; //-- check if E/W boarders coincide
  bool   isGlobalEW = dif < cEps && dif > -cEps;

  double halfGrid = grid/2;
  double N2 = N + halfGrid;
  double W2 = W - halfGrid;
  double S2 = S - halfGrid + cEps/4; //-- full half grid may introduce index overflows!
  double E2 = E + halfGrid - cEps/4;

  MvGeoBox box( N2, W2, S2, E2 );
  if( isGlobalEW )
    {
      box.set( N2, W, S2, E );  //-- if global then do not extend EW boarders
      marslog( LOG_DBUG, "averageComp: area is global W->E!" );
    }

  double geoDif   = isEW ? (N-S) : (E-W);
  int    listSize = (int)( geoDif / grid ) + 1;

  vector<double>  sum( listSize, 0.0 );
  vector<double>  wgt( listSize, 0.0 );

  for( int p=0; p < length(); ++p )
    {
      if( box.isInside( lat_y(), lon_x() ) )
	{
	  //-- index: first distance from the edge (minus halfGrid), in degrees
	  double dist;
	  if( isEW )
	      dist = N2 - lat_y();
	  else
	    {
	      double lonX = lon_x();
	      if( lonX > E2 )
		dist = lonX-360.0 - W2;
	      else
		dist = lonX - W2;
	    }

	  //-- index 'ip': convert distance into output-grid intervals
	  int ip = (int)( dist / grid );

	  //-- Do we need latitude weighting for average? if not then use "#if 1"
	  //-- Questions about weighting:
	  //--    - What about points over Poles?
	  //--          => With current weighting these are totally ignored!
	  //--    - What if gridpoints are in Polar Stereographic grid?
	  //--          => Should we use opposite type of weighting?
#if 0
	  sum[ip] += value();
	  wgt[ip] += 1.0;
#else
	  if (hasValue())  // only count valid values
	  {
	    sum[ip] += weight()*value();
	    wgt[ip] += weight();
	  }
#endif
	}
      advance();
    }

  if( ! isEW )         //-- in North-South average, check if W->E global
    {
      if( isGlobalEW ) //-- index calculation may split points at edges
	{
	  sum[0] += sum[listSize-1];
	  wgt[0] += wgt[listSize-1];

	  sum[listSize-1] = sum[0];
	  wgt[listSize-1] = wgt[0];
	}
    }

  for( int iv=0; iv < listSize; ++iv )
    {
      sum[iv] = wgt[iv] ? sum[iv]/wgt[iv] : missingValue();
    }

  return sum;
}

//===================================================================
//
// MvUnimplementedGrid:
// -------------------

MvUnimplementedGrid::MvUnimplementedGrid( field* myfield, field_state oldState )
  : MvGridBase( myfield, oldState, false )
{
  //field_ = 0;
  currentLaty_ = cMvlatLonMissingValue;
  currentLonx_ = cMvlatLonMissingValue;
}
//------------------------------------------------------

void MvUnimplementedGrid::init()
{
   MvGridBase::init();
}
//------------------------------------------------------

bool MvUnimplementedGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   return true;
}
//------------------------------------------------------

vector<double>
MvUnimplementedGrid::averageCalc(bool, double, double, double, double, double)
{
   throw MvException("averageCalc: not implemented for this type of grid!");
}

//===================================================================
//
// MvLatLonGrid:
// ------------

MvLatLonGrid::MvLatLonGrid( field* myfield
			  , field_state oldState
			  , bool memoryToBeReleased
			  , bool isRotated )
 : MvGridBase( myfield, oldState, memoryToBeReleased )
{
   gridType_ = getString( "typeOfGrid" );

   if( ! ( isRotated || gridType_ == cLatLonGrid ) ) //-- check grid type
     {
       marslog( LOG_EROR, "MvLatLonGrid: GRIB data not latlon!" );
       field_ = 0;
       return;
     }

   long grid_ew         = getLong("iDirectionIncrementInDegrees");
   long points_parallel = getLong("numberOfPointsAlongAParallel");
   if( ! ( grid_ew || points_parallel ) )
     {                                              //-- thinned grid needs to be implemented
       field_ = 0;
       marslog( LOG_EROR, "MvLatLonGrid: thinned latlon grid not yet supported!" );
       return;
     }

   long jPointsConsecutive = getLong("jPointsAreConsecutive"); //-- adjacent points in j dir are consequtive
   long iNegative          = getLong("iScansNegatively");      //-- points scan in -i dir
   if(jPointsConsecutive || iNegative)                 
     {
       marslog( LOG_EROR, "LatLon grid jPointsConsecutive and iScansNegatively scanning mode not supported");
       field_ = 0;
       return;
     }

   horisLines_  = getLong("numberOfPointsAlongAMeridian");
   horisPoints_ = getLong("numberOfPointsAlongAParallel");

   firstLonX_ = DBL_MAX;
   firstLatY_ = DBL_MAX;
   lastLonX_  = DBL_MAX;
   lastLatY_  = DBL_MAX;

   currentLaty_ = firstLatY();
   currentLonx_ = firstLonX();
                                                    //--- check/assign dx_

   double lon1  = firstLonX();
   double lon9  = lastLonX();

   long incrementsGiven = getLong("iDirectionIncrementGiven");
   if(!incrementsGiven)      //-- increments not given
     {
	     dx_ = (horisPoints_ == 1) ? 1 : (lon9-lon1) / (horisPoints_-1.0);
     }
   else                                             //-- increments given => check
     {
       dx_          = getDouble("iDirectionIncrementInDegrees"); // sec2_->grid_ew / cGridScaling;
       double dx2   = (horisPoints_ == 1) ? 1 : (lon9-lon1)/(horisPoints_-1.0);
       double dxErr = fabs(dx_-dx2);
       if( dxErr > cGridEpsilon && dxErr < 1.0 )
	 {
	   marslog( LOG_INFO
		  , "Using computed lon grid interval %g (instead of %g)"
		  , dx2
		  , dx_
		  );
	   dx_ = dx2;
	 }
     }
                                                     //--- check/assign dy_

   double lat1 = firstLatY();
   double lat9 = lastLatY();

   if(!incrementsGiven)       //-- increments not given
     {
	     dy_ =  (horisLines_ == 1) ? 1 : (MAX(lat9,lat1) - MIN(lat9,lat1)) / (horisLines_-1.0);
     }
   else                                              //-- increments given => check
     {
       dy_          = getDouble("jDirectionIncrementInDegrees"); // sec2_->grid_ns / cGridScaling;
       double dy2   = (horisLines_ == 1) ? 1 : (MAX(lat9,lat1) - MIN(lat9,lat1))/(horisLines_-1.0);
       double dyErr = fabs(dy_-dy2);
       if( dyErr > cGridEpsilon && dyErr < 1.0 )
	 {
	   marslog( LOG_INFO
		  , "Using computed lat grid interval %g (instead of %g)"
		  , dy2
		  , dy_
		  );
	   dy_ = dy2;
	 }
     }


   long jPositive = getLong("jScansPositively");      //-- points scan in +j dir
   if(!jPositive )     //-- points scan in -j dir
     {
         dy_ = -dy_;
     }
   if( ! ScanModeCheck( lat1, lat9, dy_ ) )
     {
       dy_ = -dy_;
       marslog( LOG_INFO
	      , "Ignoring jScansPositively flag (%d), setting j step to %g"
	      , (int)jPositive
	      , dy_
	      );
     }
}
//------------------------------------------------------

double MvLatLonGrid::firstLonX()
{
   if (! field_)
      return 0;			 //-- must be valid message

   if (firstLonX_ == DBL_MAX)  // value not cached?
   {
      double x1 = getDouble("longitudeOfFirstGridPointInDegrees"); // sec2_->limit_west / cGridScaling;
      double x9 = lastLonX();

      if (x1 > x9)
         x1 -= 360.0;

      firstLonX_ = x1;
   }

   return firstLonX_;
}
//------------------------------------------------------

double MvLatLonGrid::firstLatY()
{
   if( ! field_ )
      return DBL_MAX;		//-- must be valid message


   if (firstLatY_ == DBL_MAX)  // value not cached?
   {
      firstLatY_ = getDouble("latitudeOfFirstGridPointInDegrees");
   }

   return firstLatY_;
}

//------------------------------------------------------

double MvLatLonGrid::lastLonX()
{
   if (! field_)
      return 0;			 //-- must be valid message

   if (lastLonX_ == DBL_MAX)  // value not cached?
   {
      lastLonX_ = getDouble("longitudeOfLastGridPointInDegrees");
   }

   return lastLonX_;
}
//------------------------------------------------------

double MvLatLonGrid::lastLatY()
{
   if( ! field_ )
      return DBL_MAX;		//-- must be valid message


   if (lastLatY_ == DBL_MAX)  // value not cached?
   {
      lastLatY_ = getDouble("latitudeOfLastGridPointInDegrees");
   }

   return lastLatY_;
}
//------------------------------------------------------

bool MvLatLonGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   if( ++horisPointCount_ >= horisPoints_ )
     {
       currentLaty_ += dy_;
       currentLonx_  = firstLonX();
       horisPointCount_ = 0;
     }
   else
     {
       currentLonx_ += dx_;
     }

   return true;
}

//------------------------------------------------------

MvGridPoint MvLatLonGrid::nearestGridpoint( double lat_y, double lon_x )
{

    if (!field_)
    {
        return cMissingPoint;
    }


/*
    // grib_api version - no real need for this one, as it's probably overkill
    // for a lat/long grid.

    int is_lsm = 0;
    int err;
    double nearest_lon_x = 0.0, nearest_lat_y = 0.0, nearest_val = 0.0;
    double nearest_distance = 0.0;
    int nearest_index = 0;



    err = grib_nearest_find_multiple (field_->handle, is_lsm, &lat_y, &lon_x, 1,
          &nearest_lat_y, &nearest_lon_x, &nearest_val, &nearest_distance, &nearest_index);

    if (!err)
    {
        //cout << "grib_api_nearest: " << nearest_lon_x << ", " << nearest_lat_y << ": " << nearest_val;
        return MvGridPoint(nearest_val, nearest_lat_y, nearest_lon_x);
    }

    else
    {
        marslog(LOG_EROR,"MvLatLonGrid::nearestGridpoint grib_nearest_find_multiple() returned %d", err);
        return cMissingPoint;
    }
*/

//--
//-- NOTE: prerequisite: horizontal scanning i.e. does not
//--       function properly with vertical scanning (j faster)!
//--

   int    nlat = horisLines_;    //-- size of grid
   int    nlon = horisPoints_; 

   double lat1 = firstLatY();             //-- starting corner
   double lon1 = firstLonX();
   double lat9 = lat1 + (nlat-1) * dy_;   //-- ending corner
   double lon9 = lon1 + (nlon-1) * dx_;

   double west = lon1 < lon9 ? lon1 : lon9; //-- scanning E->W or W->E ?
   double east = lon1 < lon9 ? lon9 : lon1;
   double eastpoint = east;  // backup of this in case we change 'east'

   MvGeoBox myArea( lat1, west , lat9, east );

   if( lon9 - lon1 + dx_ > 359.9 )         //-- extend for globe
   {
      myArea.set( lat1, 0, lat9, 360 );
      west = 0;
      east = 360;
   }

   if( ! myArea.isInside( lat_y, lon_x ) )
   {
      return cMissingPoint; //-- outside grid area !
   }

   while( lon_x < west )                   //-- adjust longitude inside grid
      lon_x += 360.0;
   while( lon_x > eastpoint + (dx_/2.0) ) //-- half a gridpoint over the eastmost point
      lon_x -= 360.0;

   int j = int( rint( (lat_y - lat1) / dy_ ) ); //-- nearest gridpoint
   int i = int( rint( (lon_x - lon1) / dx_ ) );
                                          //-- prerequisite: horizontal scanning !

   long index = nlon*j + i;
   return MvGridPoint( valueAt( index )
                     , (lat1+j*dy_)
                     , (lon1+i*dx_)
                     , index );
}
//------------------------------------------------------

double MvLatLonGrid::interpolatePoint( double lat_y, double lon_x )
{
    const double cERRVAL = DBL_MAX;


   if( ! field_ )
    {
        return cERRVAL;
    }


    // get GRIB field parameters

   double lat1 = firstLatY();
   double lon1 = firstLonX();
   double lat9 = lastLatY();;
   double lon9 = lastLonX();



   while( lon1 < 0 )      //-- force longitudes >= 0
   {
      lon1 += 360.;
      lon9 += 360.;
   }

   while( lon_x < lon1 )
    {
      lon_x += 360.;      //-- now point is either inside or above!
    }


    // check that lats are: lat1=southern, lat9=northern

    if( lat1 > lat9 )
   {
      double tmp = lat1; lat1 = lat9; lat9 = tmp;
   }


    //-- check if the point is outside grid

   char  globe = horisPoints_*dx_ > cFullGlobeLimit ? 'y' : 'n';

   if( globe == 'n' && ( lon_x < lon1 || lon_x > lon9 ) )
      return cERRVAL;

   if( lat_y < lat1 || lat_y > lat9 )
      return cERRVAL;




#if defined (USE_GRIB_API_FOR_INTERPOLATE)

    int err;
    unsigned long flags = 0;
    double nearest_lats      [4];
    double nearest_lons      [4];
    double nearest_vals      [4];
    double nearest_distances [4];
    int    nearest_indexes   [4];
    size_t num_values [5] = {4, 4, 4, 4, 4};

    grib_nearest *gn = grib_nearest_new (field_->handle, &err);

    if (!err)
    {
        // use grib_api's function to get the nearest 4 points

        err = grib_nearest_find (gn, field_->handle, lat_y, lon_x,
                                 flags, nearest_lats, nearest_lons,
                                 nearest_vals, nearest_distances, nearest_indexes, num_values);
        if (!err)
        {
            // do not interpolate if one of the value is invalid

            if ( nearest_vals[0] == mars.grib_missing_value ||
                 nearest_vals[1] == mars.grib_missing_value ||
                 nearest_vals[2] == mars.grib_missing_value ||
                 nearest_vals[3] == mars.grib_missing_value  )
            {
                return cERRVAL;
            }


            // interpolate

            double total_distance = nearest_distances[0] + nearest_distances[1] +
                                    nearest_distances[2] + nearest_distances[3];

            double weights[4];
            double interpolated_value;

            weights[0] = nearest_distances[0] / total_distance;
            weights[1] = nearest_distances[1] / total_distance;
            weights[2] = nearest_distances[2] / total_distance;
            weights[3] = nearest_distances[3] / total_distance;

            interpolated_value = nearest_vals[0] * weights[0] +
                                 nearest_vals[1] * weights[1] +
                                 nearest_vals[2] * weights[2] +
                                 nearest_vals[3] * weights[3];


            cout << "grib_api interpolating point: "  << "(" << lat_y << "," << lon_x << ")" << endl;
            cout << "  nearest: (" << nearest_lats[0] << "," << nearest_lons[0] << ") "
                 <<            "(" << nearest_lats[1] << "," << nearest_lons[1] << ") "
                 <<            "(" << nearest_lats[2] << "," << nearest_lons[2] << ") "
                 <<            "(" << nearest_lats[3] << "," << nearest_lons[3] << ")" << endl;
            cout << "  indexes: (" << nearest_indexes[0] << "," << nearest_indexes[1] << ","
                                   << nearest_indexes[2] << "," << nearest_indexes[3] << ")" << endl;
            cout << "  values: (" << nearest_vals[0] << "," << nearest_vals[1] << ","
                                  << nearest_vals[2] << "," << nearest_vals[3] << ")" << endl;
            cout << "  grib_api interpolated: " << interpolated_value << endl;

            return interpolated_value;


        }

        else
        {
            marslog(LOG_EROR,"MvLatLonGrid::interpolatePoint grib_nearest_find() returned %d", err);
            grib_nearest_delete (gn);
            return cERRVAL;
        }

        grib_nearest_delete (gn);
    }
    else
    {
        marslog(LOG_EROR,"MvLatLonGrid::interpolatePoint grib_nearest_new() returned %d", err);
        return cERRVAL;
    }

#else

    // use our own routine to find the 4 nearest grid points

   int  ix1 = int((lon_x-lon1)/dx_); //-- index for column on the east
   int  ix2 = ix1 + 1;              //-- index for column on the west

   if( ix2 > (horisPoints_-1) )
   {
      if( globe == 'y' )
         ix2 = 0;                   //-- wrap around globe
      else
         ix2 = ix1;                 //-- possible if lon==lon9 (east boundary)
   }


    // start either from north or from south depending on scanning mode --

   int  iy1 = int( ( lat_y - ( dy_ < 0 ? lat9 : lat1 ) ) / dy_ );
   int  iy2 = iy1 + 1;

   if( iy2 > (horisLines_-1) )
      iy2 = iy1;                    //-- possible at "higher" boarder


    // get and check grid point values

   double  grid_11 = valueAt(horisPoints_*iy1+ix1);
   double  grid_12 = valueAt(horisPoints_*iy1+ix2);
   double  grid_21 = valueAt(horisPoints_*iy2+ix1);
   double  grid_22 = valueAt(horisPoints_*iy2+ix2);

   if( grid_11 == mars.grib_missing_value ||
       grid_12 == mars.grib_missing_value ||
       grid_21 == mars.grib_missing_value ||
       grid_22 == mars.grib_missing_value    )
   {
      return DBL_MAX;
   }


    // interpolate

   double  lon_ix1 = lon1 + ix1*dx_;
   double  lat_iy1 = ( dy_ < 0 ? lat9 : lat1 ) + iy1*dy_;

   double w1   = (lon_x - lon_ix1)/dx_;
   double w2   = 1. - w1;
   double val1 = w2*grid_11 + w1*grid_12;
   double val2 = w2*grid_21 + w1*grid_22;

   w1 = ( lat_y - lat_iy1 )/dy_;
   w2 = 1. - w1;

/*
    cout << "interpolating point: "  << "(" << lat_y << "," << lon_x << ")" << endl;
    cout << "  nearest: (" << lat_iy1 << "," << lon_ix1 << ") " << endl;
    cout << "  indexes: (" << horisPoints_*iy1+ix1 << "," << horisPoints_*iy1+ix2 << "," << horisPoints_*iy2+ix1 << "," << horisPoints_*iy2+ix2 << ")" <<  endl;
    cout << "  values: (" << grid_11 << "," << grid_12 << "," << grid_21 << "," << grid_22 << ")" <<  endl;
    cout << "  interpolated: " << val1*w2 + val2*w1 << endl;
*/

   return val1*w2 + val2*w1;

#endif


}

//===================================================================
//
// MvLatLonRotatedGrid:
// -------------------
//  Methods in this class have been tested with HIRLAM rotated fields
//

MvLatLonRotatedGrid::MvLatLonRotatedGrid( field* myfield
					, field_state oldState
					, bool memoryToBeReleased )
 : MvLatLonGrid( myfield, oldState, memoryToBeReleased, true )
{
  southPoleLaty_  = getDouble("latitudeOfSouthernPoleInDegrees"); // sec2_->rotation_lat / cGridScaling;
  southPoleLonx_  = getDouble("longitudeOfSouthernPoleInDegrees");// sec2_->rotation_lon / cGridScaling;

  if( gridType_ != cLatLonRotatedGrid )
    {
      marslog( LOG_EROR, "MvLatLonRotatedGrid: GRIB data not rotated latlon!" );
      field_ = 0;
    }
}

//------------------------------------------------------
// unRotate is based on Fortran routine REGROT (see below)

MvLocation MvLatLonRotatedGrid::unRotate( double lat_y, double lon_x) const
{
  double ZRADI  = 1./cToRadians;
  double ZSYCEN = sin(cToRadians*(southPoleLaty_+90.));
  double ZCYCEN = cos(cToRadians*(southPoleLaty_+90.));

  double ZSXROT = sin(cToRadians*lon_x);
  double ZCXROT = cos(cToRadians*lon_x);
  double ZSYROT = sin(cToRadians*lat_y);
  double ZCYROT = cos(cToRadians*lat_y);
  double ZSYREG = ZCYCEN*ZSYROT + ZSYCEN*ZCYROT*ZCXROT;
  ZSYREG = MAX( MIN(ZSYREG, +1.0), -1.0 );

  double PYREG = asin(ZSYREG)*ZRADI;

  double ZCYREG = cos(PYREG*cToRadians);
  double ZCXMXC = (ZCYCEN*ZCYROT*ZCXROT - ZSYCEN*ZSYROT)/ZCYREG;
  ZCXMXC = MAX( MIN(ZCXMXC, +1.0), -1.0 );
  double ZSXMXC = ZCYROT*ZSXROT/ZCYREG;
  double ZXMXC  = acos(ZCXMXC)*ZRADI;
  if( ZSXMXC < 0.0)
    ZXMXC = -ZXMXC;

  double PXREG = ZXMXC + southPoleLonx_;

  return MvLocation( PYREG, PXREG );
}
//------------------------------------------------------
// rotate is based on Fortran routine REGROT (see below)

MvLocation MvLatLonRotatedGrid::rotate( double lat_y, double lon_x) const
{
  double ZRADI  = 1./cToRadians;
  double ZSYCEN = sin(cToRadians*(southPoleLaty_+90.));
  double ZCYCEN = cos(cToRadians*(southPoleLaty_+90.));

  double ZXMXC  = cToRadians*(lon_x - southPoleLonx_);
  double ZSXMXC = sin(ZXMXC);
  double ZCXMXC = cos(ZXMXC);
  double ZSYREG = sin(cToRadians*lat_y);
  double ZCYREG = cos(cToRadians*lat_y);
  double ZSYROT = ZCYCEN*ZSYREG - ZSYCEN*ZCYREG*ZCXMXC;
  ZSYROT = MAX( MIN(ZSYROT, +1.0), -1.0 );

  double PYROT  = asin(ZSYROT)*ZRADI;

  double ZCYROT = cos(PYROT*cToRadians);
  double ZCXROT = (ZCYCEN*ZCYREG*ZCXMXC + ZSYCEN*ZSYREG)/ZCYROT;
  ZCXROT = MAX( MIN(ZCXROT, +1.0), -1.0 );
  double ZSXROT = ZCYREG*ZSXMXC/ZCYROT;

  double PXROT = acos(ZCXROT)*ZRADI;

  if( ZSXROT < 0.0)
    PXROT = -PXROT;

  return MvLocation( PYROT, PXROT );
}

//- - - - - - - - - - - - - - - - - - - - - - - - - - - -
//
// Methods 'unRotate' and 'rotate' are based on this
// Fortran routine that comes from Hirlam (thanks to
// Laura Rontu, FMI).
//                                       (010817/vk)

#if 0
c Library:util $RCSfile: REGROT.f,v $, $Revision: 1.1 $
c checked in by $Author: GCats $ at $Date: 1996/09/06 14:36:57 $
c $State: Stable $, $Locker:  $
c $Log: REGROT.f,v $
c Revision 1.1  1996/09/06 14:36:57  GCats
c Created from util.apl, HIRLAM version 2.5.6, by Gerard Cats
c
C<A NAME="header">
      SUBROUTINE REGROT(PXREG,PYREG,PXROT,PYROT,KXDIM,KYDIM,KX,KY,
     +                  PXCEN,PYCEN,KCALL)
C
      IMPLICIT NONE
C
C - - - - - - - - - - - - - - - - - - - - - - - - - - - -
C
C*    CONVERSION BETWEEN REGULAR AND ROTATED SPHERICAL COORDINATES.
C*
C*    PXREG     LONGITUDES OF THE REGULAR COORDINATES
C*    PYREG     LATITUDES OF THE REGULAR COORDINATES
C*    PXROT     LONGITUDES OF THE ROTATED COORDINATES
C*    PYROT     LATITUDES OF THE ROTATED COORDINATES
C*              ALL COORDINATES GIVEN IN DEGREES N (NEGATIVE FOR S)
C*              AND DEGREES E (NEGATIVE VALUES FOR W)
C*    KXDIM     DIMENSION OF THE GRIDPOINT FIELDS IN THE X-DIRECTION
C*    KYDIM     DIMENSION OF THE GRIDPOINT FIELDS IN THE Y-DIRECTION
C*    KX        NUMBER OF GRIDPOINT IN THE X-DIRECTION
C*    KY        NUMBER OF GRIDPOINTS IN THE Y-DIRECTION
C*    PXCEN     REGULAR LONGITUDE OF THE SOUTH POLE OF THE ROTATED GRID
C*    PYCEN     REGULAR LATITUDE OF THE SOUTH POLE OF THE ROTATED GRID
C*
C*    KCALL=-1: FIND REGULAR AS FUNCTIONS OF ROTATED COORDINATES.
C*    KCALL= 1: FIND ROTATED AS FUNCTIONS OF REGULAR COORDINATES.
C*
C*    J.E. HAUGEN   HIRLAM   JUNE -92
C
C - - - - - - - - - - - - - - - - - - - - - - - - - - - -
C
      INTEGER KXDIM,KYDIM,KX,KY,KCALL
      REAL PXREG(KXDIM,KYDIM),PYREG(KXDIM,KYDIM),
     +     PXROT(KXDIM,KYDIM),PYROT(KXDIM,KYDIM),
     +     PXCEN,PYCEN
C
C - - - - - - - - - - - - - - - - - - - - - - - - - - - -
C
      REAL PI,ZRAD,ZSYCEN,ZCYCEN,ZXMXC,ZSXMXC,ZCXMXC,ZSYREG,ZCYREG,
     X     ZSYROT,ZCYROT,ZCXROT,ZSXROT,ZRADI
      INTEGER JY,JX
C
C - - - - - - - - - - - - - - - - - - - - - - - - - - - -
C
      PI = 4.*ATAN(1.)
      ZRAD = PI/180.
      ZRADI = 1./ZRAD
      ZSYCEN = SIN(ZRAD*(PYCEN+90.))
      ZCYCEN = COS(ZRAD*(PYCEN+90.))
C
      IF (KCALL.EQ.1) THEN
C
      DO JY = 1,KY
      DO JX = 1,KX
C
      ZXMXC  = ZRAD*(PXREG(JX,JY) - PXCEN)
      ZSXMXC = SIN(ZXMXC)
      ZCXMXC = COS(ZXMXC)
      ZSYREG = SIN(ZRAD*PYREG(JX,JY))
      ZCYREG = COS(ZRAD*PYREG(JX,JY))
      ZSYROT = ZCYCEN*ZSYREG - ZSYCEN*ZCYREG*ZCXMXC
      ZSYROT = MAX(ZSYROT,-1.0)
      ZSYROT = MIN(ZSYROT,+1.0)
C
      PYROT(JX,JY) = ASIN(ZSYROT)*ZRADI
C
      ZCYROT = COS(PYROT(JX,JY)*ZRAD)
      ZCXROT = (ZCYCEN*ZCYREG*ZCXMXC +
     +          ZSYCEN*ZSYREG)/ZCYROT
      ZCXROT = MAX(ZCXROT,-1.0)
      ZCXROT = MIN(ZCXROT,+1.0)
      ZSXROT = ZCYREG*ZSXMXC/ZCYROT
C
      PXROT(JX,JY) = ACOS(ZCXROT)*ZRADI
C
      IF (ZSXROT.LT.0.0) PXROT(JX,JY) = -PXROT(JX,JY)
C
      ENDDO
      ENDDO
C
      ELSEIF (KCALL.EQ.-1) THEN
C
      DO JY = 1,KY
      DO JX = 1,KX
C
      ZSXROT = SIN(ZRAD*PXROT(JX,JY))
      ZCXROT = COS(ZRAD*PXROT(JX,JY))
      ZSYROT = SIN(ZRAD*PYROT(JX,JY))
      ZCYROT = COS(ZRAD*PYROT(JX,JY))
      ZSYREG = ZCYCEN*ZSYROT + ZSYCEN*ZCYROT*ZCXROT
      ZSYREG = MAX(ZSYREG,-1.0)
      ZSYREG = MIN(ZSYREG,+1.0)
C
      PYREG(JX,JY) = ASIN(ZSYREG)*ZRADI
C
      ZCYREG = COS(PYREG(JX,JY)*ZRAD)
      ZCXMXC = (ZCYCEN*ZCYROT*ZCXROT -
     +          ZSYCEN*ZSYROT)/ZCYREG
      ZCXMXC = MAX(ZCXMXC,-1.0)
      ZCXMXC = MIN(ZCXMXC,+1.0)
      ZSXMXC = ZCYROT*ZSXROT/ZCYREG
      ZXMXC  = ACOS(ZCXMXC)*ZRADI
      IF (ZSXMXC.LT.0.0) ZXMXC = -ZXMXC
C
      PXREG(JX,JY) = ZXMXC + PXCEN
C
      ENDDO
      ENDDO
C
      ELSE
      WRITE(6,'(1X,''INVALID KCALL IN REGROT'')')
      STOP
      ENDIF
C
      RETURN
      END
#endif

//------------------------------------------------------

MvGridPoint MvLatLonRotatedGrid::nearestGridpoint( double lat_y, double lon_x )
{
  MvLocation rLoc = rotate( lat_y, lon_x );
  MvGridPoint  gp = MvLatLonGrid::nearestGridpoint( rLoc.latitude(), rLoc.longitude() );

  //-- this one for debug checking, dbLoc should be equal to input loc --
  MvLocation dbLoc = unRotate( gp.loc_.latitude(), gp.loc_.longitude( ) );

  return MvGridPoint( gp.value_, dbLoc.latitude(), dbLoc.longitude(), gp.index_ );
}
//------------------------------------------------------

double MvLatLonRotatedGrid::interpolatePoint( double lat_y, double lon_x )
{
  MvLocation rLoc = rotate( lat_y, lon_x );
  return MvLatLonGrid::interpolatePoint( rLoc.latitude(), rLoc.longitude() );
}


//===================================================================
//
// MvLambertGrid:
// -------------
//	20051101/vk: This Lambert class has not been heavily tested, thus
//		     all feedback from usage with real data is welcome :-)
//
MvLambertGrid::MvLambertGrid( field* myfield
			  , field_state oldState
			  , bool memoryToBeReleased )
 : MvGridBase( myfield, oldState, memoryToBeReleased )
{
#if 0
 const int  cPointsAlongX  =  1;
 const int  cPointsAlongY  =  2;
 const int  cGridFirstLat  =  3;
 const int  cGridFirstLon  =  4;
 const int  cGridVertLon   =  6;
 const int  cGridLenX      =  8;
 const int  cGridLenY      =  9;
 const int  cInterSecLat1  = 13;
 const int  cInterSecLat2  = 14;
#endif

   gridType_ = getString("typeOfGrid");

   if( gridType_ != cLambertGrid )  //-- check grid type
     {
       marslog( LOG_EROR, "MvLambertGrid: GRIB data not of known Lambert!" );
       field_ = 0;
       return;
     }

   long jPointsConsecutive = getLong("jPointsAreConsecutive"); //-- adjacent points in j dir are consequtive
   long iNegative          = getLong("iScansNegatively");      //-- points scan in -i dir
   if (jPointsConsecutive || iNegative)                 
   {
       marslog( LOG_EROR, "Lambert grid jPointsConsecutive or iScansNegatively not supported" );
       return;
   }

   dx_        = getDouble("DxInMetres");
   double dy_ = getDouble("DyInMetres");
   if( dy_ != dx_ )
   {
       marslog( LOG_EROR, "MvLambertGrid: dx!=dy: not implemented!" );
       field_ = 0;
       return;
   }

   earthRadius_ = cEarthRadius;

   gridTanLat_ = getDouble("Latin1InDegrees") * 1000; // sec2_[cInterSecLat1];
   double tanLat2 = getDouble("Latin2InDegrees") * 1000;
   //if( sec2_[cInterSecLat2] != gridTanLat_ ) //-- two latitudes...?
   if( tanLat2 != gridTanLat_ )
   {
       //-- The original Fortran code was made only for a projection defined
       //-- by a single tangenting latitude. This is my personal solution
       //-- to "convert" an intersecting projection (two latitudes given) to
       //-- a tangenting projection...
       //-- (To be honest: all the formulas I found in the web I found
       //--  them to be quite hard to digest, thus this Q&D solution which
       //--  shrinks Earth to touch the Lambert cone only in one latitude ;-)

       //-- set tangenting latitude as the mean of the two latitudes
       //gridTanLat_  = (sec2_[cInterSecLat1] + sec2_[cInterSecLat2]) / 2.0;
       gridTanLat_  = (gridTanLat_ + tanLat2) / 2.0;

       //-- use simple trigonometry to shrink the Earth radius
       //double delta = 0.5 * (sec2_[cInterSecLat1] - sec2_[cInterSecLat2]) / cGridScaling;
       double delta = 0.5 * (gridTanLat_ - tanLat2);
       if( delta < 0 )
           delta = -delta;
       earthRadius_ = cos(delta*cToRadians) * cEarthRadius;

       marslog( LOG_WARN, "MvLambertGrid: two tangenting latitudes - Q&D solution!" );
   }

   earthRadiusPerDx_ = earthRadius_ / dx_;
   cout << "R, dx, R/dx:\t" << earthRadius_ << "\t" << dx_ << "\t" << earthRadiusPerDx_ << endl;

   hemiSphere_ = gridTanLat_ > 0 ? 1 : 0;
   gridTanLat_ = (gridTanLat_ / cGridScaling) * cToRadians;  //--aki cGridScaling--//
   sinLatTan_  = hemiSphere_ * sin(gridTanLat_);
   cosLatTan_  = cos(gridTanLat_);

   gridVertLon_ = getDouble("LoVInDegrees"); // sec2_[cGridVertLon] / cGridScaling;

   horisLines_  = getLong("numberOfPointsAlongYAxis"); //sec2_[cPointsAlongY];
   horisPoints_ = getLong("numberOfPointsAlongXAxis"); // sec2_[cPointsAlongX];

   gridLat1_    = getDouble("latitudeOfFirstGridPointInDegrees"); //sec2_[cGridFirstLat] / cGridScaling;
   gridLon1_    = getDouble("longitudeOfFirstGridPointInDegrees"); // sec2_[cGridFirstLon] / cGridScaling;

//-- based on Fortran code from 'http://maps.fsl.noaa.gov/fslparms/w3fb12.f':
//--
//-- SUBPROGRAM:  W3FB12        LAMBERT(I,J) TO LAT/LON FOR GRIB
//--   PRGMMR: STACKPOLE        ORG: NMC42       DATE:88-11-28

//        MAKE SURE THAT INPUT LONGITUDE DOES NOT PASS THROUGH
//        THE CUT ZONE (FORBIDDEN TERRITORY) OF THE FLAT MAP
//        AS MEASURED FROM THE VERTICAL (REFERENCE) LONGITUDE
//
   double ELON1L = gridLon1_;
   if((gridLon1_-gridVertLon_) > 180.0 )
      ELON1L = gridLon1_ - 360.0;
   if( (gridLon1_-gridVertLon_) < -180.0 )
      ELON1L = gridLon1_ + 360.0;

   double ELONVR = gridVertLon_ * cToRadians;
//
//     RADIUS TO LOWER LEFT HAND (LL) CORNER
//
   double ALA1 = gridLat1_ * cToRadians;
   double RMLL = earthRadiusPerDx_ *
                 pow(cosLatTan_,(1.-sinLatTan_)) * pow((1.+sinLatTan_),sinLatTan_) *
                 pow((cos(ALA1)/(1.+hemiSphere_*sin(ALA1))),sinLatTan_) / sinLatTan_;
//
//     USE LL POINT (LowerLeft) INFO TO LOCATE POLE POINT
//
   double ELO1 = ELON1L * cToRadians;
   double ARG = sinLatTan_ * (ELO1-ELONVR);
   poleI_ = 1. - hemiSphere_ * RMLL * sin(ARG);
   poleJ_ = 1. + RMLL * cos(ARG);
//
//          NOW THE LATITUDE
//          RECALCULATE THE THING ONLY IF MAP IS NEW SINCE LAST TIME
//
   double ANINV  = 1./sinLatTan_;
   sinLatTanInvPer2_ = ANINV/2.;
   theThing_ = pow((sinLatTan_/earthRadiusPerDx_), ANINV) /
               ( pow(cosLatTan_,((1.-sinLatTan_)*ANINV)) * (1.+ sinLatTan_) );

   currentLaty_ = firstLatY();
   currentLonx_ = firstLonX();
}
//------------------------------------------------------

double MvLambertGrid::firstLonX()
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   currentJ_ = 1;
   return gridLon1_;
}
//------------------------------------------------------

double MvLambertGrid::firstLatY()
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   currentI_ = 1;
   return gridLat1_;
}

//------------------------------------------------------

bool MvLambertGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   if( ++currentI_ > horisPoints_ )
     {
       ++currentJ_;
       currentI_ = 1;
     }

   return computeLatLon( currentI_, currentJ_ );
}
//------------------------------------------------------
bool
MvLambertGrid::computeLatLon( int ip, int jp )
{
//-- based on Fortran code from 'http://maps.fsl.noaa.gov/fslparms/w3fb12.f':
//--
//-- SUBPROGRAM:  W3FB12        LAMBERT(I,J) TO LAT/LON FOR GRIB
//--   PRGMMR: STACKPOLE        ORG: NMC42       DATE:88-11-28

    double XX = (double)ip - poleI_;
    double YY = poleJ_ - (double)jp;
    double R2 = XX*XX + YY*YY;
//
//        CHECK THAT THE REQUESTED I,J IS NOT IN THE FORBIDDEN ZONE
//           YY MUST BE POSITIVE UP FOR THIS TEST
//
    double THETA = M_PI*(1.-sinLatTan_);
    double BETA  = atan2(XX,-YY);
    if( BETA < 0 )
       BETA = -BETA;

    if( BETA <= THETA )
    {
        currentLaty_ = 999.9;
        currentLonx_ = 999.9;
        return false;
    }
//
//        NOW THE MAGIC FORMULAE
//
    if( R2 == 0 )
    {
        currentLaty_ = hemiSphere_ * 90.0;
        currentLonx_ = gridVertLon_;
    }
    else
    {
        currentLonx_ = gridVertLon_ + atan2(hemiSphere_*XX,YY)/sinLatTan_ / cToRadians;
        currentLonx_ = fmod(currentLonx_+360.0, 360.0);

	currentLaty_ = hemiSphere_ *
	               ( M_PI/2.0 - 2.0*atan( theThing_ * pow( R2, sinLatTanInvPer2_ ) ) )
		      / cToRadians;
    }

    return true;
}
//------------------------------------------------------

double
MvLambertGrid::weight() const
{
   if( ! field_ )
      return DBL_MAX;			 //-- must be valid message

   return cos( lat_y() * cToRadians );
}
//------------------------------------------------------

//-- To be implemented later (currently these functions will use the default functions in
//-- Base class - they go through the whole field and pick the value of the closest point):

//   virtual MvGridPoint MvLambertGrid::nearestGridpoint( double lat_y, double lon_x )
//   virtual double      MvLambertGrid::interpolatePoint( double lat_y, double lon_x )

//===================================================================


//===================================================================
//
// MvIrregularGrid:
// ------------------
//  Methods in Irregular classes have been tested with ECMWF fields
//  This covers Gaussian grids and reduced lat/long grids
//

MvIrregularGrid::MvIrregularGrid( field* myfield
				      , field_state oldState
				      , bool memoryToBeReleased )
 : MvGridBase( myfield, oldState, memoryToBeReleased )
{
   gridType_ = getString("typeOfGrid");

   if( ! ( gridType_==cGaussianGrid || gridType_==cGaussianReducedGrid || gridType_ == cLatLonReducedGrid) )
   {
      marslog( LOG_EROR, "MvIrregularGrid: GRIB data not Gaussian or reduced lat long!" );
      field_ = 0;
   }

   long jScansPositively = getLong("jScansPositively");
   isSouthToNorthScanning_ = jScansPositively;
   globalNS_ = true;
   globalEW_ = true;

   horisLines_    = getLong("numberOfPointsAlongAMeridian"); // sec2_->points_meridian;

   firstLonX_ = DBL_MAX;
   lastLonX_  = DBL_MAX;
}
//------------------------------------------------------



void MvIrregularGrid::checkAreaLimits()
{
  double firstLon, lastLon;
  double firstLat, lastLat;
  

  firstLon = firstLonX();
  lastLon  = lastLonX();
  firstLat = getDouble("latitudeOfFirstGridPointInDegrees");
  lastLat  = getDouble("latitudeOfLastGridPointInDegrees");



  if( horisLines_ == numGlobalParallels_ )     //-- field is global (N <-> S)
  {
      firstLatIndex_ = 0;
      lastLatIndex_  = horisLines_ - 1;

      // some grids have rows with no points in them - skip past them
      while (pointsInRow(firstLatIndex_) == 0 && firstLatIndex_ < horisLines_)
      {
         firstLatIndex_++;
      }
  }

  else                                 //-- subarea only
  {
     firstLatIndex_ = findLatIndex( firstLat );
     lastLatIndex_  = findLatIndex( lastLat );
     globalNS_ = false;
  }


  // additional check to make sure it's global - we extend the grid by another point and see whether
  // it would end up where the first point is - we use the 'middle' row, since that should extend
  // the furthest
  int numPoints = pointsInRow(horisLines_/2);  // middle line
  double dx = (lastLon - firstLon) / (numPoints - 1);
  double wrapAroundLong = dx * (numPoints);
  double epsilon = dx * 0.9;

  if (wrapAroundLong >= (360.0 - dx)) wrapAroundLong -= 360.0;

  double diff = wrapAroundLong - firstLon;

  // is the wraparound point close to the first point? If so, then it's global; otherwise not global
  if (diff > epsilon || diff < -epsilon)
  {
     globalEW_ = false;
  }


  if( isSouthToNorthScanning_ )
  {
      long    tmp    = firstLatIndex_;
      firstLatIndex_ = lastLatIndex_;
      lastLatIndex_  = tmp;
  }
}
//------------------------------------------------------
// For subareas we need to find limiting latitudes from
// the array containing all Gaussian latitudes

int MvIrregularGrid::findLatIndex( double latitude )
{
    for( int indx=0; indx<numGlobalParallels_; ++indx )
    {
        double diff = latitudes_[ indx ] - latitude;
        diff = diff < 0 ? -diff : diff;

        if( diff < 0.001 )             //-- 0.001 = precision in GRIB header
            return indx;               //-- OK, close enough
    }

    marslog( LOG_EROR
            , "MvIrregularGrid: latitude %f not found in Gaussian N%d"
            , latitude
            , numGlobalParallels_ );

    field_ = 0;        //-- set to 'non-valid'!

    return -1;         //-- not found!
}
//------------------------------------------------------

MvIrregularGrid::~MvIrregularGrid()
{
   delete [] latitudes_;
}
//------------------------------------------------------

void MvIrregularGrid::init()
{
   MvGridBase::init();
   currentLatIndex_ = firstLatIndex_;
}
//------------------------------------------------------

double MvIrregularGrid::firstLonX()
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   if (firstLonX_ == DBL_MAX)  // value not cached?
   {
      double x0 = getDouble("longitudeOfFirstGridPointInDegrees"); // sec2_->limit_west / cGridScaling;

      if( x0 > lastLonX() )
         x0 -= 360.0;

      firstLonX_ = x0;
   }

   return firstLonX_;
}
//------------------------------------------------------

double MvIrregularGrid::lastLonX()
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   if (lastLonX_ == DBL_MAX)  // value not cached?
   {
      lastLonX_ = getDouble("longitudeOfLastGridPointInDegrees");
   }

   return lastLonX_;
}
//------------------------------------------------------

double MvIrregularGrid::firstLatY()
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   //   return sec2_->limit_north / cGridScaling;
   return latitudes_[ firstLatIndex_ ];
}

//------------------------------------------------------

double  MvIrregularGrid::firstLonXInArea(int row)
{
   // default behaviour, unless overridden, is to return the first
   // longitude from the GRIB header
   return firstLonX();
}
//------------------------------------------------------

double  MvIrregularGrid::lastLonXInArea(int row)
{
   // default behaviour, unless overridden, is to return the last
   // longitude from the GRIB header
   return lastLonX();
}
//------------------------------------------------------

MvGridPoint MvIrregularGrid::nearestGridpoint( double lat_y, double lon_x )
{
  if( isSouthToNorthScanning_ )                  //-- not implemented => do it the hard way
    {
      return MvGridBase::nearestGridpoint( lat_y, lon_x );
    }

   //-- create a proper implementation here         (020227/vk)
   //-- seems to work, but needs still more testing (020228/vk)

   if( ! field_ )				 //-- non-valid msg?
     return cMissingPoint;

   if( lat_y > latitudes_[ firstLatIndex_ ] )              //-- before first row?
     return cMissingPoint;
   if( lat_y < latitudes_[ lastLatIndex_ ] )//-- after last row
     return cMissingPoint;

   int row9 = firstLatIndex_;                                 //-- find row S of pt
   double lat9 = latitudes_[ row9 ];
   while( lat9 > lat_y && row9 < firstLatIndex_+horisLines_ )
     {
       lat9 = latitudes_[ ++row9 ];
     }

   bool borderRow = (row9 == firstLatIndex_);
   //-- what about South Polish?

   int    row1 = borderRow ? row9 : row9 - 1;    //-- previous row is N of pt
   double lat1 = latitudes_[ row1 ];
   int pointsInRow1 = pointsInRow(row1);

   // to get the longitude interval, we need to consider the difference
   // between a global grid and a sub-area grid. A global grid has its
   // last point one interval before 360 degrees (because its first point
   // is already at 0 degrees, which is 360). A sub-area does not have this
   // feature, so its last grid point lies at the right-hand edge of the data.
   // A global field is like a), a sub-area is like b) below:
   //  a)   |o           o             o            |
   //  b)   |o                 o                  o | 
   
   double dataLonWidth = (globalEW_) ? 360.0 : lastLonXInArea(row1) - firstLonXInArea(row1);
   int    numIntervals = (globalEW_) ? pointsInRow1 : pointsInRow1 - 1;
   double dx1  = dataLonWidth / numIntervals;    //-- N row increment

   int firstIndex = 0;                           //-- => find pts on N row <= --//
   for( int i=firstLatIndex_; i<row1; ++i )
     firstIndex += pointsInRow( i );             //-- offset

   double firstLon = firstLonXInArea(row1);//firstLonX();
   if( globalEW_ && (firstLon > lon_x) )         //-- normalise acording to lon_x
     firstLon -= 360.0;

   int ix1 = int((lon_x-firstLon)/dx1);          //-- column W of pt
   int ix2 = (ix1 == (pointsInRow1-1) ) ?   //-- column E of pt:
              0 : ix1 + 1;                       //--    first or next!

   if (ix1 > pointsInRow1)                  //-- outside the data row?
   {
     return cMissingPoint;
   }

   double  grid_11 = valueAt(firstIndex+ix1);    //-- value in W pt
   double  grid_12 = valueAt(firstIndex+ix2);    //-- value in E pt
   double  lon_x1  = firstLon + ix1*dx1;         //-- longitude of W pt

   MvLocation  point( lat_y, lon_x );
   MvGridPoint gridPoint[4];                      //-- surrounding grid points

   gridPoint[0] = MvGridPoint( grid_11, lat1, lon_x1,     firstIndex+ix1 );
   gridPoint[1] = MvGridPoint( grid_12, lat1, lon_x1+dx1, firstIndex+ix2 );

   double closestDistance = point.distanceInMeters( gridPoint[0].loc_ );
   if( point.distanceInMeters( gridPoint[1].loc_ ) < closestDistance )
   {
       closestDistance = point.distanceInMeters( gridPoint[1].loc_ );
       gridPoint[0] = gridPoint[1];
   }

   if( borderRow )
     return gridPoint[0];

                                           //-- => find pts on S row <= --//
   firstIndex += pointsInRow1;
   firstLon = firstLonXInArea(row9);
   if( globalEW_ && (firstLon > lon_x) )         //-- normalise acording to lon_x
     firstLon -= 360.0;



   int pointsInRow9 = pointsInRow(row9);

   dataLonWidth = (globalEW_) ? 360.0 : lastLonXInArea(row9) - firstLonXInArea(row9);
   numIntervals = (globalEW_) ? pointsInRow9 : pointsInRow9 - 1;
   double dx9  = dataLonWidth / numIntervals;    //-- S row increment

   ix1 = int((lon_x-firstLon)/dx9);              //-- column W of pt
   ix2 = ( ix1 == (pointsInRow9-1) ) ?           //-- column E of pt:
              0 : ix1 + 1;                       //--    first or next!

   grid_11 = valueAt(firstIndex+ix1);            //-- value in W pt
   grid_12 = valueAt(firstIndex+ix2);            //-- value in E pt

   double lon_x9 = firstLon + ix1*dx9;

   gridPoint[2] = MvGridPoint( grid_11, lat9, lon_x9,     firstIndex+ix1 );
   gridPoint[3] = MvGridPoint( grid_12, lat9, lon_x9+dx9, firstIndex+ix2 );

   for(int p=2; p<4; ++p )                       //-- either one closer?
   {
       if( point.distanceInMeters( gridPoint[p].loc_ ) < closestDistance )
       {
         closestDistance = point.distanceInMeters( gridPoint[p].loc_ );
         gridPoint[0] = gridPoint[p];
       }
   }


   if (gridPoint[0].loc_.longitude() >= 360.0)
      gridPoint[0].loc_.set(gridPoint[0].loc_.latitude(),
                            gridPoint[0].loc_.longitude()-360.0);


#if 0
   cout << "Closest point: "
	<< ( (int)(closestDistance/100.0) / 10.0 )
	<< "km"
	<< endl;
#endif

   return gridPoint[0];

}
//------------------------------------------------------

double MvIrregularGrid::interpolatePoint( double lat_y, double lon_x )
{
  if( isSouthToNorthScanning_ )
    {
      marslog( LOG_INFO, "Nearest grid point used (no interpolation yet for S->N Gaussian grids)" );
      return nearestGridpoint( lat_y, lon_x ).value_;
    }

  if( ! field_ )					//-- non-valid msg?
    return DBL_MAX;

  // -- before first row or after last row ?
  if( lat_y > latitudes_[firstLatIndex_] || lat_y < latitudes_[lastLatIndex_] )
  {
    if (globalNS_)
	    return extrapolatePoint( lat_y, lon_x );
    else
        return DBL_MAX;
  }

  int row9 = firstLatIndex_;                    //-- find row S of pt
  double lat9 = latitudes_[ row9 ];
  while( lat9 > lat_y && row9 < firstLatIndex_+horisLines_ )
    {
      lat9 = latitudes_[ ++row9 ];
    }

  bool borderRow = (row9 == firstLatIndex_);
  //-- what about South Polish?

  int    row1 = borderRow ? row9 : row9 - 1;    //-- previous row is N of pt
  double lat1 = latitudes_[ row1 ];
  int pointsInRow1 = pointsInRow(row1);

  // to get the longitude interval, we need to consider the difference
  // between a global grid and a sub-area grid. A global grid has its
  // last point one interval before 360 degrees (because its first point
  // is already at 0 degrees, which is 360). A sub-area does not have this
  // feature, so its last grid point lies at the right-hand edge of the data.
  // A global field is like a), a sub-area is like b) below:
  //  a)   |o           o             o            |
  //  b)   |o                 o                  o | 
   
  double dataLonWidth = (globalEW_) ? 360.0 : lastLonXInArea(row1) - firstLonXInArea(row1);
  int    numIntervals = (globalEW_) ? pointsInRow1 : pointsInRow1 - 1;
  double dx1  = dataLonWidth / numIntervals;    //-- N row increment


  int firstIndex = 0;                           //-- => find pts on N row <=
  for( int i=firstLatIndex_; i<row1; ++i )
    firstIndex += pointsInRow( i );             //-- offset

  double firstLon = firstLonXInArea(row1);
  if( globalEW_ && (firstLon > lon_x) )         //-- normalise acording to lon_x
    firstLon -= 360.0;

  int ix1 = int((lon_x-firstLon)/dx1);          //-- column W of pt
  int ix2 = (ix1 == (pointsInRow1-1) ) ?   //-- column E of pt:
             0 : ix1 + 1;                       //--    first or next!

  if (ix1 > pointsInRow1 ||      //-- ix1 outside the data row?
      (!globalEW_ && (ix2 < ix1)))    //-- sub-area, and ix2 is first point?
  {
    return DBL_MAX;
  }


  double  grid_11 = valueAt(firstIndex+ix1);    //-- value in W pt
  double  grid_12 = valueAt(firstIndex+ix2);    //-- value in E pt

  if( grid_11 == mars.grib_missing_value ||
      grid_12 == mars.grib_missing_value    )
    {
      return DBL_MAX;
    }
                                                //-- interpolate on N row
  double lon_x1 = firstLon + ix1*dx1;
  double w1   = fabs((lon_x - lon_x1)/dx1);
  double w2   = 1. - w1;
  double val1 = w2*grid_11 + w1*grid_12;

  if( borderRow )
    return val1;
                                                //-- => find pts on S row <=
  firstIndex += pointsInRow1;
  firstLon = firstLonXInArea(row9);
  if( globalEW_ && (firstLon > lon_x) )         //-- normalise acording to lon_x
    firstLon -= 360.0;

  int pointsInRow9 = pointsInRow(row9);

  dataLonWidth = (globalEW_) ? 360.0 : lastLonXInArea(row9) - firstLonXInArea(row9);
  numIntervals = (globalEW_) ? pointsInRow9 : pointsInRow9 - 1;
  double dx9  = dataLonWidth / numIntervals;    //-- S row increment
  double dy  = lat1 - lat9;

  ix1 = int((lon_x-firstLon)/dx9);             //-- column W of pt
  ix2 = ( ix1 == (pointsInRow9-1) ) ?          //-- column E of pt:
             0 : ix1 + 1;                      //--    first or next!

  if (ix1 > pointsInRow9 ||      //-- ix1 outside the data row?
      (!globalEW_ && (ix2 < ix1)))    //-- sub-area, and ix2 is first point?
  {
      return DBL_MAX;
  }

  grid_11 = valueAt(firstIndex+ix1);            //-- value in W pt
  grid_12 = valueAt(firstIndex+ix2);            //-- value in E pt

  if( grid_11 == mars.grib_missing_value ||
      grid_12 == mars.grib_missing_value    )
    {
      return DBL_MAX;
    }
                                                //-- interpolate on S row
  double lon_x9 = firstLon + ix1*dx9;

  w1 = fabs((lon_x - lon_x9)/dx9);
  w2 = 1. - w1;
  double val2 = w2*grid_11 + w1*grid_12;

  w1 = ( lat_y - lat9 )/dy;                     //-- interpolate in lat
  w2 = 1. - w1;
  return val1*w1 + val2*w2;

}



//===================================================================
//
// MvReducedLatLongGrid:
// ------------------
//  Methods in reduced lat/long classes have been tested with ECMWF fields
//

MvReducedLatLongGrid::MvReducedLatLongGrid( field* myfield
				      , field_state oldState
				      , bool memoryToBeReleased )
 : MvIrregularGrid( myfield, oldState, memoryToBeReleased )
{
   gridType_ = getString("typeOfGrid");

   if( ! ( gridType_==cLatLonReducedGrid  ) )
   {
      marslog( LOG_EROR, "MvGaussianGridBase: GRIB data not reduced_ll!" );
      field_ = 0;
   }


   // create and populate the array of latutudes (these are constantly spaced)

   double dy = getDouble("jDirectionIncrementInDegrees"); // sec2_->grid_ew / cGridScaling;
   numGlobalParallels_ = (long)(180.0 / dy) + 1;

   latitudes_  = new double[ numGlobalParallels_ ];

   for (int i = 0; i < numGlobalParallels_; i++)
   {
        latitudes_[i] = 90 - (dy * i);
   }

   pointsInRow_ = 0;
   checkAreaLimits();           //-- maybe only a subarea

   currentLatIndex_ = firstLatIndex_;
   currentLaty_     = latitudes_[ currentLatIndex_ ];
   currentLonx_     = firstLonX();

   horisPoints_ = pointsInRow( currentLatIndex_ );
   dx_          = horisPoints_ ? ( 360.0 / horisPoints_ ) : 0;

}
//------------------------------------------------------


MvReducedLatLongGrid::~MvReducedLatLongGrid()
{
	if ( pointsInRow_ )
	{
		delete [] pointsInRow_;
		pointsInRow_ = 0;
	}
}

//------------------------------------------------------


int MvReducedLatLongGrid::pointsInRow( int row )
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   if( ! pointsInRow_ )
     {
       size_t len   = horisLines_;
       pointsInRow_ = new long[ len ];
       int err = grib_get_long_array( field_->handle, "pl", pointsInRow_, &len );
       if( err )
          marslog(LOG_EROR,"MvReducedLatLongGrid::pointsInRow - grib_api: 'pl' not found!");
     }

  //--
  //-- row: 0,1,2,...
  //--
  return pointsInRow_ ? pointsInRow_[row] : 0;
}


//------------------------------------------------------

bool MvReducedLatLongGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   if( ++horisPointCount_ >= horisPoints_ )
     {
       if( isSouthToNorthScanning_ )
         --currentLatIndex_;
       else
         ++currentLatIndex_;

       currentLaty_ = latitudes_[ currentLatIndex_ ];
       currentLonx_ = firstLonX();
       //horisPoints_ = field_->ksec2[ cNPtsIndexBegin + currentLatIndex_ ];
       horisPoints_ = pointsInRow( currentLatIndex_ );
       dx_          = ( 360.0 / horisPoints_ );
       horisPointCount_ = 0;
       //cout << currentIndex_ << '\t' << currentLaty_ << '\t' << horisPoints_ << endl;
     }
   else
     {
       currentLonx_ += dx_;
     }

   return true;
}



//===================================================================
//
// GaussianLatitudes:
// ------------------
//
// returns the error code from GRIB_API's grib_get_gaussian_latitudes().


int GaussianLatitudes::latitudes(long trunc, double *lats, unsigned int numLatitudes)
{
	int err = 0;
	std::map<long, vector<double> >::iterator mapIterator;


	// can we find a set of cached values for this grid?

	mapIterator = cachedLatitudes_.find(trunc);

	if (mapIterator == cachedLatitudes_.end()) // no - not cached yet
	{
		int err = grib_get_gaussian_latitudes (trunc, lats);  // this is the expensive call

		if (!err)
		{
			vector<double> vec(lats, lats+numLatitudes);
			cachedLatitudes_[trunc] = vec;
		}
	}
	else  // yes, it has been cached, so just copy it over
	{
		vector<double> &v = cachedLatitudes_[trunc];

		if (numLatitudes != v.size())  // wrong size?
		{
			marslog( LOG_EROR, "GaussianLatitudes::latitudes arrays not same size (%d and %d)",
					 numLatitudes, v.size());
			return 1;
		}
		else  // ok, copy the numbers over
		{
			memcpy(lats, &v[0], numLatitudes*sizeof(double));
		}
	}

	return err;
}


//===================================================================
//
// MvGaussianGridBase:
// ------------------
//  Methods in Gaussian classes have been tested with ECMWF fields
//

MvGaussianGridBase::MvGaussianGridBase( field* myfield
				      , field_state oldState
				      , bool memoryToBeReleased )
 : MvIrregularGrid( myfield, oldState, memoryToBeReleased )
{

   gridType_ = getString("typeOfGrid");

   if( ! ( gridType_==cGaussianGrid || gridType_==cGaussianReducedGrid ) )
   {
      marslog( LOG_EROR, "MvGaussianGridBase: GRIB data not Gaussian!" );
      field_ = 0;
   }


   long halfNumParallels_ = getLong("numberOfParallelsBetweenAPoleAndTheEquator"); // sec2_->gauss_trunc * 2;
   numGlobalParallels_ = halfNumParallels_ * 2;
   latitudes_ = new double[ numGlobalParallels_ ];


   int err = MvGaussianGridBase::gLatitudes_.latitudes(halfNumParallels_, latitudes_, numGlobalParallels_);

   if( err )
   {
       marslog(LOG_EROR,"MvGaussianGridBase: grib_get_gaussian_latitudes returned %d", err);
       field_ = 0;                   //-- something is wrong, make 'non-valid'!
   }
}
//------------------------------------------------------


double MvGaussianGridBase::extrapolatePoint( double lat_y, double lon_x )
{
// 1. compute the mean of the first/last latitude values
// 2. associate this mean value to the north/south pole
// 3. retrieve the 2 nearest points along the first/last longitude values
// 4. interpolate using these 3 values (triangle)

   // Initialize variablesCompute the initial position and npoints in the data array
   int i;
   int firstIndex;	// first position in the data array
   int np;		// number of points along the latitude
   int row;		// index: first or last latitude line
   double dy;		// latitude interval  = lat1 - lat9;
   double wlat;		// latitude weight
   if( lat_y > latitudes_[ 0 ] ) //north pole
   {
	np   = pointsInRow(0);
	dy   = 90. - latitudes_[ 0 ];
	wlat = (lat_y - latitudes_[ 0 ]) / dy;
	firstIndex = row = 0;
   }
   else if ( lat_y < latitudes_[horisLines_-1] ) // south pole
   {
	np   = pointsInRow(horisLines_-1);
	row  = horisLines_-1;
	dy   = latitudes_[horisLines_-1] - (-90.);
	wlat = (latitudes_[horisLines_-1] - lat_y) / dy;
	firstIndex = length() - np;
   }
   else
   {
	marslog(LOG_EROR,"Point is inside the area, it can not be extrapolated");
	return DBL_MAX;
   }

   // Compute the mean value
   double mean = 0.;
   for (i = 0; i < np; i++)
	mean += valueAt(firstIndex+i);

   mean = mean / (double)np;
   if( mean == mars.grib_missing_value )
      	return DBL_MAX;

   // Retrieve the 2 nearest points along the first/last latitude line
  double dx1  = 360.0 / pointsInRow( row );     //-- N row increment
  double firstLon = firstLonX();		//-- first longitude value
  if( firstLon > lon_x )                        //-- normalise acordingly
	firstLon -= 360.0;

  int ix1 = int((lon_x-firstLon)/dx1);          //-- column W of pt
  int ix2 = (ix1 == (pointsInRow(row)) ) ?      //-- column E of pt:
             0 : ix1 + 1;                       //--    first or next!

  double grid_11 = valueAt(firstIndex+ix1);     //-- value in W pt
  double grid_12 = valueAt(firstIndex+ix2);     //-- value in E pt

  if( grid_11 == mars.grib_missing_value ||
      grid_12 == mars.grib_missing_value    )
      	return DBL_MAX;

  // Interpolate on latitude row
  double lon_x1 = firstLon + ix1*dx1;
  double w1     = (lon_x - lon_x1)/dx1;
  double w2     = 1. - w1;
  double val1   = w2*grid_11 + w1*grid_12;

   // Interpolate in lat
  w2 = 1. - wlat;
  return val1*w2 + mean*wlat;
}

//------------------------------------------------------

#if 0
//------------------------------------------------------

double MvGaussianGridBase::weight() const
{
   if( ! field_ )
      return DBL_MAX;			 //-- must be valid message

   //-- should this one check the width of the lat band?
   return cos( lat_y() * cToRadians ) / horisPoints_;
}
#endif







//===================================================================
//
// MvGaussianGrid:
// --------------

MvGaussianGrid::MvGaussianGrid( field* myfield
			      , field_state oldState
			      , bool memoryToBeReleased )
  : MvGaussianGridBase( myfield, oldState, memoryToBeReleased )
{
  if( field_ )
    horisPoints_ = getLong("numberOfPointsAlongAParallel"); // sec2_->points_parallel;

   checkAreaLimits();           //-- maybe only a subarea

   currentLatIndex_ = firstLatIndex_;
   currentLaty_     = latitudes_[ currentLatIndex_ ];
   currentLonx_     = firstLonX();
   dx_              = getDouble("iDirectionIncrementInDegrees"); // sec2_->grid_ew / cGridScaling;
}
//------------------------------------------------------

bool MvGaussianGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   if( ++horisPointCount_ >= horisPoints_ )
     {
       if( isSouthToNorthScanning_ )
	 --currentLatIndex_;
       else
	 ++currentLatIndex_;

       currentLaty_ = latitudes_[ currentLatIndex_ ];
       currentLonx_ = firstLonX();
       horisPointCount_ = 0;
     }
   else
     {
       currentLonx_ += dx_;
     }

   return true;
}
//------------------------------------------------------

int MvGaussianGrid::pointsInRow( int )
{
  return horisPoints_;
}


//===================================================================
//
// MvReducedGaussianGrid:
// ---------------------

const int cNPtsIndexBegin = 22;  // Fortran index 23

//------------------------------------------------------

MvReducedGaussianGrid::MvReducedGaussianGrid( field*      myfield
					    , field_state oldState
					    , bool        memoryToBeReleased )
 : MvGaussianGridBase( myfield, oldState, memoryToBeReleased )
{
  //horisPoints_ = field_->ksec2[ cNPtsIndexBegin ];


   pointsInRow_ = 0;
   checkAreaLimits();           //-- maybe only a subarea

   currentLatIndex_ = firstLatIndex_;
   currentLaty_     = latitudes_[ currentLatIndex_ ];
   currentLonx_     = firstLonX();

   horisPoints_ = pointsInRow( 0 );
}

//------------------------------------------------------

double MvReducedGaussianGrid::firstLonXInArea(int row)
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   double globalFirstLonX = firstLonX();
   double firstLonXInArea = globalFirstLonX;

   // if the grid is on a sub-area then we need to scan along the current line in order to
   // find the location of the first point

   if (!globalEW_)
   {
       long numPts = 0;
       long indexFirst, indexLast;
       int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row] : 0;
       grib_get_reduced_row(globalPointsInThisRow, firstLonX(), lastLonX(),
                             &numPts, &indexFirst,&indexLast);

       firstLonXInArea = (indexFirst*360.0) / globalPointsInThisRow;
       if (firstLonXInArea > lastLonX())
         firstLonXInArea -= 360.0;
   }
   
   return firstLonXInArea;
}
//------------------------------------------------------

double MvReducedGaussianGrid::lastLonXInArea(int row)
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   double globalLastLonX = lastLonX();
   double lastLonXInArea = globalLastLonX;

   // if the grid is on a sub-area then we need to scan along the current line in order to
   // find the location of the first point

   if (!globalEW_)
   {
       long numPts = 0;
       long indexFirst, indexLast;
       int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row] : 0;
       grib_get_reduced_row(globalPointsInThisRow, firstLonX(), lastLonX(),
                             &numPts, &indexFirst,&indexLast);

       lastLonXInArea = (indexLast*360.0) / globalPointsInThisRow;
       if (lastLonXInArea < firstLonX())
         lastLonXInArea += 360.0;
   }
   
   return lastLonXInArea;

}

//------------------------------------------------------

bool MvReducedGaussianGrid::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   if( ++horisPointCount_ >= horisPoints_ )
   {
       if( isSouthToNorthScanning_ )
         --currentLatIndex_;
       else
         ++currentLatIndex_;

       currentLaty_ = latitudes_[ currentLatIndex_ ];
       currentLonx_ = firstLonXInArea(currentLatIndex_-firstLatIndex_);
       //horisPoints_ = field_->ksec2[ cNPtsIndexBegin + currentLatIndex_ ];
       horisPoints_ = pointsInRow( currentLatIndex_-firstLatIndex_ );
       //dx_          = ( 360.0 / horisPoints_ );
       horisPointCount_ = 0;
       //cout << currentIndex_ << '\t' << currentLaty_ << '\t' << horisPoints_ << endl;
   }
   else
   {
       currentLonx_ += dx_;
       if (fabs(currentLonx_) < 0.00001)  // try to avoid imprecision around zero
            currentLonx_ = 0.0;
   }

   return true;
}
//------------------------------------------------------

int MvReducedGaussianGrid::pointsInRow( int row )
{
   if( ! field_ )
      return 0;			 //-- must be valid message

   if( ! pointsInRow_ )
     {
       size_t len   = horisLines_;
       pointsInRow_ = new long[ len ];
       int err = grib_get_long_array( field_->handle, "pl", pointsInRow_, &len );
       if( err )
          marslog(LOG_EROR,"MvReducedGaussianGrid::pointsInRow - grib_api: 'pl' not found!");
     }

  //--
  //-- row: 0,1,2,...
  //--
  int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row] : 0;
  dx_ = ( 360.0 / globalPointsInThisRow );
  if (globalEW_)
    return globalPointsInThisRow;
  else
  {
     long numPts = 0;
     long indexFirst, indexLast;
     grib_get_reduced_row(globalPointsInThisRow, firstLonX(), lastLonX(),
                    &numPts, &indexFirst,&indexLast);

     return numPts;

//      return ((lastLonX() - firstLonX()) / dx_) + 1;
  }
}


//===================================================================
//
// MvSatelliteImage:
// ----------------

#define SATHROW true

MvSatelliteImage::MvSatelliteImage( field* myfield
				    , field_state oldState
				    , bool memoryToBeReleased
                                  )
: MvGridBase( myfield, oldState, memoryToBeReleased )
{
   nontested_grib_api_port("MvSatelliteImage()");

   gridType_ = getString("typeOfGrid");

   // Initialize Structures
   pimin1_();

   // Initialize Datum
   double Prd  = 6378160.;
   double Pflt = 0.00335289186;
   pimind_(PROJSATELLITE,"satellite",0.,0.,0.,Prd,Pflt);

   // Initialize Projection
   pimp1_("satellite","image",0,PROJSATELLITE,0,0,0.,0.,0.,Prd,Pflt,0.,0.);
   pimg1_(1,0.,0.,0.,0.);

// Initialize Satellite projection
   double  satnr = getLong("NrInRadiusOfEarth",SATHROW); //float(sec2_->altitude);
   double  satdy = getLong("dy",SATHROW); //float(sec2_->y_diameter);
   double  satdx = getLong("dx",SATHROW); //float(sec2_->x_diameter);

   double SPri  = 2.0*asin(1.0/(satnr*0.000001)) / satdy;
   double SPrj  = 2.0*asin(1.0/(satnr*0.000001)) / satdx;

   double SPis  = getDouble("YpInGridLengths",SATHROW); //double(sec2_->y_coordinate);
   double SPjs  = getDouble("XpInGridLengths",SATHROW); //double(sec2_->x_coordinate);

   //double SPla0 = double(sec2_->latitude_point) * 0.001*CDR;
   //double SPlo0 = double(sec2_->longitude_point) * 0.001*CDR;
   double lat_pt = getDouble("latitudeOfSubSatellitePoint",SATHROW);
   double lon_pt = getDouble("longitudeOfSubSatellitePoint",SATHROW);
   double SPla0  = lat_pt * 0.001*CDR;
   double SPlo0  = lon_pt * 0.001*CDR;

   double SPrs  = double(satnr) * 0.000001 * EARTH_RADIUS;
   double SPscn = getLong("scanningMode",SATHROW); //double(sec2_->scan_mode);

   //double SPyaw = double(sec2_->orientation) * 0.001*CDR;
   double orien = getDouble("orientationOfTheGrid",SATHROW); // previous key name (ga 1.8.0): orientationOfTheGridInMillidegrees
   double SPyaw = orien * 0.001*CDR;

   if( SPyaw < 0.0 )
      SPyaw += PI;
   else
      SPyaw -= PI;

   pims1_(SPri,SPrj,SPis,SPjs,SPla0,SPlo0,SPrs,SPscn,SPyaw);

// Initialize Image
   long          lin = getLong("numberOfPointsAlongYAxis",SATHROW); //short(sec2_->ny);
   long          col = getLong("numberOfPointsAlongXAxis",SATHROW); //short(sec2_->nx);
   double        satx0 = getDouble("xCoordinateOfOriginOfSectorImage",SATHROW); //double(sec2_->x_origin);
   double        saty0 = getDouble("yCoordinateOfOriginOfSectorImage",SATHROW); //double(sec2_->y_origin);

   double bllx = satx0*atan(tan(2.0*asin(1.0/(satnr*0.000001)) / satdx) *
		     ((satnr*0.000001)-1.0)) * EARTH_RADIUS;

   double blly = -(saty0 + lin-1)*atan(tan(2.*asin(1.0/(satnr*0.000001)) / satdy) *
                                ((satnr*0.000001)-1.0)) * EARTH_RADIUS;

   double burx = (satx0 + col-1)*atan(tan(2.*asin(1.0/(satnr*0.000001))/ satdx) *
			       ((satnr*0.000001)-1.0)) * EARTH_RADIUS;

   double bury = -saty0*atan(tan(2.0*asin(1.0/(satnr*0.000001)) / satdy) *
		      ((satnr*0.000001)-1.0)) * EARTH_RADIUS;

   double irx = (burx-bllx)/double(col-1);
   double iry = (bury-blly)/double(lin-1);

   // There is no need to allocate memory for the image.
   // The program is using the MvField class.
   pimii1_(bllx,blly,burx,bury,lin,col,irx,iry,0,0,Sbuf_);

   currentLaty_ = cMvlatLonMissingValue;
   currentLonx_ = cMvlatLonMissingValue;
}

MvSatelliteImage::~MvSatelliteImage ()
{
}

//static int ftest=0;
MvGridPoint MvSatelliteImage::nearestGridpoint( double lat_y, double lon_x )
{

//--
//-- NOTE: prerequisite: horizontal scanning i.e. does not
//--       function properly with vertical scanning (j faster)!
//--

   if( ! field_ )
       return cMissingPoint;

   // Compute image coordinates
   Real col = lon_x * CDR;
   Real lin = lat_y * CDR;
   pll2ic (col,lin);


#if 0
//test
if(ftest == 0)
{
   ftest=1;
   int nlat = sec2_->ny;
   int nlon = sec2_->nx;
   long size = (long)nlat * (long)nlon;
   double pix,rmax=0.,rmin=500.,rmean=0.;
   int imin,imax,ii,jj;
   long hist[256];
   for (ii=0;ii<256;ii++) hist[ii]=0L;
   for (ii=0; ii < nlat; ii++)
   {
     for (jj=0; jj < nlon; jj++)
     {
	pix = GetValue(ii,jj);
        rmean += pix;
        hist[(int)pix]++;
        if(pix > rmax)
        {
	  rmax = pix;
	  imax = ii;
        }
        if(pix < rmin)
        {
	  rmin = pix;
	  imin = ii;
        }
     }
   }
   for (ii=0;ii<256;ii++)
       cout << ii << " " << hist[ii] << endl;
   cout << "rmin=" << rmin << " index=" << imin << endl;
   cout << "rmax=" << rmax << " index=" << imax << endl;
   cout << "min=" << rmean/(double)size << endl;
}
//end test
#endif

   double pixvalue = GetValue( (int)lin, (int)col );

   return MvGridPoint( pixvalue, lat_y, lon_x);
}

bool MvSatelliteImage::getMatrixNN( double lat_y, double lon_x, MvMatrix& mat)
{
//--
//-- NOTE: prerequisite: horizontal scanning i.e. does not
//--       function properly with vertical scanning (j faster)!
//--

   if( ! field_ )
       return false;

   // Compute image coordinates
   Real col = lon_x * CDR;
   Real lin = lat_y * CDR;
   pll2ic (col,lin);

   // Compute area limits
   int i,j,k,l;
   int nlin = mat.Nlin();
   int ncol = mat.Ncol();
   int lmin = (int)lin - nlin/2;
   int lmax = lmin + nlin - 1;
   int cmin = (int)col - ncol/2;
   int cmax = cmin + ncol - 1;

   // Retrieve values
   k = 0;
   for ( i = lmin; i <= lmax; i++)
   {
	 l = 0;
	 for ( j = cmin; j <= cmax; j++)
	 {
	       mat.Mput(k,l,GetValue(i,j));
	       l++;
	 }
	 k++;
   }

   return true;
}

double MvSatelliteImage::interpolatePoint( double lat_y, double lon_x )
{
	// For Satellite images the nearest neighbour interpolation is used
	MvGridPoint gp = nearestGridpoint (lat_y,lon_x);

	return gp.value_;
}

double MvSatelliteImage::GetValue( int lin, int col )
{
   union{
	 double dd;
	 int ia[2];
   };

   // Check indexes
   long nx = getLong("numberOfPointsAlongAParallel");
   long ny = getLong("numberOfPointsAlongAMeridian");
   if (lin < 0 || lin > ny || col < 0 || col > nx )
	 return DBL_MAX;

   // Retrieve image value
   dd = valueAt( nx*(long)lin + (long)col );

   long flg = getLong("section4.flag");
   if ( flg == 0 )    // field_->ksec4[4] == 0 )
	 return dd;               //float representation
   else
   {
#ifdef LITTLE_END                 //integer representation
	      return (double)ia[0];
#else
	      return (double)ia[1];
#endif
   }

}
//------------------------------------------------------

bool MvSatelliteImage::advance()
{
   if( ! field_ || ++currentIndex_ >= length() )
      return false;

   currentLaty_ = cMvlatLonMissingValue;     //-- not yet implemented
   currentLonx_ = cMvlatLonMissingValue;

   return true;
}



//===================================================================
//
// MvGridFactory:
// -------------
//
// Checks a raw GRIB field and if data representation is
// any of the implemented ones creates a GRID object of
// that type, otherwise returns zero.
// Initialisation parameters:
//   myfield:       the MARS field as input
//   releaseMemory: copied to memoryToBeReleased
//   expandGrid:    should we expand the grid data in memory?
//------------------------------------------------------

MvGridBase* MvGridFactory( field* myfield, bool releaseMemory, bool expandGrid )
{
   if( !myfield)
      return 0;

   bool memoryToBeReleased = releaseMemory;

   field_state oldState = myfield->shape;

   //-- ensure that GRIB msg is at least in memory, not necessarily expanded
   // ideally should be packed_mem, but can only do this once we have ensured
   // that it is expanded wherever it is needed, and preferably use
   // grib_api's grib_nearest_find() function for interpolation purposes.

   if (expandGrid)
         set_field_state( myfield, expand_mem );  // (anything) -> expand_mem
   else
      if (oldState == packed_file)
         set_field_state( myfield, packed_mem );  // packed_file -> packed_mem

   // Check if the grib file is read to be used
   if ( !myfield->handle )
      return 0;

   const size_t cMaxBuf = 99;
   char strbuf[ cMaxBuf+1 ];
   size_t slen = cMaxBuf;

   MvGridBase* grd = 0;
   int err = grib_get_string(myfield->handle,"typeOfGrid",strbuf,&slen);
   if( err != 0 )
     {
       grd = new MvUnimplementedGrid( myfield, oldState );
       marslog( LOG_EROR,"MvGridFactory: failed getting grib_get_long->typeOfGrid");
       return grd;
     }

   string myGridType(strbuf);

   MvGridBase *grid = 0;

   if( myGridType == cLatLonGrid )
     grid = new MvLatLonGrid( myfield, oldState, memoryToBeReleased );

   if( myGridType == cGaussianGrid )
     grid = new MvGaussianGrid( myfield, oldState, memoryToBeReleased );

   if( myGridType == cGaussianReducedGrid )
     grid = new MvReducedGaussianGrid( myfield, oldState, memoryToBeReleased );

   if( myGridType == cLatLonRotatedGrid )
     grid = new MvLatLonRotatedGrid( myfield, oldState, memoryToBeReleased );

   if( myGridType == cLatLonReducedGrid )
   {
     // no support for limited area fields of this grid type
     MvReducedLatLongGrid *reducedGrid = new MvReducedLatLongGrid( myfield, oldState, memoryToBeReleased );

     if (!reducedGrid->globalEW_)
     {
        delete reducedGrid;
        grid = new MvUnimplementedGrid( myfield, oldState );
     }
     else
     {
        grid = reducedGrid;
     }
   }

   if( myGridType == cSatelliteImage )
     grid = new MvSatelliteImage( myfield, oldState, memoryToBeReleased );

   if( myGridType == cLambertGrid )
     grid = new MvLambertGrid( myfield, oldState, memoryToBeReleased );

   // if( myGridType == cPolarStereoGrid )
   //   grid = new MvPolarStereoGrid( myfield, oldState, memoryToBeReleased );


   if ((grid != 0) && (!grid->isValid())) // assigned, but not successfully constructed...
   {
        delete grid;
        grid = 0;
   }

    if (grid == 0)
        grid = new MvUnimplementedGrid( myfield, oldState );  // includes spectral fields


   return grid;
}


//===================================================================
//-------------------------------------------------------------------
// MvGeoBox:
// --------
// Metview has "an Area Problem" i.e. areas are defined differently
// for PlotWindow and Macroes. This class accepts geographical areas
// defined in either form:
//
//     <1>  [upper left corner]-[lower right corner] e.g. [n,w,s,e]
// or:
//     <2>  [lower left corner]-[upper right corner] e.g. [s,w,n,e]
//
// where one corner value is a [latitude,longitude] pair.
// NOTE: [latitude,longitude] corresponds to [y,x], NOT [x,y]!!
//
// Class asserts that internally:
//    o  North >= South (North and South values swapped if <2> used)
//    o   East >= West  (never swapped, only normalized)
//
// "East >= West" relation is guaranteed by normalising the values:
//    o     0 <  East <= 360
//    o  -360 <= West <= East
//    o   (East-West) <= 360
//
// Member function 'isInside' tries to normalise the longitude parameter
// between normalised East-West values.
//
// NOTE:
// Prerequisite:
// Class is designed to work properly with value ranges:
//    o  -360 <= longitude <= +360
//    o   -90 <= latitude  <=  +90.
//

//-------------------------------------------------------------------
//    MvGeoBox::MvGeoBox and MvGeoBox::set prerequisite:
//    -------------------------------------------------
//     -360 <=  west,east <= 360
//      -90 <= north,south <= 90
//

void MvGeoBox::set( double north, double west, double south, double east )
{
	n = north>south ? north : south;         //-- force n >= s
	s = south<north ? south : north;         //-- force s <= n

	e = east>0 ? east : east + 360.0;        //-- force 0 < e <= 360

	if( west == east )
	    w = e;                               //-- vertical line
	else
	    w = west>east ? west - 360.0 : west; //-- force -360 <= w <= e

	if( (e - w) > 360.0 )                    //-- force (e-w) <= 360
	    w += 360.0;

//	marslog( LOG_DBUG, "MvGeoBox [ %g, %g, %g, %g ]", n,w,s,e );
}

//-------------------------------------------------------------------
//   MvGeoBox::isInside prerequisite:
//   -------------------------------
//       -360 <= lon_x <= 360
//        -90 <= lat_y <=  90
//
//   NOTE: parameters are (latitude,longitude) or (y,x), not (x,y)!!!
//

int MvGeoBox::isInside( double lat_y, double lon_x ) const
{
	if( lat_y > n || lat_y < s )
	    return 0;                  //-- outside n-s band!

	if( lon_x > e )                //-- assume -360 <= lon_x <= 360
	    lon_x -= 360.0;            //-- and try to normalize
	if( lon_x < w )
	    lon_x += 360.0;

	if( lon_x > e || lon_x < w )
	    return 0;                  //-- outside!

	return 1;
}

#if 0
int inbox(double lon,double lat,double n,double w,double s,double e)
{
	if(lat > n || lat < s) return 0;

	while(e   > w + 360.0) e   -= 360.0; while(e   < w) e   += 360.0;
	while(lon > w + 360.0) lon -= 360.0; while(lon < w) lon += 360.0;
//            ^
//            !
// should be  >=  (this one misses the exact western boarder line!!!)
//
// e.g. inbox( 330, lat, n, -30, s, 60 ) =>
//
//  while(330 > -30 + 360.0){skipped}; while(330 < -30){skipped}
//  if(330 > 60 ...) return 0;   // boarder grid point missed!


	if(lon > e || lon < w) return 0;

	return 1;
}
#endif

