Skip to content

Commit b20a9e6

Browse files
committed
Merge branch 'master_cherry_picks'
merge cherry-picked commits from develop to master
2 parents 2221c7b + b3d35b3 commit b20a9e6

File tree

14 files changed

+390
-96
lines changed

14 files changed

+390
-96
lines changed

include/boost/geometry/algorithms/detail/overlay/discard_duplicate_turns.hpp

Lines changed: 135 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_DISCARD_DUPLICATE_TURNS_HPP
1515

1616
#include <map>
17+
#include <set>
1718

1819
#include <boost/geometry/algorithms/detail/overlay/turn_info.hpp>
1920
#include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
21+
#include <boost/geometry/views/enumerate_view.hpp>
2022

2123
namespace boost { namespace geometry
2224
{
@@ -88,72 +90,159 @@ inline bool corresponding_turn(Turn const& turn, Turn const& start_turn,
8890
return count == 2;
8991
}
9092

93+
// Verify turns (other than start, and cross) if they are present in the map, and if so,
94+
// if they have the other turns as corresponding, discard the start turn.
95+
template <typename Turns, typename TurnBySegmentMap, typename Geometry0, typename Geometry1>
96+
void discard_duplicate_start_turns(Turns& turns,
97+
TurnBySegmentMap const& start_turns_by_segment,
98+
Geometry0 const& geometry0,
99+
Geometry1 const& geometry1)
100+
{
101+
using multi_and_ring_id_type = std::pair<signed_size_type, signed_size_type>;
102+
auto adapt_id = [](segment_identifier const& seg_id)
103+
{
104+
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
105+
};
106+
107+
for (auto& turn : turns)
108+
{
109+
// Any turn which "crosses" does not have a corresponding turn.
110+
// Also avoid comparing "start" with itself
111+
if (turn.method == method_crosses || turn.method == method_start)
112+
{
113+
continue;
114+
}
115+
bool const is_touch = turn.method == method_touch;
116+
for (auto const& op : turn.operations)
117+
{
118+
auto it = start_turns_by_segment.find(adapt_id(op.seg_id));
119+
if (it == start_turns_by_segment.end())
120+
{
121+
continue;
122+
}
123+
for (std::size_t const& i : it->second)
124+
{
125+
auto& start_turn = turns[i];
126+
if (start_turn.cluster_id == turn.cluster_id
127+
&& corresponding_turn(turn, start_turn, geometry0, geometry1))
128+
{
129+
// Discard the start turn, unless there is a touch before.
130+
// In that case the start is used and the touch is discarded.
131+
(is_touch ? turn : start_turn).discarded = true;
132+
}
133+
}
134+
}
135+
}
136+
}
137+
138+
// Discard turns for the following (rare) case:
139+
// - they are consecutive
140+
// - the first has a touch, the second a touch_interior
141+
// And then one of the segments touches the others next in the middle.
142+
// This is geometrically not possible, and caused by floating point precision.
143+
// Discard the second (touch interior)
144+
template <typename Turns, typename Geometry0, typename Geometry1>
145+
void discard_touch_touch_interior_turns(Turns& turns,
146+
Geometry0 const& geometry0,
147+
Geometry1 const& geometry1)
148+
{
149+
for (auto& current_turn : turns)
150+
{
151+
if (current_turn.method != method_touch_interior)
152+
{
153+
// Because touch_interior is a rarer case, it is more efficient to start with that
154+
continue;
155+
}
156+
for (auto const& previous_turn : turns)
157+
{
158+
if (previous_turn.method != method_touch)
159+
{
160+
continue;
161+
}
162+
163+
// Compare 0 with 0 and 1 with 1
164+
// Note that 0 has always source 0 and 1 has always source 1
165+
// (not in buffer). Therefore this comparison is OK.
166+
// MAYBE we need to check for buffer.
167+
bool const common0 = current_turn.operations[0].seg_id == previous_turn.operations[0].seg_id;
168+
bool const common1 = current_turn.operations[1].seg_id == previous_turn.operations[1].seg_id;
169+
170+
// If one of the operations is common, and the other is not, then there is one comment segment.
171+
bool const has_common_segment = common0 != common1;
172+
173+
if (! has_common_segment)
174+
{
175+
continue;
176+
}
177+
178+
// If the second index (1) is common, we need to check consecutivity of the first index (0)
179+
// and vice versa.
180+
bool const consecutive =
181+
common1 ? is_consecutive(previous_turn.operations[0].seg_id, current_turn.operations[0].seg_id, geometry0, geometry1)
182+
: is_consecutive(previous_turn.operations[1].seg_id, current_turn.operations[1].seg_id, geometry0, geometry1);
183+
184+
if (consecutive)
185+
{
186+
current_turn.discarded = true;
187+
}
188+
}
189+
}
190+
}
191+
91192
template <typename Turns, typename Geometry0, typename Geometry1>
92-
inline void discard_duplicate_start_turns(Turns& turns,
93-
Geometry0 const& geometry0,
94-
Geometry1 const& geometry1)
193+
void discard_duplicate_turns(Turns& turns,
194+
Geometry0 const& geometry0,
195+
Geometry1 const& geometry1)
95196
{
96197
// Start turns are generated, in case the previous turn is missed.
97198
// But often it is not missed, and then it should be deleted.
98199
// This is how it can be
99200
// (in float, collinear, points far apart due to floating point precision)
100201
// [m, i s:0, v:6 1/1 (1) // u s:1, v:5 pnt (2.54044, 3.12623)]
101202
// [s, i s:0, v:7 0/1 (0) // u s:1, v:5 pnt (2.70711, 3.29289)]
203+
//
204+
// Also, if two turns are consecutive, and one is touch and the other touch_interior,
205+
// the touch_interior is discarded.
102206

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

105-
auto adapt_id = [](segment_identifier const& seg_id)
209+
auto add_to_map = [](auto const& turn, auto& map, std::size_t index)
106210
{
107-
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
211+
auto adapt_id = [](segment_identifier const& seg_id)
212+
{
213+
return multi_and_ring_id_type{seg_id.multi_index, seg_id.ring_index};
214+
};
215+
for (auto const& op : turn.operations)
216+
{
217+
map[adapt_id(op.seg_id)].insert(index);
218+
}
108219
};
109220

110-
// 1 Build map of start turns (multi/ring-id -> turn indices)
111-
std::map<multi_and_ring_id_type, std::vector<std::size_t>> start_turns_per_segment;
112-
std::size_t index = 0;
113-
for (auto const& turn : turns)
221+
// Build map of start turns (multi/ring-id -> turn indices)
222+
// and count touch and touch_interior turns (to verify if later checks are needed)
223+
std::map<multi_and_ring_id_type, std::set<std::size_t>> start_turns_by_segment;
224+
std::size_t touch_count = 0;
225+
std::size_t touch_interior_count = 0;
226+
for (auto const& item : util::enumerate(turns))
114227
{
115-
if (turn.method == method_start)
228+
auto const& turn = item.value;
229+
switch(turn.method)
116230
{
117-
for (auto const& op : turn.operations)
118-
{
119-
start_turns_per_segment[adapt_id(op.seg_id)].push_back(index);
120-
}
231+
case method_start: add_to_map(turn, start_turns_by_segment, item.index); break;
232+
case method_touch: touch_count++; break;
233+
case method_touch_interior: touch_interior_count++; break;
234+
default: break;
121235
}
122-
index++;
123236
}
124237

125-
// 2: Verify all other turns if they are present in the map, and if so,
126-
// if they have the other turns as corresponding
127-
for (auto const& turn : turns)
238+
if (!start_turns_by_segment.empty())
128239
{
129-
// Any turn which "crosses" does not have a corresponding turn.
130-
// Also avoid comparing "start" with itself.
131-
if (turn.method != method_crosses && turn.method != method_start)
132-
{
133-
for (auto const& op : turn.operations)
134-
{
135-
auto it = start_turns_per_segment.find(adapt_id(op.seg_id));
136-
if (it != start_turns_per_segment.end())
137-
{
138-
for (std::size_t const& i : it->second)
139-
{
140-
if (turns[i].cluster_id != turn.cluster_id)
141-
{
142-
// The turns are not part of the same cluster,
143-
// or one is clustered and the other is not.
144-
// This is not corresponding.
145-
continue;
146-
}
147-
if (corresponding_turn(turn, turns[i],
148-
geometry0, geometry1))
149-
{
150-
turns[i].discarded = true;
151-
}
152-
}
153-
}
154-
}
155-
}
156-
index++;
240+
discard_duplicate_start_turns(turns, start_turns_by_segment, geometry0, geometry1);
241+
}
242+
243+
if (touch_count > 0 && touch_interior_count > 0)
244+
{
245+
discard_touch_touch_interior_turns(turns, geometry0, geometry1);
157246
}
158247
}
159248

include/boost/geometry/algorithms/detail/overlay/enrich_intersection_points.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -442,7 +442,7 @@ inline void enrich_intersection_points(Turns& turns,
442442
has_colocations = ! clusters.empty();
443443
}
444444

445-
discard_duplicate_start_turns(turns, geometry1, geometry2);
445+
discard_duplicate_turns(turns, geometry1, geometry2);
446446

447447
// Discard turns not part of target overlay
448448
for (auto& turn : turns)

include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp

Lines changed: 32 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -343,17 +343,35 @@ struct touch_interior : public base_turn_handler
343343
template
344344
<
345345
typename IntersectionInfo,
346-
typename UniqueSubRange
346+
typename SideCalculator,
347+
typename UniqueSubRange1,
348+
typename UniqueSubRange2
347349
>
348350
static bool handle_as_touch(IntersectionInfo const& info,
349-
UniqueSubRange const& non_touching_range)
351+
SideCalculator const& side,
352+
UniqueSubRange1 const& non_touching_range,
353+
UniqueSubRange2 const& other_range)
350354
{
351355
if BOOST_GEOMETRY_CONSTEXPR (! VerifyPolicy::use_handle_as_touch)
352356
{
353357
return false;
354358
}
355359
else // else prevents unreachable code warning
356360
{
361+
bool const has_k = ! non_touching_range.is_last_segment()
362+
&& ! other_range.is_last_segment();
363+
if (has_k
364+
&& (same(side.pj_wrt_q1(), side.qj_wrt_p2())
365+
|| same(side.pj_wrt_q2(), side.qj_wrt_p1())))
366+
{
367+
// At a touch, the touching points (pj and qj) should be collinear
368+
// with both other segments.
369+
// If that is not the case (both left or both right), it should not be handled as a touch,
370+
// (though the intersection point might be close to the end),
371+
// because segments might cross each other or touch the other in the middle.
372+
return false;
373+
}
374+
357375
//
358376
//
359377
// ^ Q(i) ^ P(i)
@@ -569,7 +587,7 @@ struct touch : public base_turn_handler
569587
// ||
570588
// |^----
571589
// >----->P
572-
// * * they touch here (P/Q are (nearly) on top)
590+
// * * they touch here (P/Q are (nearly) on top of each other)
573591
//
574592
// Q continues from where P comes.
575593
// P continues from where Q comes
@@ -586,6 +604,14 @@ struct touch : public base_turn_handler
586604
// >----->P qj is LEFT of P1 and pi is LEFT of Q2
587605
// (the other way round is also possible)
588606

607+
// There are also cases like this:
608+
// P
609+
// ^
610+
// ||
611+
// ||
612+
// P----^-----<Q
613+
// This code is not for these cases because of the condition opposite(side.pi_wrt_q1(), side.qk_wrt_p2())
614+
589615
auto has_distance = [&](auto const& r1, auto const& r2) -> bool
590616
{
591617
auto const d1 = get_distance_measure(r1.at(0), r1.at(1), r2.at(1), umbrella_strategy);
@@ -674,6 +700,7 @@ struct touch : public base_turn_handler
674700
{
675701
if (side_qk_p1 == 0 && side_pk_q1 == 0
676702
&& has_pk && has_qk
703+
&& opposite(side.pi_wrt_q1(), side.qk_wrt_p2())
677704
&& handle_imperfect_touch(range_p, range_q, side_pk_q2, umbrella_strategy, ti))
678705
{
679706
// If q continues collinearly (opposite) with p, it should be blocked
@@ -1452,7 +1479,7 @@ struct get_turn_info
14521479
if ( inters.d_info().arrival[1] == 1 )
14531480
{
14541481
// Q arrives
1455-
if (handler::handle_as_touch(inters.i_info(), range_p))
1482+
if (handler::handle_as_touch(inters.i_info(), inters.sides(), range_p, range_q))
14561483
{
14571484
handle_as_touch = true;
14581485
}
@@ -1466,7 +1493,7 @@ struct get_turn_info
14661493
else
14671494
{
14681495
// P arrives, swap p/q
1469-
if (handler::handle_as_touch(inters.i_info(), range_q))
1496+
if (handler::handle_as_touch(inters.i_info(), inters.swapped_sides(), range_q, range_p))
14701497
{
14711498
handle_as_touch = true;
14721499
}

include/boost/geometry/algorithms/detail/overlay/get_turn_info_helpers.hpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,13 @@ struct side_calculator
5858
, m_range_q(range_q)
5959
{}
6060

61+
inline int pi_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pi()); }
62+
63+
inline int pj_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pj()); }
64+
inline int pj_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pj()); }
65+
inline int qj_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qj()); }
66+
inline int qj_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qj()); }
67+
6168
inline int pk_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_pk()); }
6269
inline int pk_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pk()); }
6370
inline int qk_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qk()); }
@@ -66,12 +73,6 @@ struct side_calculator
6673
inline int pk_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pk()); }
6774
inline int qk_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qk()); }
6875

69-
// Necessary when rescaling turns off:
70-
inline int qj_wrt_p1() const { return m_side_strategy.apply(get_pi(), get_pj(), get_qj()); }
71-
inline int qj_wrt_p2() const { return m_side_strategy.apply(get_pj(), get_pk(), get_qj()); }
72-
inline int pj_wrt_q1() const { return m_side_strategy.apply(get_qi(), get_qj(), get_pj()); }
73-
inline int pj_wrt_q2() const { return m_side_strategy.apply(get_qj(), get_qk(), get_pj()); }
74-
7576
inline auto const& get_pi() const { return m_range_p.at(0); }
7677
inline auto const& get_pj() const { return m_range_p.at(1); }
7778
inline auto const& get_pk() const { return m_range_p.at(2); }

0 commit comments

Comments
 (0)