geographical_distance.nbt 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. # Compute the geographical distance between two points on
  2. # Earth's surface using Vincenty's formulae, which are accurate
  3. # to sub-millimeter precision.
  4. #
  5. # See:
  6. # - http://www.movable-type.co.uk/scripts/latlong-vincenty.html
  7. # - https://en.wikipedia.org/wiki/Vincenty%27s_formulae
  8. use numerics::fixed_point
  9. struct LatLon {
  10. name: String,
  11. lat: Angle,
  12. lon: Angle,
  13. }
  14. let stuttgart = LatLon {
  15. name: "Stuttgart",
  16. lat: 48° + 47′,
  17. lon: 9° + 11′,
  18. }
  19. let paris = LatLon {
  20. name: "Paris",
  21. lat: 48° + 51′,
  22. lon: 2° + 21′,
  23. }
  24. let cape_town = LatLon {
  25. name: "Cape Town",
  26. lat: -(33° + 56′),
  27. lon: 18° + 25′,
  28. }
  29. let lima = LatLon {
  30. name: "Lima",
  31. lat: -(12° + 04′),
  32. lon: -(77° + 02′),
  33. }
  34. let p1 = stuttgart
  35. let p2 = lima
  36. let λ1 = p1.lon
  37. let φ1 = p1.lat
  38. let λ2 = p2.lon
  39. let φ2 = p2.lat
  40. # Equatorial radius of the Earth (Semi-major axis):
  41. let a: Length = 6_378_137.0 m
  42. # Polar radius of the Earth (Semi-minor axis, GR80):
  43. let b: Length = 6_356_752.314_140_347 m
  44. let flattening: Scalar = (a - b) / a
  45. # Difference in longitude:
  46. let ΔL = λ2 - λ1
  47. # Reduced latitudes:
  48. let U1 = atan((1 - flattening) * tan(φ1))
  49. let U2 = atan((1 - flattening) * tan(φ2))
  50. fn step_λ(λ: Angle) -> Angle =
  51. ΔL + (1 - C_) flattening sinα × (σ + C_ sinσ × (cos2σm + C_ cosσ × (-1 + 2 cos2σm²)))
  52. where sinλ = sin(λ)
  53. and cosλ = cos(λ)
  54. and sinσ = sqrt((cos(U2) sinλ)² + (cos(U1) sin(U2) - sin(U1) cos(U2) cosλ)²)
  55. and cosσ = sin(U1) sin(U2) + cos(U1) cos(U2) cosλ
  56. and σ = atan2(sinσ, cosσ)
  57. and sinα = cos(U1) cos(U2) sinλ / sinσ
  58. and cosα_squared = 1 - sinα²
  59. and cos2σm = cosσ - 2 sin(U1) sin(U2) / cosα_squared
  60. and C_ = flattening / 16 × cosα_squared × (4 + flattening × (4 - 3 cosα_squared))
  61. let λ = fixed_point(step_λ, ΔL, 1e-12)
  62. # TODO: It's unfortunate that we have to duplicate this part:
  63. let sinλ = sin(λ)
  64. let cosλ = cos(λ)
  65. let sinσ = sqrt((cos(U2) sinλ)² + (cos(U1) sin(U2) - sin(U1) cos(U2) cosλ)²)
  66. let cosσ = sin(U1) sin(U2) + cos(U1) cos(U2) cosλ
  67. let σ = atan2(sinσ, cosσ)
  68. let sinα = cos(U1) cos(U2) sinλ / sinσ
  69. let cosα_squared = 1 - sinα²
  70. let cos2σm = cosσ - 2 sin(U1) sin(U2) / cosα_squared
  71. let C_ = flattening / 16 × cosα_squared × (4 + flattening × (4 - 3 cosα_squared))
  72. let u_squared = cosα_squared × (a² - b²) / b²
  73. let A_ = 1 + u_squared / 16384 × (4096 + u_squared × (-768 + u_squared × (320 - 175 u_squared)))
  74. let B_ = u_squared / 1024 × (256 + u_squared × (-128 + u_squared × (74 - 47 u_squared)))
  75. let Δσ = B_ sinσ × (cos2σm + B_ / 4 × (cosσ × (-1 + 2 cos2σm²) - B_/6 × cos2σm × (-3 + 4 sinσ²) × (-3 + 4 cos2σm²)))
  76. let distance = b A_ × (σ - Δσ)
  77. print("Distance {p1.name} - {p2.name}: {distance -> km:.6f}")
  78. # The reference values are computed using
  79. # https://geodesyapps.ga.gov.au/vincenty-inverse
  80. #
  81. # Ellipsoid: GRS80
  82. # Coordinate Reference System: Geographic
  83. # Geographic Coordinate Notation: DDM
  84. # Stuttgart - Lima
  85. assert_eq(distance, 10_733.845_039 km, 1 mm)
  86. # Stuttgart - Paris
  87. # assert_eq(distance, 501.725_916 km, 1 mm)
  88. # Stuttgart - Cape Town
  89. # assert_eq(distance, 9_207.599_441 km, 40 km)