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
Enter fullscreen mode Exit fullscreen mode
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)
Enter fullscreen mode Exit fullscreen mode
we could use:
distance = √((x2 - x1)² + (y2 - y1)²)
Enter fullscreen mode Exit fullscreen mode
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
);
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
We’ll break the implementation into small pieces.
Convert Degrees to Radians
JavaScript’s trigonometric functions such as:
Math.sin()
Math.cos()
Math.atan2()
Enter fullscreen mode Exit fullscreen mode
expect radians rather than degrees.
So we first need a helper:
function toRadians(degrees) {
return degrees * Math.PI / 180;
}
Enter fullscreen mode Exit fullscreen mode
For example:
console.log(toRadians(180));
Enter fullscreen mode Exit fullscreen mode
returns approximately:
3.141592653589793
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
Usage:
const distance = distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437
);
console.log(distance);
Enter fullscreen mode Exit fullscreen mode
The result is approximately:
3935 km
Enter fullscreen mode Exit fullscreen mode
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);
Enter fullscreen mode Exit fullscreen mode
Then convert both latitude values to radians:
const latitude1 = toRadians(lat1);
const latitude2 = toRadians(lat2);
Enter fullscreen mode Exit fullscreen mode
Next:
const a =
Math.sin(dLat / 2) ** 2 +
Math.cos(latitude1) *
Math.cos(latitude2) *
Math.sin(dLon / 2) ** 2;
Enter fullscreen mode Exit fullscreen mode
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)
);
Enter fullscreen mode Exit fullscreen mode
produces the central angle between the locations.
Finally:
earthRadiusKm * c
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Longitude must be between:
-180 and 180
Enter fullscreen mode Exit fullscreen mode
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
);
}
Enter fullscreen mode Exit fullscreen mode
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"
);
}
}
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
Now invalid coordinates are rejected immediately.
For example:
distanceBetweenCoordinates(
120,
0,
40,
20
);
Enter fullscreen mode Exit fullscreen mode
throws:
RangeError: Latitude must be between -90 and 90
Enter fullscreen mode Exit fullscreen mode
Support Kilometers, Miles, and Nautical Miles
A mapping tool may need more than kilometers.
Common units include:
Kilometers
Miles
Nautical miles
Enter fullscreen mode Exit fullscreen mode
We can define Earth-radius values for each unit:
const EARTH_RADIUS = {
km: 6371.0088,
miles: 3958.7613,
nauticalMiles: 3440.0695
};
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
Usage:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"km"
)
);
Enter fullscreen mode Exit fullscreen mode
Miles:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"miles"
)
);
Enter fullscreen mode Exit fullscreen mode
Nautical miles:
console.log(
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437,
"nauticalMiles"
)
);
Enter fullscreen mode Exit fullscreen mode
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
};
}
Enter fullscreen mode Exit fullscreen mode
Usage:
console.log(
getDistance(
40.7128,
-74.0060,
34.0522,
-118.2437
)
);
Enter fullscreen mode Exit fullscreen mode
Example output:
{
kilometers: 3935.75,
miles: 2445.56,
nauticalMiles: 2125.13
}
Enter fullscreen mode Exit fullscreen mode
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)
);
Enter fullscreen mode Exit fullscreen mode
if the value will later be reused in other calculations.
Instead, return the full value:
return distance;
Enter fullscreen mode Exit fullscreen mode
and format it only when displaying it:
const distance =
distanceBetweenCoordinates(
40.7128,
-74.0060,
34.0522,
-118.2437
);
console.log(
`${distance.toFixed(2)} km`
);
Enter fullscreen mode Exit fullscreen mode
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
);
Enter fullscreen mode Exit fullscreen mode
The Haversine formula naturally returns:
0
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
Paris:
48.8566, 2.3522
Enter fullscreen mode Exit fullscreen mode
JavaScript:
const londonToParis =
distanceBetweenCoordinates(
51.5074,
-0.1278,
48.8566,
2.3522
);
console.log(
`${londonToParis.toFixed(2)} km`
);
Enter fullscreen mode Exit fullscreen mode
The result is roughly:
344 km
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
and:
driving distance
Enter fullscreen mode Exit fullscreen mode
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";
Enter fullscreen mode Exit fullscreen mode
Define the radius map:
const EARTH_RADIUS: Record<
DistanceUnit,
number
> = {
km: 6371.0088,
miles: 3958.7613,
nauticalMiles: 3440.0695
};
Enter fullscreen mode Exit fullscreen mode
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;
}
Enter fullscreen mode Exit fullscreen mode
Now this is valid:
distanceBetweenCoordinates(
40.7128,
-74.006,
34.0522,
-118.2437,
"miles"
);
Enter fullscreen mode Exit fullscreen mode
while this will be caught by TypeScript:
distanceBetweenCoordinates(
40.7128,
-74.006,
34.0522,
-118.2437,
"meters"
);
Enter fullscreen mode Exit fullscreen mode
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);
}
);
}
);
Enter fullscreen mode Exit fullscreen mode
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>
Enter fullscreen mode Exit fullscreen mode
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;
}
}
);
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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;
}
});
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
or:
New York
40.7128, -74.0060
Los Angeles
34.0522, -118.2437
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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`
);
Enter fullscreen mode Exit fullscreen mode
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
Enter fullscreen mode Exit fullscreen mode
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.
답글 남기기
댓글을 달기 위해서는 로그인해야합니다.