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

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.

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 {
				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);
		
		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;
		
		distanceUnits = (typeof distanceUnits === 'string') ? distanceUnits.toLowerCase() : 'km';
		
		if (distanceUnits === 'mi' || distanceUnits === 'miles' || distanceUnits === 'mile') {
			distance = convertKMToMiles(distance);
		} else if (distanceUnits === 'yd' || distanceUnits === 'yards') {
			distance = convertKMToMiles(distance) * 1760;
		} else if (distanceUnits === 'in' || distanceUnits === 'inches') {
			distance = convertKMToMiles(distance) * 5280 * 12;
		} else if (distanceUnits === 'ft' || distanceUnits === 'feet') {
			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') {
			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) {
			roundMultiplier = Math.pow(10, roundDigits);
			distance = Math.round(distance * roundMultiplier) / roundMultiplier;
		}
		
		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);
		
		var lat1 = 45.58568758810709, lon1 = -122.47954663934604;
		var lat2 = 45.605901771569044, lon2 = -122.56087319825683;
		
		var distanceKM = distanceCalculator.calculateDistanceBetweenCoords(
			lat1,
			lon1,
			lat2,
			lon2,
			'km',
		);
		
		var distanceMI = distanceCalculator.calculateDistanceBetweenCoords(
			lat1,
			lon1,
			lat2,
			lon2,
			null,
			5
		);
		
		gs.debug('Distance: ' + distanceKM + ' km (' + distanceMI + ' mi)');
		
		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'
};

Search