/***************************** 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 *************************************/

// MvGeoPoints.cc,   apr03/vk


#include "MvGeoPoints.h"
#include "MvLocation.h"
#include "MvMiscelaneous.h"
#include "mars.h"
#include "inc_stl.h"
#include "Tokenizer.h"

// the precision with which we write geopoints values
#define VALUE_PRECISION  (10)

//_____________________________________________________________________

MvGeoP1::MvGeoP1() :
  gfmt_     ( eGeoTraditional ),
  latitude_ ( 0 ),
  longitude_( 0 ),
  height_   ( 0 ),
  date_     ( 0 ),
  time_     ( 0 ),
  value_    ( 0 ),
  strValue_ ( "" ),  //-- Doxygen comments!
  value2_   ( 0 )
{
}
//_____________________________________________________________________

MvGeoP1::MvGeoP1( const MvGeoP1& in )
{
  _copy( in );
}
//_____________________________________________________________________
void
MvGeoP1::_copy( const MvGeoP1& in )
{
  gfmt_      = in.gfmt_;
  latitude_  = in.lat_y();
  longitude_ = in.lon_x();
  height_    = in.height();
  date_      = in.date();
  time_      = in.time();
  value_     = in.value();
  strValue_  = in.strValue();
  value2_    = in.direc();
}
//_____________________________________________________________________

MvGeoP1&
MvGeoP1::operator = ( const MvGeoP1& in )
{
  _copy( in );
  return *this;
}
//_____________________________________________________________________

bool
MvGeoP1::operator == ( const MvGeoP1& in )
{
  if( gfmt_      != in.gfmt_      ) return false;
  if( latitude_  != in.latitude_  ) return false;
  if( longitude_ != in.longitude_ ) return false;
  if( height_    != in.height_    ) return false;
  if( date_      != in.date_      ) return false;
  if( time_      != in.time_      ) return false;
  if( value_     != in.value_     ) return false;
  if( strValue_  != in.strValue_  ) return false;
  if( value2_    != in.value2_    ) return false;
  return true;
}

//_____________________________________________________________________
// Used in the sort() function. We need to compare the values as well, otherwise
// the remove_duplicates() function will not work correctly (it compares values
// of adjacent points, so if there are multiple co-located points with different
// values, they might not be detected as duplicates unless the sorting takes
// the values into account).
bool
MvGeoP1::operator <  ( const MvGeoP1& in ) const
{
    if( latitude_ != in.latitude_ )
        return latitude_ > in.latitude_;      //-- from North to South

    if( longitude_ != in.longitude_ )
       return longitude_ < in.longitude_;     //-- from West to East

    if(height_ != in.height_ )
       return height_ < in.height_;

    if(value_ != in.value_ )
       return value_ < in.value_;

    return false;
}

//_____________________________________________________________________
// Needed for GeoSubsampleFunction in bufr.cc - we can't just use the '<'
// operator, because it also compares the value, which we do not care about
// in GeoSubsampleFunction
bool
MvGeoP1::latLonHeightBefore ( const MvGeoP1& in ) const
{
    if( latitude_ != in.latitude_ )
        return latitude_ > in.latitude_;      //-- from North to South

    if( longitude_ != in.longitude_ )
       return longitude_ < in.longitude_;     //-- from West to East

    if(height_ != in.height_ )
       return height_ < in.height_;

    return false;
}


//_____________________________________________________________________
void
MvGeoP1::extract( const char* line, eGeoFormat &geoFmt )
{
  char  buf[ 255 ];               //-- for numeric/string check

  gfmt_ = geoFmt;

  istringstream myInput( line );
  if( gfmt_ == eGeoXYV )	  //-- XYV is 'X_lon,Y _lat, Val' format
    {
      myInput >> longitude_ >> latitude_ >> buf;

      _stringOrNumber( buf ); // here it would not make sense to update the gobal format
                              // as eGeoString is assumed to have 6 columns

      height_ = 0;
      date_   = time_  =  0;
      value2_ = 0;
    }
  else
    {
      double   d_date;            //-- in case user has floating point valued dates
      double   d_time;

      if( hasVector() )           //-- polar or XY vector?
      {
        myInput >> latitude_ >> longitude_ >> height_ >> d_date >> d_time >> value_ >> value2_;
      }
      else			  //-- TRADITIONAL: Lat/Lon/lev/dat/tim/Val
      {
        myInput >> latitude_ >> longitude_ >> height_ >> d_date >> d_time >> buf;

        _stringOrNumber( buf );
      
        if (gfmt_ == eGeoString)  // update the 'global' geo format?
            geoFmt = eGeoString;

        value2_ = 0;
      }

      date_ = (long)d_date;
      time_ = (long)d_time;
    }
}

//_____________________________________________________________________
string
MvGeoP1::column( int col, int& type )
{
	type = eGeoVDouble;
	if ( gfmt_ == eGeoTraditional || gfmt_ == eGeoString )
	{
		if ( col == 1 )
			return metview::toString(lat_y());
		else if ( col == 2 )
			return metview::toString(lon_x());
		else if ( col == 3 )
			return metview::toString(height());
		else if ( col == 4 )
		{
			type = eGeoVLong;
			return metview::toString(date());
		}
		else if ( col == 5 )
		{
			type = eGeoVLong;
			return metview::toString(time());
		}
		else if ( col == 6 )
			return metview::toString(value());
		else
			return string("VALUE NOT DEFINED");
	}
	else if ( gfmt_ == eGeoXYV )
	{
		if ( col == 1 )
			return metview::toString(lon_x());
		else if ( col == 2 )
			return metview::toString(lat_y());
		else if ( col == 3 )
			return metview::toString(value());
		else
			return string("VALUE NOT DEFINED");
	}
	else if ( gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY )
	{
		if ( col == 1 )
			return metview::toString(lat_y());
		else if ( col == 2 )
			return metview::toString(lon_x());
		else if ( col == 3 )
			return metview::toString(height());
		else if ( col == 4 )
		{
			type = eGeoVLong;
			return metview::toString(date());
		}
		else if ( col == 5 )
		{
			type = eGeoVLong;
			return metview::toString(time());
		}
		else if ( col == 6 )
			return metview::toString(value());
		else if ( col == 7 )
			return metview::toString(value2());
		else
			return string("VALUE NOT DEFINED");
	}

	return string("FORMAT NOT DEFINED");
}

//_____________________________________________________________________
void
MvGeoP1::_stringOrNumber( char* buf )
{
  bool isNumeric = true;
  int  dcnt = 0;
  char* p   = buf;

  if( *p == '-' || *p == '+' )         //-- sign is OK
     ++p;

  if( *p && isalpha( *p ) )
   {
     isNumeric = false;                //-- cannot be a number
   }
  else
   {
     dcnt = _countDigits( p );         //-- (leading) digits?
     if( dcnt == 0 && *p != '.' )      //-- 0 digits => only decimal point is OK
        isNumeric = false;

     if( isNumeric && *p == '.' )
      {
        ++p;
        dcnt += _countDigits( p );     //-- trailing digits?
        if( dcnt == 0 )
           isNumeric = false;          //-- decimal point without digits
      }

     if( isNumeric && ( *p == 'e' || *p == 'E' ) )
      {
        ++p;
        if( *p == '-' || *p == '+' )   //-- exponent sign is OK
           ++p;
         if( _countDigits( p ) == 0 )
           isNumeric = false;          //-- digits must follow
      }

     if( isNumeric && *p && isgraph( *p ) )
        isNumeric = false;             //-- must not follow by a printable char
   }

  if( isNumeric )
    {
      value_ = atof( buf );            //-- is numeric: convert!
    }
  else
    {
      strValue_ = buf;                 //-- is string: copy!
      value_    = 0;
      gfmt_     = eGeoString;
    }
}

//_____________________________________________________________________
int
MvGeoP1::_countDigits( char*& p )
{
  int dcnt = 0;

  while( p && *p )
   {
     if( isdigit( *p ) )
          ++dcnt;
     else
          break;

     ++p;
   }

  return dcnt;
}

//_____________________________________________________________________
bool
MvGeoP1::sameLocation( const MvGeoP1& in )
{
  return latitude_ == in.lat_y() && longitude_ == in.lon_x() && height_ == in.height_;
}

//_____________________________________________________________________
void
MvGeoP1::location( double lat, double lon )
{
  while( lon < -180 )
     lon += 360;
  while( lon > 360 )
     lon -= 360;
  longitude_ = lon;

  if( lat > 90 )
  {
     marslog(LOG_INFO, "Geopoint latitude value %g forced to be 90", lat );
     lat = 90;
  }
  if( lat < -90 )
  {
     marslog(LOG_INFO, "Geopoint latitude value %g forced to be -90", lat );
     lat = -90;
  }
  latitude_ = lat;
}

//_____________________________________________________________________

ostream& operator<< ( ostream& aStream, const MvGeoP1& gp )
{
  const char cStartOfLine[] = " ";
  const char cSeparator[]   = "\t";


  // General note about precision settings: we must be careful if a
  // user-callable function to set the precision is created. This is
  // because we need to ensure that missing values are still correctly
  // written and read. See the value defined for missing values in
  // MvGeoPoints.h to see how many decimal places are required for
  // faithful reading and writing of missing values.
  // See also MvGeoPoints::write, as this also uses the precision value.


  aStream << cStartOfLine;

  int myOldPrec = aStream.precision(); //-- store current floating point precision
  aStream.precision( 7 );              //-- default of 6 digits may not be enough

  if( gp.format() == eGeoXYV )
    {
      aStream << gp.lon_x() << cSeparator
	      << gp.lat_y() << cSeparator;
    }
  else
    {
      aStream << gp.lat_y()  << cSeparator
              << gp.lon_x()  << cSeparator
	      << gp.height() << cSeparator
	      << gp.date()   << cSeparator
              << gp.time()   << cSeparator;
    }

  aStream.precision( VALUE_PRECISION );  //-- value may need even more precision

  switch( gp.format() )
    {
    case eGeoTraditional:
    case eGeoXYV:
      aStream << gp.value();
      break;

    case eGeoString:
      aStream << gp.strValue().c_str();
      break;

    case eGeoVectorPolar:
    case eGeoVectorXY:
      aStream << gp.speed() << cSeparator
	      << gp.direc();
      break;
    }

  aStream.precision( myOldPrec );     //-- revert back to original precision

  return aStream;
}

//_____________________________________________________________________

MvGeoPoints::MvGeoPoints() :
  gfmt_(eGeoTraditional),
  pts_( 0 ),
  count_( 0 ),
  path_( "/file/name/not/given" ),
  dbSystem_(""),
  dbPath_("")
{
	this->setFormat();
	metadata_.clear();
}

//_____________________________________________________________________

MvGeoPoints::MvGeoPoints( long count ) :
  gfmt_(eGeoTraditional),
  count_( count ),
  path_( "/file/name/not/given" ),
  dbSystem_(""),
  dbPath_("")
{
	pts_  = new MvGeoP1[ count_ ];
	this->setFormat();
	metadata_.clear();
}

//_____________________________________________________________________

MvGeoPoints::MvGeoPoints( const MvGeoPoints& gp )
{
	_copy( gp );
}

//_____________________________________________________________________

MvGeoPoints::MvGeoPoints( const char* name, const int nmax ) :
  pts_( 0 ),
  count_( 0 )
{
  path_ = name;
  load( nmax );
}

//_____________________________________________________________________

MvGeoPoints::~MvGeoPoints()
{
  delete [] pts_;
}
//_____________________________________________________________________
void
MvGeoPoints::_copy( const MvGeoPoints& gp )
{
  unload();

  gfmt_  = gp.format();
  count_ = gp.count();
  sgfmt_ = gp.format();
  ncols_ = gp.ncols();
  scols_ = gp.scols();
  dbSystem_ = gp.dbSystem();
  dbColumn_ = gp.dbColumn();
  dbColumnAlias_ = gp.dbColumnAlias();
  dbPath_ = gp.dbPath();
  dbQuery_ = gp.dbQuery();
  metadata_ = gp.metadataConst();

  if( count_ > 0 )
    {
      pts_ = new MvGeoP1[ count_ ];
      for( int p=0; p<count_; ++p )
	{
	  pts_[p] = gp[p];
	}
    }
  else
      pts_ = 0;
}

//_____________________________________________________________________
MvGeoPoints&
MvGeoPoints::operator = ( const MvGeoPoints& gp )
{
  if( &gp == this )
    return *this;

  unload();
  _copy( gp );

  return *this;
}

//_____________________________________________________________________
void
MvGeoPoints::newReservedSize( long size )
{
  unload();
  count_ = size;
  pts_   = new MvGeoP1[ count_ ];
}
//_____________________________________________________________________
void
MvGeoPoints::format( eGeoFormat fmt )
{
  gfmt_ = fmt;

  if( count() > 0 )
    {
      for( int p=0; p<count(); ++p )
	{
	  pts_[ p ].format( fmt );     //-- change format for each point
	}
    }
}

//_____________________________________________________________________
bool
MvGeoPoints::load( const char* path )
{
  if( path_ != path && count_ > 0 )
    unload();                          //-- unload if different data exist

  path_ = path;

  return load();
}

//_____________________________________________________________________
bool
MvGeoPoints::load(const int nmax)
{
   if( count_ )
       return true;

   ifstream f( path_.c_str() );
   if( ! f )
   {
       marslog(LOG_EROR, "Could not open geopoints file: %s", path_.c_str());
       return false;
   }
   
   return load(f);
}

//_____________________________________________________________________
bool
MvGeoPoints::load( ifstream &f, const int nmax )
{
   char line[1024];
   int  n = 0;
   streampos sp = f.tellg();


   if ( nmax == 0 )
   {
      //-- first count the lines
      while( f.getline( line, sizeof(line) ) )
           n++;
   }
   else
      n = nmax;

   unload();
   pts_ = new MvGeoP1[ n ];

   f.clear();
   f.seekg(sp);
   //f.seekg(0, ios::beg);

   gfmt_ = eGeoTraditional;
   n     = 0;

   bool metadata = false;
   bool db_info  = false;
   bool db_query = false;

   while( f.getline( line, sizeof(line) ) )
   {
      if( strncmp( line, "#DATA", 5 ) == 0 )
      {
          break;
      }
      else if( strncmp( line, "#FORMAT ", 8 ) == 0 )
      {
         const char* fp = line+7;
         while( fp && *fp == ' ' )
             ++fp;

         if( strncmp( fp, "POLAR_VECTOR", 12 ) == 0 )
         {
            gfmt_ = eGeoVectorPolar;	      //-- polar vector extension
         }
         else if( strncmp( fp, "XY_VECTOR", 9 ) == 0 )
         {
            gfmt_ = eGeoVectorXY;           //-- cartesian vector extension
         }
         else if( strncmp( fp, "XYV", 3 ) == 0 )
         {
            gfmt_ = eGeoXYV;		      //-- "French" extension
         }
         else if( strncmp( fp, "LLV", 3 ) == 0 )
         {
            gfmt_ = eGeoXYV;		      //-- old name for XYV
         }
         else
         {
            marslog(LOG_EROR, "Unknown geopoints format: %s", fp );
         }

         // Set format info
         this->setFormat();
      }

      else if(strncmp(line, "#METADATA", 9) == 0 )
      {
         metadata = true;
         db_info  = false;
      }

      else if(metadata == true)
      {
         string sbuf(line);
         vector<string> sv;
         Tokenizer parse("=");
         parse(sbuf,sv);
         if (sv.size() == 2)
            metadata_[sv[0]] = sv[1];  // store this line of metadata
      }



     //Information about the database, query etc. that
        //generated the geopoints file
      else if(strncmp( line, "#DB_INFO ", 8 ) == 0 )
      {
         db_info  = true;
         metadata = false;
      }

      else if(db_info == true && strstr(line, "DB_SYSTEM:") != 0 )
      {
         string  sbuf(line);
         string::size_type pos=sbuf.find("DB_SYSTEM:");
         sbuf = sbuf.substr(pos+10);
         dbSystem_=sbuf;
      }

      else if(db_info == true && strstr(line, "DB_COLUMN:") != 0 )
      {
          string  sbuf(line);
          string::size_type pos=sbuf.find("DB_COLUMN:");
          sbuf = sbuf.substr(pos+10);
          vector<string> sv;

          Tokenizer parse(";");
          parse(sbuf,sv);

          if( gfmt_ == eGeoTraditional && sv.size() == 6)
          {
             dbColumn_["lat"]=sv[0];
             dbColumn_["lon"]=sv[1];
             dbColumn_["level"]=sv[2];
             dbColumn_["date"]=sv[3];
             dbColumn_["time"]=sv[4];
             dbColumn_["value"]=sv[5];
          }
          else if( gfmt_ == eGeoXYV && sv.size() == 3)
          {
             dbColumn_["lon"]=sv[0];
             dbColumn_["lat"]=sv[1];
             dbColumn_["value"]=sv[2];
          }
          else if( (gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY) && sv.size() == 7)
          {
             dbColumn_["lat"]=sv[0];
             dbColumn_["lon"]=sv[1];
             dbColumn_["level"]=sv[2];
             dbColumn_["date"]=sv[3];
             dbColumn_["time"]=sv[4];
             dbColumn_["value"]=sv[5];
             dbColumn_["value2"]=sv[6];
          }
      }

      else if(db_info == true && strstr(line, "DB_COLUMN_ALIAS:") != 0 )
      {
         string  sbuf(line);
         string::size_type pos=sbuf.find("DB_COLUMN_ALIAS:");
         sbuf = sbuf.substr(pos+16);

         vector<string> sv;
         Tokenizer parse(";");
         parse(sbuf,sv);

         if( gfmt_ == eGeoTraditional && sv.size() == 6)
         {
            dbColumnAlias_["lat"]=sv[0];
            dbColumnAlias_["lon"]=sv[1];
            dbColumnAlias_["level"]=sv[2];
            dbColumnAlias_["date"]=sv[3];
            dbColumnAlias_["time"]=sv[4];
            dbColumnAlias_["value"]=sv[5];
         }
         else if( gfmt_ == eGeoXYV && sv.size() == 3)
         {
            dbColumnAlias_["lon"]=sv[0];
            dbColumnAlias_["lat"]=sv[1];
            dbColumnAlias_["value"]=sv[2];
         }
         else if( (gfmt_ == eGeoVectorPolar || gfmt_ == eGeoVectorXY) && sv.size() == 7)
         {
            dbColumnAlias_["lat"]=sv[0];
            dbColumnAlias_["lon"]=sv[1];
            dbColumnAlias_["level"]=sv[2];
            dbColumnAlias_["date"]=sv[3];
            dbColumnAlias_["time"]=sv[4];
            dbColumnAlias_["value"]=sv[5];
            dbColumnAlias_["value2"]=sv[6];
         }
      }

      else if(db_info == true && strstr(line, "DB_PATH:") != 0 )
      {
         string  sbuf(line);
         string::size_type pos=sbuf.find("DB_PATH:");
         sbuf = sbuf.substr(pos+8);
         dbPath_=sbuf;
      }

      else if(db_info == true && strstr(line, "DB_QUERY_BEGIN") != 0 )
      {
         db_query=true;
      }

      else if(db_info == true && db_query == true)
      {
         dbQuery_.push_back(line);
      }
      else if(strstr(line, "DB_QUERY_END") != 0 )
      {
         db_query=true;
      }
   }

   db_info  = false;
   db_query = false;
   metadata = false;

   // Read data
   if ( nmax == 0 )
   {
      bool inSameGpts = true;  // used when in a geopointset file
      while( f.getline( line, sizeof(line) ) && inSameGpts)
      {
         if( (*line != '#') && (strlen( line ) > 4) )
         {
            pts_[n].extract( line, gfmt_ );
            n++;
         }
         else if (!strncmp(line, "#GEO", 4))  // start of new geopoints file
         {
            inSameGpts = false;
         }
      }
   }
   else
   {
      for ( int i = 0; i < nmax; i++ )
      {
         if ( !f.getline( line, sizeof(line) ) )
         {
             marslog(LOG_EROR, "Geopoints file has less data than expected: %s", path_.c_str());
             return false;
         }

         if( (*line != '#') && (strlen( line ) > 4) )
         {
            pts_[n].extract( line, gfmt_ );
            n++;
         }
      }
   }

   count( n );

  return true;
}

//_____________________________________________________________________
void
MvGeoPoints::unload()
{
  delete [] pts_;

  pts_   = 0;
  count_ = 0;
}

//_____________________________________________________________________
bool
MvGeoPoints::write( const char* filename )
{
    int nPreviousPrecision;

  ofstream fout( filename );
  if( ! fout )
    {
      marslog(LOG_EROR, "Unable to open geopoints file for writing: %s", filename );
      return false;
    }

  fout << "#GEO\n";

  switch( gfmt_ )
    {
    case eGeoVectorPolar:
      fout << "#FORMAT POLAR_VECTOR\n"
           << "# lat\tlon\theight\tdate\t\ttime\tspeed\tdirection\n";
      break;

    case eGeoVectorXY:
      fout << "#FORMAT XY_VECTOR\n"
           << "# lat\tlon\theight\tdate\t\ttime\tu\tv\n";
      break;

    case eGeoXYV:
      fout << "#FORMAT XYV\n"
           << "# lon-x\tlat-y\tvalue\n";
      break;

    default:
      //-- this is for both eGeoTraditional and eGeoString
      //-- no "#FORMAT" line is needed
      fout << "# lat\tlon\theight\tdate\t\ttime\tvalue\n";
      break;
    }


  // Insert a line that will tell the user which value represents missing points.
  // Take care not to disturb the floating-point precision, but we need to use
  // the correct one that will actually be used in the file.
  // Note that the storing and restoring of the original precision value
  // in the output stream is probably unnecessary, but it is done just once
  // per geopoints file and so should be insignificant and allows the
  // implementation of the << operator on a single geopoint to be changed
  // without unexpected side-effects.

  nPreviousPrecision = fout.precision();
  fout.precision( VALUE_PRECISION );
  fout << "# Missing values represented by " << GEOPOINTS_MISSING_VALUE
       << " (not user-changeable)"           << endl;
  fout.precision( nPreviousPrecision );


  // metadata, if there is any
  
  const metadata_t md = metadataConst();
  if (!md.empty())
  {
    fout << "#METADATA" << endl;
    metadata_t::const_iterator it = md.begin();
    while (it != md.end())
    {
      std::string key = it->first;
      MvVariant val = it->second;
      fout << key << "=" << val.toString() << endl;
      it++;
    }
  }
  
  // start the data section

  fout << "#DATA" << endl;

  for( int p=0; p<count_; ++p )
  {
      fout << pts_[ p ]
	   << endl;
  }

  return true;
}

//_____________________________________________________________________
MvGeoP1
MvGeoPoints::nearestPoint( double lat_y, double lon_x ) const
{
  if( count_ == 0 )
    return MvGeoP1();  //-- should we...

  MvLocation myInputLoc( lat_y, lon_x );
  MvLocation myFirstLoc( pts_[0].lat_y(), pts_[0].lon_x() );

  double     myShortestDist = myInputLoc.distanceInMeters( myFirstLoc );
  long       myNearestPi    = 0;

  for( int p=1; p<count_; ++p )
    {
      MvLocation myCurrentLoc( pts_[ p ].lat_y(), pts_[ p ].lon_x() );
      double     myCurrentDist = myInputLoc.distanceInMeters( myCurrentLoc );

      if( myCurrentDist < myShortestDist )
	{
	  myShortestDist = myCurrentDist;
	  myNearestPi    = p;
	}
    }

  return pts_[ myNearestPi ];
}


//_____________________________________________________________________
// MvGeoPoints::indexOfFirstValidPoint
// Returns the index of the first geopoint that is valid in the set.
// If none are valid, then -1 is returned.
// Note that this function only considers the first value in each
// geopoint, ignoring value2.

long
MvGeoPoints::indexOfFirstValidPoint() const
{
  int i;

  for (i = 0; i < count_; i++)
  {
    if (!pts_[i].value_missing())
    {
      return i;
    }
  }

  // if we got to here, then there are no valid points

  return -1;
}

//_____________________________________________________________________
// MvGeoPoints::sort()
// Sorts points geographically - from North to South, West to East
void
MvGeoPoints::sort()
{
    if( count() < 2 )   //-- no need to sort if empty or only one point
        return;

    //-- to make sort faster for huge files, copy input geopoints into
    //-- several latitude band lists;
    //-- here we define the width and the number of these latitude bands
    const double cLatBandSize  = 1.0;
    const int    cLatBandCount = (int)( 180.0 / cLatBandSize ) + 1;

    //-- STL provides tools for sorting
    vector< list<MvGeoP1> >  LatListVec;
    list<MvGeoP1> emptyList;
    LatListVec.assign( cLatBandCount+1, emptyList );

    //-- first coarse distribution into STL lists that are stored in STL vector
    for( int s = 0; s < count(); ++s )
    {
        int band = cLatBandCount - int( ( pts_[s].lat_y() + 90.5 ) / cLatBandSize );

        //-- if invalid latitude band value then sort into head or tail
        if( band < 0 )
            band = 0;
        else if( band > cLatBandCount )
            band = cLatBandCount;

        LatListVec[band].push_back( pts_[s] );
    }

    MvGeoP1* work = new MvGeoP1[ count_ ];
    int      iOut = 0;

    //-- sort each latitude band STL list and copy to output
    for( int vecList=0; vecList < cLatBandCount+1; ++vecList )
    {
        list<MvGeoP1> curList = LatListVec[vecList];
        if( ! curList.empty() )
        {
            curList.sort();

            for( list<MvGeoP1>::iterator p = curList.begin(); p != curList.end(); ++p )
            {
                work[ iOut ] = *p;
                ++iOut;
            }

            curList.clear();
        }
    }

    MvGeoP1* tmp = pts_;
    pts_ = work;
    delete [] tmp;
}

//_____________________________________________________________________
void
MvGeoPoints::removeDuplicates()
{
  if( count() > 0 )
    {
      sort();

      MvGeoP1* work = new MvGeoP1[ count_ ];  //-- working point(er) array
      int      iOut = 0;
      int      iRem = 0;

      MvGeoP1 curr = pts_[0];                 //-- store now in case there is just 1 point
      MvGeoP1 prev = pts_[0];                 //-- store the first point

      for( int p=1; p<count_; ++p )           //-- start from the second point
        {
          curr = pts_[p];                     //-- current geopoint
          if( curr == prev )
            {
               ++iRem;                        //-- duplicate points => skip prev
            }
          else
            {
               work[ iOut++ ] = prev;         //-- points non-equal => copy prev
            }
          prev = curr;                        //-- store current as previous
        }

      work[ iOut++ ] = curr;                  //-- last point cannot be duplicate

      MvGeoP1* tmp = pts_;                    //-- swap pointers and remove original geopoints
      pts_ = work;
      delete [] tmp;

      count_ = iOut;                          //-- adjust current point count

      // the following line removed at the request of Mark Rodwell
      // marslog(LOG_INFO, "MvGeoPoints::removeDuplicates: %d duplicates removed", iRem);
    }
}
//_____________________________________________________________________
void
MvGeoPoints::offset( double latOffset, double lonOffset )
{
  if( count() > 0 )
    {
      for( int p=0; p<count_; ++p )
	{
	  MvGeoP1 pt = pts_[p];
	  pts_[p].location( pt.lat_y() + latOffset, pt.lon_x() + lonOffset );
	}
    }
}

void MvGeoPoints::setFormat()
{
	// Clean the structure
	if ( scols_.size() )
		scols_.clear();

	if ( gfmt_ == eGeoTraditional || gfmt_ == eGeoString)
	{
		sgfmt_ = "Traditional";
		ncols_ = 6;
		scols_.reserve(ncols_);
		scols_.push_back("Lat_y");
		scols_.push_back("Lon_x");
		scols_.push_back("Level");
		scols_.push_back("Date");
		scols_.push_back("Time");
		scols_.push_back("Value");
	}
	else if ( gfmt_ == eGeoXYV )
	{
		sgfmt_ = "XYV";
		ncols_ = 3;
		scols_.reserve(ncols_);
		scols_.push_back("Lon_x");
		scols_.push_back("Lat_y");
		scols_.push_back("Value");
	}
	else if ( gfmt_ == eGeoVectorPolar )
	{
		sgfmt_ = "Polar_Vector";
		ncols_ = 7;
		scols_.reserve(ncols_);
		scols_.push_back("Lat_y");
		scols_.push_back("Lon_x");
		scols_.push_back("Level");
		scols_.push_back("Date");
		scols_.push_back("Time");
		scols_.push_back("Speed");
		scols_.push_back("Direction");
	}
	else if ( gfmt_ == eGeoVectorXY )
	{
		sgfmt_ = "XY_Vector";
		ncols_ = 7;
		scols_.reserve(ncols_);
		scols_.push_back("Lat_y");
		scols_.push_back("Lon_x");
		scols_.push_back("Level");
		scols_.push_back("Date");
		scols_.push_back("Time");
		scols_.push_back("U-comp");
		scols_.push_back("V-comp");
	}

	return;
}

string MvGeoPoints::value (long row, int col, int& type)
{
	return pts_[ row ].column(col,type);
}
