1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 // Unit Test
3 
4 // Copyright (c) 2014-2017, Oracle and/or its affiliates.
5 
6 // Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle
7 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
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 {
applyis_convertible_to_closed80     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 {
applyis_convertible_to_closed89     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
apply_to_interior_ringsis_convertible_to_closed102     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 
applyis_convertible_to_closed111     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 
applyis_convertible_to_closed123     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 {
applyis_convertible_to_cw147     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>
applyvalidity_checker224     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>
applydefault_validity_tester268     static inline bool apply(Geometry const& geometry)
269     {
270         return bg::is_valid(geometry);
271     }
272 
273     template <typename Geometry>
reasondefault_validity_tester274     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>
applyvalidity_tester_linear287     static inline bool apply(Geometry const& geometry)
288     {
289         bool const irrelevant = true;
290         bg::is_valid_default_policy<irrelevant, AllowSpikes> visitor;
291         return bg::is_valid(geometry, visitor, bg::default_strategy());
292     }
293 
294     template <typename Geometry>
reasonvalidity_tester_linear295     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);
300         bg::is_valid(geometry, visitor, bg::default_strategy());
301         return oss.str();
302     }
303 };
304 
305 
306 template <bool AllowDuplicates>
307 struct validity_tester_areal
308 {
309     template <typename Geometry>
applyvalidity_tester_areal310     static inline bool apply(Geometry const& geometry)
311     {
312         bg::is_valid_default_policy<AllowDuplicates> visitor;
313         return bg::is_valid(geometry, visitor, bg::default_strategy());
314     }
315 
316     template <typename Geometry>
reasonvalidity_tester_areal317     static inline std::string reason(Geometry const& geometry)
318     {
319         std::ostringstream oss;
320         bg::failing_reason_policy<AllowDuplicates> visitor(oss);
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>
applyvalidity_tester_geo_areal332     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>
reasonvalidity_tester_geo_areal340     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);
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>
base_test(std::string const & case_id,G const & g,bool expected_result)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:
apply(std::string const & case_id,Geometry const & geometry,bool expected_result)398     static inline void apply(std::string const& case_id,
399                              Geometry const& geometry,
400                              bool expected_result)
401     {
402         std::stringstream sstr;
403         sstr << case_id << "-original"; // which is: CCW open
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 
apply(std::string const & case_id,std::string const & wkt,bool expected_result)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:
apply(std::string const & case_id,VariantGeometry const & vg,bool expected_result)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
509