|
| 1 | +from math import atan, cos, radians, sin, tan |
| 2 | +from haversine_distance import haversine_distance |
| 3 | + |
| 4 | + |
| 5 | +def lamberts_ellipsoidal_distance( |
| 6 | + lat1: float, lon1: float, lat2: float, lon2: float |
| 7 | +) -> float: |
| 8 | + |
| 9 | + """ |
| 10 | + Calculate the shortest distance along the surface of an ellipsoid between |
| 11 | + two points on the surface of earth given longitudes and latitudes |
| 12 | + https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines |
| 13 | +
|
| 14 | + NOTE: This algorithm uses geodesy/haversine_distance.py to compute central angle, sigma |
| 15 | +
|
| 16 | + Representing the earth as an ellipsoid allows us to approximate distances between points |
| 17 | + on the surface much better than a sphere. Ellipsoidal formulas treat the Earth as an |
| 18 | + oblate ellipsoid which means accounting for the flattening that happens at the North |
| 19 | + and South poles. Lambert's formulae provide accuracy on the order of 10 meteres over |
| 20 | + thousands of kilometeres. Other methods can provide millimeter-level accuracy but this |
| 21 | + is a simpler method to calculate long range distances without increasing computational |
| 22 | + intensity. |
| 23 | +
|
| 24 | + Args: |
| 25 | + lat1, lon1: latitude and longitude of coordinate 1 |
| 26 | + lat2, lon2: latitude and longitude of coordinate 2 |
| 27 | + Returns: |
| 28 | + geographical distance between two points in metres |
| 29 | +
|
| 30 | + >>> from collections import namedtuple |
| 31 | + >>> point_2d = namedtuple("point_2d", "lat lon") |
| 32 | + >>> SAN_FRANCISCO = point_2d(37.774856, -122.424227) |
| 33 | + >>> YOSEMITE = point_2d(37.864742, -119.537521) |
| 34 | + >>> NEW_YORK = point_2d(40.713019, -74.012647) |
| 35 | + >>> VENICE = point_2d(45.443012, 12.313071) |
| 36 | + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *YOSEMITE):0,.0f} meters" |
| 37 | + '254,351 meters' |
| 38 | + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *NEW_YORK):0,.0f} meters" |
| 39 | + '4,138,992 meters' |
| 40 | + >>> f"{lamberts_ellipsoidal_distance(*SAN_FRANCISCO, *VENICE):0,.0f} meters" |
| 41 | + '9,737,326 meters' |
| 42 | + """ |
| 43 | + |
| 44 | + # CONSTANTS per WGS84 https://en.wikipedia.org/wiki/World_Geodetic_System |
| 45 | + # Distance in metres(m) |
| 46 | + AXIS_A = 6378137.0 |
| 47 | + AXIS_B = 6356752.314245 |
| 48 | + EQUATORIAL_RADIUS = 6378137 |
| 49 | + |
| 50 | + # Equation Parameters |
| 51 | + # https://en.wikipedia.org/wiki/Geographical_distance#Lambert's_formula_for_long_lines |
| 52 | + flattening = (AXIS_A - AXIS_B) / AXIS_A |
| 53 | + # Parametric latitudes https://en.wikipedia.org/wiki/Latitude#Parametric_(or_reduced)_latitude |
| 54 | + b_lat1 = atan((1 - flattening) * tan(radians(lat1))) |
| 55 | + b_lat2 = atan((1 - flattening) * tan(radians(lat2))) |
| 56 | + |
| 57 | + # Compute central angle between two points |
| 58 | + # using haversine theta. sigma = haversine_distance / equatorial radius |
| 59 | + sigma = haversine_distance(lat1, lon1, lat2, lon2) / EQUATORIAL_RADIUS |
| 60 | + |
| 61 | + # Intermediate P and Q values |
| 62 | + P_value = (b_lat1 + b_lat2) / 2 |
| 63 | + Q_value = (b_lat2 - b_lat1) / 2 |
| 64 | + |
| 65 | + # Intermediate X value |
| 66 | + # X = (sigma - sin(sigma)) * sin^2Pcos^2Q / cos^2(sigma/2) |
| 67 | + X_numerator = (sin(P_value) ** 2) * (cos(Q_value) ** 2) |
| 68 | + X_demonimator = cos(sigma / 2) ** 2 |
| 69 | + X_value = (sigma - sin(sigma)) * (X_numerator / X_demonimator) |
| 70 | + |
| 71 | + # Intermediate Y value |
| 72 | + # Y = (sigma + sin(sigma)) * cos^2Psin^2Q / sin^2(sigma/2) |
| 73 | + Y_numerator = (cos(P_value) ** 2) * (sin(Q_value) ** 2) |
| 74 | + Y_denominator = sin(sigma / 2) ** 2 |
| 75 | + Y_value = (sigma + sin(sigma)) * (Y_numerator / Y_denominator) |
| 76 | + |
| 77 | + return EQUATORIAL_RADIUS * (sigma - ((flattening / 2) * (X_value + Y_value))) |
| 78 | + |
| 79 | + |
| 80 | +if __name__ == "__main__": |
| 81 | + import doctest |
| 82 | + |
| 83 | + doctest.testmod() |
0 commit comments