Calculate Distance Between Two Locations in ServiceNow (without an API call!)

I'm gonna keep this really quick --

If you need to calculate the distance between two Location records in the cmn_location table in ServiceNow, as long as both of those locations have latitude and longitude values, you can do so using the script below!

Usage examples at the bottom.

You can turn these functions into methods of a Script Include if you like; whatever boats your float, mate.

EDIT 3/26/25: Nevermind, I turned it into a Script Include for you. 😎

This is just for calculating raw distance; it's not for calculating actual driving distance or "drive time"/duration.

The main value for something like this is ruling things out. Like for example, if you're attempting to determine the optimal path between nodes A (starting point), B, and C, you could do GIS calls for all of that, but it would be expensive.
If, instead, you use the code below to do some pre-checks initially before sending the routing requests to the API, you can identify that location B is 2 miles from A, whereas C is 50 miles from A.
Therefore, you can assume that the routing will be A > B > C; with no expensive GIS API calls.

Adding more nodes further illustrates the point even further. You may be able to determine that A, B, C, and D are all within 5 miles of the starting point and each other, but location E is 10x that.
Therefore, you can optimize the location API queries and save yourself some spend. You thereby get a sort of factorial API cost savings.

How to implement and use DistanceCalculator

  1. Ensure that you are in the scope you intend to put this script in. If you're uncertain, I recommend selecting the Global application scope.
  2. Create a new Script Include in the sys_script_include table.
  3. Set the Name field to DistanceCalculator.
  4. Set Accessible from to "All application scopes".
  5. Set the description field to a meaningful description of the function of the Script Include (such as from the comments above 1the main public function in the script).
  6. Copy the script from the DistanceCalculator.js file in this Gist (below), and paste it into the Script field.
  7. Submit the record.

Once you've created the Script Include, you can call it from any scope using the value in the API Name field, such as var distanceCalc = new global.DistanceCalculator('miles', 3);.

You can trigger a test by calling the ._exampleUsage() method using the below code.

Note that if you don't have the OOB cmn_location records with sys_ids 25ab9c4d0a0a0bb300f7dabdc0ca7c1c and 25ab8f300a0a0bb300d99f69c3ac24cd, you'll want to specify two valid sys_ids for Location records that have name, latitude, and longitude values (using the encoded query latitudeISNOTEMPTY^longitudeISNOTEMPTY^nameISNOTEMPTY).

var distanceCalculator = new DistanceCalculator();
distanceCalculator._exampleUsage();

This will output something like:

*** Script: [DEBUG] Distance: 6.715 km (4.1725 mi)
*** Script: [DEBUG] The distance between "100 South Charles Street, Baltimore,MD" and "10065 East Harvard Avenue, Denver,CO" is 1500.8 miles.

Alternatively, specifying your own Location sys_ids to use for testing:

// Example with custom sys_ids specified
//...
distanceCalculator._exampleUsage( 
	originSysID, 
	destinationSysID
);
var DistanceCalculator = Class.create();
DistanceCalculator.prototype = {
/**
* @description Contains methods for calculating the distance between two geographic
* coordinates or location records using the Haversine formula.
*
* @param {('km'|'miles'|'mile'|'mi'|'yards'|'yd'|'inches'|'in'|'feet'|'ft'|'meters'|'m'|'centimeters'|'cm'|'millimeters'|'mm'|'nautical miles'|'nm')} [distanceUnits='km']
* - The units of the distance. Default is 'km'.
*
* @param {number} [roundDigits=] - Number of decimal places to round the result to.
* The default if this argument is not specified is to not round the result at all, and use the
* math engine's default. .
* The maximum value for roundDigits is 15 (the maximum number of decimal places that
* JavaScript
* can accurately represent), but it is recommended to keep it below 10 to avoid floating
* point
* errors caused by the limitations of the weird Mozilla Rhino Java-based JavaScript engine.
*
* @constructor
*
* @example
* var distanceCalculator = new DistanceCalculator('mi', 3);
* var distanceData = distanceCalculator.distanceBetweenLocationsBySysID(
* '25ab9c4d0a0a0bb300f7dabdc0ca7c1c',
* '25ab8f300a0a0bb300d99f69c3ac24cd',
* null,
* 1 //Overrides the rounding specified in the constructor
* );
*/
initialize : function(distanceUnits, roundDigits) {
this.distanceUnits = distanceUnits || 'km';
this.roundDigits = roundDigits || 0;
},
/**
* @description Calculates the distance between two locations using the sys_ids of
* Location records in the cmn_location table.
*
* @param {String} originLocationID - The sys_id of the Location record for the origin point.
* This should be the sys_id of a valid record in the cmn_location table.
*
* @param destinationLocationID - The sys_id of the Location record for the destination point.
* This should be the sys_id of a valid record in the cmn_location table.
*
* @param {('km'|'miles'|'mile'|'mi'|'yards'|'yd'|'inches'|'in'|'feet'|'ft'|'meters'|'m'|'centimeters'|'cm'|'millimeters'|'mm'|'nautical miles'|'nm')} [distanceUnits='km']
* - The units of the distance. Default is 'km'.
*
* @param {number} [roundDigits=] - Number of decimal places to round the result to.
* The default if this argument is not specified is to not round the result at all, and use the
* math engine's default. .
* The maximum value for roundDigits is 15 (the maximum number of decimal places that
* JavaScript
* can accurately represent), but it is recommended to keep it below 10 to avoid floating
* point
* errors caused by the limitations of the weird Mozilla Rhino Java-based JavaScript engine.
*
* @returns {{origin: {name: string, latitude: number, longitude: number}, destination: {name:
* string, latitude: number, longitude: number}, distance: {value: number, units: string},
* has_errors: boolean, errors: *[]}} - An object containing the origin and destination
* location data, the distance between the two locations, and any errors that occurred
* during the calculation.
*
* @throws {Error} - If latitude and longitude values are not numbers or if invalid distance
* units are provided.
*
*
*
* @example
* var distanceCalculator = new DistanceCalculator('mi', 3);
* var distanceData = distanceCalculator.distanceBetweenLocationsBySysID(
* '25ab9c4d0a0a0bb300f7dabdc0ca7c1c',
* '25ab8f300a0a0bb300d99f69c3ac24cd',
* null,
* 1 //Overrides the rounding specified in the constructor
* );
*/
distanceBetweenLocationsBySysID : function(originLocationID, destinationLocationID, distanceUnits, roundDigits) {
distanceUnits = distanceUnits ? distanceUnits : this.distanceUnits;
roundDigits = roundDigits ? roundDigits : this.roundDigits;
var distanceNum = 0;
var grLocation = new GlideRecord('cmn_location');
var locationData = {
'origin' : {
'name' : '',
'latitude' : 0,
'longitude' : 0
},
'destination' : {
'name' : '',
'latitude' : 0,
'longitude' : 0
},
'distance' : {
'value' : 0,
'units' : ''
},
'has_errors' : false,
'errors' : []
};
grLocation.addQuery('sys_id', 'IN', [originLocationID, destinationLocationID]);
grLocation.setLimit(2);
grLocation.query();
while (grLocation.next()) {
if (grLocation.getUniqueValue() === originLocationID) {
locationData.origin.name = grLocation.getValue('name');
locationData.origin.latitude = Number(grLocation.getValue('latitude'));
locationData.origin.longitude = Number(grLocation.getValue('longitude'));
} else if (grLocation.getUniqueValue() === destinationLocationID) {
locationData.destination.name = grLocation.getValue('name');
locationData.destination.latitude = Number(grLocation.getValue('latitude'));
locationData.destination.longitude = Number(grLocation.getValue('longitude'));
} else {
// I have literally no idea how we got here...
throw new Error(
'DistanceCalculator: Unable to find the specified location records with ' +
'sys_ids: "' + originLocationID + '" and "' + destinationLocationID + '".\n' +
'Or, actually, maybe we found location records... but they were not the ' +
'ones we queried by sys_id... this should not be possible, I am so confused.'
);
}
}
if (
!locationData.origin.latitude ||
!locationData.origin.longitude ||
!locationData.destination.latitude ||
!locationData.destination.longitude
) {
locationData.has_errors = true;
locationData.errors.push('One or both of the Location records are missing latitude and/or longitude values.');
return locationData;
}
distanceNum = this.calculateDistanceBetweenCoords(
locationData.origin.latitude,
locationData.origin.longitude,
locationData.destination.latitude,
locationData.destination.longitude,
distanceUnits,
roundDigits
);
locationData.distance.value = distanceNum;
locationData.distance.units = distanceUnits;
return locationData;
},
/**
* Calculates the distance between two GPS coordinates using the Haversine formula.
*
* Limitations include the fact that nature is inconvenient, that the Earth is not a perfect
* sphere but an oblate spheroid which is a very fun thing to type, that elevation changes
* are not accounted for in this calculation, and the formula is not designed to handle
* points that are more than half the circumference of the Earth apart (how would that
* even be possible?)
*
* Result accuracy may be impacted by the limitations of floating point arithmetic in both Java
* and JavaScript, by the limitations of the Haversine formula itself, by severe
* differences in elevation between the two points for which the distance is being
* calculated, and by the shape of the Earth itself.
*
* Stupid nature, always ruining math for everyone.
*
* @param {number} lat1 - Latitude of the first coordinate.
*
* @param {number} lon1 - Longitude of the first coordinate.
*
* @param {number} lat2 - Latitude of the second coordinate.
*
* @param {number} lon2 - Longitude of the second coordinate.
*
* @param {('km'|'miles'|'mile'|'mi'|'yards'|'yd'|'inches'|'in'|'feet'|'ft'|'meters'|'m'|'centimeters'|'cm'|'millimeters'|'mm'|'nautical miles'|'nm')} [distanceUnits='km']
* - The units of the distance. Default is 'km'.
*
* @param {number} [roundDigits=] - Number of decimal places to round the result to.
* The default if this argument is not specified is to not round the result at all.
* The maximum value for roundDigits is 15 (the maximum number of decimal places that
* JavaScript can accurately represent), but it is recommended to keep it below 10 to
* avoid floating point errors caused by the limitations of the weird Mozilla Rhino
* Java-based JavaScript engine.
*
* @returns {number} - The distance between the two coordinates in the specified units.
*
* @throws {Error} - If latitude and longitude values are not numbers or if invalid distance
* units are provided.
*
* @example
* var distanceCalc = new DistanceCalculator();
*
* var lat1 = 45.58568758810709, lon1 = -122.47954663934604;
* var lat2 = 45.605901771569044, lon2 = -122.56087319825683;
*
* // Returns 6.715
* var distanceKM = distanceCalc.calculateDistanceBetweenCoords(lat1, lon1, lat2, lon2, 'km', 3);
*
* //Returns 4.172
* var distanceMI = distanceCalc.calculateDistanceBetweenCoords(lat1, lon1, lat2, lon2, 'mi', 3);
*
* // Prints "Distance: 6.715 km (4.172 mi)"
* gs.debug('Distance: ' + distanceKM + ' km (' + distanceMI + ' mi)');
*/
calculateDistanceBetweenCoords : function(
lat1,
lon1,
lat2,
lon2,
distanceUnits,
roundDigits
) {
distanceUnits = distanceUnits ? distanceUnits : this.distanceUnits;
roundDigits = roundDigits ? roundDigits : this.roundDigits;
var distance, roundMultiplier, latDistanceDegrees, lonDistanceDegrees, sqHalfChordLength,
angularDistanceRads;
var earthRadiusKM = 6371; // Radius of the Earth in kilometers
if (typeof lat1 !== 'number' || typeof lon1 !== 'number' || typeof lat2 !== 'number' || typeof lon2 !== 'number') {
throw new Error('Latitude and longitude values must be numbers.');
}
if (typeof distanceUnits !== 'undefined' && typeof distanceUnits !== 'string') {
throw new Error(
'Distance units must be a string containing one of the enumerated values: "km", ' +
'"miles", "yards", "inches", "feet", "meters", "centimeters", "millimeters", ' +
'or "nautical miles".'
);
}
if (
roundDigits && roundDigits !== 0 &&
(typeof roundDigits !== 'number' || roundDigits < 0 || roundDigits > 15)
) {
throw new Error('roundDigits must be a number between 0 and 15, if specified.');
}
latDistanceDegrees = degreesToRadians(lat2 - lat1);
lonDistanceDegrees = degreesToRadians(lon2 - lon1);
/*
~Meet the haversine formula~
I wish that I'd known about it around nine hundred hours ago when I started working on this
calculation instead of sleeping.
I mean, I could've literally just googled "how to do [the thing I'm doing]" and found *some*
version of this formula in 5 seconds, probably.
But, no. Turns out, I'm a complete dingus, and I ended up completely reinventing it from
scratch. It was only when I was troubleshooting my code to figure out why it kept barfing
when one of the points was in the southern hemisphere that I finally stumbled upon the
haversine formula which is specifically designed to handle this exact situation, and I
was like, "Oh, that's what I'm trying to do... but better."
Turns out that it also solves the exact problem I was having, and is way more elegant than
my solution was. ಠ_ಠ
But hey, at least I learned something new - and now you get to as well.
Unless you didn't read this comment.
But you wouldn't just copy and paste code without reading the comments, would you?
That would be a terrible idea.
You could be copying and pasting anything.
Like, this code could call a function that sends your social security number to North Korea.
sendToNorthKorea(yourSocialSecurityNumber);
See? You wouldn't want that.
Anyway, here's the haversine formula.
It does not send your social security number to North Korea.
It calculates the distance between two points on the surface of a sphere.
Why a sphere?
Turns out, the Earth is a sphere; kinda.
Who knew?
Yeah, I know, elevation is a thing; but for short distances, elevation changes are mostly
negligible; and for long distances, the average elevation changes come close to either
cancelling out or being negligible at least as the emu flies.
We're not calculating driving distance here, just straight-line distance (though not in a
Euclidian sense, because the Earth is a sphere(oid) - have I mentioned that?)
The formula is based on the law of haversines, which relates the sides and angles of
triangles on the surface of a sphere.
The formula is: a = sin²(Δlat/2) + cos(lat1) * cos(lat2) * sin²(Δlon/2)
c = 2 * atan2( √a, √(1−a) )
d = R * c
Where:
Δlat = lat2 - lat1
Δlon = lon2 - lon1
a is the square of half the chord length between the points
c is the angular distance in radians
d is the distance between the two points
R is the earth's radius
The atan2 function returns a value in the range of -π to π radians and is used to calculate
the angle between the x-axis and a specific point.
Anyway, NOW you can safely copy and paste this code without understanding it.
I sure as hell don't.
*/
sqHalfChordLength = Math.sin(latDistanceDegrees / 2) * Math.sin(latDistanceDegrees / 2) +
Math.cos(degreesToRadians(lat1)) * Math.cos(degreesToRadians(lat2)) *
Math.sin(lonDistanceDegrees / 2) * Math.sin(lonDistanceDegrees / 2);
angularDistanceRads = 2 * Math.atan2(Math.sqrt(sqHalfChordLength), Math.sqrt(1 - sqHalfChordLength));
distance = earthRadiusKM * angularDistanceRads;
// Default to kilometers;
distanceUnits = (typeof distanceUnits === 'string') ? distanceUnits.toLowerCase() : 'km';
// Convert the distance to the specified units
if (distanceUnits === 'mi' || distanceUnits === 'miles' || distanceUnits === 'mile') {
distance = convertKMToMiles(distance);
} else if (distanceUnits === 'yd' || distanceUnits === 'yards') {
// Convert to miles, then to feet, then to yards, because miles are neatly divisible
// into feet, and feet are neatly divisible into yards, but kilometers are not.
distance = convertKMToMiles(distance) * 1760;
} else if (distanceUnits === 'in' || distanceUnits === 'inches') {
// Convert to miles, then to feet, then to inches, because miles are neatly divisible
// into feet, and feet are neatly divisible into inches, but kilometers are not.
distance = convertKMToMiles(distance) * 5280 * 12;
} else if (distanceUnits === 'ft' || distanceUnits === 'feet') {
// Convert to miles, then to feet, because miles are neatly divisible into feet, but
// kilometers are not.
distance = convertKMToMiles(distance) * 5280;
} else if (distanceUnits === 'm' || distanceUnits === 'meters') {
distance *= 1000;
} else if (distanceUnits === 'cm' || distanceUnits === 'centimeters') {
distance *= 100000;
} else if (distanceUnits === 'mm' || distanceUnits === 'millimeters') {
distance *= 1000000;
} else if (distanceUnits === 'nm' || distanceUnits === 'nautical miles') {
distance *= 0.539957;
} else if (distanceUnits !== 'km' && distanceUnits !== 'kilometers') {
// Invalid distance units - throw an error.
throw new Error(
'Invalid distance units. Please specify one of the following: enumerated values for ' +
'the distanceUnits parameter: \n' +
'"km", "miles", "yards", "inches", "feet", "meters", "centimeters", "millimeters", ' +
'or "nautical miles".'
);
}
if (roundDigits) {
//round distance to specified number of decimal places, without adding additional
// unnecessary leading or trailing zeros
roundMultiplier = Math.pow(10, roundDigits);
distance = Math.round(distance * roundMultiplier) / roundMultiplier;
}
/*
Fun fact, SN's rounding is different from the standard rounding in JavaScript.
It's probably a super lame Java-related issue, but check this out:
//Example code:
function doRoundingOkay(numToRound, roundDigits) {
var roundMultiplier = Math.pow(10, roundDigits);
var roundedResult = Math.round(numToRound * roundMultiplier) / roundMultiplier;
return roundedResult;
}
var numToRound = 1.234567;
gs.debug('3 decimal places: ' + doRoundingOkay(numToRound, 3)); //1.235 - Cool
gs.debug('10 decimal places: ' + doRoundingOkay(numToRound, 10)); //1.234567 - Good, since the original number had <10 digits of precision.
gs.debug('18 decimal places: ' + doRoundingOkay(numToRound, 18)); //1.234567 - Okay, rad
gs.debug('19 decimal places: ' + doRoundingOkay(numToRound, 19)); //0.9223372036854776 - wait wtf...
gs.debug('35 decimal places: ' + doRoundingOkay(numToRound, 22)); //0.0009223372036854776 - ???
gs.debug('35 decimal places: ' + doRoundingOkay(numToRound, 35)); //9.223372036854776e-17 - WHAT? HOWWW??
//What if we change the number to round?
gs.debug('19 decimal places, new number: ' + doRoundingOkay(654321.789, 19)); //0.9223372036854776 - BRO WTF HOW
//What if we just round an integer - a straight-up, normal-ass integer that shouldn't need rounding at all.
gs.debug('Integer rounded to 19 decimal places: ' + doRoundingOkay(123, 19)); //0.9223372036854776 - I AM CONFUSION!
*/
return distance;
function degreesToRadians(degrees) {
return degrees * (Math.PI / 180);
}
function convertKMToMiles(km) {
return km / 1.60934;
}
},
/**
* Trigger an example / test call of the methods of this Script Include.
*
* @param {string} [originSysID="25ab9c4d0a0a0bb300f7dabdc0ca7c1c"] - Specify the sys_id
* of a location record to use. If not specified, a default OOB record will be used.
* If the OOB record with the sys_id in the default value does not exist, you might need
* to specify one here.
*
* @param {string} [destinationSysID="25ab8f300a0a0bb300d99f69c3ac24cd"] - Specify the sys_id
* of a location record to use. If not specified, a default OOB record will be used.
* If the OOB record with the sys_id in the default value does not exist, you might need
* to specify one here.
*
* @example
* var distanceCalculator = new DistanceCalculator();
* distanceCalculator._exampleUsage();
*
* @example
* // Example with custom sys_ids specified
* //...
* distanceCalculator._exampleUsage(
* originSysID,
* destinationSysID
* );
*
* @private
*/
_exampleUsage: function(originSysID, destinationSysID) {
var distanceCalculator = new DistanceCalculator('mi', 3);
/*** CALCULATING DISTANCE USING SPECIFIED LAT/LON ***/
var lat1 = 45.58568758810709, lon1 = -122.47954663934604;
var lat2 = 45.605901771569044, lon2 = -122.56087319825683;
var distanceKM = distanceCalculator.calculateDistanceBetweenCoords(
lat1,
lon1,
lat2,
lon2,
'km', //Overrides the distance units specified in the constructor
); //6.714963986312145
var distanceMI = distanceCalculator.calculateDistanceBetweenCoords(
lat1,
lon1,
lat2,
lon2,
null, //Uses the distance units specified in the constructor; same as if not specified.
5 //Overrides the rounding specified in the constructor
); //4.17249
// = 4.172
gs.debug('Distance: ' + distanceKM + ' km (' + distanceMI + ' mi)');
/*** CALCULATING DISTANCE USING LOCATION RECORDS ***/
var distanceData;
var grOriginLocation = new GlideRecord('cmn_location');
var grDestinationLocation = new GlideRecord('cmn_location');
grOriginLocation.get('sys_id', (originSysID || '25ab9c4d0a0a0bb300f7dabdc0ca7c1c'));
grDestinationLocation.get('sys_id', (destinationSysID || '25ab8f300a0a0bb300d99f69c3ac24cd'));
distanceData = distanceCalculator.distanceBetweenLocationsBySysID(
grOriginLocation.getUniqueValue(),
grDestinationLocation.getUniqueValue(),
null,
1
);
gs.debug(
'The distance between "' + grOriginLocation.getDisplayValue() + '" and "' +
grDestinationLocation.getDisplayValue() + '" is ' + distanceData.distance.value +
' miles.'
);
},
type: 'DistanceCalculator'
};
Share