G_begin_distance_calculations()
{
    double a, e2;

    factor = 1.0;
    switch (projection = G_projection())
    {
    case PROJECTION_LL:
        G_get_ellipsoid_parameters (&a, &e2);
        G_begin_geodesic_distance (a, e2);
        return 2;
    default:
      factor = G_database_units_to_meters_factor();
        if (factor <= 0.0)
        {
            factor = 1.0;          /* assume meter grid */
            return 0;
        }
        return 1;
    }
}

double
G_distance (e1,n1,e2,n2)
    double e1,n1,e2,n2;
{
    double G_geodesic_distance();
    double hypot();

    if (projection == PROJECTION_LL)
        return G_geodesic_distance (e1, n1, e2, n2);
    else
        return factor * hypot (e1-e2,n1-n2);
}

/*
 * This code is preliminary. I don't know if it is even
 * correct.
 */

/*
 * From "Map Projections" by Peter Richardus and Ron K. Alder, 1972
 * (526.8 R39m in Map & Geography Library)
 * page  19, formula 2.17
 *
 * Formula is the equation of a geodesic from (lat1,lon1) to (lat2,lon2)
 * Input is lon, output is lat (all in degrees)
 *
 * Note formula only works if 0 < abs(lon2-lon1) < 180
 * If lon1 == lon2 then geodesic is the merdian lon1 
 * (and the formula will fail)
 * if lon2-lon1=180 then the geodesic is either meridian lon1 or lon2
 */

#include "pi.h"

extern double sin(), cos(), tan(), atan();
static double A, B;
#define swap(a,b) temp=a;a=b;b=temp
 
G_begin_geodesic_equation (lon1, lat1, lon2, lat2)
    double lon1, lat1, lon2, lat2;
{
    double sin21, tan1,tan2;

    adjust_lon (&lon1);
    adjust_lon (&lon2);
    adjust_lat (&lat1);
    adjust_lat (&lat2);
    if (lon1 > lon2)
    {   
        register double temp;
        swap(lon1,lon2);
        swap(lat1,lat2);
    }
    if (lon1 == lon2)
    {
        A = B = 0.0;
      return 0;
    }
    lon1 = Radians(lon1);
    lon2 = Radians(lon2);
    lat1 = Radians(lat1);
    lat2 = Radians(lat2);

    sin21 = sin (lon2-lon1);
    tan1  = tan (lat1);
    tan2  = tan (lat2);

    A = ( tan2 * cos(lon1) - tan1 * cos(lon2) ) /sin21;
    B = ( tan2 * sin(lon1) - tan1 * sin(lon2) ) /sin21;

    return 1;
}

/* only works if lon1 < lon < lon2 */

double
G_geodesic_lat_from_lon (lon)
    double lon;
{
    adjust_lon (&lon);
    lon = Radians(lon);
    return Degrees (atan(A * sin(lon) - B * cos(lon)));
}

static
adjust_lon (lon)
    double *lon;
{
    while (*lon > 180.0)
        *lon -= 360.0;
    while (*lon < -180.0)
        *lon += 360.0;
}

static
adjust_lat (lat)
    double *lat;
{
    if (*lat >  90.0) *lat =  90.0;
    if (*lat < -90.0) *lat = -90.0;
}



#include "pi.h"
 
/* WARNING: this code is preliminary and may be changed,
 * including calling sequences to any of the functions
 * defined here
 */

/* distance from point to point along a geodesic 
 * code from
 *   Paul D. Thomas
 *   "Spheroidal Geodesics, Reference Systems, and Local Geometry"
 *   U.S. Naval Oceanographic Office, p. 162
 *   Engineering Library 526.3 T36s
 */

static double boa;
static double f;
static double ff64;
static double al;

extern double sqrt();
extern double sin(), cos(), tan(), atan(), acos();

/* must be called once to establish the ellipsoid */
G_begin_geodesic_distance (a, e2)
    double a, e2;
{
    al = a;
    boa = sqrt (1 - e2);
    f = 1 - boa;
    ff64 = f*f/64;
}

static double t1,t2,t3,t4,t1r,t2r;

/* must be called first */
G_set_geodesic_distance_lat1(lat1)
    double lat1;
{
    t1r = atan(boa*tan(Radians(lat1)));
}
/* must be called second */
G_set_geodesic_distance_lat2(lat2)
    double lat2;{
    double stm,ctm,sdtm,cdtm;
    double tm, dtm;
 
    t2r = atan(boa*tan(Radians(lat2)));

    tm  = (t1r+t2r)/2;
    dtm = (t2r-t1r)/2;

    stm = sin(tm);
    ctm = cos(tm);
    sdtm = sin(dtm);
    cdtm = cos(dtm);

    t1 = stm*cdtm;
    t1 = t1 * t1 * 2;

    t2 = sdtm*ctm;
    t2 = t2 * t2 * 2;

    t3 = sdtm*sdtm;
    t4 = cdtm*cdtm-stm*stm;}
 
double
G_geodesic_distance_lon_to_lon (lon1, lon2)
    double lon1, lon2;
{
    double a, cd, d, dl, e,
           q, sd, sdlmr, 
           t, u, v, x, y;


    sdlmr = sin(Radians(lon2-lon1)/2);

    /* special case - shapiro */
    if (sdlmr == 0.0 && t1r == t2r) return 0.0;

    q = t3+sdlmr*sdlmr*t4;

    /* special case - shapiro */
    if (q == 1.0) return PI *al;

/* Mod: shapiro
* cd=1-2q is ill-conditioned if q is small O(10**-23)
 *   (for high lats? with lon1-lon2 < .25 degrees?)
 *   the computation of cd = 1-2*q will give cd==1.0.
 * However, note that t=dl/sd is dl/sin(dl) which approaches 1 as dl->0.
 * So the first step is to compute a good value for sd without using sin()
 *   and then check cd && q to see if we got cd==1.0 when we shouldn't.
 * Note that dl isn't used except to get t,
 *   but both cd and sd are used later
 */

/* original code
    cd=1-2*q;
    dl=acos(cd);
    sd=sin(dl);
    t=dl/sd;
*/

    cd = 1-2*q;                 /* ill-conditioned subtraction for small q */
/* mod starts here */
    sd = 2* sqrt (q - q * q);   /* sd^2 = 1 - cd^2 */
    if (q != 0.0 && cd == 1.0)  /* test for small q */
        t = 1.0;    else if (sd == 0.0)
        t = 1.0;
    else
        t = acos(cd)/sd;          /* don't know how to fix acos(1-2*q) yet */
/* mod ends here */

    u = t1/(1-q);
    v = t2/q;
    d = 4*t*t;
    x = u+v;
    e = -2*cd;
    y = u-v;
    a = -d*e;

    return ( al * sd *
                ( t - f/4 * (t*x-y) +
                  ff64 *
                  (
                    x * ( a + (t - (a+e)/2) * x) +
                    y* (-2 * d + e*y)
                    + d*x*y
                  )
             )
            );
}
 
double
G_geodesic_distance (lon1, lat1, lon2, lat2)
    double lon1, lat1, lon2, lat2;
{
    G_set_geodesic_distance_lat1 (lat1);
    G_set_geodesic_distance_lat2 (lat2);
    return G_geodesic_distance_lon_to_lon (lon1, lon2);
}
