diff options
Diffstat (limited to 'boost/geometry/strategies/spherical/point_in_poly_winding.hpp')
-rw-r--r-- | boost/geometry/strategies/spherical/point_in_poly_winding.hpp | 581 |
1 files changed, 581 insertions, 0 deletions
diff --git a/boost/geometry/strategies/spherical/point_in_poly_winding.hpp b/boost/geometry/strategies/spherical/point_in_poly_winding.hpp new file mode 100644 index 0000000000..0f1a901d13 --- /dev/null +++ b/boost/geometry/strategies/spherical/point_in_poly_winding.hpp @@ -0,0 +1,581 @@ +// 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 <boost/geometry/core/access.hpp> +#include <boost/geometry/core/coordinate_system.hpp> +#include <boost/geometry/core/cs.hpp> +#include <boost/geometry/core/tags.hpp> + +#include <boost/geometry/util/math.hpp> +#include <boost/geometry/util/select_calculation_type.hpp> +#include <boost/geometry/util/normalize_spheroidal_coordinates.hpp> + +#include <boost/geometry/strategies/covered_by.hpp> +#include <boost/geometry/strategies/side.hpp> +#include <boost/geometry/strategies/spherical/ssf.hpp> +#include <boost/geometry/strategies/within.hpp> + + +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<Point>::type + >::type, + typename CalculationType = void +> +class spherical_winding_base +{ + typedef typename select_calculation_type + < + Point, + PointOfSegment, + CalculationType + >::type calculation_type; + + typedef typename coordinate_system<Point>::type::units units_t; + typedef math::detail::constants_on_spheroid<calculation_type, units_t> 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 <typename Model> + 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<units_t>(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<units_t>(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<units_t>(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<units_t>(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 <D> 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<PointOfSegment>::type scoord_t; + typedef typename coordinate_system<PointOfSegment>::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<units_t>(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<units_t>(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>, + CalculationType + > +{}; + + +#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS + +namespace services +{ + +template <typename PointLike, typename Geometry, typename AnyTag1, typename AnyTag2> +struct default_strategy<PointLike, Geometry, AnyTag1, AnyTag2, pointlike_tag, polygonal_tag, spherical_tag, spherical_tag> +{ + typedef within::detail::spherical_winding_base + < + typename geometry::point_type<PointLike>::type, + typename geometry::point_type<Geometry>::type + > type; +}; + +template <typename PointLike, typename Geometry, typename AnyTag1, typename AnyTag2> +struct default_strategy<PointLike, Geometry, AnyTag1, AnyTag2, pointlike_tag, linear_tag, spherical_tag, spherical_tag> +{ + typedef within::detail::spherical_winding_base + < + typename geometry::point_type<PointLike>::type, + typename geometry::point_type<Geometry>::type + > type; +}; + +} // namespace services + +#endif + + +}} // namespace strategy::within + + +#ifndef DOXYGEN_NO_STRATEGY_SPECIALIZATIONS +namespace strategy { namespace covered_by { namespace services +{ + +template <typename PointLike, typename Geometry, typename AnyTag1, typename AnyTag2> +struct default_strategy<PointLike, Geometry, AnyTag1, AnyTag2, pointlike_tag, polygonal_tag, spherical_tag, spherical_tag> +{ + typedef within::detail::spherical_winding_base + < + typename geometry::point_type<PointLike>::type, + typename geometry::point_type<Geometry>::type + > type; +}; + +template <typename PointLike, typename Geometry, typename AnyTag1, typename AnyTag2> +struct default_strategy<PointLike, Geometry, AnyTag1, AnyTag2, pointlike_tag, linear_tag, spherical_tag, spherical_tag> +{ + typedef within::detail::spherical_winding_base + < + typename geometry::point_type<PointLike>::type, + typename geometry::point_type<Geometry>::type + > type; +}; + +}}} // namespace strategy::covered_by::services +#endif + + +}} // namespace boost::geometry + + +#endif // BOOST_GEOMETRY_STRATEGY_SPHERICAL_POINT_IN_POLY_WINDING_HPP |