1 // Boost.Geometry (aka GGL, Generic Geometry Library)
3 // Copyright (c) 2007-2014 Barend Gehrels, Amsterdam, the Netherlands.
5 // This file was modified by Oracle on 2014.
6 // Modifications copyright (c) 2014, Oracle and/or its affiliates.
8 // Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
10 // Use, modification and distribution is subject to the Boost Software License,
11 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
12 // http://www.boost.org/LICENSE_1_0.txt)
14 #ifndef BOOST_GEOMETRY_STRATEGIES_SPHERICAL_DISTANCE_CROSS_TRACK_HPP
15 #define BOOST_GEOMETRY_STRATEGIES_SPHERICAL_DISTANCE_CROSS_TRACK_HPP
19 #include <boost/config.hpp>
20 #include <boost/concept_check.hpp>
21 #include <boost/mpl/if.hpp>
22 #include <boost/type_traits/is_void.hpp>
24 #include <boost/geometry/core/cs.hpp>
25 #include <boost/geometry/core/access.hpp>
26 #include <boost/geometry/core/radian_access.hpp>
27 #include <boost/geometry/core/tags.hpp>
29 #include <boost/geometry/algorithms/detail/course.hpp>
31 #include <boost/geometry/strategies/distance.hpp>
32 #include <boost/geometry/strategies/concepts/distance_concept.hpp>
33 #include <boost/geometry/strategies/spherical/distance_haversine.hpp>
35 #include <boost/geometry/util/math.hpp>
36 #include <boost/geometry/util/promote_floating_point.hpp>
37 #include <boost/geometry/util/select_calculation_type.hpp>
39 #ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
40 # include <boost/geometry/io/dsv/write.hpp>
44 namespace boost { namespace geometry
47 namespace strategy { namespace distance
55 Given a spherical segment AB and a point D, we are interested in
56 computing the distance of D from AB. This is usually known as the
59 If the projection (along great circles) of the point D lies inside
60 the segment AB, then the distance (cross track error) XTD is given
61 by the formula (see http://williams.best.vwh.net/avform.htm#XTE):
63 XTD = asin( sin(dist_AD) * sin(crs_AD-crs_AB) )
65 where dist_AD is the great circle distance between the points A and
66 B, and crs_AD, crs_AB is the course (bearing) between the points A,
67 D and A, B, respectively.
69 If the point D does not project inside the arc AB, then the distance
70 of D from AB is the minimum of the two distances dist_AD and dist_BD.
72 Our reference implementation for this procedure is listed below
73 (this was the old Boost.Geometry implementation of the cross track distance),
75 * The member variable m_strategy is the underlying haversine strategy.
76 * p stands for the point D.
77 * sp1 stands for the segment endpoint A.
78 * sp2 stands for the segment endpoint B.
80 ================= reference implementation -- start =================
82 return_type d1 = m_strategy.apply(sp1, p);
83 return_type d3 = m_strategy.apply(sp1, sp2);
85 if (geometry::math::equals(d3, 0.0))
87 // "Degenerate" segment, return either d1 or d2
91 return_type d2 = m_strategy.apply(sp2, p);
93 return_type crs_AD = geometry::detail::course<return_type>(sp1, p);
94 return_type crs_AB = geometry::detail::course<return_type>(sp1, sp2);
95 return_type crs_BA = crs_AB - geometry::math::pi<return_type>();
96 return_type crs_BD = geometry::detail::course<return_type>(sp2, p);
97 return_type d_crs1 = crs_AD - crs_AB;
98 return_type d_crs2 = crs_BD - crs_BA;
100 // d1, d2, d3 are in principle not needed, only the sign matters
101 return_type projection1 = cos( d_crs1 ) * d1 / d3;
102 return_type projection2 = cos( d_crs2 ) * d2 / d3;
104 if (projection1 > 0.0 && projection2 > 0.0)
107 = radius() * math::abs( asin( sin( d1 / radius() ) * sin( d_crs1 ) ));
109 // Return shortest distance, projected point on segment sp1-sp2
110 return return_type(XTD);
114 // Return shortest distance, project either on point sp1 or sp2
115 return return_type( (std::min)( d1 , d2 ) );
118 ================= reference implementation -- end =================
123 In what follows we develop a comparable version of the cross track
124 distance strategy, that meets the following goals:
125 * It is more efficient than the original cross track strategy (less
126 operations and less calls to mathematical functions).
127 * Distances using the comparable cross track strategy can not only
128 be compared with other distances using the same strategy, but also with
129 distances computed with the comparable version of the haversine strategy.
130 * It can serve as the basis for the computation of the cross track distance,
131 as it is more efficient to compute its comparable version and
132 transform that to the actual cross track distance, rather than
133 follow/use the reference implementation listed above.
137 The idea here is to use the comparable haversine strategy to compute
138 the distances d1, d2 and d3 in the above listing. Once we have done
139 that we need also to make sure that instead of returning XTD (as
140 computed above) that we return a distance CXTD that is compatible
141 with the comparable haversine distance. To achieve this CXTD must satisfy
143 XTD = 2 * R * asin( sqrt(XTD) )
144 where R is the sphere's radius.
146 Below we perform the mathematical analysis that show how to compute CXTD.
149 Mathematical analysis
150 ---------------------
151 Below we use the following trigonometric identities:
152 sin(2 * x) = 2 * sin(x) * cos(x)
153 cos(asin(x)) = sqrt(1 - x^2)
156 The distance d1 needed when the projection of the point D is within the
157 segment must be the true distance. However, comparable::haversine<>
158 returns a comparable distance instead of the one needed.
159 To remedy this, we implicitly compute what is needed.
160 More precisely, we need to compute sin(true_d1):
162 sin(true_d1) = sin(2 * asin(sqrt(d1)))
163 = 2 * sin(asin(sqrt(d1)) * cos(asin(sqrt(d1)))
164 = 2 * sqrt(d1) * sqrt(1-(sqrt(d1))^2)
165 = 2 * sqrt(d1 - d1 * d1)
166 This relation is used below.
168 As we mentioned above the goal is to find CXTD (named "a" below for
169 brevity) such that ("b" below stands for "d1", and "c" for "d_crs1"):
171 2 * R * asin(sqrt(a)) == R * asin(2 * sqrt(b-b^2) * sin(c))
174 2 * R * asin(sqrt(a)) == R * asin(2 * sqrt(b-b^2) * sin(c))
175 <=> 2 * asin(sqrt(a)) == asin(sqrt(b-b^2) * sin(c))
176 <=> sin(2 * asin(sqrt(a))) == 2 * sqrt(b-b^2) * sin(c)
177 <=> 2 * sin(asin(sqrt(a))) * cos(asin(sqrt(a))) == 2 * sqrt(b-b^2) * sin(c)
178 <=> 2 * sqrt(a) * sqrt(1-a) == 2 * sqrt(b-b^2) * sin(c)
179 <=> sqrt(a) * sqrt(1-a) == sqrt(b-b^2) * sin(c)
180 <=> sqrt(a-a^2) == sqrt(b-b^2) * sin(c)
181 <=> a-a^2 == (b-b^2) * (sin(c))^2
183 Consider the quadratic equation: x^2-x+p^2 == 0,
184 where p = sqrt(b-b^2) * sin(c); its discriminant is:
185 d = 1 - 4 * p^2 = 1 - 4 * (b-b^2) * (sin(c))^2
187 The two solutions are:
188 a_1 = (1 - sqrt(d)) / 2
189 a_2 = (1 + sqrt(d)) / 2
192 "a" refers to the distance (on the unit sphere) of D from the
193 supporting great circle Circ(A,B) of the segment AB.
194 The two different values for "a" correspond to the lengths of the two
195 arcs delimited D and the points of intersection of Circ(A,B) and the
196 great circle perperdicular to Circ(A,B) passing through D.
197 Clearly, the value we want is the smallest among these two distances,
198 hence the root we must choose is the smallest root among the two.
201 CXTD = ( 1 - sqrt(1 - 4 * (b-b^2) * (sin(c))^2) ) / 2
203 Therefore, in order to implement the comparable version of the cross
204 track strategy we need to:
205 (1) Use the comparable version of the haversine strategy instead of
206 the non-comparable one.
207 (2) Instead of return XTD when D projects inside the segment AB, we
208 need to return CXTD, given by the following formula:
209 CXTD = ( 1 - sqrt(1 - 4 * (d1-d1^2) * (sin(d_crs1))^2) ) / 2;
214 In the analysis that follows we refer to the actual implementation below.
215 In particular, instead of computing CXTD as above, we use the more
216 efficient (operation-wise) computation of CXTD shown here:
218 return_type sin_d_crs1 = sin(d_crs1);
219 return_type d1_x_sin = d1 * sin_d_crs1;
220 return_type d = d1_x_sin * (sin_d_crs1 - d1_x_sin);
221 return d / (0.5 + math::sqrt(0.25 - d));
223 Notice that instead of computing:
224 0.5 - 0.5 * sqrt(1 - 4 * d) = 0.5 - sqrt(0.25 - d)
225 we use the following formula instead:
226 d / (0.5 + sqrt(0.25 - d)).
227 This is done for numerical robustness. The expression 0.5 - sqrt(0.25 - x)
228 has large numerical errors for values of x close to 0 (if using doubles
229 the error start to become large even when d is as large as 0.001).
230 To remedy that, we re-write 0.5 - sqrt(0.25 - x) as:
232 = (0.5 - sqrt(0.25 - d) * (0.5 - sqrt(0.25 - d)) / (0.5 + sqrt(0.25 - d)).
233 The numerator is the difference of two squares:
234 (0.5 - sqrt(0.25 - d) * (0.5 - sqrt(0.25 - d))
235 = 0.5^2 - (sqrt(0.25 - d))^ = 0.25 - (0.25 - d) = d,
236 which gives the expression we use.
238 For the complexity analysis, we distinguish between two cases:
239 (A) The distance is realized between the point D and an
240 endpoint of the segment AB
243 Since we are using comparable::haversine<> which is called
252 -> 6 function calls (sqrt/asin)
253 -> 6 arithmetic operations
255 If we use comparable::cross_track<> to compute
256 cross_track<> we need to account for a call to sqrt, a call
257 to asin and 2 multiplications. In this case the net gain is:
258 -> 4 function calls (sqrt/asin)
259 -> 4 arithmetic operations
262 (B) The distance is realized between the point D and an
263 interior point of the segment AB
266 Since we are using comparable::haversine<> which is called
271 Also we gain the operations used to compute XTD:
277 So the total gains are:
278 -> 9 calls to sqrt/sin/asin
284 To compute a distance compatible with comparable::haversine<>
285 we need to perform a few more operations, namely:
293 So roughly speaking the net gain is:
294 -> 8 fewer function calls and 3 fewer arithmetic operations
296 If we were to implement cross_track directly from the
297 comparable version (much like what haversine<> does using
298 comparable::haversine<>) we need additionally
299 -> 2 function calls (asin/sqrt)
302 So it pays off to re-implement cross_track<> to use
303 comparable::cross_track<>; in this case the net gain would be:
305 -> 1 arithmetic operation
309 Following the mathematical and complexity analysis above, the
310 comparable cross track strategy (as implemented below) satisfies
311 all the goal mentioned in the beginning:
312 * It is more efficient than its non-comparable counter-part.
313 * Comparable distances using this new strategy can also be compared
314 with comparable distances computed with the comparable haversine
316 * It turns out to be more efficient to compute the actual cross
317 track distance XTD by first computing CXTD, and then computing
318 XTD by means of the formula:
319 XTD = 2 * R * asin( sqrt(CXTD) )
324 typename CalculationType = void,
325 typename Strategy = comparable::haversine<double, CalculationType>
330 template <typename Point, typename PointOfSegment>
332 : promote_floating_point
334 typename select_calculation_type
343 typedef typename Strategy::radius_type radius_type;
348 explicit inline cross_track(typename Strategy::radius_type const& r)
352 inline cross_track(Strategy const& s)
357 // It might be useful in the future
358 // to overload constructor with strategy info.
359 // crosstrack(...) {}
362 template <typename Point, typename PointOfSegment>
363 inline typename return_type<Point, PointOfSegment>::type
364 apply(Point const& p, PointOfSegment const& sp1, PointOfSegment const& sp2) const
367 #if !defined(BOOST_MSVC)
370 (concepts::PointDistanceStrategy<Strategy, Point, PointOfSegment>)
374 typedef typename return_type<Point, PointOfSegment>::type return_type;
376 #ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
377 std::cout << "Course " << dsv(sp1) << " to " << dsv(p) << " "
378 << crs_AD * geometry::math::r2d<return_type>() << std::endl;
379 std::cout << "Course " << dsv(sp1) << " to " << dsv(sp2) << " "
380 << crs_AB * geometry::math::r2d<return_type>() << std::endl;
381 std::cout << "Course " << dsv(sp2) << " to " << dsv(p) << " "
382 << crs_BD * geometry::math::r2d << std::endl;
383 std::cout << "Projection AD-AB " << projection1 << " : "
384 << d_crs1 * geometry::math::r2d<return_type>() << std::endl;
385 std::cout << "Projection BD-BA " << projection2 << " : "
386 << d_crs2 * geometry::math::r2d<return_type>() << std::endl;
389 // http://williams.best.vwh.net/avform.htm#XTE
390 return_type d1 = m_strategy.apply(sp1, p);
391 return_type d3 = m_strategy.apply(sp1, sp2);
393 if (geometry::math::equals(d3, 0.0))
395 // "Degenerate" segment, return either d1 or d2
399 return_type d2 = m_strategy.apply(sp2, p);
401 return_type crs_AD = geometry::detail::course<return_type>(sp1, p);
402 return_type crs_AB = geometry::detail::course<return_type>(sp1, sp2);
403 return_type crs_BA = crs_AB - geometry::math::pi<return_type>();
404 return_type crs_BD = geometry::detail::course<return_type>(sp2, p);
405 return_type d_crs1 = crs_AD - crs_AB;
406 return_type d_crs2 = crs_BD - crs_BA;
408 // d1, d2, d3 are in principle not needed, only the sign matters
409 return_type projection1 = cos( d_crs1 ) * d1 / d3;
410 return_type projection2 = cos( d_crs2 ) * d2 / d3;
412 if (projection1 > 0.0 && projection2 > 0.0)
414 #ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
415 return_type XTD = radius() * geometry::math::abs( asin( sin( d1 ) * sin( d_crs1 ) ));
417 std::cout << "Projection ON the segment" << std::endl;
418 std::cout << "XTD: " << XTD
419 << " d1: " << (d1 * radius())
420 << " d2: " << (d2 * radius())
423 return_type const half(0.5);
424 return_type const quarter(0.25);
426 return_type sin_d_crs1 = sin(d_crs1);
428 This is the straightforward obvious way to continue:
430 return_type discriminant
431 = 1.0 - 4.0 * (d1 - d1 * d1) * sin_d_crs1 * sin_d_crs1;
432 return 0.5 - 0.5 * math::sqrt(discriminant);
434 Below we optimize the number of arithmetic operations
435 and account for numerical robustness:
437 return_type d1_x_sin = d1 * sin_d_crs1;
438 return_type d = d1_x_sin * (sin_d_crs1 - d1_x_sin);
439 return d / (half + math::sqrt(quarter - d));
443 #ifdef BOOST_GEOMETRY_DEBUG_CROSS_TRACK
444 std::cout << "Projection OUTSIDE the segment" << std::endl;
447 // Return shortest distance, project either on point sp1 or sp2
448 return return_type( (std::min)( d1 , d2 ) );
452 inline typename Strategy::radius_type radius() const
453 { return m_strategy.radius(); }
459 } // namespace comparable
463 \brief Strategy functor for distance point to segment calculation
465 \details Class which calculates the distance of a point to a segment, for points on a sphere or globe
466 \see http://williams.best.vwh.net/avform.htm
467 \tparam CalculationType \tparam_calculation
468 \tparam Strategy underlying point-point distance strategy, defaults to haversine
472 [link geometry.reference.algorithms.distance.distance_3_with_strategy distance (with strategy)]
478 typename CalculationType = void,
479 typename Strategy = haversine<double, CalculationType>
484 template <typename Point, typename PointOfSegment>
486 : promote_floating_point
488 typename select_calculation_type
497 typedef typename Strategy::radius_type radius_type;
502 explicit inline cross_track(typename Strategy::radius_type const& r)
506 inline cross_track(Strategy const& s)
511 // It might be useful in the future
512 // to overload constructor with strategy info.
513 // crosstrack(...) {}
516 template <typename Point, typename PointOfSegment>
517 inline typename return_type<Point, PointOfSegment>::type
518 apply(Point const& p, PointOfSegment const& sp1, PointOfSegment const& sp2) const
521 #if !defined(BOOST_MSVC)
524 (concepts::PointDistanceStrategy<Strategy, Point, PointOfSegment>)
527 typedef typename return_type<Point, PointOfSegment>::type return_type;
528 typedef cross_track<CalculationType, Strategy> this_type;
530 typedef typename services::comparable_type
533 >::type comparable_type;
535 comparable_type cstrategy
536 = services::get_comparable<this_type>::apply(m_strategy);
538 return_type const a = cstrategy.apply(p, sp1, sp2);
539 return_type const c = return_type(2.0) * asin(math::sqrt(a));
543 inline typename Strategy::radius_type radius() const
544 { return m_strategy.radius(); }
553 #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
557 template <typename CalculationType, typename Strategy>
558 struct tag<cross_track<CalculationType, Strategy> >
560 typedef strategy_tag_distance_point_segment type;
564 template <typename CalculationType, typename Strategy, typename P, typename PS>
565 struct return_type<cross_track<CalculationType, Strategy>, P, PS>
566 : cross_track<CalculationType, Strategy>::template return_type<P, PS>
570 template <typename CalculationType, typename Strategy>
571 struct comparable_type<cross_track<CalculationType, Strategy> >
573 typedef comparable::cross_track
575 CalculationType, typename comparable_type<Strategy>::type
582 typename CalculationType,
585 struct get_comparable<cross_track<CalculationType, Strategy> >
587 typedef typename comparable_type
589 cross_track<CalculationType, Strategy>
590 >::type comparable_type;
592 static inline comparable_type
593 apply(cross_track<CalculationType, Strategy> const& strategy)
595 return comparable_type(strategy.radius());
602 typename CalculationType,
607 struct result_from_distance<cross_track<CalculationType, Strategy>, P, PS>
610 typedef typename cross_track
612 CalculationType, Strategy
613 >::template return_type<P, PS>::type return_type;
615 template <typename T>
616 static inline return_type
617 apply(cross_track<CalculationType, Strategy> const& , T const& distance)
624 // Specializations for comparable::cross_track
625 template <typename RadiusType, typename CalculationType>
626 struct tag<comparable::cross_track<RadiusType, CalculationType> >
628 typedef strategy_tag_distance_point_segment type;
635 typename CalculationType,
639 struct return_type<comparable::cross_track<RadiusType, CalculationType>, P, PS>
640 : comparable::cross_track
642 RadiusType, CalculationType
643 >::template return_type<P, PS>
647 template <typename RadiusType, typename CalculationType>
648 struct comparable_type<comparable::cross_track<RadiusType, CalculationType> >
650 typedef comparable::cross_track<RadiusType, CalculationType> type;
654 template <typename RadiusType, typename CalculationType>
655 struct get_comparable<comparable::cross_track<RadiusType, CalculationType> >
658 typedef comparable::cross_track<RadiusType, CalculationType> this_type;
660 static inline this_type apply(this_type const& input)
670 typename CalculationType,
674 struct result_from_distance
676 comparable::cross_track<RadiusType, CalculationType>, P, PS
680 typedef comparable::cross_track<RadiusType, CalculationType> strategy_type;
681 typedef typename return_type<strategy_type, P, PS>::type return_type;
683 template <typename T>
684 static inline return_type apply(strategy_type const& strategy,
688 = sin( (distance / strategy.radius()) / return_type(2.0) );
697 TODO: spherical polar coordinate system requires "get_as_radian_equatorial<>"
699 template <typename Point, typename PointOfSegment, typename Strategy>
700 struct default_strategy
702 segment_tag, Point, PointOfSegment,
703 spherical_polar_tag, spherical_polar_tag,
710 typename boost::mpl::if_
712 boost::is_void<Strategy>,
713 typename default_strategy
715 point_tag, Point, PointOfSegment,
716 spherical_polar_tag, spherical_polar_tag
724 template <typename Point, typename PointOfSegment, typename Strategy>
725 struct default_strategy
727 point_tag, segment_tag, Point, PointOfSegment,
728 spherical_equatorial_tag, spherical_equatorial_tag,
735 typename boost::mpl::if_
737 boost::is_void<Strategy>,
738 typename default_strategy
740 point_tag, point_tag, Point, PointOfSegment,
741 spherical_equatorial_tag, spherical_equatorial_tag
749 template <typename PointOfSegment, typename Point, typename Strategy>
750 struct default_strategy
752 segment_tag, point_tag, PointOfSegment, Point,
753 spherical_equatorial_tag, spherical_equatorial_tag,
757 typedef typename default_strategy
759 point_tag, segment_tag, Point, PointOfSegment,
760 spherical_equatorial_tag, spherical_equatorial_tag,
766 } // namespace services
767 #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS
769 }} // namespace strategy::distance
771 }} // namespace boost::geometry
773 #endif // BOOST_GEOMETRY_STRATEGIES_SPHERICAL_DISTANCE_CROSS_TRACK_HPP