When working with maps, GPS data, or location-based applications, one of the most common questions is:
How far apart are these two coordinates?
Suppose we have two locations:
Point A
Latitude: 40.7128
Longitude: -74.0060
Point B
Latitude: 34.0522
Longitude: -118.2437
These are approximately New York City and Los Angeles.
We can't simply subtract the latitude and longitude values and treat them like normal Cartesian coordinates.
The Earth is curved.
For many web applications, a practical solution is the Haversine formula, which calculates the great-circle distance between two points on a sphere.
In this article, we'll build a reusable JavaScript implementation that supports:
- Latitude and longitude validation
- Degrees-to-radians conversion
- The Haversine formula
- Kilometers
- Miles
- Nautical miles
- TypeScript
- Unit tests
- Real-world coordinate testing
Why Simple Euclidean Distance Doesn't Work
If we had two points on a flat coordinate plane:
(x1, y1)
(x2, y2)
we could use:
distance = √((x2 - x1)² + (y2 - y1)²)
Latitude and longitude don't work like that.
They represent positions on the Earth's surface.
A degree of longitude also does not represent the same physical distance everywhere.
Near the Equator, one degree of longitude covers a much larger distance than it does near the poles.
So this:
const distance = Math.sqrt(
(lat2 - lat1) ** 2 +
(lon2 - lon1) ** 2
);
does not give us a meaningful distance in kilometers or miles.
We need to account for the Earth's curvature.
The Haversine Formula
The Haversine formula estimates the great-circle distance between two points using their latitude and longitude.
The main idea is:
Latitude / Longitude
↓
Convert degrees to radians
↓
Calculate angular distance
↓
Multiply by Earth's radius
↓
Physical distance
We'll break the implementation into small pieces.
Convert Degrees to Radians
JavaScript's trigonometric functions such as:
Math.sin()
Math.cos()
Math.atan2()
expect radians rather than degrees.
So we first need a helper:
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
For example:
console.log(toRadians(180));
returns approximately:
3.141592653589793
which is π radians.
A Basic Distance Function
Let's start with a simple Haversine implementation:
function distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2
) {
const earthRadiusKm = 6371.0088;
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const latitude1 = toRadians(lat1);
const latitude2 = toRadians(lat2);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return earthRadiusKm * c;
}
Usage:
const distance = distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437
);
console.log(distance);
The result is approximately:
3935 km
depending on the Earth radius and rounding you use.
What Is Happening Inside the Formula?
Let's break the calculation down.
First, calculate the differences:
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
Then convert both latitude values to radians:
const latitude1 = toRadians(lat1);
const latitude2 = toRadians(lat2);
Next:
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
This gives us an intermediate value representing the angular relationship between the two points.
Then:
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
produces the central angle between the locations.
Finally:
earthRadiusKm * c
converts that angular distance into kilometers.
Validate Latitude and Longitude
Before calculating distance, we should validate the inputs.
Latitude must be between:
-90 and 90
Longitude must be between:
-180 and 180
Let's create reusable validators:
function isValidLatitude(value) {
return (
Number.isFinite(value) &&
value >= -90 &&
value <= 90
);
}
function isValidLongitude(value) {
return (
Number.isFinite(value) &&
value >= -180 &&
value <= 180
);
}
Then:
function validateCoordinate(
latitude,
longitude
) {
if (!isValidLatitude(latitude)) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
if (!isValidLongitude(longitude)) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
}
Now our distance function can validate both points.
A More Reliable Implementation
Let's combine validation with the Haversine calculation:
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
function isValidLatitude(value) {
return (
Number.isFinite(value) &&
value >= -90 &&
value <= 90
);
}
function isValidLongitude(value) {
return (
Number.isFinite(value) &&
value >= -180 &&
value <= 180
);
}
function validateCoordinate(
latitude,
longitude
) {
if (!isValidLatitude(latitude)) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
if (!isValidLongitude(longitude)) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
}
function distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2
) {
validateCoordinate(lat1, lon1);
validateCoordinate(lat2, lon2);
const earthRadiusKm = 6371.0088;
const latitude1 = toRadians(lat1);
const latitude2 = toRadians(lat2);
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return earthRadiusKm * c;
}
Now invalid coordinates are rejected immediately.
For example:
distanceBetweenCoordinates(
120,
0,
40,
20
);
throws:
RangeError: Latitude must be between -90 and 90
Support Kilometers, Miles, and Nautical Miles
A mapping tool may need more than kilometers.
Common units include:
Kilometers
Miles
Nautical miles
We can define Earth-radius values for each unit:
const EARTH_RADIUS = {
km: 6371.0088,
miles: 3958.7613,
nauticalMiles: 3440.0695
};
Then update the function:
function distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2,
unit = "km"
) {
validateCoordinate(lat1, lon1);
validateCoordinate(lat2, lon2);
const radius =
EARTH_RADIUS[unit];
if (!radius) {
throw new Error(
"Unsupported distance unit"
);
}
const latitude1 = toRadians(lat1);
const latitude2 = toRadians(lat2);
const dLat = toRadians(lat2 - lat1);
const dLon = toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return radius * c;
}
Usage:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"km"
)
);
Miles:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"miles"
)
);
Nautical miles:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"nauticalMiles"
)
);
Return Structured Data
For a real application, returning just one number may be too limited.
Instead, we can calculate the distance once and return multiple units.
function getDistance(
lat1,
lon1,
lat2,
lon2
) {
const kilometers =
distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2,
"km"
);
return {
kilometers,
miles: kilometers * 0.621371,
nauticalMiles:
kilometers * 0.539957
};
}
Usage:
console.log(
getDistance(
40.7128,
-74.0060,
34.0522,
-118.2437
)
);
Example output:
{
kilometers: 3935.75,
miles: 2445.56,
nauticalMiles: 2125.13
}
You can then decide how many decimal places to show in the UI.
Keep Calculation Precision Separate from Display Precision
Avoid rounding too early.
For example, don't do this inside the calculation:
return Number(
distance.toFixed(2)
);
if the value will later be reused in other calculations.
Instead, return the full value:
return distance;
and format it only when displaying it:
const distance =
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437
);
console.log(
`${distance.toFixed(2)} km`
);
This keeps your calculation layer more accurate and reusable.
Handle Identical Coordinates
What happens if both points are exactly the same?
distanceBetweenCoordinates(
40.7128,
-74.006,
40.7128,
-74.006
);
The Haversine formula naturally returns:
0
So you usually don't need a special condition.
However, if your application wants to optimize this case:
if (
lat1 === lat2 &&
lon1 === lon2
) {
return 0;
}
This can avoid unnecessary trigonometric calculations.
For most applications, either approach is fine.
Example: London to Paris
Let's try a shorter real-world distance.
London:
51.5074, -0.1278
Paris:
48.8566, 2.3522
JavaScript:
const londonToParis =
distanceBetweenCoordinates(
51.5074,
-0.1278,
48.8566,
2.3522
);
console.log(
`${londonToParis.toFixed(2)} km`
);
The result is roughly:
344 km
This is the straight-line great-circle distance, not driving distance.
That's an important distinction.
Great-Circle Distance Is Not Driving Distance
The Haversine formula answers:
What is the shortest distance over the Earth's surface between two coordinates?
It does not consider:
- Roads
- Bridges
- Traffic
- Mountains
- Walking routes
- Rail networks
- Borders
- One-way streets
So:
GPS coordinate distance
and:
driving distance
are two different problems.
For driving distance, you need a routing engine or routing API.
The Haversine formula is better suited to:
- Straight-line map distance
- Nearby-location filtering
- GPS proximity checks
- Geographic analysis
- Approximate travel radius
- Location ranking
What About Earth's Shape?
The Haversine formula assumes the Earth is a sphere.
In reality, the Earth is closer to an oblate ellipsoid.
That means Haversine distance is an approximation.
For most:
- Web applications
- Map tools
- Location searches
- GPS utilities
- Nearby-place calculations
the approximation is usually sufficient.
Applications requiring survey-grade or high-precision geodesic calculations should use an ellipsoidal model such as WGS84 with a proper geodesic algorithm.
So choose the calculation method based on the accuracy requirements of your application.
A TypeScript Version
We can make the unit options explicit with TypeScript.
type DistanceUnit =
| "km"
| "miles"
| "nauticalMiles";
Define the radius map:
const EARTH_RADIUS: Record<
DistanceUnit,
number
> = {
km: 6371.0088,
miles: 3958.7613,
nauticalMiles: 3440.0695
};
Then:
function distanceBetweenCoordinates(
lat1: number,
lon1: number,
lat2: number,
lon2: number,
unit: DistanceUnit = "km"
): number {
validateCoordinate(lat1, lon1);
validateCoordinate(lat2, lon2);
const radius =
EARTH_RADIUS[unit];
const latitude1 =
toRadians(lat1);
const latitude2 =
toRadians(lat2);
const dLat =
toRadians(lat2 - lat1);
const dLon =
toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return radius * c;
}
Now this is valid:
distanceBetweenCoordinates(
40.7128,
-74.006,
34.0522,
-118.2437,
"miles"
);
while this will be caught by TypeScript:
distanceBetweenCoordinates(
40.7128,
-74.006,
34.0522,
-118.2437,
"meters"
);
unless you explicitly support that unit.
Testing With Vitest
Geographic calculation functions are good candidates for unit tests.
Example:
import {
describe,
expect,
it
} from "vitest";
describe(
"distanceBetweenCoordinates",
() => {
it(
"returns zero for identical coordinates",
() => {
const distance =
distanceBetweenCoordinates(
40.7128,
-74.006,
40.7128,
-74.006
);
expect(distance).toBe(0);
}
);
it(
"calculates London to Paris",
() => {
const distance =
distanceBetweenCoordinates(
51.5074,
-0.1278,
48.8566,
2.3522
);
expect(distance)
.toBeGreaterThan(340);
expect(distance)
.toBeLessThan(350);
}
);
it(
"rejects invalid latitude",
() => {
expect(() =>
distanceBetweenCoordinates(
100,
0,
40,
20
)
).toThrow(RangeError);
}
);
}
);
For geographic calculations, testing a reasonable range is often safer than checking a value to many decimal places.
Different Earth-radius assumptions can produce slightly different results.
Build a Simple Distance Calculator UI
A basic HTML interface might contain:
<input
id="lat1"
type="number"
step="any"
placeholder="Latitude 1"
/>
<input
id="lon1"
type="number"
step="any"
placeholder="Longitude 1"
/>
<input
id="lat2"
type="number"
step="any"
placeholder="Latitude 2"
/>
<input
id="lon2"
type="number"
step="any"
placeholder="Longitude 2"
/>
<button id="calculate">
Calculate Distance
</button>
<p id="result"></p>
Then:
document
.querySelector("#calculate")
.addEventListener(
"click",
() => {
const lat1 = Number(
document
.querySelector("#lat1")
.value
);
const lon1 = Number(
document
.querySelector("#lon1")
.value
);
const lat2 = Number(
document
.querySelector("#lat2")
.value
);
const lon2 = Number(
document
.querySelector("#lon2")
.value
);
try {
const distance =
distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2
);
document
.querySelector("#result")
.textContent =
`${distance.toFixed(2)} km`;
} catch (error) {
document
.querySelector("#result")
.textContent =
error.message;
}
}
);
This is enough to build a simple browser-based GPS distance calculator.
Using Map Clicks Instead of Manual Coordinate Entry
For map applications, users may prefer selecting points visually.
The flow becomes:
Click Point A
↓
Store coordinates
Click Point B
↓
Store coordinates
Calculate Haversine distance
↓
Display result
For example:
const points = [];
map.on("click", event => {
points.push({
latitude: event.lngLat.lat,
longitude: event.lngLat.lng
});
if (points.length === 2) {
const [a, b] = points;
const distance =
distanceBetweenCoordinates(
a.latitude,
a.longitude,
b.latitude,
b.longitude
);
console.log(
`${distance.toFixed(2)} km`
);
points.length = 0;
}
});
This interaction works especially well for map-based measurement tools.
Test With Real Coordinates
When working on distance calculations, it's useful to test the function with real places rather than only artificial coordinate values.
I've been building CoordMap's Measure Distance tool, which lets you work with real geographic points and measure distances between locations.
You can use it to pick two locations and compare the result with your JavaScript implementation.
For example:
London
51.5074, -0.1278
Paris
48.8566, 2.3522
or:
New York
40.7128, -74.0060
Los Angeles
34.0522, -118.2437
Testing different regions is useful because it helps catch common mistakes such as:
latitude / longitude swapped
degrees passed directly into Math.sin()
incorrect Earth radius
invalid coordinate ranges
Complete Copy-Paste Version
Here's the full implementation:
const EARTH_RADIUS = {
km: 6371.0088,
miles: 3958.7613,
nauticalMiles: 3440.0695
};
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
function isValidLatitude(value) {
return (
Number.isFinite(value) &&
value >= -90 &&
value <= 90
);
}
function isValidLongitude(value) {
return (
Number.isFinite(value) &&
value >= -180 &&
value <= 180
);
}
function validateCoordinate(
latitude,
longitude
) {
if (!isValidLatitude(latitude)) {
throw new RangeError(
"Latitude must be between -90 and 90"
);
}
if (!isValidLongitude(longitude)) {
throw new RangeError(
"Longitude must be between -180 and 180"
);
}
}
function distanceBetweenCoordinates(
lat1,
lon1,
lat2,
lon2,
unit = "km"
) {
validateCoordinate(lat1, lon1);
validateCoordinate(lat2, lon2);
const radius =
EARTH_RADIUS[unit];
if (!radius) {
throw new Error(
"Unsupported distance unit"
);
}
if (
lat1 === lat2 &&
lon1 === lon2
) {
return 0;
}
const latitude1 =
toRadians(lat1);
const latitude2 =
toRadians(lat2);
const dLat =
toRadians(lat2 - lat1);
const dLon =
toRadians(lon2 - lon1);
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
const c =
2 * Math.atan2(
Math.sqrt(a),
Math.sqrt(1 - a)
);
return radius * c;
}
const distance =
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437
);
console.log(
`${distance.toFixed(2)} km`
);
Final Thoughts
Calculating the distance between two GPS coordinates is a small problem with a few important details.
The core process is:
Two GPS coordinates
↓
Validate latitude / longitude
↓
Convert degrees to radians
↓
Apply Haversine formula
↓
Calculate angular distance
↓
Multiply by Earth radius
↓
Kilometers / miles / nautical miles
The main things to remember are:
- Latitude must stay between -90 and 90.
- Longitude must stay between -180 and 180.
- JavaScript trigonometric functions use radians.
- The Haversine formula calculates great-circle distance.
- Great-circle distance is not driving distance.
- Avoid rounding until the presentation layer.
- Use a more precise geodesic model when your application requires survey-grade accuracy.
For most web mapping, GPS, and proximity applications, the Haversine formula remains a useful and straightforward solution.
Top comments (0)