Great Circle Distances in JavaScript

The shortest distance between two locations on the surface of Earth (or any planet) is known as the Great Circle Distance. Except for relatively short distances these cannot be measured on a map due to the distortion and flattening necessary to represent a sphere on a flat plane. However, the calculation of the Great Circle Distance from two coordinates is simple, although I suspect generations of midshipmen might not have agreed. In this post I will write a short JavaScript application to calculate the distance from London to various other cities round the world.

As always, Wikipedia provides a comprehensive reference on the subject of calculating Great Circle distances. The principle is very simple, if you understand radians.

Everyone is familiar with using degrees to measure angles - we all gain an understanding of them when we are given our first school protractor. Slightly less well understood is the radian. (If you already know all about radians skip to the next paragraph!) Imagine a circle (for the purposes of this project, imagine the circle is the flat part of a hemisphere, a sphere cut in half) and now imagine drawing a line from the centre to the edge. The length of that line is of course the radius. Now draw round the circumference of the circle by the same distance as the radius, finally drawing another line back to the centre of the circle. The angle between the two lines meeting at the centre is 1 radian. The size of 1 radian in degrees is 180/π which is an irrational number but 57.29577951° to 8dp.

If you skipped my scholarly discourse on radians, welcome back. If you think about it for a moment, it should become clear that if the angle between two points on the surface of our little blue planet is 1 radian, then the great circle distance between them is the same as the radius of the planet. If the angle is 2 radians, the distance is twice the radius; if the angle is 0.5 radians the distance is half the radius and so in. In short, the Great Circle Distance between two points in the angle between them multiplied by the radius. The units of distance are irrelevant as long as they are the same (don't mix miles and kilometres for example) but we must use radians rather than degrees. This is the reason we are using radians - they reduce the problem to one simple multiplication.

We're getting ahead of ourselves a bit here, as we first need to calculate the angle between two points from their latitudes and longitudes. I'll "borrow" the formula for this from Wikipedia, but first let's define some terms.

  • Φ1 and λ1 - latitude and longitude in radians of first location
  • Φ2 and λ2 - latitude and longitude in radians of second location
  • ΔΦ and Δλ - absolute differences between latitudes and longitudes
  • Δσ - angle between locations

Don't worry, I won't be using Greek in the code. Now for the formula - note use of dot notation for multiplication.

So basically we need to apply this formula to a pair of coordinates, then multiply the result by the radius of the Earth.

The Code

This project consists of a set of files, the two central JavaScript files being

  • greatcircledistance.js
  • greatcircledistancepage.js

There are also a few other files: an HTML file to display output, a graphic, a CSS file and a JavaScript file containing a function to output text to the page. All the files can be downloaded as a zip from the Downloads page of you can clone/download from Github.

Source Code Links

ZIP File
GitHub

Firstly let's take a look at the greatcircledistance.js file.

greatcircledistance.js

const DEGREES_IN_RADIAN = 57.29577951
const MEAN_EARTH_RADIUS_KM = 6371
const KILOMETRES_IN_MILE = 1.60934

function calcGreatCircle(name1,latitude1_degrees, longitude1_degrees, name2, latitude2_degrees, longitude2_degrees)
{
    const gc = {};

    gc.name1 = name1;
    gc.name2 = name2;
    gc.latitude1_degrees = latitude1_degrees;
    gc.longitude1_degrees = longitude1_degrees;
    gc.latitude2_degrees = latitude2_degrees;
    gc.longitude2_degrees = longitude2_degrees;

    gc.latitude1_radians = 0;
    gc.longitude1_radians = 0;
    gc.latitude2_radians = 0;
    gc.longitude2_radians = 0;
    gc.central_angle_radians = 0;
    gc.central_angle_degrees = 0;
    gc.distance_kilometres = 0;
    gc.distance_miles = 0;
    gc.valid = true;

    validateDegrees(gc);

    if(gc.valid)
    {
        calculateRadians(gc);
        calculateCentralAngle(gc);
        calculateDistance(gc);
    }

    return gc;
}

function printGreatCircle(gc)
{
    if(gc.valid)
    {
        writeToConsole(`${gc.name1}<br/>${"-".repeat(gc.name1.length)}<br/>`, "console");
        writeToConsole(`Latitude:  ${gc.latitude1_degrees} degrees / ${gc.latitude1_radians} radians <br/>`, "console");
        writeToConsole(`Longitude: ${gc.longitude1_degrees} degrees / ${gc.longitude1_radians} radians<br/>`, "console");

        writeToConsole(`<br/>${gc.name2}<br/>${"-".repeat(gc.name2.length)}<br/>`, "console");
        writeToConsole(`Latitude: ${gc.latitude2_degrees}  degrees / ${gc.latitude2_radians} radians<br/>`, "console");
        writeToConsole(`Longitude: ${gc.longitude2_degrees} degrees / ${gc.longitude2_radians} radians<br/>`, "console");

        writeToConsole(`<br/>Central Angle ${gc.central_angle_radians} radians / ${gc.central_angle_degrees} degrees<br/>`, "console");

        writeToConsole(`Distance ${gc.distance_kilometres} kilometres / ${gc.distance_miles} miles<br/>`, "console");
    }
    else
    {
        writeToConsole(`Latitude and longitude for ${gc.name1} to ${gc.name2} are invalid<br/>`, "console");
    }

    writeToConsole("====================================================================<br/><br/>", "console");
}

function validateDegrees(gc)
{
    gc.valid = true;

    if(gc.latitude1_degrees < -90.0 || gc.latitude1_degrees > 90.0)
    {
        gc.valid = false;
    }

    if(gc.longitude1_degrees < -180.0 || gc.longitude1_degrees > 180.0)
    {
        gc.valid = false;
    }

    if(gc.latitude2_degrees < -90.0 || gc.latitude2_degrees > 90.0)
    {
        gc.valid = false;
    }

    if(gc.longitude2_degrees < -180.0 || gc.longitude2_degrees > 180.0)
    {
        gc.valid = false;
    }
}

function calculateRadians(gc)
{
    gc.latitude1_radians = gc.latitude1_degrees / DEGREES_IN_RADIAN;
    gc.longitude1_radians = gc.longitude1_degrees / DEGREES_IN_RADIAN;

    gc.latitude2_radians = gc.latitude2_degrees / DEGREES_IN_RADIAN;
    gc.longitude2_radians = gc.longitude2_degrees / DEGREES_IN_RADIAN;
}

function calculateCentralAngle(gc)
{
    let longitudes_abs_diff;

    if(gc.longitude1_radians > gc.longitude2_radians)
    {
        longitudes_abs_diff = gc.longitude1_radians - gc.longitude2_radians;
    }
    else
    {
        longitudes_abs_diff = gc.longitude2_radians - gc.longitude1_radians;
    }

    gc.central_angle_radians = Math.acos(Math.sin(gc.latitude1_radians)
                                       * Math.sin(gc.latitude2_radians)
                                       + Math.cos(gc.latitude1_radians)
                                       * Math.cos(gc.latitude2_radians)
                                       * Math.cos(longitudes_abs_diff));

    gc.central_angle_degrees = gc.central_angle_radians * DEGREES_IN_RADIAN;
}

function calculateDistance(gc)
{
    gc.distance_kilometres = MEAN_EARTH_RADIUS_KM * gc.central_angle_radians;

    gc.distance_miles = gc.distance_kilometres / KILOMETRES_IN_MILE;
}

Conversion Factors and calcGreatCircle

Firstly we define a few conversion factors: DEGREES_IN_RADIAN, MEAN_EARTH_RADIUS_KM and KILOMETRES_IN_MILE before implementing the core calcGreatCircle function. This takes as arguments the names, latitudes and longitudes of the two places we want to calculate the distance between and adds them as properties to a new object. The properties we don't yet have values for are also created but set to 0.

We then pass the object to the validateDegrees function which sets the valid property as we'll see in a moment. If the degrees are valid the object is passed to three further functions which set the missing values - radians, central angle and finally the distance. At the end of the function the object is returned.

printGreatCircle

This function takes an object created by calcGreatCircle and prints out its properties in a neat format, or a suitable message if it is invalid.

validateDegrees

This function checks the latitudes are between -90° and +90°, and that the longitudes are between -180° and +180°.

calculateRadians

The calculateRadians function does its stuff by dividing the degrees by DEGREES_IN_RADIAN which was initialised at the top of the file.

calculateCentralAngle

Here we calculate the absolute difference between the longitudes, and then implement this formula

which we saw above to actually calculate the angle in radians. Finally it calculates the degree equivalent.

calculateDistance

The last function in this file is calculateDistance, which as I have mentioned is simply a matter of multiplying the angle in radians by the radius of the earth. We also do the kilometres to miles conversion here.

Putting the Code to Use

We can now move on to greatcircledistancepage.js which contains our window.onload function and calls the code we have written for seven different cities.

greatcircledistancepage.js

window.onload = function()
{
    writeToConsole("Great Circle Distances<br/>----------------------<br/><br/>", "console");

    let gc_london_tokyo = calcGreatCircle("London", 51.507222, -0.1275, "Tokyo", 35.683333, 139.683333);
    printGreatCircle(gc_london_tokyo);

    let gc_london_new_york = calcGreatCircle("London", 51.507222, -0.1275, "New York", 40.7127, -74.0059);
    printGreatCircle(gc_london_new_york);

    let gc_london_new_delhi = calcGreatCircle("London", 51.507222, -0.1275, "New Delhi", 28.613889, 77.208889);
    printGreatCircle(gc_london_new_delhi);

    let gc_london_sydney = calcGreatCircle("London", 51.507222, -0.1275, "Sydney", -33.865, 151.209444);
    printGreatCircle(gc_london_sydney);

    let gc_london_cape_town = calcGreatCircle("London", 51.507222, -0.1275, "Cape Town", -33.925278, 18.423889);
    printGreatCircle(gc_london_cape_town);

    let gc_london_rio_de_janeiro = calcGreatCircle("London", 51.507222, -0.1275, "Rio de Janeiro", -22.908333, -43.196389);
    printGreatCircle(gc_london_rio_de_janeiro);

    let gc_london_oblivion = calcGreatCircle("London", 51.507222, -0.1275, "Oblivion", 91, 360);
    printGreatCircle(gc_london_oblivion);
}

The last set of values includes the invalid 91° north just to see what happens...!

The code is now finished so open greatcircledistance in your browser which should give you this.

Great Circle Distances

As well as the names, latitudes and longitudes we supplied we now have the latitudes and longitudes in radians, the central angle in both degrees and radians, and the distance in kilometres and miles.