Measuring the distance between two airports sounds like a job for a ruler on a map — but a straight line on a flat map is the wrong answer, sometimes by hundreds of miles. Aircraft fly the shortest path over a sphere, the great-circle distance, which is why a New York–to–Tokyo flight arcs up over the Arctic instead of heading straight west. This post explains the great-circle formula, walks through calculating it yourself, and shows how to get airport-to-airport distance and bearing from an API when you'd rather not.

Why flat-map distance is wrong

A flat map (a Mercator projection) distorts distances badly, especially toward the poles. The actual shortest path between two points on Earth follows a great circle — the intersection of the sphere with a plane through its center. On a map that path looks curved, but it's genuinely shorter than the "straight" line you'd draw with a ruler. For any serious use — fuel planning, range rings, route previews, carbon estimates — you calculate great-circle distance, not map distance.

A San Francisco aeronautical sectional chart showing coastline, airspace, terrain, and a navigation grid

The haversine formula

The standard way to compute great-circle distance from two latitude/longitude pairs is the haversine formula. Given origin (φ₁, λ₁) and destination (φ₂, λ₂) in radians, and Earth's radius R:

a = sin²(Δφ / 2) + cos φ₁ · cos φ₂ · sin²(Δλ / 2)
c = 2 · atan2(√a, √(1 − a))
d = R · c

where Δφ = φ₂ − φ₁ and Δλ = λ₂ − λ₁. The radius R sets your unit: use 6,371 km, 3,440.1 nautical miles, or 3,958.8 statute miles. In aviation the nautical mile is the standard unit (1 NM ≈ 1.852 km), because it's tied to one minute of latitude.

In JavaScript, computing JFK → LHR looks like this:

const toRad = (deg) => (deg * Math.PI) / 180;

function haversineNm(lat1, lon1, lat2, lon2) {
  const R = 3440.1; // Earth radius in nautical miles
  const dLat = toRad(lat2 - lat1);
  const dLon = toRad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) ** 2;
  return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

// KJFK (40.6413, -73.7781) -> EGLL (51.4700, -0.4543)
haversineNm(40.6413, -73.7781, 51.47, -0.4543); // ≈ 2991 NM

That's the whole calculation. The two gotchas that bite people: work in radians, not degrees (every trig function above expects radians), and don't forget to convert the result to your desired unit by choosing R accordingly.

Distance is only half of it — you usually want bearing too

Knowing two airports are 2,991 NM apart doesn't tell you which way to point. The companion value is the initial bearing — the compass direction (degrees true) you'd depart on to follow the great circle. It's "initial" because on a great circle the bearing continuously changes along the route; you start on one heading and it drifts. Bearing has its own formula, and from it you can derive a friendly eight-point cardinal direction (N, NE, E, …) and the route's geographic midpoint for map labeling.

The wing of an airliner above a layer of cloud, seen from the cabin window

Getting distance and bearing by API

If you'd rather not maintain a haversine implementation (and keep airport coordinates in sync), SkyLink API's distance endpoint computes all of it from two airport codes — distance in three units, initial bearing, cardinal direction, and midpoint, in one call:

curl "https://skylink-api.p.rapidapi.com/distance?from_icao=KJFK&to_icao=EGLL" \
  -H "X-RapidAPI-Key: $RAPIDAPI_KEY" \
  -H "X-RapidAPI-Host: skylink-api.p.rapidapi.com"
{
  "from": "KJFK",
  "to": "EGLL",
  "distance_nm": 2991.61,
  "distance_km": 5539.91,
  "distance_mi": 3442.19,
  "bearing_deg": 51.35,
  "cardinal": "NE",
  "midpoint_lat": 52.216,
  "midpoint_lon": -41.306
}

A few things worth knowing:

  • All three distance units are always returned — distance_nm, distance_km, and distance_mi — regardless of the optional unit parameter, which is only a display hint. Pick whichever field you need in code.
  • Each side can be a code or raw coordinates, and you can mix them. Pass from_lat/from_lon for a point that isn't an airport (a waypoint, a user's GPS fix) and to_icao for the destination.
  • Validate coordinate bounds client-side (latitude −90…90, longitude −180…180) to avoid a 422 response.

Because it accepts either ICAO or IATA codes, you don't have to resolve the airport first — but if you also need runways or elevation, pair it with the airports endpoint. And when you want time rather than distance, the ML flight-time endpoint estimates gate-to-gate block time for the same city pair.

SkyLink API gives you a free tier of 1,000 requests/month covering distance, airports, and the rest of the stack, with paid plans starting at $18.59/mo for production traffic. It's available through the free trial — sign up, grab a key, and get great-circle distance and bearing for any two airports in one call.