Bash Snippet: Calculating the Distance Between 2 Coordinates

I have a tendency to do things in bash that I’d probably be better off doing in perl or python. Although bash may have super powers, math is not one of them, and so like my last post this script also requires bc. I’ll try and keep these code snippet posts short and sweet, and yes this is my feeble attempt to pad my year-end post numbers.

Calculate the distance between two longitude and latitude points in bash:

deg2rad () {
        bc -l <<< "$1 * 0.0174532925"
}

rad2deg () {
        bc -l <<< "$1 * 57.2957795"
}

acos () {
        pi="3.141592653589793"
        bc -l <<<"$pi / 2 – a($1 / sqrt(1 – $1 * $1))"
}

distance () {
        lat_1="$1"
        lon_1="$2"
        lat_2="$3"
        lon_2="$4"
        delta_lat=`bc <<<"$lat_2$lat_1"`
        delta_lon=`bc <<<"$lon_2$lon_1"`
        lat_1="`deg2rad $lat_1`"
        lon_1="`deg2rad $lon_1`"
        lat_2="`deg2rad $lat_2`"
        lon_2="`deg2rad $lon_2`"
        delta_lat="`deg2rad $delta_lat`"
        delta_lon="`deg2rad $delta_lon`"

        distance=`bc -l <<< "s($lat_1) * s($lat_2) + c($lat_1) * c($lat_2) * c($delta_lon)"`
        distance=`acos $distance`
        distance="`rad2deg $distance`"
        distance=`bc -l <<< "$distance * 60 * 1.15078"`
        distance=`bc <<<"scale=4; $distance / 1"`
        echo $distance
}

# Keywest, FL to Cuba
distance 23.137160859692981.68746948242188 24.54079389806876781.76849365234375

Edit: Top Google result for “calculate the distance between two coordinates in bash” isn’t even done in bash, but rather compiled C code called from a bash script. Seriously Dave Taylor, you have a series of articles at Linux Journal called “Work the Shell”, and this is what you came up with? At least I tried.


Posted

in

by

Tags:

Comments

4 responses to “Bash Snippet: Calculating the Distance Between 2 Coordinates”

  1. rkkki Avatar
    rkkki

    Hi, excellent tutorial.
    But, what is the unit parameter? Metric or Imperial?
    How may I convert to meters?
    Thanks

    I’ll bookmark to see the answer, please help me.

  2. Julien Lamarche Avatar
    Julien Lamarche

    Pretty sure I copied this verbatim and got 79 kms isntead of the correct 51 kms.

    Here’s the xtrace of the function:

    ++ distance 45.401244 -75.74193 45.28713 -75.10282
    ++ earth_radius=3960.00
    ++ lat_1=45.401244
    ++ lon_1=-75.74193
    ++ lat_2=45.28713
    ++ lon_2=-75.10282
    +++ bc
    ++ delta_lat=-.114114
    +++ bc
    ++ delta_lon=.63911
    +++ deg2rad 45.401244
    +++ bc -l
    ++ lat_1=.7924011913
    +++ deg2rad -75.74193
    +++ bc -l
    ++ lon_1=-1.3219460588
    +++ deg2rad 45.28713
    +++ bc -l
    ++ lat_2=.7904095263
    +++ deg2rad -75.10282
    +++ bc -l
    ++ lon_2=-1.3107914850
    +++ deg2rad -.114114
    +++ bc -l
    ++ delta_lat=-.0019916650
    +++ deg2rad .63911
    +++ bc -l
    ++ delta_lon=.0111545737
    +++ bc -l
    ++ distance=.9998
    +++ acos .9998
    +++ pi=3.141592653589793
    +++ bc -l
    ++ distance=.0200
    +++ rad2deg .0200
    +++ bc -l
    ++ distance=1.1459155
    +++ bc -l
    ++ distance=79.1713018
    +++ bc
    ++ distance=79.1713
    1. Chris Avatar
      Chris

      @rkkki & @Julien Lamarche

      The script is in those not-so-international units known as “miles”. I apologize for the confusion.

      https://en.wikipedia.org/wiki/Mile

      In order to give distance in kilometers:

      The line that reads:
      distance=`bc -l <<< "$distance * 60 * 1.15078"`

      can be changed to:
      distance=`bc -l <<< "$distance * 60 * 1.15078 * 1.609344"`

      To try to make a little more sense of the seemingly arbitrary numbers:
      degree = 1/360 of a circle
      arcminute = 1/60 of a degree
      The nautical mile is nearly equal to a minute of latitude
      https://en.wikipedia.org/wiki/Nautical_mile
      1 nautical mile is rougly 1.15078 statute (regular, or land) miles
      1 mile is roughly 1.609344 kilometers
      Although, regarding the nautical mile:
      By international agreement it has been set at 1,852 metres exactly (about 6,076 feet).

      So, if you prefer meters you could use:
      distance=`bc -l <<< "$distance * 60 * 1852"`

      You might have noticed that my script previously used 1.1515, which is used frequently on the internet for this kind of calculation, however on further investigation it seems that this value is based on a definition of nautical miles that hasn’t been used since the 1950s, and so I’ve updated the script to reflect the change.

      The ideal way to deal with kilometers in this script then is to substitute:
      distance=`bc -l <<< "$distance * 60 * 1.15078"`
      with:
      distance=`bc -l <<< "$distance * 60 * 1.85200"`

      A number of other potential issues exist:
      * The Earth isn’t actually round as this script presumes. Accuracy, particularly over long distances can be problematic.

      * Results may vary, some scripts such as this one: http://andrew.hedges.name/experiments/haversine/ use a different value for the radius of the Earth.

      * The haversine formula is not good for working with antipodal points.

      * I have no idea what I’m doing, and you probably shouldn’t rely on this for doing important work.

      @Julien Lamarche
      The output should have been 32.0006, which isn’t correct, but fairly close to the results compared to this page: http://boulter.com/gps/distance/?from=45.401244+-75.74193&to=45.28713+-75.10282&units=m

      As to how you got 79.1713, that is a good question..

      Your problem seems to exist at the value going in to the acos function. The result you get from ‘bc’ on the first ‘distance=’ assignment doesn’t really make sense. Your ‘bc’ defaults to different precision than mine, for example where you get:
      lat_1=.7924011913
      I get:
      lat_1=.7924011913958700
      This isn’t really a problem until it comes to the input going into the acos function. Your input value is 0.998 where as mine is 0.99996728459943628626, it’s a seemingly small difference, but creates a significant variance in the output. If I plug in 0.998 I get 79.1726 as my output, which is pretty close to what you are seeing. It’s very strange that the rest of your bc output gives you about 10 places of precision every where else. I wonder if you may have accidentally copied the the ‘scale=4’ directive used later in the script into the first ‘distance=’ line.

      If I use:
      distance=`bc -l <<< "scale=4; s($lat_1) * s($lat_2) + c($lat_1) * c($lat_2) * c($delta_lon)"` instead of: distance=`bc -l <<< "s($lat_1) * s($lat_2) + c($lat_1) * c($lat_2) * c($delta_lon)"` I get 79.1726, which is pretty close to your 79.1713 value, the remaining difference might be attributed to the default difference in scale. I've changed the formatting of the code block to try and display more of it on the screen, maybe this will make it easier to copy and paste.

  3. Claude Avatar
    Claude

    Nice, thanks for the script!

Leave a Reply to Chris Cancel reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.