Building log(nishant), a geospatial analytics dashboard centered on Bhopal, meant solving one problem before anything else could work: given a user's chosen point on the map and a location stored in the database, how far apart are they, in kilometers, accounting for the fact that the Earth is a sphere and not a flat grid?
Every other feature in the project sits on top of the answer to that one question. The radius filter checks against it. The traffic-weighted distance multiplies it. The sort order is built from it. Get this calculation wrong, and every feature downstream silently returns wrong results.
This is the Haversine function: what it computes, why the formula looks the way it does, and the edge cases that mattered once it was wired into a real API.
Why You Can't Just Subtract Coordinates
The tempting shortcut is to treat latitude and longitude like x and y coordinates on a flat plane and apply the Pythagorean theorem. This breaks for one structural reason: the Earth is a sphere, and longitude lines converge as you move away from the equator. One degree of longitude in Bhopal does not represent the same physical distance as one degree of longitude near the poles.
For short distances within a single city, the error is small enough to not matter much. But the moment you're computing distances across any meaningful range, the inaccuracy compounds. The Haversine formula exists specifically to compute distance correctly on a sphere.
What the Formula Actually Does
The Haversine formula calculates the great-circle distance: the shortest path between two points along the surface of a sphere, not a straight line through the Earth and not a road-network route.
The formula has three steps. First, find the angular difference between the two points' latitudes and longitudes, expressed in radians rather than degrees, because all trigonometric functions in Python's math module expect radians. Second, run that difference through the haversine formula itself to get c, the central angle between the two points as seen from the Earth's center. Third, multiply that angle by Earth's radius to convert it from an angle into an actual distance.
The Implementation
Here's the function exactly as it runs inside the Flask backend, using only Python's standard math module:
import math
def haversine_distance(lat1, lon1, lat2, lon2):
"""
Calculate the great-circle distance between two points
on Earth's surface, given their latitude and longitude.
Returns distance in kilometers.
"""
R = 6371 # Earth's mean radius in kilometers
# Convert degrees to radians — trig functions require radians, not degrees
lat1_rad = math.radians(lat1)
lon1_rad = math.radians(lon1)
lat2_rad = math.radians(lat2)
lon2_rad = math.radians(lon2)
# Differences between the two points
delta_lat = lat2_rad - lat1_rad
delta_lon = lon2_rad - lon1_rad
# Haversine formula itself
a = (math.sin(delta_lat / 2) ** 2 +
math.cos(lat1_rad) * math.cos(lat2_rad) *
math.sin(delta_lon / 2) ** 2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
# Multiply the angular distance by Earth's radius to get kilometers
return R * c
Inside the /api/filter endpoint, this function runs once per candidate location returned from the PostgreSQL query, comparing the user's center coordinates against each row:
physical_distance = haversine_distance(user_lat, user_lng, location.lat, location.lng)
# This single number gates every downstream feature
if physical_distance <= max_radius_km:
traffic_weighted_distance = physical_distance * location.traffic_multiplier
results.append({
"name": location.name,
"physical_distance_km": round(physical_distance, 2),
"traffic_weighted_distance_km": round(traffic_weighted_distance, 2)
})
# Sort by the raw physical distance so nearest results appear first
results.sort(key=lambda r: r["physical_distance_km"])
Edge Cases That Actually Mattered
Same-point queries. If the user's center point happens to exactly match a location's coordinates, delta_lat and delta_lon are both zero, a evaluates to zero, and the function correctly returns 0.0 km. No special-casing needed, but worth verifying explicitly rather than assuming.
Floating-point precision near a = 1. In rare cases involving near-antipodal points (on opposite sides of the Earth), floating-point rounding can push a slightly above 1, which makes math.sqrt(1 - a) attempt to take the square root of a negative number and crash. This project's radius queries are bounded to a single city, so this case never triggers in practice, but it's the reason production-grade geo libraries clamp a to the [0, 1] range before the square root step.
Degrees vs radians mismatches. The most common real bug with this formula isn't in the math, it's in forgetting to convert one of the four inputs to radians before passing it into math.sin or math.cos. The function converts all four inputs at the very top specifically to make this mistake impossible to make accidentally later in the function body.
Rounding before comparison, not after. The radius check compares the raw physical_distance before any rounding for display. Rounding to two decimal places only happens when building the response payload, never before the <= comparison against max_radius_km. Rounding earlier would occasionally include or exclude a location incorrectly at the boundary of the radius.
What You Now Understand
The Haversine formula is the standard answer to a specific geometric problem: measuring distance correctly on a curved surface using only latitude and longitude. In this project, that one function is the computational foundation everything else is layered on top of: the radius filter, the traffic-weighted metric, and the sort order all consume its output directly.
If you're building anything with location data, the temptation to skip straight to a geo library is reasonable for production systems. But implementing the formula by hand once, the way this project does, makes every geo library you use afterward make a lot more sense.



Top comments (0)