diff --git a/CMakeLists.txt b/CMakeLists.txt index 750938c0a5..46009fe144 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ target_link_libraries(boost_geometry Boost::type_traits Boost::utility Boost::variant + + Boost::graph ) # Required for Boost.Geometry Index @@ -112,7 +114,10 @@ if(BUILD_TESTING AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/test/CMakeLists.txt") serialization tokenizer variant - test) + test + + graph + ) if (BOOST_SRC_DIR_IS_VALID) set(BOOST_EXCLUDE_LIBRARIES ${PROJECT_NAME}) diff --git a/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp b/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp new file mode 100644 index 0000000000..33d44f58f2 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp @@ -0,0 +1,227 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_DETECT_ARTICULATION_POINTS_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DETECT_ARTICULATION_POINTS_HPP + +#include +#include + +#include +#include +#include + +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ + + +struct vertex_info +{ + signed_size_type node_id{0}; + std::set target_vertex_indices; + + bool is_extra{false}; + + // For extra nodes, also store the original node + signed_size_type original_node_id{0}; +}; + +struct state_type +{ + // Maps from vertex to vertex info + std::map vertex_map; + + // Reverse mapping. Every node (turn or cluster) has only ONE vertex, + // but there might be additional vertices, not associated with a node. + std::map node_to_vertex_index; + + // Keeps track of vertex index, which must, for Boost.Graph, + // be consecutive. The turn index is not (because of discarded and clusters). + std::size_t vertex_index{0}; + + // For some cases (returning to itself) extra nodes are inserted. + // They are numbered from turn.size() and up. + std::size_t extra_node_id{0}; +}; + +void add_target_node(signed_size_type source_node_id, signed_size_type target_node_id, + state_type& state, bool allow_returning) +{ + // Insert the source and target node (turn or cluster) + auto it_source = state.node_to_vertex_index.find(source_node_id); + if (it_source == state.node_to_vertex_index.end()) + { + it_source = state.node_to_vertex_index.insert({source_node_id, state.vertex_index++}).first; + } + + auto it_target = state.node_to_vertex_index.find(target_node_id); + if (it_target == state.node_to_vertex_index.end()) + { + it_target = state.node_to_vertex_index.insert({target_node_id, state.vertex_index++}).first; + } + + // Get the accompanying vertex into (might be a new record) + auto& vertex_info = state.vertex_map[it_source->second]; + vertex_info.node_id = source_node_id; + + if (target_node_id == source_node_id && allow_returning) + { + std::size_t const extra_node_id = state.extra_node_id++; + + // Add a brand new vertex, and add the target to it + // To keep the index right, first this brand new vertex as the target + vertex_info.target_vertex_indices.insert(state.vertex_index); + state.node_to_vertex_index.insert({extra_node_id, state.vertex_index}); + auto& extra_vertex_info = state.vertex_map[state.vertex_index++]; + extra_vertex_info.node_id = extra_node_id; + extra_vertex_info.is_extra = true; + extra_vertex_info.original_node_id = source_node_id; + extra_vertex_info.target_vertex_indices.insert(it_target->second); + } + else + { + vertex_info.target_vertex_indices.insert(it_target->second); + } +} + + +template +void fill_vertex_map(Turns const& turns, Clusters const& clusters, state_type& state) +{ + std::set visited_turns; + for (std::size_t i = 0; i < turns.size(); i++) + { + auto const & turn = turns[i]; + if (turn.discarded) + { + continue; + } + if (visited_turns.count(i) > 0) + { + continue; + } + + auto const source_node_id = get_node_id(turns, i); + auto const turn_indices = get_turn_indices_by_node_id(turns, clusters, source_node_id); + + visited_turns.insert(turn_indices.begin(), turn_indices.end()); + + auto const target_nodes = get_target_nodes(turns, clusters, turn_indices); + + for (auto const& target_node_id : target_nodes) + { + add_target_node(source_node_id, target_node_id, state, true); + } + } +} + +// Assigns biconnected components to turns +template +void assign_biconnected_component_ids(Turns& turns, Clusters const& clusters, + Graph const& graph, Components const& component, state_type const& state) +{ + auto node_id_from_it = [&state](auto const& it) + { + return it->second.is_extra + ? it->second.original_node_id + : it->second.node_id; + }; + + typename graph_traits::edge_iterator ei, ei_end; + for (boost::tie(ei, ei_end) = edges(graph); ei != ei_end; ++ei) + { + auto it_source = state.vertex_map.find(source(*ei, graph)); + auto it_target = state.vertex_map.find(target(*ei, graph)); + if (it_source == state.vertex_map.end() || it_target == state.vertex_map.end()) + { + continue; + } + + auto const source_node_id = node_id_from_it(it_source); + auto const target_node_id = node_id_from_it(it_target); + + auto const source_turn_indices = get_turn_indices_by_node_id(turns, clusters, source_node_id); + + // Assign the component to all the operations + // going from the source node to the target node. + for (auto const& turn_index : source_turn_indices) + { + auto& source_turn = turns[turn_index]; + for (std::size_t j = 0; j < 2; j++) + { + auto& op = source_turn.operations[j]; + if (op.enriched.travels_to_ip_index < 0) + { + continue; + } + auto const travels_to_node_id = get_node_id(turns, op.enriched.travels_to_ip_index); + if (travels_to_node_id == target_node_id) + { + op.enriched.component_id = static_cast(component[*ei]); + } + } + } + } +} + +template +void detect_biconnected_components(Turns& turns, Clusters const& clusters) +{ + using graph_t = boost::adjacency_list + < + boost::vecS, + boost::vecS, + boost::undirectedS, + boost::no_property, + boost::property + >; + using vertex_t = boost::graph_traits ::vertex_descriptor; + + // Mapping to add turns to vertices, count them, and then build the graph. + // (It is convenient if the vertex index is the same as the turn index. + // Therefore the default mapping is made like that, extra vertices + // are added later) + + state_type state; + state.extra_node_id = static_cast(turns.size()); + + fill_vertex_map(turns, clusters, state); + + // Build the graph from the vertices + graph_t graph(state.vertex_map.size()); + for (auto const& key_value : state.vertex_map) + { + auto const vertex_index = key_value.first; + for (auto const& target_vertex_index : key_value.second.target_vertex_indices) + { + add_edge(vertex_index, target_vertex_index, graph); + } + } + + edge_component ec; + auto component = get(ec, graph); + biconnected_components(graph, component); + fix_components(component, graph); + + assign_biconnected_component_ids(turns, clusters, graph, component, state); +} + + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DETECT_ARTICULATION_POINTS_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp b/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp index e01c13f749..f40804d27b 100644 --- a/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/enrichment_info.hpp @@ -41,7 +41,6 @@ struct enrichment_info , rank(-1) , zone(-1) , region_id(-1) - , isolated(false) {} inline signed_size_type get_next_turn_index() const @@ -69,7 +68,8 @@ struct enrichment_info signed_size_type rank; // in cluster signed_size_type zone; // open zone, in cluster signed_size_type region_id; - bool isolated; + + signed_size_type component_id{-1}; }; diff --git a/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp b/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp new file mode 100644 index 0000000000..924de19d43 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/graph_util.hpp @@ -0,0 +1,61 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_GRAPH_UTIL_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP + +#include +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ + +struct edge_component +{ + using kind = edge_property_tag; +}; + +// It appears that in an undirected graph, the components for two edges are sometimes different. +// Fix that. To be found out why this is. +template +void fix_components(Components& components, Graph const& g) +{ + typename graph_traits::edge_iterator ei, ei_end; + for (boost::tie(ei, ei_end) = edges(g); ei != ei_end; ++ei) + { + auto& component = components[*ei]; + + auto const source_vertex = source(*ei, g); + auto const target_vertex = target(*ei, g); + + // Get the reverse edge and its component + auto const reverse_edge_pair = edge(target_vertex, source_vertex, g); + if (! reverse_edge_pair.second) + { + continue; + } + + auto& reverse_component = components[reverse_edge_pair.first]; + + if (component != reverse_component) + { + component = reverse_component; + } + } +} + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/node_util.hpp b/include/boost/geometry/algorithms/detail/overlay/node_util.hpp new file mode 100644 index 0000000000..391382da62 --- /dev/null +++ b/include/boost/geometry/algorithms/detail/overlay/node_util.hpp @@ -0,0 +1,146 @@ +// Boost.Geometry + +// Copyright (c) 2024 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_NODE_UTIL_HPP +#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_NODE_UTIL_HPP + +#include + +namespace boost { namespace geometry +{ + +#ifndef DOXYGEN_NO_DETAIL +namespace detail { namespace overlay +{ +template +std::set get_turn_indices_by_cluster_id(Turns const& turns, Clusters const& clusters, signed_size_type cluster_id) +{ + std::set result; + auto it = clusters.find(cluster_id); + if (it != clusters.end()) + { + result.insert(it->second.turn_indices.begin(), it->second.turn_indices.end()); + } + return result; +} + +// Returns the node id of the turn: +// - if it is clustered, the negative cluster_id +// - if it is not clustered, the turn index +template +signed_size_type get_node_id(Turns const& turns, std::size_t turn_index) +{ + auto const& turn = turns[turn_index]; + return turn.is_clustered() ? -turn.cluster_id : turn_index; +} + +template +std::set get_turn_indices_by_node_id(Turns const& turns, Clusters const& clusters, signed_size_type node_id) +{ + if (node_id < 0) + { + return get_turn_indices_by_cluster_id(turns, clusters, -node_id); + } + if (node_id >= turns.size()) + { + // It is 'allowed' to have node_ids larger than the largest turn index (for example extra + // nodes in a graph). But they are not related to turns. + return {}; + } + + auto const turn_index = static_cast(node_id); + auto const& turn = turns[turn_index]; + if (turn.is_clustered()) + { + return get_turn_indices_by_cluster_id(turns, clusters, turn.cluster_id); + } + return {turn_index}; +} + +// TODO: later union too +template +bool is_target_operation(Operation const& op, std::size_t turn_count, int op_index) +{ + return + op.enriched.travels_to_ip_index >= 0 + && op.enriched.travels_to_ip_index < static_cast(turn_count) + && (op.operation == TargetOperation || (op_index == 0 && op.operation == operation_continue)); +} + +template +auto get_blocked_nodes(Turns const& turns, Clusters const& clusters, + std::set const& turn_indices) +{ + std::set blocked_nodes; + for (std::size_t turn_index : turn_indices) + { + auto const& turn = turns[turn_index]; + + if (turn.discarded) + { + continue; + } + if (! turn.combination(TargetOperation, operation_blocked)) + { + continue; + } + + for (std::size_t j = 0; j < 2; j++) + { + auto const& op = turn.operations[j]; + if (op.operation == operation_blocked) + { + blocked_nodes.insert(get_node_id(turns, op.enriched.travels_to_ip_index)); + } + } + } + return blocked_nodes; +} + +template +auto get_target_nodes(Turns const& turns, Clusters const& clusters, + std::set const& turn_indices) +{ + auto const blocked_nodes = get_blocked_nodes(turns, clusters, turn_indices); + + std::set target_nodes; + for (std::size_t turn_index : turn_indices) + { + auto const& turn = turns[turn_index]; + if (turn.discarded) + { + continue; + } + + bool const is_clustered_self = turn.is_self() && turn.is_clustered(); + for (std::size_t j = 0; j < 2; j++) + { + auto const& op = turn.operations[j]; + + if (is_target_operation(op, turns.size(), j)) + { + auto const target_node_id = get_node_id(turns, op.enriched.travels_to_ip_index); + if (is_clustered_self && blocked_nodes.count(target_node_id) > 0) + { + continue; + } + target_nodes.insert(target_node_id); + } + } + } + + return target_nodes; +} + + +}} // namespace detail::overlay +#endif // DOXYGEN_NO_DETAIL + +}} // namespace boost::geometry + +#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_NODE_UTIL_HPP diff --git a/include/boost/geometry/algorithms/detail/overlay/traversal.hpp b/include/boost/geometry/algorithms/detail/overlay/traversal.hpp index 653f1930fe..f980fac8e3 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traversal.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traversal.hpp @@ -609,8 +609,14 @@ public : inline sort_by_side::rank_type select_rank(sbs_type const& sbs) const { - static bool const is_intersection - = target_operation == operation_intersection; + auto is_isolated = [](turn_operation_type const& op1, turn_operation_type const& op2) -> bool + { + return //(op1.seg_id.ring_index >=0 || op2.seg_id.ring_index >= 0) && + op1.enriched.component_id >= 0 && op2.enriched.component_id >= 0 + && op1.enriched.component_id != op2.enriched.component_id; + }; + + static bool const is_intersection = target_operation == operation_intersection; // Take the first outgoing rank corresponding to incoming region, // or take another region if it is not isolated @@ -631,8 +637,8 @@ public : continue; } - if (in_op.enriched.region_id == out_op.enriched.region_id - || (is_intersection && ! out_op.enriched.isolated)) + if ((in_op.enriched.region_id == out_op.enriched.region_id) + || (is_intersection && ! is_isolated(in_op, out_op))) { // Region corresponds to incoming region, or (for intersection) // there is a non-isolated other region which should be taken diff --git a/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp b/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp index 0756034afa..9f70a8f9c2 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traversal_switch_detector.hpp @@ -74,12 +74,9 @@ namespace detail { namespace overlay // Switching using region_id is only relevant for UU or II turns. // In all T turns it will follow "u" for union or "i" for intersection, // and in C turns it will follow either direction (they are the same). -// There is also "isolated", making it more complex, and documented below. template < - bool Reverse1, - bool Reverse2, - overlay_type OverlayType, + operation_type TargetOperation, typename Geometry1, typename Geometry2, typename Turns, @@ -88,16 +85,6 @@ template > struct traversal_switch_detector { - static const operation_type target_operation - = operation_from_overlay::value; - - enum isolation_type - { - isolation_no = 0, - isolation_yes = 1, - isolation_multiple = 2 - }; - using turn_type = typename boost::range_value::type; using set_type= std::set; @@ -109,24 +96,11 @@ struct traversal_switch_detector set_type turn_indices; }; - struct connection_properties - { - std::size_t count = 0; - // Set with turn-index OR (if clustered) the negative cluster_id - set_type unique_turn_ids; - }; - - // Maps region_id -> properties - using connection_map = std::map; - // Per region, a set of properties is maintained, including its connections // to other regions struct region_properties { signed_size_type region_id = -1; - isolation_type isolated = isolation_no; - set_type unique_turn_ids; - connection_map connected_region_counts; }; // Maps ring -> properties @@ -147,292 +121,6 @@ struct traversal_switch_detector { } - bool one_connection_to_another_region(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | - // | / \| - // | | x - // | \__/| - // | | - // +----------------------+ - - if (region.connected_region_counts.size() == 1) - { - auto const& cprop = region.connected_region_counts.begin()->second; - return cprop.count <= 1; - } - return region.connected_region_counts.empty(); - } - - // TODO: might be combined with previous - bool multiple_connections_to_one_region(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | - // | / \| - // | | x - // | \ /| - // | / \| - // | | x - // | \__/| - // | | - // +----------------------+ - - if (region.connected_region_counts.size() == 1) - { - auto const& cprop = region.connected_region_counts.begin()->second; - return cprop.count > 1; - } - return false; - } - - bool one_connection_to_multiple_regions(region_properties const& region) const - { - // For example: - // +----------------------+ - // | __ | __ - // | / \|/ | - // | | x | - // | \__/|\__| - // | | - // +----------------------+ - - bool first = true; - signed_size_type first_turn_id = 0; - for (auto const& key_val : region.connected_region_counts) - { - auto const& cprop = key_val.second; - - if (cprop.count != 1) - { - return false; - } - auto const unique_turn_id = *cprop.unique_turn_ids.begin(); - if (first) - { - first_turn_id = unique_turn_id; - first = false; - } - else if (first_turn_id != unique_turn_id) - { - return false; - } - } - return true; - } - - bool ii_turn_connects_two_regions(region_properties const& region, - region_properties const& connected_region, - signed_size_type turn_index) const - { - turn_type const& turn = m_turns[turn_index]; - if (! turn.both(operation_intersection)) - { - return false; - } - - signed_size_type const id0 = turn.operations[0].enriched.region_id; - signed_size_type const id1 = turn.operations[1].enriched.region_id; - - return (id0 == region.region_id && id1 == connected_region.region_id) - || (id1 == region.region_id && id0 == connected_region.region_id); - } - - - bool isolated_multiple_connection(region_properties const& region, - region_properties const& connected_region) const - { - if (connected_region.isolated != isolation_multiple) - { - return false; - } - - // First step: compare turns of regions with turns of connected region - set_type turn_ids = region.unique_turn_ids; - for (auto turn_id : connected_region.unique_turn_ids) - { - turn_ids.erase(turn_id); - } - - // There should be one connection (turn or cluster) left - if (turn_ids.size() != 1) - { - return false; - } - - for (auto id_or_index : connected_region.unique_turn_ids) - { - if (id_or_index >= 0) - { - if (! ii_turn_connects_two_regions(region, connected_region, id_or_index)) - { - return false; - } - } - else - { - signed_size_type const cluster_id = -id_or_index; - auto it = m_clusters.find(cluster_id); - if (it != m_clusters.end()) - { - cluster_info const& cinfo = it->second; - for (auto turn_index : cinfo.turn_indices) - { - if (! ii_turn_connects_two_regions(region, connected_region, turn_index)) - { - return false; - } - } - } - } - } - - return true; - } - - bool has_only_isolated_children(region_properties const& region) const - { - bool first_with_turn = true; - signed_size_type first_turn_id = 0; - - for (auto const& key_val : region.connected_region_counts) - { - signed_size_type const region_id = key_val.first; - connection_properties const& cprop = key_val.second; - - auto mit = m_connected_regions.find(region_id); - if (mit == m_connected_regions.end()) - { - // Should not occur - return false; - } - - region_properties const& connected_region = mit->second; - - if (cprop.count != 1) - { - // If there are more connections, check their isolation - if (! isolated_multiple_connection(region, connected_region)) - { - return false; - } - } - - if (connected_region.isolated != isolation_yes - && connected_region.isolated != isolation_multiple) - { - signed_size_type const unique_turn_id = *cprop.unique_turn_ids.begin(); - if (first_with_turn) - { - first_turn_id = unique_turn_id; - first_with_turn = false; - } - else if (first_turn_id != unique_turn_id) - { - return false; - } - } - } - - // If there is only one connection (with a 'parent'), and all other - // connections are itself isolated, it is isolated - return true; - } - - void get_isolated_regions() - { - // First time: check regions isolated (one connection only), - // semi-isolated (multiple connections between same region), - // and complex isolated (connection with multiple rings but all - // at same point) - for (auto& key_val : m_connected_regions) - { - region_properties& properties = key_val.second; - if (one_connection_to_another_region(properties)) - { - properties.isolated = isolation_yes; - } - else if (multiple_connections_to_one_region(properties)) - { - properties.isolated = isolation_multiple; - } - else if (one_connection_to_multiple_regions(properties)) - { - properties.isolated = isolation_yes; - } - } - - // Propagate isolation to next level - // TODO: should be optimized - std::size_t defensive_check = 0; - bool changed = true; - while (changed && defensive_check++ < m_connected_regions.size()) - { - changed = false; - for (auto& key_val : m_connected_regions) - { - region_properties& properties = key_val.second; - - if (properties.isolated == isolation_no - && has_only_isolated_children(properties)) - { - properties.isolated = isolation_yes; - changed = true; - } - } - } - } - - void assign_isolation_to_enriched() - { - for (turn_type& turn : m_turns) - { - constexpr auto order1 = geometry::point_order::value; - constexpr bool reverse1 = (order1 == boost::geometry::counterclockwise) - ? ! Reverse1 : Reverse1; - - constexpr auto order2 = geometry::point_order::value; - constexpr bool reverse2 = (order2 == boost::geometry::counterclockwise) - ? ! Reverse2 : Reverse2; - - // For difference, for the input walked through in reverse, - // the meaning is reversed: what is isolated is actually not, - // and vice versa. - bool const reverseMeaningInTurn - = (reverse1 || reverse2) - && ! turn.is_self() - && ! turn.is_clustered() - && uu_or_ii(turn) - && turn.operations[0].enriched.region_id - != turn.operations[1].enriched.region_id; - - for (auto& op : turn.operations) - { - auto mit = m_connected_regions.find(op.enriched.region_id); - if (mit != m_connected_regions.end()) - { - bool const reverseMeaningInOp - = reverseMeaningInTurn - && ((op.seg_id.source_index == 0 && reverse1) - || (op.seg_id.source_index == 1 && reverse2)); - - // It is assigned to isolated if it's property is "Yes", - // (one connected interior, or chained). - // "Multiple" doesn't count for isolation, - // neither for intersection, neither for difference. - region_properties const& prop = mit->second; - op.enriched.isolated - = reverseMeaningInOp - ? false - : prop.isolated == isolation_yes; - } - } - } - } - void assign_region_ids_to_enriched() { for (auto const& key_val : m_turns_per_ring) @@ -477,31 +165,10 @@ struct traversal_switch_detector if (id0 != -1) { m_connected_regions[id0].region_id = id0; - m_connected_regions[id0].unique_turn_ids.insert(unique_turn_id); } if (id1 != -1 && id0 != id1) { m_connected_regions[id1].region_id = id1; - m_connected_regions[id1].unique_turn_ids.insert(unique_turn_id); - } - - if (id0 != id1 && id0 != -1 && id1 != -1) - { - // Assign connections - connection_properties& prop0 = m_connected_regions[id0].connected_region_counts[id1]; - connection_properties& prop1 = m_connected_regions[id1].connected_region_counts[id0]; - - // Reference this turn or cluster to later check uniqueness on ring - if (prop0.unique_turn_ids.count(unique_turn_id) == 0) - { - prop0.count++; - prop0.unique_turn_ids.insert(unique_turn_id); - } - if (prop1.unique_turn_ids.count(unique_turn_id) == 0) - { - prop1.count++; - prop1.unique_turn_ids.insert(unique_turn_id); - } } } } @@ -532,7 +199,7 @@ struct traversal_switch_detector return ! uu_or_ii(turn); } - if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_union) + if BOOST_GEOMETRY_CONSTEXPR (TargetOperation == operation_union) { // It is a cluster, check zones // (assigned by sort_by_side/handle colocations) of both operations @@ -606,41 +273,11 @@ struct traversal_switch_detector #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) void debug_show_results() - { - auto isolation_to_string = [](isolation_type const& iso) -> std::string - { - switch(iso) - { - case isolation_no : return "no"; - case isolation_yes : return "yes"; - case isolation_multiple : return "multiple"; - } - return "error"; - }; - auto set_to_string = [](auto const& s) -> std::string - { - std::ostringstream result; - for (auto item : s) { result << " " << item; } - return result.str(); - }; - + { for (auto const& kv : m_connected_regions) { auto const& prop = kv.second; - - std::ostringstream sub; - sub << "[turns" << set_to_string(prop.unique_turn_ids) - << "] regions"; - for (auto const& kvs : prop.connected_region_counts) - { - sub << " { " << kvs.first - << " : via [" << set_to_string(kvs.second.unique_turn_ids) - << " ] }"; - } - std::cout << "REGION " << prop.region_id - << " " << isolation_to_string(prop.isolated) - << " " << sub.str() << std::endl; } @@ -654,13 +291,10 @@ struct traversal_switch_detector << " " << turn_index << " (" << geometry::get<0>(turn.point) << ", " << geometry::get<1>(turn.point) << ")" - << " -> " << std::boolalpha - << " [" << turn.operations[0].seg_id.source_index + << " -> [" << turn.operations[0].seg_id.source_index << "/" << turn.operations[1].seg_id.source_index << "] " << "(" << turn.operations[0].enriched.region_id - << " " << turn.operations[0].enriched.isolated - << ") / (" << turn.operations[1].enriched.region_id - << " " << turn.operations[1].enriched.isolated << ")" + << ") / (" << turn.operations[1].enriched.region_id << ")" << std::endl; } } @@ -677,10 +311,7 @@ struct traversal_switch_detector void iterate() { #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) - std::cout << "BEGIN SWITCH DETECTOR (region_ids and isolation)" - << (Reverse1 ? " REVERSE_1" : "") - << (Reverse2 ? " REVERSE_2" : "") - << std::endl; + std::cout << "BEGIN SWITCH DETECTOR (region_ids and isolation)" << std::endl; #endif // Collect turns per ring @@ -691,7 +322,7 @@ struct traversal_switch_detector { turn_type const& turn = m_turns[turn_index]; - if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_intersection) + if BOOST_GEOMETRY_CONSTEXPR (TargetOperation == operation_intersection) { if (turn.discarded) { @@ -716,8 +347,6 @@ struct traversal_switch_detector assign_region_ids_to_enriched(); assign_connected_regions(); - get_isolated_regions(); - assign_isolation_to_enriched(); } #if defined(BOOST_GEOMETRY_DEBUG_TRAVERSAL_SWITCH_DETECTOR) diff --git a/include/boost/geometry/algorithms/detail/overlay/traverse.hpp b/include/boost/geometry/algorithms/detail/overlay/traverse.hpp index 6c0b4488e8..79589b3f6e 100644 --- a/include/boost/geometry/algorithms/detail/overlay/traverse.hpp +++ b/include/boost/geometry/algorithms/detail/overlay/traverse.hpp @@ -18,8 +18,10 @@ #include #include +#include #include #include +#include namespace boost { namespace geometry @@ -76,9 +78,16 @@ public : Clusters& clusters, Visitor& visitor) { + constexpr operation_type target_operation = operation_from_overlay::value; + + if BOOST_GEOMETRY_CONSTEXPR (target_operation == operation_intersection) + { + detect_biconnected_components(turns, clusters); + } + traversal_switch_detector < - Reverse1, Reverse2, OverlayType, + target_operation, Geometry1, Geometry2, Turns, Clusters, Visitor diff --git a/test/algorithms/overlay/multi_overlay_cases.hpp b/test/algorithms/overlay/multi_overlay_cases.hpp index 1f2cd53343..32783a6e16 100644 --- a/test/algorithms/overlay/multi_overlay_cases.hpp +++ b/test/algorithms/overlay/multi_overlay_cases.hpp @@ -1463,6 +1463,32 @@ static std::string case_recursive_boxes_88[2] = "MULTIPOLYGON(((4 0,4 1,5 1,5 0,4 0)),((3 4,3 3,1 3,1 5,3 5,2.5 4.5,3 4)),((3 4,3 5,4 5,4 4,3 4)),((1 1,2 0,1 0,1 1)),((1 1,1 2,2 2,2 1,1 1)),((1 2,0 2,0 3,1 3,1 2)),((4 4,5 4,5 2,4 2,4 4)))" }; +static std::string case_recursive_boxes_89[2] = +{ + "MULTIPOLYGON(((1 3,2 3,2 2,1 2,1 3)),((1 1,2 2,2 1,1 1)))", + "MULTIPOLYGON(((1 0,1 3,3 3,3 2,2 2,3 1,2 1,1 0)))" +}; + +static std::string case_recursive_boxes_90[2] = +{ + "MULTIPOLYGON(((5 3,5 4,6 4,5 3)),((7 1,6 1,6 2,7 1)),((5 1,5 2,6 2,5 1)))", + "MULTIPOLYGON(((7 1,4 1,5 2,5 3,6 3,6 2,7 2,7 1)),((4 3,5 2,4 2,4 3)),((7 5,6 4,6 5,7 5)))" +}; + +static std::string case_bitset_1[2] = +{ + "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", + "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" +}; + +static std::string case_bitset_2[3] = +{ + // (The last one is an invalid version of the first one) + "MULTIPOLYGON(((12 2,12 0,6 0,6 2,8 2,8 4,10 4,10 8,12 8,12 6,14 6,14 2,12 2)),((4 2,4 0,0 0,0 2,2 2,2 4,0 4,0 10,2 10,2 14,6 14,6 12,8 12,8 14,10 14,10 12,12 12,12 10,8 10,8 8,8 6,4 6,4 4,6 4,6 2,4 2),(4 8,2 8,2 6,4 6,4 8),(6 8,6 10,4 10,4 8,6 8)),((14 10,14 8,12 8,12 10,14 10)),((12 12,12 14,14 14,14 12,12 12)))", + "MULTIPOLYGON(((6 2,6 0,0 0,0 2,2 2,2 4,4 4,4 2,6 2)),((10 2,10 0,8 0,8 2,10 2)),((14 4,14 2,12 2,12 4,14 4)))", + "MULTIPOLYGON(((14 10,14 8,12 8,12 6,14 6,14 2,12 2,12 0,6 0,6 2,4 2,4 0,0 0,0 2,2 2,2 4,0 4,0 10,2 10,2 14,6 14,6 12,8 12,8 14,10 14,10 12,12 12,12 10,14 10),(12 10,8 10,8 8,8 6,4 6,4 4,6 4,6 2,8 2,8 4,10 4,10 8,12 8,12 10),(4 6,4 8,2 8,2 6,4 6),(4 8,6 8,6 10,4 10,4 8)),((14 12,12 12,12 14,14 14,14 12)))" +}; + static std::string pie_21_7_21_0_3[2] = { "MULTIPOLYGON(((2500 2500,2500 3875,2855 3828,3187 3690,3472 3472,3690 3187,3828 2855,3875 2500,3828 2144,3690 1812,3472 1527,3187 1309,2855 1171,2499 1125,2144 1171,1812 1309,1527 1527,1309 1812,1171 2144,1125 2499,1171 2855,1309 3187,2500 2500)))", @@ -1781,12 +1807,6 @@ static std::string issue_1349_inverse[2] = "MULTIPOLYGON(((-1000 -1000,-1000 3000,3000 3000,3000 -1000,-1000 -1000),(1592.0101999999999407 1149.8399999999999181, 1586.9901999999999589 1149.8399999999999181, 1586.9901999999999589 1178.0000000000000000, 1592.0101999999999407 1178.0000000000000000, 1592.0101999999999407 1149.8399999999999181),(2001.9988000000000739 230.0014999999999930, 2001.9988000000000739 0.0015000000000000, 1000.9238000000000284 0.0000000000000000, 1000.9238000000000284 230.0765000000000100, 2001.9988000000000739 230.0014999999999930)))" }; -static std::string issue_1350_comment[2] = -{ - "MULTIPOLYGON(((2 10,2 8,0 8,0 10,2 10)),((10 8,10 2,8 2,8 0,0 0,0 4,2 4,2 8,4 8,4 10,6 10,6 8,6 6,8 6,8 8,10 8),(8 2,8 4,4 4,4 2,8 2)))", - "MULTIPOLYGON(((2 6,2 4,2 2,2 0,0 0,0 6,2 6)),((2 10,2 8,0 8,0 10,2 10)),((6 8,6 6,2 6,2 8,6 8)),((8 4,8 2,6 2,6 4,8 4)),((10 8,10 6,8 6,8 8,10 8)),((8 10,8 8,6 8,6 10,8 10)))" -}; - static std::string issue_1354[2] = { "MULTIPOLYGON(((295.199999999999989 -295.199999999999989,295.199999999999989 295.199999999999989,-295.199999999999989 295.199999999999989,-295.199999999999989 -295.199999999999989,295.199999999999989 -295.199999999999989),(-179.997681584096682 -159.068022953381728,-179.997941555125578 -158.950077048237887,-180 -158.832148820362704,-180 -14.5241121286848625,-179.998787156680379 -14.4546283814396119,-179.999195199119924 -14.3851352478099308,-179.995132365653177 -14.2452455437218966,-179.992689924229154 -14.1053181682548523,-179.989052871933012 -14.0359190762519734,-179.987035390416196 -13.9664540354863789,-179.978092951651348 -13.8267913392705939,-179.970768603117904 -13.6870344437553584,-179.96471177303323 -13.6178045590987882,-179.960271225550514 -13.5484522436487858,-179.899302518930739 -12.8010583222347041,-179.862001646775752 -12.4192286301278614,-179.812514069087342 -12.0387864551623842,-179.756717919995367 -11.6575893141408002,-179.69354553378804 -11.2702990769800557,-179.617743949785876 -10.8852814175903685,-179.449050736785438 -10.0966985448598905,-179.370713374286112 -9.75497969251792263,-179.282427103876586 -9.41569507620525137,-179.18426728065765 -9.07913428598321026,-178.935424198586816 -8.27008653807789607,-178.812412221610401 -7.89249729706248626,-178.67697357776251 -7.51918515533267851,-178.52206744438692 -7.11329246102736157,-178.372718942212316 -6.74078412878685995,-178.210997411857903 -6.37347828985937515,-177.83796586524096 -5.56325522558086671,-177.683643331306087 -5.24079944287654875,-177.519784336166936 -4.92308352260890025,-177.346534296916872 -4.61038942260158002,-176.888642811189385 -3.8122348268109878,-176.682925174829364 -3.46716074752500347,-176.4657719066781 -3.12916618508967126,-176.203261097574938 -2.73523344331954199,-175.97364845346371 -2.40292746742542196,-175.732982121071331 -2.078537355980135,-175.143129804051284 -1.310831533374758,-174.920901565835123 -1.03035395323309942,-174.690409107515819 -0.756627283942919782,-174.451857396213086 -0.489894938965182547,-173.780108917183412 0.239037812837519681,-173.533488205359163 0.498765442860035435,-173.279226152669509 0.751017352386726955,-173.017549264610096 0.995568826627545533,-172.318055071282714 1.62999550300293983,-172.067822666567167 1.85068903895734671,-171.811551390238662 2.06434045156852441,-171.549439357600335 2.27078457361611186,-171.281689199261223 2.46986180961050472,-171.031950157793972 2.65021087534550936,-170.768065071279523 2.835433324854856,-170.499299340935181 3.01349982052276921,-170.225846968387685 3.18428182963124229,-169.947905338195852 3.34765607758145833,-169.680262243237394 3.50017672557883053,-169.398031980415396 3.65602528487362832,-169.111716805616084 3.80423566024079918,-168.821523388081118 3.94470086983453072,-168.527661196461878 4.07731952245764262,-168.245202320314831 4.20025185193289374,-167.947883471472693 4.32492822022349799,-167.64732257736847 4.44157230975317141,-167.343736590149689 4.55009992398729324,-167.037344645549581 4.6504327250600852,-166.743329216418374 4.74235824805159822,-166.434352475578294 4.83442381331231275,-166.123013965408205 4.91815568760162414,-165.809538417600521 4.99349343122062095,-165.494152106413281 5.06038266355761301,-165.191972700917376 5.12024067253396975,-164.874903279844006 5.17863311131773951,-164.556379617287462 5.2284866074688674,-164.23663163135754 5.26976517552396029,-163.915890123910316 5.3024390196108131,-163.609033520920434 5.32953882673178825,-163.287530010959983 5.35358436207679489,-162.965496567522251 5.36898423202898734,-162.643165642162018 5.375727320588501,-162.320769901163743 5.37380876043037148,-162.012776848660081 5.36783766723553946,-161.690549005093743 5.35725884322304502,-161.368721650836875 5.33802739140809557,-161.047527088684063 5.31015719351933235,-160.727197164665256 5.27366836694196373,-160.421621523858249 5.23469527250653854,-160.102387459886472 5.18961415576110952,-159.784479686856571 5.13597328949175136,-159.468127678314318 5.07381139297753059,-159.153559784816935 5.00317333618393612,-158.853927522957889 4.93164794210813451,-158.541370807245158 4.85258471329888774,-158.231050880339666 4.7651533822312695,-157.923191738695721 4.66941705896188797,-157.618015602513111 4.56544484829227759,-157.327784107566345 4.46219246969767802,-157.025511260386196 4.35005942129268952,-156.726359890085519 4.22984647550804382,-156.430545931383392 4.1016404049654156,-156.138282909974265 3.96553375191790813,-155.860801096610373 3.83174579432549756,-155.572299975036202 3.68783680385857515,-155.287769000407764 3.53622935297387997,-155.007413554087719 3.37703287560877818,-154.731436003441075 3.21036228364411969,-154.46990567067823 3.04758249561918326,-154.198505222998278 2.87355809593285194,-153.931877781715087 2.69230550356012799,-153.670215804986441 2.50395555102093859,-153.413708166777326 2.30864419388203324,-153.171147053156858 2.11875085407343189,-152.87864945418977 1.88227751953715927,-152.593706706865078 1.63675335759936136,-152.316598756515816 1.38241958634308926,-152.047597851087176 1.11952607895504297,-151.758492641328019 0.827987423984159143,-151.497863063903992 0.556792463263721338,-151.245860873059797 0.277562487870442354,-151.002733651293767 -0.00942816944216584574,-150.768720261787024 -0.303897551347931838,-150.580866676220865 -0.54804178071738896,-150.387712159765329 -0.806177457173726353,-150.201562005690334 -1.06940894241931783,-150.022550581694702 -1.33754622957983571,-149.850807102568638 -1.61039577065407258,-149.690225111389282 -1.87328129413026989,-149.633025334764199 -1.97070420726289952,-149.574266655031067 -2.06719491150401513,-149.526997232690093 -2.15129174077182661,-149.478152856477351 -2.23448369888000897,-149.424387935046411 -2.33384350802175922)))", diff --git a/test/algorithms/overlay/overlay.cpp b/test/algorithms/overlay/overlay.cpp index dd7b760648..ee6135ccc1 100644 --- a/test/algorithms/overlay/overlay.cpp +++ b/test/algorithms/overlay/overlay.cpp @@ -55,6 +55,35 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v return; } + auto label_component = [](auto const& turn) + { + std::ostringstream out; + auto const& c0 = turn.operations[0].enriched.component_id; + auto const& c1 = turn.operations[1].enriched.component_id; + if (c0 < 0 && c1 < 0) + { + out << "-"; + } + else if (c0 == c1) + { + out << c0; + } + else if (c0 < 0) + { + out << c1; + } + else if (c1 < 0) + { + out << c0; + } + else + { + out << c0 << " | " << c1; + } + return out.str(); + }; + + for (auto const& enumerated : boost::geometry::util::enumerate(turns)) { auto index = enumerated.index; @@ -66,8 +95,7 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v out //<< " l:" << op.count_left << " r:" << op.count_right //<< " rank:" << op.rank // << " z:" << op.zone - << " region:" << op.region_id - << (op.isolated ? " ISOLATED" : ""); + << " region:" << op.region_id; return out.str(); }; auto label_operation_ids = [&turn](int op_index) @@ -107,6 +135,8 @@ struct geojson_visitor : public boost::geometry::detail::overlay::overlay_null_v m_writer.add_property("operation_1", label_operation_ids(1)); m_writer.add_property("enriched_0", label_enriched(0)); m_writer.add_property("enriched_1", label_enriched(1)); + + m_writer.add_property("component", label_component(turn)); } } @@ -131,6 +161,15 @@ void test_overlay(std::string const& caseid, bg::correct(g1); bg::correct(g2); + if (! bg::is_valid(g1)) + { + std::cerr << "WARNING: Invalid input 1: " << caseid << std::endl; + } + if (! bg::is_valid(g2)) + { + std::cerr << "WARNING: Invalid input 2: " << caseid << std::endl; + } + #if defined(TEST_WITH_GEOJSON) std::ostringstream filename; // For QGis, it is usually convenient to always write to the same geojson file. diff --git a/test/algorithms/set_operations/intersection/intersection_multi.cpp b/test/algorithms/set_operations/intersection/intersection_multi.cpp index 7a2298f37b..2008d79e1e 100644 --- a/test/algorithms/set_operations/intersection/intersection_multi.cpp +++ b/test/algorithms/set_operations/intersection/intersection_multi.cpp @@ -166,7 +166,7 @@ void test_areal() case_recursive_boxes_3[0], case_recursive_boxes_3[1], 19, 84, 12.5); // Area from SQL Server - TEST_INTERSECTION_IGNORE(case_recursive_boxes_4, 13, 158, 67.0); + TEST_INTERSECTION(case_recursive_boxes_4, 13, 158, 67.0); // Fixed by replacing handle_tangencies in less_by_segment_ratio sort order // Should contain 6 output polygons diff --git a/test/algorithms/set_operations/set_ops_areal_areal.cpp b/test/algorithms/set_operations/set_ops_areal_areal.cpp index f4157119b5..17649d24bb 100644 --- a/test/algorithms/set_operations/set_ops_areal_areal.cpp +++ b/test/algorithms/set_operations/set_ops_areal_areal.cpp @@ -241,15 +241,15 @@ void test_all(std::string const& name, std::string const& wkt1, std::string cons int test_main(int, char* []) { - TEST_CASE_WITH(case_141_multi, 0, 1, ut_settings().ignore_reverse()); + TEST_CASE(case_141_multi); TEST_CASE(case_142_multi); TEST_CASE(case_143_multi); TEST_CASE(case_144_multi); TEST_CASE(case_145_multi); - TEST_CASE_WITH(case_146_multi, 0, 1, ut_settings().ignore_validity_intersection()); + TEST_CASE(case_146_multi); TEST_CASE(case_147_multi); - TEST_CASE_WITH(issue_1221, 0, 1, ut_settings().ignore_validity_diff()); + TEST_CASE(issue_1221); TEST_CASE(issue_1222); TEST_CASE_WITH(issue_1226, 0, 1, ut_settings().ignore_validity_diff()); @@ -271,10 +271,16 @@ int test_main(int, char* []) TEST_CASE_WITH(issue_1345_a, 1, 0, ut_settings()); TEST_CASE_WITH(issue_1345_b, 1, 0, ut_settings()); - TEST_CASE_WITH(issue_1349, 0, 1, ut_settings().ignore_diff()); + TEST_CASE(issue_1349); TEST_CASE(issue_1349_inverse); - TEST_CASE(issue_1350_comment); + TEST_CASE(case_bitset_1); + TEST_CASE(case_bitset_2); + + // TEST_CASE(case_recursive_boxes_89); + // TEST_CASE(case_recursive_boxes_90); + TEST_CASE_WITH(case_recursive_boxes_89, 0, 1, ut_settings().ignore_diff()); + TEST_CASE_WITH(case_recursive_boxes_90, 0, 1, ut_settings().ignore_diff()); #if defined(BOOST_GEOMETRY_TEST_FAILURES) // Fails in union or intersection, and in difference. Also the union is invalid.