// Boost.Geometry (aka GGL, Generic Geometry Library) // Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands. // Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland. // This file was modified by Oracle on 2013, 2014, 2016, 2017. // Modifications copyright (c) 2013-2017 Oracle and/or its affiliates. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle // Parts of Boost.Geometry are redesigned from Geodan's Geographic Library // (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands. // 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_STRATEGY_SPHERICAL_POINT_IN_POLY_WINDING_HPP #define BOOST_GEOMETRY_STRATEGY_SPHERICAL_POINT_IN_POLY_WINDING_HPP #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace geometry { namespace strategy { namespace within { #ifndef DOXYGEN_NO_DETAIL namespace detail { template < typename Point, typename PointOfSegment = Point, typename SideStrategy = typename strategy::side::services::default_strategy < typename cs_tag::type >::type, typename CalculationType = void > class spherical_winding_base { typedef typename select_calculation_type < Point, PointOfSegment, CalculationType >::type calculation_type; typedef typename coordinate_system::type::units units_t; typedef math::detail::constants_on_spheroid constants; /*! subclass to keep state */ class counter { int m_count; //int m_count_n; int m_count_s; int m_raw_count; int m_raw_count_anti; bool m_touches; inline int code() const { if (m_touches) { return 0; } if (m_raw_count != 0 && m_raw_count_anti != 0) { if (m_raw_count > 0) // right, wrap around south pole { return (m_count + m_count_s) == 0 ? -1 : 1; } else // left, wrap around north pole { //return (m_count + m_count_n) == 0 ? -1 : 1; // m_count_n is 0 return m_count == 0 ? -1 : 1; } } return m_count == 0 ? -1 : 1; } public : friend class spherical_winding_base; inline counter() : m_count(0) //, m_count_n(0) , m_count_s(0) , m_raw_count(0) , m_raw_count_anti(0) , m_touches(false) {} }; struct count_info { explicit count_info(int c = 0, bool ia = false) : count(c) , is_anti(ia) {} int count; bool is_anti; }; public: typedef typename SideStrategy::envelope_strategy_type envelope_strategy_type; inline envelope_strategy_type get_envelope_strategy() const { return m_side_strategy.get_envelope_strategy(); } typedef typename SideStrategy::disjoint_strategy_type disjoint_strategy_type; inline disjoint_strategy_type get_disjoint_strategy() const { return m_side_strategy.get_disjoint_strategy(); } spherical_winding_base() {} template explicit spherical_winding_base(Model const& model) : m_side_strategy(model) {} // Typedefs and static methods to fulfill the concept typedef Point point_type; typedef PointOfSegment segment_point_type; typedef counter state_type; inline bool apply(Point const& point, PointOfSegment const& s1, PointOfSegment const& s2, counter& state) const { bool eq1 = false; bool eq2 = false; bool s_antipodal = false; count_info ci = check_segment(point, s1, s2, state, eq1, eq2, s_antipodal); if (ci.count != 0) { if (! ci.is_anti) { int side = 0; if (ci.count == 1 || ci.count == -1) { side = side_equal(point, eq1 ? s1 : s2, ci, s1, s2); } else // count == 2 || count == -2 { if (! s_antipodal) { // 1 left, -1 right side = m_side_strategy.apply(s1, s2, point); } else { calculation_type const pi = constants::half_period(); calculation_type const s1_lat = get<1>(s1); calculation_type const s2_lat = get<1>(s2); side = math::sign(ci.count) * (pi - s1_lat - s2_lat <= pi // segment goes through north pole ? -1 // going right all points will be on right side : 1); // going right all points will be on left side } } if (side == 0) { // Point is lying on segment state.m_touches = true; state.m_count = 0; return false; } // Side is NEG for right, POS for left. // The count is -2 for left, 2 for right (or -1/1) // Side positive thus means RIGHT and LEFTSIDE or LEFT and RIGHTSIDE // See accompagnying figure (TODO) if (side * ci.count > 0) { state.m_count += ci.count; } state.m_raw_count += ci.count; } else { // Count negated because the segment is on the other side of the globe // so it is reversed to match this side of the globe // Assuming geometry wraps around north pole, for segments on the other side of the globe // the point will always be RIGHT+RIGHTSIDE or LEFT+LEFTSIDE, so side*-count always < 0 //state.m_count_n -= 0; // Assuming geometry wraps around south pole, for segments on the other side of the globe // the point will always be RIGHT+LEFTSIDE or LEFT+RIGHTSIDE, so side*-count always > 0 state.m_count_s -= ci.count; state.m_raw_count_anti -= ci.count; } } return ! state.m_touches; } static inline int result(counter const& state) { return state.code(); } private: static inline count_info check_segment(Point const& point, PointOfSegment const& seg1, PointOfSegment const& seg2, counter& state, bool& eq1, bool& eq2, bool& s_antipodal) { if (check_touch(point, seg1, seg2, state, eq1, eq2, s_antipodal)) { return count_info(0, false); } return calculate_count(point, seg1, seg2, eq1, eq2, s_antipodal); } static inline int check_touch(Point const& point, PointOfSegment const& seg1, PointOfSegment const& seg2, counter& state, bool& eq1, bool& eq2, bool& s_antipodal) { calculation_type const c0 = 0; calculation_type const c2 = 2; calculation_type const pi = constants::half_period(); calculation_type const half_pi = pi / c2; calculation_type const p_lon = get<0>(point); calculation_type const s1_lon = get<0>(seg1); calculation_type const s2_lon = get<0>(seg2); calculation_type const p_lat = get<1>(point); calculation_type const s1_lat = get<1>(seg1); calculation_type const s2_lat = get<1>(seg2); // NOTE: lat in {-90, 90} and arbitrary lon // it doesn't matter what lon it is if it's a pole // so e.g. if one of the segment endpoints is a pole // then only the other lon matters bool eq1_strict = longitudes_equal(s1_lon, p_lon); bool eq2_strict = longitudes_equal(s2_lon, p_lon); bool eq1_anti = false; bool eq2_anti = false; calculation_type const anti_p_lon = p_lon + (p_lon <= c0 ? pi : -pi); eq1 = eq1_strict // lon strictly equal to s1 || (eq1_anti = longitudes_equal(s1_lon, anti_p_lon)) // anti-lon strictly equal to s1 || math::equals(math::abs(s1_lat), half_pi); // s1 is pole eq2 = eq2_strict // lon strictly equal to s2 || (eq2_anti = longitudes_equal(s2_lon, anti_p_lon)) // anti-lon strictly equal to s2 || math::equals(math::abs(s2_lat), half_pi); // s2 is pole // segment overlapping pole calculation_type const s_lon_diff = math::longitude_distance_signed(s1_lon, s2_lon); s_antipodal = math::equals(s_lon_diff, pi); if (s_antipodal) { eq1 = eq2 = eq1 || eq2; // segment overlapping pole and point is pole if (math::equals(math::abs(p_lat), half_pi)) { eq1 = eq2 = true; } } // Both equal p -> segment vertical // The only thing which has to be done is check if point is ON segment if (eq1 && eq2) { // segment endpoints on the same sides of the globe if (! s_antipodal) { // p's lat between segment endpoints' lats if ( (s1_lat <= p_lat && s2_lat >= p_lat) || (s2_lat <= p_lat && s1_lat >= p_lat) ) { if (!eq1_anti || !eq2_anti) { state.m_touches = true; } } } else { // going through north or south pole? if (pi - s1_lat - s2_lat <= pi) { if ( (eq1_strict && s1_lat <= p_lat) || (eq2_strict && s2_lat <= p_lat) // north || math::equals(p_lat, half_pi) ) // point on north pole { state.m_touches = true; } else if (! eq1_strict && ! eq2_strict && math::equals(p_lat, -half_pi) ) // point on south pole { return false; } } else // south pole { if ( (eq1_strict && s1_lat >= p_lat) || (eq2_strict && s2_lat >= p_lat) // south || math::equals(p_lat, -half_pi) ) // point on south pole { state.m_touches = true; } else if (! eq1_strict && ! eq2_strict && math::equals(p_lat, half_pi) ) // point on north pole { return false; } } } return true; } return false; } // Called if point is not aligned with a vertical segment static inline count_info calculate_count(Point const& point, PointOfSegment const& seg1, PointOfSegment const& seg2, bool eq1, bool eq2, bool s_antipodal) { // If both segment endpoints were poles below checks wouldn't be enough // but this means that either both are the same or that they are N/S poles // and therefore the segment is not valid. // If needed (eq1 && eq2 ? 0) could be returned calculation_type const c0 = 0; calculation_type const pi = constants::half_period(); calculation_type const p = get<0>(point); calculation_type const s1 = get<0>(seg1); calculation_type const s2 = get<0>(seg2); calculation_type const s1_p = math::longitude_distance_signed(s1, p); if (s_antipodal) { return count_info(s1_p < c0 ? -2 : 2, false); // choose W/E } calculation_type const s1_s2 = math::longitude_distance_signed(s1, s2); if (eq1 || eq2) // Point on level s1 or s2 { return count_info(s1_s2 < c0 ? -1 : 1, // choose W/E longitudes_equal(p + pi, (eq1 ? s1 : s2))); } // Point between s1 and s2 if ( math::sign(s1_p) == math::sign(s1_s2) && math::abs(s1_p) < math::abs(s1_s2) ) { return count_info(s1_s2 < c0 ? -2 : 2, false); // choose W/E } calculation_type const s1_p_anti = math::longitude_distance_signed(s1, p + pi); // Anti-Point between s1 and s2 if ( math::sign(s1_p_anti) == math::sign(s1_s2) && math::abs(s1_p_anti) < math::abs(s1_s2) ) { return count_info(s1_s2 < c0 ? -2 : 2, true); // choose W/E } return count_info(0, false); } // Fix for https://svn.boost.org/trac/boost/ticket/9628 // For floating point coordinates, the coordinate of a point is compared // with the segment's points using some EPS. If the coordinates are "equal" // the sides are calculated. Therefore we can treat a segment as a long areal // geometry having some width. There is a small ~triangular area somewhere // between the segment's effective area and a segment's line used in sides // calculation where the segment is on the one side of the line but on the // other side of a segment (due to the width). // Below picture assuming D = 1, if D = 0 horiz<->vert, E<->N, RIGHT<->UP. // For the s1 of a segment going NE the real side is RIGHT but the point may // be detected as LEFT, like this: // RIGHT // ___-----> // ^ O Pt __ __ // EPS __ __ // v__ __ BUT DETECTED AS LEFT OF THIS LINE // _____7 // _____/ // _____/ // In the code below actually D = 0, so segments are nearly-vertical // Called when the point is on the same level as one of the segment's points // but the point is not aligned with a vertical segment inline int side_equal(Point const& point, PointOfSegment const& se, count_info const& ci, PointOfSegment const& s1, PointOfSegment const& s2) const { typedef typename coordinate_type::type scoord_t; typedef typename coordinate_system::type::units units_t; if (math::equals(get<1>(point), get<1>(se))) { return 0; } // Create a horizontal segment intersecting the original segment's endpoint // equal to the point, with the derived direction (E/W). PointOfSegment ss1, ss2; set<1>(ss1, get<1>(se)); set<0>(ss1, get<0>(se)); set<1>(ss2, get<1>(se)); scoord_t ss20 = get<0>(se); if (ci.count > 0) { ss20 += small_angle(); } else { ss20 -= small_angle(); } math::normalize_longitude(ss20); set<0>(ss2, ss20); // Check the side using this vertical segment return m_side_strategy.apply(ss1, ss2, point); } // 1 deg or pi/180 rad static inline calculation_type small_angle() { return constants::half_period() / calculation_type(180); }; static inline bool longitudes_equal(calculation_type const& lon1, calculation_type const& lon2) { return math::equals( math::longitude_distance_signed(lon1, lon2), calculation_type(0)); } SideStrategy m_side_strategy; }; } // namespace detail #endif // DOXYGEN_NO_DETAIL /*! \brief Within detection using winding rule in spherical coordinate system. \ingroup strategies \tparam Point \tparam_point \tparam PointOfSegment \tparam_segment_point \tparam CalculationType \tparam_calculation \qbk{ [heading See also] [link geometry.reference.algorithms.within.within_3_with_strategy within (with strategy)] } */ template < typename Point, typename PointOfSegment = Point, typename CalculationType = void > class spherical_winding : public within::detail::spherical_winding_base < Point, PointOfSegment, side::spherical_side_formula, CalculationType > {}; #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS namespace services { template struct default_strategy { typedef within::detail::spherical_winding_base < typename geometry::point_type::type, typename geometry::point_type::type > type; }; template struct default_strategy { typedef within::detail::spherical_winding_base < typename geometry::point_type::type, typename geometry::point_type::type > type; }; } // namespace services #endif }} // namespace strategy::within #ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS namespace strategy { namespace covered_by { namespace services { template struct default_strategy { typedef within::detail::spherical_winding_base < typename geometry::point_type::type, typename geometry::point_type::type > type; }; template struct default_strategy { typedef within::detail::spherical_winding_base < typename geometry::point_type::type, typename geometry::point_type::type > type; }; }}} // namespace strategy::covered_by::services #endif }} // namespace boost::geometry #endif // BOOST_GEOMETRY_STRATEGY_SPHERICAL_POINT_IN_POLY_WINDING_HPP