/********************************************************************
** @source JEEPS projection functions
**
** @author Copyright (C) 1999 Alan Bleasby
** @version 1.0
** @modified Feb 04 2000 Alan Bleasby. First version
** @@
**
** This library is free software; you can redistribute it and/or
** modify it under the terms of the GNU Library General Public
** License as published by the Free Software Foundation; either
** version 2 of the License, or (at your option) any later version.
**
** This library is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
** Library General Public License for more details.
**
** You should have received a copy of the GNU Library General Public
** License along with this library; if not, write to the
** Free Software Foundation, Inc., 59 Temple Place - Suite 330,
** Boston, MA  02111-1307, USA.
********************************************************************/
#include "gps.h"
#include <math.h>
#include <string.h>


/* @func GPS_Math_Albers_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Albers projection easting and
** northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi1 [double] standard latitude (parallel) 1 (deg)
** @param [r] phi2 [double] standard latitude (parallel) 2 (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Albers_LatLon_To_EN(double phi, double lambda, double* E,
                                  double* N, double phi1, double phi2,
                                  double phi0, double M0, double E0,
                                  double N0, double a, double b)

{
  double dlambda;
  double phis;
  double phic;
  double e;
  double esq;
  double esqs;
  double omesqs2;

  double a2;
  double b2;
  double q;
  double q0;
  double q1;
  double q2;
  double m1;
  double m2;
  double n;
  double phi0s;
  double phi1s;
  double phi1c;
  double phi2s;
  double phi2c;
  double ess;
  double om0;
  double m1sq;
  double C;
  double nq;
  double nq0;
  double rho;
  double rho0;
  double theta;

  phi     = GPS_Math_Deg_To_Rad(phi);
  phi0    = GPS_Math_Deg_To_Rad(phi0);
  phi1    = GPS_Math_Deg_To_Rad(phi1);
  phi2    = GPS_Math_Deg_To_Rad(phi2);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  M0      = GPS_Math_Deg_To_Rad(M0);

  dlambda = lambda - M0;
  if (dlambda > GPS_PI) {
    dlambda -= ((double)2.0 * GPS_PI);
  }
  if (dlambda < -GPS_PI) {
    dlambda += ((double)2.0 * GPS_PI);
  }

  phis = sin(phi);
  phic = cos(phi);

  a2 = a*a;
  b2 = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);


  phi0s = sin(phi0);
  ess = e * phi0s;
  om0 = ((double)1.0 - ess*ess);
  q0  = ((double)1.0 - esq) * (phi0s / om0-((double)1.0/(e+e)) *
                               log(((double)1.0-ess)/((double)1.0+ess)));
  phi1s = sin(phi1);
  phi1c = cos(phi1);
  ess = e * phi1s;
  om0 = ((double)1.0 - ess*ess);
  m1 = phi1c/pow(om0,(double)0.5);
  q1  = ((double)1.0 - esq) * (phi1s / om0-((double)1.0/(e+e)) *
                               log(((double)1.0-ess)/((double)1.0+ess)));

  m1sq = m1*m1;
  if (fabs(phi1-phi2)>1.0e-10) {
    phi2s = sin(phi2);
    phi2c = cos(phi2);
    ess   = e * phi2s;
    om0   = ((double)1.0 - ess*ess);
    m2 = phi2c/pow(om0,(double)0.5);
    q2 = ((double)1.0 - esq) * (phi2s / om0-((double)1.0/(e+e)) *
                                log(((double)1.0-ess)/((double)1.0+ess)));
    n  = (m1sq - m2*m2) / (q2-q1);
  } else {
    n  = phi1s;
  }

  C = m1sq + n*q1;
  nq0 = n * q0;
  if (C < nq0) {
    rho0 = (double)0.;
  } else {
    rho0 = (a/n) * pow(C-nq0,(double)0.5);
  }


  esqs = e * phis;
  omesqs2 = ((double)1.0 - esqs*esqs);
  q  = ((double)1.0 - esq) * (phis / omesqs2-((double)1.0/(e+e)) *
                              log(((double)1.0-esqs)/((double)1.0+esqs)));
  nq = n*q;
  if (C<nq) {
    rho = (double)0.;
  } else {
    rho = (a/n) * pow(C-nq,(double)0.5);
  }

  theta = n*dlambda;
  *E = rho * sin(theta) + E0;
  *N = rho0 - rho * cos(theta) + N0;

  return;
}




/* @func GPS_Math_Albers_EN_To_LatLon **********************************
**
** Convert Albers easting and northing to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi1 [double] standard latitude (parallel) 1 (deg)
** @param [r] phi2 [double] standard latitude (parallel) 2 (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Albers_EN_To_LatLon(double E, double N, double* phi,
                                  double* lambda, double phi1, double phi2,
                                  double phi0, double M0, double E0,
                                  double N0, double a, double b)
{
  double po2;
  double rho;
  double rho0;
  double C;
  double a2;
  double b2;
  double esq;
  double e;
  double phi0s;
  double q0;
  double q1;
  double q2;
  double phi1s;
  double phi1c;
  double phi2s;
  double phi2c;
  double m1;
  double m1sq;
  double m2;
  double n;
  double nq0;

  double dx;
  double dy;
  double rhom;
  double q;
  double qc;
  double qd2;
  double rhon;
  double lat;
  double dphi;
  double phis;
  double ess;
  double om0;
  double theta;
  double tol;


  phi0    = GPS_Math_Deg_To_Rad(phi0);
  phi1    = GPS_Math_Deg_To_Rad(phi1);
  phi2    = GPS_Math_Deg_To_Rad(phi2);
  M0      = GPS_Math_Deg_To_Rad(M0);

  a2 = a*a;
  b2 = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);


  phi0s = sin(phi0);
  ess = e * phi0s;
  om0 = ((double)1.0 - ess*ess);
  q0  = ((double)1.0 - esq) * (phi0s / om0-((double)1.0/(e+e)) *
                               log(((double)1.0-ess)/((double)1.0+ess)));
  phi1s = sin(phi1);
  phi1c = cos(phi1);
  ess = e * phi1s;
  om0 = ((double)1.0 - ess*ess);
  m1 = phi1c/pow(om0,(double)0.5);
  q1  = ((double)1.0 - esq) * (phi1s / om0-((double)1.0/(e+e)) *
                               log(((double)1.0-ess)/((double)1.0+ess)));

  m1sq = m1*m1;
  if (fabs(phi1-phi2)>1.0e-10) {
    phi2s = sin(phi2);
    phi2c = cos(phi2);
    ess   = e * phi2s;
    om0   = ((double)1.0 - ess*ess);
    m2 = phi2c/pow(om0,(double)0.5);
    q2 = ((double)1.0 - esq) * (phi2s / om0-((double)1.0/(e+e)) *
                                log(((double)1.0-ess)/((double)1.0+ess)));
    n  = (m1sq - m2*m2) / (q2-q1);
  } else {
    n  = phi1s;
  }

  C = m1sq + n*q1;
  nq0 = n * q0;
  if (C < nq0) {
    rho0 = (double)0.;
  } else {
    rho0 = (a/n) * pow(C-nq0,(double)0.5);
  }


  dphi  = (double) 1.0;
  theta = (double) 0.0;
  tol   = (double) 4.85e-10;
  po2   = (double)GPS_PI / (double)2.0;

  dy   = N-N0;
  dx   = E-E0;
  rhom = rho0-dy;
  rho  = pow(dx*dx+rhom*rhom,(double)0.5);

  if (n<0.0) {
    rho  *= (double)-1.0;
    dx   *= (double)-1.0;
    dy   *= (double)-1.0;
    rhom *= (double)-1.0;
  }

  if (rho) {
    theta = atan2(dx,rhom);
  }
  rhon = rho*n;
  q    = (C - (rhon*rhon) / a2) / n;
  qc   = (double)1.0 - ((double)1.0 / (e+e)) *
         log(((double)1.0-e)/((double)1.0+e));
  if (fabs(fabs(qc)-fabs(q))>1.9e-6) {
    qd2 = q/(double)2.0;
    if (qd2>1.0) {
      *phi = po2;
    } else if (qd2<-1.0) {
      *phi = -po2;
    } else {
      lat = asin(qd2);
      if (e<1.0e-10) {
        *phi = lat;
      } else {
        while (fabs(dphi)>tol) {
          phis = sin(lat);
          ess  = e*phis;
          om0  = ((double)1.0 - ess*ess);
          dphi = (om0*om0) / ((double)2.0*cos(lat))*
                 (q/((double)1.0-esq) - phis / om0 +
                  (log(((double)1.0-ess)/((double)1.0+ess)) /
                   (e+e)));
          lat += dphi;
        }
        *phi = lat;
      }

      if (*phi > po2) {
        *phi = po2;
      } else if (*phi<-po2) {
        *phi = -po2;
      }
    }
  } else {
    if (q>=0.0) {
      *phi = po2;
    } else {
      *phi = -po2;
    }
  }

  *lambda = M0 + theta / n;
  if (*lambda > GPS_PI) {
    *lambda -= GPS_PI * (double)2.0;
  }
  if (*lambda < -GPS_PI) {
    *lambda += GPS_PI * (double)2.0;
  }
  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda = -GPS_PI;
  }

  *phi    = GPS_Math_Rad_To_Deg(*phi);
  *lambda = GPS_Math_Rad_To_Deg(*lambda);

  return;
}



/* @func GPS_Math_LambertCC_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Lambert Conformal Conic projection
** easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi1 [double] standard latitude (parallel) 1 (deg)
** @param [r] phi2 [double] standard latitude (parallel) 2 (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_LambertCC_LatLon_To_EN(double phi, double lambda, double* E,
                                     double* N, double phi1, double phi2,
                                     double phi0, double M0, double E0,
                                     double N0, double a, double b)

{
  double po2;
  double po4;
  double a2;
  double b2;
  double phi0s;
  double e;
  double esq;
  double ed2;
  double ess;
  double t0;
  double t1;
  double t2;
  double m1;
  double m2;
  double phi1s;
  double phi1c;
  double phi2s;
  double phi2c;
  double n;
  double F;
  double Fa;
  double rho;
  double rho0;
  double phis;
  double t;
  double theta;
  double dphi;

  phi     = GPS_Math_Deg_To_Rad(phi);
  phi0    = GPS_Math_Deg_To_Rad(phi0);
  phi1    = GPS_Math_Deg_To_Rad(phi1);
  phi2    = GPS_Math_Deg_To_Rad(phi2);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  M0      = GPS_Math_Deg_To_Rad(M0);


  po2 = (double)GPS_PI / (double)2.0;
  po4 = (double)GPS_PI / (double)4.0;
  a2  = a*a;
  b2  = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);
  ed2 = e / (double)2.0;

  phi0s = sin(phi0);
  ess   = e * phi0s;
  t0    = tan(po4-phi0/(double)2.0) / pow(((double)1.0-ess) /
                                          ((double)1.0+ess),ed2);


  phi1s = sin(phi1);
  phi1c = cos(phi1);
  ess   = e * phi1s;
  m1    = phi1c / pow(((double)1.0-ess*ess),(double)0.5);
  t1    = tan(po4-phi1/(double)2.0) / pow(((double)1.0-ess) /
                                          ((double)1.0+ess),ed2);

  if (fabs(phi1-phi2)>1.0e-10) {
    phi2s = sin(phi2);
    phi2c = cos(phi2);
    ess   = e * phi2s;
    m2    = phi2c / pow(((double)1.0-ess*ess),(double)0.5);
    t2    = tan(po4-phi2/(double)2.0) / pow(((double)1.0-ess) /
                                            ((double)1.0+ess),ed2);
    n     = log(m1/m2) / log(t1/t2);
  } else {
    n = phi1s;
  }

  F  = m1 / (n*pow(t1,n));
  Fa = F*a;

  rho0 = pow(t0,n) * Fa;

  if (fabs(fabs(phi)-po2)>1.0e-10) {
    phis = sin(phi);
    ess  = e * phis;
    t    = tan(po4-phi/(double)2.0) / pow(((double)1.0-ess) /
                                          ((double)1.0+ess),ed2);
    rho  = pow(t,n) * Fa;
  } else {
    if ((phi*n)<=(double)0.0) {
      return;
    }
    rho = (double)0.0;
  }

  dphi = lambda - M0;
  if (dphi>GPS_PI) {
    dphi -= (double)GPS_PI * (double)2.0;
  }
  if (dphi<-GPS_PI) {
    dphi += (double)GPS_PI * (double)2.0;
  }
  theta = dphi*n;

  *E = rho * sin(theta) + E0;
  *N = rho0 - rho * cos(theta) + N0;

  return;
}




/* @func GPS_Math_LambertCC_EN_To_LatLon **********************************
**
** Convert Lambert Conformal Conic  easting and northing to latitude and
** longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi1 [double] standard latitude (parallel) 1 (deg)
** @param [r] phi2 [double] standard latitude (parallel) 2 (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_LambertCC_EN_To_LatLon(double E, double N, double* phi,
                                     double* lambda, double phi1, double phi2,
                                     double phi0, double M0, double E0,
                                     double N0, double a, double b)
{
  double po2;
  double po4;
  double a2;
  double b2;
  double phi0s;
  double e;
  double esq;
  double ed2;
  double ess;
  double t0;
  double t1;
  double t2;
  double m1;
  double m2;
  double phi1s;
  double phi1c;
  double phi2s;
  double phi2c;
  double n;
  double F;
  double Fa;
  double rho;
  double rho0;
  double phis;
  double t;
  double theta;

  double dx;
  double dy;
  double rhom;
  double lat;
  double tlat;
  double tol;



  phi0    = GPS_Math_Deg_To_Rad(phi0);
  phi1    = GPS_Math_Deg_To_Rad(phi1);
  phi2    = GPS_Math_Deg_To_Rad(phi2);
  M0      = GPS_Math_Deg_To_Rad(M0);


  po2 = (double)GPS_PI / (double)2.0;
  po4 = (double)GPS_PI / (double)4.0;
  a2  = a*a;
  b2  = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);
  ed2 = e / (double)2.0;

  phi0s = sin(phi0);
  ess   = e * phi0s;
  t0    = tan(po4-phi0/(double)2.0) / pow(((double)1.0-ess) /
                                          ((double)1.0+ess),ed2);


  phi1s = sin(phi1);
  phi1c = cos(phi1);
  ess   = e * phi1s;
  m1    = phi1c / pow(((double)1.0-ess*ess),(double)0.5);
  t1    = tan(po4-phi1/(double)2.0) / pow(((double)1.0-ess) /
                                          ((double)1.0+ess),ed2);

  if (fabs(phi1-phi2)>1.0e-10) {
    phi2s = sin(phi2);
    phi2c = cos(phi2);
    ess   = e * phi2s;
    m2    = phi2c / pow(((double)1.0-ess*ess),(double)0.5);
    t2    = tan(po4-phi2/(double)2.0) / pow(((double)1.0-ess) /
                                            ((double)1.0+ess),ed2);
    n     = log(m1/m2) / log(t1/t2);
  } else {
    n = phi1s;
  }

  F  = m1 / (n*pow(t1,n));
  Fa = F*a;

  rho0 = pow(t0,n) * Fa;

  tlat = theta = (double)0.0;
  tol  = (double)4.85e-10;

  dx = E - E0;
  dy = N - N0;
  rhom = rho0 - dy;
  rho  = pow(dx*dx + rhom*rhom,(double)0.5);

  if (n<0.0) {
    rhom *= (double)-1.0;
    dy   *= (double)-1.0;
    dx   *= (double)-1.0;
    rho  *= (double)-1.0;
  }

  if (rho) {
    theta = atan2(dx,rhom);
    t = pow(rho/Fa,(double)1.0/n);
    lat = po2 - (double)2.0*atan(t);
    while (fabs(lat-tlat)>tol) {
      tlat = lat;
      phis = sin(lat);
      ess  = e * phis;
      lat  = po2 - (double)2.0 * atan(t*pow(((double)1.0-ess) /
                                            ((double)1.0+ess),
                                            e / (double)2.0));
    }
    *phi = lat;
    *lambda = theta/n + M0;

    if (*phi>po2) {
      *phi=po2;
    } else if (*phi<-po2) {
      *phi=-po2;
    }
    if (*lambda>GPS_PI) {
      *lambda -= (double)GPS_PI * (double)2.0;
    }
    if (*lambda<-GPS_PI) {
      *lambda += (double)GPS_PI * (double)2.0;
    }

    if (*lambda>GPS_PI) {
      *lambda = GPS_PI;
    } else if (*lambda<-GPS_PI) {
      *lambda = -GPS_PI;
    }
  } else {
    if (n>0.0) {
      *phi = po2;
    } else {
      *phi = -po2;
    }
    *lambda = M0;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Miller_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Miller Cylindrical projection easting and
** northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Miller_LatLon_To_EN(double phi, double lambda, double* E,
                                  double* N, double M0, double E0,
                                  double N0, double a, double b)
{
  double a2;
  double b2;
  double R;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double phis;
  double dlam;


  phi     = GPS_Math_Deg_To_Rad(phi);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;

  R = a*((double)1.0-e2/(double)6.0-(double)17.0*e4/(double)360.0-
         (double)67.0*e6/(double)3024.0);

  if (M0>GPS_PI) {
    M0 -= p2;
  }

  phis = sin((double)0.8 * phi);

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam-=p2;
  }
  if (dlam<-GPS_PI) {
    dlam+=p2;
  }

  *E = R*dlam+E0;
  *N = (R/(double)1.6) * log(((double)1.0+phis) / ((double)1.0-phis)) + N0;

  return;
}




/* @func GPS_Math_Miller_EN_To_LatLon **********************************
**
** Convert latitude and longitude to Miller Cylindrical projection easting and
** northing
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Miller_EN_To_LatLon(double E, double N, double* phi,
                                  double* lambda, double M0, double E0,
                                  double N0, double a, double b)
{
  double a2;
  double b2;
  double R;
  double e;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double dx;
  double dy;

  dx = E - E0;
  dy = N - N0;

  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;
  e   = pow(e2,(double)0.5);

  R = a*((double)1.0-e2/(double)6.0-(double)17.0*e4/(double)360.0-
         (double)67.0*e6/(double)3024.0);
  if (M0>GPS_PI) {
    M0 -= p2;
  }

  *phi    = atan(sinh((double)0.8*dy/R)) / (double)0.8;
  *lambda = M0+dx/R;

  if (*phi>po2) {
    *phi=po2;
  } else if (*phi<-po2) {
    *phi=-po2;
  }

  if (*lambda>GPS_PI) {
    *lambda-=p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda+=p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Bonne_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Bonne pseudoconic equal area  projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Bonne_LatLon_To_EN(double phi, double lambda, double* E,
                                 double* N, double phi0, double M0, double E0,
                                 double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double M1;
  double m1;
  double c0;
  double c1;
  double c2;
  double c3;
  double j;
  double te4;
  double E1;
  double E2;
  double E3;
  double E4;
  double x;
  double phi0s;
  double lat;
  double phi0c;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double as;

  double phis;
  double phic;
  double phis2;
  double phis4;
  double phis6;
  double dlam;
  double mm;
  double MM;
  double rho;
  double EE;
  double tol;


  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  phi0s = sin(phi0);
  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  phi0c = cos(phi0);
  m1 = phi0c/ pow(((double)1.0-e2*phi0s*phi0s),(double)0.5);
  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  M1 = a*(lat-phi0s2+phi0s4-phi0s6);

  x = pow((double)1.0-e2,(double)0.5);
  E1 = ((double)1.0-x) / ((double)1.0+x);
  E2 = E1*E1;
  E3 = E2*E1;
  E4 = E3*E1;

  if (!phi0s) {
    as = (double)0.0;
  } else {
    as = a*m1/phi0s;
  }


  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  phis = sin(phi);
  phic = cos(phi);

  tol = (double)0.0001;
  if (!(phi-phi0) && (((po2-tol)<fabs(phi)) && (fabs(phi)<po2+tol))) {
    *E = *N = (double)0.0;
  } else {
    mm = phic / pow(((double)1.0-e2*phis*phis),(double)0.5);
    lat   = c0 * phi;
    phis2 = c1 * sin((double)2.0*phi);
    phis4 = c2 * sin((double)4.0*phi);
    phis6 = c3 * sin((double)6.0*phi);
    MM = a * (lat-phis2+phis4-phis6);

    rho = as + M1 - MM;
    if (!rho) {
      EE = (double)0.0;
    } else {
      EE = a * mm * dlam / rho;
    }

    *E = rho * sin(EE) + E0;
    *N = as - rho * cos(EE) + N0;
  }

  return;
}




/* @func GPS_Math_Bonne_EN_To_LatLon **********************************
**
** Convert Bonne pseudoconic equal area easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Bonne_EN_To_LatLon(double E, double N, double* phi,
                                 double* lambda, double phi0, double M0,
                                 double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double M1;
  double m1;
  double c0;
  double c1;
  double c2;
  double c3;
  double A0;
  double A1;
  double A2;
  double A3;
  double j;
  double te4;
  double E1;
  double E2;
  double E3;
  double E4;
  double x;
  double phi0s;
  double lat;
  double phi0c;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double as;

  double phis;
  double phic;
  double dx;
  double dy;
  double mu;
  double mm;
  double MM;
  double asdy;
  double rho;
  double smu2;
  double smu4;
  double smu6;
  double smu8;
  double tol;


  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  phi0s = sin(phi0);
  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  phi0c = cos(phi0);
  m1 = phi0c/ pow(((double)1.0-e2*phi0s*phi0s),(double)0.5);
  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  M1 = a*(lat-phi0s2+phi0s4-phi0s6);

  x = pow((double)1.0-e2,(double)0.5);
  E1 = ((double)1.0-x) / ((double)1.0+x);
  E2 = E1*E1;
  E3 = E2*E1;
  E4 = E3*E1;
  A0 = (double)3.0*E1/(double)2.0-(double)27.0*E3/(double)32.0;
  A1 = (double)21.0*E2/(double)16.0-(double)55.0*E4/(double)32.0;
  A2 = (double)151.0*E3/(double)96.0;
  A3 = (double)1097.0*E4/(double)512.0;
  if (!phi0s) {
    as = (double)0.0;
  } else {
    as = a*m1/phi0s;
  }


  dx = E - E0;
  dy = N - N0;
  asdy = as - dy;
  rho = pow(dx*dx+asdy*asdy,(double)0.5);
  if (phi0<(double)0.0) {
    rho=-rho;
  }
  MM = as+M1-rho;

  mu = MM / (a*c0);
  smu2 = A0 * sin((double)2.0*mu);
  smu4 = A1 * sin((double)4.0*mu);
  smu6 = A2 * sin((double)6.0*mu);
  smu8 = A3 * sin((double)8.0*mu);
  *phi = mu+smu2+smu4+smu6+smu8;

  tol = (double)0.00001;
  if (((po2-tol)<fabs(*phi)) && (fabs(*phi)<po2+tol)) {
    *lambda = M0;
  } else {
    phic = cos(*phi);
    phis = sin(*phi);
    mm   = phic / pow(((double)1.0-e2*phis*phis),(double)0.5);
    if (phi0<(double)0.0) {
      dx = -dx;
      asdy = -asdy;
    }
    *lambda = M0 + rho * (atan2(dx,asdy)) / (a * mm);
  }

  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Cassini_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Cassini transverse cylindrical projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cassini_LatLon_To_EN(double phi, double lambda, double* E,
                                   double* N, double phi0, double M0,
                                   double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double AM0;
  double c0;
  double c1;
  double c2;
  double c3;
  double om0;
  double A0;
  double A1;
  double A2;
  double A3;
  double j;
  double te4;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double lat;
  double x;
  double E1;
  double E2;
  double E3;
  double E4;

  double phis;
  double phic;
  double phit;
  double phis2;
  double phis4;
  double phis6;
  double RD;
  double dlam;
  double NN;
  double TT;
  double WW;
  double WW2;
  double WW3;
  double WW4;
  double WW5;
  double CC;
  double MM;


  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  phi    = GPS_Math_Deg_To_Rad(phi);
  M0     = GPS_Math_Deg_To_Rad(M0);


  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  te4 = (double)3.0 * e4;
  j   = (double)45. * e6 / (double)1024.;
  c0 = (double)1.0-e2/(double)4.-te4/(double)64.-(double)5.*e6/(double)256.;
  c1 = (double)3.*e2/(double)8.+te4/(double)32.+j;
  c2 = (double)15.*e4/(double)256.+j;
  c3 = (double)35.*e6/(double)3072.;

  lat = c0*phi0;
  phi0s2 = c1 * sin((double)2.*phi0);
  phi0s4 = c2 * sin((double)4.*phi0);
  phi0s6 = c3 * sin((double)6.*phi0);
  AM0 = a * (lat-phi0s2+phi0s4-phi0s6);

  om0 = (double)1.0 - e2;
  x = pow(om0,(double)0.5);
  E1 = ((double)1.0 - x) / ((double)1.0 + x);
  E2 = E1*E1;
  E3 = E1*E2;
  E4 = E1*E3;
  A0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  A1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  A2 = (double)151.*E3/(double)96.;
  A3 = (double)1097.*E4/(double)512.;


  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  phis = sin(phi);
  phic = cos(phi);
  phit = tan(phi);
  RD = pow((double)1.-e2*phis*phis,(double).5);
  NN = a/RD;
  TT = phit*phit;
  WW = dlam*phic;
  WW2 = WW*WW;
  WW3 = WW*WW2;
  WW4 = WW*WW3;
  WW5 = WW*WW4;
  CC = e2*phic*phic/om0;
  lat = c0*phi;
  phis2 = c1 * sin((double)2.*phi);
  phis4 = c2 * sin((double)4.*phi);
  phis6 = c3 * sin((double)6.*phi);
  MM = a * (lat-phis2+phis4-phis6);

  *E = NN*(WW-(TT*WW3/(double)6.)-((double)8.-TT+(double)8.*CC)*
           (TT*WW5/(double)120.)) + E0;
  *N = MM-AM0+NN*phit*((WW2/(double)2.)+((double)5.-TT+(double)6.*CC) *
                       WW4/(double)24.) + N0;
  return;
}




/* @func GPS_Math_Cassini_EN_To_LatLon **********************************
**
** Convert Cassini transverse cylindrical easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cassini_EN_To_LatLon(double E, double N, double* phi,
                                   double* lambda, double phi0, double M0,
                                   double E0, double N0, double a, double b)

{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double AM0;
  double c0;
  double c1;
  double c2;
  double c3;
  double om0;
  double A0;
  double A1;
  double A2;
  double A3;
  double j;
  double te4;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double lat;
  double x;
  double E1;
  double E2;
  double E3;
  double E4;

  double dx;
  double dy;
  double mu;
  double mus2;
  double mus4;
  double mus6;
  double mus8;
  double M1;
  double phi1;
  double phi1s;
  double phi1c;
  double phi1t;
  double T;
  double T1;
  double N1;
  double R1;
  double RD;
  double DD;
  double D2;
  double D3;
  double D4;
  double D5;
  double tol;

  M0 = GPS_Math_Deg_To_Rad(M0);
  phi0 = GPS_Math_Deg_To_Rad(phi0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  te4 = (double)3.0 * e4;
  j   = (double)45. * e6 / (double)1024.;
  c0 = (double)1.0-e2/(double)4.-te4/(double)64.-(double)5.*e6/(double)256.;
  c1 = (double)3.*e2/(double)8.+te4/(double)32.+j;
  c2 = (double)15.*e4/(double)256.+j;
  c3 = (double)35.*e6/(double)3072.;

  lat = c0*phi0;
  phi0s2 = c1 * sin((double)2.*phi0);
  phi0s4 = c2 * sin((double)4.*phi0);
  phi0s6 = c3 * sin((double)6.*phi0);
  AM0 = a * (lat-phi0s2+phi0s4-phi0s6);

  om0 = (double)1.0 - e2;
  x = pow(om0,(double)0.5);
  E1 = ((double)1.0 - x) / ((double)1.0 + x);
  E2 = E1*E1;
  E3 = E1*E2;
  E4 = E1*E3;
  A0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  A1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  A2 = (double)151.*E3/(double)96.;
  A3 = (double)1097.*E4/(double)512.;



  tol = (double)1.e-5;

  dx = E - E0;
  dy = N - N0;
  M1 = AM0 + dy;
  mu = M1 / (a*c0);
  mus2 = A0 * sin((double)2.*mu);
  mus4 = A1 * sin((double)4.*mu);
  mus6 = A2 * sin((double)6.*mu);
  mus8 = A3 * sin((double)8.*mu);
  phi1 = mu + mus2 + mus4 + mus6 + mus8;

  if ((((po2-tol)<phi1)&&(phi1<(po2+tol)))) {
    *phi = po2;
    *lambda = M0;
  } else if ((((-po2-tol)<phi1)&&(phi1<(-po2+tol)))) {
    *phi = -po2;
    *lambda = M0;
  } else {
    phi1s = sin(phi1);
    phi1c = cos(phi1);
    phi1t = tan(phi1);
    T1 = phi1t*phi1t;
    RD = pow((double)1.-e2*phi1s*phi1s,(double).5);
    N1 = a/RD;
    R1 = N1 * om0 / (RD*RD);
    DD = dx/N1;
    D2 = DD*DD;
    D3 = DD*D2;
    D4 = DD*D3;
    D5 = DD*D4;
    T = (double)1. + (double)3.*T1;
    *phi = phi1-(N1*phi1t/R1)*(D2/(double)2.-T*D4/(double)24.);
    *lambda = M0+(DD-T1*D3/(double)3.+T*T1*D5/(double)15.)/phi1c;

    if (*phi>po2) {
      *phi=po2;
    } else if (*phi<-po2) {
      *phi=-po2;
    }

    if (*lambda>GPS_PI) {
      *lambda -= p2;
    }
    if (*lambda<-GPS_PI) {
      *lambda += p2;
    }

    if (*lambda>GPS_PI) {
      *lambda=GPS_PI;
    } else if (*lambda<-GPS_PI) {
      *lambda=-GPS_PI;
    }
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Cylea_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Cylindrical equal area  projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cylea_LatLon_To_EN(double phi, double lambda, double* E,
                                 double* N, double phi0, double M0,
                                 double E0, double N0, double a, double b)
{
  double a2;
  double b2;
  double e;
  double e2;
  double e4;
  double e6;
  double k0;
  double ak0;
  double k2;
  double c0;
  double c1;
  double c2;
  double p2;
  double po2;
  double phi0s;
  double phi0c;

  double dlam;
  double qq;
  double x;
  double phis;

  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  phi    = GPS_Math_Deg_To_Rad(phi);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  e  = pow(e2,(double).5);
  c0 = e2/(double)3.+(double)31.*e4/(double)180.+(double)517.*
       e6/(double)5040.;
  c1 = (double)23.*e4/(double)360.+(double)251.*e6/(double)3780.;
  c2 = (double)761.*e6/(double)45360.;

  phi0s = sin(phi0);
  phi0c = cos(phi0);
  k0    = phi0c / pow((double)1.-e2*phi0s*phi0s,(double).5);
  ak0   = a*k0;
  k2    = k0 * (double)2.;

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam-=p2;
  }
  if (dlam<-GPS_PI) {
    dlam+=p2;
  }

  phis = sin(phi);
  x = e * phis;
  qq = ((double)1.-e2)*(phis/((double)1.-x*x)-((double)1./((double)2.*e))*
                        log(((double)1.-x)/((double)1.+x)));
  *E = ak0 * dlam + E0;
  *N = a * qq / k2 + N0;

  return;
}




/* @func GPS_Math_Cylea_EN_To_LatLon **********************************
**
** Convert Cylindrical equal area  easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cylea_EN_To_LatLon(double E, double N, double* phi,
                                 double* lambda, double phi0, double M0,
                                 double E0, double N0, double a, double b)

{
  double a2;
  double b2;
  double e;
  double e2;
  double e4;
  double e6;
  double k0;
  double ak0;
  double k2;
  double c0;
  double c1;
  double c2;
  double p2;
  double po2;
  double phi0s;
  double phi0c;

  double dx;
  double dy;
  double qp;
  double bt;
  double phis;
  double i;
  double x;
  double bs2;
  double bs4;
  double bs6;


  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  e  = pow(e2,(double).5);
  c0 = e2/(double)3.+(double)31.*e4/(double)180.+(double)517.*
       e6/(double)5040.;
  c1 = (double)23.*e4/(double)360.+(double)251.*e6/(double)3780.;
  c2 = (double)761.*e6/(double)45360.;

  phi0s = sin(phi0);
  phi0c = cos(phi0);
  k0    = phi0c / pow((double)1.-e2*phi0s*phi0s,(double).5);
  ak0   = a*k0;
  k2    = k0 * (double)2.;

  dx = E - E0;
  dy = N - N0;
  phis = sin(po2);
  x = e*phis;
  qp = ((double)1.-e2)*(phis/((double)1.-x*x)-((double)1./((double)2.*e))*
                        log(((double)1.-x)/((double)1.+x)));
  i = k2*dy/(a*qp);
  if (i>(double)1.) {
    i=(double)1.;
  } else if (i<(double)-1.) {
    i=(double)-1.;
  }
  bt = asin(i);
  bs2 = c0 * sin((double)2.*bt);
  bs4 = c1 * sin((double)4.*bt);
  bs6 = c2 * sin((double)6.*bt);

  *phi = bt+bs2+bs4+bs6;
  *lambda = M0 + dx/ak0;

  if (*phi>po2) {
    *phi=po2;
  } else if (*phi<-po2) {
    *phi=-po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_EckertIV_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Eckert IV equal area elliptical
** pseudocylindrical projection easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_EckertIV_LatLon_To_EN(double phi, double lambda, double* E,
                                    double* N, double M0, double E0, double N0,
                                    double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra0;
  double Ra1;
  double po2;
  double p2;

  double Ra;

  double phis;
  double theta;
  double dtheta;
  double thetas;
  double thetac;
  double n;
  double dlam;
  double tol;


  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2) / a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  Ra0 = Ra * (double)0.4222382;
  Ra1 = Ra * (double)1.3265004;

  theta = phi / (double)2.;
  dtheta = (double)1.;
  tol = (double)4.85e-10;
  phis = sin(phi);

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  while (fabs(dtheta)>tol) {
    thetas = sin(theta);
    thetac = cos(theta);
    n = theta+thetas*thetac+(double)2.*thetas;
    dtheta = -(n-((double)2.+po2)*phis) /
             ((double)2.*thetac*((double)1.+thetac));
    theta += dtheta;
  }

  *E = Ra0*dlam*((double)1.+cos(theta))+E0;
  *N = Ra1*sin(theta)+N0;

  return;
}




/* @func GPS_Math_EckertIV_EN_To_LatLon **********************************
**
** Convert Eckert IV equal area elliptical pseudocylindrical projection
** easting and northing to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_EckertIV_EN_To_LatLon(double E, double N, double* phi,
                                    double* lambda, double M0, double E0,
                                    double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra0;
  double Ra1;
  double po2;
  double p2;

  double Ra;
  double theta;
  double thetas;
  double thetac;
  double n;
  double dx;
  double dy;
  double i;


  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2) / a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  Ra0 = Ra * (double)0.4222382;
  Ra1 = Ra * (double)1.3265004;

  dx = E - E0;
  dy = N - N0;
  i = dy/Ra1;
  if (i>(double)1.) {
    i=(double)1.;
  } else if (i<(double)-1.) {
    i=(double)-1.;
  }

  theta = asin(i);
  thetas = sin(theta);
  thetac = cos(theta);
  n = theta+thetas*thetac+(double)2.*thetas;

  *phi = asin(n/((double)2. + po2));
  *lambda = M0 + dx / (Ra0*((double)1.+thetac));

  if (*phi>po2) {
    *phi=po2;
  } else if (*phi<-po2) {
    *phi=-po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}





/* @func GPS_Math_EckertVI_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Eckert VI equal area
** pseudocylindrical projection easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_EckertVI_LatLon_To_EN(double phi, double lambda, double* E,
                                    double* N, double M0, double E0, double N0,
                                    double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double Rsq;
  double IRa;
  double po2;
  double p2;

  double phis;
  double theta;
  double dtheta;
  double dlam;
  double tol;


  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2) / a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  Rsq = Ra/pow((double)2.+GPS_PI,(double).5);
  IRa = (double)1./Rsq;

  phis = sin(phi);
  theta = phi;
  dtheta = (double)1.;
  tol = (double)4.85e-10;

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  while (fabs(dtheta)>tol) {
    dtheta = -(theta+sin(theta)-((double)1.+po2)*phis) /
             ((double)1.+cos(theta));
    theta += dtheta;
  }

  *E = Rsq*dlam*((double)1.+cos(theta))+E0;
  *N = (double)2.*Rsq*theta+N0;

  return;
}




/* @func GPS_Math_EckertVI_EN_To_LatLon **********************************
**
** Convert Eckert VI equal area pseudocylindrical projection
** easting and northing to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_EckertVI_EN_To_LatLon(double E, double N, double* phi,
                                    double* lambda, double M0, double E0,
                                    double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Rsq;
  double IRa;
  double po2;
  double p2;

  double Ra;
  double theta;
  double dx;
  double dy;
  double i;


  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.;
  po2 = (double)GPS_PI / (double)2.;

  if (M0>GPS_PI) {
    M0-=p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2) / a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  Rsq = Ra/pow((double)2.+GPS_PI,(double).5);
  IRa = (double)1./Rsq;


  dx = E - E0;
  dy = N - N0;
  theta = IRa * dy / (double)2.;
  i = (theta+sin(theta)) / ((double)1.+po2);
  if (i>(double)1.) {
    *phi = po2;
  } else if (i<(double)-1.) {
    *phi = -po2;
  } else {
    *phi= asin(i);
  }
  *lambda = M0 + IRa * dx / ((double)1.+cos(theta));

  if (*phi>po2) {
    *phi=po2;
  } else if (*phi<-po2) {
    *phi=-po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}





/* @func GPS_Math_Cyled_LatLon_To_EN **********************************
**
** Convert latitude and longitude to cylindrical equidistant projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cyled_LatLon_To_EN(double phi, double lambda, double* E,
                                 double* N, double phi0, double M0, double E0,
                                 double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double Rac;
  double phi0c;

  double dlam;

  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  phi0c = cos(phi0);
  Rac   = Ra * phi0c;

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  *E = Rac * dlam + E0;
  *N = Ra * phi + N0;

  return;
}




/* @func GPS_Math_Cyled_EN_To_LatLon **********************************
**
** Convert cylindrical equidistant easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Cyled_EN_To_LatLon(double E, double N, double* phi,
                                 double* lambda, double phi0, double M0,
                                 double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double Rac;
  double phi0c;

  double dx;
  double dy;


  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-
          (double)67.*e6/(double)3024.);
  phi0c = cos(phi0);
  Rac   = Ra * phi0c;

  dx = E - E0;
  dy = N - N0;

  if (!Rac) {
    *lambda = (double)0.;
  } else {
    *lambda = M0 + dx / Rac;
  }

  *phi = dy/Ra;

  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_VderGrinten_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Van der Grinten polyconic projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_VderGrinten_LatLon_To_EN(double phi, double lambda, double* E,
                                       double* N, double M0, double E0,
                                       double N0, double a, double b)
{
  double po2;
  double p2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double pRa;

  double gg;
  double pp;
  double pp2;
  double gm0;
  double ppa;
  double thetai;
  double theta;
  double thetas;
  double thetac;
  double qq;
  double tol;
  double aa;
  double aa2;
  double dlam;

  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-(double)67.*
          e6/(double)3024.);
  pRa = (double)GPS_PI * Ra;

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  tol = (double)1.0e-5;

  if (!phi) {
    *N = (double)0.0;
    *E = Ra*dlam+E0;
  } else if (!dlam || (((po2-tol)<tol)&&(phi<(po2+tol))) ||
             (((-po2-tol)<tol)&&(phi<(-po2+tol)))) {
    thetai = fabs(((double)2./(double)GPS_PI) * phi);
    if (thetai>(double)1.) {
      thetai=(double)1.;
    } else if (thetai<(double)-1.) {
      thetai=(double)-1.;
    }

    theta = asin(thetai);
    *E = 0;
    *N = pRa * tan(theta/(double)2.) * N0;
    if (phi<(double)0.0) {
      *N *= (double)-1.;
    }
  } else {
    aa = (double).5*fabs((double)GPS_PI/dlam - dlam/(double)GPS_PI);
    thetai = fabs(((double)2./(double)GPS_PI) * phi);
    if (thetai>(double)1.) {
      thetai=(double)1.;
    } else if (thetai<(double)-1.) {
      thetai=(double)-1.;
    }

    theta = asin(thetai);
    thetas = sin(theta);
    thetac = cos(theta);
    gg = thetac/(thetas+thetac-(double)1.);
    pp = gg*((double)2./thetas-(double)1.);
    aa2 = aa*aa;
    pp2 = pp*pp;
    gm0 = gg-pp2;
    ppa = pp2+aa2;
    qq = aa2+gg;
    *E = pRa*(aa*gm0+pow(aa2*gm0*gm0-ppa*(gg*gg-pp2),(double).5))/ppa+E0;
    if (dlam<(double)0.0) {
      *E *= (double)-1.;
    }
    *N = pRa*(pp*qq-aa*pow((aa2+(double)1.)*ppa-qq*qq,(double).5))/ppa+N0;
    if (phi<(double)0.0) {
      *N *= (double)-1.;
    }
  }

  return;
}




/* @func GPS_Math_VderGrinten_EN_To_LatLon **********************************
**
** Convert Van der Grinten polyconic easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_VderGrinten_EN_To_LatLon(double E, double N, double* phi,
                                       double* lambda, double M0, double E0,
                                       double N0, double a, double b)
{
  double po2;
  double p2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double pRa;

  double dx;
  double dy;
  double xx;
  double xx2;
  double yy;
  double yy2;
  double tyy2;
  double xpy;
  double c1;
  double c2;
  double c3;
  double c3c3;
  double co;
  double dd;
  double a1;
  double m1;
  double i;
  double theta;

  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }


  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  Ra = a*((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-(double)67.*
          e6/(double)3024.);
  pRa = (double)GPS_PI * Ra;


  dx = E - E0;
  dy = N - N0;
  xx = dx/pRa;
  yy = dy/pRa;
  xx2 = xx*xx;
  yy2 = yy*yy;
  xpy = xx2+yy2;
  tyy2 = yy2*(double)2.;

  if (!N) {
    *phi=(double)0.0;
  } else {
    c1 = -fabs(yy)*((double)1.+xpy);
    c2 = c1-tyy2+xx2;
    c3 = (double)-2.*c1+(double)1.+tyy2+xpy*xpy;
    co = c2/((double)3.*c3);
    c3c3 = c3*c3;
    dd = yy2/c3+(((double)2.*c2*c2*c2)/(c3c3*c3)-((double)9.*c1*c2)/
                 c3c3)/(double)27.;
    a1 = (c1-c2*co)/c3;
    m1 = (double)2.* pow(-((double)1./(double)3.)*a1,(double).5);
    i = (double)3.*dd/(a1*m1);
    if ((i>(double)1.)||(i<(double)-1.)) {
      *phi=po2;
    } else {
      theta = ((double)1./(double)3.)*acos((double)3.*dd/(a1*m1));
      *phi = (double)GPS_PI*(-m1*cos(theta+(double)GPS_PI/(double)3.)-
                             co);
    }
  }

  if (N<(double)0.0) {
    *phi *= (double)-1.0;
  }

  if (!xx) {
    *lambda = M0;
  } else
    *lambda = (double)GPS_PI * (xpy-(double)1.+
                                pow((double)1.+((double)2.*xx2-tyy2)+xpy*xpy,(double).5)) /
              ((double)2.*xx) + M0;
  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Bonne_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Bonne pseudoconic equal area  projection
** easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi1 [double] latitude of true scale (deg)
** @param [r] lambda1 [double] longitude from pole (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_PolarSt_LatLon_To_EN(double phi, double lambda, double* E,
                                   double* N, double phi1, double lambda1,
                                   double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4=(double)0.;
  double e;
  double eo2;
  double sh;
  double mc;
  double tc=(double)0.;
  double amc=(double)0.;
  double ta;
  double phi1s;
  double phi1c;
  double es;
  double op;
  double om;
  double pe;
  double polat;
  double polon;

  double dlam;
  double phis;
  double t;
  double rho;


  lambda1 = GPS_Math_Deg_To_Rad(lambda1);
  phi1    = GPS_Math_Deg_To_Rad(phi1);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  phi     = GPS_Math_Deg_To_Rad(phi);


  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;

  ta = a * (double)2.0;
  if (lambda1>GPS_PI) {
    lambda1 -= p2;
  }
  if (phi1<(double)0.0) {
    sh=(double)1.0;
    polat = -phi1;
    polon = -lambda1;
  } else {
    sh=(double)0.0;
    polat = phi1;
    polon = lambda1;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e  = pow(e2,(double).5);
  eo2 = e/(double)2.;

  if (fabs(fabs(polat)-po2)>(double)1.0e-10) {
    phi1s = sin(polat);
    phi1c = cos(polat);
    es    = e*phi1s;
    pe    = pow(((double)1.-es)/((double)1.+es),eo2);
    mc    = phi1c / pow((double)1.-es*es,(double).5);
    amc   = mc * a;
    tc    = tan(((double)GPS_PI/(double)4.)-polat/(double)2.) / pe;
  } else {
    op = (double)1. + e;
    om = (double)1. - e;
    e4 = pow(pow(op,op)*pow(om,om),(double).5);
  }



  if (fabs(fabs(phi)-po2)<(double)1.0e-10) {
    *E = *N = (double)0.0;
  } else {
    if (sh) {
      phi *= (double)-1.0;
      lambda *= (double)-1.0;
    }

    dlam = lambda - polon;
    if (dlam>GPS_PI) {
      dlam -= p2;
    }
    if (dlam<-GPS_PI) {
      dlam += p2;
    }
    phis = sin(phi);
    es   = e * phis;
    pe   = pow(((double)1.-es)/((double)1.+es),eo2);
    t    = tan(((double)GPS_PI/(double)4.)-phi/(double)2.) / pe;

    if (fabs(fabs(polat)-po2)>(double)1.0e-10) {
      rho = amc * t / tc;
    } else {
      rho = ta * t / e4;
    }
    *E = rho * sin(dlam) + E0;

    if (sh) {
      *E *= (double)-1.;
      *N = rho * cos(dlam) + N0;
    } else {
      *N = -rho * cos(dlam) + N0;
    }
  }

  return;
}




/* @func GPS_Math_PolarSt_EN_To_LatLon **********************************
**
** Convert Polar Stereographic easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi1 [double] latitude of true scale (deg)
** @param [r] lambda1 [double] longitude from pole (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_PolarSt_EN_To_LatLon(double E, double N, double* phi,
                                   double* lambda, double phi1, double lambda1,
                                   double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4=(double)0.;
  double e;
  double eo2;
  double sh;
  double mc;
  double tc=(double)0.;
  double amc=(double)0.;
  double ta;
  double phi1s;
  double phi1c;
  double es;
  double op;
  double om;
  double pe;
  double polat;
  double polon;

  double dx;
  double dy;
  double t;
  double rho;
  double PHI;
  double PHIS;
  double TPHI;


  lambda1 = GPS_Math_Deg_To_Rad(lambda1);
  phi1    = GPS_Math_Deg_To_Rad(phi1);


  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;

  ta = a * (double)2.0;
  if (lambda1>GPS_PI) {
    lambda1 -= p2;
  }
  if (phi1<(double)0.0) {
    sh=(double)1.0;
    polat = -phi1;
    polon = -lambda1;
  } else {
    sh=(double)0.0;
    polat = phi1;
    polon = lambda1;
  }

  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e  = pow(e2,(double).5);
  eo2 = e/(double)2.;

  if (fabs(fabs(polat)-po2)>(double)1.0e-10) {
    phi1s = sin(polat);
    phi1c = cos(polat);
    es    = e*phi1s;
    pe    = pow(((double)1.-es)/((double)1.+es),eo2);
    mc    = phi1c / pow((double)1.-es*es,(double).5);
    amc   = mc * a;
    tc    = tan(((double)GPS_PI/(double)4.)-polat/(double)2.) / pe;
  } else {
    op = (double)1. + e;
    om = (double)1. - e;
    e4 = pow(pow(op,op)*pow(om,om),(double).5);
  }


  dx = E - E0;
  dy = N - N0;
  if (!dx && !dy) {
    *phi = po2;
    *lambda = polon;
  } else {
    if (sh) {
      dx *= (double)-1.;
      dy *= (double)-1.;
    }
    rho = pow(dx*dx+dy*dy,(double).5);
    if (fabs(fabs(polat)-po2)>(double)1.0e-10) {
      t = rho * tc / amc;
    } else {
      t = rho * e4 / ta;
    }
    TPHI = (double)0.0;
    PHI  = po2 - (double)2.*atan(t);
    while (fabs(PHI-TPHI)>(double)1.0e-10) {
      TPHI=PHI;
      PHIS = sin(PHI);
      es = e * PHIS;
      pe    = pow(((double)1.-es)/((double)1.+es),eo2);
      PHI = po2 - (double)2. * atan(t*pe);
    }
    *phi = PHI;
    *lambda = polon + atan2(dx,-dy);

    if (*phi>po2) {
      *phi = po2;
    } else if (*phi<-po2) {
      *phi = -po2;
    }

    if (*lambda>GPS_PI) {
      *lambda -= p2;
    }
    if (*lambda<-GPS_PI) {
      *lambda += p2;
    }

    if (*lambda>GPS_PI) {
      *lambda = GPS_PI;
    } else if (*lambda<-GPS_PI) {
      *lambda=-GPS_PI;
    }
  }
  if (sh) {
    *phi *= (double)-1.;
    *lambda *= (double)1.;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Mollweide_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Mollweide projection easting and
** northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Mollweide_LatLon_To_EN(double phi, double lambda, double* E,
                                     double* N, double M0, double E0,
                                     double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double Ra;
  double sRa2;
  double sRa8;

  double ps;
  double dlam;
  double theta;
  double thetap;
  double d;
  double tol;

  phi     = GPS_Math_Deg_To_Rad(phi);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;

  Ra = a*((double)1.0-e2/(double)6.0-(double)17.0*e4/(double)360.0-
          (double)67.0*e6/(double)3024.0);
  sRa2 = pow((double)2.,(double).5) * Ra;
  sRa8 = pow((double)8.,(double).5) * Ra;

  if (M0>GPS_PI) {
    M0 -= p2;
  }

  ps  = sin(phi) * (double)GPS_PI;
  d   = (double)0.1745329;
  tol = (double)4.85e-10;
  thetap = phi;

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam-=p2;
  }
  if (dlam<-GPS_PI) {
    dlam+=p2;
  }

  while (fabs(d)>tol) {
    d = -(thetap+sin(thetap)-ps)/((double)1.+cos(thetap));
    thetap += d;
  }
  theta = thetap / (double)2.;
  *E = (sRa8/(double)GPS_PI) * dlam * cos(theta) + E0;
  *N = sRa2 * sin(theta) + N0;

  return;
}




/* @func GPS_Math_Mollweide_EN_To_LatLon **********************************
**
** Convert latitude and longitude to Mollweide projection easting and
** northing
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Mollweide_EN_To_LatLon(double E, double N, double* phi,
                                     double* lambda, double M0, double E0,
                                     double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double Ra;
  double sRa2;
  double sRa8;

  double dx;
  double dy;
  double theta=(double)0.;
  double tt;
  double i;

  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;

  Ra = a*((double)1.0-e2/(double)6.0-(double)17.0*e4/(double)360.0-
          (double)67.0*e6/(double)3024.0);
  sRa2 = pow((double)2.,(double).5) * Ra;
  sRa8 = pow((double)8.,(double).5) * Ra;

  if (M0>GPS_PI) {
    M0 -= p2;
  }

  dx = E - E0;
  dy = N - N0;
  i  = dy/sRa2;
  if (fabs(i)>(double)1.) {
    *phi = po2;
    if (N<(double)0.0) {
      *phi *= (double)-1.;
    }
  } else {
    theta = asin(i);
    tt = theta * (double)2.;
    *phi = asin((tt+sin(tt))/(double)GPS_PI);
    if (*phi>po2) {
      *phi=po2;
    } else if (*phi<-po2) {
      *phi=-po2;
    }
  }

  if (fabs(fabs(*phi)-po2)<(double)1.0e-10) {
    *lambda = M0;
  } else {
    *lambda = M0 + (double)GPS_PI * dx / (sRa8 * cos(theta));
  }


  if (*lambda>GPS_PI) {
    *lambda-=p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda+=p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Orthog_LatLon_To_EN **********************************
**
** Convert latitude and longitude to orthographic projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Orthog_LatLon_To_EN(double phi, double lambda, double* E,
                                  double* N, double phi0, double lambda0,
                                  double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double phi0s;
  double phi0c;

  double phis;
  double phic;
  double dlam;
  double clc;
  double cc;


  lambda  = GPS_Math_Deg_To_Rad(lambda);
  phi     = GPS_Math_Deg_To_Rad(phi);
  phi0    = GPS_Math_Deg_To_Rad(phi0);
  lambda0 = GPS_Math_Deg_To_Rad(lambda0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (lambda0>GPS_PI) {
    lambda0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a * ((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-(double)67.*
            e6/(double)3024.);
  phi0s = sin(phi0);
  phi0c = cos(phi0);

  dlam = lambda - lambda0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }


  phis = sin(phi);
  phic = cos(phi);
  clc = phic * cos(dlam);
  cc  = phi0s * phis + phi0c * clc;

  *E = Ra * phic * sin(dlam) + E0;
  *N = Ra * (phi0c * phis - phi0s * clc) + N0;

  return;
}




/* @func GPS_Math_Orthog_EN_To_LatLon **********************************
**
** Convert Orthogonal easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Orthog_EN_To_LatLon(double E, double N, double* phi,
                                  double* lambda, double phi0, double lambda0,
                                  double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double Ra;
  double phi0s;
  double phi0c;

  double dx;
  double dy;
  double rho;
  double adod;
  double ror;
  double cc;
  double ccs;
  double ccc;




  phi0    = GPS_Math_Deg_To_Rad(phi0);
  lambda0 = GPS_Math_Deg_To_Rad(lambda0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (lambda0>GPS_PI) {
    lambda0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  Ra = a * ((double)1.-e2/(double)6.-(double)17.*e4/(double)360.-(double)67.*
            e6/(double)3024.);
  phi0s = sin(phi0);
  phi0c = cos(phi0);


  dx = E - E0;
  dy = N - N0;
  adod = atan(dx/dy);
  rho = pow(dx*dx+dy*dy,(double).5);
  if (!rho) {
    *phi = phi0;
    *lambda = lambda0;
  } else {
    ror = rho/Ra;
    if (ror>(double)1.) {
      ror=(double)1.;
    } else if (ror<(double)-1.) {
      ror=(double)-1.;
    }
    cc = asin(ror);
    ccs = sin(cc);
    ccc = cos(cc);
    *phi = asin(ccc*phi0s+(dy*ccs*phi0c/rho));
    if (phi0==po2) {
      *lambda = lambda0 - adod;
    } else if (phi0==-po2) {
      *lambda = lambda0 + adod;
    } else {
      *lambda = lambda0+atan(dx*ccs/(rho*phi0c*ccc-dy*phi0s*ccs));
    }
  }

  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Polycon_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Polyconic  projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Polycon_LatLon_To_EN(double phi, double lambda, double* E,
                                   double* N, double phi0, double M0,
                                   double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double AM0;
  double c0;
  double c1;
  double c2;
  double c3;
  double j;
  double te4;
  double phi0s2;
  double phi0s4;
  double phi0s6;

  double phis;
  double phis2;
  double phis4;
  double phis6;
  double dlam;
  double NN;
  double NNot;
  double MM;
  double EE;
  double lat;


  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  AM0    = a*(lat-phi0s2+phi0s4-phi0s6);



  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  phis = sin(phi);

  if (!phi) {
    *E = a * dlam + E0;
    *N = -AM0 + N0;
  } else {
    NN = a / pow((double)1.-e2*phis*phis,(double).5);
    NNot = NN / tan(phi);
    lat = c0 * phi;
    phis2 = c1 * sin((double)2.0*phi);
    phis4 = c2 * sin((double)4.0*phi);
    phis6 = c3 * sin((double)6.0*phi);
    MM    = a*(lat-phis2+phis4-phis6);
    EE    = dlam *phis;
    *E = NNot * sin(EE) + E0;
    *N = MM - AM0 + NNot * ((double)1.-cos(EE)) + N0;
  }

  return;
}




/* @func GPS_Math_Polycon_EN_To_LatLon **********************************
**
** Convert Polyconic easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Polycon_EN_To_LatLon(double E, double N, double* phi,
                                   double* lambda, double phi0, double M0,
                                   double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double AM0;
  double c0;
  double c1;
  double c2;
  double c3;
  double j;
  double te4;
  double phi0s2;
  double phi0s4;
  double phi0s6;

  double dx;
  double dy;
  double dxoa;
  double AA;
  double BB;
  double CC=(double)0.;
  double PHIn;
  double PHId;
  double PHIs;
  double PHI;
  double PHIs2;
  double PHIs4;
  double PHIs6;
  double Mn;
  double Mnp;
  double Ma;
  double AAMa;
  double mpb;
  double AAmin;
  double tol;
  double lat;


  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;

  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  AM0    = a*(lat-phi0s2+phi0s4-phi0s6);

  tol = (double)1.0e-12;

  dx = E - E0;
  dy = N - N0;
  dxoa = dx/a;
  if ((((-AM0-(double)1.)<dy)&&(dy<(-AM0+(double)1.)))) {
    *phi = (double)0.;
    *lambda = dxoa + M0;
  } else {
    AA = (AM0+dy) / a;
    BB = dxoa * dxoa + (AA*AA);
    PHIn = AA;
    PHId = (double)1.;

    while (fabs(PHId)>tol) {
      PHIs = sin(PHIn);
      CC = pow((double)1.-e2*PHIs*PHIs,(double).5) * tan(PHIn);
      PHI = PHIn * c0;
      PHIs2 = c1 * sin((double)2.0*PHIn);
      PHIs4 = c2 * sin((double)4.0*PHIn);
      PHIs6 = c3 * sin((double)6.0*PHIn);
      Mn    = a*(PHI-PHIs2+PHIs4-PHIs6);
      Mnp = c0 - (double)2.*c1*cos((double)2.*PHIn)+(double)4.*c2*
            cos((double)4.*PHIn)-(double)6.*c3*cos((double)6.*PHIn);
      Ma = Mn / a;
      AAMa = AA * Ma;
      mpb = Ma*Ma+BB;
      AAmin = AA - Ma;
      PHId = (AAMa*CC+AAmin-(double).5*mpb*CC)/
             (e2*PHIs2*(mpb-(double)2.*AAMa) /
              (double)4.*CC+AAmin*(CC*Mnp-(double)2./PHIs2)-Mnp);
      PHIn -= PHId;
    }
    *phi = PHIn;

    if (*phi>po2) {
      *phi = po2;
    } else if (*phi<-po2) {
      *phi = -po2;
    }

    if ((((po2-(double).00001)<fabs(*phi))&&
         (fabs(*phi)<(po2+(double).00001)))) {
      *lambda = M0;
    } else {
      *lambda = (asin(dxoa*CC)) / sin(*phi) + M0;
    }
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Sinusoid_LatLon_To_EN **********************************
**
** Convert latitude and longitude to Sinusoidal projection easting and
** northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Sinusoid_LatLon_To_EN(double phi, double lambda, double* E,
                                    double* N, double M0, double E0,
                                    double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double c0;
  double c1;
  double c2;
  double c3;
  double A1;
  double A0;
  double A2;
  double A3;
  double E1;
  double E2;
  double E3;
  double E4;
  double j;
  double om0;
  double som0;

  double phis;
  double phis2;
  double phis4;
  double phis6;
  double mm;
  double MM;
  double dlam;


  phi     = GPS_Math_Deg_To_Rad(phi);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;

  j = (double)45.*e6/(double)1024.;
  c0 = (double)1.-e2/(double)4.-(double)3.*e4/(double)64.-(double)5.*
       e6/(double)256.;
  c1 = (double)3.*e2/(double)8.+(double)3.*e4/(double)32.+j;
  c2 = (double)15.*e4/(double)256.+j;
  c3 = (double)35.*e6/(double)3072.;
  om0 = (double)1. - e2;
  som0 = pow(om0,(double).5);
  E1 = ((double)1.-som0)/((double)1.+som0);
  E2 = E1*E1;
  E3 = E1*E2;
  E4 = E1*E3;
  A0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  A1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  A2 = (double)151.*E3/(double)96.;
  A3 = (double)1097.*E4/(double)512.;

  if (M0>GPS_PI) {
    M0 -= p2;
  }

  phis = sin(phi);

  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam-=p2;
  }
  if (dlam<-GPS_PI) {
    dlam+=p2;
  }

  mm = pow((double)1.-e2*phis*phis,(double).5);
  phis2 = c1 * sin((double)2.*phi);
  phis4 = c2 * sin((double)4.*phi);
  phis6 = c3 * sin((double)6.*phi);
  MM = a * (c0*phi-phis2+phis4-phis6);



  *E = a*dlam*cos(phi)/mm+E0;
  *N = MM + N0;

  return;
}




/* @func GPS_Math_Sinusoid_EN_To_LatLon **********************************
**
** Convert latitude and longitude to Sinusoidal projection easting and
** northing
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Sinusoid_EN_To_LatLon(double E, double N, double* phi,
                                    double* lambda, double M0, double E0,
                                    double N0, double a, double b)
{
  double a2;
  double b2;
  double e2;
  double e4;
  double e6;
  double p2;
  double po2;
  double c0;
  double c1;
  double c2;
  double c3;
  double A1;
  double A0;
  double A2;
  double A3;
  double E1;
  double E2;
  double E3;
  double E4;
  double j;
  double om0;
  double som0;

  double dx;
  double dy;
  double mu;
  double mu2s;
  double mu4s;
  double mu6s;
  double mu8s;
  double phis;


  M0      = GPS_Math_Deg_To_Rad(M0);

  po2 = (double)GPS_PI / (double)2.0;
  p2  = (double)GPS_PI * (double)2.0;
  a2  = a*a;
  b2  = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e4*e2;

  j = (double)45.*e6/(double)1024.;
  c0 = (double)1.-e2/(double)4.-(double)3.*e4/(double)64.-(double)5.*
       e6/(double)256.;
  c1 = (double)3.*e2/(double)8.+(double)3.*e4/(double)32.+j;
  c2 = (double)15.*e4/(double)256.+j;
  c3 = (double)35.*e6/(double)3072.;
  om0 = (double)1. - e2;
  som0 = pow(om0,(double).5);
  E1 = ((double)1.-som0)/((double)1.+som0);
  E2 = E1*E1;
  E3 = E1*E2;
  E4 = E1*E3;
  A0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  A1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  A2 = (double)151.*E3/(double)96.;
  A3 = (double)1097.*E4/(double)512.;


  dx = E - E0;
  dy = N - N0;

  mu = dy/(c0*a);
  mu2s = A0 * sin((double)2.*mu);
  mu4s = A1 * sin((double)4.*mu);
  mu6s = A2 * sin((double)6.*mu);
  mu8s = A3 * sin((double)8.*mu);
  *phi = mu + mu2s + mu4s + mu6s + mu8s;

  if (*phi>po2) {
    *phi=po2;
  } else if (*phi<-po2) {
    *phi=-po2;
  }

  if ((((po2-(double)1.0e-8)<fabs(*phi))&&(fabs(*phi)<(po2+(double)1.0e-8)))) {
    *lambda = M0;
  } else {
    phis = sin(*phi);
    *lambda = M0 + dx*pow((double)1.0-e2*phis*phis,(double).5) /
              (a*cos(*phi));
  }

  if (*lambda>GPS_PI) {
    *lambda-=p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda+=p2;
  }

  if (*lambda>GPS_PI) {
    *lambda=GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_TCylEA_LatLon_To_EN **********************************
**
** Convert latitude and longitude to transverse cylindrical  equal area
** projection easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_TCylEA_LatLon_To_EN(double phi, double lambda, double* E,
                                  double* N, double phi0, double M0, double E0,
                                  double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e;
  double e2;
  double e4;
  double e6;
  double AM0;
  double qp;
  double om;
  double oo;
  double c0;
  double c1;
  double c2;
  double c3;
  double b0;
  double b1;
  double B2;
  double b3;
  double A0;
  double A1;
  double A2;
  double sf;
  double x;
  double som;
  double phis;
  double j;
  double te4;
  double lat;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double E1;
  double E2;
  double E3;
  double E4;

  double dlam;
  double qq;
  double qqo;
  double bt;
  double btc;
  double PHI;
  double PHIs2;
  double PHIs4;
  double PHIs6;
  double bts2;
  double bts4;
  double bts6;
  double PHIc;
  double PHIcs;
  double Mc;



  sf = (double)1.0; /* scale factor */

  lambda = GPS_Math_Deg_To_Rad(lambda);
  phi    = GPS_Math_Deg_To_Rad(phi);
  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  e  = pow(e2,(double).5);
  om = (double)1.-e2;
  som  = pow(om,(double).5);
  oo = (double)1./((double)2.*e);

  phis = sin(po2);
  x  = e * phis;
  qp = om*(phis/((double)1.-e2*phis*phis)-oo*
           log(((double)1.-x)/((double)1.+x)));

  A0 = e2 / (double)3.+(double)31.*e4/(double)180.+(double)517.*
       e6/(double)5040.;
  A1 = (double)23.*e4/(double)360.+(double)251.*e6/(double)3780.;
  A2 = (double)761.*e6/(double)45360.;

  E1 = ((double)1.0-som) / ((double)1.0+som);
  E2 = E1*E1;
  E3 = E2*E1;
  E4 = E3*E1;

  b0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  b1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  B2 = (double)151.*E3/(double)96.;
  b3 = (double)1097.*E4/(double)512.;


  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  AM0 = a*(lat-phi0s2+phi0s4-phi0s6);


  dlam = lambda - M0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }

  phis = sin(phi);

  if (phi==po2) {
    qq = qp;
    qqo = (double)1.;
  } else {
    x = e * phis;
    qq = om*(phis/((double)1.-e2*phis*phis)-oo*
             log(((double)1.-x)/((double)1.+x)));
    qqo = qq/qp;
  }

  if (qqo>(double)1.) {
    qqo = (double)1.;
  } else if (qqo<(double)-1.) {
    qqo = (double)-1.;
  }

  bt = asin(qqo);
  btc = atan(tan(bt)/cos(dlam));

  if ((fabs(btc)-po2)>(double)1.0e-8) {
    PHIc = btc;
  } else {
    bts2 = A0 * sin((double)2.0*btc);
    bts4 = A1 * sin((double)4.0*btc);
    bts6 = A2 * sin((double)6.0*btc);
    PHIc = btc + bts2 + bts4 + bts6;
  }

  PHIcs = sin(PHIc);
  *E = a*cos(bt)*cos(PHIc)*sin(dlam)/(sf*cos(btc)*
                                      pow((double)1.-e2*PHIcs*PHIcs,
                                          (double).5)) + E0;
  PHI = c0 * PHIc;
  PHIs2 = c1 * sin((double)2.0*PHIc);
  PHIs4 = c2 * sin((double)4.0*PHIc);
  PHIs6 = c3 * sin((double)6.0*PHIc);
  Mc = a*(PHI-PHIs2+PHIs4-PHIs6);

  *N = sf * (Mc-AM0) + N0;

  return;
}




/* @func GPS_Math_TCylEA_EN_To_LatLon **********************************
**
** Convert transverse cylindrical equal area easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] M0 [double] central meridian (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_TCylEA_EN_To_LatLon(double E, double N, double* phi,
                                  double* lambda, double phi0, double M0,
                                  double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e;
  double e2;
  double e4;
  double e6;
  double AM0;
  double qp;
  double om;
  double oo;
  double c0;
  double c1;
  double c2;
  double c3;
  double b0;
  double b1;
  double B2;
  double b3;
  double A0;
  double A1;
  double A2;
  double sf;
  double x;
  double som;
  double phis;
  double j;
  double te4;
  double lat;
  double phi0s2;
  double phi0s4;
  double phi0s6;
  double E1;
  double E2;
  double E3;
  double E4;

  double dx;
  double dy;
  double bt;
  double btc;
  double btp;
  double btcc;
  double Mc;
  double Muc;
  double mus2;
  double mus4;
  double mus6;
  double mus8;
  double bts2;
  double bts4;
  double bts6;
  double PHIc;
  double Qc;
  double Qco;
  double t;


  sf = (double)1.0; /* scale factor */

  phi0   = GPS_Math_Deg_To_Rad(phi0);
  M0     = GPS_Math_Deg_To_Rad(M0);

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (M0>GPS_PI) {
    M0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  e2 = (a2-b2)/a2;
  e4 = e2*e2;
  e6 = e2*e4;
  e  = pow(e2,(double).5);
  om = (double)1.-e2;
  som  = pow(om,(double).5);
  oo = (double)1./((double)2.*e);

  phis = sin(po2);
  x  = e * phis;
  qp = om*(phis/((double)1.-e2*phis*phis)-oo*
           log(((double)1.-x)/((double)1.+x)));

  A0 = e2 / (double)3.+(double)31.*e4/(double)180.+(double)517.*
       e6/(double)5040.;
  A1 = (double)23.*e4/(double)360.+(double)251.*e6/(double)3780.;
  A2 = (double)761.*e6/(double)45360.;

  E1 = ((double)1.0-som) / ((double)1.0+som);
  E2 = E1*E1;
  E3 = E2*E1;
  E4 = E3*E1;

  b0 = (double)3.*E1/(double)2.-(double)27.*E3/(double)32.;
  b1 = (double)21.*E2/(double)16.-(double)55.*E4/(double)32.;
  B2 = (double)151.*E3/(double)96.;
  b3 = (double)1097.*E4/(double)512.;


  j = (double)45.0*e6/(double)1024.0;
  te4 = (double)3.0 * e4;
  c0 = (double)1.0-e2/(double)4.0-te4/(double)64.0-(double)5.0*e6/
       (double)256.0;
  c1 = (double)3.0*e2/(double)8.0+te4/(double)32.0+j;
  c2 = (double)15.0*e4/(double)256.0+j;
  c3 = (double)35.0*e6/(double)3072.0;

  lat = c0 * phi0;

  phi0s2 = c1 * sin((double)2.0*phi0);
  phi0s4 = c2 * sin((double)4.0*phi0);
  phi0s6 = c3 * sin((double)6.0*phi0);
  AM0 = a*(lat-phi0s2+phi0s4-phi0s6);



  dx = E - E0;
  dy = N - N0;
  Mc = AM0 + dy/sf;
  Muc = Mc / (c0*a);

  mus2 = b0 * sin((double)2.0*Muc);
  mus4 = b1 * sin((double)4.0*Muc);
  mus6 = B2 * sin((double)6.0*Muc);
  mus8 = b3 * sin((double)6.0*Muc);
  PHIc = Muc + mus2 + mus4 + mus6 + mus8;

  phis = sin(PHIc);
  x = e * phis;
  Qc = om*(phis/((double)1.-e2*phis*phis)-oo*
           log(((double)1.-x)/((double)1.+x)));
  Qco = Qc/qp;

  if (Qco>(double)1.) {
    Qco = (double)1.;
  } else if (Qco<(double)-1.) {
    Qco = (double)-1.;
  }

  btc = asin(Qco);
  btcc = cos(btc);
  t = sf*dx*btcc*pow((double)1.-e2*phis*phis,(double).5)/(a*cos(PHIc));
  if (t>(double)1.) {
    t=(double)1.;
  } else if (t<(double)-1.) {
    t=(double)-1.;
  }
  btp = -asin(t);
  bt = asin(cos(btp)*sin(btc));

  bts2 = A0 * sin((double)2.0*bt);
  bts4 = A1 * sin((double)4.0*bt);
  bts6 = A2 * sin((double)6.0*bt);
  *phi = bt + bts2 + bts4 + bts6;
  *lambda = M0 - atan(tan(btp)/btcc);

  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_Mercator_LatLon_To_EN **********************************
**
** Convert latitude and longitude to standard Mercator projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Mercator_LatLon_To_EN(double phi, double lambda, double* E,
                                    double* N, double phi0, double lambda0,
                                    double E0, double N0, double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e3;
  double e;
  double es;
  double ab;
  double bb;
  double cb;
  double db;
  double ml;
  double phi0s;
  double sf;

  double dlam;
  double ct;
  double ex;
  double tt;
  double pt;


  lambda  = GPS_Math_Deg_To_Rad(lambda);
  phi     = GPS_Math_Deg_To_Rad(phi);
  phi0    = GPS_Math_Deg_To_Rad(phi0);
  lambda0 = GPS_Math_Deg_To_Rad(lambda0);

  ml = ((double)GPS_PI*(double)89.5)/(double)180.;

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (lambda0>GPS_PI) {
    lambda0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  es = (a2-b2)/a2;
  e2 = es*es;
  e3 = e2*es;
  e4 = e3*es;

  e  = pow(es,(double).5);
  phi0s = sin(phi0);
  sf = (double)1. / (pow((double)1.-es*phi0s*phi0s,(double).5)/cos(phi0));

  ab = es/(double)2.+(double)5.*e2/(double)24.+e3/(double)12.+(double)13.*
       e4/(double)360.;
  bb = (double)7.*e2/(double)48.+(double)29.*e3/(double)240.+
       (double)811.*e4/(double)11520.;
  cb = (double)7.*e3/(double)120.+(double)81.*e4/(double)1120.;
  db = (double)4279.*e4/(double)161280.;



  if (lambda>(double)GPS_PI) {
    lambda -= p2;
  }

  dlam = lambda - lambda0;
  if (dlam>GPS_PI) {
    dlam -= p2;
  }
  if (dlam<-GPS_PI) {
    dlam += p2;
  }


  ex = e * sin(phi);
  tt = tan((double)GPS_PI/(double)4.+phi/(double)2.);
  pt = pow((((double)1.-ex)/((double)1.+ex)),(e/(double)2.));

  ct = tt * pt;
  *N = sf * a * log(ct) + N0;
  *E = sf * a * dlam + E0;

  return;
}




/* @func GPS_Math_Mercator_EN_To_LatLon **********************************
**
** Convert standard Mercator easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_Mercator_EN_To_LatLon(double E, double N, double* phi,
                                    double* lambda, double phi0,
                                    double lambda0, double E0, double N0,
                                    double a, double b)
{
  double p2;
  double po2;
  double a2;
  double b2;
  double e2;
  double e4;
  double e3;
  double e;
  double es;
  double ab;
  double bb;
  double cb;
  double db;
  double ml;
  double phi0s;
  double sf;

  double dx;
  double dy;
  double x;

  phi0    = GPS_Math_Deg_To_Rad(phi0);
  lambda0 = GPS_Math_Deg_To_Rad(lambda0);

  ml = ((double)GPS_PI*(double)89.5)/(double)180.;

  p2 = (double)GPS_PI * (double)2.0;
  po2 = (double)GPS_PI / (double)2.0;
  if (lambda0>GPS_PI) {
    lambda0 -= p2;
  }
  a2 = a*a;
  b2 = b*b;
  es = (a2-b2)/a2;
  e2 = es*es;
  e3 = e2*es;
  e4 = e3*es;

  e  = pow(es,(double).5);
  phi0s = sin(phi0);
  sf = (double)1. / (pow((double)1.-es*phi0s*phi0s,(double).5)/cos(phi0));

  ab = es/(double)2.+(double)5.*e2/(double)24.+e3/(double)12.+(double)13.*
       e4/(double)360.;
  bb = (double)7.*e2/(double)48.+(double)29.*e3/(double)240.+
       (double)811.*e4/(double)11520.;
  cb = (double)7.*e3/(double)120.+(double)81.*e4/(double)1120.;
  db = (double)4279.*e4/(double)161280.;

  dx = E - E0;
  dy = N - N0;
  *lambda = lambda0 + dx / (sf*a);
  x = (double)GPS_PI / (double)2. -
      (double)2.*atan((double)1./exp(dy/(sf*a)));
  *phi = x+ab*sin((double)2.*x)+bb*sin((double)4.*x)+cb*sin((double)6.*x)
         + db*sin((double)8.*x);

  if (*phi>po2) {
    *phi = po2;
  } else if (*phi<-po2) {
    *phi = -po2;
  }

  if (*lambda>GPS_PI) {
    *lambda -= p2;
  }
  if (*lambda<-GPS_PI) {
    *lambda += p2;
  }

  if (*lambda>GPS_PI) {
    *lambda = GPS_PI;
  } else if (*lambda<-GPS_PI) {
    *lambda=-GPS_PI;
  }

  *lambda = GPS_Math_Rad_To_Deg(*lambda);
  *phi    = GPS_Math_Rad_To_Deg(*phi);

  return;
}




/* @func GPS_Math_TMerc_LatLon_To_EN **********************************
**
** Convert latitude and longitude to transverse Mercator projection
**  easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] F0 [double] scale factor
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_TMerc_LatLon_To_EN(double phi, double lambda, double* E,
                                 double* N, double phi0, double lambda0,
                                 double E0, double N0, double F0,
                                 double a, double b)
{
  GPS_Math_LatLon_To_EN(E,N,phi,lambda,N0,E0,phi0,lambda0,F0,a,b);

  return;
}




/* @func GPS_Math_TMerc_EN_To_LatLon **********************************
**
** Convert transverse Mercator easting and northing projection
** to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude of origin (deg)
** @param [r] lambda0 [double] longitude of origin (deg)
** @param [r] E0 [double] false easting
** @param [r] N0 [double] false northing
** @param [r] F0 [double] scale factor
** @param [r] a [double] semi-major axis
** @param [r] b [double] semi-minor axis
**
** @return [void]
************************************************************************/
void GPS_Math_TMerc_EN_To_LatLon(double E, double N, double* phi,
                                 double* lambda, double phi0, double lambda0,
                                 double E0, double N0, double F0,
                                 double a, double b)
{
  GPS_Math_EN_To_LatLon(E,N,phi,lambda,N0,E0,phi0,lambda0,F0,a,b);

  return;
}




/* @func GPS_Math_Swiss_LatLon_To_EN ***********************************
**
** Convert latitude and longitude to Swiss grid easting and northing
**
** @param [r] phi [double] latitude (deg)
** @param [r] lambda [double] longitude (deg)
** @param [w] E [double *] easting (metre)
** @param [w] N [double *] northing (metre)
** @param [r] phi0 [double] latitude origin (deg)     [normally 46.95240556]
** @param [r] lambda0 [double] longitude origin (deg) [normally  7.43958333]
** @param [r] E0 [double] false easting (metre)       [normally 600000.0]
** @param [r] N0 [double] false northing (metre)      [normally 200000.0]
** @param [r] a [double] semi-major axis              [normally 6377397.000]
** @param [r] b [double] semi-minor axis              [normally 6356078.823]
**
** @return [void]
***************************************************************************/
void GPS_Math_Swiss_LatLon_To_EN(double phi, double lambda, double* E,
                                 double* N,double phi0,double lambda0,
                                 double E0, double N0, double a, double b)

{
  double a2;
  double b2;
  double esq;
  double e;
  double c;
  double ephi0p;
  double phip;
  double sphip;
  double phid;
  double slambda2;
  double lambda1;
  double lambda2;
  double K;
  double po4;
  double w;
  double R;

  lambda0 = GPS_Math_Deg_To_Rad(lambda0);
  phi0    = GPS_Math_Deg_To_Rad(phi0);
  lambda  = GPS_Math_Deg_To_Rad(lambda);
  phi     = GPS_Math_Deg_To_Rad(phi);

  po4=GPS_PI/(double)4.0;

  a2 = a*a;
  b2 = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);

  c = sqrt(1+((esq*pow(cos(phi0),(double)4.))/((double)1.-esq)));

  ephi0p = asin(sin(phi0)/c);

  K = log(tan(po4+ephi0p/(double)2.)) - c*(log(tan(po4+phi0/(double)2.)) -
      e/(double)2. * log(((double)1.+e*sin(phi0)) /
                         ((double)1.-e*sin(phi0))));
  lambda1 = c*(lambda-lambda0);
  w = c*(log(tan(po4+phi/(double)2.)) - e/(double)2. *
         log(((double)1.+e*sin(phi)) / ((double)1.-e*sin(phi)))) + K;


  phip = (double)2. * (atan(exp(w)) - po4);

  sphip = cos(ephi0p) * sin(phip) - sin(ephi0p) * cos(phip) * cos(lambda1);
  phid  = asin(sphip);

  slambda2 = cos(phip)*sin(lambda1) / cos(phid);
  lambda2  = asin(slambda2);

  R = a*sqrt((double)1.-esq) / ((double)1.-esq*sin(phi0) * sin(phi0));

  *N = R*log(tan(po4 + phid/(double)2.)) + N0;
  *E = R*lambda2 + E0;
  return;
}




/* @func GPS_Math_Swiss_EN_To_LatLon ************************************
**
** Convert Swiss Grid easting and northing to latitude and longitude
**
** @param [r] E [double] easting (metre)
** @param [r] N [double] northing (metre)
** @param [w] phi [double *] latitude (deg)
** @param [w] lambda [double *] longitude (deg)
** @param [r] phi0 [double] latitude origin (deg)     [normally 46.95240556]
** @param [r] lambda0 [double] longitude origin (deg) [normally  7.43958333]
** @param [r] E0 [double] false easting (metre)       [normally 600000.0]
** @param [r] N0 [double] false northing (metre)      [normally 200000.0]
** @param [r] a [double] semi-major axis              [normally 6377397.000]
** @param [r] b [double] semi-minor axis              [normally 6356078.823]
**
** @return [void]
*************************************************************************/

void GPS_Math_Swiss_EN_To_LatLon(double E, double N, double* phi,
                                 double* lambda, double phi0, double lambda0,
                                 double E0, double N0, double a, double b)
{
  double a2;
  double b2;
  double esq;
  double e;
  double R;
  double c;
  double po4;
  double phid;
  double phi1;
  double lambdad;
  double lambda1;
  double slambda1;
  double ephi0p;
  double sphip;
  double tol;
  double cr;
  double C;
  double K;

  lambda0 = GPS_Math_Deg_To_Rad(lambda0);
  phi0    = GPS_Math_Deg_To_Rad(phi0);

  po4=GPS_PI/(double)4.0;
  tol=(double)0.00001;

  a2 = a*a;
  b2 = b*b;
  esq = (a2-b2)/a2;
  e   = pow(esq,(double)0.5);

  R = a*sqrt((double)1.-esq) / ((double)1.-esq*sin(phi0) * sin(phi0));

  phid = (double)2.*(atan(exp((N - N0)/R)) - po4);
  lambdad = (E - E0)/R;

  c = sqrt((double)1.+((esq * pow(cos(phi0), (double)4.)) /
                       ((double)1.-esq)));
  ephi0p = asin(sin(phi0) / c);

  sphip = cos(ephi0p)*sin(phid) + sin(ephi0p)*cos(phid)*cos(lambdad);
  phi1 = asin(sphip);

  slambda1 = cos(phid)*sin(lambdad)/cos(phi1);
  lambda1  = asin(slambda1);

  *lambda = GPS_Math_Rad_To_Deg((lambda1/c + lambda0));

  K = log(tan(po4 + ephi0p/(double)2.)) -c*(log(tan(po4 + phi0/(double)2.))
      - e/(double)2. * log(((double)1.+e*sin(phi0)) /
                           ((double)1.-e*sin(phi0))));
  C = (K - log(tan(po4 + phi1/(double)2.)))/c;

  do {
    cr = (C + log(tan(po4 + phi1/(double)2.)) - e/(double)2. *
          log(((double)1.+e*sin(phi1)) / ((double)1.-e*sin(phi1)))) *
         ((((double)1.-esq*sin(phi1)*sin(phi1)) * cos(phi1)) /
          ((double)1.-esq));
    phi1 -= cr;
  } while (fabs(cr) > tol);

  *phi = GPS_Math_Rad_To_Deg(phi1);

  return;
}
