fix: traverse first through non clustered turns, remove cluster exits, make priority consistent

fixes #1293 #1294 and #1295
This commit is contained in:
Barend Gehrels 2024-07-30 19:55:46 +02:00
parent adc9044da2
commit cace2c73fb
3 changed files with 48 additions and 310 deletions

View File

@ -1,233 +0,0 @@
// Boost.Geometry (aka GGL, Generic Geometry Library)
// Copyright (c) 2020 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2023 Adam Wulkiewicz, Lodz, Poland.
// This file was modified by Oracle on 2020-2023.
// Modifications copyright (c) 2020-2023 Oracle and/or its affiliates.
// Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
// 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_CLUSTER_EXITS_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_CLUSTER_EXITS_HPP
#include <cstddef>
#include <set>
#include <vector>
#include <boost/range/value_type.hpp>
#include <boost/geometry/core/assert.hpp>
#include <boost/geometry/algorithms/detail/overlay/overlay_type.hpp>
#include <boost/geometry/algorithms/detail/overlay/sort_by_side.hpp>
#include <boost/geometry/algorithms/detail/signed_size_type.hpp>
#include <boost/geometry/util/constexpr.hpp>
#if defined(BOOST_GEOMETRY_DEBUG_INTERSECTION) \
|| defined(BOOST_GEOMETRY_OVERLAY_REPORT_WKT) \
|| defined(BOOST_GEOMETRY_DEBUG_TRAVERSE)
# include <string>
# include <boost/geometry/algorithms/detail/overlay/debug_turn_info.hpp>
# include <boost/geometry/io/wkt/wkt.hpp>
#endif
namespace boost { namespace geometry
{
#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace overlay
{
// Structure to check relatively simple cluster cases
template <overlay_type OverlayType,
typename Turns,
typename Sbs>
struct cluster_exits
{
private :
static const operation_type target_operation = operation_from_overlay<OverlayType>::value;
typedef typename boost::range_value<Turns>::type turn_type;
typedef typename turn_type::turn_operation_type turn_operation_type;
struct linked_turn_op_info
{
explicit linked_turn_op_info(signed_size_type ti = -1, int oi = -1,
signed_size_type nti = -1)
: turn_index(ti)
, op_index(oi)
, next_turn_index(nti)
, rank_index(-1)
{}
signed_size_type turn_index;
int op_index;
signed_size_type next_turn_index;
signed_size_type rank_index;
};
inline signed_size_type get_rank(Sbs const& sbs,
linked_turn_op_info const& info) const
{
for (auto const& rp : sbs.m_ranked_points)
{
if (rp.turn_index == info.turn_index
&& rp.operation_index == info.op_index
&& rp.direction == sort_by_side::dir_to)
{
return rp.rank;
}
}
return -1;
}
std::set<signed_size_type> const& m_ids;
std::vector<linked_turn_op_info> possibilities;
std::vector<linked_turn_op_info> blocked;
bool m_valid;
bool collect(Turns const& turns)
{
for (auto cluster_turn_index : m_ids)
{
turn_type const& cluster_turn = turns[cluster_turn_index];
if (cluster_turn.discarded)
{
continue;
}
if (cluster_turn.both(target_operation))
{
// Not (yet) supported, can be cluster of u/u turns
return false;
}
for (int i = 0; i < 2; i++)
{
turn_operation_type const& op = cluster_turn.operations[i];
turn_operation_type const& other_op = cluster_turn.operations[1 - i];
signed_size_type const ni = op.enriched.get_next_turn_index();
if (op.operation == target_operation
|| op.operation == operation_continue)
{
if (ni == cluster_turn_index)
{
// Not (yet) supported, traveling to itself, can be
// hole
return false;
}
possibilities.push_back(
linked_turn_op_info(cluster_turn_index, i, ni));
}
else if (op.operation == operation_blocked
&& ! (ni == other_op.enriched.get_next_turn_index())
&& m_ids.count(ni) == 0)
{
// Points to turn, not part of this cluster,
// and that way is blocked. But if the other operation
// points at the same turn, it is still fine.
blocked.push_back(
linked_turn_op_info(cluster_turn_index, i, ni));
}
}
}
return true;
}
bool check_blocked(Sbs const& sbs)
{
if (blocked.empty())
{
return true;
}
for (auto& info : possibilities)
{
info.rank_index = get_rank(sbs, info);
}
for (auto& info : blocked)
{
info.rank_index = get_rank(sbs, info);
}
for (auto const& lti : possibilities)
{
for (auto const& blti : blocked)
{
if (blti.next_turn_index == lti.next_turn_index
&& blti.rank_index == lti.rank_index)
{
return false;
}
}
}
return true;
}
public :
cluster_exits(Turns const& turns,
std::set<signed_size_type> const& ids,
Sbs const& sbs)
: m_ids(ids)
, m_valid(collect(turns) && check_blocked(sbs))
{
}
inline bool apply(signed_size_type& turn_index,
int& op_index,
bool first_run = true) const
{
if (! m_valid)
{
return false;
}
// Traversal can either enter the cluster in the first turn,
// or it can start halfway.
// If there is one (and only one) possibility pointing outside
// the cluster, take that one.
linked_turn_op_info target;
for (auto const& lti : possibilities)
{
if (m_ids.count(lti.next_turn_index) == 0)
{
if (target.turn_index >= 0
&& target.next_turn_index != lti.next_turn_index)
{
// Points to different target
return false;
}
if BOOST_GEOMETRY_CONSTEXPR (OverlayType == overlay_buffer)
{
if (first_run && target.turn_index >= 0)
{
// Target already assigned, so there are more targets
// or more ways to the same target
return false;
}
}
target = lti;
}
}
if (target.turn_index < 0)
{
return false;
}
turn_index = target.turn_index;
op_index = target.op_index;
return true;
}
};
}} // namespace detail::overlay
#endif // DOXYGEN_NO_DETAIL
}} // namespace boost::geometry
#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_CLUSTER_EXITS_HPP

View File

@ -1,6 +1,6 @@
// Boost.Geometry (aka GGL, Generic Geometry Library)
// Copyright (c) 2007-2012 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2007-2024 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2023-2024 Adam Wulkiewicz, Lodz, Poland.
// This file was modified by Oracle on 2017-2024.
@ -23,7 +23,6 @@
#include <boost/range/value_type.hpp>
#include <boost/geometry/algorithms/detail/overlay/cluster_info.hpp>
#include <boost/geometry/algorithms/detail/overlay/cluster_exits.hpp>
#include <boost/geometry/algorithms/detail/overlay/is_self_turn.hpp>
#include <boost/geometry/algorithms/detail/overlay/sort_by_side.hpp>
#include <boost/geometry/algorithms/detail/overlay/turn_info.hpp>
@ -231,26 +230,12 @@ public :
segment_identifier const& previous_seg_id) const
{
// For uu/ii, only switch sources if indicated
if BOOST_GEOMETRY_CONSTEXPR (OverlayType == overlay_buffer)
{
// Buffer does not use source_index (always 0).
return select_source_generic<&segment_identifier::multi_index>(
// Buffer and self-turns do not use source_index (always 0).
return OverlayType == overlay_buffer || is_self_turn<OverlayType>(turn)
? select_source_generic<&segment_identifier::multi_index>(
turn, candidate_seg_id, previous_seg_id)
: select_source_generic<&segment_identifier::source_index>(
turn, candidate_seg_id, previous_seg_id);
}
else // else prevents unreachable code warning
{
if (is_self_turn<OverlayType>(turn))
{
// Also, if it is a self-turn, stay on same ring (multi/ring)
return select_source_generic<&segment_identifier::multi_index>(
turn, candidate_seg_id, previous_seg_id);
}
// Use source_index
return select_source_generic<&segment_identifier::source_index>(
turn, candidate_seg_id, previous_seg_id);
}
}
inline bool traverse_possible(signed_size_type turn_index) const
@ -569,7 +554,7 @@ public :
// 3: OK
// 4: OK and start turn matches
// 5: OK and start turn and start operation both match, this is the best
inline int priority_of_turn_in_cluster_union(sort_by_side::rank_type selected_rank,
inline int priority_of_turn_in_cluster(sort_by_side::rank_type selected_rank,
typename sbs_type::rp const& ranked_point,
cluster_info const& cinfo,
signed_size_type start_turn_index, int start_op_index) const
@ -591,14 +576,14 @@ public :
if BOOST_GEOMETRY_CONSTEXPR (OverlayType != overlay_dissolve)
{
if (op.enriched.count_left != 0 || op.enriched.count_right == 0)
if ((op.enriched.count_left != 0 || op.enriched.count_right == 0) && cinfo.spike_count > 0)
{
// Check counts: in some cases interior rings might be generated with
// polygons on both sides. For dissolve it can be anything.
// If this forms a spike, going to/from the cluster point in the same
// (opposite) direction, it can still be used.
return cinfo.spike_count > 0 ? 1 : 0;
return 1;
}
}
@ -657,9 +642,8 @@ public :
return -1;
}
inline bool select_from_cluster_union(signed_size_type& turn_index,
cluster_info const& cinfo,
int& op_index, sbs_type const& sbs,
inline bool select_from_cluster(signed_size_type& turn_index, int& op_index,
cluster_info const& cinfo, sbs_type const& sbs,
signed_size_type start_turn_index, int start_op_index) const
{
sort_by_side::rank_type const selected_rank = select_rank(sbs);
@ -674,7 +658,7 @@ public :
break;
}
int const priority = priority_of_turn_in_cluster_union(selected_rank,
int const priority = priority_of_turn_in_cluster(selected_rank,
ranked_point, cinfo, start_turn_index, start_op_index);
if (priority > current_priority)
@ -687,7 +671,9 @@ public :
return current_priority > 0;
}
inline bool analyze_cluster_intersection(signed_size_type& turn_index,
// Analyzes a clustered intersection, as if it is clustered.
// This is used for II intersections
inline bool analyze_ii_cluster(signed_size_type& turn_index,
int& op_index, sbs_type const& sbs) const
{
// Select the rank based on regions and isolation
@ -823,34 +809,13 @@ public :
}
}
cluster_exits<OverlayType, Turns, sbs_type> exits(m_turns, cinfo.turn_indices, sbs);
if (exits.apply(turn_index, op_index))
{
return true;
}
bool result = false;
if BOOST_GEOMETRY_CONSTEXPR (is_union)
{
result = select_from_cluster_union(turn_index, cinfo,
op_index, sbs,
start_turn_index, start_op_index);
if (! result)
{
// There no way out found, try second pass in collected cluster exits
result = exits.apply(turn_index, op_index, false);
}
}
else
{
result = analyze_cluster_intersection(turn_index, op_index, sbs);
}
return result;
return select_from_cluster(turn_index, op_index, cinfo, sbs, start_turn_index, start_op_index);
}
// Analyzes a non-clustered "ii" intersection, as if it is clustered.
// TODO: it, since select_from_cluster is generalized (July 2024),
// uses a specific function used only for "ii" intersections.
// Therefore the sort-by-side solution should not be necessary and can be refactored.
inline bool analyze_ii_intersection(signed_size_type& turn_index, int& op_index,
turn_type const& current_turn,
segment_identifier const& previous_seg_id)
@ -874,9 +839,7 @@ public :
sbs.apply(current_turn.point);
bool result = analyze_cluster_intersection(turn_index, op_index, sbs);
return result;
return analyze_ii_cluster(turn_index, op_index, sbs);
}
inline void change_index_for_self_turn(signed_size_type& to_vertex_index,
@ -1075,7 +1038,6 @@ private :
};
}} // namespace detail::overlay
#endif // DOXYGEN_NO_DETAIL

View File

@ -322,31 +322,40 @@ struct traversal_ring_creator
void iterate(Rings& rings, std::size_t& finalized_ring_size,
typename Backtrack::state_type& state)
{
for (std::size_t turn_index = 0; turn_index < m_turns.size(); ++turn_index)
auto do_iterate = [&](int phase)
{
turn_type const& turn = m_turns[turn_index];
for (std::size_t turn_index = 0; turn_index < m_turns.size(); ++turn_index)
{
turn_type const& turn = m_turns[turn_index];
if (turn.discarded || turn.blocked())
{
// Skip discarded and blocked turns
continue;
}
if (turn.both(operation_continue))
{
traverse_with_operation(turn, turn_index,
get_operation_index(turn),
rings, finalized_ring_size, state);
}
else
{
for (int op_index = 0; op_index < 2; op_index++)
if (turn.discarded || turn.blocked() || (phase == 0 && turn.is_clustered()))
{
traverse_with_operation(turn, turn_index, op_index,
// Skip discarded and blocked turns
continue;
}
if (turn.both(operation_continue))
{
traverse_with_operation(turn, turn_index,
get_operation_index(turn),
rings, finalized_ring_size, state);
}
else
{
for (int op_index = 0; op_index < 2; op_index++)
{
traverse_with_operation(turn, turn_index, op_index,
rings, finalized_ring_size, state);
}
}
}
}
};
// Traverse all turns, first starting with the non-clustered ones.
do_iterate(0);
// Traverse remaining clustered turns, if any.
do_iterate(1);
}
template <typename Rings>