// Boost.Geometry (aka GGL, Generic Geometry Library) // Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands. // Copyright (c) 2023 Adam Wulkiewicz, Lodz, Poland. // This file was modified by Oracle on 2013-2022. // Modifications copyright (c) 2013-2022 Oracle and/or its affiliates. // Contributed and/or modified by Adam Wulkiewicz, 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_ALGORITHMS_DETAIL_RELATE_LINEAR_AREAL_HPP #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_LINEAR_AREAL_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace geometry { #ifndef DOXYGEN_NO_DETAIL namespace detail { namespace relate { // WARNING! // TODO: In the worst case calling this Pred in a loop for MultiLinestring/MultiPolygon may take O(NM) // Use the rtree in this case! // may be used to set IE and BE for a Linear geometry for which no turns were generated template < typename Geometry2, typename Result, typename Strategy, typename BoundaryChecker, bool TransposeResult > class no_turns_la_linestring_pred { public: no_turns_la_linestring_pred(Geometry2 const& geometry2, Result & res, Strategy const& strategy, BoundaryChecker const& boundary_checker) : m_geometry2(geometry2) , m_result(res) , m_strategy(strategy) , m_boundary_checker(boundary_checker) , m_interrupt_flags(0) { if ( ! may_update(m_result) ) { m_interrupt_flags |= 1; } if ( ! may_update(m_result) ) { m_interrupt_flags |= 2; } if ( ! may_update(m_result) ) { m_interrupt_flags |= 4; } if ( ! may_update(m_result) ) { m_interrupt_flags |= 8; } } template bool operator()(Linestring const& linestring) { std::size_t const count = boost::size(linestring); // invalid input if ( count < 2 ) { // ignore // TODO: throw an exception? return true; } // if those flags are set nothing will change if ( m_interrupt_flags == 0xF ) { return false; } int const pig = detail::within::point_in_geometry(range::front(linestring), m_geometry2, m_strategy); //BOOST_GEOMETRY_ASSERT_MSG(pig != 0, "There should be no IPs"); if ( pig > 0 ) { update(m_result); m_interrupt_flags |= 1; } else { update(m_result); m_interrupt_flags |= 2; } // check if there is a boundary if ((m_interrupt_flags & 0xC) != 0xC // if wasn't already set && (m_boundary_checker.is_endpoint_boundary(range::front(linestring)) || m_boundary_checker.is_endpoint_boundary(range::back(linestring)))) { if ( pig > 0 ) { update(m_result); m_interrupt_flags |= 4; } else { update(m_result); m_interrupt_flags |= 8; } } return m_interrupt_flags != 0xF && ! m_result.interrupt; } private: Geometry2 const& m_geometry2; Result & m_result; Strategy const& m_strategy; BoundaryChecker const& m_boundary_checker; unsigned m_interrupt_flags; }; // may be used to set EI and EB for an Areal geometry for which no turns were generated template class no_turns_la_areal_pred { public: no_turns_la_areal_pred(Result & res) : m_result(res) , interrupt(! may_update(m_result) && ! may_update(m_result) ) {} template bool operator()(Areal const& areal) { if ( interrupt ) { return false; } // TODO: // handle empty/invalid geometries in a different way than below? using point_type = typename geometry::point_type::type; typename helper_geometry::type pt; bool const ok = geometry::point_on_border(pt, areal); // TODO: for now ignore, later throw an exception? if ( !ok ) { return true; } update(m_result); update(m_result); return false; } private: Result & m_result; bool const interrupt; }; template inline It find_next_non_duplicated(It first, It current, It last, Strategy const& strategy) { BOOST_GEOMETRY_ASSERT(current != last); It it = current; for (++it ; it != last ; ++it) { if (! equals::equals_point_point(*current, *it, strategy)) { return it; } } // if not found start from the beginning for (it = first ; it != current ; ++it) { if (! equals::equals_point_point(*current, *it, strategy)) { return it; } } return current; } // calculate inside or outside based on side_calc // this is simplified version of a check from equal<> template < typename Pi, typename Pj, typename Pk, typename Qi, typename Qj, typename Qk, typename Strategy > inline bool calculate_from_inside_sides(Pi const& pi, Pj const& pj, Pk const& pk, Qi const& , Qj const& qj, Qk const& qk, Strategy const& strategy) { auto const side_strategy = strategy.side(); int const side_pk_p = side_strategy.apply(pi, pj, pk); int const side_qk_p = side_strategy.apply(pi, pj, qk); // If they turn to same side (not opposite sides) if (! overlay::base_turn_handler::opposite(side_pk_p, side_qk_p)) { int const side_pk_q2 = side_strategy.apply(qj, qk, pk); return side_pk_q2 == -1; } else { return side_pk_p == -1; } } // check if the passed turn's segment of Linear geometry arrived // from the inside or the outside of the Areal geometry template < std::size_t OpId, typename Geometry1, typename Geometry2, typename Turn, typename Strategy > inline bool calculate_from_inside(Geometry1 const& geometry1, Geometry2 const& geometry2, Turn const& turn, Strategy const& strategy) { static const std::size_t op_id = OpId; static const std::size_t other_op_id = (OpId + 1) % 2; if (turn.operations[op_id].position == overlay::position_front) { return false; } auto const& range1 = sub_range(geometry1, turn.operations[op_id].seg_id); using range2_view = detail::closed_clockwise_view::type const>; range2_view const range2(sub_range(geometry2, turn.operations[other_op_id].seg_id)); BOOST_GEOMETRY_ASSERT(boost::size(range1)); std::size_t const s2 = boost::size(range2); BOOST_GEOMETRY_ASSERT(s2 > 2); std::size_t const seg_count2 = s2 - 1; std::size_t const p_seg_ij = static_cast(turn.operations[op_id].seg_id.segment_index); std::size_t const q_seg_ij = static_cast(turn.operations[other_op_id].seg_id.segment_index); BOOST_GEOMETRY_ASSERT(p_seg_ij + 1 < boost::size(range1)); BOOST_GEOMETRY_ASSERT(q_seg_ij + 1 < s2); auto const& pi = range::at(range1, p_seg_ij); auto const& qi = range::at(range2, q_seg_ij); auto const& qj = range::at(range2, q_seg_ij + 1); bool const is_ip_qj = equals::equals_point_point(turn.point, qj, strategy); // TODO: test this! // BOOST_GEOMETRY_ASSERT(!equals::equals_point_point(turn.point, pi)); // BOOST_GEOMETRY_ASSERT(!equals::equals_point_point(turn.point, qi)); if (is_ip_qj) { std::size_t const q_seg_jk = (q_seg_ij + 1) % seg_count2; // TODO: the following function should return immediately, however the worst case complexity is O(N) // It would be good to replace it with some O(1) mechanism auto qk_it = find_next_non_duplicated(boost::begin(range2), range::pos(range2, q_seg_jk), boost::end(range2), strategy); // Calculate sides in a different point order for P and Q // Will this sequence of points be always correct? return calculate_from_inside_sides(qi, turn.point, pi, qi, qj, *qk_it, strategy); } else { // Calculate sides with different points for P and Q return calculate_from_inside_sides(qi, turn.point, pi, qi, turn.point, qj, strategy); } } // The implementation of an algorithm calculating relate() for L/A template struct linear_areal { // check Linear / Areal BOOST_STATIC_ASSERT(topological_dimension::value == 1 && topological_dimension::value == 2); static const bool interruption_enabled = true; template struct multi_turn_info : turns::get_turns::template turn_info_type::type { multi_turn_info() : priority(0) {} int priority; // single-geometry sorting priority }; template struct turn_info_type : std::conditional < util::is_multi::value, multi_turn_info, typename turns::get_turns::template turn_info_type::type > {}; template static inline void apply(Geometry1 const& geometry1, Geometry2 const& geometry2, Result & result, Strategy const& strategy) { boundary_checker boundary_checker1(geometry1, strategy); apply(geometry1, geometry2, boundary_checker1, result, strategy); } template static inline void apply(Geometry1 const& geometry1, Geometry2 const& geometry2, BoundaryChecker1 const& boundary_checker1, Result & result, Strategy const& strategy) { // TODO: If Areal geometry may have infinite size, change the following line: update::value, TransposeResult>(result);// FFFFFFFFd, d in [1,9] or T if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } // get and analyse turns using turn_type = typename turn_info_type::type; std::vector turns; interrupt_policy_linear_areal interrupt_policy(geometry2, result); turns::get_turns::apply(turns, geometry1, geometry2, interrupt_policy, strategy); if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } no_turns_la_linestring_pred < Geometry2, Result, Strategy, BoundaryChecker1, TransposeResult > pred1(geometry2, result, strategy, boundary_checker1); for_each_disjoint_geometry_if<0, Geometry1>::apply(turns.begin(), turns.end(), geometry1, pred1); if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } no_turns_la_areal_pred pred2(result); for_each_disjoint_geometry_if<1, Geometry2>::apply(turns.begin(), turns.end(), geometry2, pred2); if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } if ( turns.empty() ) { return; } // This is set here because in the case if empty Areal geometry were passed // those shouldn't be set update(result);// FFFFFF2Fd if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } { sort_dispatch(turns.begin(), turns.end(), strategy, util::is_multi()); turns_analyser analyser; analyse_each_turn(result, analyser, turns.begin(), turns.end(), geometry1, geometry2, boundary_checker1, strategy); if ( BOOST_GEOMETRY_CONDITION( result.interrupt ) ) { return; } } // If 'c' (insersection_boundary) was not found we know that any Ls isn't equal to one of the Rings if ( !interrupt_policy.is_boundary_found ) { update(result); } // Don't calculate it if it's required else if ( may_update(result) ) { // TODO: REVISE THIS CODE AND PROBABLY REWRITE SOME PARTS TO BE MORE HUMAN-READABLE // IN GENERAL IT ANALYSES THE RINGS OF AREAL GEOMETRY AND DETECTS THE ONES THAT // MAY OVERLAP THE INTERIOR OF LINEAR GEOMETRY (NO IPs OR NON-FAKE 'u' OPERATION) // NOTE: For one case std::sort may be called again to sort data by operations for data already sorted by ring index // In the worst case scenario the complexity will be O( NlogN + R*(N/R)log(N/R) ) // So always should remain O(NlogN) -> for R==1 <-> 1(N/1)log(N/1), for R==N <-> N(N/N)log(N/N) // Some benchmarking should probably be done to check if only one std::sort should be used // sort by multi_index and rind_index std::sort(turns.begin(), turns.end(), less_ring()); segment_identifier * prev_seg_id_ptr = NULL; // for each ring for (auto it = turns.begin() ; it != turns.end() ; ) { // it's the next single geometry if ( prev_seg_id_ptr == NULL || prev_seg_id_ptr->multi_index != it->operations[1].seg_id.multi_index ) { // if the first ring has no IPs if ( it->operations[1].seg_id.ring_index > -1 ) { // we can be sure that the exterior overlaps the boundary update(result); break; } // if there was some previous ring if ( prev_seg_id_ptr != NULL ) { signed_size_type const next_ring_index = prev_seg_id_ptr->ring_index + 1; BOOST_GEOMETRY_ASSERT(next_ring_index >= 0); // if one of the last rings of previous single geometry was ommited if ( static_cast(next_ring_index) < geometry::num_interior_rings( single_geometry(geometry2, *prev_seg_id_ptr)) ) { // we can be sure that the exterior overlaps the boundary update(result); break; } } } // if it's the same single geometry else /*if ( previous_multi_index == it->operations[1].seg_id.multi_index )*/ { // and we jumped over one of the rings if ( prev_seg_id_ptr != NULL // just in case && prev_seg_id_ptr->ring_index + 1 < it->operations[1].seg_id.ring_index ) { // we can be sure that the exterior overlaps the boundary update(result); break; } } prev_seg_id_ptr = boost::addressof(it->operations[1].seg_id); // find the next ring first iterator and check if the analysis should be performed has_boundary_intersection has_boundary_inters; auto next = find_next_ring(it, turns.end(), has_boundary_inters); // if there is no 1d overlap with the boundary if ( !has_boundary_inters.result ) { // we can be sure that the exterior overlaps the boundary update(result); break; } // else there is 1d overlap with the boundary so we must analyse the boundary else { // u, c using less_t = turns::less<1, turns::less_op_areal_linear<1>, Strategy>; std::sort(it, next, less_t()); // analyse areal_boundary_analyser analyser; for (auto rit = it ; rit != next ; ++rit) { // if the analyser requests, break the search if ( !analyser.apply(it, rit, next, strategy) ) break; } // if the boundary of Areal goes out of the Linear if ( analyser.is_union_detected ) { // we can be sure that the boundary of Areal overlaps the exterior of Linear update(result); break; } } it = next; } // if there was some previous ring if ( prev_seg_id_ptr != NULL ) { signed_size_type const next_ring_index = prev_seg_id_ptr->ring_index + 1; BOOST_GEOMETRY_ASSERT(next_ring_index >= 0); // if one of the last rings of previous single geometry was ommited if ( static_cast(next_ring_index) < geometry::num_interior_rings( single_geometry(geometry2, *prev_seg_id_ptr)) ) { // we can be sure that the exterior overlaps the boundary update(result); } } } } template static void for_each_equal_range(It first, It last, Pred pred, Comp comp) { if ( first == last ) { return; } It first_equal = first; It prev = first; for ( ++first ; ; ++first, ++prev ) { if ( first == last || !comp(*prev, *first) ) { pred(first_equal, first); first_equal = first; } if ( first == last ) { break; } } } struct same_ip { template bool operator()(Turn const& left, Turn const& right) const { return left.operations[0].seg_id == right.operations[0].seg_id && left.operations[0].fraction == right.operations[0].fraction; } }; struct same_ip_and_multi_index { template bool operator()(Turn const& left, Turn const& right) const { return same_ip()(left, right) && left.operations[1].seg_id.multi_index == right.operations[1].seg_id.multi_index; } }; template struct set_turns_group_priority { template void operator()(TurnIt first, TurnIt last) const { BOOST_GEOMETRY_ASSERT(first != last); static OpToPriority op_to_priority; // find the operation with the least priority int least_priority = op_to_priority(first->operations[0]); for ( TurnIt it = first + 1 ; it != last ; ++it ) { int priority = op_to_priority(it->operations[0]); if ( priority < least_priority ) least_priority = priority; } // set the least priority for all turns of the group for ( TurnIt it = first ; it != last ; ++it ) { it->priority = least_priority; } } }; template struct sort_turns_group { struct less { template bool operator()(Turn const& left, Turn const& right) const { return left.operations[1].seg_id.multi_index == right.operations[1].seg_id.multi_index ? SingleLess()(left, right) : left.priority < right.priority; } }; template void operator()(TurnIt first, TurnIt last) const { std::sort(first, last, less()); } }; template static void sort_dispatch(TurnIt first, TurnIt last, Strategy const& , std::true_type const& /*is_multi*/) { // sort turns by Linear seg_id, then by fraction, then by other multi_index using less_t = turns::less<0, turns::less_other_multi_index<0>, Strategy>; std::sort(first, last, less_t()); // For the same IP and multi_index - the same other's single geometry // set priorities as the least operation found for the whole single geometry // so e.g. single geometries containing 'u' will always be before those only containing 'i' typedef turns::op_to_int<0,2,3,1,4,0> op_to_int_xuic; for_each_equal_range(first, last, set_turns_group_priority(), // least operation in xuic order same_ip_and_multi_index()); // other's multi_index // When priorities for single geometries are set now sort turns for the same IP // if multi_index is the same sort them according to the single-less // else use priority of the whole single-geometry set earlier using single_less_t = turns::less<0, turns::less_op_linear_areal_single<0>, Strategy>; for_each_equal_range(first, last, sort_turns_group(), same_ip()); } template static void sort_dispatch(TurnIt first, TurnIt last, Strategy const& , std::false_type const& /*is_multi*/) { // sort turns by Linear seg_id, then by fraction, then // for same ring id: x, u, i, c // for different ring id: c, i, u, x using less_t = turns::less<0, turns::less_op_linear_areal_single<0>, Strategy>; std::sort(first, last, less_t()); } // interrupt policy which may be passed to get_turns to interrupt the analysis // based on the info in the passed result/mask template class interrupt_policy_linear_areal { public: static bool const enabled = true; interrupt_policy_linear_areal(Areal const& areal, Result & result) : m_result(result), m_areal(areal) , is_boundary_found(false) {} // TODO: since we update result for some operations here, we may not do it in the analyser! template inline bool apply(Range const& turns) { for (auto it = boost::begin(turns) ; it != boost::end(turns) ; ++it) { if ( it->operations[0].operation == overlay::operation_intersection ) { bool const no_interior_rings = geometry::num_interior_rings( single_geometry(m_areal, it->operations[1].seg_id)) == 0; // WARNING! THIS IS TRUE ONLY IF THE POLYGON IS SIMPLE! // OR WITHOUT INTERIOR RINGS (AND OF COURSE VALID) if ( no_interior_rings ) update(m_result); } else if ( it->operations[0].operation == overlay::operation_continue ) { update(m_result); is_boundary_found = true; } else if ( ( it->operations[0].operation == overlay::operation_union || it->operations[0].operation == overlay::operation_blocked ) && it->operations[0].position == overlay::position_middle ) { // TODO: here we could also check the boundaries and set BB at this point update(m_result); } } return m_result.interrupt; } private: Result & m_result; Areal const& m_areal; public: bool is_boundary_found; }; // This analyser should be used like Input or SinglePass Iterator // IMPORTANT! It should be called also for the end iterator - last template class turns_analyser { typedef typename TurnInfo::point_type turn_point_type; static const std::size_t op_id = 0; static const std::size_t other_op_id = 1; public: turns_analyser() : m_previous_turn_ptr(NULL) , m_previous_operation(overlay::operation_none) , m_boundary_counter(0) , m_interior_detected(false) , m_first_interior_other_id_ptr(NULL) , m_first_from_unknown(false) , m_first_from_unknown_boundary_detected(false) {} template void apply(Result & res, TurnIt it, Geometry const& geometry, OtherGeometry const& other_geometry, BoundaryChecker const& boundary_checker, Strategy const& strategy) { overlay::operation_type op = it->operations[op_id].operation; if ( op != overlay::operation_union && op != overlay::operation_intersection && op != overlay::operation_blocked && op != overlay::operation_continue ) // operation_boundary / operation_boundary_intersection { return; } segment_identifier const& seg_id = it->operations[op_id].seg_id; segment_identifier const& other_id = it->operations[other_op_id].seg_id; const bool first_in_range = m_seg_watcher.update(seg_id); // TODO: should apply() for the post-last ip be called if first_in_range ? // this would unify how last points in ranges are handled // possibly replacing parts of the code below // e.g. for is_multi and m_interior_detected // handle possible exit bool fake_enter_detected = false; if ( m_exit_watcher.get_exit_operation() == overlay::operation_union ) { // real exit point - may be multiple // we know that we entered and now we exit if ( ! turn_on_the_same_ip(m_exit_watcher.get_exit_turn(), *it, strategy) ) { m_exit_watcher.reset_detected_exit(); update(res); // next single geometry if ( first_in_range && m_previous_turn_ptr ) { // NOTE: similar code is in the post-last-ip-apply() segment_identifier const& prev_seg_id = m_previous_turn_ptr->operations[op_id].seg_id; bool const prev_back_b = boundary_checker.is_endpoint_boundary( range::back(sub_range(geometry, prev_seg_id))); // if there is a boundary on the last point if ( prev_back_b ) { update(res); } } } // fake exit point, reset state else if ( op == overlay::operation_intersection || op == overlay::operation_continue ) // operation_boundary { m_exit_watcher.reset_detected_exit(); fake_enter_detected = true; } } else if ( m_exit_watcher.get_exit_operation() == overlay::operation_blocked ) { // ignore multiple BLOCKs for this same single geometry1 if ( op == overlay::operation_blocked && seg_id.multi_index == m_previous_turn_ptr->operations[op_id].seg_id.multi_index ) { return; } if ( ( op == overlay::operation_intersection || op == overlay::operation_continue ) && turn_on_the_same_ip(m_exit_watcher.get_exit_turn(), *it, strategy) ) { fake_enter_detected = true; } m_exit_watcher.reset_detected_exit(); } if BOOST_GEOMETRY_CONSTEXPR (util::is_multi::value) { if (m_first_from_unknown) { // For MultiPolygon many x/u operations may be generated as a first IP // if for all turns x/u was generated and any of the Polygons doesn't contain the LineString // then we know that the LineString is outside // Similar with the u/u turns, if it was the first one it doesn't mean that the // Linestring came from the exterior if ((m_previous_operation == overlay::operation_blocked && (op != overlay::operation_blocked // operation different than block || seg_id.multi_index != m_previous_turn_ptr->operations[op_id].seg_id.multi_index)) // or the next single-geometry || (m_previous_operation == overlay::operation_union && ! turn_on_the_same_ip(*m_previous_turn_ptr, *it, strategy))) { update(res); if ( m_first_from_unknown_boundary_detected ) { update(res); } m_first_from_unknown = false; m_first_from_unknown_boundary_detected = false; } } } // NOTE: THE WHOLE m_interior_detected HANDLING IS HERE BECAUSE WE CAN'T EFFICIENTLY SORT TURNS (CORRECTLY) // BECAUSE THE SAME IP MAY BE REPRESENTED BY TWO SEGMENTS WITH DIFFERENT DISTANCES // IT WOULD REQUIRE THE CALCULATION OF MAX DISTANCE // TODO: WE COULD GET RID OF THE TEST IF THE DISTANCES WERE NORMALIZED // UPDATE: THEY SHOULD BE NORMALIZED NOW // TODO: THIS IS POTENTIALLY ERROREOUS! // THIS ALGORITHM DEPENDS ON SOME SPECIFIC SEQUENCE OF OPERATIONS // IT WOULD GIVE WRONG RESULTS E.G. // IN THE CASE OF SELF-TOUCHING POINT WHEN 'i' WOULD BE BEFORE 'u' // handle the interior overlap if ( m_interior_detected ) { BOOST_GEOMETRY_ASSERT_MSG(m_previous_turn_ptr, "non-NULL ptr expected"); // real interior overlap if ( ! turn_on_the_same_ip(*m_previous_turn_ptr, *it, strategy) ) { update(res); m_interior_detected = false; // new range detected - reset previous state and check the boundary if ( first_in_range ) { segment_identifier const& prev_seg_id = m_previous_turn_ptr->operations[op_id].seg_id; bool const prev_back_b = boundary_checker.is_endpoint_boundary( range::back(sub_range(geometry, prev_seg_id))); // if there is a boundary on the last point if ( prev_back_b ) { update(res); } // The exit_watcher is reset below // m_exit_watcher.reset(); } } // fake interior overlap else if ( op == overlay::operation_continue ) { m_interior_detected = false; } else if ( op == overlay::operation_union ) { // TODO: this probably is not a good way of handling the interiors/enters // the solution similar to exit_watcher would be more robust // all enters should be kept and handled. // maybe integrate it with the exit_watcher -> enter_exit_watcher if ( m_first_interior_other_id_ptr && m_first_interior_other_id_ptr->multi_index == other_id.multi_index ) { m_interior_detected = false; } } } // NOTE: If post-last-ip apply() was called this wouldn't be needed if ( first_in_range ) { m_exit_watcher.reset(); m_boundary_counter = 0; m_first_from_unknown = false; m_first_from_unknown_boundary_detected = false; } // i/u, c/u if ( op == overlay::operation_intersection || op == overlay::operation_continue ) // operation_boundary/operation_boundary_intersection { bool const first_point = first_in_range || m_first_from_unknown; bool no_enters_detected = m_exit_watcher.is_outside(); m_exit_watcher.enter(*it); if ( op == overlay::operation_intersection ) { if ( m_boundary_counter > 0 && it->operations[op_id].is_collinear ) --m_boundary_counter; if ( m_boundary_counter == 0 ) { // interiors overlaps //update(res); // TODO: think about the implementation of the more robust version // this way only the first enter will be handled if ( !m_interior_detected ) { // don't update now // we might enter a boundary of some other ring on the same IP m_interior_detected = true; m_first_interior_other_id_ptr = boost::addressof(other_id); } } } else // operation_boundary { // don't add to the count for all met boundaries // only if this is the "new" boundary if ( first_point || !it->operations[op_id].is_collinear ) ++m_boundary_counter; update(res); } bool const this_b = is_ip_on_boundary(it->point, it->operations[op_id], boundary_checker); // going inside on boundary point if ( this_b ) { update(res); } // going inside on non-boundary point else { update(res); // if we didn't enter in the past, we were outside if ( no_enters_detected && ! fake_enter_detected && it->operations[op_id].position != overlay::position_front ) { // TODO: calculate_from_inside() is only needed if the current Linestring is not closed bool const from_inside = first_point && calculate_from_inside(geometry, other_geometry, *it, strategy); if ( from_inside ) update(res); else update(res); // if it's the first IP then the first point is outside if ( first_point ) { bool const front_b = boundary_checker.is_endpoint_boundary( range::front(sub_range(geometry, seg_id))); // if there is a boundary on the first point if ( front_b ) { if ( from_inside ) update(res); else update(res); } } } } if BOOST_GEOMETRY_CONSTEXPR (util::is_multi::value) { m_first_from_unknown = false; m_first_from_unknown_boundary_detected = false; } } // u/u, x/u else if ( op == overlay::operation_union || op == overlay::operation_blocked ) { bool const op_blocked = op == overlay::operation_blocked; bool const no_enters_detected = m_exit_watcher.is_outside() // TODO: is this condition ok? // TODO: move it into the exit_watcher? && m_exit_watcher.get_exit_operation() == overlay::operation_none; if ( op == overlay::operation_union ) { if ( m_boundary_counter > 0 && it->operations[op_id].is_collinear ) --m_boundary_counter; } else // overlay::operation_blocked { m_boundary_counter = 0; } // we're inside, possibly going out right now if ( ! no_enters_detected ) { if ( op_blocked && it->operations[op_id].position == overlay::position_back ) // ignore spikes! { // check if this is indeed the boundary point // NOTE: is_ip_on_boundary<>() should be called here but the result will be the same if (boundary_checker.is_endpoint_boundary(it->point)) { update(res); } } // union, inside, but no exit -> collinear on self-intersection point // not needed since we're already inside the boundary /*else if ( !exit_detected ) { update(res); }*/ } // we're outside or inside and this is the first turn else { bool const this_b = is_ip_on_boundary(it->point, it->operations[op_id], boundary_checker); // if current IP is on boundary of the geometry if ( this_b ) { update(res); } // if current IP is not on boundary of the geometry else { update(res); } // TODO: very similar code is used in the handling of intersection if ( it->operations[op_id].position != overlay::position_front ) { // TODO: calculate_from_inside() is only needed if the current Linestring is not closed // NOTE: this is not enough for MultiPolygon and operation_blocked // For LS/MultiPolygon multiple x/u turns may be generated // the first checked Polygon may be the one which LS is outside for. bool const first_point = first_in_range || m_first_from_unknown; bool const first_from_inside = first_point && calculate_from_inside(geometry, other_geometry, *it, strategy); if ( first_from_inside ) { update(res); // notify the exit_watcher that we started inside m_exit_watcher.enter(*it); // and reset unknown flags since we know that we started inside m_first_from_unknown = false; m_first_from_unknown_boundary_detected = false; } else { if BOOST_GEOMETRY_CONSTEXPR (util::is_multi::value) /*&& ( op == overlay::operation_blocked || op == overlay::operation_union )*/ // if we're here it's u or x { m_first_from_unknown = true; } else { update(res); } } // first IP on the last segment point - this means that the first point is outside or inside if ( first_point && ( !this_b || op_blocked ) ) { bool const front_b = boundary_checker.is_endpoint_boundary( range::front(sub_range(geometry, seg_id))); // if there is a boundary on the first point if ( front_b ) { if ( first_from_inside ) { update(res); } else { if BOOST_GEOMETRY_CONSTEXPR (util::is_multi::value) /*&& ( op == overlay::operation_blocked || op == overlay::operation_union )*/ // if we're here it's u or x { BOOST_GEOMETRY_ASSERT(m_first_from_unknown); m_first_from_unknown_boundary_detected = true; } else { update(res); } } } } } } // if we're going along a boundary, we exit only if the linestring was collinear if ( m_boundary_counter == 0 || it->operations[op_id].is_collinear ) { // notify the exit watcher about the possible exit m_exit_watcher.exit(*it); } } // store ref to previously analysed (valid) turn m_previous_turn_ptr = boost::addressof(*it); // and previously analysed (valid) operation m_previous_operation = op; } // it == last template void apply(Result & res, TurnIt first, TurnIt last, Geometry const& geometry, OtherGeometry const& /*other_geometry*/, BoundaryChecker const& boundary_checker) { boost::ignore_unused(first, last); //BOOST_GEOMETRY_ASSERT( first != last ); // For MultiPolygon many x/u operations may be generated as a first IP // if for all turns x/u was generated and any of the Polygons doesn't contain the LineString // then we know that the LineString is outside if BOOST_GEOMETRY_CONSTEXPR (util::is_multi::value) { if (m_first_from_unknown) { update(res); if ( m_first_from_unknown_boundary_detected ) { update(res); } // done below //m_first_from_unknown = false; //m_first_from_unknown_boundary_detected = false; } } // here, the possible exit is the real one // we know that we entered and now we exit if ( /*m_exit_watcher.get_exit_operation() == overlay::operation_union // THIS CHECK IS REDUNDANT ||*/ m_previous_operation == overlay::operation_union && !m_interior_detected ) { // for sure update(res); BOOST_GEOMETRY_ASSERT(first != last); BOOST_GEOMETRY_ASSERT(m_previous_turn_ptr); segment_identifier const& prev_seg_id = m_previous_turn_ptr->operations[op_id].seg_id; bool const prev_back_b = boundary_checker.is_endpoint_boundary( range::back(sub_range(geometry, prev_seg_id))); // if there is a boundary on the last point if ( prev_back_b ) { update(res); } } // we might enter some Areal and didn't go out, else if ( m_previous_operation == overlay::operation_intersection || m_interior_detected ) { // just in case update(res); m_interior_detected = false; BOOST_GEOMETRY_ASSERT(first != last); BOOST_GEOMETRY_ASSERT(m_previous_turn_ptr); segment_identifier const& prev_seg_id = m_previous_turn_ptr->operations[op_id].seg_id; bool const prev_back_b = boundary_checker.is_endpoint_boundary( range::back(sub_range(geometry, prev_seg_id))); // if there is a boundary on the last point if ( prev_back_b ) { update(res); } } // This condition may be false if the Linestring is lying on the Polygon's collinear spike // if Polygon's spikes are not handled in get_turns() or relate() (they currently aren't) //BOOST_GEOMETRY_ASSERT_MSG(m_previous_operation != overlay::operation_continue, // "Unexpected operation! Probably the error in get_turns(L,A) or relate(L,A)"); // Currently one c/c turn is generated for the exit // when a Linestring is going out on a collinear spike // When a Linestring is going in on a collinear spike // the turn is not generated for the entry // So assume it's the former if ( m_previous_operation == overlay::operation_continue ) { update(res); segment_identifier const& prev_seg_id = m_previous_turn_ptr->operations[op_id].seg_id; bool const prev_back_b = boundary_checker.is_endpoint_boundary( range::back(sub_range(geometry, prev_seg_id))); // if there is a boundary on the last point if ( prev_back_b ) { update(res); } } // Reset exit watcher before the analysis of the next Linestring m_exit_watcher.reset(); m_boundary_counter = 0; m_first_from_unknown = false; m_first_from_unknown_boundary_detected = false; } private: exit_watcher m_exit_watcher; segment_watcher m_seg_watcher; TurnInfo * m_previous_turn_ptr; overlay::operation_type m_previous_operation; unsigned m_boundary_counter; bool m_interior_detected; const segment_identifier * m_first_interior_other_id_ptr; bool m_first_from_unknown; bool m_first_from_unknown_boundary_detected; }; // call analyser.apply() for each turn in range // IMPORTANT! The analyser is also called for the end iterator - last template static inline void analyse_each_turn(Result & res, Analyser & analyser, TurnIt first, TurnIt last, Geometry const& geometry, OtherGeometry const& other_geometry, BoundaryChecker const& boundary_checker, Strategy const& strategy) { if ( first == last ) { return; } for ( TurnIt it = first ; it != last ; ++it ) { analyser.apply(res, it, geometry, other_geometry, boundary_checker, strategy); if ( BOOST_GEOMETRY_CONDITION( res.interrupt ) ) { return; } } analyser.apply(res, first, last, geometry, other_geometry, boundary_checker); } // less comparator comparing multi_index then ring_index // may be used to sort turns by ring struct less_ring { template inline bool operator()(Turn const& left, Turn const& right) const { return left.operations[1].seg_id.multi_index < right.operations[1].seg_id.multi_index || ( left.operations[1].seg_id.multi_index == right.operations[1].seg_id.multi_index && left.operations[1].seg_id.ring_index < right.operations[1].seg_id.ring_index ); } }; // policy/functor checking if a turn's operation is operation_continue struct has_boundary_intersection { has_boundary_intersection() : result(false) {} template inline void operator()(Turn const& turn) { if ( turn.operations[1].operation == overlay::operation_continue ) result = true; } bool result; }; // iterate through the range and search for the different multi_index or ring_index // also call fun for each turn in the current range template static inline It find_next_ring(It first, It last, Fun & fun) { if ( first == last ) return last; signed_size_type const multi_index = first->operations[1].seg_id.multi_index; signed_size_type const ring_index = first->operations[1].seg_id.ring_index; fun(*first); ++first; for ( ; first != last ; ++first ) { if ( multi_index != first->operations[1].seg_id.multi_index || ring_index != first->operations[1].seg_id.ring_index ) { return first; } fun(*first); } return last; } // analyser which called for turns sorted by seg/distance/operation // checks if the boundary of Areal geometry really got out // into the exterior of Linear geometry template class areal_boundary_analyser { public: areal_boundary_analyser() : is_union_detected(false) , m_previous_turn_ptr(NULL) {} template bool apply(TurnIt /*first*/, TurnIt it, TurnIt last, Strategy const& strategy) { overlay::operation_type op = it->operations[1].operation; if ( it != last ) { if ( op != overlay::operation_union && op != overlay::operation_continue ) { return true; } if ( is_union_detected ) { BOOST_GEOMETRY_ASSERT(m_previous_turn_ptr != NULL); if ( !detail::equals::equals_point_point(it->point, m_previous_turn_ptr->point, strategy) ) { // break return false; } else if ( op == overlay::operation_continue ) // operation_boundary { is_union_detected = false; } } if ( op == overlay::operation_union ) { is_union_detected = true; m_previous_turn_ptr = boost::addressof(*it); } return true; } else { return false; } } bool is_union_detected; private: const TurnInfo * m_previous_turn_ptr; }; }; template struct areal_linear { typedef linear_areal linear_areal_type; static const bool interruption_enabled = linear_areal_type::interruption_enabled; template static inline void apply(Geometry1 const& geometry1, Geometry2 const& geometry2, Result & result, Strategy const& strategy) { linear_areal_type::apply(geometry2, geometry1, result, strategy); } template static inline void apply(Geometry1 const& geometry1, Geometry2 const& geometry2, BoundaryChecker2 const& boundary_checker2, Result & result, Strategy const& strategy) { linear_areal_type::apply(geometry2, geometry1, boundary_checker2, result, strategy); } }; }} // namespace detail::relate #endif // DOXYGEN_NO_DETAIL }} // namespace boost::geometry #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_RELATE_LINEAR_AREAL_HPP