/***************************** 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 <math.h>
#include <ctype.h>
#include <assert.h>

#include <algorithm>
#include <cmath>
#include <cstring>
#include <iomanip>
#include <iostream>
#include <map>
#include <string>
#include <utility>
#include <vector>

#include "MvGrid.h"
#include "MvException.h"
#include "proj_braz.hpp"
#include "MvLog.h"

// define this if you want to use a version of MvLatLonGrid::interpolatePoint()
// which uses built_in ecCodes functions to get the nearest 4 points.
// Note that the ecCodes 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 double cFullGlobeD =  360.;
const double cFullGlobeLimitX = 359.9;
const double cFullGlobeLimitY = 179.9;
const double cGridEpsilon = 5e-5;  //-- 0.05 millidegrees
const double cToRadians = M_PI / 180.0;

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 eccodes_port_missing(const char* msg)
{
    std::cout << ">>>>\n"
              << ">>> ecCodes port not done for: \n"
              << ">> " << msg << "\n"
              << ">  " << std::endl;
    marslog(LOG_INFO, "ecCodes missing: %s", msg);
}

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

void MvGridPoint::sortByDistance(std::vector<MvGridPoint>& points, const MvLocation& refPoint)
{
    using SortD = std::pair<MvGridPoint, double>;
    std::vector<SortD> sortVec;
    for (auto const &p: points) {
        sortVec.push_back(std::make_pair(p, refPoint.distanceInMeters(p.loc_)));
    }

    auto sortFunc = [&refPoint](const SortD& a, const SortD& b) {
        if (a.second < b.second) {return true;}
        else if (a.second > b.second) {return false;}
        // if the distance is the same
        else {
            // we prefer the one closer in longitude
            auto d1 = std::fabs(a.first.loc_.longitude() - refPoint.longitude());
            auto d2 = std::fabs(b.first.loc_.longitude() - refPoint.longitude());
            if (d1 < d2) { return true;}
            else if (d1 > d2) {return false;}
            // if the longitudes are equally close
            else {
                // prefers the smaller longitude
                if (a.first.loc_.longitude() < b.first.loc_.longitude()) {
                    return true;
                } else if (a.first.loc_.longitude() > b.first.loc_.longitude()) {
                    return false;
                // if the logitudes are the same we prefer the one closer in latutude
                } else {
                    d1 = std::fabs(a.first.loc_.latitude() - refPoint.latitude());
                    d2 = std::fabs(b.first.loc_.latitude() - refPoint.latitude());
                    return (d1 < d2);
                }
            }
        }
        return a.first.index_ < b.first.index_;
    };

    std::stable_sort(sortVec.begin(), sortVec.end(), sortFunc);

    assert(points.size() == sortVec.size());
    for (size_t i = 0; i < points.size(); i++) {
        points[i] = sortVec[i].first;
//        std::cout << std::setprecision(26) << " idx=" << points[i].index_+1 << " lon=" <<
//                      points[i].loc_.longitude()  << "  " <<
//                  refPoint.longitude()  << " dist=" << sortVec[i].second << std::endl;
    }
}

//===================================================================
//
// 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),
    restoreShapeOnDestroy_(true)
{
    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_) {
        // if we do not want to save the data back to the message.
        if (forgetValuesOnDestroy_) {
            // if we revert back to packed_mem (from expand_mem) set_field_state()
            // will always write the values back to the grib_handle. So we apply the code below
            // to delete the values and go back from expand_mem to packed mem in a safe way.
            if (field_->shape == expand_mem) {
                release_mem(field_->values);
                field_->values = NULL;
                field_->value_count = 0;
                field_->shape = packed_mem;
            }
        }

        if (memoryToBeReleased_) {
            // see comment below, and note that mars_free_field will() decrement field_->refcnt first and if
            // it is <=0 it will completely deallocate the field_!!!
            bool restore_field_state = restoreShapeOnDestroy_ && (field_->refcnt > 1);

            // 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.
            mars_free_field(field_);

            if (restore_field_state) {
                // if we are here we can be sure that the field_ still exists!
                // TODO: find a way to check it
                set_field_state(field_, oldState_);
            }
        }
    }

    if (vertCoord_)
        delete[] vertCoord_;

    //  std::cout << "MvGridBase::~MvGridBase(): currentIndex_ = " << currentIndex_ << std::endl;
}
//------------------------------------------------------
eGribHeaderType MvGridBase::getNativeType(field* fld, const char* name)
{
    int valueType;
    int err = grib_get_native_type(fld->handle, name, &valueType);

    if (err == GRIB_SUCCESS) {
        size_t len;
        err = grib_get_size(fld->handle, name, &len);
        bool array = (len > 1);
        if (err == GRIB_SUCCESS) {
            switch (valueType) {
                case GRIB_TYPE_DOUBLE:
                    return (array) ? GRIB_DOUBLE_ARRAY : GRIB_DOUBLE;
                    break;
                case GRIB_TYPE_LONG:
                    return (array) ? GRIB_LONG_ARRAY : GRIB_LONG;
                    break;
                case GRIB_TYPE_STRING:
                default:
                    return GRIB_STRING;
            }
        }
    }
    return GRIB_TYPE_ERROR;
}

eGribHeaderType MvGridBase::getNativeType(const char* name) const
{
    return getNativeType(field_, name);
}

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

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

    return val;
}

long MvGridBase::getLong(const char* name, bool throwOnError, bool quiet) const
{
    return getLong(field_, name, throwOnError, quiet);
}

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

long MvGridBase::getLongArray(field* fld, const char* name, long** lvals, bool throwOnError, bool quiet)
{
    size_t numVals = 0;

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

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

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

    return numVals;
}

long MvGridBase::getLongArray(const char* name, long** lvals, bool throwOnError, bool quiet) const
{
    return getLongArray(field_, name, lvals, throwOnError, quiet);
}
//------------------------------------------------------

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

    return true;
}


bool MvGridBase::setLong(const char* name, long val)
{
    return setLong(field_, name, val);
}

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

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

    return true;
}


bool MvGridBase::setLongArray(const char* name, long* val, size_t len)
{
    return setLongArray(field_, name, val, len);
}

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

    return val;
}

double
MvGridBase::getDouble(const char* name, bool throwOnError, bool quiet) const
{
    return getDouble(field_, name, throwOnError, quiet);
}

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

long MvGridBase::getDoubleArray(field* fld, const char* name, double** dvals, bool throwOnError, bool quiet)
{
    size_t numVals = 0;

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

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

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

    return numVals;
}

long MvGridBase::getDoubleArray(const char* name, double** dvals, bool throwOnError, bool quiet) const
{
    return getDoubleArray(field_, name, dvals, throwOnError, quiet);
}

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

bool MvGridBase::setDouble(const char* name, double val)
{
    return setDouble(field_, name, val);
}

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

    return true;
}

bool MvGridBase::setString(const char* name, std::string& val)
{
    return setString(field_, name, val);
}
//------------------------------------------------------string
std::string
MvGridBase::getString(field* fld, const char* name, bool throwOnError, bool quiet)
{
    std::string val("NA");
    if (!fld) {
        return val;
    }

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

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

    return val;
}

std::string
MvGridBase::getString(const char* name, bool throwOnError, bool quiet) const
{
    return getString(field_, name, throwOnError, quiet);
}

double MvGridBase::validityDateTime(field* fld)
{
    auto d = getDouble(fld, "validityDate");
    auto t = getDouble(fld, "validityTime");
    return d + t / 2400.0;
}

double MvGridBase::validityDateTime() const
{
    return validityDateTime(field_);
}

//------------------------------------------------------
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
    {
        auto 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 ecCodes'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;
}

void MvGridBase::setAllValuesToMissing(bool flagBitmap)
{
    setAllValuesToMissing(field_, flagBitmap);
}

void MvGridBase::setAllValuesToMissing(field* f, bool flagBitmap)
{
    if (f) {
        if (flagBitmap) {
            f->bitmap = true;
        }
        for (std::size_t i = 0; i < f->value_count; i++) {
            f->values[i] = 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, bool nearestValid)
{
    //--
    //--  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();
        if (!nearestValid || myCurrentPoint.value_ != mars.grib_missing_value) {
            double myDistance = myCurrentPoint.loc_.distanceInMeters(myPoint);
            if (myDistance < myClosestDistance) {
                myClosestPoint = myCurrentPoint;
                myClosestDistance = myDistance;
            }
        }
    }
    /*
   std::cout << "INFO: "
        << MvLocation(lat_y,lon_x)
        << ": nearest grid point at "
    << myClosestPoint.loc_
    << ", distance "
    << ( (int)(myClosestDistance/100.0) / 10.0 )  //-- xx.x km
    << " km"
    << std::endl;
*/
    return myClosestPoint;
}
//------------------------------------------------------

double MvGridBase::interpolatePoint(double lat_y, double lon_x, std::vector<MvGridPoint>* /*surroundingPoints*/)
{
    //--
    //--  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, false).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
//
std::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) - cFullGlobeD;  //-- 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;

    std::vector<double> sum(listSize, 0.0);
    std::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 - cFullGlobeD - 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;
}
//------------------------------------------------------

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


//===================================================================
//
// MvGridUsingGribIterator:
// Base class to be inherited from
// -------------------------------

MvGridUsingGribIterator::MvGridUsingGribIterator(field* myfield, field_state oldState, bool memoryToBeReleased) :
    MvGridBase(myfield, oldState, memoryToBeReleased)
{
    // try to create a GRIB iterator
    // XXX - Question: do we always want to do this, or is it better to
    // only do it on the first call to gridPoint() for efficiency?
    // One reason to do it here is that we will now know whether we can
    // iterate over the points or not (hasIterator() function).
    int griberr = 0;
    iter_ = grib_iterator_new(field_->handle, 0, &griberr);
    if (griberr != GRIB_SUCCESS) {
        iter_ = nullptr;
    }
    else {
        advance();  // set the current point to the first from the iterator
    }
}


MvGridUsingGribIterator::~MvGridUsingGribIterator()
{
    if (iter_ != nullptr)
        grib_iterator_delete(iter_);
}

bool MvGridUsingGribIterator::advance()
{
    // we just rely on the GRIB iterator for this
    int next = grib_iterator_next(iter_, &currentLaty_, &currentLonx_, &currentVal_);

    return (next != 0);
}

//--------------------------------------------------------------------------
// we need to override the base class value() function because it relies on
// indexing the array of values, but we may not be guaranteed that the GRIB
// iterator goes through the values sequentially like this, so we rely
// completely on the GRIB iterator instead.
//--------------------------------------------------------------------------

double MvGridUsingGribIterator::value() const
{
    return currentVal_;
}


//===================================================================
//
// 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_);
    }


    globalEW_ = (horisPoints_ * fabs(dx_) > cFullGlobeLimitX);
    globalNS_ = (horisLines_ * fabs(dy_) > cFullGlobeLimitY);

    // assign north/south-most latitudes
    southernLat_ = lat1;
    northernLat_ = lat9;
    if (southernLat_ > northernLat_) {
        double tmp = southernLat_;
        southernLat_ = northernLat_;
        northernLat_ = tmp;
    }
}
//------------------------------------------------------

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 -= cFullGlobeD;

        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_;
}
//------------------------------------------------------

void MvLatLonGrid::boundingBox(std::vector<double>& v)
{
    v.push_back(southernLat_);
    v.push_back(firstLonX_);
    v.push_back(northernLat_);
    v.push_back(lastLonX_);
    if (globalNS_) {
        v[0] = -90;
        v[2] = 90;
    }
    if (globalEW_) {
        v[1] = -180;
        v[3] = 180;
    }
    if (v[1] > 180 && v[3] > 180) {
        v[1] = v[1] - cFullGlobeD;
        v[3] = v[3] - cFullGlobeD;
    }
}

void MvLatLonGrid::init()
{
    MvGridBase::init();
    horisPointCount_ = 0;
}

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

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

    return true;
}


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

bool MvLatLonGrid::surroundingGridpointsForExtrapolation(double lat_y, double lon_x, std::vector<MvGridPoint>& points,
                                                         bool canHaveMissing, bool doSort)
{
    // Initialize variablesCompute the initial position and npoints in the data array
    int firstIndex;         // first position in the data array
    int np = horisPoints_;  // number of points along the latitude
    int row;                // index: first or last latitude line
    double gLat;


    // north pole
    if (lat_y > northernLat_) {
        gLat = northernLat_;
        row = (northernLat_ == firstLatY()) ? 0 : horisLines_ - 1;
        // south pole
    }
    else if (lat_y < southernLat_) {
        gLat = southernLat_;
        row = (southernLat_ == lastLatY()) ? horisLines_ - 1 : 0;
    }
    else {
        marslog(LOG_EROR, "Point is inside the area, it can not be extrapolated");
        return false;
    }

    firstIndex = np * row;

    double startLon = firstLonX();  //-- first longitude value

    // normalise accordingly
    if (startLon > lon_x)
        startLon -= cFullGlobeD;

    // reverse order so that we could get the same nearest point as with nearest_gridpoint!!
    for (int i = np - 1; i >= 0; i--) {
        double gLon = startLon + i * dx_;
        int idx = firstIndex + i;
        double val = valueAt(idx);
        if (!canHaveMissing && val == mars.grib_missing_value)
            return false;
        else
            points.push_back(MvGridPoint(val, gLat, gLon, idx));
    }

    if (doSort) {
        MvGridPoint::sortByDistance(points, MvLocation(lat_y, lon_x));
    }

    return true;
}


bool MvLatLonGrid::surroundingGridpoints(double lat_y, double lon_x, std::vector<MvGridPoint>& points,
                                         bool canHaveMissing, bool doSort)
{
    if (!field_) {
        return false;
    }

    // get GRIB field parameters
    double lat1 = firstLatY();
    double lon1 = firstLonX();
    double lat9 = lastLatY();
    double lon9 = lastLonX();

    double lon1Ori = lon1;

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

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

    // 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
    if (!globalEW_ && (lon_x < lon1 || lon_x > lon9))
        return false;

    // -- before first row or after last row ?
    if (lat_y < lat1 || lat_y > lat9) {
        if (globalNS_)
            return surroundingGridpointsForExtrapolation(lat_y, lon_x, points, canHaveMissing, doSort);
        else
            return false;
    }


    // use our own routine to find the 4 nearest grid points
    int ix1 = int((lon_x - lon1) / dx_);  //-- index for column on the west

    if (ix1 > (horisPoints_ - 1)) {
        if (globalEW_)
            ix1 = 0;  //-- wrap around globe
        else
            return false;
    }
    //-- index for column on the east
    int ix2 = ix1 + 1;

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

    double latStart = (dy_ < 0) ? lat9 : lat1;

    // start either from north or from south depending on scanning mode --
    int iy1 = int((lat_y - latStart) / dy_);
    int iy2 = iy1 + 1;

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

    // get and check grid point values

    // point 11
    long index = horisPoints_ * iy1 + ix1;
    double val = valueAt(index);
    if (!canHaveMissing && val == mars.grib_missing_value)
        return false;

    points.push_back(MvGridPoint(val, latStart + iy1 * dy_, lon1Ori + ix1 * dx_, index));

    // point 12
    index = horisPoints_ * iy1 + ix2;
    val = valueAt(index);
    if (!canHaveMissing && val == mars.grib_missing_value)
        return false;

    points.push_back(MvGridPoint(val, latStart + iy1 * dy_, lon1Ori + ix2 * dx_, index));

    // point 21
    index = horisPoints_ * iy2 + ix1;
    val = valueAt(index);
    if (!canHaveMissing && val == mars.grib_missing_value)
        return false;

    points.push_back(MvGridPoint(val, latStart + iy2 * dy_, lon1Ori + ix1 * dx_, index));

    // point 22
    index = horisPoints_ * iy2 + ix2;
    val = valueAt(index);
    if (!canHaveMissing && val == mars.grib_missing_value)
        return false;

    points.push_back(MvGridPoint(val, latStart + iy2 * dy_, lon1Ori + ix2 * dx_, index));

    // sort by distance to reference point
    if (doSort)
        MvGridPoint::sortByDistance(points, MvLocation(lat_y, lon_x));

    return true;
}

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

MvGridPoint MvLatLonGrid::nearestGridpoint(double lat_y, double lon_x, bool nearestValid)
{
    if (!field_) {
        return cMissingPoint;
    }

    /*
    // ecCodes 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)!
    //--

    if (nearestValid) {
        std::vector<MvGridPoint> points;
        if (surroundingGridpoints(lat_y, lon_x, points, true, true)) {
            for (auto& point : points) {
                if (point.value_ != mars.grib_missing_value) {
                    return point;
                }
            }
        }
        return cMissingPoint;
    }

    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, cFullGlobeD);
        west = 0;
    }

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

    // adjust longitude inside grid
    while (lon_x < west)
        lon_x += cFullGlobeD;

    // half a gridpoint over the eastmost point
    while (lon_x > eastpoint + (dx_ / 2.0))
        lon_x -= cFullGlobeD;

    // nearest gridpoint
    int j = int(rint((lat_y - lat1) / dy_));
    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, std::vector<MvGridPoint>* surroundingPoints)
{
    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 += cFullGlobeD;
        lon9 += cFullGlobeD;
    }

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

    // check that lats are: lat1=southern, lat9=northern
    if (lat1 > lat9) {
        std::swap(lat1, lat9);
    }

    //-- check if the point is outside grid
    if (!globalEW_ && (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 ecCodes'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];


            std::cout << "ecCodes interpolating point: "
                      << "(" << lat_y << "," << lon_x << ")" << std::endl;
            std::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] << ")" << std::endl;
            std::cout << "  indexes: (" << nearest_indexes[0 || ] << "," << nearest_indexes[1] << ","
                      << nearest_indexes[2] << "," << nearest_indexes[3] << ")" << std::endl;
            std::cout << "  values: (" << nearest_vals[0] << "," << nearest_vals[1] << ","
                      << nearest_vals[2] << "," << nearest_vals[3] << ")" << std::endl;
            std::cout << "  ecCodes interpolated: " << interpolated_value << std::endl;

            return interpolated_value;
        }

        else {
#use_first_non_missing USE_NEAREST_NON_MISSING = 1

            test_nearest_gridpoint_info(data, 90, 0, 90, 0, 1) #top row,
                first point

                    USE_NEAREST_NON_MISSING = 0 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

    // make sure lon9 is 360 deg away from lon1
    if (globalEW_) {
        lon9 = lon1 + cFullGlobeD;
    }

    // use our own routine to find the 4 nearest grid points
    int ix1 = int((lon_x - lon1) / dx_);  //-- index for column on the west
    int ix2 = -1; //-- index for column on the east

    // check situation when lon is extremely close to the eastern border
    if (globalEW_) {
        if (ix1 == horisPoints_) {
            if (lon_x <= lon9 && (lon9 - lon_x) <  lonDeltaNearEastBorder_) {
                ix1 =  horisPoints_ - 1;
            } else {
                ix1 = 0;
            }
        } else if (ix1 > horisPoints_) {
            return false;
        }
        ix2 = ix1 + 1;
        if (ix2 >= horisPoints_) {
            ix2 = 0;
        }
    } else {
        if (ix1 == horisPoints_ - 1) {
            if (lon_x <= lon9 &&  (lon9 - lon_x) <  lonDeltaNearEastBorder_) {
                ix1 =  horisPoints_ - 2;
        }
        } else if (ix1 > horisPoints_ - 1) {
            return false;
        }
        ix2 = ix1 + 1;
        if (ix2 > horisPoints_-1) {
            return false;
        }
    }

    // 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
    int index_x1y1 = horisPoints_ * iy1 + ix1;
    int index_x2y1 = horisPoints_ * iy1 + ix2;
    int index_x1y2 = horisPoints_ * iy2 + ix1;
    int index_x2y2 = horisPoints_ * iy2 + ix2;

    double grid_11 = valueAt(index_x1y1);
    double grid_12 = valueAt(index_x2y1);
    double grid_21 = valueAt(index_x1y2);
    double grid_22 = valueAt(index_x2y2);

    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 lon_ix2 = lon1 + ix2 * dx_;
    double lat_iy1 = (dy_ < 0 ? lat9 : lat1) + iy1 * dy_;
    double lat_iy2 = (dy_ < 0 ? lat9 : lat1) + iy2 * 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;

    if (surroundingPoints) {
        // the calling function must sort the points by distance
        surroundingPoints->push_back(MvGridPoint(grid_11, lat_iy1, lon_ix1, index_x1y1));
        surroundingPoints->push_back(MvGridPoint(grid_12, lat_iy1, lon_ix2, index_x2y1));
        surroundingPoints->push_back(MvGridPoint(grid_21, lat_iy2, lon_ix1, index_x1y2));
        surroundingPoints->push_back(MvGridPoint(grid_22, lat_iy2, lon_ix2, index_x2y2));
    }

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

    return val1 * w2 + val2 * w1;

#endif
}

bool MvLatLonGrid::isGlobalInLon() const
{
    return horisPoints_ * dx_ > cFullGlobeLimitX;
}

// ... v1 v0 v2 ...
double MvLatLonGrid::centralDiff(int idx1, int idx2, double w)
{
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);

    if (v1 == mars.grib_missing_value || v2 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }

    return (v2 - v1) / w;
}

// ... v1 v0 v2 ...
double MvLatLonGrid::centralDiffSecond(int idx0, int idx1, int idx2, double w)
{
    double v0 = valueAt(idx0);
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);

    if (v1 == mars.grib_missing_value || v2 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }

    return (v2 - 2 * v0 + v1) / w;
}

// v0 v1 v2 ....
double MvLatLonGrid::forwardDiff(int idx0, int idx1, int idx2, double w)
{
    double v0 = valueAt(idx0);
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);

    if (v1 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }
    else if (v2 == mars.grib_missing_value) {
        // first order accuracy
        return (v1 - v0) / (0.5 * w);
    }

    // second order accuracy
    return (-3 * v0 + 4 * v1 - v2) / w;
}

// v0 v1 v2 ....
double MvLatLonGrid::forwardDiffSecond(int idx0, int idx1, int idx2, int idx3, double w)
{
    double v0 = valueAt(idx0);
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);
    double v3 = valueAt(idx3);

    if (v1 == mars.grib_missing_value || v2 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }
    else if (v3 == mars.grib_missing_value) {
        // first order accuracy
        return (v0 - 2.0 * v1 + v2) / w;
    }

    // second order accuracy
    return (2.0 * v0 - 5.0 * v1 + 4.0 * v2 - v3) / w;
}

// ... v2 v1 v0
double MvLatLonGrid::backwardDiff(int idx0, int idx1, int idx2, double w)
{
    double v0 = valueAt(idx0);
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);

    if (v1 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }
    else if (v2 == mars.grib_missing_value) {
        // first order accuracy
        return (v0 - v1) / (0.5 * w);
    }

    // second order accuracy
    return (3 * v0 - 4 * v1 + v2) / w;
}

// ... v3 v2 v1 v0
double MvLatLonGrid::backwardDiffSecond(int idx0, int idx1, int idx2, int idx3, double w)
{
    double v0 = valueAt(idx0);
    double v1 = valueAt(idx1);
    double v2 = valueAt(idx2);
    double v3 = valueAt(idx3);

    if (v1 == mars.grib_missing_value || v2 == mars.grib_missing_value) {
        return mars.grib_missing_value;
    }
    else if (v3 == mars.grib_missing_value) {
        // first order accuracy
        return (v0 - 2.0 * v1 + v2) / w;
    }

    // second order accuracy
    return (2.0 * v0 - 5.0 * v1 + 4.0 * v2 - v3) / w;
}

// Compute west-east derivative (positive towards east) with
// finite difference schemes (second order accuracy)
void MvLatLonGrid::firstDerivativeX(MvGridBase* outGrd)
{
    assert(outGrd);

    // the basic weight, we need to multiply it by the
    // cosine of the latitude
    double w = 2 * cEarthRadius * dx_ * cToRadians;
    double wLat = 1.;

    for (int p = 0; p < length(); ++p) {
        // for missing value or if the row size is too short we set the
        // gradient to missing value
        if (value() == mars.grib_missing_value || horisPoints_ <= 3) {
            outGrd->setValueToMissing();
            outGrd->advance();
            advance();
            continue;
        }

        double v = mars.grib_missing_value;  // default, this will be the value on the poles

        // we are outside the pole
        if (currentLaty_ <= 90. - cGridEpsilon &&
            currentLaty_ >= -90. + cGridEpsilon) {
            wLat = cos(currentLaty_ * cToRadians) * w;

            // new row (lon band)
            if (horisPointCount_ == 0) {
                // periodic in lon - central diff
                //  west ---> east
                //    .. v1 v0 v2 ..
                if (isGlobalInLon()) {
                    v = centralDiff(currentIndex_ + horisPoints_ - 1, currentIndex_ + 1, wLat);
                }
                // western edge - we need forward difference
                //  west ---> east
                //    v0 v1 v2 ....
                else {
                    v = forwardDiff(currentIndex_, currentIndex_ + 1, currentIndex_ + 2, wLat);
                }
            }

            // end of row (lon band)
            else if (horisPointCount_ == horisPoints_ - 1) {
                // periodic in lon - central diff
                //  west ---> east
                //    .. v1 v0 v2 ...
                if (isGlobalInLon()) {
                    v = centralDiff(currentIndex_ - 1, currentIndex_ - (horisPoints_ - 1), wLat);
                }
                // eastern edge - we need backward difference
                //  west ---> east
                //    ... v2 v1 v0
                else {
                    v = backwardDiff(currentIndex_, currentIndex_ - 1, currentIndex_ - 2, wLat);
                }
            }
            // central diff
            // west ---> east
            //   .. v1 v0 v2 ...
            else {
                v = centralDiff(currentIndex_ - 1, currentIndex_ + 1, wLat);
            }
        }

        if (v == mars.grib_missing_value)
            outGrd->setValueToMissing();
        else
            outGrd->value(v);

        outGrd->advance();

        advance();
    }
}

// Compute north-south derivative (positive towards north) with
// finite difference schemes (second order accuracy)
void MvLatLonGrid::firstDerivativeY(MvGridBase* outGrd)
{
    assert(outGrd);
    bool nsScan = (dy_ < 0);
    double w = 2 * cEarthRadius * dy_ * cToRadians * ((nsScan) ? -1 : 1);

    for (int p = 0; p < length(); ++p) {
        // for missing value or if the column lenght is too short we set the
        // gradient to missing value
        if (value() == mars.grib_missing_value || horisLines_ <= 3) {
            outGrd->setValueToMissing();
            outGrd->advance();
            advance();
            continue;
        }

        double v = mars.grib_missing_value;  // default, this will be the value on the poles

        // we are outside the pole
        if (currentLaty_ <= 90. - cGridEpsilon &&
            currentLaty_ >= -90. + cGridEpsilon) {
            // N->S scan
            if (nsScan) {
                // new column - we are on the northern edge
                // backward difference
                //       north
                //   v0   ^
                //   v1   |
                //   v2   |
                //       south
                if (currentIndex_ - horisPoints_ < 0) {
                    v = backwardDiff(currentIndex_, currentIndex_ + horisPoints_, currentIndex_ + 2 * horisPoints_, w);
                }
                // bottom of the column - we are on the souther edge
                // forward difference
                //       north
                //   v2   ^
                //   v1   |
                //   v0   |
                //       south
                else if (currentIndex_ + horisPoints_ > length() - 1) {
                    v = forwardDiff(currentIndex_, currentIndex_ - horisPoints_, currentIndex_ - 2 * horisPoints_, w);
                }
                // central difference
                //       north
                //   v2   ^
                //   v0   |
                //   v1   |
                //       south
                else {
                    v = centralDiff(currentIndex_ + horisPoints_, currentIndex_ - horisPoints_, w);
                }
            }
            // S->N scan
            else {
                // new column - we are on the southern edge
                // forward difference
                //       north
                //   v2   ^
                //   v1   |
                //   v0   |
                //       south
                if (currentIndex_ - horisPoints_ < 0) {
                    v = forwardDiff(currentIndex_, currentIndex_ + horisPoints_, currentIndex_ + 2 * horisPoints_, w);
                }
                // bottom of the column - we are on the northern edge
                // backward difference
                //       north
                //   v0   ^
                //   v1   |
                //   v2   |
                //       south
                else if (currentIndex_ + horisPoints_ > length() - 1) {
                    v = forwardDiff(currentIndex_, currentIndex_ - horisPoints_, currentIndex_ - 2 * horisPoints_, w);
                }
                // central difference
                //       north
                //   v2   ^
                //   v0   |
                //   v1   |
                //       south
                else {
                    v = centralDiff(currentIndex_ - horisPoints_, currentIndex_ + horisPoints_, w);
                }
            }
        }

        if (v == mars.grib_missing_value)
            outGrd->setValueToMissing();
        else
            outGrd->value(v);

        outGrd->advance();

        advance();
    }
}

// Compute east-west second derivative (positive towards east) with
// finite difference schemes (second order accuracy)
void MvLatLonGrid::secondDerivativeX(MvGridBase* outGrd)
{
    assert(outGrd);

    // the basic weight, we need to multiply it by
    // cosine of the latitude
    double w = cEarthRadius * dx_ * cToRadians;
    double wLat = 1.;

    for (int p = 0; p < length(); ++p) {
        // for missing value or if the row size is too short we set the
        // gradient to missing value
        if (value() == mars.grib_missing_value || horisPoints_ <= 4) {
            outGrd->setValueToMissing();
            outGrd->advance();
            advance();
            continue;
        }

        double v = mars.grib_missing_value;  // default, this will be the value on the poles

        // we are outside the pole
        if (currentLaty_ <= 90. - cGridEpsilon &&
            currentLaty_ >= -90. + cGridEpsilon) {
            wLat = cos(currentLaty_ * cToRadians) * w;
            wLat *= wLat;

            // new row (lon band)
            if (horisPointCount_ == 0) {
                // periodic in lon - central diff
                //  west ---> east
                //    .. v1 v0 v2 ..
                if (isGlobalInLon()) {
                    v = centralDiffSecond(currentIndex_, currentIndex_ + horisPoints_ - 1, currentIndex_ + 1, wLat);
                }
                // western edge - we need forward difference
                //  west ---> east
                //    v0 v1 v2 v3 ....
                else {
                    v = forwardDiffSecond(currentIndex_, currentIndex_ + 1, currentIndex_ + 2, currentIndex_ + 3, wLat);
                }
            }

            // end of row (lon band)
            else if (horisPointCount_ == horisPoints_ - 1) {
                // periodic in lon - central diff
                //  west ---> east
                //    .. v1 v0 v2 ...
                if (isGlobalInLon()) {
                    v = centralDiffSecond(currentIndex_, currentIndex_ - 1, currentIndex_ - (horisPoints_ - 1), wLat);
                }
                // eastern edge - we need backward difference
                //  west ---> east
                //    ... v3 v2 v1 v0
                else {
                    v = backwardDiffSecond(currentIndex_, currentIndex_ - 1, currentIndex_ - 2, currentIndex_ - 3, wLat);
                }
            }
            // central diff
            // west ---> east
            //   .. v1 v0 v2 ...
            else {
                v = centralDiffSecond(currentIndex_, currentIndex_ - 1, currentIndex_ + 1, wLat);
            }
        }

        if (v == mars.grib_missing_value)
            outGrd->setValueToMissing();
        else
            outGrd->value(v);

        outGrd->advance();

        advance();
    }
}

// Compute north-south derivative (positive towards north) with
// finite difference schemes (second order accuracy)
void MvLatLonGrid::secondDerivativeY(MvGridBase* outGrd)
{
    assert(outGrd);
    bool nsScan = (dy_ < 0);
    // this weight is constant
    double w = cEarthRadius * dy_ * cToRadians * ((nsScan) ? -1 : 1);
    w *= w;

    for (int p = 0; p < length(); ++p) {
        // for missing value or if the column lenght is too short we set the
        // gradient to missing value
        if (value() == mars.grib_missing_value || horisLines_ <= 4) {
            outGrd->setValueToMissing();
            outGrd->advance();
            advance();
            continue;
        }

        double v = mars.grib_missing_value;  // default, this will be the value on the poles

        // we are outside the pole
        if (currentLaty_ <= 90. - cGridEpsilon &&
            currentLaty_ >= -90. + cGridEpsilon) {
            // N->S scan
            if (nsScan) {
                // new column - we are on the northern edge
                // backward difference
                //       north
                //   v0   ^
                //   v1   |
                //   v2   |
                //   v3   |
                //       south
                if (currentIndex_ - horisPoints_ < 0) {
                    v = backwardDiffSecond(currentIndex_, currentIndex_ + horisPoints_,
                                           currentIndex_ + 2 * horisPoints_, currentIndex_ + 3 * horisPoints_, w);
                }
                // bottom of the column - we are on the southern edge
                // forward difference
                //       north
                //   v3   ^
                //   v2   |
                //   v1   |
                //   v0   |
                //       south
                else if (currentIndex_ + horisPoints_ > length() - 1) {
                    v = forwardDiffSecond(currentIndex_, currentIndex_ - horisPoints_,
                                          currentIndex_ - 2 * horisPoints_, currentIndex_ - 3 * horisPoints_, w);
                }
                // central difference
                //       north
                //   v2   ^
                //   v0   |
                //   v1   |
                //       south
                else {
                    v = centralDiffSecond(currentIndex_, currentIndex_ + horisPoints_, currentIndex_ - horisPoints_, w);
                }
            }
            // S->N scan
            else {
                // new column - we are on the southern edge
                // forward difference
                //       north
                //   v3   ^
                //   v2   |
                //   v1   |
                //   v0   |
                //       south
                if (currentIndex_ - horisPoints_ < 0) {
                    v = forwardDiffSecond(currentIndex_, currentIndex_ + horisPoints_,
                                          currentIndex_ + 2 * horisPoints_, currentIndex_ + 3 * horisPoints_, w);
                }
                // bottom of the column - we are on the northern edge
                // backward difference
                //       north
                //   v0   ^
                //   v1   |
                //   v2   |
                //   v3   |
                //       south
                else if (currentIndex_ + horisPoints_ > length() - 1) {
                    v = forwardDiffSecond(currentIndex_, currentIndex_ - horisPoints_,
                                          currentIndex_ - 2 * horisPoints_, currentIndex_ - 3 * horisPoints_, w);
                }
                // central difference
                //       north
                //   v2   ^
                //   v0   |
                //   v1   |
                //       south
                else {
                    v = centralDiffSecond(currentIndex_, currentIndex_ - horisPoints_, currentIndex_ + horisPoints_, w);
                }
            }
        }

        if (v == mars.grib_missing_value)
            outGrd->setValueToMissing();
        else
            outGrd->value(v);

        outGrd->advance();
        advance();
    }
}

//------------------------------------------------------------------
// Computes grid cell area (m2 units)
//
// This is the grid layout:
// (jScanning from N->S, dy < 0)
// (iScanning: W->E, dx > 0)
// -----------------------------------------------------------------
//    lon1,lat1           lon2, lat1
//       o                     o
//
//                x lon, lat
//
//    lon1, lat2          lon2, lat2
//       o                     o
//
//  where:
//      x: the gridpoint
//      o: the cell corners
//      lat1 = lat - dy/2
//      lat2 = lat + dy/2
//      lon1 = lon - dx/2
//      lon2 = lon + dx/2
// ---------------------------------------------
// The exact formula is based on the area of a spherical segment:
//   spherical_segment_area = 4*pi*R^2*cos((lat1_rad+lat2_rad)/2)*sin((lat1_rad-lat2_rad)/2)
//   for a given cell we need to split it into 360/dx parts:
//      4*pi*R^2*cos((lat1_rad+lat2_rad)/2)*sin((lat1_rad-lat2_rad)/2)*dx/360
//   where
//      lat1_rad, lat2_rad: in radians
//      dx: in degrees
//   we know that:
//      lat2_rad - lat1_rad = dy_rad
//      (lat1_rad+lat2_rad)/2 = lat_rad
//   so we can write:
//     4*pi*R^2*cos(lat_rad)*sin(dy_rad/2)*dx/360
// An approximation of this formula:
//   if: dx_rad is small we can write: sin(dx_rad/2) = dx_rad/2
//   so we can write:
//     2*pi*R^2*cos(lat_rad)*dy_rad*dx/360 = R^2*cos(lat_rad)*dy_rad*dx_rad
//
// ----------------------------------------------
// At the Poles we use the area of a spherical cap:
//  spherical_cap_area = 2*pi*R^2*(1-sin(lat2_rad))
//  where:
//      lat2_rad = 90 - dy_rad/2 (North Pole)
//  however we need to split it into 360/dx parts since the Pole,
//  altough it is one point, is present multiple times in a lat-lon grid!
//  So we get:
//      2*pi*R^2*(1-sin(lat2_rad))*dx/360 = R^2*(1-sin(lat2_rad))*dx_rad
//

void MvLatLonGrid::gridCellArea(MvGridBase* outGrd)
{
    assert(outGrd);

    // the basic weight
    double w = fabs(2 * cEarthRadius * cEarthRadius * sin(dy_ * cToRadians / 2.) * dx_ * cToRadians);

    // the cell area on the Pole is constant
    double areaPole = fabs(cEarthRadius * cEarthRadius * (1. - sin((90 - fabs(dy_ / 2)) * cToRadians)) * dx_ * cToRadians);

    double v = 0;

    for (int p = 0; p < length(); ++p) {
        // we are outside the Pole
        if (currentLaty_ <= 90. - cGridEpsilon &&
            currentLaty_ >= -90. + cGridEpsilon) {
            // we start a new latitude row -
            // the grid is not necesarrily aligned at the Pole!
            if (horisPointCount_ == 0) {
                // cell goes beyond the Pole
                if (currentLaty_ + fabs(dy_) / 2. > 90. + cGridEpsilon ||
                    currentLaty_ - fabs(dy_) / 2. < -90 - cGridEpsilon) {
                    v = fabs(cEarthRadius * cEarthRadius * (1. - sin((90. - fabs(currentLaty_)) * cToRadians / 2.)) * dx_ * cToRadians);
                }
                else {
                    v = w * cos(currentLaty_ * cToRadians);
                }
            }

            // we are on the Pole
        }
        else {
            v = areaPole;
        }

        outGrd->value(v);
        outGrd->advance();
        advance();
    }
}

//===================================================================
//
// 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, bool nearestValid)
{
    MvLocation rLoc = rotate(lat_y, lon_x);
    MvGridPoint gp = MvLatLonGrid::nearestGridpoint(rLoc.latitude(), rLoc.longitude(), nearestValid);

    //-- 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, std::vector<MvGridPoint>* surroundingPoints)
{
    MvLocation rLoc = rotate(lat_y, lon_x);
    return MvLatLonGrid::interpolatePoint(rLoc.latitude(), rLoc.longitude(), surroundingPoints);
}


//===================================================================
//
// 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;
    }

    long jPositive = getLong("jScansPositively");  //-- points scan in +j dir
    jPositive_ = (jPositive == 1);

    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_;
    // std::cout << "R, dx, R/dx:\t" << earthRadius_ << "\t" << dx_ << "\t" << earthRadiusPerDx_ << std::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_ - cFullGlobeD;
    if ((gridLon1_ - gridVertLon_) < -180.0)
        ELON1L = gridLon1_ + cFullGlobeD;

    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 = (jPositive_) ? poleJ_ - (double)jp : poleJ_ - (double)(1 + horisLines_ - 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_ + cFullGlobeD, cFullGlobeD);

        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, std::vector<MvGridPoint> *surroundingPoints )

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


//===================================================================
//
// MvLambertAzimuthalEqualAreaGrid:
// Just uses the functionality from MvGridUsingGribIterator
// --------------------------------------------------------

MvLambertAzimuthalEqualAreaGrid::MvLambertAzimuthalEqualAreaGrid(
    field* myfield, field_state oldState, bool memoryToBeReleased) :
    MvGridUsingGribIterator(myfield, oldState, memoryToBeReleased)
{
}


//===================================================================
//
// MvMercatorGrid:
// Just uses the functionality from MvGridUsingGribIterator
// --------------------------------------------------------

MvMercatorGrid::MvMercatorGrid(
    field* myfield, field_state oldState, bool memoryToBeReleased) :
    MvGridUsingGribIterator(myfield, oldState, memoryToBeReleased)
{
}


//===================================================================
//
// 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(firstLatIndex_ + (horisLines_ / 2));  // middle line
    double dx = (lastLon - firstLon) / (numPoints - 1);
    double wrapAroundLong = dx * (numPoints);
    double epsilon = dx * 0.9;

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

    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 -= cFullGlobeD;

        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::lastLatY()
{
    if (!field_)
        return 0;  //-- must be valid message

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

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

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

// returns the latitudes of the rows above and below the current latitude row
void MvIrregularGrid::currentBoundingLats(double& lat1, double& lat2)
{
    if (currentLatIndex_ > 0)
        lat1 = latitudes_[currentLatIndex_ - 1];
    else
        lat1 = latitudes_[currentLatIndex_];

    if (currentLatIndex_ < numGlobalParallels_ - 1)
        lat2 = latitudes_[currentLatIndex_ + 1];
    else
        lat2 = latitudes_[currentLatIndex_];
}


bool MvIrregularGrid::surroundingGridpoints(double lat_y, double lon_x, std::vector<MvGridPoint>& points,
                                            bool canHaveMissing, bool doSort)
{
    if (!field_)
        return false;

    if (isSouthToNorthScanning_)
        return false;

    // longitude of 360 should be converted to 0
    if (lon_x == cFullGlobeD)
        lon_x = 0.0;

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

    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_ || row9 == lastLatIndex_+1);
    if (row9 == lastLatIndex_ + 1)  { // spill over the last row? then go back to the last row
        row9 = lastLatIndex_;
    }

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

    // it can be the case that rows are encoded with no points in them at all
    // - in this case in either of our surrounding rows, we will return no points
    if (pointsInRow1 == 0 || pointsInRow9 == 0)
        return false;

    // 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 |
    // note that it's possible to have a row with just one point, so its 'dx'
    // is not a valid concept and we set it to zero

    int moreThanOnePointInRow = (pointsInRow1 > 1);
    double dataLonWidth = (globalEW_) ? cFullGlobeD : lastLonXInArea(row1) - firstLonXInArea(row1);
    int numIntervals = (globalEW_) ? pointsInRow1 : pointsInRow1 - 1;
    double dx1 = (moreThanOnePointInRow) ? (dataLonWidth / numIntervals) : 0;  //-- N row increment

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

    double firstLon = MvLocation::normaliseLongitudeToOther(firstLonXInArea(row1), lon_x);

    // determine column on west (ix1) and east (ix2) side of the point
    int ix1 = 0;
    int ix2 = 0;
    if (moreThanOnePointInRow) {
        if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx1, pointsInRow1, ix1, ix2)) {
            return false;
        }
    }

    double grid_11 = valueAt(firstIndex + ix1);  //-- value in W pt
    double grid_12 = valueAt(firstIndex + ix2);  //-- value in E pt
    if (!canHaveMissing &&
        (grid_11 == mars.grib_missing_value || grid_12 == mars.grib_missing_value)) {
        return false;
    }

    //-- N row
    double lon_x1 = firstLon + ix1 * dx1;
    double lon_x2 = firstLon + ix2 * dx1;

    // TODO: has to be treated properly
    // if (borderRow)
    //    return val1;

    //-- => find pts on S row <=
    int firstIndexRow2 = firstIndex + pointsInRow1;
    firstLon = MvLocation::normaliseLongitudeToOther(firstLonXInArea(row9), lon_x);
    if (globalEW_ && (firstLon > lon_x))  //-- normalise acording to lon_x
        firstLon -= cFullGlobeD;

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

    int ix3 = 0;
    int ix4 = 0;
    if (moreThanOnePointInRow) {
        if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx9, pointsInRow9, ix3, ix4)) {
            return false;
        }
    }

    double grid_21 = valueAt(firstIndexRow2 + ix3);  //-- value in W pt
    double grid_22 = valueAt(firstIndexRow2 + ix4);  //-- value in E pt

    if (!canHaveMissing &&
        (grid_21 == mars.grib_missing_value || grid_22 == mars.grib_missing_value)) {
        return false;
    }

    double lon_x8 = firstLon + ix3 * dx9;
    double lon_x9 = firstLon + ix4 * dx9;

    points.push_back(MvGridPoint(grid_11, lat1, lon_x1, firstIndex + ix1));
    points.push_back(MvGridPoint(grid_12, lat1, lon_x2, firstIndex + ix2));
    points.push_back(MvGridPoint(grid_21, lat9, lon_x8, firstIndexRow2 + ix3));
    points.push_back(MvGridPoint(grid_22, lat9, lon_x9, firstIndexRow2 + ix4));

    if (doSort) {
        MvGridPoint::sortByDistance(points, MvLocation(lat_y, lon_x));
    }

    return true;
}

MvGridPoint MvIrregularGrid::nearestGridpoint(double lat_y, double lon_x, bool nearestValid)
{
    if (nearestValid) {
        std::vector<MvGridPoint> points;
        if (surroundingGridpoints(lat_y, lon_x, points, true, true)) {
            for (auto& point : points) {
                if (point.value_ != mars.grib_missing_value) {
                    return point;
                }
            }
        }
        return cMissingPoint;
    }

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

    //-- 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 (!globalNS_ && lat_y > latitudes_[firstLatIndex_])  //-- before first row in a sub-area?
        return cMissingPoint;
    if (!globalNS_ && lat_y < latitudes_[lastLatIndex_])  //-- after last row in a sub-area?
        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_ || row9 == lastLatIndex_+1);
    if (row9 == lastLatIndex_ + 1)  { // spill over the last row? then go back to the last row
        row9 = lastLatIndex_;
    }

    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 |
    // note that it's possible to have a row with just one point, so its 'dx'
    // is not a valid concept and we set it to zero

    int moreThanOnePointInRow = (pointsInRow1 > 1);
    double dataLonWidth = (globalEW_) ? cFullGlobeD : lastLonXInArea(row1) - firstLonXInArea(row1);
    int numIntervals = (globalEW_) ? pointsInRow1 : pointsInRow1 - 1;
    double dx1 = (moreThanOnePointInRow) ? (dataLonWidth / numIntervals) : 0;  //-- N row increment

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

    double firstLon = MvLocation::normaliseLongitudeToOther(firstLonXInArea(row1), lon_x);

    // determine column on west (ix1) and east (ix2) side of the point
    bool northJustOutSide = false;
    int ix1 = 0;
    int ix2 = 0;
    if (moreThanOnePointInRow) {
        if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx1, pointsInRow1, ix1, ix2)) {
            if (!globalEW_ &&
                ix1 == pointsInRow1-1 && ix2 == pointsInRow1) {
                ix1--;
                ix2--;
                northJustOutSide = true;
            } else {
                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)  // border row - no need to look at another row
    {
        gridPoint[0].loc_.ensureLongitudeBelow360();
        return gridPoint[0];
    }

    //-- => find pts on S row <= --//
    firstIndex += pointsInRow1;
    firstLon =  MvLocation::normaliseLongitudeToOther(firstLonXInArea(row9), lon_x);
    int pointsInRow9 = pointsInRow(row9);
    moreThanOnePointInRow = (pointsInRow9 > 1);

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

    // determine column on west (ix1) and east (ix2) side of the point
    ix1 = 0;
    ix2 = 0;
    if (moreThanOnePointInRow) {
        if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx9, pointsInRow9, ix1, ix2)) {
            if (!globalEW_) {
                if (!northJustOutSide && ix1 == pointsInRow9-1 && ix2 == pointsInRow9) {
                    ix1--;
                    ix2--;
                } else {
                    return cMissingPoint;
                }
            } else {
                return  cMissingPoint;
            }
        }
    }

    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() >= cFullGlobeD)
        gridPoint[0].loc_.set(gridPoint[0].loc_.latitude(),
                              gridPoint[0].loc_.longitude() - cFullGlobeD);


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

   return gridPoint[0];
}
//------------------------------------------------------

double MvIrregularGrid::interpolatePoint(double lat_y, double lon_x, std::vector<MvGridPoint>* surroundingPoints)
{
    if (isSouthToNorthScanning_) {
        marslog(LOG_INFO, "Nearest grid point used (no interpolation yet for S->N Gaussian grids)");
        return nearestGridpoint(lat_y, lon_x, false).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, surroundingPoints);  ////  XXXX don't forget to do this!!!
        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_);

    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_) ? cFullGlobeD : 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 = MvLocation::normaliseLongitudeToOther(firstLonXInArea(row1), lon_x);
//    std::cout  << std::setprecision(20) << "term1= " << lon_x - firstLon << " term2=" << (lon_x - firstLon) / dx1 << std::endl;

    // determine column on west (ix1) and east (ix2) side of the point
    int ix1 = 0;
    int ix2 = 0;
    if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx1, pointsInRow1, ix1, ix2)) {
         return DBL_MAX;
    }

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

//    std::cout  << "lat1=" << lat1 << " pointsInRow1=" << pointsInRow1 << " ix1=" << ix1 << " ix2=" << ix2 <<
//                  " grid_11=" << grid_11 << " grid_12=" << grid_12 << std::endl;

    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 lon_x2 = firstLon + ix2 * 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 <=
    int firstIndexRow2 = firstIndex + pointsInRow1;

    firstLon = MvLocation::normaliseLongitudeToOther(firstLonXInArea(row9), lon_x);

    int pointsInRow9 = pointsInRow(row9);

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

    // determine column on west (ix3) and east (ix4) side of the point
    int ix3 = -1;
    int ix4 = -1;
    if (!computeLonIndex(lon_x, firstLon, dataLonWidth, dx9, pointsInRow9, ix3, ix4)) {
         return DBL_MAX;
    }

    double grid_21 = valueAt(firstIndexRow2 + ix3);  //-- value in W pt
    double grid_22 = valueAt(firstIndexRow2 + ix4);  //-- value in E pt

//    std::cout << "lat9=" << lat9 << " pointsInRow9=" << pointsInRow9 << " ix3=" << ix3 << " ix4=" << ix4 <<
//                     " grid_21=" << grid_21 << " grid_22=" << grid_22 << std::endl;

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

    if (surroundingPoints) {
        double lon_x8 = firstLon + ix3 * dx9;
        double lon_x9 = firstLon + ix4 * dx9;

        // the calling function wants a list of the surrounding grid points sorted by distance
        surroundingPoints->push_back(MvGridPoint(grid_11, lat1, lon_x1, firstIndex + ix1));
        surroundingPoints->push_back(MvGridPoint(grid_12, lat1, lon_x2, firstIndex + ix2));
        surroundingPoints->push_back(MvGridPoint(grid_21, lat9, lon_x8, firstIndexRow2 + ix3));
        surroundingPoints->push_back(MvGridPoint(grid_22, lat9, lon_x9, firstIndexRow2 + ix4));
    }

    //-- interpolate on S row
    double lon_x9 = firstLon + ix3 * dx9;

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

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

// determines west (idx1) and east (idx2) gridpoint index for the specified lon_x value.
bool MvIrregularGrid::computeLonIndex(double lon_x, double firstLon, double dataLonWidth, double dx, int pointsInRow, int& idx1, int& idx2) const
{
    idx1 = -1;
    idx2 = -1;

    idx1 = int((lon_x - firstLon) / dx);

    // check situation when lon is extremely close to the eastern border
    if (globalEW_) {
        if (idx1 == pointsInRow &&
            lon_x <= firstLon + dataLonWidth &&
            std::fabs(dataLonWidth - (lon_x - firstLon)) <  lonDeltaNearEastBorder_) {
                idx1--;
        }
        idx2 = (idx1 == (pointsInRow - 1)) ? 0: idx1 + 1;
        if (idx1 >= pointsInRow || idx2 > pointsInRow) {
            return false;
        }
    } else {
        if (idx1 == pointsInRow-1 &&
            lon_x <= firstLon + dataLonWidth &&
            std::fabs(dataLonWidth - (lon_x - firstLon)) <  lonDeltaNearEastBorder_) {
               idx1--;
        }
        idx2 = idx1 + 1;
        if (idx1 > pointsInRow-2 || idx2 > pointsInRow) {
            return false;
        }
    }

    return (idx1 >= 0 && idx2 >= 0);
}

// see the description at MvLatLonGrid::gridCellArea
void MvIrregularGrid::gridCellArea(MvGridBase* outGrd)
{
    assert(outGrd);
    assert(currentLatIndex_ == firstLatIndex_);

    // the basic weight for spherical segments
    double w = 2 * cEarthRadius * cEarthRadius;

    // the area around the Poles is a spherical cap
    double areaPole = 0.;
    if (globalNS_ && numGlobalParallels_ > 1) {
        assert(currentLatIndex_ == 0 ||
               currentLatIndex_ == numGlobalParallels_ - 1);

        double lat1 = latitudes_[0];
        double lat2 = latitudes_[1];
        double latCap = (fabs(lat1) + fabs(lat2)) / 2.;
        areaPole = fabs(cEarthRadius * cEarthRadius * (1. - sin(latCap * cToRadians)) * dx_ * cToRadians);
    }

    double v = 0;

    for (int p = 0; p < length(); ++p) {
        // global grid in NS -  and we are next to the Poles
        if (globalNS_ &&
            (currentLatIndex_ == firstLatIndex_ ||
             currentLatIndex_ == lastLatIndex_)) {
            v = areaPole;

            // non-global grid in NS or we are outside the Pole
        }
        else {
            // we start a new latitude row
            if (horisPointCount_ == 0) {
                double lat_b1, lat_b2;
                currentBoundingLats(lat_b1, lat_b2);
                double lat1 = (currentLaty_ + lat_b1) / 2;
                double lat2 = (currentLaty_ + lat_b2) / 2;
                v = w * cos(((lat1 + lat2) / 2.) * cToRadians) * sin((fabs(lat1 - lat2) / 2.) * cToRadians) * dx_ * cToRadians;
            }
        }

        outGrd->value(v);
        outGrd->advance();
        advance();
    }
}

void MvIrregularGrid::boundingBox(std::vector<double>& v)
{
    v.push_back(lastLatY());
    v.push_back(firstLonX());
    v.push_back(firstLatY());
    v.push_back(lastLonX());
    if (globalNS_) {
        v[0] = -90;
        v[2] = 90;
    }
    if (globalEW_) {
        v[1] = -180;
        v[3] = 180;
    }

    if (v[1] > 180 && v[3] > 180) {
        v[1] = v[1] - cFullGlobeD;
        v[3] = v[3] - cFullGlobeD;
    }
}

//===================================================================
//
// 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_ ? (cFullGlobeD / 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 - ecCodes: '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_ = (cFullGlobeD / horisPoints_);
        horisPointCount_ = 0;
        // cout << currentIndex_ << '\t' << currentLaty_ << '\t' << horisPoints_ << std::endl;
    }
    else {
        currentLonx_ += dx_;
    }

    return true;
}


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


int GaussianLatitudes::latitudes(long trunc, double* lats, unsigned int numLatitudes)
{
    int err = 0;
    std::map<long, std::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) {
            std::vector<double> vec(lats, lats + numLatitudes);
            cachedLatitudes_[trunc] = vec;
        }
    }
    else  // yes, it has been cached, so just copy it over
    {
        std::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
        {
            std::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'!
    }
}
//------------------------------------------------------

// extract surrounding gridpoints when target location is outside the grid
// (i.e. north or south polar cap).
bool MvGaussianGridBase::surroundingGridpointsForExtrapolation(double lat_y, double lon_x, std::vector<MvGridPoint>& points,
                                                               bool canHaveMissing, bool doSort)
{
    // Initialize variablesCompute the initial position and npoints in the data array
    int firstIndex;  // first position in the data array
    int np;          // number of points along the latitude
    double gLat;

    // north pole
    if (lat_y > latitudes_[0]) {
        np = pointsInRow(0);
        gLat = latitudes_[0];
        firstIndex = 0;

        // south pole
    }
    else if (lat_y < latitudes_[horisLines_ - 1]) {
        np = pointsInRow(horisLines_ - 1);
        gLat = latitudes_[horisLines_ - 1];
        firstIndex = length() - np;
    }
    else {
        marslog(LOG_EROR, "Point is inside the area, it can not be extrapolated");
        return false;
    }

    double dx = cFullGlobeD / np;         //-- N row increment
    double startLon = MvLocation::normaliseLongitudeToOther(firstLonX(), lon_x);  //-- first longitude value

    // reverse order so that we could get the same nearest point as with nearest_gridpoint!!
    for (int i = np - 1; i >= 0; i--) {
        double gLon = startLon + i * dx;
        int idx = firstIndex + i;
        double val = valueAt(idx);
        if (!canHaveMissing && val == mars.grib_missing_value)
            return false;
        else
            points.push_back(MvGridPoint(val, gLat, gLon, idx));
    }

    if (doSort) {
        MvGridPoint::sortByDistance(points, MvLocation(lat_y, lon_x));
    }

    return true;
}

double MvGaussianGridBase::extrapolatePoint(double lat_y, double lon_x, std::vector<MvGridPoint>* surroundingPoints)
{
    // 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 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
    double gLat;
    if (lat_y > latitudes_[0])  // north pole
    {
        np = pointsInRow(0);
        dy = 90. - latitudes_[0];
        wlat = (lat_y - latitudes_[0]) / dy;
        gLat = latitudes_[0];
        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;
        gLat = latitudes_[horisLines_ - 1];
        firstIndex = length() - np;
    }
    else {
        marslog(LOG_EROR, "Point is inside the area, it can not be extrapolated");
        return DBL_MAX;
    }

    // if we only need the surrounding points
    if (surroundingPoints) {
        double dx = cFullGlobeD / np;         //-- N row increment
        double startLon = MvLocation::normaliseLongitudeToOther(firstLonX(), lon_x);  //-- first longitude value

        for (int i = 0; i < np; i++) {
            double gLon = startLon + i * dx;
            int idx = firstIndex + i;
            surroundingPoints->push_back(MvGridPoint(valueAt(idx), gLat, gLon, idx));
        }
    }

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

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

    auto pointsInCurrentRow = pointsInRow(row);

    // Retrieve the 2 nearest points along the first/last latitude line
    double dx1 = cFullGlobeD / pointsInCurrentRow;  //-- N row increment
    double firstLon = MvLocation::normaliseLongitudeToOther(firstLonX(), lon_x);          //-- first longitude value

    //-- columns W (ix1) and E (ix2) of pt
    int ix1 = 0;
    int ix2 = 0;
    if (!computeLonIndex(lon_x, firstLon, cFullGlobeD, dx1, pointsInCurrentRow, ix1, ix2)) {
        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 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:
// ---------------------

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

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

    // this sets the current dx_ as well
    horisPoints_ = pointsInRow(firstLatIndex_);
}

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

double MvReducedGaussianGrid::firstLonXInArea(int row)
{
    assert(row >= firstLatIndex_);

    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
    long numPts = 0;

    if (!globalEW_) {
        double lonlast = 0;
        int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row - firstLatIndex_] : 0;
        grib_get_reduced_row_p(globalPointsInThisRow, firstLonX(), lastLonX(), &numPts, &firstLonXInArea, &lonlast);
    }

    return firstLonXInArea;
}
//------------------------------------------------------

double MvReducedGaussianGrid::lastLonXInArea(int row)
{
    assert(row >= firstLatIndex_);

    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;
        double firstLonXInArea;
        int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row - firstLatIndex_] : 0;
        grib_get_reduced_row_p(globalPointsInThisRow, firstLonX(), lastLonX(), &numPts, &firstLonXInArea, &lastLonXInArea);
    }

    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_);
        // horisPoints_ = field_->ksec2[ cNPtsIndexBegin + currentLatIndex_ ];
        horisPoints_ = pointsInRow(currentLatIndex_);
        // dx_          = ( 360.0 / horisPoints_ );
        horisPointCount_ = 0;
        // std::cout << currentIndex_ << '\t' << currentLaty_ << '\t' << horisPoints_ << std::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)
{
    assert(row >= firstLatIndex_);

    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 - ecCodes: 'pl' not found!");
    }

    //--
    //-- row: 0,1,2,...
    //--
    int globalPointsInThisRow = pointsInRow_ ? pointsInRow_[row - firstLatIndex_] : 0;
    dx_ = (cFullGlobeD / globalPointsInThisRow);
    if (globalEW_)
        return globalPointsInThisRow;
    else {
        long numPts = 0;
        double firstLonXInArea, lonlast;
        grib_get_reduced_row_p(globalPointsInThisRow, firstLonX(), lastLonX(),
                               &numPts, &firstLonXInArea, &lonlast);

        return numPts;

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

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

#define SATHROW true

MvSatelliteImage::MvSatelliteImage(field* myfield, field_state oldState, bool memoryToBeReleased) :
    MvGridUsingGribIterator(myfield, oldState, memoryToBeReleased)
{
    nontested_eccodes_port("MvSatelliteImage()");

    gridType_ = getString("typeOfGrid");

    // Initialize Structures
    pimin1_();

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

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

    // Initialize Satellite projection
    double satnr = getDouble("NrInRadiusOfEarthScaled", 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) / satdy;
    double SPrj = 2.0 * asin(1.0 / satnr) / 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 = satnr * 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) / satdx) * (satnr - 1.0)) * EARTH_RADIUS;

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

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

    double bury = -saty0 * atan(tan(2.0 * asin(1.0 / satnr) / satdy) * (satnr - 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_);
}

MvSatelliteImage::~MvSatelliteImage() = default;

// static int ftest=0;
MvGridPoint MvSatelliteImage::nearestGridpoint(double lat_y, double lon_x, bool /*nearestValid*/)
{
    //--
    //-- 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++)
       std::cout << ii << " " << hist[ii] << std::endl;
   std::cout << "rmin=" << rmin << " index=" << imin << std::endl;
   std::cout << "rmax=" << rmax << " index=" << imax << std::endl;
   std::cout << "min=" << rmean/(double)size << std::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, std::vector<MvGridPoint>* /*surroundingPoints*/)
{
    // For Satellite images the nearest neighbour interpolation is used
    MvGridPoint gp = nearestGridpoint(lat_y, lon_x, false);

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


//===================================================================
//
// 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
    // ecCodes's grib_nearest_find() function for interpolation purposes.

    if (expandGrid)
        set_field_state(myfield, expand_mem);  // (anything) -> expand_mem
    // TODO: oldState can also be unknown!!!!!
    else if (oldState == packed_file)
        set_field_state(myfield, packed_mem);  // packed_file -> packed_mem

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

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

    std::string myGridType(strbuf);

    MvGridBase* grid = nullptr;

    if (myGridType == cLatLonGrid)
        grid = new MvLatLonGrid(myfield, oldState, memoryToBeReleased);
    else if (myGridType == cGaussianGrid)
        grid = new MvGaussianGrid(myfield, oldState, memoryToBeReleased);
    else if (myGridType == cGaussianReducedGrid)
        grid = new MvReducedGaussianGrid(myfield, oldState, memoryToBeReleased);

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

        if (!reducedGrid->globalEW_) {
            delete reducedGrid;
            grid = new MvUnimplementedGrid(myfield, oldState);
        }
        else {
            grid = reducedGrid;
        }
    }
    else if (myGridType == cSatelliteImage)
        grid = new MvSatelliteImage(myfield, oldState, memoryToBeReleased);
    else if (myGridType == cLambertGrid)
        grid = new MvLambertGrid(myfield, oldState, memoryToBeReleased);
    else if (myGridType == cLambertAzEqGrid)
        grid = new MvLambertAzimuthalEqualAreaGrid(myfield, oldState, memoryToBeReleased);
    else if (myGridType == cMercatorGrid)
        grid = new MvMercatorGrid(myfield, oldState, memoryToBeReleased);

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


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

    if (grid == nullptr)
        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 + cFullGlobeD;  //-- force 0 < e <= 360

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

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

    //	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
{
    // NOTE: in theory, this function could test for missing lat/lon values,
    // but in practice these are very large values and will always fall outside
    // of the domain, so we can save the expense of checking for these explicity

    if (lat_y > n || lat_y < s)
        return 0;  //-- outside n-s band!

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

    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
