Skip to content

Commit 9759656

Browse files
Merge pull request #736 from barendgehrels/fix/imperfect-touch
Fix errors in some floating point cases
2 parents c217b45 + 0969372 commit 9759656

File tree

6 files changed

+199
-67
lines changed

6 files changed

+199
-67
lines changed

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

Lines changed: 150 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,60 @@ struct base_turn_handler
111111
both(ti, condition ? operation_union : operation_intersection);
112112
}
113113

114+
115+
#if ! defined(BOOST_GEOMETRY_USE_RESCALING)
116+
template
117+
<
118+
typename UniqueSubRange1,
119+
typename UniqueSubRange2
120+
>
121+
static inline int side_with_distance_measure(UniqueSubRange1 const& range_p,
122+
UniqueSubRange2 const& range_q,
123+
int range_index, int point_index)
124+
{
125+
if (range_index >= 1 && range_p.is_last_segment())
126+
{
127+
return 0;
128+
}
129+
if (point_index >= 2 && range_q.is_last_segment())
130+
{
131+
return 0;
132+
}
133+
134+
typedef typename select_coordinate_type
135+
<
136+
typename UniqueSubRange1::point_type,
137+
typename UniqueSubRange2::point_type
138+
>::type coordinate_type;
139+
140+
typedef detail::distance_measure<coordinate_type> dm_type;
141+
142+
dm_type const dm = get_distance_measure(range_p.at(range_index), range_p.at(range_index + 1), range_q.at(point_index));
143+
return dm.measure == 0 ? 0 : dm.measure > 0 ? 1 : -1;
144+
}
145+
146+
template
147+
<
148+
typename UniqueSubRange1,
149+
typename UniqueSubRange2
150+
>
151+
static inline int verified_side(int side,
152+
UniqueSubRange1 const& range_p,
153+
UniqueSubRange2 const& range_q,
154+
int range_index,
155+
int point_index)
156+
{
157+
return side == 0 ? side_with_distance_measure(range_p, range_q, range_index, point_index) : side;
158+
}
159+
#else
160+
template <typename T1, typename T2>
161+
static inline int verified_side(int side, T1 const& , T2 const& , int , int)
162+
{
163+
return side;
164+
}
165+
#endif
166+
167+
114168
template <typename TurnInfo, typename IntersectionInfo>
115169
static inline void assign_point(TurnInfo& ti,
116170
method_type method,
@@ -178,7 +232,7 @@ struct base_turn_handler
178232
<
179233
typename UniqueSubRange1::point_type,
180234
typename UniqueSubRange2::point_type
181-
>::type
235+
>::type
182236
> dm_type;
183237

184238
const bool p_closer =
@@ -369,14 +423,80 @@ struct touch : public base_turn_handler
369423
return side1 == side2 && ! opposite(side1, turn);
370424
}
371425

372-
/*static inline void block_second(bool block, TurnInfo& ti)
426+
#if ! defined(BOOST_GEOMETRY_USE_RESCALING)
427+
template
428+
<
429+
typename UniqueSubRange1,
430+
typename UniqueSubRange2
431+
>
432+
static inline bool handle_imperfect_touch(UniqueSubRange1 const& range_p,
433+
UniqueSubRange2 const& range_q, TurnInfo& ti)
373434
{
374-
if (block)
435+
// Q
436+
// ^
437+
// ||
438+
// ||
439+
// |^----
440+
// >----->P
441+
// * * they touch here (P/Q are (nearly) on top)
442+
//
443+
// Q continues from where P comes.
444+
// P continues from where Q comes
445+
// This is often a blocking situation,
446+
// unless there are FP issues: there might be a distance
447+
// between Pj and Qj, in that case handle it as a union.
448+
//
449+
// Exaggerated:
450+
// Q
451+
// ^ Q is nearly vertical
452+
// \ but not completely - and still ends above P
453+
// | \qj In this case it should block P and
454+
// | ^------ set Q to Union
455+
// >----->P qj is LEFT of P1 and pi is LEFT of Q2
456+
// (the other way round is also possible)
457+
458+
typedef typename select_coordinate_type
459+
<
460+
typename UniqueSubRange1::point_type,
461+
typename UniqueSubRange2::point_type
462+
>::type coordinate_type;
463+
464+
typedef detail::distance_measure<coordinate_type> dm_type;
465+
466+
dm_type const dm_qj_p1 = get_distance_measure(range_p.at(0), range_p.at(1), range_q.at(1));
467+
dm_type const dm_pi_q2 = get_distance_measure(range_q.at(1), range_q.at(2), range_p.at(0));
468+
469+
if (dm_qj_p1.measure > 0 && dm_pi_q2.measure > 0)
375470
{
376-
ti.operations[1].operation = operation_blocked;
471+
// Even though there is a touch, Q(j) is left of P1
472+
// and P(i) is still left from Q2.
473+
// It can continue.
474+
ti.operations[0].operation = operation_blocked;
475+
// Q turns right -> union (both independent),
476+
// Q turns left -> intersection
477+
ti.operations[1].operation = operation_union;
478+
ti.touch_only = true;
479+
return true;
377480
}
378-
}*/
379481

482+
dm_type const dm_pj_q1 = get_distance_measure(range_q.at(0), range_q.at(1), range_p.at(1));
483+
dm_type const dm_qi_p2 = get_distance_measure(range_p.at(1), range_p.at(2), range_q.at(0));
484+
485+
if (dm_pj_q1.measure > 0 && dm_qi_p2.measure > 0)
486+
{
487+
// Even though there is a touch, Q(j) is left of P1
488+
// and P(i) is still left from Q2.
489+
// It can continue.
490+
ti.operations[0].operation = operation_union;
491+
// Q turns right -> union (both independent),
492+
// Q turns left -> intersection
493+
ti.operations[1].operation = operation_blocked;
494+
ti.touch_only = true;
495+
return true;
496+
}
497+
return false;
498+
}
499+
#endif
380500

381501
template
382502
<
@@ -400,9 +520,10 @@ struct touch : public base_turn_handler
400520
bool const has_pk = ! range_p.is_last_segment();
401521
bool const has_qk = ! range_q.is_last_segment();
402522

403-
int const side_qi_p1 = dir_info.sides.template get<1, 0>();
404-
int const side_qk_p1 = has_qk ? side.qk_wrt_p1() : 0;
523+
int const side_pk_q1 = has_pk ? side.pk_wrt_q1() : 0;
405524

525+
int const side_qi_p1 = verified_side(dir_info.sides.template get<1, 0>(), range_p, range_q, 0, 0);
526+
int const side_qk_p1 = has_qk ? verified_side(side.qk_wrt_p1(), range_p, range_q, 0, 2) : 0;
406527

407528
// If Qi and Qk are both at same side of Pi-Pj,
408529
// or collinear (so: not opposite sides)
@@ -413,6 +534,7 @@ struct touch : public base_turn_handler
413534
int const side_qk_q = has_qk ? side.qk_wrt_q1() : 0;
414535

415536
bool const q_turns_left = side_qk_q == 1;
537+
416538
bool const block_q = side_qk_p1 == 0
417539
&& ! same(side_qi_p1, side_qk_q)
418540
;
@@ -422,9 +544,18 @@ struct touch : public base_turn_handler
422544
// or Q is fully collinear && P turns not to left
423545
if (side_pk_p == side_qi_p1
424546
|| side_pk_p == side_qk_p1
425-
|| (side_qi_p1 == 0 && side_qk_p1 == 0 && side_pk_p != -1)
426-
)
547+
|| (side_qi_p1 == 0 && side_qk_p1 == 0 && side_pk_p != -1))
427548
{
549+
#if ! defined(BOOST_GEOMETRY_USE_RESCALING)
550+
if (side_qk_p1 == 0 && side_pk_q1 == 0
551+
&& has_qk && has_qk
552+
&& handle_imperfect_touch(range_p, range_q, ti))
553+
{
554+
// If q continues collinearly (opposite) with p, it should be blocked
555+
// but (FP) not if there is just a tiny space in between
556+
return;
557+
}
558+
#endif
428559
// Collinear -> lines join, continue
429560
// (#BRL2)
430561
if (side_pk_q2 == 0 && ! block_q)
@@ -433,8 +564,6 @@ struct touch : public base_turn_handler
433564
return;
434565
}
435566

436-
int const side_pk_q1 = has_pk && has_qk ? side.pk_wrt_q1() : 0;
437-
438567
// Collinear opposite case -> block P
439568
// (#BRL4, #BLR8)
440569
if (side_pk_q1 == 0)
@@ -457,7 +586,6 @@ struct touch : public base_turn_handler
457586
{
458587
ti.operations[1].operation = operation_blocked;
459588
}
460-
//block_second(block_q, ti);
461589
return;
462590
}
463591

@@ -483,7 +611,6 @@ struct touch : public base_turn_handler
483611
{
484612
ti.touch_only = true;
485613
}
486-
//block_second(block_q, ti);
487614
return;
488615
}
489616
}
@@ -508,8 +635,9 @@ struct touch : public base_turn_handler
508635
}
509636
else
510637
{
638+
// The qi/qk are opposite to each other, w.r.t. p1
511639
// From left to right or from right to left
512-
int const side_pk_p = has_pk ? side.pk_wrt_p1() : 0;
640+
int const side_pk_p = has_pk ? verified_side(side.pk_wrt_p1(), range_p, range_p, 0, 2) : 0;
513641
bool const right_to_left = side_qk_p1 == 1;
514642

515643
// If p turns into direction of qi (1,2)
@@ -601,23 +729,23 @@ struct equal : public base_turn_handler
601729
// Without rescaling, to check for union/intersection,
602730
// try to check side values (without any thresholds)
603731
typedef typename select_coordinate_type
604-
<
605-
typename UniqueSubRange1::point_type,
606-
typename UniqueSubRange2::point_type
607-
>::type coordinate_type;
732+
<
733+
typename UniqueSubRange1::point_type,
734+
typename UniqueSubRange2::point_type
735+
>::type coordinate_type;
608736

609737
typedef detail::distance_measure<coordinate_type> dm_type;
610738

611-
dm_type const dm_qk_p
739+
dm_type const dm_pk_q2
612740
= get_distance_measure(range_q.at(1), range_q.at(2), range_p.at(2));
613-
dm_type const dm_pk_q
741+
dm_type const dm_qk_p2
614742
= get_distance_measure(range_p.at(1), range_p.at(2), range_q.at(2));
615743

616-
if (dm_pk_q.measure != dm_qk_p.measure)
744+
if (dm_qk_p2.measure != dm_pk_q2.measure)
617745
{
618746
// A (possibly very small) difference is detected, which
619747
// can be used to distinguish between union/intersection
620-
ui_else_iu(dm_pk_q.measure < dm_qk_p.measure, ti);
748+
ui_else_iu(dm_qk_p2.measure < dm_pk_q2.measure, ti);
621749
return;
622750
}
623751
}

include/boost/geometry/strategies/cartesian/intersection.hpp

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,38 @@ struct cartesian_segments
371371
return unified<ratio_type>(sinfo, p, q, policy, modelled_range_p, modelled_range_q);
372372
}
373373

374+
//! Returns true if two segments do not overlap.
375+
//! If not, then no further calculations need to be done.
376+
template
377+
<
378+
std::size_t Dimension,
379+
typename CoordinateType,
380+
typename PointP,
381+
typename PointQ
382+
>
383+
static inline bool disjoint_by_range(PointP const& p1, PointP const& p2,
384+
PointQ const& q1, PointQ const& q2)
385+
{
386+
CoordinateType minp = get<Dimension>(p1);
387+
CoordinateType maxp = get<Dimension>(p2);
388+
CoordinateType minq = get<Dimension>(q1);
389+
CoordinateType maxq = get<Dimension>(q2);
390+
if (minp > maxp)
391+
{
392+
std::swap(minp, maxp);
393+
}
394+
if (minq > maxq)
395+
{
396+
std::swap(minq, maxq);
397+
}
398+
399+
// In this case, max(p) < min(q)
400+
// P Q
401+
// <-------> <------->
402+
// (and the space in between is not extremely small)
403+
return math::smaller(maxp, minq) || math::smaller(maxq, minp);
404+
}
405+
374406
// Implementation for either rescaled or non rescaled versions.
375407
template
376408
<
@@ -413,6 +445,12 @@ struct cartesian_segments
413445
;
414446
}
415447

448+
if (disjoint_by_range<0, coordinate_type>(p1, p2, q1, q2)
449+
|| disjoint_by_range<1, coordinate_type>(p1, p2, q1, q2))
450+
{
451+
return Policy::disjoint();
452+
}
453+
416454
side_info sides;
417455
sides.set<0>(side_strategy_type::apply(q1, q2, p1),
418456
side_strategy_type::apply(q1, q2, p2));
@@ -467,8 +505,8 @@ struct cartesian_segments
467505
{
468506
// If this is the case, no rescaling is done for FP precision.
469507
// We set it to collinear, but it indicates a robustness issue.
470-
sides.set<0>(0,0);
471-
sides.set<1>(0,0);
508+
sides.set<0>(0, 0);
509+
sides.set<1>(0, 0);
472510
collinear = true;
473511
}
474512
else

test/algorithms/buffer/buffer_linestring_aimes.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -483,8 +483,8 @@ int test_main(int, char* [])
483483
test_aimes<bg::model::point<default_test_type, 2, bg::cs::cartesian> >();
484484

485485
#if defined(BOOST_GEOMETRY_TEST_FAILURES)
486-
// Non-rescaled reports 22 failures (failures in validity only)
487-
BoostGeometryWriteExpectedFailures(BG_NO_FAILURES, 22);
486+
// Non-rescaled reports failures, but in validity only
487+
BoostGeometryWriteExpectedFailures(BG_NO_FAILURES, 4);
488488
#endif
489489

490490
return 0;

test/algorithms/buffer/buffer_multi_polygon.cpp

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,6 @@ static std::string const mysql_report_2015_07_05_1
307307
static std::string const mysql_report_2015_07_05_2
308308
= "MULTIPOLYGON(((19777 -21893,3.22595e+307 6.86823e+307,-40 -13,19777 -21893)),((-1322 4851,8.49998e+307 3.94481e+307,75 -69,8.64636e+307 3.94909e+307,-1.15292e+18 7.20576e+16,-1322 4851)))";
309309

310-
#if ! defined(BOOST_GEOMETRY_USE_RESCALING) \
311-
&& defined(BOOST_GEOMETRY_USE_KRAMER_RULE) \
312-
&& ! defined(BOOST_GEOMETRY_TEST_FAILURES)
313-
// These testcases are failing for non-rescaled Kramer rule
314-
#define BOOST_GEOMETRY_EXCLUDE
315-
#endif
316-
317310
template <bool Clockwise, typename P>
318311
void test_all()
319312
{
@@ -453,18 +446,14 @@ void test_all()
453446
test_one<multi_polygon_type, polygon_type>("rt_p19", rt_p19, join_miter, end_flat, 25.5637, 1.0);
454447
test_one<multi_polygon_type, polygon_type>("rt_p20", rt_p20, join_miter, end_flat, 25.4853, 1.0);
455448
test_one<multi_polygon_type, polygon_type>("rt_p21", rt_p21, join_miter, end_flat, 17.1716, 1.0);
456-
#if ! defined(BOOST_GEOMETRY_EXCLUDE)
457449
test_one<multi_polygon_type, polygon_type>("rt_p22", rt_p22, join_miter, end_flat, 26.5711, 1.0);
458-
#endif
459450

460451
test_one<multi_polygon_type, polygon_type>("rt_q1", rt_q1, join_miter, end_flat, 27, 1.0);
461452
test_one<multi_polygon_type, polygon_type>("rt_q2", rt_q2, join_miter, end_flat, 26.4853, 1.0);
462453
test_one<multi_polygon_type, polygon_type>("rt_q2", rt_q2, join_miter, end_flat, 0.9697, -0.25);
463454

464455
test_one<multi_polygon_type, polygon_type>("rt_r", rt_r, join_miter, end_flat, 21.0761, 1.0);
465-
#if ! defined(BOOST_GEOMETRY_EXCLUDE)
466456
test_one<multi_polygon_type, polygon_type>("rt_s1", rt_s1, join_miter, end_flat, 20.4853, 1.0);
467-
#endif
468457

469458
test_one<multi_polygon_type, polygon_type>("rt_s2", rt_s2, join_miter, end_flat, 24.6495, 1.0);
470459

@@ -543,7 +532,7 @@ int test_main(int, char* [])
543532
#endif
544533

545534
#if defined(BOOST_GEOMETRY_TEST_FAILURES)
546-
BoostGeometryWriteExpectedFailures(1, 5);
535+
BoostGeometryWriteExpectedFailures(1, 1);
547536
#endif
548537

549538
return 0;

0 commit comments

Comments
 (0)