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