Skip to content

Fix issue #11308 Haversine Distance Calculation to Use Raw Latitudes #11648

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
66 changes: 28 additions & 38 deletions geodesy/haversine_distance.py
Original file line number Diff line number Diff line change
@@ -1,54 +1,44 @@
from math import asin, atan, cos, radians, sin, sqrt, tan

AXIS_A = 6378137.0
AXIS_B = 6356752.314245
RADIUS = 6378137
from math import asin, cos, radians, sin, sqrt


def haversine_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""
Calculate great circle distance between two points in a sphere,
given longitudes and latitudes https://en.wikipedia.org/wiki/Haversine_formula

We know that the globe is "sort of" spherical, so a path between two points
isn't exactly a straight line. We need to account for the Earth's curvature
when calculating distance from point A to B. This effect is negligible for
small distances but adds up as distance increases. The Haversine method treats
the earth as a sphere which allows us to "project" the two points A and B
onto the surface of that sphere and approximate the spherical distance between
them. Since the Earth is not a perfect sphere, other methods which model the
Earth's ellipsoidal nature are more accurate but a quick and modifiable
computation like Haversine can be handy for shorter range distances.
Calculate the great-circle distance between two points
on the Earth specified by latitude and longitude using
the Haversine formula.

Args:
lat1, lon1: latitude and longitude of coordinate 1
lat2, lon2: latitude and longitude of coordinate 2
lat1, lon1: Latitude and longitude of point 1 in decimal degrees.
lat2, lon2: Latitude and longitude of point 2 in decimal degrees.


Returns:
geographical distance between two points in metres
Distance between the two points in meters.

>>> from collections import namedtuple
>>> point_2d = namedtuple("point_2d", "lat lon")
>>> SAN_FRANCISCO = point_2d(37.774856, -122.424227)
>>> YOSEMITE = point_2d(37.864742, -119.537521)
>>> f"{haversine_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters"
'254,352 meters'
'254,033 meters'
"""
# CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System
# Distance in metres(m)
# Equation parameters
# Equation https://en.wikipedia.org/wiki/Haversine_formula#Formulation
flattening = (AXIS_A - AXIS_B) / AXIS_A
phi_1 = atan((1 - flattening) * tan(radians(lat1)))
phi_2 = atan((1 - flattening) * tan(radians(lat2)))
lambda_1 = radians(lon1)
lambda_2 = radians(lon2)
# Equation
sin_sq_phi = sin((phi_2 - phi_1) / 2)
sin_sq_lambda = sin((lambda_2 - lambda_1) / 2)
# Square both values
sin_sq_phi *= sin_sq_phi
sin_sq_lambda *= sin_sq_lambda
h_value = sqrt(sin_sq_phi + (cos(phi_1) * cos(phi_2) * sin_sq_lambda))
return 2 * RADIUS * asin(h_value)

radius = 6378137 # earth radius (meters)

lat1_rad = radians(lat1)
lat2_rad = radians(lat2)
delta_lat = radians(lat2 - lat1)
delta_lon = radians(lon2 - lon1)

# Haversine formula
a = (
sin(delta_lat / 2) ** 2
+ cos(lat1_rad) * cos(lat2_rad) * sin(delta_lon / 2) ** 2
)
c = 2 * asin(sqrt(a))

# Great-Circle Distance
return radius * c


if __name__ == "__main__":
Expand Down
32 changes: 12 additions & 20 deletions geodesy/lamberts_ellipsoidal_distance.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,21 @@ def lamberts_ellipsoidal_distance(
two points on the surface of earth given longitudes and latitudes
https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines

NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle,
sigma

NOTE: This algorithm uses geodesy/haversine_distance.py to compute the
central angle, sigma
Representing the earth as an ellipsoid allows us to approximate distances between
points on the surface much better than a sphere. Ellipsoidal formulas treat the
Earth as an oblate ellipsoid which means accounting for the flattening that happens
at the North and South poles. Lambert's formulae provide accuracy on the order of
10 meteres over thousands of kilometeres. Other methods can provide
millimeter-level accuracy but this is a simpler method to calculate long range
at the North and South poles. Lambert's formulas provide accuracy on the order of
10 meters over thousands of kilometers. Other methods can provide
millimeter-level accuracy, but this is a simpler method to calculate long-range
distances without increasing computational intensity.

Args:
lat1, lon1: latitude and longitude of coordinate 1
lat2, lon2: latitude and longitude of coordinate 2
Returns:
geographical distance between two points in metres
geographical distance between two points in meters

>>> from collections import namedtuple
>>> point_2d = namedtuple("point_2d", "lat lon")
Expand All @@ -39,39 +38,32 @@ def lamberts_ellipsoidal_distance(
>>> NEW_YORK = point_2d(40.713019, -74.012647)
>>> VENICE = point_2d(45.443012, 12.313071)
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters"
'254,351 meters'
'254,032 meters'
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *NEW_YORK):0,.0f} meters"
'4,138,992 meters'
'4,133,295 meters'
>>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *VENICE):0,.0f} meters"
'9,737,326 meters'
'9,719,525 meters'
"""

# CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System
# Distance in metres(m)
# Equation Parameters
# https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines
flattening = (AXIS_A - AXIS_B) / AXIS_A
# Parametric latitudes
# https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude
b_lat1 = atan((1 - flattening) * tan(radians(lat1)))
b_lat2 = atan((1 - flattening) * tan(radians(lat2)))

# Compute central angle between two points
# using haversine theta. sigma = haversine_distance / equatorial radius
# Compute the central angle between two points using the haversine function
sigma = haversine_distance(lat1, lon1, lat2, lon2) / EQUATORIAL_RADIUS

# Intermediate P and Q values
p_value = (b_lat1 + b_lat2) / 2
q_value = (b_lat2 - b_lat1) / 2

# Intermediate X value
# X = (sigma - sin(sigma)) * sin^2Pcos^2Q / cos^2(sigma/2)
x_numerator = (sin(p_value) ** 2) * (cos(q_value) ** 2)
x_demonimator = cos(sigma / 2) ** 2
x_value = (sigma - sin(sigma)) * (x_numerator / x_demonimator)
x_denominator = cos(sigma / 2) ** 2
x_value = (sigma - sin(sigma)) * (x_numerator / x_denominator)

# Intermediate Y value
# Y = (sigma + sin(sigma)) * cos^2Psin^2Q / sin^2(sigma/2)
y_numerator = (cos(p_value) ** 2) * (sin(q_value) ** 2)
y_denominator = sin(sigma / 2) ** 2
y_value = (sigma + sin(sigma)) * (y_numerator / y_denominator)
Expand Down
3 changes: 3 additions & 0 deletions geodesy/temp_code_runner_file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# if __name__ == "__main__":
# import doctest
# doctest.testmod()