// Boost.Geometry (aka GGL, Generic Geometry Library) // Copyright (c) 2016-2017, Oracle and/or its affiliates. // Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle // Use, modification and distribution is subject to the Boost Software License, // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at // http://www.boost.org/LICENSE_1_0.txt) #ifndef BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP #define BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK # include #endif #ifndef BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS #define BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS 100 #endif #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK #include #endif namespace boost { namespace geometry { namespace strategy { namespace distance { /*! \brief Strategy functor for distance point to segment calculation on ellipsoid Algorithm uses direct and inverse geodesic problems as subroutines. The algorithm approximates the distance by an iterative Newton method. \ingroup strategies \details Class which calculates the distance of a point to a segment, for points on the ellipsoid \see C.F.F.Karney - Geodesics on an ellipsoid of revolution, https://arxiv.org/abs/1102.1215 \tparam FormulaPolicy underlying point-point distance strategy \tparam Spheroid is the spheroidal model used \tparam CalculationType \tparam_calculation \tparam EnableClosestPoint computes the closest point on segment if true */ template < typename FormulaPolicy = strategy::andoyer, typename Spheroid = srs::spheroid, typename CalculationType = void, bool EnableClosestPoint = false > class geographic_cross_track { public : template struct return_type : promote_floating_point < typename select_calculation_type < Point, PointOfSegment, CalculationType >::type > {}; struct distance_strategy { typedef geographic type; }; inline typename distance_strategy::type get_distance_strategy() const { typedef typename distance_strategy::type distance_type; return distance_type(m_spheroid); } explicit geographic_cross_track(Spheroid const& spheroid = Spheroid()) : m_spheroid(spheroid) {} template inline typename return_type::type apply(Point const& p, PointOfSegment const& sp1, PointOfSegment const& sp2) const { typedef typename coordinate_system::type::units units_type; return (apply(get<0>(sp1), get<1>(sp1), get<0>(sp2), get<1>(sp2), get<0>(p), get<1>(p), m_spheroid)).distance; } private : template struct result_distance_point_segment { result_distance_point_segment() : distance(0) , closest_point_lon(0) , closest_point_lat(0) {} CT distance; CT closest_point_lon; CT closest_point_lat; }; template result_distance_point_segment static inline non_iterative_case(CT lon, CT lat, CT distance) { result_distance_point_segment result; result.distance = distance; if (EnableClosestPoint) { result.closest_point_lon = lon; result.closest_point_lat = lat; } return result; } template result_distance_point_segment static inline non_iterative_case(CT lon1, CT lat1, //p1 CT lon2, CT lat2, //p2 Spheroid const& spheroid) { CT distance = geometry::strategy::distance::geographic ::apply(lon1, lat1, lon2, lat2, spheroid); return non_iterative_case(lon1, lat1, distance); } template CT static inline normalize(CT g4) { CT const pi = math::pi(); if (g4 < 0 && g4 < -pi)//close to -270 { return g4 + 1.5 * pi; } else if (g4 > 0 && g4 > pi)//close to 270 { return - g4 + 1.5 * pi; } else if (g4 < 0 && g4 > -pi)//close to -90 { return -g4 - pi/2; } return g4 - pi/2; } template result_distance_point_segment static inline apply(CT lon1, CT lat1, //p1 CT lon2, CT lat2, //p2 CT lon3, CT lat3, //query point p3 Spheroid const& spheroid) { typedef typename FormulaPolicy::template inverse inverse_distance_quantities_type; typedef typename FormulaPolicy::template inverse inverse_azimuth_type; typedef typename FormulaPolicy::template inverse inverse_azimuth_reverse_type; typedef typename FormulaPolicy::template direct direct_distance_type; CT const earth_radius = geometry::formula::mean_radius(spheroid); result_distance_point_segment result; // Constants CT const f = geometry::formula::flattening(spheroid); CT const pi = math::pi(); CT const half_pi = pi / CT(2); CT const c0 = CT(0); // Convert to radians lon1 = math::as_radian(lon1); lat1 = math::as_radian(lat1); lon2 = math::as_radian(lon2); lat2 = math::as_radian(lat2); lon3 = math::as_radian(lon3); lat3 = math::as_radian(lat3); if (lon1 > lon2) { std::swap(lon1, lon2); std::swap(lat1, lat2); } //segment on equator //Note: antipodal points on equator does not define segment on equator //but pass by the pole CT diff = geometry::math::longitude_distance_signed(lon1, lon2); if (math::equals(lat1, c0) && math::equals(lat2, c0) && !math::equals(math::abs(diff), pi)) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "Equatorial segment" << std::endl; #endif if (lon3 <= lon1) { return non_iterative_case(lon1, lat1, lon3, lat3, spheroid); } if (lon3 >= lon2) { return non_iterative_case(lon2, lat2, lon3, lat3, spheroid); } return non_iterative_case(lon3, lat1, lon3, lat3, spheroid); } if (math::equals(math::abs(diff), pi)) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "Meridian segment" << std::endl; #endif result_distance_point_segment d1 = apply(lon1, lat1, lon1, half_pi, lon3, lat3, spheroid); result_distance_point_segment d2 = apply(lon2, lat2, lon2, half_pi, lon3, lat3, spheroid); if (d1.distance < d2.distance) { return d1; } else { return d2; } } CT d1 = geometry::strategy::distance::geographic ::apply(lon1, lat1, lon3, lat3, spheroid); CT d3 = geometry::strategy::distance::geographic ::apply(lon1, lat1, lon2, lat2, spheroid); if (geometry::math::equals(d3, c0)) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "Degenerate segment" << std::endl; std::cout << "distance between points=" << d1 << std::endl; #endif return non_iterative_case(lon1, lat2, d1); } CT d2 = geometry::strategy::distance::geographic ::apply(lon2, lat2, lon3, lat3, spheroid); // Compute a12 (GEO) geometry::formula::result_inverse res12 = inverse_azimuth_reverse_type::apply(lon1, lat1, lon2, lat2, spheroid); CT a12 = res12.azimuth; CT a13 = inverse_azimuth_type::apply(lon1, lat1, lon3, lat3, spheroid).azimuth; CT a312 = a13 - a12; if (geometry::math::equals(a312, c0)) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "point on segment" << std::endl; #endif return non_iterative_case(lon3, lat3, c0); } CT projection1 = cos( a312 ) * d1 / d3; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "segment=(" << lon1 * math::r2d(); std::cout << "," << lat1 * math::r2d(); std::cout << "),(" << lon2 * math::r2d(); std::cout << "," << lat2 * math::r2d(); std::cout << ")\np=(" << lon3 * math::r2d(); std::cout << "," << lat3 * math::r2d(); std::cout << ")\na1=" << a12 * math::r2d() << std::endl; std::cout << "a13=" << a13 * math::r2d() << std::endl; std::cout << "a312=" << a312 * math::r2d() << std::endl; std::cout << "cos(a312)=" << cos(a312) << std::endl; #endif if (projection1 < 0.0) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "projection closer to p1" << std::endl; #endif // projection of p3 on geodesic spanned by segment (p1,p2) fall // outside of segment on the side of p1 return non_iterative_case(lon1, lat1, lon3, lat3, spheroid); } CT a21 = res12.reverse_azimuth - pi; CT a23 = inverse_azimuth_type::apply(lon2, lat2, lon3, lat3, spheroid).azimuth; CT a321 = a23 - a21; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "a21=" << a21 * math::r2d() << std::endl; std::cout << "a23=" << a23 * math::r2d() << std::endl; std::cout << "a321=" << a321 * math::r2d() << std::endl; std::cout << "cos(a321)=" << cos(a321) << std::endl; #endif CT projection2 = cos( a321 ) * d2 / d3; if (projection2 < 0.0) { #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "projection closer to p2" << std::endl; #endif // projection of p3 on geodesic spanned by segment (p1,p2) fall // outside of segment on the side of p2 return non_iterative_case(lon2, lat2, lon3, lat3, spheroid); } // Guess s14 (SPHERICAL) typedef geometry::model::point < CT, 2, geometry::cs::spherical_equatorial > point; CT bet1 = atan((1 - f) * tan(lon1)); CT bet2 = atan((1 - f) * tan(lon2)); CT bet3 = atan((1 - f) * tan(lon3)); point p1 = point(bet1, lat1); point p2 = point(bet2, lat2); point p3 = point(bet3, lat3); geometry::strategy::distance::cross_track cross_track(earth_radius); CT s34 = cross_track.apply(p3, p1, p2); geometry::strategy::distance::haversine str(earth_radius); CT s13 = str.apply(p1, p3); CT s14 = acos( cos(s13/earth_radius) / cos(s34/earth_radius) ) * earth_radius; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "s34=" << s34 << std::endl; std::cout << "s13=" << s13 << std::endl; std::cout << "s14=" << s14 << std::endl; std::cout << "===============" << std::endl; #endif // Update s14 (using Newton method) CT prev_distance = 0; geometry::formula::result_direct res14; geometry::formula::result_inverse res34; int counter = 0; // robustness CT g4; CT delta_g4; do{ prev_distance = res34.distance; // Solve the direct problem to find p4 (GEO) res14 = direct_distance_type::apply(lon1, lat1, s14, a12, spheroid); // Solve an inverse problem to find g4 // g4 is the angle between segment (p1,p2) and segment (p3,p4) that meet on p4 (GEO) CT a4 = inverse_azimuth_type::apply(res14.lon2, res14.lat2, lon2, lat2, spheroid).azimuth; res34 = inverse_distance_quantities_type::apply(res14.lon2, res14.lat2, lon3, lat3, spheroid); g4 = res34.azimuth - a4; delta_g4 = normalize(g4); CT M43 = res34.geodesic_scale; // cos(s14/earth_radius) is the spherical limit CT m34 = res34.reduced_length; CT der = (M43 / m34) * sin(g4); s14 = s14 - delta_g4 / der; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "p4=" << res14.lon2 * math::r2d() << "," << res14.lat2 * math::r2d() << std::endl; std::cout << "delta_g4=" << delta_g4 << std::endl; std::cout << "g4=" << g4 * math::r2d() << std::endl; std::cout << "der=" << der << std::endl; std::cout << "M43=" << M43 << std::endl; std::cout << "spherical limit=" << cos(s14/earth_radius) << std::endl; std::cout << "m34=" << m34 << std::endl; std::cout << "new_s14=" << s14 << std::endl; std::cout << std::setprecision(16) << "dist =" << res34.distance << std::endl; std::cout << "---------end of step " << counter << std::endl<< std::endl; #endif result.distance = prev_distance; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK if (g4 == half_pi) { std::cout << "Stop msg: g4 == half_pi" << std::endl; } if (res34.distance >= prev_distance && prev_distance != 0) { std::cout << "Stop msg: res34.distance >= prev_distance" << std::endl; } if (delta_g4 == 0) { std::cout << "Stop msg: delta_g4 == 0" << std::endl; } if (counter == 19) { std::cout << "Stop msg: counter" << std::endl; } #endif } while (g4 != half_pi && (prev_distance > res34.distance || prev_distance == 0) && delta_g4 != 0 && ++counter < BOOST_GEOMETRY_DETAIL_POINT_SEGMENT_DISTANCE_MAX_STEPS ) ; #ifdef BOOST_GEOMETRY_DEBUG_GEOGRAPHIC_CROSS_TRACK std::cout << "distance=" << res34.distance << std::endl; point p4(res14.lon2, res14.lat2); CT s34_sph = str.apply(p4, p3); std::cout << "s34(sph) =" << s34_sph << std::endl; std::cout << "s34(geo) =" << inverse_distance_quantities_type::apply(get<0>(p4), get<1>(p4), lon3, lat3, spheroid).distance << ", p4=(" << get<0>(p4) * math::r2d() << "," << get<1>(p4) * math::r2d() << ")" << std::endl; CT s31 = inverse_distance_quantities_type::apply(lon3, lat3, lon1, lat1, spheroid).distance; CT s32 = inverse_distance_quantities_type::apply(lon3, lat3, lon2, lat2, spheroid).distance; CT a4 = inverse_azimuth_type::apply(get<0>(p4), get<1>(p4), lon2, lat2, spheroid).azimuth; geometry::formula::result_direct res4 = direct_distance_type::apply(get<0>(p4), get<1>(p4), .04, a4, spheroid); CT p4_plus = inverse_distance_quantities_type::apply(res4.lon2, res4.lat2, lon3, lat3, spheroid).distance; geometry::formula::result_direct res1 = direct_distance_type::apply(lon1, lat1, s14-.04, a12, spheroid); CT p4_minus = inverse_distance_quantities_type::apply(res1.lon2, res1.lat2, lon3, lat3, spheroid).distance; std::cout << "s31=" << s31 << "\ns32=" << s32 << "\np4_plus=" << p4_plus << ", p4=(" << res4.lon2 * math::r2d() << "," << res4.lat2 * math::r2d() << ")" << "\np4_minus=" << p4_minus << ", p4=(" << res1.lon2 * math::r2d() << "," << res1.lat2 * math::r2d() << ")" << std::endl; if (res34.distance <= p4_plus && res34.distance <= p4_minus) { std::cout << "Closest point computed" << std::endl; } else { std::cout << "There is a closer point nearby" << std::endl; } #endif return result; } Spheroid m_spheroid; }; #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS namespace services { //tags template struct tag > { typedef strategy_tag_distance_point_segment type; }; template < typename FormulaPolicy, typename Spheroid > struct tag > { typedef strategy_tag_distance_point_segment type; }; template < typename FormulaPolicy, typename Spheroid, typename CalculationType > struct tag > { typedef strategy_tag_distance_point_segment type; }; //return types template struct return_type, P, PS> : geographic_cross_track::template return_type {}; template < typename FormulaPolicy, typename Spheroid, typename P, typename PS > struct return_type, P, PS> : geographic_cross_track::template return_type {}; template < typename FormulaPolicy, typename Spheroid, typename CalculationType, typename P, typename PS > struct return_type, P, PS> : geographic_cross_track::template return_type {}; //comparable types template < typename FormulaPolicy, typename Spheroid, typename CalculationType > struct comparable_type > { typedef geographic_cross_track < FormulaPolicy, Spheroid, CalculationType > type; }; template < typename FormulaPolicy, typename Spheroid, typename CalculationType > struct get_comparable > { typedef typename comparable_type < geographic_cross_track >::type comparable_type; public : static inline comparable_type apply(geographic_cross_track const& ) { return comparable_type(); } }; template < typename FormulaPolicy, typename P, typename PS > struct result_from_distance, P, PS> { private : typedef typename geographic_cross_track < FormulaPolicy >::template return_type::type return_type; public : template static inline return_type apply(geographic_cross_track const& , T const& distance) { return distance; } }; template struct default_strategy < point_tag, segment_tag, Point, PointOfSegment, geographic_tag, geographic_tag > { typedef geographic_cross_track<> type; }; template struct default_strategy < segment_tag, point_tag, PointOfSegment, Point, geographic_tag, geographic_tag > { typedef typename default_strategy < point_tag, segment_tag, Point, PointOfSegment, geographic_tag, geographic_tag >::type type; }; } // namespace services #endif // DOXYGEN_NO_STRATEGY_SPECIALIZATIONS }} // namespace strategy::distance }} // namespace boost::geometry #endif // BOOST_GEOMETRY_STRATEGIES_GEOGRAPHIC_DISTANCE_CROSS_TRACK_HPP