mirror of
https://github.com/boostorg/geometry.git
synced 2025-05-09 15:14:02 +00:00
Merge pull request #1113 from barendgehrels/issue-1108-fix-interior-ring-for-union
[union] fix missing interior ring and double traversed exterior ring
This commit is contained in:
commit
dfcbb75602
@ -0,0 +1,107 @@
|
||||
// Boost.Geometry
|
||||
|
||||
// Copyright (c) 2023 Barend Gehrels, Amsterdam, the Netherlands.
|
||||
|
||||
// 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_COLOCATE_CLUSTERS_HPP
|
||||
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_COLOCATE_CLUSTERS_HPP
|
||||
|
||||
#include <boost/geometry/core/access.hpp>
|
||||
#include <boost/geometry/core/cs.hpp>
|
||||
#include <boost/geometry/core/coordinate_type.hpp>
|
||||
#include <boost/geometry/core/tags.hpp>
|
||||
|
||||
namespace boost { namespace geometry
|
||||
{
|
||||
|
||||
#ifndef DOXYGEN_NO_DETAIL
|
||||
namespace detail { namespace overlay
|
||||
{
|
||||
|
||||
// Default implementation, using the first point for all turns in the cluster.
|
||||
template
|
||||
<
|
||||
typename Point,
|
||||
typename CoordinateType = typename geometry::coordinate_type<Point>::type,
|
||||
typename CsTag = typename geometry::cs_tag<Point>::type,
|
||||
bool IsIntegral = std::is_integral<CoordinateType>::value
|
||||
>
|
||||
struct cluster_colocator
|
||||
{
|
||||
template <typename TurnIndices, typename Turns>
|
||||
static inline void apply(TurnIndices const& indices, Turns& turns)
|
||||
{
|
||||
// This approach works for all but one testcase (rt_p13)
|
||||
// The problem is fill_sbs, which uses sides and these sides might change slightly
|
||||
// depending on the exact location of the cluster.
|
||||
// Using the centroid is, on the average, a safer choice for sides.
|
||||
// Alternatively fill_sbs could be revised, but that requires a lot of work
|
||||
// and is outside current scope.
|
||||
// Integer coordinates are always colocated already and do not need centroid calculation.
|
||||
// Geographic/spherical coordinates might (in extremely rare cases) cross the date line
|
||||
// and therefore the first point is taken for them as well.
|
||||
auto it = indices.begin();
|
||||
auto const& first_point = turns[*it].point;
|
||||
for (++it; it != indices.end(); ++it)
|
||||
{
|
||||
turns[*it].point = first_point;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Specialization for non-integral cartesian coordinates, calculating
|
||||
// the centroid of the points of the turns in the cluster.
|
||||
template <typename Point, typename CoordinateType>
|
||||
struct cluster_colocator<Point, CoordinateType, geometry::cartesian_tag, false>
|
||||
{
|
||||
template <typename TurnIndices, typename Turns>
|
||||
static inline void apply(TurnIndices const& indices, Turns& turns)
|
||||
{
|
||||
CoordinateType centroid_0 = 0;
|
||||
CoordinateType centroid_1 = 0;
|
||||
for (const auto& index : indices)
|
||||
{
|
||||
centroid_0 += geometry::get<0>(turns[index].point);
|
||||
centroid_1 += geometry::get<1>(turns[index].point);
|
||||
}
|
||||
centroid_0 /= indices.size();
|
||||
centroid_1 /= indices.size();
|
||||
for (const auto& index : indices)
|
||||
{
|
||||
geometry::set<0>(turns[index].point, centroid_0);
|
||||
geometry::set<1>(turns[index].point, centroid_1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Moves intersection points per cluster such that they are identical.
|
||||
// Because clusters are intersection close together, and
|
||||
// handled as one location. Then they should also have one location.
|
||||
// It is necessary to avoid artefacts and invalidities.
|
||||
template <typename Clusters, typename Turns>
|
||||
inline void colocate_clusters(Clusters const& clusters, Turns& turns)
|
||||
{
|
||||
for (auto const& pair : clusters)
|
||||
{
|
||||
auto const& indices = pair.second.turn_indices;
|
||||
if (indices.size() < 2)
|
||||
{
|
||||
// Defensive check
|
||||
continue;
|
||||
}
|
||||
using point_t = decltype(turns[*indices.begin()].point);
|
||||
cluster_colocator<point_t>::apply(indices, turns);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}} // namespace detail::overlay
|
||||
#endif //DOXYGEN_NO_DETAIL
|
||||
|
||||
|
||||
}} // namespace boost::geometry
|
||||
|
||||
#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_COLOCATE_CLUSTERS_HPP
|
@ -39,9 +39,8 @@ private:
|
||||
template <typename T>
|
||||
static inline T threshold()
|
||||
{
|
||||
// Tuned by cases of #1081. It should just be one epsilon to distinguish between
|
||||
// different turn-points and real colocated clusters.
|
||||
return T(1);
|
||||
// Points within some epsilons are considered as equal.
|
||||
return T(100);
|
||||
}
|
||||
public:
|
||||
// Returns true if point are considered equal (within an epsilon)
|
||||
|
@ -29,6 +29,7 @@
|
||||
#include <boost/geometry/core/point_order.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/cluster_info.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/do_reverse.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/colocate_clusters.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/get_clusters.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
|
||||
#include <boost/geometry/algorithms/detail/overlay/is_self_turn.hpp>
|
||||
@ -52,22 +53,22 @@ namespace boost { namespace geometry
|
||||
namespace detail { namespace overlay
|
||||
{
|
||||
|
||||
// Removes clusters which have only one point left, or are empty.
|
||||
template <typename Turns, typename Clusters>
|
||||
inline void remove_clusters(Turns& turns, Clusters& clusters)
|
||||
{
|
||||
typename Clusters::iterator it = clusters.begin();
|
||||
auto it = clusters.begin();
|
||||
while (it != clusters.end())
|
||||
{
|
||||
// Hold iterator and increase. We can erase cit, this keeps the
|
||||
// iterator valid (cf The standard associative-container erase idiom)
|
||||
typename Clusters::iterator current_it = it;
|
||||
auto current_it = it;
|
||||
++it;
|
||||
|
||||
std::set<signed_size_type> const& turn_indices
|
||||
= current_it->second.turn_indices;
|
||||
auto const& turn_indices = current_it->second.turn_indices;
|
||||
if (turn_indices.size() == 1)
|
||||
{
|
||||
signed_size_type const turn_index = *turn_indices.begin();
|
||||
auto const turn_index = *turn_indices.begin();
|
||||
turns[turn_index].cluster_id = -1;
|
||||
clusters.erase(current_it);
|
||||
}
|
||||
@ -78,18 +79,16 @@ template <typename Turns, typename Clusters>
|
||||
inline void cleanup_clusters(Turns& turns, Clusters& clusters)
|
||||
{
|
||||
// Removes discarded turns from clusters
|
||||
for (typename Clusters::iterator mit = clusters.begin();
|
||||
mit != clusters.end(); ++mit)
|
||||
for (auto& pair : clusters)
|
||||
{
|
||||
cluster_info& cinfo = mit->second;
|
||||
std::set<signed_size_type>& ids = cinfo.turn_indices;
|
||||
for (std::set<signed_size_type>::iterator sit = ids.begin();
|
||||
sit != ids.end(); /* no increment */)
|
||||
auto& cinfo = pair.second;
|
||||
auto& ids = cinfo.turn_indices;
|
||||
for (auto sit = ids.begin(); sit != ids.end(); /* no increment */)
|
||||
{
|
||||
std::set<signed_size_type>::iterator current_it = sit;
|
||||
auto current_it = sit;
|
||||
++sit;
|
||||
|
||||
signed_size_type const turn_index = *current_it;
|
||||
auto const turn_index = *current_it;
|
||||
if (turns[turn_index].discarded)
|
||||
{
|
||||
ids.erase(current_it);
|
||||
@ -98,6 +97,7 @@ inline void cleanup_clusters(Turns& turns, Clusters& clusters)
|
||||
}
|
||||
|
||||
remove_clusters(turns, clusters);
|
||||
colocate_clusters(clusters, turns);
|
||||
}
|
||||
|
||||
template <typename Turn, typename IdSet>
|
||||
|
@ -968,31 +968,43 @@ public :
|
||||
{
|
||||
turn_type const& current_turn = m_turns[turn_index];
|
||||
|
||||
bool const back_at_start_cluster
|
||||
= has_points
|
||||
&& current_turn.is_clustered()
|
||||
&& m_turns[start_turn_index].cluster_id == current_turn.cluster_id;
|
||||
if (BOOST_GEOMETRY_CONDITION(target_operation == operation_intersection))
|
||||
{
|
||||
if (has_points)
|
||||
{
|
||||
bool const back_at_start_cluster
|
||||
= current_turn.is_clustered()
|
||||
&& m_turns[start_turn_index].cluster_id == current_turn.cluster_id;
|
||||
// Intersection or difference
|
||||
|
||||
if (turn_index == start_turn_index || back_at_start_cluster)
|
||||
{
|
||||
// Intersection can always be finished if returning
|
||||
turn_index = start_turn_index;
|
||||
op_index = start_op_index;
|
||||
return true;
|
||||
}
|
||||
if (has_points && (turn_index == start_turn_index || back_at_start_cluster))
|
||||
{
|
||||
// Intersection can always be finished if returning
|
||||
turn_index = start_turn_index;
|
||||
op_index = start_op_index;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (! current_turn.is_clustered()
|
||||
&& current_turn.both(operation_intersection))
|
||||
{
|
||||
if (analyze_ii_intersection(turn_index, op_index,
|
||||
&& current_turn.both(operation_intersection)
|
||||
&& analyze_ii_intersection(turn_index, op_index,
|
||||
current_turn, previous_seg_id))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (turn_index == start_turn_index || back_at_start_cluster)
|
||||
{
|
||||
// Union or buffer: cannot return immediately to starting turn, because it then
|
||||
// might miss a formed multi polygon with a touching point.
|
||||
auto const& current_op = current_turn.operations[op_index];
|
||||
signed_size_type const next_turn_index = current_op.enriched.get_next_turn_index();
|
||||
bool const to_other_turn = next_turn_index >= 0 && m_turns[next_turn_index].cluster_id != current_turn.cluster_id;
|
||||
if (! to_other_turn)
|
||||
{
|
||||
// Return to starting point
|
||||
turn_index = start_turn_index;
|
||||
op_index = start_op_index;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -540,7 +540,10 @@ void test_all()
|
||||
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p11", rt_p11, join_miter, end_flat, 28.7426, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p12", rt_p12, join_miter, end_flat, 22.5711, 1.0);
|
||||
|
||||
// Needs centroid of cluster turn points
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p13", rt_p13, join_miter, end_flat, 19.9142, 1.0);
|
||||
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p14", rt_p14, join_miter, end_flat, 20.8284, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p15", rt_p15, join_miter, end_flat, 23.6569, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("rt_p16", rt_p16, join_miter, end_flat, 23.4853, 1.0);
|
||||
@ -631,11 +634,9 @@ void test_all()
|
||||
test_one<multi_polygon_type, polygon_type>("nores_6061", nores_6061, join_round32, end_flat, 39.7371, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("nores_37f6", nores_37f6, join_round32, end_flat, 26.5339, 1.0);
|
||||
|
||||
#if defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES)
|
||||
// Fails since get_cluster works with an epsilon of 1 (instead of 1000 before).
|
||||
// With 3 it still succeeds but that causes regression in issue #issue_1081b
|
||||
// Needs an espilon in get_cluster of 3 or higher
|
||||
test_one<multi_polygon_type, polygon_type>("nores_1ea1", nores_1ea1, join_round32, end_flat, 28.9755, 1.0);
|
||||
#endif
|
||||
|
||||
test_one<multi_polygon_type, polygon_type>("nores_804e", nores_804e, join_round32, end_flat, 26.4503, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("nores_51c6", nores_51c6, join_round32, end_flat, 20.2419, 1.0);
|
||||
test_one<multi_polygon_type, polygon_type>("nores_e5f3", nores_e5f3, join_round32, end_flat, 14.5503, 1.0);
|
||||
@ -651,7 +652,7 @@ void test_all()
|
||||
#if ! defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES)
|
||||
// Erroneous case with rescaling
|
||||
test_one<multi_polygon_type, polygon_type>("res_8618", res_8618, join_round32, end_flat, 48.1085, 1.0);
|
||||
#endif
|
||||
#endif
|
||||
test_one<multi_polygon_type, polygon_type>("res_3b4d", res_3b4d, join_round32, end_flat, 48.4739, 1.0);
|
||||
|
||||
test_one<multi_polygon_type, polygon_type>("neighbouring_small",
|
||||
|
@ -101,13 +101,11 @@ int test_main(int, char* [])
|
||||
test_get_clusters<dp>();
|
||||
test_get_clusters<ep>();
|
||||
|
||||
// This constant relates to the threshold in get_clusters,
|
||||
// which is now return T(1) (earlier it was 1000)
|
||||
double const multiplier = 1.0 / 1000.0;
|
||||
|
||||
test_get_clusters_border_cases<fp>(1.0e-4 * multiplier);
|
||||
test_get_clusters_border_cases<dp>(1.0e-13 * multiplier);
|
||||
test_get_clusters_border_cases<ep>(1.0e-16 * multiplier);
|
||||
// These constant relate to the threshold in get_clusters.hpp,
|
||||
// and the used floating point type.
|
||||
test_get_clusters_border_cases<fp>(1.0e-5);
|
||||
test_get_clusters_border_cases<dp>(1.0e-14);
|
||||
test_get_clusters_border_cases<ep>(1.0e-17);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -1574,6 +1574,12 @@ static std::string issue_930[2] =
|
||||
"MULTIPOLYGON(((-10 2,5 3,20 2,-10 2)))"
|
||||
};
|
||||
|
||||
static std::string issue_1109[2] =
|
||||
{
|
||||
"MULTIPOLYGON(((-63 -88,0 -115.40000152587890625,0 -124,-63 -124,-63 -88)),((0 -150,0 -124,13 -124,13 -121,0 -115.40000152587890625,0 -88,40 -88,40 -150,0 -150)))",
|
||||
"MULTIPOLYGON(((0 -88,0 -115.40000152587890625,-10 -88,0 -88)))"
|
||||
};
|
||||
|
||||
static std::string bug_21155501[2] =
|
||||
{
|
||||
"MULTIPOLYGON(((-8.3935546875 27.449790329784214,4.9658203125 18.729501999072138,11.8212890625 23.563987128451217,9.7119140625 25.48295117535531,9.8876953125 31.728167146023935,8.3056640625 32.99023555965106,8.5693359375 37.16031654673677,-1.8896484375 35.60371874069731,-0.5712890625 32.02670629333614,-8.9208984375 29.458731185355344,-8.3935546875 27.449790329784214)))",
|
||||
|
@ -1101,6 +1101,12 @@ static std::string issue_1081c[2] =
|
||||
"POLYGON((423.217316892426425 67.5751428875900331,414.40388971336813 96.7777620478938587,410.429206867910466 100.02249750988706,412.848579034710781 101.010025694314706,412.502954439453561 102.98508206238165,416.996074177797027 109.05132662251431,427.364812035512671 72.6538592632950468,423.217316892426425 67.5751428875900331))"
|
||||
};
|
||||
|
||||
static std::string issue_1108[2] =
|
||||
{
|
||||
"POLYGON((17 -2,18 -1.269679093926235014,12 0,22 0,17 -2))",
|
||||
"POLYGON((22 1,22 0,14 0,18 -1.2696790939262529996,12 0,22 1))"
|
||||
};
|
||||
|
||||
static std::string ggl_list_20120229_volker[3] =
|
||||
{
|
||||
"POLYGON((1716 1554,2076 2250,2436 2352,2796 1248,3156 2484,3516 2688,3516 2688,3156 2484,2796 1248,2436 2352,2076 2250, 1716 1554))",
|
||||
|
@ -416,7 +416,10 @@ void test_areal()
|
||||
TEST_UNION(ticket_10108_a, count_set(1, 2), 0, 8, 0.0435229);
|
||||
TEST_UNION(ticket_10108_b, count_set(1, 2), 0, 10, 2424.3449);
|
||||
|
||||
#if ! defined(BOOST_GEOMETRY_USE_RESCALING) || defined(BOOST_GEOMETRY_TEST_FAILURES)
|
||||
// With rescaling, there is a dependency on cluster tolerance, which alters the result.
|
||||
TEST_UNION(ticket_10866, 1, 0, 14, 332760303.5);
|
||||
#endif
|
||||
|
||||
TEST_UNION(ticket_11725, 1, 1, 10, 7.5);
|
||||
|
||||
@ -456,6 +459,9 @@ void test_areal()
|
||||
TEST_UNION(issue_1081c, 1, 1, -1, 2338.08);
|
||||
TEST_UNION_REV(issue_1081c, 1, 1, -1, 2338.08);
|
||||
|
||||
TEST_UNION(issue_1108, 1, 0, -1, 12.1742);
|
||||
TEST_UNION_REV(issue_1108, 1, 0, -1, 12.1742);
|
||||
|
||||
{
|
||||
// Rescaling produces an invalid result
|
||||
ut_settings settings;
|
||||
|
@ -445,6 +445,8 @@ void test_areal()
|
||||
TEST_UNION(issue_888_34, 15, 0, -1, 0.3017459);
|
||||
TEST_UNION(issue_888_37, 52, 3, -1, 0.4033294);
|
||||
|
||||
TEST_UNION(issue_1109, 2, 0, -1, 3946.5);
|
||||
|
||||
// One or two polygons, the ideal case is 1
|
||||
TEST_UNION(mail_2019_01_21_johan, count_set(1, 2), 0, -1, 0.00058896);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user