Skip to content

Commit

Permalink
Merge branch 'master_cherry_picks'
Browse files Browse the repository at this point in the history
merge cherry-picked commits from develop to master
  • Loading branch information
vissarion committed Nov 27, 2024
2 parents 2221c7b + b3d35b3 commit b20a9e6
Show file tree
Hide file tree
Showing 14 changed files with 390 additions and 96 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DISCARD_DUPLICATE_TURNS_HPP

#include <map>
#include <set>

#include <boost/geometry/algorithms/detail/overlay/turn_info.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
#include <boost/geometry/views/enumerate_view.hpp>

namespace boost { namespace geometry
{
Expand Down Expand Up @@ -88,72 +90,159 @@ inline bool corresponding_turn(Turn const& turn, Turn const& start_turn,
return count == 2;
}

// Verify turns (other than start, and cross) if they are present in the map, and if so,
// if they have the other turns as corresponding, discard the start turn.
template <typename Turns, typename TurnBySegmentMap, typename Geometry0, typename Geometry1>
void discard_duplicate_start_turns(Turns& turns,
TurnBySegmentMap const& start_turns_by_segment,
Geometry0 const& geometry0,
Geometry1 const& geometry1)
{
using multi_and_ring_id_type = std::pair<signed_size_type, signed_size_type>;
auto adapt_id = [](segment_identifier const& seg_id)
{
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
};

for (auto& turn : turns)
{
// Any turn which "crosses" does not have a corresponding turn.
// Also avoid comparing "start" with itself
if (turn.method == method_crosses || turn.method == method_start)
{
continue;
}
bool const is_touch = turn.method == method_touch;
for (auto const& op : turn.operations)
{
auto it = start_turns_by_segment.find(adapt_id(op.seg_id));
if (it == start_turns_by_segment.end())
{
continue;
}
for (std::size_t const& i : it->second)
{
auto& start_turn = turns[i];
if (start_turn.cluster_id == turn.cluster_id
&& corresponding_turn(turn, start_turn, geometry0, geometry1))
{
// Discard the start turn, unless there is a touch before.
// In that case the start is used and the touch is discarded.
(is_touch ? turn : start_turn).discarded = true;
}
}
}
}
}

// Discard turns for the following (rare) case:
// - they are consecutive
// - the first has a touch, the second a touch_interior
// And then one of the segments touches the others next in the middle.
// This is geometrically not possible, and caused by floating point precision.
// Discard the second (touch interior)
template <typename Turns, typename Geometry0, typename Geometry1>
void discard_touch_touch_interior_turns(Turns& turns,
Geometry0 const& geometry0,
Geometry1 const& geometry1)
{
for (auto& current_turn : turns)
{
if (current_turn.method != method_touch_interior)
{
// Because touch_interior is a rarer case, it is more efficient to start with that
continue;
}
for (auto const& previous_turn : turns)
{
if (previous_turn.method != method_touch)
{
continue;
}

// Compare 0 with 0 and 1 with 1
// Note that 0 has always source 0 and 1 has always source 1
// (not in buffer). Therefore this comparison is OK.
// MAYBE we need to check for buffer.
bool const common0 = current_turn.operations[0].seg_id == previous_turn.operations[0].seg_id;
bool const common1 = current_turn.operations[1].seg_id == previous_turn.operations[1].seg_id;

// If one of the operations is common, and the other is not, then there is one comment segment.
bool const has_common_segment = common0 != common1;

if (! has_common_segment)
{
continue;
}

// If the second index (1) is common, we need to check consecutivity of the first index (0)
// and vice versa.
bool const consecutive =
common1 ? is_consecutive(previous_turn.operations[0].seg_id, current_turn.operations[0].seg_id, geometry0, geometry1)
: is_consecutive(previous_turn.operations[1].seg_id, current_turn.operations[1].seg_id, geometry0, geometry1);

if (consecutive)
{
current_turn.discarded = true;
}
}
}
}

template <typename Turns, typename Geometry0, typename Geometry1>
inline void discard_duplicate_start_turns(Turns& turns,
Geometry0 const& geometry0,
Geometry1 const& geometry1)
void discard_duplicate_turns(Turns& turns,
Geometry0 const& geometry0,
Geometry1 const& geometry1)
{
// Start turns are generated, in case the previous turn is missed.
// But often it is not missed, and then it should be deleted.
// This is how it can be
// (in float, collinear, points far apart due to floating point precision)
// [m, i s:0, v:6 1/1 (1) // u s:1, v:5 pnt (2.54044, 3.12623)]
// [s, i s:0, v:7 0/1 (0) // u s:1, v:5 pnt (2.70711, 3.29289)]
//
// Also, if two turns are consecutive, and one is touch and the other touch_interior,
// the touch_interior is discarded.

using multi_and_ring_id_type = std::pair<signed_size_type, signed_size_type>;

auto adapt_id = [](segment_identifier const& seg_id)
auto add_to_map = [](auto const& turn, auto& map, std::size_t index)
{
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
auto adapt_id = [](segment_identifier const& seg_id)
{
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
};
for (auto const& op : turn.operations)
{
map[adapt_id(op.seg_id)].insert(index);
}
};

// 1 Build map of start turns (multi/ring-id -> turn indices)
std::map<multi_and_ring_id_type, std::vector<std::size_t>> start_turns_per_segment;
std::size_t index = 0;
for (auto const& turn : turns)
// Build map of start turns (multi/ring-id -> turn indices)
// and count touch and touch_interior turns (to verify if later checks are needed)
std::map<multi_and_ring_id_type, std::set<std::size_t>> start_turns_by_segment;
std::size_t touch_count = 0;
std::size_t touch_interior_count = 0;
for (auto const& item : util::enumerate(turns))
{
if (turn.method == method_start)
auto const& turn = item.value;
switch(turn.method)
{
for (auto const& op : turn.operations)
{
start_turns_per_segment[adapt_id(op.seg_id)].push_back(index);
}
case method_start: add_to_map(turn, start_turns_by_segment, item.index); break;
case method_touch: touch_count++; break;
case method_touch_interior: touch_interior_count++; break;
default: break;
}
index++;
}

// 2: Verify all other turns if they are present in the map, and if so,
// if they have the other turns as corresponding
for (auto const& turn : turns)
if (!start_turns_by_segment.empty())
{
// Any turn which "crosses" does not have a corresponding turn.
// Also avoid comparing "start" with itself.
if (turn.method != method_crosses && turn.method != method_start)
{
for (auto const& op : turn.operations)
{
auto it = start_turns_per_segment.find(adapt_id(op.seg_id));
if (it != start_turns_per_segment.end())
{
for (std::size_t const& i : it->second)
{
if (turns[i].cluster_id != turn.cluster_id)
{
// The turns are not part of the same cluster,
// or one is clustered and the other is not.
// This is not corresponding.
continue;
}
if (corresponding_turn(turn, turns[i],
geometry0, geometry1))
{
turns[i].discarded = true;
}
}
}
}
}
index++;
discard_duplicate_start_turns(turns, start_turns_by_segment, geometry0, geometry1);
}

if (touch_count > 0 && touch_interior_count > 0)
{
discard_touch_touch_interior_turns(turns, geometry0, geometry1);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,7 @@ inline void enrich_intersection_points(Turns& turns,
has_colocations = ! clusters.empty();
}

discard_duplicate_start_turns(turns, geometry1, geometry2);
discard_duplicate_turns(turns, geometry1, geometry2);

// Discard turns not part of target overlay
for (auto& turn : turns)
Expand Down
37 changes: 32 additions & 5 deletions include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -343,17 +343,35 @@ struct touch_interior : public base_turn_handler
template
<
typename IntersectionInfo,
typename UniqueSubRange
typename SideCalculator,
typename UniqueSubRange1,
typename UniqueSubRange2
>
static bool handle_as_touch(IntersectionInfo const& info,
UniqueSubRange const& non_touching_range)
SideCalculator const& side,
UniqueSubRange1 const& non_touching_range,
UniqueSubRange2 const& other_range)
{
if BOOST_GEOMETRY_CONSTEXPR (! VerifyPolicy::use_handle_as_touch)
{
return false;
}
else // else prevents unreachable code warning
{
bool const has_k = ! non_touching_range.is_last_segment()
&& ! other_range.is_last_segment();
if (has_k
&& (same(side.pj_wrt_q1(), side.qj_wrt_p2())
|| same(side.pj_wrt_q2(), side.qj_wrt_p1())))
{
// At a touch, the touching points (pj and qj) should be collinear
// with both other segments.
// If that is not the case (both left or both right), it should not be handled as a touch,
// (though the intersection point might be close to the end),
// because segments might cross each other or touch the other in the middle.
return false;
}

//
//
// ^ Q(i) ^ P(i)
Expand Down Expand Up @@ -569,7 +587,7 @@ struct touch : public base_turn_handler
// ||
// |^----
// >----->P
// * * they touch here (P/Q are (nearly) on top)
// * * they touch here (P/Q are (nearly) on top of each other)
//
// Q continues from where P comes.
// P continues from where Q comes
Expand All @@ -586,6 +604,14 @@ struct touch : public base_turn_handler
// >----->P qj is LEFT of P1 and pi is LEFT of Q2
// (the other way round is also possible)

// There are also cases like this:
// P
// ^
// ||
// ||
// P----^-----<Q
// This code is not for these cases because of the condition opposite(side.pi_wrt_q1(), side.qk_wrt_p2())

auto has_distance = [&](auto const& r1, auto const& r2) -> bool
{
auto const d1 = get_distance_measure(r1.at(0), r1.at(1), r2.at(1), umbrella_strategy);
Expand Down Expand Up @@ -674,6 +700,7 @@ struct touch : public base_turn_handler
{
if (side_qk_p1 == 0 && side_pk_q1 == 0
&& has_pk && has_qk
&& opposite(side.pi_wrt_q1(), side.qk_wrt_p2())
&& handle_imperfect_touch(range_p, range_q, side_pk_q2, umbrella_strategy, ti))
{
// If q continues collinearly (opposite) with p, it should be blocked
Expand Down Expand Up @@ -1452,7 +1479,7 @@ struct get_turn_info
if ( inters.d_info().arrival[1] == 1 )
{
// Q arrives
if (handler::handle_as_touch(inters.i_info(), range_p))
if (handler::handle_as_touch(inters.i_info(), inters.sides(), range_p, range_q))
{
handle_as_touch = true;
}
Expand All @@ -1466,7 +1493,7 @@ struct get_turn_info
else
{
// P arrives, swap p/q
if (handler::handle_as_touch(inters.i_info(), range_q))
if (handler::handle_as_touch(inters.i_info(), inters.swapped_sides(), range_q, range_p))
{
handle_as_touch = true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ struct side_calculator
, m_range_q(range_q)
{}

inline int pi_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pi()); }

inline int pj_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pj()); }
inline int pj_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pj()); }
inline int qj_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qj()); }
inline int qj_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qj()); }

inline int pk_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_pk()); }
inline int pk_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pk()); }
inline int qk_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qk()); }
Expand All @@ -66,12 +73,6 @@ struct side_calculator
inline int pk_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pk()); }
inline int qk_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qk()); }

// Necessary when rescaling turns off:
inline int qj_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qj()); }
inline int qj_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qj()); }
inline int pj_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pj()); }
inline int pj_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pj()); }

inline auto const& get_pi() const { return m_range_p.at(0); }
inline auto const& get_pj() const { return m_range_p.at(1); }
inline auto const& get_pk() const { return m_range_p.at(2); }
Expand Down
Loading

0 comments on commit b20a9e6

Please sign in to comment.