forked from boostorg/geometry
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add biconnected_components instead of isolated
- Loading branch information
1 parent
2ae9d60
commit c4fc5e3
Showing
12 changed files
with
554 additions
and
400 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
227 changes: 227 additions & 0 deletions
227
include/boost/geometry/algorithms/detail/overlay/detect_biconnected_components.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <map> | ||
#include <set> | ||
|
||
#include <boost/geometry/algorithms/detail/overlay/segment_identifier.hpp> | ||
#include <boost/geometry/algorithms/detail/overlay/node_util.hpp> | ||
#include <boost/geometry/algorithms/detail/overlay/graph_util.hpp> | ||
|
||
#include <boost/graph/biconnected_components.hpp> | ||
#include <boost/graph/adjacency_list.hpp> | ||
|
||
namespace boost { namespace geometry | ||
{ | ||
|
||
#ifndef DOXYGEN_NO_DETAIL | ||
namespace detail { namespace overlay | ||
{ | ||
|
||
|
||
struct vertex_info | ||
{ | ||
signed_size_type node_id{0}; | ||
std::set<std::size_t> 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<std::size_t, vertex_info> 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<signed_size_type, std::size_t> 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 <operation_type TargetOperation, typename Turns, typename Clusters> | ||
void fill_vertex_map(Turns const& turns, Clusters const& clusters, state_type& state) | ||
{ | ||
std::set<std::size_t> 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<TargetOperation>(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 <typename Turns, typename Clusters, typename Graph, typename Components> | ||
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<Graph>::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<int>(component[*ei]); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
template <operation_type TargetOperation, typename Turns, typename Clusters> | ||
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<edge_component, std::size_t> | ||
>; | ||
using vertex_t = boost::graph_traits <graph_t>::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<std::size_t>(turns.size()); | ||
|
||
fill_vertex_map<TargetOperation>(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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
include/boost/geometry/algorithms/detail/overlay/graph_util.hpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
// #define SKIP_SELF_TURNS | ||
|
||
// 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 <boost/graph/biconnected_components.hpp> | ||
#include <boost/graph/adjacency_list.hpp> | ||
|
||
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 <typename Graph, typename Components> | ||
void fix_components(Components& components, Graph const& g) | ||
{ | ||
typename graph_traits<Graph>::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) | ||
{ | ||
#if defined(BOOST_GEOMETRY_DEBUG_ARTICULATION_POINTS) | ||
std::cout << "Fix component for edge <" << source_vertex << ", " << target_vertex << "> " | ||
<< component << " -> " << reverse_component << std::endl; | ||
#endif | ||
component = reverse_component; | ||
} | ||
} | ||
} | ||
|
||
}} // namespace detail::overlay | ||
#endif // DOXYGEN_NO_DETAIL | ||
|
||
}} // namespace boost::geometry | ||
|
||
#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_GRAPH_UTIL_HPP |
Oops, something went wrong.