When building location-based applications, one of the most common requirements is calculating the distance between two points on Earth. While it might seem straightforward at first, accurately calculating distances on a spherical surface requires some careful consideration. In this article, I'll share how I implemented this calculation for an educational geography game, Flagle Explorer.
The primary requirements for our distance calculation were:
- Accuracy suitable for educational purposes
- Performance efficient enough for real-time calculations
- Easy to understand and maintain
- Typescript implementation for type safety
The Haversine formula is the go-to solution for calculating great-circle distances between two points on a sphere. It's particularly well-suited for geographic calculations on Earth, providing a good balance between accuracy and computational complexity.
The formula determines the shortest distance between two points on a sphere using their latitudes and longitudes. While it assumes Earth is perfectly spherical (which it isn't), the results are accurate enough for most applications, with errors typically less than 0.5%.
Here's my TypeScript implementation using the Haversine formula:
interface GeoPoint {
lat: number; // Latitude in degrees
lon: number; // Longitude in degrees
}
/**
* Calculates the distance between two geographic points in kilometers
* using the Haversine formula
*
* @param point1 First geographic point with latitude and longitude
* @param point2 Second geographic point with latitude and longitude
* @returns Distance in kilometers
*/
function calculateDistance(point1: GeoPoint, point2: GeoPoint): number {
// Early return if points are identical
if (point1.lat === point2.lat && point1.lon === point2.lon) {
return 0;
}
// Earth's radius in meters
const R = 6371000;
// Convert latitude and longitude differences to radians
const dLat = (point2.lat - point1.lat) * Math.PI / 180;
const dLon = (point2.lon - point1.lon) * Math.PI / 180;
// Convert latitudes to radians for formula
const lat1Rad = point1.lat * Math.PI / 180;
const lat2Rad = point2.lat * Math.PI / 180;
// Haversine formula
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1Rad) * Math.cos(lat2Rad) *
Math.sin(dLon / 2) * Math.sin(dLon / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
// Convert result to kilometers
return (R * c) / 1000;
}
/**
* Calculate the planar map direction angle of point B relative to point A (simplified rectangular coordinate system calculation)
* - Suitable for small areas or flat maps ignoring Earth's curvature
* - Returns angle range 0-360 degrees, where 0 is North, 90 is East, 180 is South, and 270 is West
* @param pointA Reference point coordinates
* @param pointB Target point coordinates
*/
function calculatePlanarOrientation(pointA: GeoPoint, pointB: GeoPoint): number {
if (pointA.lat === pointB.lat && pointA.lon === pointB.lon) return 0;
// Calculate longitude difference (X-axis) and latitude difference (Y-axis)
const dLon = pointB.lon - pointA.lon;
const dLat = pointB.lat - pointA.lat;
// Calculate angle using planar rectangular coordinate system (North as 0 degrees)
let angle = Math.atan2(dLon, dLat) * 180 / Math.PI;
// Convert angle to 0-360 range
if (angle < 0) angle += 360;
return Number(angle.toFixed(2));
}
-
Type Safety: We define a
GeoPoint
interface to ensure type safety when handling geographic coordinates. -
Input Validation: The function includes an early return for identical points, optimizing performance for this common case.
-
Constants: We use Earth's radius in meters (6,371,000m) as our base constant. While Earth isn't perfectly spherical, this average radius works well for most applications.
-
Unit Conversion:
- Input coordinates are in degrees but need to be converted to radians for trigonometric calculations
- The final result is converted from meters to kilometers for more practical use
The implementation makes several optimizations:
- Early return for identical points
- Minimal variable creation
- Direct mathematical operations without unnecessary abstractions
- Single pass calculation without loops
This distance calculation function has numerous practical applications:
-
Educational Tools
- Geography learning games
- Interactive map exercises
- Distance estimation challenges
-
Travel Applications
- Flight distance calculations
- Route planning
- Fuel consumption estimates
-
Location-Based Services
- Nearby point-of-interest searches
- Delivery radius calculations
- Location-based game mechanics
-
Weather Applications
- Storm distance tracking
- Weather radar range calculations
- Climate zone mapping
Here's a practical example of how to use the function:
// Example coordinates (New York City and London)
const newYork: GeoPoint = { lat: 40.7128, lon: -74.0060 };
const london: GeoPoint = { lat: 51.5074, lon: -0.1278 };
// Calculate distance
const distance = calculateDistance(newYork, london);
// or
// const distance = calculatePlanarOrientation(newYork, london);
console.log(`Distance between New York and London: ${distance.toFixed(2)} km`);
While this implementation is suitable for most applications, it's important to note its limitations:
- Assumes a spherical Earth (doesn't account for ellipsoid shape)
- Accuracy decreases slightly for very small distances
- Doesn't account for elevation differences
- May not be suitable for highly precise scientific calculations
The Haversine formula provides a reliable way to calculate geographic distances in TypeScript. This implementation offers a good balance of accuracy, performance, and code maintainability. Whether you're building an educational tool, a travel application, or a location-based service, this function provides a solid foundation for geographic distance calculations.
Feel free to use and modify this code for your projects. If you need more precise calculations, consider using the Vincenty formula or specialized geographic libraries.
GitHub: horushe93
Project: