andoyer_inverse.hpp 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Boost.Geometry
  2. // Copyright (c) 2018-2023 Adam Wulkiewicz, Lodz, Poland.
  3. // Copyright (c) 2015-2020 Oracle and/or its affiliates.
  4. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
  5. // Use, modification and distribution is subject to the Boost Software License,
  6. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  7. // http://www.boost.org/LICENSE_1_0.txt)
  8. #ifndef BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP
  9. #define BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP
  10. #include <boost/math/constants/constants.hpp>
  11. #include <boost/geometry/core/radius.hpp>
  12. #include <boost/geometry/util/constexpr.hpp>
  13. #include <boost/geometry/util/math.hpp>
  14. #include <boost/geometry/formulas/differential_quantities.hpp>
  15. #include <boost/geometry/formulas/flattening.hpp>
  16. #include <boost/geometry/formulas/result_inverse.hpp>
  17. namespace boost { namespace geometry { namespace formula
  18. {
  19. /*!
  20. \brief The solution of the inverse problem of geodesics on latlong coordinates,
  21. Forsyth-Andoyer-Lambert type approximation with first order terms.
  22. \author See
  23. - Technical Report: PAUL D. THOMAS, MATHEMATICAL MODELS FOR NAVIGATION SYSTEMS, 1965
  24. http://www.dtic.mil/docs/citations/AD0627893
  25. - Technical Report: PAUL D. THOMAS, SPHEROIDAL GEODESICS, REFERENCE SYSTEMS, AND LOCAL GEOMETRY, 1970
  26. http://www.dtic.mil/docs/citations/AD703541
  27. */
  28. template <
  29. typename CT,
  30. bool EnableDistance,
  31. bool EnableAzimuth,
  32. bool EnableReverseAzimuth = false,
  33. bool EnableReducedLength = false,
  34. bool EnableGeodesicScale = false
  35. >
  36. class andoyer_inverse
  37. {
  38. static const bool CalcQuantities = EnableReducedLength || EnableGeodesicScale;
  39. static const bool CalcAzimuths = EnableAzimuth || EnableReverseAzimuth || CalcQuantities;
  40. static const bool CalcFwdAzimuth = EnableAzimuth || CalcQuantities;
  41. static const bool CalcRevAzimuth = EnableReverseAzimuth || CalcQuantities;
  42. public:
  43. typedef result_inverse<CT> result_type;
  44. template <typename T1, typename T2, typename Spheroid>
  45. static inline result_type apply(T1 const& lon1,
  46. T1 const& lat1,
  47. T2 const& lon2,
  48. T2 const& lat2,
  49. Spheroid const& spheroid)
  50. {
  51. result_type result;
  52. // coordinates in radians
  53. if ( math::equals(lon1, lon2) && math::equals(lat1, lat2) )
  54. {
  55. return result;
  56. }
  57. CT const c0 = CT(0);
  58. CT const c1 = CT(1);
  59. CT const pi = math::pi<CT>();
  60. CT const f = formula::flattening<CT>(spheroid);
  61. CT const dlon = lon2 - lon1;
  62. CT const sin_dlon = sin(dlon);
  63. CT const cos_dlon = cos(dlon);
  64. CT const sin_lat1 = sin(lat1);
  65. CT const cos_lat1 = cos(lat1);
  66. CT const sin_lat2 = sin(lat2);
  67. CT const cos_lat2 = cos(lat2);
  68. // H,G,T = infinity if cos_d = 1 or cos_d = -1
  69. // lat1 == +-90 && lat2 == +-90
  70. // lat1 == lat2 && lon1 == lon2
  71. CT cos_d = sin_lat1*sin_lat2 + cos_lat1*cos_lat2*cos_dlon;
  72. // on some platforms cos_d may be outside valid range
  73. if (cos_d < -c1)
  74. cos_d = -c1;
  75. else if (cos_d > c1)
  76. cos_d = c1;
  77. CT const d = acos(cos_d); // [0, pi]
  78. CT const sin_d = sin(d); // [-1, 1]
  79. if BOOST_GEOMETRY_CONSTEXPR (EnableDistance)
  80. {
  81. CT const K = math::sqr(sin_lat1-sin_lat2);
  82. CT const L = math::sqr(sin_lat1+sin_lat2);
  83. CT const three_sin_d = CT(3) * sin_d;
  84. CT const one_minus_cos_d = c1 - cos_d;
  85. CT const one_plus_cos_d = c1 + cos_d;
  86. // cos_d = 1 means that the points are very close
  87. // cos_d = -1 means that the points are antipodal
  88. CT const H = math::equals(one_minus_cos_d, c0) ?
  89. c0 :
  90. (d + three_sin_d) / one_minus_cos_d;
  91. CT const G = math::equals(one_plus_cos_d, c0) ?
  92. c0 :
  93. (d - three_sin_d) / one_plus_cos_d;
  94. CT const dd = -(f/CT(4))*(H*K+G*L);
  95. CT const a = CT(get_radius<0>(spheroid));
  96. result.distance = a * (d + dd);
  97. }
  98. if BOOST_GEOMETRY_CONSTEXPR (CalcAzimuths)
  99. {
  100. // sin_d = 0 <=> antipodal points (incl. poles) or very close
  101. if (math::equals(sin_d, c0))
  102. {
  103. // T = inf
  104. // dA = inf
  105. // azimuth = -inf
  106. // TODO: The following azimuths are inconsistent with distance
  107. // i.e. according to azimuths below a segment with antipodal endpoints
  108. // travels through the north pole, however the distance returned above
  109. // is the length of a segment traveling along the equator.
  110. // Furthermore, this special case handling is only done in andoyer
  111. // formula.
  112. // The most correct way of fixing it is to handle antipodal regions
  113. // correctly and consistently across all formulas.
  114. // points very close
  115. if (cos_d >= c0)
  116. {
  117. result.azimuth = c0;
  118. result.reverse_azimuth = c0;
  119. }
  120. // antipodal points
  121. else
  122. {
  123. // Set azimuth to 0 unless the first endpoint is the north pole
  124. if (! math::equals(sin_lat1, c1))
  125. {
  126. result.azimuth = c0;
  127. result.reverse_azimuth = pi;
  128. }
  129. else
  130. {
  131. result.azimuth = pi;
  132. result.reverse_azimuth = c0;
  133. }
  134. }
  135. }
  136. else
  137. {
  138. CT const c2 = CT(2);
  139. CT A = c0;
  140. CT U = c0;
  141. if (math::equals(cos_lat2, c0))
  142. {
  143. if (sin_lat2 < c0)
  144. {
  145. A = pi;
  146. }
  147. }
  148. else
  149. {
  150. CT const tan_lat2 = sin_lat2/cos_lat2;
  151. CT const M = cos_lat1*tan_lat2-sin_lat1*cos_dlon;
  152. A = atan2(sin_dlon, M);
  153. CT const sin_2A = sin(c2*A);
  154. U = (f/ c2)*math::sqr(cos_lat1)*sin_2A;
  155. }
  156. CT B = c0;
  157. CT V = c0;
  158. if (math::equals(cos_lat1, c0))
  159. {
  160. if (sin_lat1 < c0)
  161. {
  162. B = pi;
  163. }
  164. }
  165. else
  166. {
  167. CT const tan_lat1 = sin_lat1/cos_lat1;
  168. CT const N = cos_lat2*tan_lat1-sin_lat2*cos_dlon;
  169. B = atan2(sin_dlon, N);
  170. CT const sin_2B = sin(c2*B);
  171. V = (f/ c2)*math::sqr(cos_lat2)*sin_2B;
  172. }
  173. CT const T = d / sin_d;
  174. // even with sin_d == 0 checked above if the second point
  175. // is somewhere in the antipodal area T may still be great
  176. // therefore dA and dB may be great and the resulting azimuths
  177. // may be some more or less arbitrary angles
  178. if BOOST_GEOMETRY_CONSTEXPR (CalcFwdAzimuth)
  179. {
  180. CT const dA = V*T - U;
  181. result.azimuth = A - dA;
  182. normalize_azimuth(result.azimuth, A, dA);
  183. }
  184. if BOOST_GEOMETRY_CONSTEXPR (CalcRevAzimuth)
  185. {
  186. CT const dB = -U*T + V;
  187. if (B >= 0)
  188. result.reverse_azimuth = pi - B - dB;
  189. else
  190. result.reverse_azimuth = -pi - B - dB;
  191. normalize_azimuth(result.reverse_azimuth, B, dB);
  192. }
  193. }
  194. }
  195. if BOOST_GEOMETRY_CONSTEXPR (CalcQuantities)
  196. {
  197. CT const b = CT(get_radius<2>(spheroid));
  198. typedef differential_quantities<CT, EnableReducedLength, EnableGeodesicScale, 1> quantities;
  199. quantities::apply(dlon, sin_lat1, cos_lat1, sin_lat2, cos_lat2,
  200. result.azimuth, result.reverse_azimuth,
  201. b, f,
  202. result.reduced_length, result.geodesic_scale);
  203. }
  204. return result;
  205. }
  206. private:
  207. static inline void normalize_azimuth(CT & azimuth, CT const& A, CT const& dA)
  208. {
  209. CT const c0 = 0;
  210. if (A >= c0) // A indicates Eastern hemisphere
  211. {
  212. if (dA >= c0) // A altered towards 0
  213. {
  214. if (azimuth < c0)
  215. {
  216. azimuth = c0;
  217. }
  218. }
  219. else // dA < 0, A altered towards pi
  220. {
  221. CT const pi = math::pi<CT>();
  222. if (azimuth > pi)
  223. {
  224. azimuth = pi;
  225. }
  226. }
  227. }
  228. else // A indicates Western hemisphere
  229. {
  230. if (dA <= c0) // A altered towards 0
  231. {
  232. if (azimuth > c0)
  233. {
  234. azimuth = c0;
  235. }
  236. }
  237. else // dA > 0, A altered towards -pi
  238. {
  239. CT const minus_pi = -math::pi<CT>();
  240. if (azimuth < minus_pi)
  241. {
  242. azimuth = minus_pi;
  243. }
  244. }
  245. }
  246. }
  247. };
  248. }}} // namespace boost::geometry::formula
  249. #endif // BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP