thomas_direct.hpp 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Boost.Geometry
  2. // Copyright (c) 2023 Adam Wulkiewicz, Lodz, Poland.
  3. // Copyright (c) 2016-2020 Oracle and/or its affiliates.
  4. // Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle
  5. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
  6. // Use, modification and distribution is subject to the Boost Software License,
  7. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  8. // http://www.boost.org/LICENSE_1_0.txt)
  9. #ifndef BOOST_GEOMETRY_FORMULAS_THOMAS_DIRECT_HPP
  10. #define BOOST_GEOMETRY_FORMULAS_THOMAS_DIRECT_HPP
  11. #include <boost/math/constants/constants.hpp>
  12. #include <boost/geometry/core/assert.hpp>
  13. #include <boost/geometry/core/radius.hpp>
  14. #include <boost/geometry/util/constexpr.hpp>
  15. #include <boost/geometry/util/math.hpp>
  16. #include <boost/geometry/util/normalize_spheroidal_coordinates.hpp>
  17. #include <boost/geometry/formulas/differential_quantities.hpp>
  18. #include <boost/geometry/formulas/flattening.hpp>
  19. #include <boost/geometry/formulas/result_direct.hpp>
  20. namespace boost { namespace geometry { namespace formula
  21. {
  22. /*!
  23. \brief The solution of the direct problem of geodesics on latlong coordinates,
  24. Forsyth-Andoyer-Lambert type approximation with first/second order terms.
  25. \author See
  26. - Technical Report: PAUL D. THOMAS, MATHEMATICAL MODELS FOR NAVIGATION SYSTEMS, 1965
  27. http://www.dtic.mil/docs/citations/AD0627893
  28. - Technical Report: PAUL D. THOMAS, SPHEROIDAL GEODESICS, REFERENCE SYSTEMS, AND LOCAL GEOMETRY, 1970
  29. http://www.dtic.mil/docs/citations/AD0703541
  30. */
  31. template <
  32. typename CT,
  33. bool SecondOrder = true,
  34. bool EnableCoordinates = true,
  35. bool EnableReverseAzimuth = false,
  36. bool EnableReducedLength = false,
  37. bool EnableGeodesicScale = false
  38. >
  39. class thomas_direct
  40. {
  41. static const bool CalcQuantities = EnableReducedLength || EnableGeodesicScale;
  42. static const bool CalcCoordinates = EnableCoordinates || CalcQuantities;
  43. static const bool CalcRevAzimuth = EnableReverseAzimuth || CalcCoordinates || CalcQuantities;
  44. public:
  45. typedef result_direct<CT> result_type;
  46. template <typename T, typename Dist, typename Azi, typename Spheroid>
  47. static inline result_type apply(T const& lo1,
  48. T const& la1,
  49. Dist const& distance,
  50. Azi const& azimuth12,
  51. Spheroid const& spheroid)
  52. {
  53. result_type result;
  54. CT const lon1 = lo1;
  55. CT const lat1 = la1;
  56. CT const c0 = 0;
  57. CT const c1 = 1;
  58. CT const c2 = 2;
  59. CT const c4 = 4;
  60. CT const a = CT(get_radius<0>(spheroid));
  61. CT const b = CT(get_radius<2>(spheroid));
  62. CT const f = formula::flattening<CT>(spheroid);
  63. CT const one_minus_f = c1 - f;
  64. CT const pi = math::pi<CT>();
  65. CT const pi_half = pi / c2;
  66. BOOST_GEOMETRY_ASSERT(-pi <= azimuth12 && azimuth12 <= pi);
  67. // keep azimuth small - experiments show low accuracy
  68. // if the azimuth is closer to (+-)180 deg.
  69. CT azi12_alt = azimuth12;
  70. CT lat1_alt = lat1;
  71. bool alter_result = vflip_if_south(lat1, azimuth12, lat1_alt, azi12_alt);
  72. CT const theta1 = math::equals(lat1_alt, pi_half) ? lat1_alt :
  73. math::equals(lat1_alt, -pi_half) ? lat1_alt :
  74. atan(one_minus_f * tan(lat1_alt));
  75. CT const sin_theta1 = sin(theta1);
  76. CT const cos_theta1 = cos(theta1);
  77. CT const sin_a12 = sin(azi12_alt);
  78. CT const cos_a12 = cos(azi12_alt);
  79. CT const M = cos_theta1 * sin_a12; // cos_theta0
  80. CT const theta0 = acos(M);
  81. CT const sin_theta0 = sin(theta0);
  82. CT const N = cos_theta1 * cos_a12;
  83. CT const C1 = f * M; // lower-case c1 in the technical report
  84. CT const C2 = f * (c1 - math::sqr(M)) / c4; // lower-case c2 in the technical report
  85. CT D = 0;
  86. CT P = 0;
  87. if BOOST_GEOMETRY_CONSTEXPR (SecondOrder)
  88. {
  89. D = (c1 - C2) * (c1 - C2 - C1 * M);
  90. P = C2 * (c1 + C1 * M / c2) / D;
  91. }
  92. else
  93. {
  94. D = c1 - c2 * C2 - C1 * M;
  95. P = C2 / D;
  96. }
  97. // special case for equator:
  98. // sin_theta0 = 0 <=> lat1 = 0 ^ |azimuth12| = pi/2
  99. // NOTE: in this case it doesn't matter what's the value of cos_sigma1 because
  100. // theta1=0, theta0=0, M=1|-1, C2=0 so X=0 and Y=0 so d_sigma=d
  101. // cos_a12=0 so N=0, therefore
  102. // lat2=0, azi21=pi/2|-pi/2
  103. // d_eta = atan2(sin_d_sigma, cos_d_sigma)
  104. // H = C1 * d_sigma
  105. CT const cos_sigma1 = math::equals(sin_theta0, c0)
  106. ? c1
  107. : normalized1_1(sin_theta1 / sin_theta0);
  108. CT const sigma1 = acos(cos_sigma1);
  109. CT const d = distance / (a * D);
  110. CT const u = 2 * (sigma1 - d);
  111. CT const cos_d = cos(d);
  112. CT const sin_d = sin(d);
  113. CT const cos_u = cos(u);
  114. CT const sin_u = sin(u);
  115. CT const W = c1 - c2 * P * cos_u;
  116. CT const V = cos_u * cos_d - sin_u * sin_d;
  117. CT const Y = c2 * P * V * W * sin_d;
  118. CT X = 0;
  119. CT d_sigma = d - Y;
  120. if BOOST_GEOMETRY_CONSTEXPR (SecondOrder)
  121. {
  122. X = math::sqr(C2) * sin_d * cos_d * (2 * math::sqr(V) - c1);
  123. d_sigma += X;
  124. }
  125. CT const sin_d_sigma = sin(d_sigma);
  126. CT const cos_d_sigma = cos(d_sigma);
  127. if BOOST_GEOMETRY_CONSTEXPR (CalcRevAzimuth)
  128. {
  129. result.reverse_azimuth = atan2(M, N * cos_d_sigma - sin_theta1 * sin_d_sigma);
  130. if (alter_result)
  131. {
  132. vflip_rev_azi(result.reverse_azimuth, azimuth12);
  133. }
  134. }
  135. if BOOST_GEOMETRY_CONSTEXPR (CalcCoordinates)
  136. {
  137. CT const S_sigma = c2 * sigma1 - d_sigma;
  138. CT cos_S_sigma = 0;
  139. CT H = C1 * d_sigma;
  140. if BOOST_GEOMETRY_CONSTEXPR (SecondOrder)
  141. {
  142. cos_S_sigma = cos(S_sigma);
  143. H = H * (c1 - C2) - C1 * C2 * sin_d_sigma * cos_S_sigma;
  144. }
  145. CT const d_eta = atan2(sin_d_sigma * sin_a12, cos_theta1 * cos_d_sigma - sin_theta1 * sin_d_sigma * cos_a12);
  146. CT const d_lambda = d_eta - H;
  147. result.lon2 = lon1 + d_lambda;
  148. if (! math::equals(M, c0))
  149. {
  150. CT const sin_a21 = sin(result.reverse_azimuth);
  151. CT const tan_theta2 = (sin_theta1 * cos_d_sigma + N * sin_d_sigma) * sin_a21 / M;
  152. result.lat2 = atan(tan_theta2 / one_minus_f);
  153. }
  154. else
  155. {
  156. CT const sigma2 = S_sigma - sigma1;
  157. //theta2 = asin(cos(sigma2)) <=> sin_theta0 = 1
  158. // NOTE: cos(sigma2) defines the sign of tan_theta2
  159. CT const tan_theta2 = cos(sigma2) / math::abs(sin(sigma2));
  160. result.lat2 = atan(tan_theta2 / one_minus_f);
  161. }
  162. if (alter_result)
  163. {
  164. result.lat2 = -result.lat2;
  165. }
  166. }
  167. if BOOST_GEOMETRY_CONSTEXPR (CalcQuantities)
  168. {
  169. typedef differential_quantities<CT, EnableReducedLength, EnableGeodesicScale, 2> quantities;
  170. quantities::apply(lon1, lat1, result.lon2, result.lat2,
  171. azimuth12, result.reverse_azimuth,
  172. b, f,
  173. result.reduced_length, result.geodesic_scale);
  174. }
  175. if BOOST_GEOMETRY_CONSTEXPR (CalcCoordinates)
  176. {
  177. // For longitudes close to the antimeridian the result can be out
  178. // of range. Therefore normalize.
  179. // It has to be done at the end because otherwise differential
  180. // quantities are calculated incorrectly.
  181. math::detail::normalize_angle_cond<radian>(result.lon2);
  182. }
  183. return result;
  184. }
  185. private:
  186. static inline bool vflip_if_south(CT const& lat1, CT const& azi12, CT & lat1_alt, CT & azi12_alt)
  187. {
  188. CT const c2 = 2;
  189. CT const pi = math::pi<CT>();
  190. CT const pi_half = pi / c2;
  191. if (azi12 > pi_half)
  192. {
  193. azi12_alt = pi - azi12;
  194. lat1_alt = -lat1;
  195. return true;
  196. }
  197. else if (azi12 < -pi_half)
  198. {
  199. azi12_alt = -pi - azi12;
  200. lat1_alt = -lat1;
  201. return true;
  202. }
  203. return false;
  204. }
  205. static inline void vflip_rev_azi(CT & rev_azi, CT const& azimuth12)
  206. {
  207. CT const c0 = 0;
  208. CT const pi = math::pi<CT>();
  209. if (rev_azi == c0)
  210. {
  211. rev_azi = azimuth12 >= 0 ? pi : -pi;
  212. }
  213. else if (rev_azi > c0)
  214. {
  215. rev_azi = pi - rev_azi;
  216. }
  217. else
  218. {
  219. rev_azi = -pi - rev_azi;
  220. }
  221. }
  222. static inline CT normalized1_1(CT const& value)
  223. {
  224. CT const c1 = 1;
  225. return value > c1 ? c1 :
  226. value < -c1 ? -c1 :
  227. value;
  228. }
  229. };
  230. }}} // namespace boost::geometry::formula
  231. #endif // BOOST_GEOMETRY_FORMULAS_THOMAS_DIRECT_HPP