Advertisement
Guest User

Geodesic Distance Triangulation in Python

a guest
Jun 27th, 2015
498
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 18.83 KB | None | 0 0
  1. #! /usr/bin/env python
  2.  
  3. ''' Geodesic distance triangulation
  4.  
  5.    Find two locations that are at given distances
  6.    from two fixed points, using the WGS84 reference ellipsoid
  7.  
  8.    First approximate using spherical trig, then refine
  9.    using Newton's method.
  10.  
  11.    Uses geographiclib by Charles Karney http://geographiclib.sourceforge.net
  12.    which can be installed using pip.
  13.  
  14.    Written by PM 2Ring 2015.06.20
  15. '''
  16.  
  17. import sys, getopt
  18. from operator import itemgetter
  19. from math import degrees, radians, sin, cos, acos, atan2, pi
  20. from random import seed, random
  21.  
  22. from geographiclib.geodesic import Geodesic
  23.  
  24. Geo = Geodesic.WGS84
  25.  
  26. #60 international nautical miles in metres
  27. metres_per_deg = 111120.0
  28.  
  29. # Tolerance in calculated distances, in metres.
  30. # Try adjusting this value if the geo_newton function fails to converge.
  31. distance_tol = 1E-8
  32.  
  33. # Tolerance in calculated positions, in degrees.
  34. position_tol = 5E-10
  35.  
  36. # Crude floating-point equality test
  37. def almost_equal(a, b, tol=5E-12):
  38.     return abs(a - b) < tol
  39.  
  40.  
  41. def normalize_lon(x, a):
  42.     ''' reduce longitude to range -a <= x < a '''
  43.     return (x + a) % (2 * a) - a
  44.  
  45.  
  46. class LatLong(object):
  47.     ''' latitude & longitude in degrees & radians.
  48.        Also colatitude in radians.
  49.    '''
  50.     def __init__(self, lat, lon, in_radians=False):
  51.         ''' Initialize with latitude and longitude,
  52.            in either radians or degrees
  53.        '''
  54.         if in_radians:
  55.             lon = normalize_lon(lon, pi)
  56.             self.lat = lat
  57.             self.colat = 0.5 * pi - lat
  58.             self.lon = lon
  59.             self.dlat = degrees(lat)
  60.             self.dlon = degrees(lon)
  61.         else:
  62.             lon = normalize_lon(lon, 180.0)
  63.             self.lat = radians(lat)
  64.             self.colat = radians(90.0 - lat)
  65.             self.lon = radians(lon)
  66.             self.dlat = lat
  67.             self.dlon = lon
  68.  
  69.     def __repr__(self):
  70.         return 'LatLong(%.12f, %.12f, in_radians=True)' % (self.lat, self.lon)
  71.  
  72.     def __str__(self):
  73.         return 'Lat: %.9f, Lon: %.9f' % (self.dlat, self.dlon)
  74.  
  75. # -------------------------------------------------------------------
  76. # Spherical triangle solutions based on the spherical cos rule
  77. # cos(c) = cos(a) * cos(b) + sin(a) * sin(b) * cos(C)
  78. # Side calculations use functions that are less susceptible
  79. # to round-off errors, from
  80. # https://en.wikipedia.org/wiki/Solution_of_triangles#Two_sides_and_the_included_angle_given
  81.  
  82. def opp_angle(a, b, c):
  83.     ''' Given sides a, b & c find C, the angle opposite side c '''
  84.     t = (cos(c) - cos(a) * cos(b)) / (sin(a) * sin(b))
  85.     try:
  86.         C = acos(t)
  87.     except ValueError:
  88.         # "Reflect" t if it's out of bounds due to rounding errors.
  89.         # This happens roughly 1% of the time, on average.
  90.         if t > 1.0:
  91.             t = 2.0 - t
  92.         elif t < -1.0:
  93.             t = -2.0 - t
  94.         C = acos(t)
  95.     return C
  96.  
  97.  
  98. def opp_side(a, b, C):
  99.     ''' Find side c given sides a, b and the included angle C '''
  100.     sa, ca = sin(a), cos(a)
  101.     sb, cb = sin(b), cos(b)
  102.     sC, cC = sin(C), cos(C)
  103.  
  104.     u = sa * cb - ca * sb * cC
  105.     v = sb * sC
  106.     num = (u ** 2 + v ** 2) ** 0.5
  107.     den = ca * cb + sa * sb * cC
  108.     return atan2(num, den)
  109.  
  110. def opp_side_azi(a, b, C):
  111.     ''' Find side c given sides a, b and the included angle C
  112.        Also find angle A
  113.    '''
  114.     sa, ca = sin(a), cos(a)
  115.     sb, cb = sin(b), cos(b)
  116.     sC, cC = sin(C), cos(C)
  117.  
  118.     u = sa * cb - ca * sb * cC
  119.     v = sb * sC
  120.     num = (u ** 2 + v ** 2) ** 0.5
  121.     den = ca * cb + sa * sb * cC
  122.  
  123.     # side c, angle A
  124.     return atan2(num, den), atan2(v, u)
  125.  
  126.  
  127. def gc_distance(p, q):
  128.     ''' The great circle distance between two points '''
  129.     return opp_side(p.colat, q.colat, q.lon - p.lon)
  130.  
  131. def gc_distance_azi(p, q):
  132.     ''' The great circle distance between two points & azimuth at p '''
  133.     return opp_side_azi(p.colat, q.colat, q.lon - p.lon)
  134.  
  135.  
  136. def azi_dist(p, azi, dist):
  137.     ''' Find point x given point p, azimuth px, and distance px '''
  138.     x_colat, delta = opp_side_azi(p.colat, dist, azi)
  139.     x = LatLong(0.5 * pi - x_colat, p.lon + delta, in_radians=True)
  140.     return x
  141.  
  142.  
  143. def tri_test(*sides):
  144.     ''' Triangle inequality.
  145.  
  146.        Check that the longest side is not greater than
  147.        the sum of the other 2 sides.
  148.        Return None if triangle ok, otherwise return
  149.        the longest side's index & length and the excess.
  150.    '''
  151.     i, m = max(enumerate(sides), key=itemgetter(1))
  152.     # m > (a + b) -> 2*m > (m + a + b)
  153.     excess = 2 * m - sum(sides)
  154.     if excess > 0:
  155.         return i, m, excess
  156.     return None
  157.  
  158.  
  159. def gc_triangulate(a, ax_dist, b, bx_dist, verbose=0):
  160.     ''' Great circle distance triangulation
  161.  
  162.        Given points a & b find the two points x0 & x1 that
  163.        are both ax_dist & bx_dist, respectively, from a & b.
  164.        Distances are in degrees.
  165.    '''
  166.     # Distance AB, the base of the OAB triangle
  167.     ab_dist, ab_azi = gc_distance_azi(a, b)
  168.     ab_dist_deg = degrees(ab_dist)
  169.     if verbose > 1:
  170.         print ('AB distance: %f\nAB azimuth: %f' %
  171.             (ab_dist_deg, degrees(ab_azi)))
  172.  
  173.     # Make sure sides of ABX obey triangle inequality
  174.     bad = tri_test(ab_dist_deg, ax_dist, bx_dist)
  175.     if bad is not None:
  176.         print 'Bad gc side length %d: %f, excess = %f' % bad
  177.         raise ValueError
  178.  
  179.     # Now we need the distance args in radians
  180.     ax_dist, bx_dist = radians(ax_dist), radians(bx_dist)
  181.  
  182.     # Angle BAX
  183.     bax = opp_angle(ax_dist, ab_dist, bx_dist)
  184.     if verbose > 1:
  185.         print 'Angle BAX: %f\n' % degrees(bax)
  186.  
  187.     # OAX triangle, towards the pole
  188.     ax_azi = ab_azi - bax
  189.     if verbose > 1:
  190.         print 'AX0 azimuth: %f' % degrees(ax_azi)
  191.     x0 = azi_dist(a, ax_azi, ax_dist)
  192.  
  193.     # OAX triangle, away from the pole
  194.     ax_azi = ab_azi + bax
  195.     if verbose > 1:
  196.         print 'AX1 azimuth: %f\n' % degrees(ax_azi)
  197.     x1 = azi_dist(a, ax_azi, ax_dist)
  198.     return x0, x1
  199.  
  200. # -------------------------------------------------------------------
  201. # Geodesic stuff using geographiclib & the WGS84 ellipsoid
  202.  
  203. # default keys returned by Geo.Inverse
  204. # ['a12', 'azi1', 'azi2', 'lat1', 'lat2', 'lon1', 'lon2', 's12']
  205. # full set of keys
  206. # ['M12', 'M21', 'S12', 'a12', 'azi1', 'azi2',
  207. # 'lat1', 'lat2', 'lon1', 'lon2', 'm12', 's12']
  208.  
  209. def geo_distance(p, q):
  210.     ''' The geodesic distance between two points '''
  211.     return Geo.Inverse(p.dlat, p.dlon, q.dlat, q.dlon)['s12']
  212.  
  213.  
  214. # Meridional radius of curvature and Radius of circle of latitude,
  215. # multiplied by (pi / 180.0) to simplify calculation of partial derivatives
  216. # of geodesic length wrt latitude & longitude in degrees
  217. # Geo._a : equatorial radius of the ellipsoid, in metres
  218. # Geo._e2 : eccentricity ** 2 of the ellipsoid
  219. def rho_R(lat):
  220.     lat = radians(lat)
  221.     w2 = 1.0 - Geo._e2 * sin(lat) ** 2
  222.     w = w2 ** 0.5
  223.  
  224.     # Meridional radius of curvature
  225.     rho = Geo._a * (1.0 - Geo._e2) / (w * w2)
  226.  
  227.     # Radius of circle of latitude; a / w is the normal radius of curvature
  228.     R = Geo._a * cos(lat) / w
  229.     return radians(rho), radians(R)
  230.  
  231.  
  232. def normalize_lat_lon(lat, lon):
  233.     ''' Fix latitude & longitude if abs(lat) > 90 degrees,
  234.        i.e., the point has gone over the pole.
  235.        Also normalize longitude to -180 <= x < 180
  236.    '''
  237.     if lat > 90.0:
  238.         lat = 180.0 - lat
  239.         lon = -lon
  240.     elif lat < -90.0:
  241.         lat = -180.0 - lat
  242.         lon = -lon
  243.     lon = normalize_lon(lon, 180.0)
  244.     return lat, lon
  245.  
  246.  
  247. def geo_newton(a, b, x, ax_dist, bx_dist, verbose=0):
  248.     ''' Solve a pair of simultaneous geodesic distance equations
  249.        using Newton's method.
  250.        Refine an initial approximation of point x such that
  251.        the distance from point a to x is ax_dist, and
  252.        the distance from point b to x is bx_dist
  253.    '''
  254.     # Original approximations
  255.     x_dlat, x_dlon = x.dlat, x.dlon
  256.  
  257.     # Typically, 4 or 5 loops are adequate.
  258.     for i in xrange(30):
  259.         # Find geodesic distance from a to x, & azimuth at x
  260.         d = Geo.Inverse(a.dlat, a.dlon, x_dlat, x_dlon)
  261.         f0, a_azi = d['s12'], d['azi2']
  262.  
  263.         # Find geodesic distance from b to x, & azimuth at x
  264.         d = Geo.Inverse(b.dlat, b.dlon, x_dlat, x_dlon)
  265.         g0, b_azi = d['s12'], d['azi2']
  266.  
  267.         #Current errors in f & g
  268.         delta_f = ax_dist - f0
  269.         delta_g = bx_dist - g0
  270.         if verbose > 1:
  271.             print i, ': delta_f =', delta_f, ', delta_g =', delta_g
  272.  
  273.         if (almost_equal(delta_f, 0, distance_tol) and
  274.             almost_equal(delta_g, 0, distance_tol)):
  275.             if verbose and i > 9: print 'Loops =', i
  276.             break
  277.  
  278.         # Calculate partial derivatives of f & g
  279.         # wrt latitude & longitude in degrees
  280.         a_azi = radians(a_azi)
  281.         b_azi = radians(b_azi)
  282.         rho, R = rho_R(x_dlat)
  283.         f_lat = rho * cos(a_azi)
  284.         g_lat = rho * cos(b_azi)
  285.         f_lon = R * sin(a_azi)
  286.         g_lon = R * sin(b_azi)
  287.  
  288.         # Generate new approximations of x_dlat & x_dlon
  289.         den = f_lat * g_lon - f_lon * g_lat
  290.         dd_lat = delta_f * g_lon - f_lon * delta_g
  291.         dd_lon = f_lat * delta_g - delta_f * g_lat
  292.         x_dlat += dd_lat / den
  293.         x_dlon += dd_lon / den
  294.         x_dlat, x_dlon = normalize_lat_lon(x_dlat, x_dlon)
  295.     else:
  296.         print ('Warning: Newton approximation loop fell through '
  297.         'without finding a solution after %d loops.' % (i + 1))
  298.  
  299.     return LatLong(x_dlat, x_dlon)
  300.  
  301.  
  302. def geo_triangulate(a, ax_dist, b, bx_dist, verbose=0):
  303.     ''' Geodesic Triangulation
  304.  
  305.        Given points a & b find the two points x0 & x1 that
  306.        are both ax_dist & bx_dist, respectively from a & b.
  307.        Distances are in metres.
  308.    '''
  309.     ab_dist = geo_distance(a, b)
  310.     if verbose:
  311.         print 'ab_dist =', ab_dist
  312.  
  313.     # Make sure sides of ABX obey triangle inequality
  314.     bad = tri_test(ab_dist, ax_dist, bx_dist)
  315.     if bad is not None:
  316.         print 'Bad geo side length %d: %f, excess = %f' % bad
  317.         raise ValueError
  318.  
  319.     # Find approximate great circle solutions.
  320.     # Distances need to be given in degrees, so we approximate them
  321.     # using 1 degree = 60 nautical miles
  322.     ax_deg = ax_dist / metres_per_deg
  323.     bx_deg = bx_dist / metres_per_deg
  324.     ab_deg = degrees(gc_distance(a, b))
  325.  
  326.     # Make sure that the sides of the great circle triangle
  327.     # obey the triangle inequality
  328.     bad = tri_test(ab_deg, ax_deg, bx_deg)
  329.     if bad is not None:
  330.         if verbose > 1:
  331.             print ('Bad gc side length after conversion '
  332.                 '%d: %f, excess = %f. Adjusting...' % bad)
  333.         i, _, excess = bad
  334.         if i == 1:
  335.             ax_deg -= excess
  336.             bx_deg += excess
  337.         elif i == 2:
  338.             bx_deg -= excess
  339.             ax_deg += excess
  340.         else:
  341.             ax_deg += excess
  342.             bx_deg += excess
  343.         #bad = tri_test(ab_deg, ax_deg, bx_deg)
  344.         #if bad:
  345.             #print 'BAD ', bad
  346.             #print (ab_deg, ax_deg, bx_deg)
  347.             #print a, ax_dist, b, bx_dist
  348.  
  349.     x0, x1 = gc_triangulate(a, ax_deg,  b, bx_deg, verbose=verbose)
  350.  
  351.     if verbose:
  352.         ax = geo_distance(a, x0)
  353.         bx = geo_distance(b, x0)
  354.         print '\nInitial X0:', x0
  355.         print 'ax0 =', ax, ', error =', ax - ax_dist
  356.         print 'bx0 =', bx, ', error =', bx - bx_dist
  357.  
  358.     # Use Newton's method to get the true position of x0
  359.     x0 = geo_newton(a, b, x0, ax_dist, bx_dist, verbose=verbose)
  360.  
  361.     if verbose:
  362.         ax = geo_distance(a, x1)
  363.         bx = geo_distance(b, x1)
  364.         print '\nInitial X1:', x1
  365.         print 'ax1 =', ax, ', error =', ax - ax_dist
  366.         print 'bx1 =', bx, ', error =', bx - bx_dist
  367.         print
  368.  
  369.     # Use Newton's method to get the true position of x1
  370.     x1 = geo_newton(a, b, x1, ax_dist, bx_dist, verbose=verbose)
  371.     return x0, x1
  372.  
  373. # -------------------------------------------------------------------
  374.  
  375. def rand_lat_lon(maxlat=90.0, maxlon=180.0):
  376.     lat = 2 * maxlat * random() - maxlat
  377.     lon = 2 * maxlon * random() - maxlon
  378.     return lat, lon
  379.  
  380. def test(numtests, verbose=0):
  381.     print 'Testing...'
  382.     for i in xrange(1, numtests+1):
  383.         if verbose:
  384.             print '\n### Test %d ###' % i
  385.  
  386.         # Generate 3 random points
  387.         alat, alon = rand_lat_lon()
  388.         blat, blon = rand_lat_lon()
  389.         xlat, xlon = rand_lat_lon()
  390.  
  391.         # Skip if any points are too close to each other
  392.         if ((almost_equal(alat, blat, position_tol)
  393.          and almost_equal(alon, blon, position_tol)) or
  394.             (almost_equal(alat, xlat, position_tol)
  395.          and almost_equal(alon, xlon, position_tol)) or
  396.             (almost_equal(blat, xlat, position_tol)
  397.          and almost_equal(blon, xlon, position_tol))):
  398.             continue
  399.  
  400.         a = LatLong(alat, alon)
  401.         b = LatLong(blat, blon)
  402.         x = LatLong(xlat, xlon)
  403.         ax = geo_distance(a, x)
  404.         bx = geo_distance(b, x)
  405.  
  406.         if verbose:
  407.             print ('A: %s\nB: %s\nX: %s\nax = %.15f\nbx = %.15f\n' %
  408.             (a, b, x, ax, bx))
  409.  
  410.         x0, x1 = geo_triangulate(a, ax, b, bx, verbose=verbose)
  411.         if verbose:
  412.             print 'X0', x0
  413.             print 'X1', x1
  414.  
  415.         if (almost_equal(x0.dlat, x1.dlat, position_tol) and
  416.             almost_equal(x0.dlon, x1.dlon, position_tol)):
  417.             print (' %d Warning: only 1 solution found\n'
  418.                 'x0 %.15f, %.15f\nx1 %.15f, %.15f'
  419.                 % (i, x0.dlat, x0.dlon, x1.dlat, x1.dlon))
  420.  
  421.         # Verify that one of the computed x's is (almost) in the
  422.         # same position as the original x
  423.         if not (
  424.             (almost_equal(x0.dlat, xlat, position_tol) and
  425.              almost_equal(x0.dlon, xlon, position_tol)) or
  426.             (almost_equal(x1.dlat, xlat, position_tol) and
  427.              almost_equal(x1.dlon, xlon, position_tol))):
  428.             print (' %d Warning: Bad x\nx  %.15f, %.15f\nx0 %.15f, %.15f\n'
  429.                 'x1 %.15f, %.15f\na %.15f %.15f; b %.15f %.15f\n'
  430.                 'ax %.15f, bx %.15f' % (i, x.dlat, x.dlon,
  431.                 x0.dlat, x0.dlon, x1.dlat, x1.dlon,
  432.                 a.dlat, a.dlon, b.dlat, b.dlon, ax, bx))
  433.  
  434.         # Calculate the distances
  435.         ax0 = geo_distance(a, x0)
  436.         ax1 = geo_distance(a, x1)
  437.         bx0 = geo_distance(b, x0)
  438.         bx1 = geo_distance(b, x1)
  439.  
  440.         if verbose:
  441.             print 'ax0 = %.15f\nbx0 = %.15f' % (ax0, bx0)
  442.             print 'ax1 = %.15f\nbx1 = %.15f' % (ax1, bx1)
  443.  
  444.         # Verify the distances
  445.         if not almost_equal(ax0, ax, distance_tol):
  446.             print ' %d ax0 error: %.15f, %.15f' % (i, ax0, ax)
  447.  
  448.         if not almost_equal(ax1, ax, distance_tol):
  449.             print ' %d ax1 error: %.15f, %.15f' % (i, ax1, ax)
  450.  
  451.         if not almost_equal(bx0, bx, distance_tol):
  452.             print ' %d bx0 error: %.15f, %.15f' % (i, bx0, bx)
  453.  
  454.         if not almost_equal(bx1, bx, distance_tol):
  455.             print ' %d bx1 error: %.15f, %.15f' % (i, bx1, bx)
  456.  
  457.         if not verbose and i % 10 == 0:
  458.             sys.stderr.write('\r %d ' % i)
  459.     if not verbose: sys.stderr.write('\n')
  460.  
  461. # -------------------------------------------------------------------
  462.  
  463. def usage(msg=None):
  464.     s = 'Error: %s\n\n' % msg if msg != None else ''
  465.     s += ('Geodesic distance triangulation.\n'
  466.         'Find two locations that are at given WGS84 '
  467.         'geodesic distances from two fixed points.\n\n'
  468.  
  469.         'Usage\n\n'
  470.         'Print this help:\n'
  471.         ' {0} -h\n\n'
  472.  
  473.         'Normal mode:\n'
  474.         ' {0} [-v verbosity] -- A_lat A_lon A_dist  B_lat B_lon B_dist  '
  475.         '[X_lat X_lon]...\n\n'
  476.  
  477.         'Test mode: run tests using sets of 3 random points\n'
  478.         ' {0} [-v verbosity] [-s rand_seed] -t num_tests\n\n'
  479.  
  480.         'In normal mode, position arguments are in decimal degrees '
  481.         '(N/E +ve, S/W -ve), distances are in metres.\n'
  482.         'The end-of-options marker -- should precede the position arguments '
  483.         'so that negative latitudes or longitudes\n'
  484.         'are not misinterpreted as invalid options.\n\n'
  485.  
  486.         'This program generates initial approximate solutions using '
  487.         'spherical trigonometry.\n'
  488.         'These solutions are then refined using Newton\'s method to '
  489.         'obtain geodesic solutions.\n'
  490.         'Alternatively, any number of approximate solutions X_lat X_lon '
  491.         'may follow the A & B positions & distances.\n\n'
  492.  
  493.         'In test mode, if no seed string is given system time is used '
  494.         'to generate a random seed string.\n'
  495.         'This string is printed to simplify repeating such test series.\n\n'
  496.  
  497.         'The verbosity option is a number from 0 to 2.\n'
  498.         'A non-zero verbosity tells the program to print various significant '
  499.         'parameters; the higher the verbosity, the more gets printed.\n'
  500.         'Please see the source code for details.'.format(sys.argv[0]))
  501.     print s
  502.     exit()
  503.  
  504. def main():
  505.     # Default args
  506.     verbose = 0
  507.     numtests = 0
  508.     rand_seed = None
  509.  
  510.     try:
  511.         opts, args = getopt.getopt(sys.argv[1:], "hv:t:s:")
  512.     except getopt.GetoptError as e:
  513.         usage(e.msg)
  514.  
  515.     for o, a in opts:
  516.         if o == "-h": usage()
  517.         elif o == "-v": verbose = int(a)
  518.         elif o == "-t": numtests = int(a)
  519.         elif o == "-s": rand_seed = a
  520.  
  521.     # Test mode
  522.     if numtests > 0:
  523.         if rand_seed is None:
  524.             seed(None)
  525.             rand_seed = str(random())
  526.         print 'seed', rand_seed
  527.         seed(rand_seed)
  528.         test(numtests, verbose=verbose)
  529.         exit()
  530.  
  531.     # Normal mode
  532.     numargs = len(args)
  533.     if numargs < 6 or numargs % 2 != 0:
  534.         usage('Bad number of arguments for normal mode.')
  535.  
  536.     try:
  537.         args = [float(s) for s in args]
  538.     except ValueError as e:
  539.         usage(e)
  540.  
  541.     a_lat, a_lon, ax_dist, b_lat, b_lon, bx_dist = args[:6]
  542.     a = LatLong(a_lat, a_lon)
  543.     b = LatLong(b_lat, b_lon)
  544.  
  545.     args = args[6:]
  546.     if not args:
  547.         # No initial approximations given; (try to) find 2 solutions
  548.         # via great circle geo_triangulation.
  549.         x0, x1 = geo_triangulate(a, ax_dist, b, bx_dist, verbose=verbose)
  550.         for i, x in enumerate((x0, x1)):
  551.             print 'X%d: %s' % (i, x)
  552.             ax = geo_distance(a, x)
  553.             bx = geo_distance(b, x)
  554.             print 'AX=%.15f, BX=%.15f\n' % (ax, bx)
  555.     else:
  556.         #Collect X latitude, longitude pairs into points
  557.         args = zip(*[iter(args)] * 2)
  558.         xlist = [LatLong(*t) for t in args]
  559.  
  560.         #Process each given initial approximation.
  561.         for i, x in enumerate(xlist):
  562.             print 'Initial X%d: %s' % (i, x)
  563.             x = geo_newton(a, b, x, ax_dist, bx_dist, verbose=verbose)
  564.             print '  Final X%d: %s' % (i, x)
  565.             ax = geo_distance(a, x)
  566.             bx = geo_distance(b, x)
  567.             print 'AX=%.15f, BX=%.15f\n' % (ax, bx)
  568.  
  569.  
  570. if __name__ == '__main__':
  571.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement