]>
Commit | Line | Data |
---|---|---|
1 | // Boost.Geometry (aka GGL, Generic Geometry Library) | |
2 | // Unit Test | |
3 | ||
4 | // Copyright (c) 2014-2021, Oracle and/or its affiliates. | |
5 | ||
6 | // Contributed and/or modified by Vissarion Fysikopoulos, on behalf of Oracle | |
7 | // Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle | |
8 | // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle | |
9 | ||
10 | // Licensed under the Boost Software License version 1.0. | |
11 | // http://www.boost.org/users/license.html | |
12 | ||
13 | #ifndef BOOST_GEOMETRY_TEST_IS_VALID_HPP | |
14 | #define BOOST_GEOMETRY_TEST_IS_VALID_HPP | |
15 | ||
16 | #include <iostream> | |
17 | #include <sstream> | |
18 | #include <string> | |
19 | ||
20 | #include <boost/core/ignore_unused.hpp> | |
21 | #include <boost/range/begin.hpp> | |
22 | #include <boost/range/end.hpp> | |
23 | #include <boost/range/size.hpp> | |
24 | #include <boost/range/value_type.hpp> | |
25 | #include <boost/variant/variant.hpp> | |
26 | ||
27 | #include <boost/geometry/core/closure.hpp> | |
28 | #include <boost/geometry/core/exterior_ring.hpp> | |
29 | #include <boost/geometry/core/interior_rings.hpp> | |
30 | #include <boost/geometry/core/point_order.hpp> | |
31 | #include <boost/geometry/core/ring_type.hpp> | |
32 | #include <boost/geometry/core/tag.hpp> | |
33 | #include <boost/geometry/core/tags.hpp> | |
34 | ||
35 | #include <boost/geometry/geometries/point_xy.hpp> | |
36 | #include <boost/geometry/geometries/segment.hpp> | |
37 | #include <boost/geometry/geometries/box.hpp> | |
38 | #include <boost/geometry/geometries/linestring.hpp> | |
39 | #include <boost/geometry/geometries/ring.hpp> | |
40 | #include <boost/geometry/geometries/polygon.hpp> | |
41 | #include <boost/geometry/geometries/multi_point.hpp> | |
42 | #include <boost/geometry/geometries/multi_linestring.hpp> | |
43 | #include <boost/geometry/geometries/multi_polygon.hpp> | |
44 | ||
45 | #include <boost/geometry/strategies/strategies.hpp> | |
46 | ||
47 | #include <boost/geometry/io/wkt/wkt.hpp> | |
48 | ||
49 | #include <boost/geometry/algorithms/convert.hpp> | |
50 | #include <boost/geometry/algorithms/num_points.hpp> | |
51 | #include <boost/geometry/algorithms/is_valid.hpp> | |
52 | ||
53 | #include <from_wkt.hpp> | |
54 | ||
55 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
56 | #include "pretty_print_geometry.hpp" | |
57 | #endif | |
58 | ||
59 | ||
60 | namespace bg = ::boost::geometry; | |
61 | ||
62 | typedef bg::model::point<double, 2, bg::cs::cartesian> point_type; | |
63 | typedef bg::model::segment<point_type> segment_type; | |
64 | typedef bg::model::box<point_type> box_type; | |
65 | typedef bg::model::linestring<point_type> linestring_type; | |
66 | typedef bg::model::multi_linestring<linestring_type> multi_linestring_type; | |
67 | typedef bg::model::multi_point<point_type> multi_point_type; | |
68 | ||
69 | ||
70 | //---------------------------------------------------------------------------- | |
71 | ||
72 | ||
73 | // returns true if a geometry can be converted to closed | |
74 | template | |
75 | < | |
76 | typename Geometry, | |
77 | typename Tag = typename bg::tag<Geometry>::type, | |
78 | bg::closure_selector Closure = bg::closure<Geometry>::value | |
79 | > | |
80 | struct is_convertible_to_closed | |
81 | { | |
82 | static inline bool apply(Geometry const&) | |
83 | { | |
84 | return false; | |
85 | } | |
86 | }; | |
87 | ||
88 | template <typename Ring> | |
89 | struct is_convertible_to_closed<Ring, bg::ring_tag, bg::open> | |
90 | { | |
91 | static inline bool apply(Ring const& ring) | |
92 | { | |
93 | return boost::size(ring) > 0; | |
94 | } | |
95 | }; | |
96 | ||
97 | template <typename Polygon> | |
98 | struct is_convertible_to_closed<Polygon, bg::polygon_tag, bg::open> | |
99 | { | |
100 | using ring_type = typename bg::ring_type<Polygon>::type; | |
101 | ||
102 | template <typename InteriorRings> | |
103 | static inline | |
104 | bool apply_to_interior_rings(InteriorRings const& interior_rings) | |
105 | { | |
106 | return std::all_of(boost::begin(interior_rings), | |
107 | boost::end(interior_rings), | |
108 | []( auto const& ring ){ | |
109 | return is_convertible_to_closed<ring_type>::apply(ring); | |
110 | }); | |
111 | } | |
112 | ||
113 | static inline bool apply(Polygon const& polygon) | |
114 | { | |
115 | return boost::size(bg::exterior_ring(polygon)) > 0 | |
116 | && apply_to_interior_rings(bg::interior_rings(polygon)); | |
117 | } | |
118 | }; | |
119 | ||
120 | template <typename MultiPolygon> | |
121 | struct is_convertible_to_closed<MultiPolygon, bg::multi_polygon_tag, bg::open> | |
122 | { | |
123 | using polygon_type = typename boost::range_value<MultiPolygon>::type; | |
124 | ||
125 | static inline bool apply(MultiPolygon const& multi_polygon) | |
126 | { | |
127 | return !boost::empty(multi_polygon) && // do not allow empty multi-polygon | |
128 | std::none_of(boost::begin(multi_polygon), boost::end(multi_polygon), | |
129 | []( auto const& polygon ){ | |
130 | return ! is_convertible_to_closed<polygon_type>::apply(polygon); | |
131 | }); | |
132 | } | |
133 | }; | |
134 | ||
135 | ||
136 | //---------------------------------------------------------------------------- | |
137 | ||
138 | ||
139 | // returns true if a geometry can be converted to cw | |
140 | template | |
141 | < | |
142 | typename Geometry, | |
143 | typename Tag = typename bg::tag<Geometry>::type, | |
144 | bg::order_selector Order = bg::point_order<Geometry>::value | |
145 | > | |
146 | struct is_convertible_to_cw | |
147 | { | |
148 | static inline bool apply(Geometry const&) | |
149 | { | |
150 | return bg::point_order<Geometry>::value == bg::counterclockwise; | |
151 | } | |
152 | }; | |
153 | ||
154 | ||
155 | //---------------------------------------------------------------------------- | |
156 | ||
157 | ||
158 | // returns true if geometry can be converted to polygon | |
159 | template | |
160 | < | |
161 | typename Geometry, | |
162 | typename Tag = typename bg::tag<Geometry>::type | |
163 | > | |
164 | struct is_convertible_to_polygon | |
165 | { | |
166 | typedef Geometry type; | |
167 | static bool const value = false; | |
168 | }; | |
169 | ||
170 | template <typename Ring> | |
171 | struct is_convertible_to_polygon<Ring, bg::ring_tag> | |
172 | { | |
173 | typedef bg::model::polygon | |
174 | < | |
175 | typename bg::point_type<Ring>::type, | |
176 | bg::point_order<Ring>::value == bg::clockwise, | |
177 | bg::closure<Ring>::value == bg::closed | |
178 | > type; | |
179 | ||
180 | static bool const value = true; | |
181 | }; | |
182 | ||
183 | ||
184 | //---------------------------------------------------------------------------- | |
185 | ||
186 | ||
187 | // returns true if geometry can be converted to multi-polygon | |
188 | template | |
189 | < | |
190 | typename Geometry, | |
191 | typename Tag = typename bg::tag<Geometry>::type | |
192 | > | |
193 | struct is_convertible_to_multipolygon | |
194 | { | |
195 | typedef Geometry type; | |
196 | static bool const value = false; | |
197 | }; | |
198 | ||
199 | template <typename Ring> | |
200 | struct is_convertible_to_multipolygon<Ring, bg::ring_tag> | |
201 | { | |
202 | typedef bg::model::multi_polygon | |
203 | < | |
204 | typename is_convertible_to_polygon<Ring>::type | |
205 | > type; | |
206 | ||
207 | static bool const value = true; | |
208 | }; | |
209 | ||
210 | template <typename Polygon> | |
211 | struct is_convertible_to_multipolygon<Polygon, bg::polygon_tag> | |
212 | { | |
213 | typedef bg::model::multi_polygon<Polygon> type; | |
214 | static bool const value = true; | |
215 | }; | |
216 | ||
217 | ||
218 | //---------------------------------------------------------------------------- | |
219 | ||
220 | ||
221 | template <typename ValidityTester> | |
222 | struct validity_checker | |
223 | { | |
224 | template <typename Geometry> | |
225 | static inline bool apply(std::string const& case_id, | |
226 | Geometry const& geometry, | |
227 | bool expected_result, | |
228 | std::string& reason) | |
229 | { | |
230 | bool valid = ValidityTester::apply(geometry); | |
231 | std::string const reason_valid | |
232 | = bg::validity_failure_type_message(bg::no_failure); | |
233 | reason = ValidityTester::reason(geometry); | |
234 | std::string reason_short = reason.substr(0, reason_valid.length()); | |
235 | ||
236 | BOOST_CHECK_MESSAGE(valid == expected_result, | |
237 | "case id: " << case_id | |
238 | << ", Expected: " << expected_result | |
239 | << ", detected: " << valid | |
240 | << "; wkt: " << bg::wkt(geometry)); | |
241 | ||
242 | BOOST_CHECK_MESSAGE(reason != "", | |
243 | "case id (empty reason): " << case_id | |
244 | << ", Expected: " << valid | |
245 | << ", detected reason: " << reason | |
246 | << "; wkt: " << bg::wkt(geometry)); | |
247 | ||
248 | BOOST_CHECK_MESSAGE((valid && reason == reason_valid) | |
249 | || | |
250 | (! valid && reason != reason_valid) | |
251 | || | |
252 | (! valid && reason_short != reason_valid), | |
253 | "case id (reason): " << case_id | |
254 | << ", Expected: " << valid | |
255 | << ", detected reason: " << reason | |
256 | << "; wkt: " << bg::wkt(geometry)); | |
257 | ||
258 | return valid; | |
259 | } | |
260 | }; | |
261 | ||
262 | ||
263 | //---------------------------------------------------------------------------- | |
264 | ||
265 | ||
266 | struct default_validity_tester | |
267 | { | |
268 | template <typename Geometry> | |
269 | static inline bool apply(Geometry const& geometry) | |
270 | { | |
271 | return bg::is_valid(geometry); | |
272 | } | |
273 | ||
274 | template <typename Geometry> | |
275 | static inline std::string reason(Geometry const& geometry) | |
276 | { | |
277 | std::string message; | |
278 | bg::is_valid(geometry, message); | |
279 | return message; | |
280 | } | |
281 | }; | |
282 | ||
283 | ||
284 | template <bool AllowSpikes> | |
285 | struct validity_tester_linear | |
286 | { | |
287 | template <typename Geometry> | |
288 | static inline bool apply(Geometry const& geometry) | |
289 | { | |
290 | bool const irrelevant = true; | |
291 | bg::is_valid_default_policy<irrelevant, AllowSpikes> visitor; | |
292 | return bg::is_valid(geometry, visitor, bg::default_strategy()); | |
293 | } | |
294 | ||
295 | template <typename Geometry> | |
296 | static inline std::string reason(Geometry const& geometry) | |
297 | { | |
298 | bool const irrelevant = true; | |
299 | std::ostringstream oss; | |
300 | bg::failing_reason_policy<irrelevant, AllowSpikes> visitor(oss); | |
301 | bg::is_valid(geometry, visitor, bg::default_strategy()); | |
302 | return oss.str(); | |
303 | } | |
304 | }; | |
305 | ||
306 | ||
307 | template <bool AllowDuplicates> | |
308 | struct validity_tester_areal | |
309 | { | |
310 | template <typename Geometry> | |
311 | static inline bool apply(Geometry const& geometry) | |
312 | { | |
313 | bg::is_valid_default_policy<AllowDuplicates> visitor; | |
314 | return bg::is_valid(geometry, visitor, bg::default_strategy()); | |
315 | } | |
316 | ||
317 | template <typename Geometry> | |
318 | static inline std::string reason(Geometry const& geometry) | |
319 | { | |
320 | std::ostringstream oss; | |
321 | bg::failing_reason_policy<AllowDuplicates> visitor(oss); | |
322 | bg::is_valid(geometry, visitor, bg::default_strategy()); | |
323 | return oss.str(); | |
324 | } | |
325 | ||
326 | }; | |
327 | ||
328 | ||
329 | template <bool AllowDuplicates> | |
330 | struct validity_tester_geo_areal | |
331 | { | |
332 | template <typename Geometry> | |
333 | static inline bool apply(Geometry const& geometry) | |
334 | { | |
335 | bg::is_valid_default_policy<AllowDuplicates> visitor; | |
336 | bg::strategy::intersection::geographic_segments<> s; | |
337 | return bg::is_valid(geometry, visitor, s); | |
338 | } | |
339 | ||
340 | template <typename Geometry> | |
341 | static inline std::string reason(Geometry const& geometry) | |
342 | { | |
343 | std::ostringstream oss; | |
344 | bg::failing_reason_policy<AllowDuplicates> visitor(oss); | |
345 | bg::strategy::intersection::geographic_segments<> s; | |
346 | bg::is_valid(geometry, visitor, s); | |
347 | return oss.str(); | |
348 | } | |
349 | ||
350 | }; | |
351 | ||
352 | ||
353 | //---------------------------------------------------------------------------- | |
354 | ||
355 | ||
356 | template | |
357 | < | |
358 | typename ValidityTester, | |
359 | typename Geometry, | |
360 | typename ClosedGeometry = Geometry, | |
361 | typename CWGeometry = Geometry, | |
362 | typename CWClosedGeometry = Geometry, | |
363 | typename Tag = typename bg::tag<Geometry>::type | |
364 | > | |
365 | class test_valid | |
366 | { | |
367 | protected: | |
368 | template <typename G> | |
369 | static inline void base_test(std::string const& case_id, | |
370 | G const& g, | |
371 | bool expected_result) | |
372 | { | |
373 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
374 | std::cout << "=======" << std::endl; | |
375 | #endif | |
376 | ||
377 | std::string reason; | |
378 | bool valid = validity_checker | |
379 | < | |
380 | ValidityTester | |
381 | >::apply(case_id, g, expected_result, reason); | |
382 | boost::ignore_unused(valid); | |
383 | ||
384 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
385 | std::cout << "case id: " << case_id << ", Geometry: "; | |
386 | pretty_print_geometry<G>::apply(std::cout, g); | |
387 | std::cout << std::endl; | |
388 | std::cout << "wkt: " << bg::wkt(g) << std::endl; | |
389 | std::cout << std::boolalpha; | |
390 | std::cout << "is valid? " << valid << std::endl; | |
391 | std::cout << "expected result: " << expected_result << std::endl; | |
392 | std::cout << "reason: " << reason << std::endl; | |
393 | std::cout << "=======" << std::endl; | |
394 | std::cout << std::noboolalpha; | |
395 | #endif | |
396 | } | |
397 | ||
398 | public: | |
399 | static inline void apply(std::string const& case_id, | |
400 | Geometry const& geometry, | |
401 | bool expected_result) | |
402 | { | |
403 | std::stringstream sstr; | |
404 | sstr << case_id << "-original"; // which is: CCW open | |
405 | base_test(sstr.str(), geometry, expected_result); | |
406 | ||
407 | if ( is_convertible_to_closed<Geometry>::apply(geometry) ) | |
408 | { | |
409 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
410 | std::cout << "...checking closed geometry..." | |
411 | << std::endl; | |
412 | #endif | |
413 | ClosedGeometry closed_geometry; | |
414 | bg::convert(geometry, closed_geometry); | |
415 | sstr.str(""); | |
416 | sstr << case_id << "-2closed"; | |
417 | base_test(sstr.str(), closed_geometry, expected_result); | |
418 | } | |
419 | if ( is_convertible_to_cw<Geometry>::apply(geometry) ) | |
420 | { | |
421 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
422 | std::cout << "...checking cw open geometry..." | |
423 | << std::endl; | |
424 | #endif | |
425 | CWGeometry cw_geometry; | |
426 | bg::convert(geometry, cw_geometry); | |
427 | sstr.str(""); | |
428 | sstr << case_id << "-2CW"; | |
429 | base_test(sstr.str(), cw_geometry, expected_result); | |
430 | if ( is_convertible_to_closed<CWGeometry>::apply(cw_geometry) ) | |
431 | { | |
432 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
433 | std::cout << "...checking cw closed geometry..." | |
434 | << std::endl; | |
435 | #endif | |
436 | CWClosedGeometry cw_closed_geometry; | |
437 | bg::convert(cw_geometry, cw_closed_geometry); | |
438 | sstr.str(""); | |
439 | sstr << case_id << "-2CWclosed"; | |
440 | base_test(sstr.str(), cw_closed_geometry, expected_result); | |
441 | } | |
442 | } | |
443 | ||
444 | if ( BOOST_GEOMETRY_CONDITION(is_convertible_to_polygon<Geometry>::value) ) | |
445 | { | |
446 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
447 | std::cout << "...checking geometry converted to polygon..." | |
448 | << std::endl; | |
449 | #endif | |
450 | typename is_convertible_to_polygon<Geometry>::type polygon; | |
451 | bg::convert(geometry, polygon); | |
452 | sstr.str(""); | |
453 | sstr << case_id << "-2Polygon"; | |
454 | base_test(sstr.str(), polygon, expected_result); | |
455 | } | |
456 | ||
457 | if ( BOOST_GEOMETRY_CONDITION(is_convertible_to_multipolygon<Geometry>::value) ) | |
458 | { | |
459 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
460 | std::cout << "...checking geometry converted to multi-polygon..." | |
461 | << std::endl; | |
462 | #endif | |
463 | typename is_convertible_to_multipolygon | |
464 | < | |
465 | Geometry | |
466 | >::type multipolygon; | |
467 | ||
468 | bg::convert(geometry, multipolygon); | |
469 | sstr.str(""); | |
470 | sstr << case_id << "-2MultiPolygon"; | |
471 | base_test(sstr.str(), multipolygon, expected_result); | |
472 | } | |
473 | ||
474 | #ifdef BOOST_GEOMETRY_TEST_DEBUG | |
475 | std::cout << std::endl << std::endl << std::endl; | |
476 | #endif | |
477 | } | |
478 | ||
479 | static inline void apply(std::string const& case_id, | |
480 | std::string const& wkt, | |
481 | bool expected_result) | |
482 | { | |
483 | apply(case_id, from_wkt<Geometry>(wkt), expected_result); | |
484 | } | |
485 | }; | |
486 | ||
487 | ||
488 | //---------------------------------------------------------------------------- | |
489 | ||
490 | ||
491 | template <typename VariantGeometry> | |
492 | class test_valid_variant | |
493 | : test_valid<default_validity_tester, VariantGeometry> | |
494 | { | |
495 | private: | |
496 | typedef test_valid<default_validity_tester, VariantGeometry> base_type; | |
497 | ||
498 | public: | |
499 | static inline void apply(std::string const& case_id, | |
500 | VariantGeometry const& vg, | |
501 | bool expected_result) | |
502 | { | |
503 | std::ostringstream oss; | |
504 | base_type::base_test(case_id, vg, expected_result); | |
505 | } | |
506 | }; | |
507 | ||
508 | ||
509 | #endif // BOOST_GEOMETRY_TEST_IS_VALID_HPP |