andoyer_inverse.hpp 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  1. // Boost.Geometry
  2. // Copyright (c) 2018 Adam Wulkiewicz, Lodz, Poland.
  3. // Copyright (c) 2015-2017 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/condition.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_CONDITION(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 or cos_d = -1 means that the points are antipodal
  87. CT const H = math::equals(one_minus_cos_d, c0) ?
  88. c0 :
  89. (d + three_sin_d) / one_minus_cos_d;
  90. CT const G = math::equals(one_plus_cos_d, c0) ?
  91. c0 :
  92. (d - three_sin_d) / one_plus_cos_d;
  93. CT const dd = -(f/CT(4))*(H*K+G*L);
  94. CT const a = CT(get_radius<0>(spheroid));
  95. result.distance = a * (d + dd);
  96. }
  97. if ( BOOST_GEOMETRY_CONDITION(CalcAzimuths) )
  98. {
  99. // sin_d = 0 <=> antipodal points (incl. poles)
  100. if (math::equals(sin_d, c0))
  101. {
  102. // T = inf
  103. // dA = inf
  104. // azimuth = -inf
  105. // TODO: The following azimuths are inconsistent with distance
  106. // i.e. according to azimuths below a segment with antipodal endpoints
  107. // travels through the north pole, however the distance returned above
  108. // is the length of a segment traveling along the equator.
  109. // Furthermore, this special case handling is only done in andoyer
  110. // formula.
  111. // The most correct way of fixing it is to handle antipodal regions
  112. // correctly and consistently across all formulas.
  113. // Set azimuth to 0 unless the first endpoint is the north pole
  114. if (! math::equals(sin_lat1, c1))
  115. {
  116. result.azimuth = c0;
  117. result.reverse_azimuth = pi;
  118. }
  119. else
  120. {
  121. result.azimuth = pi;
  122. result.reverse_azimuth = 0;
  123. }
  124. }
  125. else
  126. {
  127. CT const c2 = CT(2);
  128. CT A = c0;
  129. CT U = c0;
  130. if (math::equals(cos_lat2, c0))
  131. {
  132. if (sin_lat2 < c0)
  133. {
  134. A = pi;
  135. }
  136. }
  137. else
  138. {
  139. CT const tan_lat2 = sin_lat2/cos_lat2;
  140. CT const M = cos_lat1*tan_lat2-sin_lat1*cos_dlon;
  141. A = atan2(sin_dlon, M);
  142. CT const sin_2A = sin(c2*A);
  143. U = (f/ c2)*math::sqr(cos_lat1)*sin_2A;
  144. }
  145. CT B = c0;
  146. CT V = c0;
  147. if (math::equals(cos_lat1, c0))
  148. {
  149. if (sin_lat1 < c0)
  150. {
  151. B = pi;
  152. }
  153. }
  154. else
  155. {
  156. CT const tan_lat1 = sin_lat1/cos_lat1;
  157. CT const N = cos_lat2*tan_lat1-sin_lat2*cos_dlon;
  158. B = atan2(sin_dlon, N);
  159. CT const sin_2B = sin(c2*B);
  160. V = (f/ c2)*math::sqr(cos_lat2)*sin_2B;
  161. }
  162. CT const T = d / sin_d;
  163. // even with sin_d == 0 checked above if the second point
  164. // is somewhere in the antipodal area T may still be great
  165. // therefore dA and dB may be great and the resulting azimuths
  166. // may be some more or less arbitrary angles
  167. if (BOOST_GEOMETRY_CONDITION(CalcFwdAzimuth))
  168. {
  169. CT const dA = V*T - U;
  170. result.azimuth = A - dA;
  171. normalize_azimuth(result.azimuth, A, dA);
  172. }
  173. if (BOOST_GEOMETRY_CONDITION(CalcRevAzimuth))
  174. {
  175. CT const dB = -U*T + V;
  176. if (B >= 0)
  177. result.reverse_azimuth = pi - B - dB;
  178. else
  179. result.reverse_azimuth = -pi - B - dB;
  180. normalize_azimuth(result.reverse_azimuth, B, dB);
  181. }
  182. }
  183. }
  184. if (BOOST_GEOMETRY_CONDITION(CalcQuantities))
  185. {
  186. CT const b = CT(get_radius<2>(spheroid));
  187. typedef differential_quantities<CT, EnableReducedLength, EnableGeodesicScale, 1> quantities;
  188. quantities::apply(dlon, sin_lat1, cos_lat1, sin_lat2, cos_lat2,
  189. result.azimuth, result.reverse_azimuth,
  190. b, f,
  191. result.reduced_length, result.geodesic_scale);
  192. }
  193. return result;
  194. }
  195. private:
  196. static inline void normalize_azimuth(CT & azimuth, CT const& A, CT const& dA)
  197. {
  198. CT const c0 = 0;
  199. if (A >= c0) // A indicates Eastern hemisphere
  200. {
  201. if (dA >= c0) // A altered towards 0
  202. {
  203. if (azimuth < c0)
  204. {
  205. azimuth = c0;
  206. }
  207. }
  208. else // dA < 0, A altered towards pi
  209. {
  210. CT const pi = math::pi<CT>();
  211. if (azimuth > pi)
  212. {
  213. azimuth = pi;
  214. }
  215. }
  216. }
  217. else // A indicates Western hemisphere
  218. {
  219. if (dA <= c0) // A altered towards 0
  220. {
  221. if (azimuth > c0)
  222. {
  223. azimuth = c0;
  224. }
  225. }
  226. else // dA > 0, A altered towards -pi
  227. {
  228. CT const minus_pi = -math::pi<CT>();
  229. if (azimuth < minus_pi)
  230. {
  231. azimuth = minus_pi;
  232. }
  233. }
  234. }
  235. }
  236. };
  237. }}} // namespace boost::geometry::formula
  238. #endif // BOOST_GEOMETRY_FORMULAS_ANDOYER_INVERSE_HPP