/* **********************************************************************
 * *	                                                                *
 * *             Routines to Calculate Earth/Map Projections            *
 * *                                                                    *
 * **********************************************************************
 * File: libs/himath/MapProjInt.c
 *
 * COPYRIGHT (c) 1996, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006 BY
 *            VAISALA INC., WESTFORD MASSACHUSETTS, U.S.A.
 * 
 * THIS SOFTWARE IS FURNISHED UNDER A LICENSE AND MAY BE USED AND  COPIED
 * ONLY  IN  ACCORDANCE WITH  THE  TERMS  OF  SUCH  LICENSE  AND WITH THE
 * INCLUSION OF THE ABOVE COPYRIGHT NOTICE.  THIS SOFTWARE  OR  ANY OTHER
 * COPIES  THEREOF MAY NOT BE PROVIDED OR OTHERWISE MADE AVAILABLE TO ANY
 * OTHER PERSON.  NO TITLE TO AND OWNERSHIP OF  THE  SOFTWARE  IS  HEREBY
 * TRANSFERED.
 */

#include <math.h>
#include <stdio.h>
#include <string.h>

#include "sigtypes.h"
#include "projection.h"
#include "himath_lib.h"
#include "user_lib.h"

#define DEBUG (0)

#define RANGERROR (0.003)	       /* Permissible range error in km */

/* ==================================================
 * Internal prototypes
 */
static FLT8 fTransverseMercatorM
( FLT8 fa_a,
  FLT8 fe2_a,
  FLT8 fPhi_a );  /* Latitude */

static void LambertSupport
( const struct mpj_projection8 *projection_a, /* Type of map projection */
  FLT8 fa_a,
  FLT8 fe_a,
  FLT8 *fn_a,
  FLT8 *fF_a,
  FLT8 *fRho0_a );

static FLT8 fNormalizeAngle
( FLT8 fRawAngle_a );

/* ==================================================
 * Internal routine -- Validate the projection type.
 */
MESSAGE mpj_validate_projection
  (const struct mpj_projection8 *projection_a)  /* Type of map projection */
{
  if (projection_a->iprojection_type>=MAPROJ_COUNT) return HIMATH_MPJ_BADPROJ ;

  return SS_NORMAL;
}

/* ==================================================
 * Extract information about the Earths's Geode model from the projection
 * definition.
 */
void mpj_get_geode_info
( FLT8 *pfa_a,    /* a is the equitorial radius of the earth in meters */
  FLT8 *pfb_a,    /* b is the polar radius of the earth in meters */
  FLT8 *pfe_a,    /* e is the eccentricity of the earth */
  FLT8 *pff_a,    /* f is 1/the flattening of the earth, 0 for circular */
  const struct mpj_projection8 *projection_a )
{

  /* Since legacy structures will not have a earth radius filled in,
   * all invalid numbers will map to the legacy spherical earth.
   */
  if ( (projection_a->fEarthRadius<(FLT8)IMAP_EARTH_MIN_RADIUS) ||
       (projection_a->fEarthRadius>(FLT8)IMAP_EARTH_MAX_RADIUS))
    {
      *pfa_a = 1000.0*FEARTH_MEAN_RADIUS;
      *pfb_a = *pfa_a;
      *pfe_a = 0.0;
      *pff_a = 0.0;
      return;
    }
  
  *pfa_a = 1000.0*projection_a->fEarthRadius;
  
  /* Invalid flattening values also map to a spherical earth */
  if ( (projection_a->fFlattening<(FLT8)IMAP_EARTH_MIN_FLAT) ||
       (projection_a->fFlattening>(FLT8)IMAP_EARTH_MAX_FLAT))
    {
      *pfb_a = *pfa_a;
      *pfe_a = 0.0;
      *pff_a = 0.0;
    }
  else
    {
      FLT8 fFlat= 1.0/projection_a->fFlattening;
      *pfb_a = *pfa_a*(1.0-fFlat);
      *pfe_a = sqrt( 2.0*fFlat - fFlat*fFlat );
      *pff_a = projection_a->fFlattening;
    }
  return;

}
/* ==================================================
 * Return the reverse flattening from the a and b earth radii.
 */
FLT8 mpj_rf_from_a_b
( FLT8 fa_a,     /* a is the equitorial radius of the earth in meters */
  FLT8 fb_a )    /* b is the polar radius of the earth in meters */
  
{
  FLT8 fRatio, fFlat;
  /* Check for reasonable limits on the radii
   */
  if ( (fa_a<1000.0*(FLT8)IMAP_EARTH_MIN_RADIUS) ||
       (fa_a>1000.0*(FLT8)IMAP_EARTH_MAX_RADIUS))  return 0.0;
  
  if ( (fb_a<1000.0*(FLT8)IMAP_EARTH_MIN_RADIUS) ||
       (fb_a>1000.0*(FLT8)IMAP_EARTH_MAX_RADIUS))  return 0.0;
  
  fRatio = fb_a/fa_a;

  if (fRatio >= 1.0) return 0.0;  /* Wrong direction ellipse */

  fFlat = 1.0 - fRatio;

  /* Check for reasonable limits on flattening */
  if ( (fFlat<(1.0/(FLT8)IMAP_EARTH_MAX_FLAT)) ||
       (fFlat>(1.0/(FLT8)IMAP_EARTH_MIN_FLAT)) ) return 0.0;
  
  return ( 1.0/fFlat);
  
}
/* ==================================================
 * Internal routine -- Given a map projection definition, convert a Lat/Lon
 * Earth location to a coordinate in the map.  The map coordinates are
 * based in km, and have an arbitrary point of origin.
 */
MESSAGE mpj_earth_to_map
(const struct mpj_projection8 *projection_a, /* Type of map projection */
 volatile const struct latlon8 *pEarth_a,    /* Earth coordinates (radians) */
 struct XYPoint8 *pMap_a,                    /* Map coordinates (unit earth) */
 UINT1 *plValid_a )                          /* Map output is valid */  
{
  FLT8 fa, fe;  /* Earth radius and ecentricity */
  FLT8 fb, ff;
  MESSAGE istatus;

  if (DEBUG) fprintf( stderr, "mpj_earth_to_map called, Lat=%6.4f, Lon=%6.4f\n",
		      (180.0/FPI)*pEarth_a->fLat, (180.0/FPI)*pEarth_a->fLon);
  istatus = mpj_validate_projection( projection_a ) ;
  if( istatus != SS_NORMAL ) return( istatus ) ;
  *plValid_a = TRUE;

  mpj_get_geode_info( &fa, &fb, &fe, &ff, projection_a );

  if (DEBUG) fprintf( stderr, "Geode: Radius: %8.2f m, Ecentricity: %9.8f\n", fa, fe );
  switch( projection_a->iprojection_type )
    {
    case MAPROJ_MERCATOR:
      {
	/* Equations:
	 * X = Lon - LonRef
	 * Y = ln( tan( PI/4 + Lat/2 ) );
	 */
	FLT8 fRelLon;
	fRelLon = fNormalizeAngle(pEarth_a->fLon - projection_a->Ref.fLon);
	
	pMap_a->fX = FEARTH_MEAN_RADIUS * fRelLon;
	pMap_a->fY = FEARTH_MEAN_RADIUS * log( tan( (FPI/4.0) + (0.5 * pEarth_a->fLat) ) ) ;
      }
      break ;

    case MAPROJ_AZIMUTHAL:
    default:
      {
	struct D2Polar8 Polar;
	PolarFromEarth( &projection_a->Ref, pEarth_a, &Polar ) ;
        pMap_a->fX = Polar.fRange * sin( Polar.fAz ) ;
        pMap_a->fY = Polar.fRange * cos( Polar.fAz ) ;
      }
      break ;

    case MAPROJ_POLSTEREO:
      {
	/* Equations:
	 * X =  R * tan( PI/4 - Lat/2 ) * sin( Lon - LonRef );
	 * Y = -R * tan( PI/4 - Lat/2 ) * cos( Lon - LonRef );
	 */
        FLT8  fTanLat, fRelLon ;
	fTanLat = tan( ((FPI/2.0) - pEarth_a->fLat) / 2.0 );
        fRelLon = pEarth_a->fLon - projection_a->Ref.fLon;
        pMap_a->fX =  fTanLat * FEARTH_MEAN_RADIUS * sin( fRelLon ) ;
        pMap_a->fY = -fTanLat * FEARTH_MEAN_RADIUS * cos( fRelLon ) ;
      }
      break ;

    case MAPROJ_UTM:
    case MAPROJ_GAUSS:
      {
	FLT8 fCosLat, fTanLat, fSinLat, fe2;
	FLT8 fA, fM, fM0, fC, fT, fN, fePrime2;
	/* Equations from USGS 1395 Page 60-63
	 * A = (Lat - LatRef)cos(Lon)                    (8-15)
	 * ePrime2 = e**2 / ( 1 - e**2)                  (8-12)
	 * C = ePrime2 * cos(Lat)**2                     (8-14)
	 * T = tan(Lat)**2                               (8-13)
	 * N = a/sqrt(1 - e**2 * sin(Lat)**2)            (4-20)
	 */
	fe2 = fe*fe;
	fM      = fTransverseMercatorM(fa,fe2,pEarth_a->fLat);
	fM0     = fTransverseMercatorM(fa,fe2,projection_a->Ref.fLat);
	fCosLat = cos(pEarth_a->fLat);
	if (DEBUG) fprintf( stderr, "e**2=%10.9f, M=%8.1f m, M0=%8.1f m\n", fe2, fM, fM0 );
	/* Special case at the poles */
	if (0.0==fCosLat)
	  {
	    pMap_a->fX = 0.0;
	    pMap_a->fY = fM - fM0;
	  }
	else
	  {
	    FLT8 fConst1, fConst2, fConst3, fA2, fA3, fA4, fA5, fA6;
	    fSinLat = sin(pEarth_a->fLat);
	    fTanLat = tan(pEarth_a->fLat);
	    fA      = fNormalizeAngle( pEarth_a->fLon - projection_a->Ref.fLon) * fCosLat;
	    fA2 = fA*fA; fA3 = fA*fA2; fA4 = fA*fA3; fA5 = fA*fA4; fA6 = fA*fA5;
	    fePrime2 = fe2 / (1.0 - fe2);
	    fC      = fePrime2 * fCosLat * fCosLat;
	    fT      = fTanLat * fTanLat;
	    fN      = fa / sqrt(1.0 - (fe2*fSinLat*fSinLat));

	    /* x = N*[A + (1-T+C)A**3/6 + (5-18*T+T**2+72*C-58ePrime2)A**5/120]  (8-9) */
	    fConst1 = (1.0 - fT ) + fC;
	    fConst2 = (5.0 - (18.0*fT)) + (fT*fT) + (72.0*fC) - (58.0*fePrime2);
	    pMap_a->fX = fN * ( fA + (fConst1*fA3/6.0) + (fConst2*fA5/120.0) );

	    /* Const3 = A**2/2 + (5-T+9C+4C**2)A**4/24 + (61-58T+T**2+600C-330ePrime2)A**6/720 (8-10) */
	    /* y = M - M0 + N * tan(Lat) * Const3                            (8-10) */
	    fConst1 = (5.0 - fT) + (9.0*fC) + (4.0*fC*fC);
	    fConst2 = (61.0 - (58.0*fT)) + (fT*fT) + (600.0*fC) - (330.0*fePrime2);
	    fConst3 = (fA2/2.0) + (fConst1*fA4/24.0) + (fConst2*fA6/720.0);
	    pMap_a->fY = (fM - fM0) + fN * fTanLat * fConst3;
	  }

	/* The 0.9996 factor used in UTM only makes isocols of zero distortion 180 km either
	 * site of the reference meridian.  This makes no difference in the transformation.
	 */
	if ( projection_a->iprojection_type==MAPROJ_UTM)
	  {
	    pMap_a->fX *= 0.9996;
	    pMap_a->fY *= 0.9996;
	  }
	if (DEBUG) fprintf( stderr, "fX=%10.1f m, fY=%10.1f m\n", pMap_a->fX, pMap_a->fY );

	/* Rescale output from meters to km */
	pMap_a->fX /= 1000.0;
	pMap_a->fY /= 1000.0;
	

      }
      break ;

    case MAPROJ_PERSPECTIVE:
      {
	/* Equations from USGS 1395 Page 173, removing R scaling:
	 * X =  k' cos(Lat)*sin( Lon - LonRef )
	 * Y =  k'[cos(LatRef)*sin(Lat) - sin(LatRef)*cos(Lat)*cos(Lon-LonRef)]
	 * Where k' = (P-1)*(P-cos(c))
	 *  Where P = Distance from center of earth divided by the radius of earth.
	 *        cos(c) = sin(LatRef)*sin(Lat) + cos(LatRef)*cos(Lat)*cos(Lon-LonRef)
	 */
        FLT8  fP, fRelLon, fCosC, fPMinusCosC, fKPrime;
	fP = FEARTH_GEO_HEIGHT/FEARTH_MEAN_RADIUS;
        fRelLon = pEarth_a->fLon - projection_a->Ref.fLon;
	fCosC = sin(projection_a->Ref.fLat)*sin(pEarth_a->fLat) +
	  cos(projection_a->Ref.fLat)*cos(pEarth_a->fLat)*cos(fRelLon);
	fPMinusCosC = fP - fCosC;

	/* Fill in map coordinates even for invalid points.  This helps
	 * plotting.
	 */
	if (fPMinusCosC > 0.0)
	  {
	    fKPrime = (fP-1.0)/fPMinusCosC;
	    pMap_a->fX = FEARTH_MEAN_RADIUS * fKPrime * cos(pEarth_a->fLat) * sin(fRelLon);
	    pMap_a->fY = FEARTH_MEAN_RADIUS * fKPrime * 
	      ( cos(projection_a->Ref.fLat)*sin(pEarth_a->fLat) - 
		sin(projection_a->Ref.fLat)*cos(pEarth_a->fLat)*cos(fRelLon) ) ;
	  }
	else
	  {
	    pMap_a->fX = 0.0;
	    pMap_a->fY = 0.0;
	  }
	/* Test for validly visible */
	if (fCosC < (1.0/fP)) *plValid_a = FALSE;
      }
      break ;

    case MAPROJ_EDC:
      {
	/* Equations:
	 * X = R * (Lon - LonRef)
	 * Y = R * Lat;
	 */
	FLT8 fRelLon;
	fRelLon = fNormalizeAngle(pEarth_a->fLon - projection_a->Ref.fLon);
	
	pMap_a->fX = FEARTH_MEAN_RADIUS * fRelLon;
	pMap_a->fY = FEARTH_MEAN_RADIUS * pEarth_a->fLat;
      }
      break ;

    case MAPROJ_GNOMONIC:
      {
	/* Equations from USGS 1395 Page 165:
	 * X = Rk' cos(Lat)*sin( Lon - LonRef )
	 * Y = Rk'[cos(LatRef)*sin(Lat) - sin(LatRef)*cos(Lat)*cos(Lon-LonRef)]
	 * Where k' = 1 / cos(c)
	 *  Where P = Distance from center of earth divided by the radius of earth.
	 *        cos(c) = sin(LatRef)*sin(Lat) + cos(LatRef)*cos(Lat)*cos(Lon-LonRef)
	 */
        FLT8  fRelLon, fCosC;
        fRelLon = pEarth_a->fLon - projection_a->Ref.fLon;
	fCosC = sin(projection_a->Ref.fLat)*sin(pEarth_a->fLat) +
	  cos(projection_a->Ref.fLat)*cos(pEarth_a->fLat)*cos(fRelLon);  /* (5-3) */

	/* Test for validly visible */
	if (fCosC > 0.0)
	  {
	    FLT8 fKPrime;
	    fKPrime = 1.0/fCosC;
	    pMap_a->fX = FEARTH_MEAN_RADIUS * fKPrime * cos(pEarth_a->fLat) * sin(fRelLon);
	    pMap_a->fY = FEARTH_MEAN_RADIUS * fKPrime * 
	      ( cos(projection_a->Ref.fLat)*sin(pEarth_a->fLat) - 
		sin(projection_a->Ref.fLat)*cos(pEarth_a->fLat)*cos(fRelLon) ) ;
	  }
	else
	  {
	    pMap_a->fX = 0.0;
	    pMap_a->fY = 0.0;
	    *plValid_a = FALSE;
	  }

      }
      break ;

    case MAPROJ_LAMBERT:
      {
	/* Need to test for degenerate cases:
	 * StdPar at the poles, or with opposite sign, or on the equator.
	 */
	/* Equations from USGS 1395 Page 107-108:
	 * t  = tan( PI/4 - Lat/2 ) /[(1 - e sin(Lat))   /(1+ e sin(Lat))]**(e/2)     (15-9)
	 * m  = cos(Lat) / sqrt(1 - (e**2)*(sin(Lat)**2))                             (14-15)
	 */
        FLT8 ft, fTemp, fSinLat, fn;
	FLT8 fF, fRho0, fRho, fTheta;

	LambertSupport( projection_a, fa, fe, &fn, &fF, &fRho0 );
	
	if (DEBUG) fprintf( stderr, "Lambert: n=%10.7f, F=%9.8f, Rho0=%8.2f\n",
			    fn, fF, fRho0 );

	fSinLat = sin(pEarth_a->fLat);
	fTemp = (1.0 - fe * fSinLat) / (1.0 + fe * fSinLat);
	ft = tan( (FPI/4.0) - (pEarth_a->fLat/2.0) )/pow(fTemp,fe/2.0);

	/* Theta = n *( Lat - LatRef )                     (14-4)
	 * Rho   = a * F * t**n                            (15-7)
	 * x     = Rho * sin(Theta)                        (14-1)
	 * y     = Rho0 - Rh0 * cos(Theta)                 (14-2)
	 */
	/* Should we normalize here? */
	fTheta = fn * ( pEarth_a->fLon - projection_a->Ref.fLon );
	fRho   = fa * fF * pow( ft, fn );

	if (DEBUG) fprintf( stderr, "t=%10.7f, Rho=%9.2f, Theta=%8.2f\n", ft, fRho, 
			    (180.0/FPI)*fTheta );

	/* Rescale output from meters to km */
	pMap_a->fX = (fRho * sin(fTheta))/1000.0;
	pMap_a->fY = (fRho0 - (fRho * cos(fTheta)))/1000.0;
      }
      break ;

    }  /* end  switch( projection_a->iprojection_type ) */
  return( istatus ) ;
}
/* ===================================================================
 * Compute the M values used in Transverse Mercator from a series approximation.
 * From equation (3-21)
 */
static FLT8 fTransverseMercatorM
( FLT8 fa_a,
  FLT8 fe2_a,
  FLT8 fPhi_a )  /* Latitude in radians */
{
  FLT8 fM, fConst, fe4, fe6;
  fe4 = fe2_a*fe2_a;
  fe6 = fe4*fe2_a;
  /* a*( 1 - e**2/4 - 3e**4/64 - 5e**6/255)*Phi */
  fConst = fa_a*(((1.0 - (fe2_a/4.0)) - (3.0*fe4/64.0)) - (5.0*fe6/256.0));
  fM = fConst*fPhi_a;

  /* -a*( 3e**2/8 + 3e**4/32 + 45e**6/1024)*sin(2*Phi) */
  fConst = fa_a*((3.0*fe2_a/8.0) + (3.0*fe4/32.0) + (45.0*fe6/1024.0));
  fM -= fConst*sin(2.0*fPhi_a);

  /* +a*( 15e**4/256 + 45e**6/1024)*sin(4*Phi) */
  fConst = fa_a*((15.0*fe4/256.0) + (45.0*fe6/1024.0));
  fM += fConst*sin(4.0*fPhi_a);

  /* -a*( 35e**6/3072)*sin(6*Phi) */
  fConst = fa_a*(35.0*fe6/3072.0);
  fM -= fConst*sin(6.0*fPhi_a);

  return fM;
}

/* ====================================================================
 * Function to calculate common values needed by the Lambert projection.
 */
static void LambertSupport
( const struct mpj_projection8 *projection_a, /* Type of map projection */
  FLT8 fa_a,
  FLT8 fe_a,
  FLT8 *fn_a,
  FLT8 *fF_a,
  FLT8 *fRho0_a )
{
  /* Equations from USGS 1395 Page 107-108, removing R scaling:
   * t0 = tan( PI/4 - LatRef/2 ) /[(1 - e sin(LatRef))/(1+ e sin(LatRef))]**(e/2)    (15-9)
   * t1 = tan( PI/4 - StdPar1/2 )/[(1 - e sin(StdPar1))/(1+ e sin(StdPar1))]**(e/2)
   * t2 = tan( PI/4 - StdPar2/2 )/[(1 - e sin(StdPar2))/(1+ e sin(StdPar2))]**(e/2)
   * m1 = cos(StdPar1) / sqrt(1 - (e**2)*(sin(StdPar1)**2))                          (14-15)
   * m2 = cos(StdPar2) / sqrt(1 - (e**2)*(sin(StdPar2)**2))
   */
  FLT8 ft0, ft1, fTemp, fSinLat, fSinLat1, fm1;
  
  fSinLat = sin(projection_a->Ref.fLat);
  fTemp = (1.0 - fe_a * fSinLat) / (1.0 + fe_a * fSinLat);
  ft0 = tan( FPI/4.0 - (projection_a->Ref.fLat/2.0) )/pow(fTemp,fe_a/2.0);
  
  fSinLat1 = sin(projection_a->fStdPar1);
  fTemp = (1.0 - fe_a * fSinLat1) / (1.0 + fe_a * fSinLat1);
  ft1 = tan( FPI/4.0 - (projection_a->fStdPar1/2.0) )/pow(fTemp,fe_a/2.0);
  
  fTemp = sqrt( 1.0 - fe_a*fe_a*fSinLat1*fSinLat1 );
  fm1 = cos(projection_a->fStdPar1) / fTemp;

  if (0) fprintf( stderr, "LambertSupport, t0=%8.8f, t1=%8.8f, m1=%8.8f\n", ft0, ft1, fm1 );

  /* Assume a single standard parallel if they are within 0.1 degree */
  if (fabs(projection_a->fStdPar1-projection_a->fStdPar2) < (FPI/1800.0))
    {
      /* n = sin(StdPar1) */
      *fn_a = sin(projection_a->fStdPar1);
    }
  else
    {
      FLT8 ft2, fm2, fSinLat2;
      fSinLat2 = sin(projection_a->fStdPar2);
      fTemp = (1.0 - fe_a * fSinLat2) / (1.0 + fe_a * fSinLat2);
      ft2 = tan( (FPI/4.0) - (projection_a->fStdPar2/2.0) )/pow(fTemp,fe_a/2.0);
      
      fTemp = sqrt( 1.0 - fe_a*fe_a*fSinLat2*fSinLat2 );
      fm2 = cos(projection_a->fStdPar2) / fTemp;
      
      /* n = (ln(m1)-ln(m2)) / (ln(t1)-ln(t2))     (15-8) */
      *fn_a = (log(fm1) - log(fm2) ) / (log(ft1) - log(ft2));
    }
  /* F     = m1 / (n*t1**n)                          (15-10) 
   * Rho0  = a * F * t0**n                           (15-7a)
   */
  *fF_a     = fm1 / (*fn_a * pow( ft1, *fn_a ) );
  *fRho0_a  = fa_a * (*fF_a) * pow( ft0, *fn_a );

}

/* ================================================================
 * Internal routine -- Given a map projection definition, convert a
 * coordinate in the map to a Lat/Lon Earth location.  The map coordinates
 * in km, and have an arbitrary point of origin.
 */
MESSAGE mpj_map_to_earth
(const struct mpj_projection8 *projection_a, /* Type of map projection */
 const struct XYPoint8 *pMap_a,              /* Map coordinates (unit earth) */
 volatile struct latlon8 *pEarth_a )         /* Earth coordinates (radians) */
{
  FLT8 fa, fe;  /* Earth radius and ecentricity */
  FLT8 fb, ff;
  MESSAGE istatus;
  if (DEBUG) fprintf( stderr, "mpj_map_to_earth called, X=%10.8f, Y=%10.8f\n",
		      pMap_a->fX, pMap_a->fY );

  istatus = mpj_validate_projection( projection_a ) ;
  memset( (void*)pEarth_a, 0, sizeof(*pEarth_a) );  /* Cast discards qualifiers */

  if( istatus != SS_NORMAL ) return( istatus ) ;

  mpj_get_geode_info( &fa, &fb, &fe, &ff, projection_a );

  switch( projection_a->iprojection_type )
    {
    case MAPROJ_MERCATOR:
      /* Equations:
       * Lon = X/R + LonRef
       * Lat = 2 * ( atan( exp(Y/R) ) - PI/4 );
       */
      pEarth_a->fLon = fNormalizeAngle(pMap_a->fX/FEARTH_MEAN_RADIUS + projection_a->Ref.fLon);
      pEarth_a->fLat = 2.0 * ( atan( exp(pMap_a->fY/FEARTH_MEAN_RADIUS) ) - (FPI_P/4.0) ) ;
      break ;

    case MAPROJ_AZIMUTHAL:
    default:
      {
	struct D2Polar8 Polar;
        Polar.fRange = sqrt( (pMap_a->fX * pMap_a->fX) + (pMap_a->fY * pMap_a->fY) ) ;
	if( Polar.fRange < RANGERROR )
	  {
	    *pEarth_a = projection_a->Ref ;
	  }
	else
	  {
	    Polar.fAz = atan2( pMap_a->fX, pMap_a->fY ) ;
	    if (Polar.fAz<0.0) Polar.fAz += 2.0*FPI;
	    EarthFromPolar( &projection_a->Ref, &Polar, pEarth_a ) ;
	  }
      }
      break ;
    case MAPROJ_POLSTEREO:
      {
	/* Equations:
	 * R = sqrt( X**2 + Y**2 )
	 * Lat = PI/2 - 2 * atan( R/Radius )
	 * Lon = LonRef + atan2( X, -Y )
	 */
        FLT8 fRange, fRelLon;
        fRange = sqrt( (pMap_a->fX * pMap_a->fX) + (pMap_a->fY * pMap_a->fY) ) ;
        pEarth_a->fLat =  (FPI/2.0) - 2.0 * atan( fRange/FEARTH_MEAN_RADIUS ) ;
	if( fRange < RANGERROR )
	  {
	    fRelLon = 0.0;
	  }
	else
	  {
	    fRelLon = atan2( pMap_a->fX, -pMap_a->fY ) ;
	  }
	pEarth_a->fLon = fNormalizeAngle( fRelLon + projection_a->Ref.fLon );
      }
      break ;
    case MAPROJ_UTM:
    case MAPROJ_GAUSS:
      {
	FLT8 fConst1, fConst2, fConst3, fConst4;
	FLT8 fM, fM0, fx, fy, fe2, fe4, fe6, fMu;
	FLT8 fe1, fe12, fe13, fe14;
	FLT8 fPhi1, fCosPhi1, fSinPhi1, fTanPhi1;
	FLT8 fePrime2, fC1, fT1, fN1, fR1;
	FLT8 fD, fD2, fD3, fD4, fD5, fD6;
	/* Equations from USGS 1395 Page 63-64:
	 * M  = M0 + y/k0                                 (8-20)
	 * Mu = M/[a*(1 - e**2/4 - 3e**4/64 -5e**6/256)]  (7-19)
	 */
	fe2 = fe*fe;
	fe4 = fe2*fe2;
	fe6 = fe2*fe4;
	if( projection_a->iprojection_type==MAPROJ_UTM)
	  {
	    fx = pMap_a->fX/0.9996;
	    fy = pMap_a->fY/0.9996;
	  }
	else
	  {
	    fx = pMap_a->fX;
	    fy = pMap_a->fY;
	  }
	/* Convert to meters */
	fx *= 1000.0;
	fy *= 1000.0;
	fM0  = fTransverseMercatorM(fa,fe2,projection_a->Ref.fLat);
	fM   = fM0 + fy;
	fMu  = fM/(fa * (1.0 - ( (fe2/4.0) + (3.0*fe4/64.0) + (5.0*fe6/256.0) )));

	/* e1 = [1 - sqrt(1 - e**2)] / [1 + sqrt(1 - e**2) ]  (3-24)
	 */
	fConst1 = sqrt( 1.0 - fe2 );
	fe1     = (1.0 - fConst1) / (1.0 + fConst1);
	fe12 = fe1*fe1;
	fe13 = fe1*fe12;
	fe14 = fe1*fe13;

	/* Const1 = (3*e1/2 - 27e1**3/32)                (3-24)
	 * Const2 = (21*e1**2/16 - 55e1**4/32)           (3-24)
	 * Const3 = (151*e1**3/96)                       (3-24)
	 * Const4 = (1097*e1**4/512)                     (3-24)
	 * Phi1   = Mu + Const1*sin(2Mu) + Const2*Sin(4Mu) + Const3*Sin(6Mu) + Const4*sin(8Mu) 
	 */
	fConst1 = (3.0*fe1/2.0) - (27.0*fe13/32);
	fConst2 = (21.0*fe12/16.0) - (55.0*fe14/32);
	fConst3 = (151.0*fe13/96.0);
	fConst4 = (1097.0*fe14/512.0);
	fPhi1   = fMu + (fConst1*sin(2.0*fMu)) + (fConst2*sin(4.0*fMu));
	fPhi1  +=       (fConst3*sin(6.0*fMu)) + (fConst4*sin(8.0*fMu));

	if (DEBUG) fprintf( stderr, "M0=%2.1f, e1=%10.9f, Mu=%10.9f, Phi1=%10.9f\n", 
			    fM0, fe1, fMu, fPhi1 );

	/* ePrime2 = e**2/(1 - e**2)                                (8-12)
	 * C1      = e'**2 * cos(Phi1)**2                           (8-21)
	 * T1      = tan(Phi1)**2                                   (8-22)
	 * N1      = a/sqrt(1 - (e**2 * sin(Phi1)**2))              (8-23)
	 * R1      = a*(1-e**2) / (1 - (e**2 * sin(Phi1)**2))**1.5  (8-24)
	 * D       = x/(N1*k0)                                      (8-25)
	 */
	fePrime2 = fe2/(1.0 - fe2);
	fCosPhi1 = cos(fPhi1);
	fSinPhi1 = sin(fPhi1);
	fTanPhi1 = tan(fPhi1);
	fConst1  = 1.0 - (fe2 * fSinPhi1*fSinPhi1);
	fC1      = fePrime2 * fCosPhi1*fCosPhi1;
	fT1      = fTanPhi1*fTanPhi1;
	fN1      = fa / sqrt(fConst1);
	fR1      = fa*(1.0-fe2) / pow(fConst1, 1.5);
	fD       = fx/fN1;
	fD2      = fD*fD;
	fD3      = fD*fD2;
	fD4      = fD*fD3;
	fD5      = fD*fD4;
	fD6      = fD*fD5;

	/* Const1 = (5 + 3*T1 + 10*C1 - 4*C1**2 - 9*ePrime2)/24.0                   (8-17)
	 * Const2 = (61 + 90*T1 + 298*C1 + 45*T1**2 + 252*ePrime2 - 3*C1**2)/720   (8-17)
	 * Lat    = Phi1 - (N1*tan(Phi1)/R1)*
	 *              [(D**2)/2 + (-Const1*D**4) + (Const2*D**6)]           (8-17)
	 */
	fConst1 = ((5.0 + (3.0*fT1) + (10.0*fC1) - (4.0*fC1*fC1)) - (9.0*fePrime2))/24.0;
	fConst2 = 61.0 + (90.0*fT1) + (298.0*fC1) + (45.0*fT1*fT1) - (252.0*fePrime2);
	fConst2 -= (3.0*fC1*fC1);
	fConst2 /= 720.0;
	pEarth_a->fLat = fPhi1;
	pEarth_a->fLat -= (fN1*fTanPhi1/fR1)*((fD2/2.0)+(-fConst1*fD4)+(fConst2*fD6));

	/* Const1 = (1 + 2*T1 + C1)/6.0                                       (8-18)
	 * Const2 = (5 - 2*C1 + 28T1 - 3C1**2 + 8*ePrime2 +24*T1**2)/120      (8-18)
	 * Lon    = LonRef + [(D + (-Const1*D**3) + (Const2*D**5)]/cos(Phi1)  (8-18)
	 */

	fConst1 = (1.0 + (2.0*fT1) + fC1)/6.0;
	fConst2 = ((5.0 - (2.0*fC1) ) + (28.0*fT1) - (3.0*fC1*fC1)) + (8.0*fePrime2);
	fConst2 += 24.0*fT1*fT1;
	fConst2 /= 120.0;
	pEarth_a->fLon = ( fD + (-fConst1*fD3) + (fConst2*fD5) )/fCosPhi1;
	pEarth_a->fLon = fNormalizeAngle( projection_a->Ref.fLon + pEarth_a->fLon );
      }
      break ;
    case MAPROJ_PERSPECTIVE:
      {
	/* Equations from USGS 1395 Page 175, removing R scaling:
	 * Lat = arcsin( (cos(c)*sin(LatRef)) + (y*sin(c)cos(LatRef)/rho) )
	 * Lon = LonRef + arctan( x*sin(c) / (rho*cos(LatRef)*cos(c) - y*sin(LatRef)*sin(c)))
	 * Where rho = sqrt( x*x + y*y )                   (20-18)
	 *         c = arcsin((P - sqrt(A) / B )           (23-4)
	 *  Where    A = (1 - (((rho**2)*(P+1)) / (P-1)))
	 *           B = (P-1)/rho + rho/(P-1) 
	 */
        FLT8  fP, fRho, fRhoR;
	fP = FEARTH_GEO_HEIGHT/FEARTH_MEAN_RADIUS;
	fRho = sqrt( pMap_a->fX*pMap_a->fX + pMap_a->fY*pMap_a->fY );
	fRhoR = fRho/FEARTH_MEAN_RADIUS;

	if (fRho<=0.0)
	  {
	    *pEarth_a = projection_a->Ref;
	  }
	else
	  {
	    FLT8 fA;
	    /* We are off the visible edge of the earth when A is <= 0.0 */
	    fA = 1.0 - ((fRhoR*fRhoR*(fP+1.0))/ (fP-1.0));
	    if (fA<=0.0)
	      {
		pEarth_a->fLat = 0.0;
		pEarth_a->fLon = 0.0;
		pEarth_a->iFlags |= LATLON_FLG_INVALID;
	      }
	    else
	      {
		FLT8 fB, fC, fSinC, fRelLon;
		fB = ((fP-1.0)/fRhoR) + (fRhoR/(fP-1.0));
		fSinC = (fP - sqrt(fA)) / fB;
		fC = asin( fSinC );
		pEarth_a->fLat = asin( (cos(fC)*sin(projection_a->Ref.fLat)) +
				       (pMap_a->fY*fSinC*cos(projection_a->Ref.fLat)/fRho) );

		fRelLon = atan2( pMap_a->fX*fSinC,
				 (fRho*cos(projection_a->Ref.fLat)*cos(fC)) -
				 (pMap_a->fY*sin(projection_a->Ref.fLat)*fSinC) );
	    
		pEarth_a->fLon = fNormalizeAngle(fRelLon + projection_a->Ref.fLon);
	      }

	  }
      }
      break ;

    case MAPROJ_EDC:
      /* Equations:
       * Lon = X + LonRef
       * Lat = Y;
       */
      pEarth_a->fLon = fNormalizeAngle(pMap_a->fX/FEARTH_MEAN_RADIUS + projection_a->Ref.fLon);
      pEarth_a->fLat = pMap_a->fY/FEARTH_MEAN_RADIUS;
      /* We are off the earth if the latitude is outsize +/- PI/2 */
      if (pEarth_a->fLat>(FPI/2.0))
	{
	  pEarth_a->fLat = 0.0;
	  pEarth_a->iFlags |= LATLON_FLG_INVALID;
	}
      else if (pEarth_a->fLat<(-FPI/2.0))
	{
	  pEarth_a->fLat = 0.0;
	  pEarth_a->iFlags |= LATLON_FLG_INVALID;
	}
      break ;

    case MAPROJ_GNOMONIC:
      {
	/* Equations from USGS 1395 Page 167:
	 * Lat = arcsin( (cos(c)*sin(LatRef)) + (y*sin(c)cos(LatRef)/rho) )  (20-14)
	 * Lon = LonRef + arctan( x*sin(c) / (rho*cos(LatRef)*cos(c) - y*sin(LatRef)*sin(c)))
	 * Where rho = sqrt( x*x + y*y )
	 *         c = arctan( rho )
	 */
        FLT8  fRho;
	fRho = sqrt( pMap_a->fX*pMap_a->fX + pMap_a->fY*pMap_a->fY );  /* (20-18) */

	if (fRho<=0.0)
	  {
	    *pEarth_a = projection_a->Ref;
	  }
	else 
	  {
	    FLT8 fC, fSinC, fRelLon;
	    fC = atan( fRho/FEARTH_MEAN_RADIUS );
	    fSinC = sin(fC);

	    /* All points are mappable, the distortion goes up forever */
	    pEarth_a->fLat = asin( (cos(fC)*sin(projection_a->Ref.fLat)) +
				   (pMap_a->fY*fSinC*cos(projection_a->Ref.fLat)/fRho) );
	    
	    fRelLon = atan2( pMap_a->fX*fSinC,
			     (fRho*cos(projection_a->Ref.fLat)*cos(fC)) -
			     (pMap_a->fY*sin(projection_a->Ref.fLat)*fSinC) );
	    
	    pEarth_a->fLon = fNormalizeAngle(fRelLon + projection_a->Ref.fLon);
	  }
      }
      break ;

    case MAPROJ_LAMBERT:
      {
	int  iLoopCount;
        FLT8 ft, fn, fx;
	FLT8 fF, fRho0, fRho, fTheta, fRho0minusY, fLat, fLatChange;
	/* Equations from USGS 1395 Page 109:
	 * Theta = arctan( x / (Rho0-y) )                  (14-11)
	 * Lon   = Theta/n + LonRef                        (14-9)
	 * Rho   = +/-sqrt( x**2 + (Rho0-y)**2 )           (14-10)
	 * t     = (Rho/(a*F))**(1/n)                      (15-11)
	 * Lat   = PI/2 - 2*arctan{t*[(1-e*sin(Lat))/(1+e*sin(Lat))]**(e/2)} (7-9)
	 */
	LambertSupport( projection_a, fa, fe, &fn, &fF, &fRho0 );
	    
	/* Scale X and Y to meters from km */
	fx = 1000.0*pMap_a->fX;
	fRho0minusY = fRho0-(1000.0*pMap_a->fY);
	if ((fx==0.0)&&(fRho0minusY==0.0))
	  fTheta = 0.0;
	else
	  fTheta = atan2( fx, fRho0minusY );
	pEarth_a->fLon = fNormalizeAngle((fTheta/fn) + projection_a->Ref.fLon);
	fRho = sqrt( (fx*fx) + (fRho0minusY*fRho0minusY) );
	if (fn<0.0) fRho = -fRho;
	ft = pow( fRho/(fa*fF), 1.0/fn );
	fLat = (FPI/2.0) - 2.0 * atan(ft);
	fLatChange = 1.0;  /* Just to start the loop */
	iLoopCount=0;
	/* Convert to 1 second of arc */
	while((iLoopCount<10)&&(fLatChange>FPI/(180.0*60.0*60.0)))
	  {
	    FLT8 fLatOld = fLat;
	    FLT8 fSinLat = sin(fLatOld);
	    FLT8 fTemp   = (1.0-(fe*fSinLat))/(1.0+(fe*fSinLat));
	    fLat = (FPI/2.0) - (2.0 * atan(ft*pow(fTemp,fe/2.0)));
	    fLatChange = fabs( fLat-fLatOld );
	  }
	if (iLoopCount>=10) sig_signal_va( USER_TEST_STRING, 1, "Lambert converge failed" );
	pEarth_a->fLat = fLat;

      }
      break ;

    }
  return( istatus ) ;
}
/* ============================================================
 * Normalize angle to range -PI to +PI.  Input angle from angle
 * addition or subraction.  So has range -2PI to +2PI.
 */
static FLT8 fNormalizeAngle
( FLT8 fRawAngle_a )
{
  if (fRawAngle_a > FPI) 
    return fRawAngle_a - 2.0*FPI;
  else if (fRawAngle_a < -FPI) 
    return fRawAngle_a + 2.0*FPI;
  else
    return fRawAngle_a;    
}
