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