Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -266,7 +266,7 @@ class piece_turn_visitor

typedef detail::overlay::get_turn_info
<
detail::overlay::assign_null_policy
detail::overlay::assign_policy_only_start_turns
> turn_policy;

turn_policy::apply(unique_sub_range1, unique_sub_range2,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ struct assign_disjoint_policy
static bool const include_no_turn = true;
static bool const include_degenerate = true;
static bool const include_opposite = true;
static bool const include_start_turn = false;
};


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -491,8 +491,8 @@ inline void enrich_intersection_points(Turns& turns,
{
detail::overlay::discard_closed_turns
<
OverlayType,
target_operation
OverlayType,
target_operation
>::apply(turns, clusters, geometry1, geometry2,
strategy);
detail::overlay::discard_open_turns
Expand Down
87 changes: 85 additions & 2 deletions include/boost/geometry/algorithms/detail/overlay/get_turn_info.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -830,6 +830,65 @@ struct equal : public base_turn_handler
}
};

template
<
typename TurnInfo
>
struct start : public base_turn_handler
{
template
<
typename UniqueSubRange1,
typename UniqueSubRange2,
typename IntersectionInfo,
typename DirInfo,
typename SideCalculator,
typename UmbrellaStrategy
>
static inline bool apply(UniqueSubRange1 const& /*range_p*/,
UniqueSubRange2 const& /*range_q*/,
TurnInfo& ti,
IntersectionInfo const& info,
DirInfo const& dir_info,
SideCalculator const& side,
UmbrellaStrategy const& )
{
#if defined(BOOST_GEOMETRY_USE_RESCALING)
// With rescaling, start turns are not needed.
return false;
#endif

// Start turns have either how_a = -1, or how_b = -1 (either p leaves or q leaves)
BOOST_GEOMETRY_ASSERT(dir_info.how_a != dir_info.how_b);
BOOST_GEOMETRY_ASSERT(dir_info.how_a == -1 || dir_info.how_b == -1);
BOOST_GEOMETRY_ASSERT(dir_info.how_a == 0 || dir_info.how_b == 0);

if (dir_info.how_b == -1)
{
// p --------------->
// |
// | q q leaves
// v
//

int const side_qj_p1 = side.qj_wrt_p1();
ui_else_iu(side_qj_p1 == -1, ti);
}
else if (dir_info.how_a == -1)
{
// p leaves
int const side_pj_q1 = side.pj_wrt_q1();
ui_else_iu(side_pj_q1 == 1, ti);
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is relatively simple now, I first had more conditions but they were not relevant in the end.
But I might find cases where some behaviour should be fine-tuned.


// Copy intersection point
assign_point(ti, method_start, info, 0);
return true;
}

};


template
<
typename TurnInfo,
Expand Down Expand Up @@ -1194,6 +1253,15 @@ struct assign_null_policy
static bool const include_no_turn = false;
static bool const include_degenerate = false;
static bool const include_opposite = false;
static bool const include_start_turn = false;
};

struct assign_policy_only_start_turns
{
static bool const include_no_turn = false;
static bool const include_degenerate = false;
static bool const include_opposite = false;
static bool const include_start_turn = true;
};

/*!
Expand Down Expand Up @@ -1257,9 +1325,24 @@ struct get_turn_info
bool handle_as_equal = method == 'e';
bool const handle_as_collinear = method == 'c';
bool const handle_as_degenerate = method == '0';
bool const handle_as_start = method == 's';

// (angle, from, start)
bool const do_only_convert = method == 'a' || method == 'f' || method == 's';
// (angle, from)
bool do_only_convert = method == 'a' || method == 'f';

if (handle_as_start)
{
// It is in some cases necessary to handle a start turn
if (AssignPolicy::include_start_turn
&& start<TurnInfo>::apply(range_p, range_q, tp, inters.i_info(), inters.d_info(), inters.sides(), umbrella_strategy))
{
*out++ = tp;
}
else
{
do_only_convert = true;
}
}

if (handle_as_touch_interior)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,46 @@ inline void discard_interior_exterior_turns(Turns& turns, Clusters& clusters)
}
}

template<typename Turns, typename Clusters>
inline void discard_start_turns(Turns& turns, Clusters& clusters)
{
for (auto& nv : clusters)
{
cluster_info& cinfo = nv.second;
auto& indices = cinfo.turn_indices;
std::size_t start_count{0};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why not std::size_t start_count = 0; ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Habit, it the office we're preferring the new uniform initialization.

What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used to be very keen in preferring the uniform initialization heavily and everywhere, until I read the abseil guidelines and tips, in particular https://abseil.io/tips/88

First, “uniform” is a stretch: there are cases where ambiguity still exists (for the casual reader, not the compiler) in what is being called and how.

std::vector<std::string> strings{2}; // A vector of two empty strings.
std::vector<int> ints{2};            // A vector containing only the integer 2.

It's a pity the uniform initialization is not corner-case free improvement to C++.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this particular case I'd use = 0 (also for numeric classes) because in such case the variable represents 0.

This is not a case when an object of a class is created by passing some abstract which may describe something like number of elements.

From the same reason I'd probably initialize the vector like the one below. In other words to express that the initializer list on the right represents the vector or that it is the vector (just like with 0 being the number):

std::vector<int> ints = {2};

This works right? ;)

And then for all other things I don't have an opinion. I'd probably use the old parentheses. But maybe that's just because it's what I'm familiar with. Maybe with the exception of avoiding double parentheses to deal with the most vexing parse.

So I guess I could also express this as a conservative stance where old initialization is used when possible as it was in the past and new brackets only for new features.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting visions, thanks.
I agree with the statement that it's not that intuitive, int i{0} is quite different from all other languages where it is something like int i=0;
I'll think about it. For now I will merge this, assuming these were not objections.

for (signed_size_type index : indices)
{
auto const& turn = turns[index];
if (turn.method == method_start)
{
start_count++;
}
}
if (start_count == 0 && start_count == indices.size())
{
// There are no start turns, or all turns in the cluster are start turns.
continue;
}

// Discard the start turns and simultaneously erase them from the indices
for (auto it = indices.begin(); it != indices.end();)
{
auto& turn = turns[*it];
if (turn.method == method_start)
{
turn.discarded = true;
turn.cluster_id = -1;
it = indices.erase(it);
}
else
{
++it;
}
}
}
}

template <typename Geometry0, typename Geometry1>
inline segment_identifier get_preceding_segment_id(segment_identifier const& id,
Geometry0 const& geometry0, Geometry1 const& geometry1)
Expand Down Expand Up @@ -700,6 +740,8 @@ inline bool handle_colocations(Turns& turns, Clusters& clusters,
// on turns which are discarded afterwards
set_colocation<OverlayType>(turns, clusters);

discard_start_turns(turns, clusters);

if (BOOST_GEOMETRY_CONDITION(target_operation == operation_intersection))
{
discard_interior_exterior_turns
Expand Down Expand Up @@ -783,7 +825,7 @@ inline bool fill_sbs(Sbs& sbs, Point& turn_point,
{
signed_size_type turn_index = *sit;
turn_type const& turn = turns[turn_index];
if (first )
if (first)
{
turn_point = turn.point;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ class linear_linear_linestring
static bool const include_no_turn = false;
static bool const include_degenerate = EnableDegenerateTurns;
static bool const include_opposite = false;
static bool const include_start_turn = false;
};


Expand Down
6 changes: 3 additions & 3 deletions include/boost/geometry/algorithms/detail/overlay/overlay.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ std::cout << "get turns" << std::endl;
geometry::get_turns
<
Reverse1, Reverse2,
detail::overlay::assign_null_policy
assign_policy_only_start_turns
>(geometry1, geometry2, strategy, robust_policy, turns, policy);

visitor.visit_turns(1, turns);
Expand All @@ -318,12 +318,12 @@ std::cout << "get turns" << std::endl;
// and if necessary (e.g.: multi-geometry, polygon with interior rings)
if (needs_self_turns<Geometry1>::apply(geometry1))
{
self_get_turn_points::self_turns<Reverse1, assign_null_policy>(geometry1,
self_get_turn_points::self_turns<Reverse1, assign_policy_only_start_turns>(geometry1,
strategy, robust_policy, turns, policy, 0);
}
if (needs_self_turns<Geometry2>::apply(geometry2))
{
self_get_turn_points::self_turns<Reverse2, assign_null_policy>(geometry2,
self_get_turn_points::self_turns<Reverse2, assign_policy_only_start_turns>(geometry2,
strategy, robust_policy, turns, policy, 1);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ enum method_type
method_touch_interior,
method_collinear,
method_equal,
method_start,
method_error
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ struct areal_interrupt_policy
return true;
}
break;
case overlay::method_start :
case overlay::method_none :
case overlay::method_disjoint :
case overlay::method_error :
Expand Down
65 changes: 64 additions & 1 deletion test/algorithms/buffer/buffer_multi_polygon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,52 @@ static std::string const rt_v3
static std::string const rt_v4
= "MULTIPOLYGON(((5 4,5 5,6 5,6 4,5 4)),((7 1,6 1,7 2,7 1)),((7 1,8 1,8 0,7 0,7 1)),((6 1,5 1,5 2,6 1)))";

// From 2020 runs of robustness test recursive_polygons_buffer, without rescaling
// For the same problem there can be multiple cases, but details differ

// Cases missing a turn, or needing a start turn
static std::string const nores_mt_1
= "MULTIPOLYGON(((4 8,4 9,5 9,4 8)),((3 6,3 7,4 6,3 6)))";

static std::string const nores_mt_2
= "MULTIPOLYGON(((5 3,6 4,6 3,5 3)),((4 4,3 4,4 5,5 5,4 4)),((4 5,3 5,3 6,4 5)))";

static std::string const nores_mt_3
= "MULTIPOLYGON(((7 4,7 5,8 5,8 4,7 4)),((2 6,2 7,3 6,2 6)),((3 10,4 10,4 9,4 8,3 8,3 10)))";

static std::string const nores_mt_4
= "MULTIPOLYGON(((6 8,6 9,7 9,6 8)),((1 5,1 6,2 6,1 5)),((7 7,8 8,8 7,7 7)),((0 3,0 4,1 3,0 3)))";

static std::string const nores_mt_5
= "MULTIPOLYGON(((4 3,4 4,5 4,5 3,4 3)),((3 1,3 2,4 1,3 1)),((1 6,2 7,2 6,1 6)),((3 6,4 5,3 4,3 6)))";

static std::string const nores_mt_6
= "MULTIPOLYGON(((5 7,5 6,4 6,4 5,4 4,3 4,3 6,3 7,5 7)))";

// Cases generating an extra turn, and/or a cluster not yet handled correctly
static std::string const nores_et_1
= "MULTIPOLYGON(((5 7,5 8,6 8,5 7)),((5 4,5 5,6 4,5 4)),((3 6,4 7,4 6,3 6)))";

static std::string const nores_et_2
= "MULTIPOLYGON(((4 2,5 3,5 2,4 2)),((6 3,6 4,7 4,7 3,6 3)),((7 2,8 3,8 2,7 2)),((4 4,4 5,5 5,5 4,4 4)))";

static std::string const nores_et_3
= "MULTIPOLYGON(((3 1,3 2,4 2,4 1,3 1)),((5 4,5 3,4 3,5 4)),((5 3,6 2,5 2,5 3)),((8 1,7 1,6 1,7 2,7 3,7.5 2.5,8 3,8 1)))";

static std::string const nores_et_4
= "MULTIPOLYGON(((4 7,4 8,5 8,5 7,4 7)),((3 5,3 6,4 5,3 5)),((1 6,2 7,2 6,1 6)))";

static std::string const nores_et_5
= "MULTIPOLYGON(((3 2,3 3,4 3,4 2,3 2)),((0 3,0 4,1 3,0 3)),((2 2,2 1,1 1,2 2)))";


// Cases having wrong next turn information, somehow, taking the "i" (intersection),
// which is wrong for buffer (it should take the "u" like union)
static std::string const nores_wn_1
= "MULTIPOLYGON(((8 3,8 4,9 4,9 3,8 3)),((9 5,9 6,10 5,9 5)),((8 8,9 9,9 8,8 8)),((8 8,8 7,7 7,8 8)))";
static std::string const nores_wn_2
= "MULTIPOLYGON(((9 5,9 6,10 5,9 5)),((8 8,8 7,7 7,7 8,8 8)),((8 8,9 9,9 8,8 8)))";


static std::string const neighbouring
= "MULTIPOLYGON(((0 0,0 10,10 10,10 0,0 0)),((10 10,10 20,20 20,20 10,10 10)))";
Expand Down Expand Up @@ -498,6 +544,23 @@ void test_all()
test_one<multi_polygon_type, polygon_type>("rt_v3", rt_v3, join_round32, end_flat, 22.9158, 1.0);
test_one<multi_polygon_type, polygon_type>("rt_v4", rt_v4, join_round32, end_flat, 23.4146, 1.0);

test_one<multi_polygon_type, polygon_type>("nores_mt_1", nores_mt_1, join_round32, end_flat, 13.4113, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_mt_2", nores_mt_2, join_round32, end_flat, 17.5265, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_mt_3", nores_mt_3, join_round32, end_flat, 25.6091, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_mt_4", nores_mt_4, join_round32, end_flat, 26.0946, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_mt_5", nores_mt_5, join_round32, end_flat, 26.4375, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_mt_6", nores_mt_6, join_round32, end_flat, 16.9018, 1.0);

#if defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES)
test_one<multi_polygon_type, polygon_type>("nores_et_1", nores_et_1, join_round32, end_flat, 18.9866, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_et_2", nores_et_2, join_round32, end_flat, 23.8389, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_et_3", nores_et_3, join_round32, end_flat, 26.9030, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_et_4", nores_et_4, join_round32, end_flat, 19.9626, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_et_5", nores_et_5, join_round32, end_flat, 19.9615, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_wn_1", nores_wn_1, join_round32, end_flat, 23.7659, 1.0);
test_one<multi_polygon_type, polygon_type>("nores_wn_2", nores_wn_2, join_round32, end_flat, 18.2669, 1.0);
#endif

test_one<multi_polygon_type, polygon_type>("neighbouring_small",
neighbouring,
join_round32, end_round32, 128.0, -1.0);
Expand Down Expand Up @@ -541,7 +604,7 @@ int test_main(int, char* [])
#endif

#if defined(BOOST_GEOMETRY_TEST_FAILURES)
BoostGeometryWriteExpectedFailures(1, 1, 2, 3);
BoostGeometryWriteExpectedFailures(1, 14, 2, 11);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it's related to precision, for float these changes were not necessary.
double/long double add more errors, just because I added the failing cases and part of them are not yet solved.

#endif

return 0;
Expand Down
1 change: 1 addition & 0 deletions test/algorithms/set_operations/check_turn_less.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ struct check_turn_less
static bool const include_no_turn = false;
static bool const include_degenerate = EnableDegenerateTurns;
static bool const include_opposite = false;
static bool const include_start_turn = false;

template
<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ class test_get_turns_ll_invariance
static bool const include_no_turn = false;
static bool const include_degenerate = EnableDegenerateTurns;
static bool const include_opposite = false;
static bool const include_start_turn = false;

template
<
Expand Down