distance_cross_track.hpp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027
  1. // Boost.Geometry (aka GGL, Generic Geometry Library)
  2. // Copyright (c) 2023-2025 Adam Wulkiewicz, Lodz, Poland.
  3. // Copyright (c) 2016-2022, 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_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP
  10. #define BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP
  11. #include <algorithm>
  12. #include <boost/config.hpp>
  13. #include <boost/concept_check.hpp>
  14. #include <boost/geometry/core/cs.hpp>
  15. #include <boost/geometry/core/access.hpp>
  16. #include <boost/geometry/core/coordinate_promotion.hpp>
  17. #include <boost/geometry/core/radian_access.hpp>
  18. #include <boost/geometry/core/tags.hpp>
  19. #include <boost/geometry/strategies/distance.hpp>
  20. #include <boost/geometry/strategies/concepts/distance_concept.hpp>
  21. #include <boost/geometry/strategies/spherical/distance_cross_track.hpp>
  22. #include <boost/geometry/strategies/spherical/distance_haversine.hpp>
  23. #include <boost/geometry/strategies/spherical/point_in_point.hpp>
  24. #include <boost/geometry/strategies/geographic/azimuth.hpp>
  25. #include <boost/geometry/strategies/geographic/distance.hpp>
  26. #include <boost/geometry/strategies/geographic/parameters.hpp>
  27. #include <boost/geometry/strategies/geographic/intersection.hpp>
  28. #include <boost/geometry/formulas/vincenty_direct.hpp>
  29. #include <boost/geometry/util/math.hpp>
  30. #include <boost/geometry/util/select_calculation_type.hpp>
  31. #include <boost/geometry/util/normalize_spheroidal_coordinates.hpp>
  32. #include <boost/geometry/formulas/result_direct.hpp>
  33. #include <boost/geometry/formulas/mean_radius.hpp>
  34. #include <boost/geometry/formulas/spherical.hpp>
  35. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  36. #include <boost/geometry/io/dsv/write.hpp>
  37. #endif
  38. #ifndef BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS
  39. #define BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS 100
  40. #endif
  41. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  42. #include <iostream>
  43. #endif
  44. namespace boost { namespace geometry
  45. {
  46. namespace strategy { namespace distance
  47. {
  48. namespace detail
  49. {
  50. template <bool EnableClosestPoint>
  51. struct set_result
  52. {
  53. template <typename CT, typename ResultType>
  54. static void apply(CT const& distance,
  55. CT const&,
  56. CT const&,
  57. ResultType& result)
  58. {
  59. result.distance = distance;
  60. }
  61. };
  62. template<>
  63. struct set_result<true>
  64. {
  65. template <typename CT, typename ResultType>
  66. static void apply(CT const&,
  67. CT const& lon,
  68. CT const& lat,
  69. ResultType& result)
  70. {
  71. result.lon = lon;
  72. result.lat = lat;
  73. }
  74. };
  75. /*!
  76. \brief Strategy functor for distance point to segment calculation on ellipsoid
  77. Algorithm uses direct and inverse geodesic problems as subroutines.
  78. The algorithm approximates the distance by an iterative Newton method.
  79. \ingroup strategies
  80. \details Class which calculates the distance of a point to a segment, for points
  81. on the ellipsoid
  82. \see C.F.F.Karney - Geodesics on an ellipsoid of revolution,
  83. https://arxiv.org/abs/1102.1215
  84. \tparam FormulaPolicy underlying point-point distance strategy
  85. \tparam Spheroid is the spheroidal model used
  86. \tparam CalculationType \tparam_calculation
  87. \tparam EnableClosestPoint computes the closest point on segment if true
  88. */
  89. template
  90. <
  91. typename FormulaPolicy = strategy::andoyer,
  92. typename Spheroid = srs::spheroid<double>,
  93. typename CalculationType = void,
  94. bool Bisection = false,
  95. bool EnableClosestPoint = false
  96. >
  97. class geographic_cross_track
  98. {
  99. public:
  100. geographic_cross_track() = default;
  101. explicit geographic_cross_track(Spheroid const& spheroid)
  102. : m_spheroid(spheroid)
  103. {}
  104. Spheroid const& model() const
  105. {
  106. return m_spheroid;
  107. }
  108. template <typename Point, typename PointOfSegment>
  109. struct return_type
  110. : promote_floating_point
  111. <
  112. typename select_calculation_type
  113. <
  114. Point,
  115. PointOfSegment,
  116. CalculationType
  117. >::type
  118. >
  119. {};
  120. template <typename Point, typename PointOfSegment>
  121. auto apply(Point const& p,
  122. PointOfSegment const& sp1,
  123. PointOfSegment const& sp2) const
  124. {
  125. return apply(get_as_radian<0>(sp1), get_as_radian<1>(sp1),
  126. get_as_radian<0>(sp2), get_as_radian<1>(sp2),
  127. get_as_radian<0>(p), get_as_radian<1>(p),
  128. m_spheroid).distance;
  129. }
  130. // points on a meridian not crossing poles
  131. template <typename CT>
  132. inline CT vertical_or_meridian(CT const& lat1, CT const& lat2) const
  133. {
  134. using meridian_inverse = typename formula::meridian_inverse
  135. <
  136. CT,
  137. strategy::default_order<FormulaPolicy>::value
  138. >;
  139. return meridian_inverse::meridian_not_crossing_pole_dist(lat1, lat2, m_spheroid);
  140. }
  141. private:
  142. template <typename CT>
  143. struct result_type
  144. {
  145. result_type()
  146. : distance(0)
  147. , lon(0)
  148. , lat(0)
  149. {}
  150. CT distance;
  151. CT lon;
  152. CT lat;
  153. };
  154. template <typename CT>
  155. static inline CT normalize(CT const& g4, CT& der)
  156. {
  157. CT const pi = math::pi<CT>();
  158. CT const c_1_5 = 1.5;
  159. if (g4 < -1.25*pi)//close to -270
  160. {
  161. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  162. std::cout << "g4=" << g4 * math::r2d<CT>() << ", close to -270" << std::endl;
  163. #endif
  164. return g4 + c_1_5 * pi;
  165. }
  166. else if (g4 > 1.25*pi)//close to 270
  167. {
  168. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  169. std::cout << "g4=" << g4 * math::r2d<CT>() << ", close to 270" << std::endl;
  170. #endif
  171. der = -der;
  172. return - g4 + c_1_5 * pi;
  173. }
  174. else if (g4 < 0 && g4 > -0.75*pi)//close to -90
  175. {
  176. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  177. std::cout << "g4=" << g4 * math::r2d<CT>() << ", close to -90" << std::endl;
  178. #endif
  179. der = -der;
  180. return -g4 - pi/2;
  181. }
  182. return g4 - pi/2;
  183. }
  184. template <typename CT>
  185. static void bisection(CT const& lon1, CT const& lat1, //p1
  186. CT const& lon2, CT const& lat2, //p2
  187. CT const& lon3, CT const& lat3, //query point p3
  188. Spheroid const& spheroid,
  189. CT const& s14_start, CT const& a12,
  190. result_type<CT>& result)
  191. {
  192. using direct_distance_type =
  193. typename FormulaPolicy::template direct<CT, true, false, false, false>;
  194. using inverse_distance_type =
  195. typename FormulaPolicy::template inverse<CT, true, false, false, false, false>;
  196. geometry::formula::result_direct<CT> res14;
  197. int counter = 0; // robustness
  198. bool dist_improve = true;
  199. CT pl_lon = lon1;
  200. CT pl_lat = lat1;
  201. CT pr_lon = lon2;
  202. CT pr_lat = lat2;
  203. CT s14 = s14_start;
  204. do {
  205. // Solve the direct problem to find p4 (GEO)
  206. res14 = direct_distance_type::apply(lon1, lat1, s14, a12, spheroid);
  207. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  208. std::cout << "dist(pl,p3)="
  209. << inverse_distance_type::apply(lon3, lat3, pr_lon, pr_lat, spheroid).distance
  210. << std::endl;
  211. std::cout << "dist(pr,p3)="
  212. << inverse_distance_type::apply(lon3, lat3, pr_lon, pr_lat, spheroid).distance
  213. << std::endl;
  214. #endif
  215. CT const dist_l =
  216. inverse_distance_type::apply(lon3, lat3, pl_lon, pl_lat, spheroid).distance;
  217. CT const dist_r =
  218. inverse_distance_type::apply(lon3, lat3, pr_lon, pr_lat, spheroid).distance;
  219. if (dist_l < dist_r)
  220. {
  221. s14 -= inverse_distance_type::apply(res14.lon2, res14.lat2, pl_lon,
  222. pl_lat, spheroid).distance/2;
  223. pr_lon = res14.lon2;
  224. pr_lat = res14.lat2;
  225. }
  226. else
  227. {
  228. s14 += inverse_distance_type::apply(res14.lon2, res14.lat2, pr_lon,
  229. pr_lat, spheroid).distance/2;
  230. pl_lon = res14.lon2;
  231. pl_lat = res14.lat2;
  232. }
  233. CT const new_distance = inverse_distance_type::apply(lon3, lat3, res14.lon2, res14.lat2,
  234. spheroid).distance;
  235. dist_improve = new_distance != result.distance;
  236. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  237. std::cout << "p4=" << res14.lon2 * math::r2d<CT>() <<
  238. "," << res14.lat2 * math::r2d<CT>() << std::endl;
  239. std::cout << "pl=" << pl_lon * math::r2d<CT>() << ","
  240. << pl_lat * math::r2d<CT>()<< std::endl;
  241. std::cout << "pr=" << pr_lon * math::r2d<CT>() << ","
  242. << pr_lat * math::r2d<CT>() << std::endl;
  243. std::cout << "new_s14=" << s14 << std::endl;
  244. std::cout << std::setprecision(16) << "result.distance ="
  245. << result.distance << std::endl;
  246. std::cout << std::setprecision(16) << "new_distance ="
  247. << new_distance << std::endl;
  248. std::cout << "---------end of step " << counter
  249. << std::endl<< std::endl;
  250. if (!dist_improve)
  251. {
  252. std::cout << "Stop msg: res34.distance >= prev_distance" << std::endl;
  253. }
  254. if (counter == BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS)
  255. {
  256. std::cout << "Stop msg: counter" << std::endl;
  257. }
  258. #endif
  259. set_result<EnableClosestPoint>::apply(new_distance, res14.lon2, res14.lat2, result);
  260. } while (dist_improve && counter++
  261. < BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS);
  262. }
  263. template <typename CT>
  264. static void newton(CT const& lon1, CT const& lat1, //p1
  265. CT const& lon2, CT const& lat2, //p2
  266. CT const& lon3, CT const& lat3, //query point p3
  267. Spheroid const& spheroid,
  268. CT const& s14_start, CT const& a12,
  269. result_type<CT>& result)
  270. {
  271. using inverse_distance_azimuth_quantities_type =
  272. typename FormulaPolicy::template inverse<CT, true, true, false, true, true>;
  273. using inverse_dist_azimuth_type =
  274. typename FormulaPolicy::template inverse<CT, false, true, false, false, false>;
  275. using direct_distance_type =
  276. typename FormulaPolicy::template direct<CT, true, false, false, false>;
  277. CT const half_pi = math::pi<CT>() / CT(2);
  278. geometry::formula::result_direct<CT> res14;
  279. geometry::formula::result_inverse<CT> res34;
  280. res34.distance = -1;
  281. int counter = 0; // robustness
  282. CT g4;
  283. CT delta_g4 = 0;
  284. bool dist_improve = true;
  285. CT s14 = s14_start;
  286. do {
  287. auto prev_distance = res34.distance;
  288. auto prev_res = res14;
  289. // Solve the direct problem to find p4 (GEO)
  290. res14 = direct_distance_type::apply(lon1, lat1, s14, a12, spheroid);
  291. // Solve an inverse problem to find g4
  292. // g4 is the angle between segment (p1,p2) and segment (p3,p4)
  293. // that meet on p4 (GEO)
  294. CT const a4 = inverse_dist_azimuth_type::apply(res14.lon2, res14.lat2,
  295. lon2, lat2, spheroid).azimuth;
  296. res34 = inverse_distance_azimuth_quantities_type::apply(res14.lon2, res14.lat2,
  297. lon3, lat3, spheroid);
  298. g4 = res34.azimuth - a4;
  299. // cos(s14/earth_radius) is the spherical limit
  300. CT M43 = res34.geodesic_scale;
  301. CT m34 = res34.reduced_length;
  302. if (m34 != 0)
  303. {
  304. CT der = (M43 / m34) * sin(g4);
  305. delta_g4 = normalize(g4, der);
  306. s14 -= der != 0 ? delta_g4 / der : 0;
  307. }
  308. dist_improve = prev_distance > res34.distance || prev_distance == -1;
  309. if (dist_improve)
  310. {
  311. set_result<EnableClosestPoint>::apply(res34.distance, res14.lon2, res14.lat2,
  312. result);
  313. }
  314. else
  315. {
  316. set_result<EnableClosestPoint>::apply(prev_distance, prev_res.lon2, prev_res.lat2,
  317. result);
  318. }
  319. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  320. std::cout << "p4=" << res14.lon2 * math::r2d<CT>() <<
  321. "," << res14.lat2 * math::r2d<CT>() << std::endl;
  322. std::cout << "a34=" << res34.azimuth * math::r2d<CT>() << std::endl;
  323. std::cout << "a4=" << a4 * math::r2d<CT>() << std::endl;
  324. std::cout << "g4(normalized)=" << g4 * math::r2d<CT>() << std::endl;
  325. std::cout << "delta_g4=" << delta_g4 * math::r2d<CT>() << std::endl;
  326. std::cout << "M43=" << M43 << std::endl;
  327. std::cout << "m34=" << m34 << std::endl;
  328. std::cout << "new_s14=" << s14 << std::endl;
  329. std::cout << std::setprecision(16) << "dist ="
  330. << res34.distance << std::endl;
  331. std::cout << "---------end of step " << counter
  332. << std::endl<< std::endl;
  333. if (g4 == half_pi)
  334. {
  335. std::cout << "Stop msg: g4 == half_pi" << std::endl;
  336. }
  337. if (!dist_improve)
  338. {
  339. std::cout << "Stop msg: res34.distance >= prev_distance" << std::endl;
  340. }
  341. if (delta_g4 == 0)
  342. {
  343. std::cout << "Stop msg: delta_g4 == 0" << std::endl;
  344. }
  345. if (counter == BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS)
  346. {
  347. std::cout << "Stop msg: counter" << std::endl;
  348. }
  349. #endif
  350. } while (g4 != half_pi
  351. && dist_improve
  352. && delta_g4 != 0
  353. && counter++ < BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS);
  354. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  355. std::cout << "distance=" << res34.distance << std::endl;
  356. std::cout << "s34(geo) ="
  357. << inverse_distance_azimuth_quantities_type
  358. ::apply(res14.lon2, res14.lat2,
  359. lon3, lat3, spheroid).distance
  360. << ", p4=(" << res14.lon2 * math::r2d<CT>() << ","
  361. << res14.lat2 * math::r2d<CT>() << ")"
  362. << std::endl;
  363. CT s31 = inverse_distance_azimuth_quantities_type::apply(lon3, lat3, lon1, lat1, spheroid)
  364. .distance;
  365. CT s32 = inverse_distance_azimuth_quantities_type::apply(lon3, lat3, lon2, lat2, spheroid)
  366. .distance;
  367. CT a4 = inverse_dist_azimuth_type::apply(res14.lon2, res14.lat2, lon2, lat2, spheroid)
  368. .azimuth;
  369. geometry::formula::result_direct<CT> res4 =
  370. direct_distance_type::apply(res14.lon2, res14.lat2, .04, a4, spheroid);
  371. CT p4_plus = inverse_distance_azimuth_quantities_type::apply(res4.lon2, res4.lat2, lon3,
  372. lat3, spheroid).distance;
  373. geometry::formula::result_direct<CT> res1 =
  374. direct_distance_type::apply(lon1, lat1, s14-.04, a12, spheroid);
  375. CT p4_minus = inverse_distance_azimuth_quantities_type::apply(res1.lon2, res1.lat2, lon3,
  376. lat3, spheroid).distance;
  377. std::cout << "s31=" << s31 << "\ns32=" << s32
  378. << "\np4_plus=" << p4_plus << ", p4=("
  379. << res4.lon2 * math::r2d<CT>() << ","
  380. << res4.lat2 * math::r2d<CT>() << ")"
  381. << "\np4_minus=" << p4_minus << ", p4=("
  382. << res1.lon2 * math::r2d<CT>() << ","
  383. << res1.lat2 * math::r2d<CT>() << ")"
  384. << std::endl;
  385. if (res34.distance <= p4_plus && res34.distance <= p4_minus)
  386. {
  387. std::cout << "Closest point computed" << std::endl;
  388. }
  389. else
  390. {
  391. std::cout << "There is a closer point nearby" << std::endl;
  392. }
  393. #endif
  394. }
  395. template <typename CT>
  396. static inline auto non_iterative_case(CT const& , CT const& , //p1
  397. CT const& lon2, CT const& lat2, //p2
  398. CT const& distance)
  399. {
  400. result_type<CT> result;
  401. set_result<EnableClosestPoint>::apply(distance, lon2, lat2, result);
  402. return result;
  403. }
  404. template <typename CT>
  405. static inline auto non_iterative_case(CT const& lon1, CT const& lat1, //p1
  406. CT const& lon2, CT const& lat2, //p2
  407. Spheroid const& spheroid)
  408. {
  409. CT distance = geometry::strategy::distance::geographic
  410. <
  411. FormulaPolicy,
  412. Spheroid,
  413. CT
  414. >::apply(lon1, lat1, lon2, lat2, spheroid);
  415. return non_iterative_case(lon1, lat1, lon2, lat2, distance);
  416. }
  417. protected:
  418. template <typename CT>
  419. static inline auto apply(CT const& lo1, CT const& la1, //p1
  420. CT const& lo2, CT const& la2, //p2
  421. CT const& lo3, CT const& la3, //query point p3
  422. Spheroid const& spheroid)
  423. {
  424. using inverse_dist_azimuth_type =
  425. typename FormulaPolicy::template inverse<CT, true, true, false, false, false>;
  426. using inverse_dist_azimuth_reverse_type =
  427. typename FormulaPolicy::template inverse<CT, true, true, true, false, false>;
  428. CT const earth_radius = geometry::formula::mean_radius<CT>(spheroid);
  429. result_type<CT> result;
  430. // if the query points coincide with one of segments' endpoints
  431. if ((lo1 == lo3 && la1 == la3) || (lo2 == lo3 && la2 == la3))
  432. {
  433. result.lon = lo3;
  434. result.lat = la3;
  435. return result;
  436. }
  437. // Constants
  438. //CT const f = geometry::formula::flattening<CT>(spheroid);
  439. CT const pi = math::pi<CT>();
  440. CT const half_pi = pi / CT(2);
  441. CT const c0 = CT(0);
  442. CT lon1 = lo1;
  443. CT lat1 = la1;
  444. CT lon2 = lo2;
  445. CT lat2 = la2;
  446. CT const lon3 = lo3;
  447. CT const lat3 = la3;
  448. if (lon1 > lon2)
  449. {
  450. std::swap(lon1, lon2);
  451. std::swap(lat1, lat2);
  452. }
  453. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  454. std::cout << ">>\nSegment=(" << lon1 * math::r2d<CT>();
  455. std::cout << "," << lat1 * math::r2d<CT>();
  456. std::cout << "),(" << lon2 * math::r2d<CT>();
  457. std::cout << "," << lat2 * math::r2d<CT>();
  458. std::cout << ")\np=(" << lon3 * math::r2d<CT>();
  459. std::cout << "," << lat3 * math::r2d<CT>();
  460. std::cout << ")" << std::endl;
  461. #endif
  462. //segment on equator
  463. //Note: antipodal points on equator does not define segment on equator
  464. //but pass by the pole
  465. CT const diff = geometry::math::longitude_distance_signed<geometry::radian>(lon1, lon2);
  466. using meridian_inverse = typename formula::meridian_inverse<CT>;
  467. bool const meridian_not_crossing_pole =
  468. meridian_inverse::meridian_not_crossing_pole(lat1, lat2, diff);
  469. bool const meridian_crossing_pole =
  470. meridian_inverse::meridian_crossing_pole(diff);
  471. if (math::equals(lat1, c0) && math::equals(lat2, c0)
  472. && !meridian_crossing_pole)
  473. {
  474. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  475. std::cout << "Equatorial segment" << std::endl;
  476. std::cout << "segment=(" << lon1 * math::r2d<CT>();
  477. std::cout << "," << lat1 * math::r2d<CT>();
  478. std::cout << "),(" << lon2 * math::r2d<CT>();
  479. std::cout << "," << lat2 * math::r2d<CT>();
  480. std::cout << ")\np=(" << lon3 * math::r2d<CT>();
  481. std::cout << "," << lat3 * math::r2d<CT>() << ")\n";
  482. #endif
  483. if (lon3 <= lon1)
  484. {
  485. return non_iterative_case(lon3, lat3, lon1, lat1, spheroid);
  486. }
  487. if (lon3 >= lon2)
  488. {
  489. return non_iterative_case(lon3, lat3, lon2, lat2, spheroid);
  490. }
  491. return non_iterative_case(lon3, lat3, lon3, lat1, spheroid);
  492. }
  493. if ( (meridian_not_crossing_pole || meridian_crossing_pole )
  494. && std::abs(lat1) > std::abs(lat2))
  495. {
  496. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  497. std::cout << "Meridian segment not crossing pole" << std::endl;
  498. #endif
  499. std::swap(lat1,lat2);
  500. }
  501. if (meridian_crossing_pole)
  502. {
  503. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  504. std::cout << "Meridian segment crossing pole" << std::endl;
  505. #endif
  506. CT const sign_non_zero = lat3 >= c0 ? 1 : -1;
  507. auto const res13 = apply(lon1, lat1, lon1, half_pi * sign_non_zero, lon3, lat3, spheroid);
  508. auto const res23 = apply(lon2, lat2, lon2, half_pi * sign_non_zero, lon3, lat3, spheroid);
  509. return (res13.distance) < (res23.distance) ? res13 : res23;
  510. }
  511. auto const res12 = inverse_dist_azimuth_reverse_type::apply(lon1, lat1, lon2, lat2, spheroid);
  512. auto const res13 = inverse_dist_azimuth_type::apply(lon1, lat1, lon3, lat3, spheroid);
  513. if (geometry::math::equals(res12.distance, c0))
  514. {
  515. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  516. std::cout << "Degenerate segment" << std::endl;
  517. std::cout << "distance between points="
  518. << res13.distance << std::endl;
  519. #endif
  520. auto const res = meridian_inverse::apply(lon1, lat1, lon3, lat3, spheroid);
  521. return non_iterative_case(lon3, lat3, lon1, lat2,
  522. res.meridian ? res.distance : res13.distance);
  523. }
  524. // Compute a12 (GEO)
  525. CT const a312 = res13.azimuth - res12.azimuth;
  526. // TODO: meridian case optimization
  527. if (geometry::math::equals(a312, c0) && meridian_not_crossing_pole)
  528. {
  529. auto const minmax_elem = std::minmax(lat1, lat2);
  530. if (lat3 >= std::get<0>(minmax_elem) &&
  531. lat3 <= std::get<1>(minmax_elem))
  532. {
  533. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  534. std::cout << "Point on meridian segment" << std::endl;
  535. #endif
  536. return non_iterative_case(lon3, lat3, lon3, lat3, c0);
  537. }
  538. }
  539. CT const projection1 = cos( a312 ) * res13.distance / res12.distance;
  540. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  541. std::cout << "a1=" << res12.azimuth * math::r2d<CT>() << std::endl;
  542. std::cout << "a13=" << res13.azimuth * math::r2d<CT>() << std::endl;
  543. std::cout << "a312=" << a312 * math::r2d<CT>() << std::endl;
  544. std::cout << "cos(a312)=" << cos(a312) << std::endl;
  545. std::cout << "projection 1=" << projection1 << std::endl;
  546. #endif
  547. if (projection1 < c0)
  548. {
  549. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  550. std::cout << "projection closer to p1" << std::endl;
  551. #endif
  552. // projection of p3 on geodesic spanned by segment (p1,p2) fall
  553. // outside of segment on the side of p1
  554. return non_iterative_case(lon3, lat3, lon1, lat1, spheroid);
  555. }
  556. auto const res23 = inverse_dist_azimuth_type::apply(lon2, lat2, lon3, lat3, spheroid);
  557. CT const a321 = res23.azimuth - res12.reverse_azimuth + pi;
  558. CT const projection2 = cos( a321 ) * res23.distance / res12.distance;
  559. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  560. std::cout << "a21=" << res12.reverse_azimuth * math::r2d<CT>()
  561. << std::endl;
  562. std::cout << "a23=" << res23.azimuth * math::r2d<CT>() << std::endl;
  563. std::cout << "a321=" << a321 * math::r2d<CT>() << std::endl;
  564. std::cout << "cos(a321)=" << cos(a321) << std::endl;
  565. std::cout << "projection 2=" << projection2 << std::endl;
  566. #endif
  567. if (projection2 < c0)
  568. {
  569. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  570. std::cout << "projection closer to p2" << std::endl;
  571. #endif
  572. // projection of p3 on geodesic spanned by segment (p1,p2) fall
  573. // outside of segment on the side of p2
  574. return non_iterative_case(lon3, lat3, lon2, lat2, spheroid);
  575. }
  576. // Guess s14 (SPHERICAL) aka along-track distance
  577. using point = geometry::model::point
  578. <
  579. CT,
  580. 2,
  581. geometry::cs::spherical_equatorial<geometry::radian>
  582. >;
  583. point const p1(lon1, lat1);
  584. point const p2(lon2, lat2);
  585. point const p3(lon3, lat3);
  586. using haversine_t = geometry::strategy::distance::haversine<CT>;
  587. using cross_track_t = geometry::strategy::distance::cross_track<void, haversine_t>;
  588. cross_track_t const cross_track(earth_radius);
  589. CT const s34_sph = cross_track.apply(p3, p1, p2);
  590. haversine_t const str(earth_radius);
  591. CT const s13_sph = str.apply(p1, p3);
  592. //CT s14 = acos( cos(s13/earth_radius) / cos(s34/earth_radius) ) * earth_radius;
  593. CT const cos_frac = cos(s13_sph / earth_radius) / cos(s34_sph / earth_radius);
  594. CT const s14_sph = cos_frac >= 1 ? CT(0)
  595. : cos_frac <= -1 ? pi * earth_radius
  596. : acos(cos_frac) * earth_radius;
  597. CT const a12_sph = geometry::formula::spherical_azimuth<>(lon1, lat1, lon2, lat2);
  598. auto const res = geometry::formula::spherical_direct<true, false>(lon1, lat1,
  599. s14_sph, a12_sph, srs::sphere<CT>(earth_radius));
  600. // this is what postgis (version 2.5) returns
  601. // geometry::strategy::distance::geographic<FormulaPolicy, Spheroid, CT>
  602. // ::apply(lon3, lat3, res.lon2, res.lat2, spheroid);
  603. #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK
  604. std::cout << "s34=" << s34_sph << std::endl;
  605. std::cout << "s13=" << res13.distance << std::endl;
  606. std::cout << "s14=" << s14_sph << std::endl;
  607. std::cout << "===============" << std::endl;
  608. #endif
  609. // Update s14 (using Newton method)
  610. if (Bisection)
  611. {
  612. bisection(lon1, lat1, lon2, lat2, lon3, lat3, spheroid,
  613. res12.distance/2, res12.azimuth, result);
  614. }
  615. else
  616. {
  617. CT const s14_start = geometry::strategy::distance::geographic
  618. <
  619. FormulaPolicy,
  620. Spheroid,
  621. CT
  622. >::apply(lon1, lat1, res.lon2, res.lat2, spheroid);
  623. newton(lon1, lat1, lon2, lat2, lon3, lat3, spheroid, s14_start, res12.azimuth, result);
  624. }
  625. return result;
  626. }
  627. Spheroid m_spheroid;
  628. };
  629. } // namespace detail
  630. template
  631. <
  632. typename FormulaPolicy = strategy::andoyer,
  633. typename Spheroid = srs::spheroid<double>,
  634. typename CalculationType = void
  635. >
  636. class geographic_cross_track
  637. : public detail::geographic_cross_track
  638. <
  639. FormulaPolicy,
  640. Spheroid,
  641. CalculationType,
  642. false,
  643. false
  644. >
  645. {
  646. using base_t = detail::geographic_cross_track
  647. <
  648. FormulaPolicy,
  649. Spheroid,
  650. CalculationType,
  651. false,
  652. false
  653. >;
  654. public :
  655. explicit geographic_cross_track(Spheroid const& spheroid = Spheroid())
  656. : base_t(spheroid)
  657. {}
  658. };
  659. #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  660. namespace services
  661. {
  662. //tags
  663. template <typename FormulaPolicy>
  664. struct tag<geographic_cross_track<FormulaPolicy> >
  665. {
  666. typedef strategy_tag_distance_point_segment type;
  667. };
  668. template
  669. <
  670. typename FormulaPolicy,
  671. typename Spheroid
  672. >
  673. struct tag<geographic_cross_track<FormulaPolicy, Spheroid> >
  674. {
  675. typedef strategy_tag_distance_point_segment type;
  676. };
  677. template
  678. <
  679. typename FormulaPolicy,
  680. typename Spheroid,
  681. typename CalculationType
  682. >
  683. struct tag<geographic_cross_track<FormulaPolicy, Spheroid, CalculationType> >
  684. {
  685. typedef strategy_tag_distance_point_segment type;
  686. };
  687. template
  688. <
  689. typename FormulaPolicy,
  690. typename Spheroid,
  691. typename CalculationType,
  692. bool Bisection,
  693. bool EnableClosestPoint
  694. >
  695. struct tag<detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint> >
  696. {
  697. typedef strategy_tag_distance_point_segment type;
  698. };
  699. //return types
  700. template <typename FormulaPolicy, typename P, typename PS>
  701. struct return_type<geographic_cross_track<FormulaPolicy>, P, PS>
  702. : geographic_cross_track<FormulaPolicy>::template return_type<P, PS>
  703. {};
  704. template
  705. <
  706. typename FormulaPolicy,
  707. typename Spheroid,
  708. typename P,
  709. typename PS
  710. >
  711. struct return_type<geographic_cross_track<FormulaPolicy, Spheroid>, P, PS>
  712. : geographic_cross_track<FormulaPolicy, Spheroid>::template return_type<P, PS>
  713. {};
  714. template
  715. <
  716. typename FormulaPolicy,
  717. typename Spheroid,
  718. typename CalculationType,
  719. typename P,
  720. typename PS
  721. >
  722. struct return_type<geographic_cross_track<FormulaPolicy, Spheroid, CalculationType>, P, PS>
  723. : geographic_cross_track<FormulaPolicy, Spheroid, CalculationType>::template return_type<P, PS>
  724. {};
  725. template
  726. <
  727. typename FormulaPolicy,
  728. typename Spheroid,
  729. typename CalculationType,
  730. bool Bisection,
  731. bool EnableClosestPoint,
  732. typename P,
  733. typename PS
  734. >
  735. struct return_type<detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint>, P, PS>
  736. : detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint>::template return_type<P, PS>
  737. {};
  738. //comparable types
  739. template
  740. <
  741. typename FormulaPolicy,
  742. typename Spheroid,
  743. typename CalculationType
  744. >
  745. struct comparable_type<geographic_cross_track<FormulaPolicy, Spheroid, CalculationType> >
  746. {
  747. typedef geographic_cross_track
  748. <
  749. FormulaPolicy, Spheroid, CalculationType
  750. > type;
  751. };
  752. template
  753. <
  754. typename FormulaPolicy,
  755. typename Spheroid,
  756. typename CalculationType,
  757. bool Bisection,
  758. bool EnableClosestPoint
  759. >
  760. struct comparable_type<detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint> >
  761. {
  762. typedef detail::geographic_cross_track
  763. <
  764. FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint
  765. > type;
  766. };
  767. template
  768. <
  769. typename FormulaPolicy,
  770. typename Spheroid,
  771. typename CalculationType
  772. >
  773. struct get_comparable<geographic_cross_track<FormulaPolicy, Spheroid, CalculationType> >
  774. {
  775. public :
  776. static inline geographic_cross_track<FormulaPolicy, Spheroid, CalculationType>
  777. apply(geographic_cross_track<FormulaPolicy, Spheroid, CalculationType> const& strategy)
  778. {
  779. return strategy;
  780. }
  781. };
  782. template
  783. <
  784. typename FormulaPolicy,
  785. typename Spheroid,
  786. typename CalculationType,
  787. bool Bisection,
  788. bool EnableClosestPoint
  789. >
  790. struct get_comparable<detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint> >
  791. {
  792. public :
  793. static inline detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint>
  794. apply(detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint> const& strategy)
  795. {
  796. return strategy;
  797. }
  798. };
  799. template
  800. <
  801. typename FormulaPolicy,
  802. typename Spheroid,
  803. typename CalculationType,
  804. typename P,
  805. typename PS
  806. >
  807. struct result_from_distance<geographic_cross_track<FormulaPolicy, Spheroid, CalculationType>, P, PS>
  808. {
  809. private :
  810. typedef typename geographic_cross_track
  811. <
  812. FormulaPolicy, Spheroid, CalculationType
  813. >::template return_type<P, PS>::type return_type;
  814. public :
  815. template <typename T>
  816. static inline return_type
  817. apply(geographic_cross_track<FormulaPolicy, Spheroid, CalculationType> const& , T const& distance)
  818. {
  819. return distance;
  820. }
  821. };
  822. template
  823. <
  824. typename FormulaPolicy,
  825. typename Spheroid,
  826. typename CalculationType,
  827. bool Bisection,
  828. bool EnableClosestPoint,
  829. typename P,
  830. typename PS
  831. >
  832. struct result_from_distance<detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint>, P, PS>
  833. {
  834. private :
  835. typedef typename detail::geographic_cross_track
  836. <
  837. FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint
  838. >::template return_type<P, PS>::type return_type;
  839. public :
  840. template <typename T>
  841. static inline return_type
  842. apply(detail::geographic_cross_track<FormulaPolicy, Spheroid, CalculationType, Bisection, EnableClosestPoint> const& , T const& distance)
  843. {
  844. return distance;
  845. }
  846. };
  847. template <typename Point, typename PointOfSegment>
  848. struct default_strategy
  849. <
  850. point_tag, segment_tag, Point, PointOfSegment,
  851. geographic_tag, geographic_tag
  852. >
  853. {
  854. typedef geographic_cross_track<> type;
  855. };
  856. template <typename PointOfSegment, typename Point>
  857. struct default_strategy
  858. <
  859. segment_tag, point_tag, PointOfSegment, Point,
  860. geographic_tag, geographic_tag
  861. >
  862. {
  863. typedef typename default_strategy
  864. <
  865. point_tag, segment_tag, Point, PointOfSegment,
  866. geographic_tag, geographic_tag
  867. >::type type;
  868. };
  869. } // namespace services
  870. #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
  871. }} // namespace strategy::distance
  872. }} // namespace boost::geometry
  873. #endif // BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP