/*! * \file lib/gis/geodist.c * * \brief GIS Library - Geodesic distance routines. * * Distance from point to point along a geodesic code from Paul * D. Thomas, 1970 "Spheroidal Geodesics, Reference Systems, and Local * Geometry" U.S. Naval Oceanographic Office, p. 162 Engineering * Library 526.3 T36s * http://stinet.dtic.mil/oai/oai?&verb=getRecord&metadataPrefix=html&identifier=AD0703541 * * WARNING: this code is preliminary and may be changed, * including calling sequences to any of the functions defined here. * * (C) 2001-2009 by the GRASS Development Team * * This program is free software under the GNU General Public License * (>=v2). Read the file COPYING that comes with GRASS for details. * * \author Original author CERL */ #include #include #include "pi.h" static struct state { double boa; double f; double ff64; double al; double t1, t2, t3, t4, t1r, t2r; } state; static struct state *st = &state; /*! * \brief Begin geodesic distance. * * Initializes the distance calculations for the ellipsoid with * semi-major axis a (in meters) and ellipsoid eccentricity squared * e2. It is used only for the latitude-longitude projection. * * Note: Must be called once to establish the ellipsoid. * * \param a semi-major axis in meters * \param e2 ellipsoid eccentricity */ void G_begin_geodesic_distance(double a, double e2) { st->al = a; st->boa = sqrt(1 - e2); st->f = 1 - st->boa; st->ff64 = st->f * st->f / 64; } /*! * \brief Sets geodesic distance lat1. * * Set the first latitude. * * Note: Must be called first. * * \param lat1 first latitude * \return */ void G_set_geodesic_distance_lat1(double lat1) { st->t1r = atan(st->boa * tan(Radians(lat1))); } /*! * \brief Sets geodesic distance lat2. * * Set the second latitude. * * Note: Must be called second. * * \param lat2 second latitidue */ void G_set_geodesic_distance_lat2(double lat2) { double stm, ctm, sdtm, cdtm; double tm, dtm; st->t2r = atan(st->boa * tan(Radians(lat2))); tm = (st->t1r + st->t2r) / 2; dtm = (st->t2r - st->t1r) / 2; stm = sin(tm); ctm = cos(tm); sdtm = sin(dtm); cdtm = cos(dtm); st->t1 = stm * cdtm; st->t1 = st->t1 * st->t1 * 2; st->t2 = sdtm * ctm; st->t2 = st->t2 * st->t2 * 2; st->t3 = sdtm * sdtm; st->t4 = cdtm * cdtm - stm * stm; } /*! * \brief Calculates geodesic distance. * * Calculates the geodesic distance from lon1,lat1 to * lon2,lat2 in meters where lat1 was the latitude * passed to G_set_geodesic_distance_latl() and lat2 was the * latitude passed to G_set_geodesic_distance_lat2(). * * \param lon1 first longitude * \param lon2 second longitude * * \return double distance in meters */ double G_geodesic_distance_lon_to_lon(double lon1, double lon2) { double a, cd, d, e, /*dl, */ q, sd, sdlmr, t, u, v, x, y; sdlmr = sin(Radians(lon2 - lon1) / 2); /* special case - shapiro */ if (sdlmr == 0.0 && st->t1r == st->t2r) return 0.0; q = st->t3 + sdlmr * sdlmr * st->t4; /* special case - shapiro */ if (q == 1.0) return M_PI * st->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 = st->t1 / (1 - q); v = st->t2 / q; d = 4 * t * t; x = u + v; e = -2 * cd; y = u - v; a = -d * e; return st->al * sd * (t - st->f / 4 * (t * x - y) + st->ff64 * (x * (a + (t - (a + e) / 2) * x) + y * (-2 * d + e * y) + d * x * y)); } /*! * \brief Calculates geodesic distance. * * Calculates the geodesic distance from lon1,lat1 to * lon2,lat2 in meters. * * Note: The calculation of the geodesic distance is fairly * costly. * * \param lon1,lat1 longitude,latitude of first point * \param lon2,lat2 longitude,latitude of second point * * \return distance in meters */ double G_geodesic_distance(double lon1, double lat1, double lon2, double lat2) { G_set_geodesic_distance_lat1(lat1); G_set_geodesic_distance_lat2(lat2); return G_geodesic_distance_lon_to_lon(lon1, lon2); }