HOME ARCHIVE TAGS ABOUT RSS

Calculate the Distance Between Two GPS Points with Python (Vincenty’s Inverse Formula)

Recently the work I have been doing requires a higher degree of accuracy than which the haversine method I was using could provide. This prompted me to implement a Python version of the Vincenty’s inverse formula. Unlike the Haversine method (which I posted about previously) of directly calculating the great-circle distance between two points on a perfectly spherical Earth, Vincenty’s formulae is an iterative method which more realistically assumes Earth as an oblate spheroid.

Before proceeding further, several values need to be defined:

\( a = 6378137.0 \) : length of semi-major axis of the ellipsoid aka radius of the equator in meters (WGS-84)
\( f = 1 / 298.257223563 \) : flattening of the ellipsoid (WGS-84)
\( b = \left ( 1 - f \right ) a \) : length of semi-minor axis of the ellipsoid (radius at the poles, 6356752.314245 meters in WGS-84)
\( \phi_1, \phi_2 \) : latitude of the points
\( U_1 = \arctan \left [ \left ( 1 - f \right ) \tan \phi_1 \right ] \) : reduced latitude (latitude on the auxiliary sphere)
\( U_2 = \arctan \left [ \left ( 1 - f \right ) \tan \phi_2 \right ] \) : reduced latitude (latitude on the auxiliary sphere)
\( L = L_2 - L_1 \) : difference in longitude of two points
\( \lambda_1, \lambda_2 \) : longitude of the points on the auxiliary sphere
\( \alpha_1, \alpha_2 \) : forward azimuths at the points
\( \alpha \) : azimuth at the equator
\( s \) : ellipsoidal distance between the two points
\( \sigma \) : arc length between points on the auxiliary sphere

Using the values defined above, and the eight equations below, the process of iterating towards a solution can begin. Starting with the first equation on top, sequentially progress towards the last equation at the bottom where the output is \( \lambda \). Once through the first iteration, simply use this newly calculated value of \( \lambda \) and dump it back into the first equation and repeat the process. The end game here is to minimize the value of \( \lambda \).

$$ \sin \sigma = \sqrt{(\cos U_2 \sin\lambda)^2 + (\cos U_1 \sin U_2 - \sin U_1 \cos U_2 \cos\lambda)^2}$$ $$ \cos \sigma = \sin U_1 \sin U_2 + \cos U_1 \cos U_2 \cos \lambda $$ $$ \sigma = \arctan \frac{\sin \sigma}{\cos \sigma} $$ $$ \sin \alpha = \frac{ \cos U_1 \cos U_2 \sin \lambda }{ \sin \sigma }$$ $$ \cos^2 \alpha = 1 - \sin^2 \alpha $$ $$ \cos \left( 2 \sigma_m \right) = \cos \sigma - \frac{2 \sin U_1 sin U_2 }{ \cos^2 \alpha }$$ $$ C = \frac{f}{16} \cos^2 \alpha \left[ 4 + f \left( 4 - 3 \cos^2 \alpha \right) \right]$$ $$ \lambda = L + (1 - C) f \sin \alpha \left \{ \sigma + C \sin \sigma \left [ \cos \left ( 2 \sigma_m \right) + C \cos \sigma \left ( -1 + 2\cos^2 \left ( 2 \sigma_m \right ) \right ) \right ] \right \} $$

When the difference between the current value of \( \lambda \) and the value of \( \lambda \) from the previous iteration is less than the convergence tolerance, we can move on to the final stage (a convergence tolerance of 10e-12 translates to an accuracy of approximately 0.06mm).

$$ u^2 = \cos^2 \alpha \frac{a^2 - b^2}{b^2} $$ $$ A = 1 + \frac{u^2}{16384} \left \{ 4096 + u^2 \left [ -768 + u^2 \left ( 320 - 175 u^2 \right ) \right ] \right \} $$ $$ B = \frac{u^2}{1024} \left \{ 256 + u^2 \left [ -128 + u^2 \left ( 74 - 47 u^2 \right ) \right ] \right \} $$ $$ \Delta \sigma = B \sin \sigma \left \{ \cos \left ( 2 \sigma_m \right ) + \frac{1}{4} B \left [ \cos \sigma \left ( -1 + 2 \cos^2 \left ( 2 \sigma_m \right ) \right ) - \frac{1}{6} B \cos \left ( 2 \sigma_m \right ) \left ( -3 + 4 \sin^2 \sigma \right ) \left ( -3 + 4 \cos^2 \left ( 2 \sigma_m \right ) \right ) \right ] \right \} $$ $$ s = b A \left ( \sigma - \Delta \sigma \right ) $$ $$ \alpha_1 = \arctan \left ( \frac{ \cos U_2 \sin \lambda }{ \cos U_1 \sin U_2 - \sin U_1 \cos U_2 \cos \lambda } \right ) $$ $$ \alpha_2 = \arctan \left ( \frac{ \cos U_1 \sin \lambda }{ - \sin U_1 \cos U_2 + \cos U_1 \sin U_2 \cos \lambda } \right ) $$

Below is the Python implementation of the above equations. Note, I included a maximum number of iterations (maxIter=200).

#------------------------------------------------------------------------------+
#
#   Nathan A. Rooy
#   2016-SEP-30
#   Solve the inverse Vincenty's formulae
#   https://en.wikipedia.org/wiki/Vincenty%27s_formulae
#
#------------------------------------------------------------------------------+

#--- IMPORT DEPENDENCIES ------------------------------------------------------+

from __future__ import division
from math import atan
from math import atan2
from math import cos
from math import radians
from math import sin
from math import sqrt
from math import tan

#--- MAIN ---------------------------------------------------------------------+

class vincenty_inverse:
    def __init__(self,coord1,coord2,maxIter=200,tol=10**-12):

        #--- CONSTANTS ------------------------------------+
        
        a=6378137.0                             # radius at equator in meters (WGS-84)
        f=1/298.257223563                       # flattening of the ellipsoid (WGS-84)
        b=(1-f)*a

        phi_1,L_1,=coord1                       # coordinate pair #1
        phi_2,L_2,=coord2                       # coordinate pair #2

        U_1=atan((1-f)*tan(radians(phi_1)))
        U_2=atan((1-f)*tan(radians(phi_2)))

        L=radians(L_2-L_1)

        Lambda=L                                # set initial value of lambda to L

        sinU1=sin(U_1)
        cosU1=cos(U_1)
        sinU2=sin(U_2)
        cosU2=cos(U_2)

        #--- BEGIN ITERATIONS -----------------------------+
        self.iters=0
        for i in range(0,maxIter):
            self.iters+=1
            
            cosLambda=cos(Lambda)
            sinLambda=sin(Lambda)
            sinSigma=sqrt((cosU2*sin(Lambda))**2+(cosU1*sinU2-sinU1*cosU2*cosLambda)**2)
            cosSigma=sinU1*sinU2+cosU1*cosU2*cosLambda
            sigma=atan2(sinSigma,cosSigma)
            sin_alpha=(cosU1*cosU2*sinLambda)/sinSigma
            cosSq_alpha=1-sin_alpha**2
            cos2Sig_m=cosSigma-((2*sinU1*sinU2)/cosSq_alpha)
            C=(f/16)*cosSq_alpha*(4+f*(4-3*cosSq_alpha))
            Lambda_prev=Lambda
            Lambda=L+(1-C)*f*sin_alpha*(sigma+C*sinSigma*(cos2Sig_m+C*cosSigma*(-1+2*cos2Sig_m**2)))

            # successful convergence
            diff=abs(Lambda_prev-Lambda)
            if diff<=tol:
                break
            
        uSq=cosSq_alpha*((a**2-b**2)/b**2)
        A=1+(uSq/16384)*(4096+uSq*(-768+uSq*(320-175*uSq)))
        B=(uSq/1024)*(256+uSq*(-128+uSq*(74-47*uSq)))
        deltaSig=B*sinSigma*(cos2Sig_m+0.25*B*(cosSigma*(-1+2*cos2Sig_m**2)-(1/6)*B*cos2Sig_m*(-3+4*sinSigma**2)*(-3+4*cos2Sig_m**2)))

        self.m=b*A*(sigma-deltaSig)             # output distance in meters     

if __name__ == "__vincenty_inverse__":
    main()
    

Example usage:

>>> from vincenty import vincenty_inverse
>>> vincenty_inverse([39.152501,-84.412977],[39.152505,-84.412946]).m
2.7161912585815897

And that’s it! Not too difficult right? The code final code can be found on my GitHub (here).