From 16967cc7f8a3b51cf6a68ed2e5c72003ca8fc9ab Mon Sep 17 00:00:00 2001 From: Barend Gehrels Date: Wed, 2 Sep 2015 14:27:19 +0200 Subject: [PATCH] [overlay] add handle_touch to examine u/u turns First phase: check if a u/u turn should be traversed or discarded, including unit test --- .../detail/overlay/handle_touch.hpp | 259 +++++++++++ test/algorithms/overlay/handle_touch.cpp | 431 ++++++++++++++++++ 2 files changed, 690 insertions(+) create mode 100644 include/boost/geometry/algorithms/detail/overlay/handle_touch.hpp create mode 100644 test/algorithms/overlay/handle_touch.cpp diff --git a/include/boost/geometry/algorithms/detail/overlay/handle_touch.hpp b/include/boost/geometry/algorithms/detail/overlay/handle_touch.hpp new file mode 100644 index 000000000..4af84919c --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/handle_touch.hpp @@ -0,0 +1,259 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) + +// Copyright (c) 2015 Barend Gehrels, 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_ALGORITHMS_DETAIL_OVERLAY_HANDLE_TOUCH_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_HANDLE_TOUCH_HPP + +#include + +#include +#include + +#include + +#include +#include +#include +#include + + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ + + +template +class handle_touch_uu +{ +private : + typedef typename boost::range_value::type turn_type; + typedef typename boost::range_iterator::type turn_iterator; + typedef typename boost::range_iterator::type turn_const_iterator; + + typedef typename boost::range_iterator + < + typename turn_type::container_type const + >::type operation_const_iterator; + + typedef std::map > map_type; + +public : + static inline void apply(detail::overlay::operation_type operation, + Turns& turns) + { + if (! has_uu(turns)) + { + // Performance - if there is no u/u at all, nothing to be done + return; + } + + map_type turns_per_ring; + create_ring_map(turns, turns_per_ring); + + handle(turns, turns_per_ring); + } + +private : + + // Generic utility to be moved somewhere else + static inline + ring_identifier ring_id_from_seg_id(const segment_identifier& seg_id) + { + return ring_identifier(seg_id.source_index, + seg_id.multi_index, + seg_id.ring_index); + } + + static inline + ring_identifier ring_id_from_op(const turn_type& turn, + int operation_index) + { + return ring_id_from_seg_id(turn.operations[operation_index].seg_id); + } + + static inline bool has_uu(const Turns& turns) + { + for (turn_const_iterator it = boost::begin(turns); + it != boost::end(turns); + ++it) + { + const turn_type& turn = *it; + if (turn.both(operation_union)) + { + return true; + } + } + return false; + } + + // Create a map of turns per ring (in both sources), excluding u/u turns + // and other discarded turns + static inline void create_ring_map(const Turns& turns, map_type& map) + { + int index = 0; + for (turn_const_iterator it = boost::begin(turns); + it != boost::end(turns); + ++it, ++index) + { + const turn_type& turn = *it; + if (! turn.both(operation_union) && ! turn.discarded) + { + map[ring_id_from_op(turn, 0)].push_back(index); + map[ring_id_from_op(turn, 1)].push_back(index); + } + } + } + + + static inline void handle(Turns& turns, const map_type& map) + { + // Iterate through all u/u points + int index = 0; + for (turn_iterator it = boost::begin(turns); + it != boost::end(turns); + ++it, ++index) + { + turn_type& turn = *it; + if (turn.both(operation_union)) + { + bool traverse = turn_should_be_traversed(turns, turn, map); +#ifdef BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + std::cout << " " << index << " " + << std::boolalpha << traverse + << std::endl; +#endif + + if (traverse) + { + // Remove the discarded flag + turn.discarded = false; + + // TODO: insert into sequence + } + } + } + } + + static inline bool turn_should_be_traversed(const Turns& turns, + const turn_type& uu_turn, + const map_type& map) + { + // Suppose this is a u/u turn between P and Q + // Examine all other turns on P and check if Q can be reached + + ring_identifier const ring_id_p = ring_id_from_op(uu_turn, 0); + ring_identifier const ring_id_q = ring_id_from_op(uu_turn, 1); + + // Use one of the operations and check if you can reach the other + + map_type::const_iterator mit = map.find(ring_id_p); + if (mit == map.end()) + { + // No other turns found + return false; + } + +#ifdef BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + std::cout << " Check p: " << ring_id_p << " q: " << ring_id_q << std::endl; +#endif + + for (std::vector::const_iterator vit = mit->second.begin(); + vit != mit->second.end(); + ++vit) + { + int const turn_index = *vit; + const turn_type& current_turn = turns[turn_index]; +#ifdef BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + std::cout << "-> Examine " << turn_index << std::endl; +#endif + if (can_reach(turns, current_turn, ring_id_q, turn_index)) + { + return true; + } + } + + return false; + } + + static inline bool can_reach(const Turns& turns, + const turn_type& turn, + const ring_identifier& target_ring_id, + signed_size_type original_turn_index, + std::size_t iteration = 0) + { + signed_size_type const turns_size = + static_cast(boost::size(turns)); + + // For any union operation in both operations, check if the specified + // ring can be reached again + for (operation_const_iterator it = boost::begin(turn.operations); + it != boost::end(turn.operations); + ++it) + { + if (it->operation == operation_union) + { + signed_size_type index = it->enriched.travels_to_ip_index; + if (index == original_turn_index) + { + // Traveled through, not found + return false; + } + if (index >= 0 && index < turns_size) + { +#ifdef BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + std::cout << " Now to " << index << std::endl; +#endif + const turn_type& new_turn = turns[index]; + + + ring_identifier const ring_id1 = ring_id_from_op(new_turn, 0); + ring_identifier const ring_id2 = ring_id_from_op(new_turn, 1); + if (ring_id1 == target_ring_id + || ring_id2 == target_ring_id) + { +#ifdef BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + std::cout << " Found!" << std::endl; +#endif + return true; + } + + if (iteration >= boost::size(turns)) + { + // Defensive check to avoid infinite recursion + return false; + } + + // Recursively check this turn + if (can_reach(turns, new_turn, target_ring_id, + original_turn_index, iteration + 1)) + { + return true; + } + } + } + } + return false; + } +}; + +template +inline void handle_touch(detail::overlay::operation_type operation, + Turns& turns) +{ + handle_touch_uu::apply(operation, turns); +} + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_HANDLE_TOUCH_HPP diff --git a/test/algorithms/overlay/handle_touch.cpp b/test/algorithms/overlay/handle_touch.cpp new file mode 100644 index 000000000..a914afe7a --- /dev/null +++ b/test/algorithms/overlay/handle_touch.cpp @@ -0,0 +1,431 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) +// Unit Test + +// Copyright (c) 2015 Barend Gehrels, 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) + +#define BOOST_GEOMETRY_DEBUG_IDENTIFIER +#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER + +#define BOOST_GEOMETRY_DEBUG_HANDLE_TOUCH + + +#include +#include +#include +#include +#include + + +#include + + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +#include +#include +#include + +#include + +#include + + +#if defined(TEST_WITH_SVG) +# include +#endif + + + + +namespace detail +{ + +template +< + typename G1, typename G2, + bg::detail::overlay::operation_type Direction, + bool Reverse1, bool Reverse2 + > +struct test_handle_touch +{ + + static void apply(std::string const& case_id, + std::size_t expected_traverse, + std::size_t expected_discarded, + G1 const& g1, G2 const& g2) + { + + typedef typename bg::strategy::side::services::default_strategy + < + typename bg::cs_tag::type + >::type side_strategy_type; + + typedef typename bg::point_type::type point_type; + typedef typename bg::rescale_policy_type::type + rescale_policy_type; + + rescale_policy_type rescale_policy + = bg::get_rescale_policy(g1, g2); + + typedef bg::detail::overlay::traversal_turn_info + < + point_type, + typename bg::segment_ratio_type::type + > turn_info; + std::vector turns; + + bg::detail::get_turns::no_interrupt_policy policy; + bg::get_turns(g1, g2, rescale_policy, turns, policy); + bg::enrich_intersection_points(turns, + Direction == 1 ? bg::detail::overlay::operation_union + : bg::detail::overlay::operation_intersection, + g1, g2, rescale_policy, side_strategy_type()); + + typedef bg::model::ring::type> ring_type; + typedef std::vector out_vector; + + std::cout << "*** Case: " << case_id << std::endl; + + bg::detail::overlay::handle_touch(Direction, turns); + + // Check number of resulting u/u turns + + std::size_t uu_traverse = 0; + std::size_t uu_discarded = 0; + BOOST_FOREACH(turn_info const& turn, turns) + { + if (turn.both(bg::detail::overlay::operation_union)) + { + if (turn.discarded) + { + uu_discarded++; + } + else + { + uu_traverse++; + } + } + } + + BOOST_CHECK_MESSAGE(expected_traverse == uu_traverse, + "handle_touch: " << case_id + << " traverse expected: " << expected_traverse + << " detected: " << uu_traverse + << " type: " << string_from_type + ::type>::name()); + BOOST_CHECK_MESSAGE(expected_discarded == uu_discarded, + "handle_touch: " << case_id + << " discarded expected: " << expected_discarded + << " detected: " << uu_discarded + << " type: " << string_from_type + ::type>::name()); + +#if defined(TEST_WITH_SVG) + { + std::ostringstream filename; + filename << "handle_touch" + << "_" << case_id + << "_" << string_from_type::type>::name() + << ".svg"; + + std::ofstream svg(filename.str().c_str()); + + bg::svg_mapper::type> mapper(svg, 500, 500); + mapper.add(g1); + mapper.add(g2); + + // Input shapes in green/blue + mapper.map(g1, "fill-opacity:0.5;fill:rgb(153,204,0);" + "stroke:rgb(153,204,0);stroke-width:3"); + mapper.map(g2, "fill-opacity:0.3;fill:rgb(51,51,153);" + "stroke:rgb(51,51,153);stroke-width:3"); + + + // turn points in orange, + enrichment/traversal info + typedef typename bg::coordinate_type::type coordinate_type; + + // Simple map to avoid two texts at same place (note that can still overlap!) + std::map, int> offsets; + int index = 0; + int const margin = 5; + + BOOST_FOREACH(turn_info const& turn, turns) + { + int lineheight = 8; + mapper.map(turn.point, "fill:rgb(255,128,0);" + "stroke:rgb(0,0,0);stroke-width:1", 3); + + { + coordinate_type half = 0.5; + coordinate_type ten = 10; + // Map characteristics + // Create a rounded off point + std::pair p + = std::make_pair( + boost::numeric_cast(half + + ten * bg::get<0>(turn.point)), + boost::numeric_cast(half + + ten * bg::get<1>(turn.point)) + ); + + std::string color = "fill:rgb(0,0,0);"; + std::string fontsize = "font-size:8px;"; + + if (turn.both(bg::detail::overlay::operation_union)) + { + // Adapt color to give visual feedback in SVG + if (turn.discarded) + { + color = "fill:rgb(255,0,0);"; // red + } + else + { + color = "fill:rgb(0,128,0);"; // green + } + } + else if (turn.discarded) + { + color = "fill:rgb(92,92,92);"; + fontsize = "font-size:6px;"; + lineheight = 6; + } + const std::string style = color + fontsize + "font-family:Arial;"; + + { + std::ostringstream out; + out << index + << ": " << bg::method_char(turn.method) + << std::endl + << "op: " << bg::operation_char(turn.operations[0].operation) + << " / " << bg::operation_char(turn.operations[1].operation) + //<< (turn.is_discarded() ? " (discarded) " : turn.blocked() ? " (blocked)" : "") + << std::endl; + + if (turn.operations[0].enriched.next_ip_index != -1) + { + out << "ip: " << turn.operations[0].enriched.next_ip_index; + } + else + { + out << "vx: " << turn.operations[0].enriched.travels_to_vertex_index + << " -> ip: " << turn.operations[0].enriched.travels_to_ip_index; + } + out << " / "; + if (turn.operations[1].enriched.next_ip_index != -1) + { + out << "ip: " << turn.operations[1].enriched.next_ip_index; + } + else + { + out << "vx: " << turn.operations[1].enriched.travels_to_vertex_index + << " -> ip: " << turn.operations[1].enriched.travels_to_ip_index; + } + + out << std::endl; + + + + offsets[p] += lineheight; + int offset = offsets[p]; + offsets[p] += lineheight * 3; + mapper.text(turn.point, out.str(), style, margin, offset, lineheight); + } + index++; + } + } + } +#endif + } +}; +} + +template +< + typename G1, typename G2, + bg::detail::overlay::operation_type Direction, + bool Reverse1 = false, + bool Reverse2 = false + > +struct test_handle_touch +{ + typedef detail::test_handle_touch + < + G1, G2, Direction, Reverse1, Reverse2 + > detail_test_handle_touch; + + inline static void apply(std::string const& case_id, + std::size_t expected_traverse, + std::size_t expected_discarded, + std::string const& wkt1, std::string const& wkt2) + { + if (wkt1.empty() || wkt2.empty()) + { + return; + } + + G1 g1; + bg::read_wkt(wkt1, g1); + + G2 g2; + bg::read_wkt(wkt2, g2); + + bg::correct(g1); + bg::correct(g2); + + detail_test_handle_touch::apply(case_id, + expected_traverse, expected_discarded, + g1, g2); + + } +}; + + +template +void test_geometries() +{ + namespace ov = bg::detail::overlay; + + typedef test_handle_touch + < + MultiPolygon, MultiPolygon, + ov::operation_intersection + > test_handle_touch_intersection; + typedef test_handle_touch + < + MultiPolygon, MultiPolygon, + ov::operation_union + > test_handle_touch_union; + + test_handle_touch_union::apply + ( + "case_36", 1, 0, + "MULTIPOLYGON(((1 0,0 3,4 2,1 0)))", + "MULTIPOLYGON(((1 5,5 5,4 2,3 3,2 1,1 2,1 5)))" + ); + test_handle_touch_union::apply + ( + "case_85", 1, 0, + "MULTIPOLYGON(((0 0,0 40,40 40,40 0,0 0),(10 10,30 10,30 30,10 30,10 10)))", + "MULTIPOLYGON(((5 15,5 30,30 15,5 15)))" + ); + + test_handle_touch_union::apply + ( + "uu_case_1", 0, 1, + "MULTIPOLYGON(((4 0,2 2,4 4,6 2,4 0)))", + "MULTIPOLYGON(((4 4,2 6,4 8,6 6,4 4)))" + ); + test_handle_touch_union::apply + ( + "uu_case_2", 0, 2, + "MULTIPOLYGON(((0 0,0 2,2 4,4 2,6 4,8 2,8 0,0 0)))", + "MULTIPOLYGON(((0 8,8 8,8 6,6 4,4 6,2 4,0 6,0 8)))" + ); + + // Provided by Menelaos (1) + test_handle_touch_union::apply + ( + "uu_case_3", 0, 2, + "MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((15 5,15 10,20 10,20 5,15 5)))", + "MULTIPOLYGON(((10 0,15 5,15 0,10 0)),((10 5,10 10,15 10,15 5,10 5)))" + ); + // Provided by Menelaos (2) + test_handle_touch_union::apply + ( + "uu_case_4", 1, 0, + "MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((15 5,15 10,20 10,20 5,15 5)))", + "MULTIPOLYGON(((10 0,15 5,20 5,20 0,10 0)),((10 5,10 10,15 10,15 5,10 5)))" + ); + + // Mailed by Barend + test_handle_touch_union::apply + ( + "uu_case_5", 1, 0, + "MULTIPOLYGON(((4 0,2 2,4 4,6 2,4 0)),((4 6,6 8,8 6,6 4,4 6)))", + "MULTIPOLYGON(((4 4,2 6,4 8,6 6,4 4)),((4 2,7 6,8 3,4 2)))" + ); + + // Formerly referred to as a + test_handle_touch_union::apply + ( + "uu_case_6", 2, 0, + "MULTIPOLYGON(((4 8,4 10,6 10,6 8,4 8)),((7 7,7 11,10 11,10 7,7 7)))", + "MULTIPOLYGON(((6 6,6 8,8 8,8 6,6 6)),((6 10,6 12,8 12,8 10,6 10)),((9 9,11 9,11 2,3 2,3 9,5 9,5 3,9 3,9 9)))" + ); + // Should result in 1 polygon with 2 holes + // "POLYGON((4 9,4 10,6 10,6 12,8 12,8 11,10 11,10 9,11 9,11 2,3 2,3 9,4 9),(6 10,6 8,7 8,7 10,6 10),(6 8,5 8,5 3,9 3,9 7,8 7,8 6,6 6,6 8))" + + // Formerly referred to as b + test_handle_touch_union::apply + ( + "uu_case_7", 0, 2, + "MULTIPOLYGON(((4 8,4 10,6 10,6 8,4 8)),((7 7,7 11,10 11,10 7,7 7)))", + "MULTIPOLYGON(((6 6,6 8,8 8,8 6,6 6)),((6 10,6 12,8 12,8 10,6 10)))" + ); + // Should result in 2 polygons + // "MULTIPOLYGON(((4 8,4 10,6 10,6 8,4 8)),((7 8,7 10,6 10,6 12,8 12,8 11,10 11,10 7,8 7,8 6,6 6,6 8,7 8)))" + + // Formerly referred to as c + test_handle_touch_union::apply + ( + "uu_case_8", 0, 4, + "MULTIPOLYGON(((4 8,4 10,6 10,6 8,4 8)),((8 8,8 10,10 10,10 8,8 8)),((7 11,7 13,13 13,13 5,7 5,7 7,11 7,11 11,7 11)))", + "MULTIPOLYGON(((6 6,6 8,8 8,8 6,6 6)),((6 10,6 12,8 12,8 10,6 10)))" + ); + + // Shoud result in 3 polygons: + // "MULTIPOLYGON(((4 8,4 10,6 10,6 8,4 8)),((8 8,8 10,10 10,10 8,8 8)),((7 12,7 13,13 13,13 5,7 5,7 6,6 6,6 8,8 8,8 7,11 7,11 11,8 11,8 10,6 10,6 12,7 12)))" + + // Formerly referred to as d + test_handle_touch_union::apply + ( + "uu_case_9", 0, 2, + "MULTIPOLYGON(((2 4,2 6,4 6,4 4,2 4)),((6 4,6 6,8 6,8 4,6 4)),((1 0,1 3,9 3,9 0,1 0)))", + "MULTIPOLYGON(((0 2,0 4,2 4,2 2,0 2)),((8 2,8 4,10 4,10 2,8 2)),((3 5,3 7,7 7,7 5,3 5)))" + ); + // Should result in 2 polygons: + // "MULTIPOLYGON(((2 4,2 6,3 6,3 7,7 7,7 6,8 6,8 4,6 4,6 5,4 5,4 4,2 4)),((1 0,1 2,0 2,0 4,2 4,2 3,8 3,8 4,10 4,10 2,9 2,9 0,1 0)))" + +} + + +template +void test_all() +{ + typedef bg::model::point point_type; + + typedef bg::model::multi_polygon + < + bg::model::polygon + > multi_polygon; + + typedef bg::model::multi_polygon + < + bg::model::polygon + > multi_polygon_ccw; + + test_geometries(); + // test_geometries(); +} + + +int test_main(int, char* []) +{ + test_all(); + + return 0; +}