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