Skip to content

Commit e480444

Browse files
committed
feat: [extensions] include correct from Wouter Kleunen
1 parent 43120e2 commit e480444

File tree

2 files changed

+841
-0
lines changed

2 files changed

+841
-0
lines changed
Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
// Boost.Geometry
2+
//
3+
// Copyright (c) 2025 Barend Gehrels, Amsterdam, the Netherlands.
4+
5+
// Distributed under the Boost Software License, Version 1.0.
6+
// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
7+
8+
// Official repository: https://github.com/boostorg/geometry
9+
// Documentation: http://www.boost.org/libs/geometry
10+
11+
#if defined(TEST_WITH_GEOJSON)
12+
#define BOOST_GEOMETRY_DEBUG_SEGMENT_IDENTIFIER
13+
#define BOOST_GEOMETRY_DEBUG_IDENTIFIER
14+
#endif
15+
16+
#include <geometry_test_common.hpp>
17+
18+
#include <boost/geometry/extensions/algorithms/dissolve_using_correct.hpp>
19+
20+
// To check results
21+
#include <boost/geometry/algorithms/correct_closure.hpp>
22+
#include <boost/geometry/algorithms/area.hpp>
23+
#include <boost/geometry/algorithms/is_valid.hpp>
24+
25+
#include <boost/geometry/geometries/geometries.hpp>
26+
#include <boost/geometry/geometries/point_xy.hpp>
27+
28+
#include <boost/geometry/strategies/strategies.hpp>
29+
30+
#include <boost/geometry/io/wkt/wkt.hpp>
31+
32+
#include "dissolve_overlay_cases.hpp"
33+
34+
35+
#if defined(TEST_WITH_GEOJSON)
36+
#include <boost/geometry/extensions/gis/io/geojson/geojson_writer.hpp>
37+
#include "dissolve_geojson_visitor.hpp"
38+
#endif
39+
40+
// Equivalent with BOOST_CHECK_CLOSE
41+
// See also "expectation_limits.hpp" in the test directory
42+
template <typename Settings>
43+
bool is_equal_within_tolerance(Settings const& settings, double const value, double const expected)
44+
{
45+
double const fraction = settings.percentage / 100.0;
46+
double const lower_limit = expected * (1.0 - fraction);
47+
double const upper_limit = expected * (1.0 + fraction);
48+
return value >= lower_limit && value <= upper_limit;
49+
}
50+
51+
//! Unittest settings
52+
struct ut_settings
53+
{
54+
double remove_spike_threshold{1.0e-12};
55+
double percentage{0.01};
56+
bool test_validity{true};
57+
};
58+
59+
template <typename Geometry>
60+
std::string as_wkt(Geometry const& geometry)
61+
{
62+
std::ostringstream out;
63+
out << bg::wkt(geometry);
64+
return out.str();
65+
}
66+
67+
template <typename GeometryOut, typename Geometry>
68+
void dissolve_alternative(GeometryOut& geometry_out, Geometry const& geometry,
69+
double remove_spike_threshold)
70+
{
71+
boost::geometry::correct_non_zero(geometry, geometry_out, remove_spike_threshold);
72+
}
73+
74+
template <typename GeometryOut, typename Geometry>
75+
void test_dissolve(std::string const& caseid, Geometry const& geometry,
76+
double expected_area, ut_settings const& settings)
77+
{
78+
#if defined(TEST_WITH_GEOJSON)
79+
std::ostringstream filename;
80+
// For QGis, it is usually convenient to always write to the same geojson file.
81+
filename << "/tmp/"
82+
// << caseid << "_"
83+
<< "dissolve.geojson";
84+
std::ofstream geojson_file(filename.str().c_str());
85+
86+
boost::geometry::geojson_writer writer(geojson_file);
87+
#endif
88+
89+
using coordinate_type = typename bg::coordinate_type<Geometry>::type;
90+
using multi_polygon = bg::model::multi_polygon<GeometryOut>;
91+
multi_polygon dissolved;
92+
93+
dissolve_alternative(dissolved, geometry, settings.remove_spike_threshold);
94+
95+
#if defined(TEST_WITH_GEOJSON)
96+
geojson_visitor visitor(writer);
97+
#else
98+
bg::detail::overlay::overlay_null_visitor visitor;
99+
#endif
100+
101+
if (settings.test_validity)
102+
{
103+
std::string message;
104+
bool const valid = bg::is_valid(dissolved, message);
105+
BOOST_CHECK_MESSAGE(valid,
106+
"dissolve: " << caseid
107+
<< " geometry is not valid: " << message);
108+
}
109+
110+
auto const detected_area = bg::area(dissolved);
111+
112+
BOOST_CHECK_MESSAGE(is_equal_within_tolerance(settings, detected_area, expected_area),
113+
"dissolve: " << caseid
114+
<< " #area expected: " << expected_area
115+
<< " detected: " << detected_area
116+
);
117+
118+
#if defined(TEST_WITH_GEOJSON)
119+
writer.feature(geometry);
120+
writer.add_property("type", "input");
121+
122+
for (const auto& polygon : dissolved)
123+
{
124+
writer.feature(polygon);
125+
writer.add_property("type", "dissolved");
126+
}
127+
#endif
128+
129+
}
130+
131+
template <typename Geometry, typename GeometryOut>
132+
void test_one(std::string caseid, std::string const& wkt,
133+
double expected_area, ut_settings const& settings)
134+
{
135+
Geometry geometry;
136+
bg::read_wkt(wkt, geometry);
137+
138+
// If defined as closed, it should be closed. The algorithm itself
139+
// cannot close it without making a copy.
140+
bg::correct_closure(geometry);
141+
142+
test_dissolve<GeometryOut>(caseid, geometry,
143+
expected_area,
144+
settings);
145+
146+
// Verify if reversed version is identical
147+
bg::reverse(geometry);
148+
149+
caseid += "_rev";
150+
test_dissolve<GeometryOut>(caseid, geometry,
151+
expected_area,
152+
settings);
153+
}
154+
155+
#define TEST_DISSOLVE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \
156+
ut_settings settings; \
157+
(test_one<polygon, polygon>) ( #caseid, caseid, area, settings); }
158+
159+
#define TEST_DISSOLVE_WITH(caseid, area, clips_ignored, holes_ignored, points_ignored, settings) { \
160+
(test_one<polygon, polygon>) ( #caseid, caseid, area, settings); }
161+
162+
#define TEST_DISSOLVE_IGNORE(caseid, area, clips_ignored, holes_ignored, points_ignored) { \
163+
ut_settings settings; settings.test_validity = false; \
164+
(test_one<polygon, polygon>) ( #caseid, caseid, area, settings); }
165+
166+
#define TEST_MULTI(caseid, area, clips_ignored, holes_ignored, points_ignored) { \
167+
ut_settings settings; \
168+
(test_one<multi_polygon, polygon>) ( #caseid, caseid, area, settings); }
169+
170+
template <typename P, bool Clockwise>
171+
void test_all()
172+
{
173+
typedef bg::model::polygon<P, Clockwise> polygon;
174+
typedef bg::model::multi_polygon<polygon> multi_polygon;
175+
176+
TEST_DISSOLVE(dissolve_1, 8.0, 1, 0, 4);
177+
TEST_DISSOLVE(dissolve_2, 7.9296875, 1, 1, 12);
178+
TEST_DISSOLVE(dissolve_3, 4.0, 2, 0, 8);
179+
TEST_DISSOLVE(dissolve_4, 8.0, 2, 0, 8);
180+
TEST_DISSOLVE_IGNORE(dissolve_5, 12.0, 2, 0, 8);
181+
TEST_DISSOLVE(dissolve_6, 16.0, 1, 0, 5);
182+
183+
TEST_DISSOLVE(dissolve_7, 50.48056402439, 1, 0, 7);
184+
TEST_DISSOLVE(dissolve_8, 25.6158412, 1, 0, 11);
185+
186+
// CCW polygons should turn CW after dissolve
187+
TEST_DISSOLVE(dissolve_9, 25.6158412, 1, 0, 11);
188+
TEST_DISSOLVE(dissolve_10, 60.0, 1, 0, 7);
189+
TEST_DISSOLVE(dissolve_11, 60.0, 1, 0, 7);
190+
191+
// More pentagrams
192+
TEST_DISSOLVE(dissolve_12, 186556.84077318, 1, 0, 15);
193+
TEST_DISSOLVE(dissolve_13, 361733.91651, 1, 0, 15);
194+
195+
TEST_DISSOLVE(dissolve_14, 4.0, 3, 0, 13);
196+
TEST_DISSOLVE(dissolve_15, 4.0, 3, 0, 13);
197+
TEST_DISSOLVE(dissolve_16, 8.1333, 8, 0, 38);
198+
199+
TEST_DISSOLVE(dissolve_17, 14.5, 2, 0, 11);
200+
TEST_DISSOLVE(dissolve_18, 15.0, 3, 0, 15);
201+
202+
TEST_DISSOLVE(dissolve_d1, 8.0, 1, 0, 4);
203+
TEST_DISSOLVE(dissolve_d2, 16.0, 1, 0, 5);
204+
205+
TEST_DISSOLVE(dissolve_h1_a, 14.0, 1, 1, 9);
206+
TEST_DISSOLVE(dissolve_h1_b, 14.0, 1, 1, 9);
207+
TEST_DISSOLVE(dissolve_h2, 12.25, 2, 0, 13);
208+
TEST_DISSOLVE(dissolve_h3, 10.75, 1, 1, 14);
209+
TEST_DISSOLVE(dissolve_h4, 14.3447, 1, 3, 17);
210+
211+
TEST_DISSOLVE(dissolve_star_a, 7.38821, 2, 0, 15);
212+
TEST_DISSOLVE(dissolve_star_b, 7.28259, 2, 0, 15);
213+
TEST_DISSOLVE(dissolve_star_c, 7.399696, 1, 0, 11);
214+
215+
TEST_DISSOLVE(dissolve_mail_2017_09_24_a, 0.5, 2, 0, 8);
216+
217+
TEST_DISSOLVE(dissolve_mail_2017_09_24_b, 16.0, 1, 0, 5);
218+
TEST_DISSOLVE(dissolve_mail_2017_09_24_c, 0.5, 2, 0, 8);
219+
TEST_DISSOLVE(dissolve_mail_2017_09_24_d, 0.5, 1, 0, 4);
220+
TEST_DISSOLVE(dissolve_mail_2017_09_24_e, 0.001801138128, 5, 0, 69);
221+
TEST_DISSOLVE(dissolve_mail_2017_09_24_f, 0.000361308800, 5, 0, 69);
222+
TEST_DISSOLVE(dissolve_mail_2017_09_24_g, 0.5, 1, 0, 4);
223+
TEST_DISSOLVE(dissolve_mail_2017_09_24_h, 0.5, 1, 0, 4);
224+
225+
TEST_DISSOLVE(dissolve_mail_2017_10_26_a, 7.0, 1, 1, 12);
226+
TEST_DISSOLVE(dissolve_mail_2017_10_26_b, 16.0, 1, 0, 5);
227+
TEST_DISSOLVE(dissolve_mail_2017_10_26_c, 6.0, 1, 0, 6);
228+
229+
TEST_DISSOLVE(dissolve_mail_2017_10_30_a, 0.0001241171, 2, 0, 9);
230+
231+
TEST_DISSOLVE(dissolve_ticket10713, 0.157052766, 2, 0, 8);
232+
233+
TEST_MULTI(multi_three_triangles, 42.614078674948232, 1, 1, 13);
234+
TEST_MULTI(multi_simplex_two, 14.7, 1, 0, 8);
235+
TEST_MULTI(multi_simplex_three, 16.7945, 1, 0, 14);
236+
TEST_MULTI(multi_simplex_four, 20.7581, 1, 0, 18);
237+
TEST_MULTI(multi_disjoint, 24.0, 4, 0, 16);
238+
TEST_MULTI(multi_new_interior, 19.5206, 1, 1, 18);
239+
TEST_MULTI(ggl_list_20110307_javier_01_a, 6400.0, 2, 0, 11);
240+
241+
TEST_DISSOLVE(ggl_list_20110307_javier_01_b, 3993600.0, 1, 2, 16);
242+
TEST_DISSOLVE(dissolve_ticket17, 0.00920834633689, 1, 1, 228);
243+
TEST_DISSOLVE(dissolve_reallife, 91756.916526794434, 1, 0, 25);
244+
245+
TEST_DISSOLVE(gitter_2013_04_a, 2829.7832, 3, 0, 21);
246+
TEST_DISSOLVE(gitter_2013_04_b, 31210.429356259738, 1, 0, 11);
247+
248+
TEST_DISSOLVE(ggl_list_denis, 21123.3281, 2, 0, 22);
249+
250+
TEST_DISSOLVE(dissolve_mail_2018_08_19, 30.711696, 2, 1, 15);
251+
}
252+
253+
254+
int test_main(int, char* [])
255+
{
256+
test_all<bg::model::d2::point_xy<double>, true >();
257+
return 0;
258+
}

0 commit comments

Comments
 (0)