From f2fea86f7ebc373259f03b802bed5b3b3cccb47c Mon Sep 17 00:00:00 2001 From: Adam Wulkiewicz Date: Tue, 1 Apr 2014 22:20:29 +0200 Subject: [PATCH] [relate] fix special case of L/L and L/A - going out and in on the same point but with different segment. Linestring going into the exterior of the second geometry and then going inside on the same point. In this case the exterior wasn't taken into account. Now in addition to the Point, seg_id is checked. The check should also be faster since integral segment ids are checked before Points. exit_watcher methods now take Turn instead of Point, seg_id and other_id. Also added some tests for non-simple A/A. --- .../algorithms/detail/relate/areal_areal.hpp | 44 ++++++------ .../detail/relate/follow_helpers.hpp | 69 ++++++++++++++++--- .../algorithms/detail/relate/linear_areal.hpp | 12 ++-- .../detail/relate/linear_linear.hpp | 14 ++-- test/algorithms/relate.cpp | 37 +++++++++- 5 files changed, 129 insertions(+), 47 deletions(-) diff --git a/include/boost/geometry/algorithms/detail/relate/areal_areal.hpp b/include/boost/geometry/algorithms/detail/relate/areal_areal.hpp index 90274fd48..794c0c54c 100644 --- a/include/boost/geometry/algorithms/detail/relate/areal_areal.hpp +++ b/include/boost/geometry/algorithms/detail/relate/areal_areal.hpp @@ -133,22 +133,20 @@ struct areal_areal if ( turns.empty() ) return; -// -// { -// // for different multi or same ring id: x, u, i, c -// // for same multi and different ring id: c, i, u, x -// std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,2,3,1,4,0,0>()); -// -// turns_analyser analyser; -// analyse_each_turn(result, analyser, -// turns.begin(), turns.end(), -// geometry1, geometry2, -// boundary_checker1); -// -// if ( result.interrupt ) -// return; -// } -// + //{ + // // for different multi or same ring id: u, i, x, c + // // for same multi and different ring id: c, x, i, u + // std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,1,2,3,4,0,0>()); + + // turns_analyser analyser; + // analyse_each_turn(result, analyser, + // turns.begin(), turns.end(), + // geometry1, geometry2); + + // if ( result.interrupt ) + // return; + //} + //// TODO: CALCULATE THE FOLLOWING ONLY IF IT'S REQUIRED BY THE RESULT! //// AND ONLY IF IT WAS NOT SET BY THE no_turns_la_areal_pred // @@ -326,7 +324,9 @@ struct areal_areal else if ( op == overlay::operation_blocked ) { update(m_result); - update(m_result); + // NOTE: currently E^E is set without any checks before turns are gathered + // so currently the following line is redundant + //update(m_result); } } @@ -387,7 +387,7 @@ struct areal_areal { // real exit point - may be multiple // we know that we entered and now we exit - if ( !detail::equals::equals_point_point(it->point, m_exit_watcher.get_exit_point()) ) + if ( ! turn_on_the_same_ip(m_exit_watcher.get_exit_turn(), *it) ) { m_exit_watcher.reset_detected_exit(); @@ -425,7 +425,7 @@ struct areal_areal if ( m_interior_detected ) { // real interior overlap - if ( !detail::equals::equals_point_point(it->point, m_previous_turn_ptr->point) ) + if ( ! turn_on_the_same_ip(*m_previous_turn_ptr, *it) ) { update(res); m_interior_detected = false; @@ -467,7 +467,7 @@ struct areal_areal || op == overlay::operation_continue ) // operation_boundary/operation_boundary_intersection { bool no_enters_detected = m_exit_watcher.is_outside(); - m_exit_watcher.enter(it->point, other_id); + m_exit_watcher.enter(*it); if ( op == overlay::operation_intersection ) { @@ -640,7 +640,7 @@ struct areal_areal || it->operations[op_id].is_collinear ) { // notify the exit watcher about the possible exit - m_exit_watcher.exit(it->point, other_id, op); + m_exit_watcher.exit(*it); } } @@ -837,7 +837,7 @@ struct areal_areal } private: - exit_watcher m_exit_watcher; + exit_watcher m_exit_watcher; segment_watcher m_seg_watcher; TurnInfo * m_previous_turn_ptr; overlay::operation_type m_previous_operation; diff --git a/include/boost/geometry/algorithms/detail/relate/follow_helpers.hpp b/include/boost/geometry/algorithms/detail/relate/follow_helpers.hpp index eed9a8228..7e42a356f 100644 --- a/include/boost/geometry/algorithms/detail/relate/follow_helpers.hpp +++ b/include/boost/geometry/algorithms/detail/relate/follow_helpers.hpp @@ -127,6 +127,9 @@ struct for_each_disjoint_geometry_if } }; +// WARNING! This class stores pointers! +// Passing a reference to local variable will result in undefined behavior! + // TODO: rename to point_id_ref? template class point_identifier @@ -159,6 +162,8 @@ private: const Point * pt_ptr; }; +// WARNING! This class stores pointers! +// Passing a reference to local variable will result in undefined behavior! class same_multi_index { public: @@ -181,6 +186,8 @@ private: const segment_identifier * sid_ptr; }; +// WARNING! This class stores pointers! +// Passing a reference to local variable will result in undefined behavior! class segment_watcher { public: @@ -199,25 +206,35 @@ private: const segment_identifier * m_seg_id_ptr; }; -template +// WARNING! This class stores pointers! +// Passing a reference to local variable will result in undefined behavior! +template class exit_watcher { - typedef point_identifier point_info; + static const std::size_t op_id = OpId; + static const std::size_t other_op_id = (OpId + 1) % 2; + + typedef typename TurnInfo::point_type point_type; + typedef point_identifier point_info; public: exit_watcher() : exit_operation(overlay::operation_none) + , exit_turn(0) {} - void enter(Point const& point, segment_identifier const& other_id) + void enter(TurnInfo const& turn) { - other_entry_points.push_back(point_info(other_id, point)); + other_entry_points.push_back( + point_info(turn.operations[other_op_id].seg_id, turn.point) ); } - void exit(Point const& point, - segment_identifier const& other_id, - overlay::operation_type exit_op) + void exit(TurnInfo const& turn) { + segment_identifier const& seg_id = turn.operations[op_id].seg_id; + segment_identifier const& other_id = turn.operations[other_op_id].seg_id; + overlay::operation_type exit_op = turn.operations[op_id].operation; + 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(), @@ -230,7 +247,7 @@ public: // here we know that we possibly left LS // we must still check if we didn't get back on the same point exit_operation = exit_op; - exit_id = point_info(other_id, point); + exit_turn = boost::addressof(turn); // erase the corresponding entry point other_entry_points.erase(entry_it); @@ -248,10 +265,18 @@ public: return exit_operation; } - Point const& get_exit_point() const + point_type const& get_exit_point() const { BOOST_ASSERT(exit_operation != overlay::operation_none); - return exit_id.point(); + BOOST_ASSERT(exit_turn); + return exit_turn->point; + } + + TurnInfo const& get_exit_turn() const + { + BOOST_ASSERT(exit_operation != overlay::operation_none); + BOOST_ASSERT(exit_turn); + return *exit_turn; } void reset_detected_exit() @@ -267,10 +292,32 @@ public: private: overlay::operation_type exit_operation; - point_info exit_id; + const TurnInfo * exit_turn; std::vector other_entry_points; // TODO: use map here or sorted vector? }; +template +inline bool turn_on_the_same_ip(Turn const& prev_turn, Turn const& curr_turn) +{ + segment_identifier const& prev_seg_id = prev_turn.operations[OpId].seg_id; + segment_identifier const& curr_seg_id = curr_turn.operations[OpId].seg_id; + + if ( prev_seg_id.multi_index != curr_seg_id.multi_index + || prev_seg_id.ring_index != curr_seg_id.ring_index ) + { + return false; + } + + if ( prev_seg_id.segment_index != curr_seg_id.segment_index + && ( ! geometry::math::equals(curr_turn.operations[OpId].enriched.distance, 0) + || prev_seg_id.segment_index + 1 != curr_seg_id.segment_index ) ) + { + return false; + } + + return detail::equals::equals_point_point(prev_turn.point, curr_turn.point); +} + template diff --git a/include/boost/geometry/algorithms/detail/relate/linear_areal.hpp b/include/boost/geometry/algorithms/detail/relate/linear_areal.hpp index 948f5eb96..59de09059 100644 --- a/include/boost/geometry/algorithms/detail/relate/linear_areal.hpp +++ b/include/boost/geometry/algorithms/detail/relate/linear_areal.hpp @@ -442,7 +442,7 @@ struct linear_areal { // real exit point - may be multiple // we know that we entered and now we exit - if ( !detail::equals::equals_point_point(it->point, m_exit_watcher.get_exit_point()) ) + if ( ! turn_on_the_same_ip(m_exit_watcher.get_exit_turn(), *it) ) { m_exit_watcher.reset_detected_exit(); @@ -480,7 +480,7 @@ struct linear_areal if ( m_interior_detected ) { // real interior overlap - if ( !detail::equals::equals_point_point(it->point, m_previous_turn_ptr->point) ) + if ( ! turn_on_the_same_ip(*m_previous_turn_ptr, *it) ) { update(res); m_interior_detected = false; @@ -522,7 +522,7 @@ struct linear_areal || op == overlay::operation_continue ) // operation_boundary/operation_boundary_intersection { bool no_enters_detected = m_exit_watcher.is_outside(); - m_exit_watcher.enter(it->point, other_id); + m_exit_watcher.enter(*it); if ( op == overlay::operation_intersection ) { @@ -664,7 +664,7 @@ struct linear_areal update(res); // notify the exit_watcher that we started inside - m_exit_watcher.enter(it->point, other_id); + m_exit_watcher.enter(*it); } else { @@ -695,7 +695,7 @@ struct linear_areal || it->operations[op_id].is_collinear ) { // notify the exit watcher about the possible exit - m_exit_watcher.exit(it->point, other_id, op); + m_exit_watcher.exit(*it); } } @@ -892,7 +892,7 @@ struct linear_areal } private: - exit_watcher m_exit_watcher; + exit_watcher m_exit_watcher; segment_watcher m_seg_watcher; TurnInfo * m_previous_turn_ptr; overlay::operation_type m_previous_operation; diff --git a/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp b/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp index ba9e0f137..a485845e4 100644 --- a/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp +++ b/include/boost/geometry/algorithms/detail/relate/linear_linear.hpp @@ -245,7 +245,7 @@ struct linear_linear // x, u, i, c std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,2,3,1,4,0,0>()); - turns_analyser<0, turn_type> analyser; + turns_analyser analyser; analyse_each_turn(result, analyser, turns.begin(), turns.end(), geometry1, geometry2, @@ -259,7 +259,7 @@ struct linear_linear // x, u, i, c std::sort(turns.begin(), turns.end(), turns::less_seg_dist_op<0,2,3,1,4,0,1>()); - turns_analyser<1, turn_type> analyser; + turns_analyser analyser; analyse_each_turn(result, analyser, turns.begin(), turns.end(), geometry2, geometry1, @@ -311,7 +311,7 @@ struct linear_linear }; // This analyser should be used like Input or SinglePass Iterator - template + template class turns_analyser { typedef typename TurnInfo::point_type turn_point_type; @@ -361,7 +361,7 @@ struct linear_linear { // real exit point - may be multiple // we know that we entered and now we exit - if ( !detail::equals::equals_point_point(it->point, m_exit_watcher.get_exit_point()) ) + if ( ! turn_on_the_same_ip(m_exit_watcher.get_exit_turn(), *it) ) { m_exit_watcher.reset_detected_exit(); @@ -413,7 +413,7 @@ struct linear_linear if ( op == overlay::operation_intersection ) { bool was_outside = m_exit_watcher.is_outside(); - m_exit_watcher.enter(it->point, other_id); + m_exit_watcher.enter(*it); // interiors overlaps update(res); @@ -479,7 +479,7 @@ struct linear_linear // to exit we must be currently inside and the current segment must be collinear if ( !was_outside && is_collinear ) { - m_exit_watcher.exit(it->point, other_id, op); + m_exit_watcher.exit(*it); } bool op_blocked = op == overlay::operation_blocked; @@ -638,7 +638,7 @@ struct linear_linear } private: - exit_watcher m_exit_watcher; + exit_watcher m_exit_watcher; segment_watcher m_seg_watcher; TurnInfo * m_previous_turn_ptr; overlay::operation_type m_previous_operation; diff --git a/test/algorithms/relate.cpp b/test/algorithms/relate.cpp index b9e0d2565..4be48128f 100644 --- a/test/algorithms/relate.cpp +++ b/test/algorithms/relate.cpp @@ -40,7 +40,7 @@ #include //TEST -//#include +#include namespace bgdr = bg::detail::relate; @@ -318,6 +318,10 @@ void test_linestring_linestring() test_geometry("LINESTRING(1 0,5 0,7 0,8 1)", "LINESTRING(0 0,10 0,10 10,4 -1)", "1F10F0102"); + // self-IP going out and in on the same point + test_geometry("LINESTRING(2 0,5 0,5 5,6 5,5 0,8 0)", "LINESTRING(1 0,9 0)", + "1F10FF102"); + // duplicated points test_geometry("LINESTRING(1 1, 2 2, 2 2)", "LINESTRING(0 0, 2 2, 4 2)", "1FF0FF102"); test_geometry("LINESTRING(1 1, 1 1, 2 2)", "LINESTRING(0 0, 2 2, 4 2)", "1FF0FF102"); @@ -530,6 +534,13 @@ void test_linestring_polygon() "POLYGON((0 0,0 10,10 10,10 0,0 0),(2 2,5 5,2 8,2 2))", "F1FFFF212"); + // self-IP going on the boundary then into the exterior and to the boundary again + test_geometry("LINESTRING(2 10,5 10,5 15,6 15,5 10,8 10)", "POLYGON((0 0,0 10,10 10,10 0,0 0))", + "F11F0F212"); + // self-IP going on the boundary then into the interior and to the boundary again + test_geometry("LINESTRING(2 10,5 10,5 5,6 5,5 10,8 10)", "POLYGON((0 0,0 10,10 10,10 0,0 0))", + "11FF0F212"); + // ccw { typedef bg::model::polygon ccwpoly; @@ -666,6 +677,30 @@ void polygon_polygon() test_geometry("POLYGON((0 0,0 10,10 10,10 0,0 0))", "POLYGON((0 20,0 30,10 30,10 20,0 20))", "FF2FF1212"); + + // NON-SIMPLE + + // equal non-simple / hole + test_geometry("POLYGON((0 0,0 10,10 10,10 0,0 0),(5 5,10 5,5 6,5 5))", + "POLYGON((0 0,0 10,10 10,10 5,5 6,5 5,10 5,10 0,0 0))", + "2FFF1FFF2"); + to_svg("POLYGON((0 0,0 10,10 10,10 0,0 0),(5 5,10 5,5 6,5 5))", + "POLYGON((0 0,0 10,10 10,10 5,5 6,5 5,10 5,10 0,0 0))", + "a.svg"); + + to_svg("POLYGON((0 0,0 10,10 10,10 0,0 0),(5 5,10 5,5 6,5 5))", + "POLYGON((0 0,5 5,10 5,10 0,0 0))", + "b1.svg"); + to_svg("POLYGON((0 0,5 5,10 5,10 0,0 0))", + "POLYGON((0 0,0 10,10 10,10 0,0 0),(5 5,10 5,5 6,5 5))", + "b2.svg"); + + to_svg("POLYGON((0 0,0 10,10 10,10 0,0 0))", + "POLYGON((0 0,0 10,10 10,10 5,5 6,5 5,10 5,10 0,0 0))", + "c1.svg"); + to_svg("POLYGON((0 0,0 10,10 10,10 5,5 6,5 5,10 5,10 0,0 0))", + "POLYGON((0 0,0 10,10 10,10 0,0 0))", + "c2.svg"); } template