From d56a7bcc2ebe3eadd8c2a41cb5aaccfc9cb05f6c Mon Sep 17 00:00:00 2001 From: Adam Wulkiewicz Date: Fri, 7 Feb 2014 18:58:53 +0100 Subject: [PATCH] relate(L,L) algorithm without preliminary boundaries analisys - work in progress --- .../detail/relate/linear_linear.hpp | 409 ++++++++++++++---- 1 file changed, 336 insertions(+), 73 deletions(-) diff --git a/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp b/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp index d76ecd69d..2652f4114 100644 --- a/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp +++ b/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp @@ -25,6 +25,8 @@ namespace boost { namespace geometry #ifndef DOXYGEN_NO_DETAIL namespace detail { namespace relate { +// update_result + template struct update_result_dispatch { @@ -49,6 +51,109 @@ inline void update_result(result & res) update_result_dispatch::apply(res); } +// boundary_checker + +template ::type> +class boundary_checker {}; + +template +class boundary_checker +{ + typedef typename point_type::type point_type; + +public: + boundary_checker(Geometry const& g) + : has_boundary(! detail::equals::equals_point_point(range::front(g), range::back(g))) + , geometry(g) + {} + + // TODO: optimization expect ENTRY or EXIT + bool is_boundary(point_type const& pt, segment_identifier const& sid) + { + // TODO: replace with assert? + if ( boost::size(geometry) < 2 ) + return false; + + // TODO: handle also linestrings with points_num == 2 and equals(front, back) - treat like point? + + return has_boundary + && ( ( sid.segment_index == 0 + && detail::equals::equals_point_point(pt, range::front(geometry)) ) + || ( sid.segment_index + 2 == geometry::num_points(geometry) + && detail::equals::equals_point_point(pt, range::back(geometry)) ) ); + } + +private: + bool has_boundary; + Geometry const& geometry; +}; + +template +class boundary_checker +{ + typedef typename point_type::type point_type; + +public: + boundary_checker(Geometry const& g) + : is_filled(false), geometry(g) + {} + + // TODO: optimization expect ENTRY or EXIT + bool is_boundary(point_type const& pt, segment_identifier const& sid) + { + typedef typename boost::range_size::type size_type; + size_type multi_count = boost::size(geometry); + + // TODO: replace with assert? + if ( multi_count < 1 ) + return false; + + if ( sid.segment_index != 0 && sid.segment_index + 2 != geometry::num_points(geometry) ) + return false; + + if ( ! is_filled ) + { + //boundary_points.clear(); + boundary_points.reserve(multi_count * 2); + + typedef typename boost::range_iterator::type multi_iterator; + for ( multi_iterator it = boost::begin(geometry) ; + it != boost::end(geometry) ; ++ it ) + { + // point - no boundary + if ( boost::size(*it) < 2 ) + continue; + + // TODO: handle also linestrings with points_num == 2 and equals(front, back) - treat like point? + + boundary_points.push_back(range::front(geometry)); + boundary_points.push_back(range::back(geometry)); + } + + std::sort(boundary_points.begin(), boundary_points.end(), geometry::less()); + + is_filled = true; + } + + std::size_t equal_points_count + = boost::size( + std::equal_range(boundary_points.begin(), + boundary_points.end(), + geometry::less()) + ); + + return equal_points_count % 2 != 0 && equal_points_count > 0; // the number is odd and > 0 + + } + +private: + bool is_filled; + // TODO: store references/pointers instead of points? + std::vector boundary_points; + Geometry const& geometry; +}; + // currently works only for linestrings template struct linear_linear @@ -86,11 +191,10 @@ struct linear_linear if ( dimension < 10 ) res.template set(); - // TODO: implement generic function working also for multilinestrings, also use it in point_in_geometry - bool has_boundary1 = ! detail::equals::equals_point_point(range::front(geometry1), range::back(geometry1)); - bool has_boundary2 = ! detail::equals::equals_point_point(range::front(geometry2), range::back(geometry2)); - - handle_boundaries(res, geometry1, geometry2, has_boundary1, has_boundary2); + // TEMP + //bool has_boundary1 = ! detail::equals::equals_point_point(range::front(geometry1), range::back(geometry1)); + //bool has_boundary2 = ! detail::equals::equals_point_point(range::front(geometry2), range::back(geometry2)); + //handle_boundaries(res, geometry1, geometry2, has_boundary1, has_boundary2); // get and analyse turns @@ -103,19 +207,27 @@ struct linear_linear return res; } + boundary_checker boundary_checker1(geometry1); + boundary_checker boundary_checker2(geometry2); + // TODO: turns must be sorted and followed only if it's possible to go out and in on the same point // for linear geometries union operation must be detected which I guess would be quite often std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,1,2,4,3,0,0>()); - analyse_turns<0, 1>(res, turns.begin(), turns.end(), geometry1, geometry2, has_boundary1, has_boundary2); + analyse_turns<0, 1>(res, turns.begin(), turns.end(), + geometry1, geometry2, + boundary_checker1, boundary_checker2); std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,1,2,4,3,0,1>()); - analyse_turns<1, 0>(res, turns.begin(), turns.end(), geometry2, geometry1, has_boundary2, has_boundary1); + analyse_turns<1, 0>(res, turns.begin(), turns.end(), + geometry2, geometry1, + boundary_checker2, boundary_checker1); return res; } + // TODO: rename to point_id_ref? template class point_identifier { @@ -154,140 +266,291 @@ struct linear_linear : sid_ptr(boost::addressof(sid)) {} - template - bool operator()(point_identifier const& pid) + bool operator()(segment_identifier const& sid) const { - return pid.seg_id().multi_index == sid_ptr->multi_index - && pid.seg_id().ring_index == sid_ptr->ring_index; + return sid.multi_index == sid_ptr->multi_index + && sid.ring_index == sid_ptr->ring_index; } + + template + bool operator()(point_identifier const& pid) const + { + return operator()(pid.seg_id()); + } + private: const segment_identifier * sid_ptr; }; + class segment_watcher + { + public: + segment_watcher() + : m_seg_id_ptr(0) + {} + + bool update(segment_identifier const& seg_id) + { + bool result = m_seg_id_ptr == 0 || !same_ranges(*m_seg_id_ptr)(seg_id); + m_seg_id_ptr = boost::addressof(seg_id); + return result; + } + + private: + const segment_identifier * m_seg_id_ptr; + }; + + template + class exit_watcher + { + typedef point_identifier point_identifier; + + public: + exit_watcher() + : exit_detected(false) + {} + + // returns true if before the call we were outside + bool enter(Point const& point, segment_identifier const& other_id) + { + bool result = other_entry_points.empty(); + other_entry_points.push_back(point_identifier(other_id, point)); + return result; + } + + // returns true if before the call we were outside + bool exit(Point const& point, + segment_identifier const& other_id, + bool is_last_point) + { + // if we didn't entered anything in the past, we're outside + if ( other_entry_points.empty() ) + return true; + + typedef typename std::vector::iterator point_iterator; + // search for the entry point in the same range of other geometry + point_iterator entry_it = std::find_if(other_entry_points.begin(), + other_entry_points.end(), + same_ranges(other_id)); + + // this end point has corresponding entry point + if ( entry_it != other_entry_points.end() ) + { + if ( ! is_last_point ) + { + // here we know that we possibly left LS + // we must still check if we didn't get back on the same point + exit_detected = true; + exit_id = point_identifier(other_id, point); + } + + // erase the corresponding entry point + other_entry_points.erase(entry_it); + } + + return false; + } + + bool is_exit_detected() const + { + return exit_detected; + } + + Point const& get_exit_point() const + { + BOOST_ASSERT(exit_detected); + return exit_id.point(); + } + + overlay::operation_type get_exit_operation() const + { + BOOST_ASSERT(exit_detected); + return exit_operation; + } + + void reset_detected_exit() + { + exit_detected = false; + } + + private: + bool exit_detected; + point_identifier exit_id; + std::vector other_entry_points; // TODO: use map here or sorted vector? + }; + template + typename OtherGeometry, + typename BoundaryChecker, + typename OtherBoundaryChecker> static inline void analyse_turns(result & res, TurnIt first, TurnIt last, Geometry const& geometry, OtherGeometry const& other_geometry, - bool has_boundary, - bool other_has_boundary) + BoundaryChecker boundary_checker, + OtherBoundaryChecker other_boundary_checker) { - // should be the same as the one stored in Turn + // point_type should be the same as the one stored in Turn typedef typename point_type::type point_type; - typedef point_identifier point_identifier; static const bool reverse_result = OpId != 0; if ( first == last ) return; - typedef typename std::vector::iterator point_iterator; - std::vector other_entry_points; // TODO: use map here or sorted vector? - - bool possible_exit_detected = false; - point_identifier possible_exit_point; + exit_watcher exit_watcher; + segment_watcher seg_watcher; for ( TurnIt it = first ; it != last ; ++it ) { overlay::operation_type op = it->operations[OpId].operation; - + segment_identifier const& seg_id = it->operations[OpId].seg_id; + segment_identifier const& other_id = it->operations[OtherOpId].seg_id; + + bool first_in_range = seg_watcher.update(seg_id); + bool fake_enter_detected = false; + // handle possible exit - if ( possible_exit_detected && + if ( exit_watcher.is_exit_detected() && ( op == overlay::operation_union || op == overlay::operation_intersection || op == overlay::operation_continue || op == overlay::operation_blocked ) ) { // real exit point - may be multiple // we know that we entered and now we exit - if ( !detail::equals::equals_point_point(it->point, possible_exit_point.point()) ) + if ( !detail::equals::equals_point_point(it->point, exit_watcher.get_exit_point()) ) { - possible_exit_detected = false; + exit_watcher.reset_detected_exit(); // not the last IP update_result(res); } // fake exit point, reset state // in reality this will be op == overlay::operation_intersection - else if ( op != overlay::operation_union ) + else if ( op == overlay::operation_intersection ) { - possible_exit_detected = false; + exit_watcher.reset_detected_exit(); + fake_enter_detected = true; } } + // i/i, i/x, i/u if ( op == overlay::operation_intersection ) { - // here we know that we entered the range - other_entry_points.push_back(point_identifier(it->operations[OpId].other_id, it->point)); + bool was_outside = exit_watcher.enter(it->point, other_id); + // interiors overlaps update_result(res); - // if the first point of P and/or Q is boundary then also - //update_result(res); - // or - //update_result(res); - } - else if ( op == overlay::operation_blocked - || op == overlay::operation_union ) - { - if ( !other_entry_points.empty() ) + + // going inside on boundary point + if ( boundary_checker.is_boundary(it->point, seg_id) ) { - segment_identifier const& other_id = it->operations[OpId].other_id; - point_iterator entry_it = std::find_if(other_entry_points.begin(), - other_entry_points.end(), - same_ranges(other_id)); - if ( entry_it != other_entry_points.end() ) + // TODO: check operation_blocked here - only for Ls, for MLs it's not enough + if ( other_boundary_checker.is_boundary(it->point, other_id) ) { - if ( op == overlay::operation_union ) - { - // here we know that we possibly left LS - // we must still check if we didn't get back on the same point - possible_exit_detected = true; - possible_exit_point = point_identifier(other_id, it->point); - } - else // op == overlay::operation_blocked - { - // here we know that we're on the last point of the range - - // depending on the other operation - //update_result(res); - // or - //update_result(res); - } - - // erase the corresponding entry point, - // don't postpone the erasure decision because - // there may be multiple exit IPs on the same point - // and we'd be forced to store them all just like the entry points - other_entry_points.erase(entry_it); + update_result(res); + } + else + { + update_result(res); } } + // going inside on non-boundary point + else + { + // if we didn't enter in the past, we were outside + if ( was_outside && !fake_enter_detected ) + { + update_result(res); + + if ( first_in_range ) + update_result(res); + } + } + } + // u/i, u/u, u/x, x/i, x/u, x/x + else if ( op == overlay::operation_union || op == overlay::operation_blocked ) + { + bool is_last_point = op == overlay::operation_blocked; + bool was_outside = exit_watcher.exit(it->point, other_id, is_last_point); + + // we're inside, possibly going out right now + if ( ! was_outside ) + { + if ( is_last_point ) + { + // check if this is indeed the boundary point +// TODO: For Linestring it's enough to check has_boundary +// because we know that this is the last point of the range + if ( boundary_checker.is_boundary(it->point, seg_id) ) + { + // TODO: check operation_blocked here - only for Ls, for MLs it's not enough + if ( other_boundary_checker.is_boundary(it->point, other_id) ) + { + update_result(res); + } + else + { + update_result(res); + } + } + } + } + // we're outside else { update_result(res); + // boundaries don't overlap - just an optimization if ( it->method == overlay::method_crosses ) + { update_result(res); - // or depending on the IP - //update_result(res); - // or - //update_result(res); + if ( first_in_range ) + update_result(res); + } + else + { + // TODO: check operation_blocked here - only for Ls, for MLs it's not enough + if ( boundary_checker.is_boundary(it->point, seg_id) ) + { + // TODO: check operation_blocked here - only for Ls, for MLs it's not enough + if ( other_boundary_checker.is_boundary(it->point, other_id) ) + update_result(res); + else + update_result(res); + } + // boundaries don't overlap + else + { + update_result(res); + + if ( first_in_range ) + update_result(res); + } + } + +// TODO: if this IP is not on the last point then also B/E should be updated: +// update_result(res); + } } else if ( op == overlay::operation_continue ) { - update_result(res); + //update_result(res); } } // here, the possible exit is the real one // we know that we entered and now we exit - if ( possible_exit_detected ) + if ( exit_watcher.is_exit_detected() ) { update_result(res); - // if has boundary on the last point of the current range - //update_result(res); + +// TODO: check if the last point is indeed the boundary +// For LS it's sufficient to check has_boundary + update_result(res); } }