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