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