test_buffer.hpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. // Boost.Geometry (aka GGL, Generic Geometry Library)
  2. // Unit Test Helper
  3. // Copyright (c) 2010-2019 Barend Gehrels, Amsterdam, the Netherlands.
  4. // This file was modified by Oracle on 2016-2017.
  5. // Modifications copyright (c) 2016-2017, Oracle and/or its affiliates.
  6. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
  7. // Use, modification and distribution is subject to the Boost Software License,
  8. // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
  9. // http://www.boost.org/LICENSE_1_0.txt)
  10. #ifndef BOOST_GEOMETRY_TEST_BUFFER_HPP
  11. #define BOOST_GEOMETRY_TEST_BUFFER_HPP
  12. #if defined(TEST_WITH_SVG)
  13. // Define before including any buffer headerfile
  14. #define BOOST_GEOMETRY_BUFFER_USE_HELPER_POINTS
  15. #endif
  16. #include <iostream>
  17. #include <fstream>
  18. #include <iomanip>
  19. #include <boost/foreach.hpp>
  20. #include "geometry_test_common.hpp"
  21. #include <boost/geometry/algorithms/envelope.hpp>
  22. #include <boost/geometry/algorithms/area.hpp>
  23. #include <boost/geometry/algorithms/buffer.hpp>
  24. #include <boost/geometry/algorithms/correct.hpp>
  25. #include <boost/geometry/algorithms/disjoint.hpp>
  26. #include <boost/geometry/algorithms/intersects.hpp>
  27. #include <boost/geometry/algorithms/is_empty.hpp>
  28. #include <boost/geometry/algorithms/is_valid.hpp>
  29. #include <boost/geometry/algorithms/num_interior_rings.hpp>
  30. #include <boost/geometry/algorithms/union.hpp>
  31. #include <boost/geometry/algorithms/detail/overlay/debug_turn_info.hpp>
  32. #include <boost/geometry/algorithms/detail/overlay/self_turn_points.hpp>
  33. #include <boost/geometry/geometries/geometries.hpp>
  34. #include <boost/geometry/strategies/strategies.hpp>
  35. #include <boost/geometry/strategies/buffer.hpp>
  36. #include <boost/geometry/io/wkt/wkt.hpp>
  37. #include <boost/geometry/util/condition.hpp>
  38. const double same_distance = -999;
  39. #if defined(TEST_WITH_SVG)
  40. # include "test_buffer_svg.hpp"
  41. # include "test_buffer_svg_per_turn.hpp"
  42. #endif
  43. //-----------------------------------------------------------------------------
  44. template <typename JoinStrategy>
  45. struct JoinTestProperties
  46. {
  47. static std::string name() { return "joinunknown"; }
  48. };
  49. template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_round>
  50. {
  51. static std::string name() { return "round"; }
  52. };
  53. template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_miter>
  54. {
  55. static std::string name() { return "miter"; }
  56. };
  57. template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_round_by_divide>
  58. {
  59. static std::string name() { return "divide"; }
  60. };
  61. //-----------------------------------------------------------------------------
  62. template <typename EndStrategy>
  63. struct EndTestProperties { };
  64. template<> struct EndTestProperties<boost::geometry::strategy::buffer::end_round>
  65. {
  66. static std::string name() { return "round"; }
  67. };
  68. template<> struct EndTestProperties<boost::geometry::strategy::buffer::end_flat>
  69. {
  70. static std::string name() { return "flat"; }
  71. };
  72. struct ut_settings
  73. {
  74. double tolerance;
  75. bool test_validity;
  76. bool test_area;
  77. bool use_ln_area;
  78. int points_per_circle;
  79. explicit ut_settings(double tol = 0.01, bool val = true, int points = 88)
  80. : tolerance(tol)
  81. , test_validity(val)
  82. , test_area(true)
  83. , use_ln_area(false)
  84. , points_per_circle(points)
  85. {}
  86. static inline ut_settings ignore_validity()
  87. {
  88. ut_settings result;
  89. result.test_validity = false;
  90. return result;
  91. }
  92. static inline ut_settings assertions_only()
  93. {
  94. ut_settings result;
  95. result.test_validity = false;
  96. result.test_area = false;
  97. return result;
  98. }
  99. static inline double ignore_area() { return 9999.9; }
  100. };
  101. template
  102. <
  103. typename GeometryOut,
  104. typename JoinStrategy,
  105. typename EndStrategy,
  106. typename DistanceStrategy,
  107. typename SideStrategy,
  108. typename PointStrategy,
  109. typename AreaStrategy,
  110. typename Geometry
  111. >
  112. void test_buffer(std::string const& caseid,
  113. bg::model::multi_polygon<GeometryOut>& buffered,
  114. Geometry const& geometry,
  115. JoinStrategy const& join_strategy,
  116. EndStrategy const& end_strategy,
  117. DistanceStrategy const& distance_strategy,
  118. SideStrategy const& side_strategy,
  119. PointStrategy const& point_strategy,
  120. AreaStrategy const& area_strategy,
  121. int expected_count,
  122. int expected_holes_count,
  123. double expected_area,
  124. ut_settings const& settings)
  125. {
  126. namespace bg = boost::geometry;
  127. typedef typename bg::coordinate_type<Geometry>::type coordinate_type;
  128. typedef typename bg::point_type<Geometry>::type point_type;
  129. typedef typename bg::tag<Geometry>::type tag;
  130. // TODO use something different here:
  131. std::string type = boost::is_same<tag, bg::polygon_tag>::value ? "poly"
  132. : boost::is_same<tag, bg::linestring_tag>::value ? "line"
  133. : boost::is_same<tag, bg::point_tag>::value ? "point"
  134. : boost::is_same<tag, bg::multi_polygon_tag>::value ? "multipoly"
  135. : boost::is_same<tag, bg::multi_linestring_tag>::value ? "multiline"
  136. : boost::is_same<tag, bg::multi_point_tag>::value ? "multipoint"
  137. : ""
  138. ;
  139. bg::model::box<point_type> envelope;
  140. if (bg::is_empty(geometry))
  141. {
  142. bg::assign_values(envelope, 0, 0, 1, 1);
  143. }
  144. else
  145. {
  146. bg::envelope(geometry, envelope);
  147. }
  148. std::string join_name = JoinTestProperties<JoinStrategy>::name();
  149. std::string end_name = EndTestProperties<EndStrategy>::name();
  150. if ( BOOST_GEOMETRY_CONDITION((
  151. boost::is_same<tag, bg::point_tag>::value
  152. || boost::is_same<tag, bg::multi_point_tag>::value )) )
  153. {
  154. join_name.clear();
  155. }
  156. std::ostringstream complete;
  157. complete
  158. << type << "_"
  159. << caseid << "_"
  160. << string_from_type<coordinate_type>::name()
  161. << "_" << join_name
  162. << (end_name.empty() ? "" : "_") << end_name
  163. << (distance_strategy.negative() ? "_deflate" : "")
  164. << (bg::point_order<GeometryOut>::value == bg::counterclockwise ? "_ccw" : "")
  165. #if defined(BOOST_GEOMETRY_USE_RESCALING)
  166. << "_rescaled"
  167. #endif
  168. // << "_" << point_buffer_count
  169. ;
  170. //std::cout << complete.str() << std::endl;
  171. #if defined(TEST_WITH_SVG_PER_TURN)
  172. save_turns_visitor<point_type> visitor;
  173. #elif defined(TEST_WITH_SVG)
  174. buffer_svg_mapper<point_type> buffer_mapper(complete.str());
  175. std::ostringstream filename;
  176. filename << "buffer_" << complete.str() << ".svg";
  177. std::ofstream svg(filename.str().c_str());
  178. typedef bg::svg_mapper<point_type> mapper_type;
  179. mapper_type mapper(svg, 1000, 800);
  180. svg_visitor<mapper_type, bg::model::box<point_type> > visitor(mapper);
  181. buffer_mapper.prepare(mapper, visitor, envelope,
  182. distance_strategy.negative()
  183. ? 1.0
  184. : 1.1 * distance_strategy.max_distance(join_strategy, end_strategy)
  185. );
  186. #else
  187. bg::detail::buffer::visit_pieces_default_policy visitor;
  188. #endif
  189. typedef typename bg::point_type<Geometry>::type point_type;
  190. typedef typename bg::rescale_policy_type<point_type>::type
  191. rescale_policy_type;
  192. typedef typename bg::strategy::intersection::services::default_strategy
  193. <
  194. typename bg::cs_tag<Geometry>::type
  195. >::type strategy_type;
  196. typedef typename strategy_type::envelope_strategy_type envelope_strategy_type;
  197. // Enlarge the box to get a proper rescale policy
  198. bg::buffer(envelope, envelope, distance_strategy.max_distance(join_strategy, end_strategy));
  199. strategy_type strategy;
  200. rescale_policy_type rescale_policy
  201. = bg::get_rescale_policy<rescale_policy_type>(envelope);
  202. envelope_strategy_type envelope_strategy;
  203. buffered.clear();
  204. bg::detail::buffer::buffer_inserter<GeometryOut>(geometry,
  205. std::back_inserter(buffered),
  206. distance_strategy,
  207. side_strategy,
  208. join_strategy,
  209. end_strategy,
  210. point_strategy,
  211. strategy,
  212. rescale_policy,
  213. visitor);
  214. #if defined(TEST_WITH_SVG)
  215. buffer_mapper.map_input_output(mapper, geometry, buffered, distance_strategy.negative());
  216. #endif
  217. //Uncomment to create simple CSV to compare/use in tests - adapt precision if necessary
  218. //std::cout << complete.str() << "," << std::fixed << std::setprecision(0) << area << std::endl;
  219. //return;
  220. if (bg::is_empty(buffered) && bg::math::equals(expected_area, 0.0))
  221. {
  222. // As expected - don't get rescale policy for output (will be invalid)
  223. return;
  224. }
  225. if (settings.test_area)
  226. {
  227. BOOST_CHECK_MESSAGE
  228. (
  229. ! bg::is_empty(buffered),
  230. complete.str() << " output is empty (unexpected)."
  231. );
  232. }
  233. bg::model::box<point_type> envelope_output;
  234. bg::assign_values(envelope_output, 0, 0, 1, 1);
  235. bg::envelope(buffered, envelope_output, envelope_strategy);
  236. // std::cout << caseid << std::endl;
  237. // std::cout << "INPUT: " << bg::wkt(geometry) << std::endl;
  238. // std::cout << "OUTPUT: " << area << std::endl;
  239. // std::cout << "OUTPUT env: " << bg::wkt(envelope_output) << std::endl;
  240. // std::cout << bg::wkt(buffered) << std::endl;
  241. if (expected_count >= 0)
  242. {
  243. BOOST_CHECK_MESSAGE
  244. (
  245. int(buffered.size()) == expected_count,
  246. "#outputs not as expected."
  247. << " Expected: " << expected_count
  248. << " Detected: " << buffered.size()
  249. );
  250. }
  251. if (expected_holes_count >= 0)
  252. {
  253. std::size_t nholes = bg::num_interior_rings(buffered);
  254. BOOST_CHECK_MESSAGE
  255. (
  256. int(nholes) == expected_holes_count,
  257. complete.str() << " #holes not as expected."
  258. << " Expected: " << expected_holes_count
  259. << " Detected: " << nholes
  260. );
  261. }
  262. if (settings.test_area)
  263. {
  264. // Because areas vary hugely in buffer, the Boost.Test methods are not convenient.
  265. // Use just the abs - but if areas are really small that is not convenient neither.
  266. // Therefore there is a logarithmic option too.
  267. typename bg::default_area_result<GeometryOut>::type area = bg::area(buffered, area_strategy);
  268. double const difference = settings.use_ln_area
  269. ? std::log(area) - std::log(expected_area)
  270. : area - expected_area;
  271. BOOST_CHECK_MESSAGE
  272. (
  273. bg::math::abs(difference) < settings.tolerance,
  274. complete.str() << " not as expected. "
  275. << std::setprecision(18)
  276. << " Expected: " << expected_area
  277. << " Detected: " << area
  278. << " Diff: " << difference
  279. << " Tol: " << settings.tolerance
  280. << std::setprecision(3)
  281. << " , " << 100.0 * (difference / expected_area) << "%"
  282. );
  283. // if (settings.use_ln_area)
  284. // {
  285. // std::cout << complete.str()
  286. // << std::setprecision(6)
  287. // << " ln(detected)=" << std::log(area)
  288. // << " ln(expected)=" << std::log(expected_area)
  289. // << " diff=" << difference
  290. // << " detected=" << area
  291. // << std::endl;
  292. // }
  293. }
  294. #if ! defined(BOOST_GEOMETRY_TEST_ALWAYS_CHECK_VALIDITY)
  295. if (settings.test_validity)
  296. #endif
  297. {
  298. if (! bg::is_valid(buffered))
  299. {
  300. BOOST_CHECK_MESSAGE(bg::is_valid(buffered), complete.str() << " is not valid");
  301. }
  302. }
  303. #if defined(TEST_WITH_SVG_PER_TURN)
  304. {
  305. // Create a per turn visitor to map per turn, and buffer again with it
  306. per_turn_visitor<point_type> ptv(complete.str(), visitor.get_points());
  307. bg::detail::buffer::buffer_inserter<GeometryOut>(geometry,
  308. std::back_inserter(buffered),
  309. distance_strategy,
  310. side_strategy,
  311. join_strategy,
  312. end_strategy,
  313. point_strategy,
  314. rescale_policy,
  315. ptv);
  316. ptv.map_input_output(geometry, buffered, distance_strategy.negative());
  317. // self_ips NYI here
  318. }
  319. #elif defined(TEST_WITH_SVG)
  320. rescale_policy_type rescale_policy_output
  321. = bg::get_rescale_policy<rescale_policy_type>(envelope_output);
  322. buffer_mapper.map_self_ips(mapper, buffered, strategy, rescale_policy_output);
  323. #endif
  324. }
  325. template
  326. <
  327. typename GeometryOut,
  328. typename JoinStrategy,
  329. typename EndStrategy,
  330. typename DistanceStrategy,
  331. typename SideStrategy,
  332. typename PointStrategy,
  333. typename Geometry
  334. >
  335. void test_buffer(std::string const& caseid, bg::model::multi_polygon<GeometryOut>& buffered, Geometry const& geometry,
  336. JoinStrategy const& join_strategy,
  337. EndStrategy const& end_strategy,
  338. DistanceStrategy const& distance_strategy,
  339. SideStrategy const& side_strategy,
  340. PointStrategy const& point_strategy,
  341. double expected_area,
  342. ut_settings const& settings = ut_settings())
  343. {
  344. typename bg::strategy::area::services::default_strategy
  345. <
  346. typename bg::cs_tag<Geometry>::type
  347. >::type area_strategy;
  348. test_buffer<GeometryOut>(caseid, buffered, geometry,
  349. join_strategy, end_strategy, distance_strategy, side_strategy, point_strategy,
  350. area_strategy,
  351. -1, -1, expected_area, settings);
  352. }
  353. #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS
  354. static int counter = 0;
  355. #endif
  356. template
  357. <
  358. typename Geometry,
  359. typename GeometryOut,
  360. typename JoinStrategy,
  361. typename EndStrategy
  362. >
  363. void test_one(std::string const& caseid, std::string const& wkt,
  364. JoinStrategy const& join_strategy, EndStrategy const& end_strategy,
  365. int expected_count, int expected_holes_count, double expected_area,
  366. double distance_left, ut_settings const& settings = ut_settings(),
  367. double distance_right = same_distance)
  368. {
  369. namespace bg = boost::geometry;
  370. Geometry g;
  371. bg::read_wkt(wkt, g);
  372. bg::correct(g);
  373. #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS
  374. std::cout
  375. << (counter > 0 ? "union " : "")
  376. << "select " << counter++
  377. << ", '" << caseid << "' as caseid"
  378. << ", ST_Area(ST_Buffer(ST_GeomFromText('" << wkt << "'), "
  379. << distance_left
  380. << ", 'endcap=" << end_name << " join=" << join_name << "'))"
  381. << ", " << expected_area
  382. << std::endl;
  383. #endif
  384. bg::strategy::buffer::side_straight side_strategy;
  385. bg::strategy::buffer::point_circle circle_strategy(settings.points_per_circle);
  386. bg::strategy::buffer::distance_asymmetric
  387. <
  388. typename bg::coordinate_type<Geometry>::type
  389. > distance_strategy(distance_left,
  390. bg::math::equals(distance_right, same_distance)
  391. ? distance_left : distance_right);
  392. typename bg::strategy::area::services::default_strategy
  393. <
  394. typename bg::cs_tag<Geometry>::type
  395. >::type area_strategy;
  396. bg::model::multi_polygon<GeometryOut> buffered;
  397. test_buffer<GeometryOut>
  398. (caseid, buffered, g,
  399. join_strategy, end_strategy,
  400. distance_strategy, side_strategy, circle_strategy,
  401. area_strategy,
  402. expected_count, expected_holes_count, expected_area,
  403. settings);
  404. #if !defined(BOOST_GEOMETRY_COMPILER_MODE_DEBUG) && defined(BOOST_GEOMETRY_COMPILER_MODE_RELEASE)
  405. // Also test symmetric distance strategy if right-distance is not specified
  406. // (only in release mode)
  407. if (bg::math::equals(distance_right, same_distance))
  408. {
  409. bg::strategy::buffer::distance_symmetric
  410. <
  411. typename bg::coordinate_type<Geometry>::type
  412. > sym_distance_strategy(distance_left);
  413. test_buffer<GeometryOut>
  414. (caseid + "_sym", buffered, g,
  415. join_strategy, end_strategy,
  416. sym_distance_strategy, side_strategy, circle_strategy,
  417. area_strategy,
  418. expected_count, expected_holes_count, expected_area,
  419. settings);
  420. }
  421. #endif
  422. }
  423. template
  424. <
  425. typename Geometry,
  426. typename GeometryOut,
  427. typename JoinStrategy,
  428. typename EndStrategy
  429. >
  430. void test_one(std::string const& caseid, std::string const& wkt,
  431. JoinStrategy const& join_strategy, EndStrategy const& end_strategy,
  432. double expected_area,
  433. double distance_left, ut_settings const& settings = ut_settings(),
  434. double distance_right = same_distance)
  435. {
  436. test_one<Geometry, GeometryOut>(caseid, wkt, join_strategy, end_strategy,
  437. -1 ,-1, expected_area,
  438. distance_left, settings, distance_right);
  439. }
  440. template
  441. <
  442. typename Geometry,
  443. typename GeometryOut,
  444. typename JoinStrategy,
  445. typename EndStrategy,
  446. typename DistanceStrategy,
  447. typename SideStrategy,
  448. typename PointStrategy
  449. >
  450. void test_with_custom_strategies(std::string const& caseid,
  451. std::string const& wkt,
  452. JoinStrategy const& join_strategy,
  453. EndStrategy const& end_strategy,
  454. DistanceStrategy const& distance_strategy,
  455. SideStrategy const& side_strategy,
  456. PointStrategy const& point_strategy,
  457. double expected_area,
  458. ut_settings const& settings = ut_settings())
  459. {
  460. namespace bg = boost::geometry;
  461. Geometry g;
  462. bg::read_wkt(wkt, g);
  463. bg::correct(g);
  464. bg::model::multi_polygon<GeometryOut> buffered;
  465. test_buffer<GeometryOut>
  466. (caseid, buffered, g,
  467. join_strategy, end_strategy,
  468. distance_strategy, side_strategy, point_strategy,
  469. expected_area, settings);
  470. }
  471. #endif