]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // Boost.Geometry (aka GGL, Generic Geometry Library) |
2 | // Unit Test Helper | |
3 | ||
4 | // Copyright (c) 2010-2015 Barend Gehrels, Amsterdam, the Netherlands. | |
5 | ||
6 | // This file was modified by Oracle on 2016. | |
7 | // Modifications copyright (c) 2016, 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 | { | |
65 | static std::string name() { return "joinunknown"; } | |
66 | }; | |
67 | ||
68 | template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_round> | |
69 | { | |
70 | static std::string name() { return "round"; } | |
71 | }; | |
72 | ||
73 | template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_miter> | |
74 | { | |
75 | static std::string name() { return "miter"; } | |
76 | }; | |
77 | ||
78 | template<> struct JoinTestProperties<boost::geometry::strategy::buffer::join_round_by_divide> | |
79 | { | |
80 | 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 | { | |
90 | static std::string name() { return "round"; } | |
91 | }; | |
92 | ||
93 | template<> struct EndTestProperties<boost::geometry::strategy::buffer::end_flat> | |
94 | { | |
95 | static std::string name() { return "flat"; } | |
96 | }; | |
97 | ||
98 | ||
99 | ||
100 | template <typename Geometry, typename RescalePolicy> | |
101 | std::size_t count_self_ips(Geometry const& geometry, RescalePolicy const& rescale_policy) | |
102 | { | |
103 | typedef typename bg::point_type<Geometry>::type point_type; | |
104 | typedef bg::detail::overlay::turn_info | |
105 | < | |
106 | point_type, | |
107 | typename bg::segment_ratio_type<point_type, RescalePolicy>::type | |
108 | > turn_info; | |
109 | ||
110 | std::vector<turn_info> turns; | |
111 | ||
112 | bg::detail::self_get_turn_points::no_interrupt_policy policy; | |
113 | bg::self_turns | |
114 | < | |
115 | bg::detail::overlay::assign_null_policy | |
116 | >(geometry, rescale_policy, turns, policy); | |
117 | ||
118 | return turns.size(); | |
119 | } | |
120 | ||
121 | template | |
122 | < | |
123 | typename GeometryOut, | |
124 | typename JoinStrategy, | |
125 | typename EndStrategy, | |
126 | typename DistanceStrategy, | |
127 | typename SideStrategy, | |
128 | typename PointStrategy, | |
129 | typename Geometry | |
130 | > | |
131 | void test_buffer(std::string const& caseid, Geometry const& geometry, | |
132 | JoinStrategy const& join_strategy, | |
133 | EndStrategy const& end_strategy, | |
134 | DistanceStrategy const& distance_strategy, | |
135 | SideStrategy const& side_strategy, | |
136 | PointStrategy const& point_strategy, | |
137 | bool check_self_intersections, | |
138 | int expected_count, | |
139 | int expected_holes_count, | |
140 | double expected_area, | |
141 | double tolerance, | |
142 | std::size_t* self_ip_count) | |
143 | { | |
144 | namespace bg = boost::geometry; | |
145 | ||
146 | typedef typename bg::coordinate_type<Geometry>::type coordinate_type; | |
147 | typedef typename bg::point_type<Geometry>::type point_type; | |
148 | ||
149 | typedef typename bg::tag<Geometry>::type tag; | |
150 | // TODO use something different here: | |
151 | std::string type = boost::is_same<tag, bg::polygon_tag>::value ? "poly" | |
152 | : boost::is_same<tag, bg::linestring_tag>::value ? "line" | |
153 | : boost::is_same<tag, bg::point_tag>::value ? "point" | |
154 | : boost::is_same<tag, bg::multi_polygon_tag>::value ? "multipoly" | |
155 | : boost::is_same<tag, bg::multi_linestring_tag>::value ? "multiline" | |
156 | : boost::is_same<tag, bg::multi_point_tag>::value ? "multipoint" | |
157 | : "" | |
158 | ; | |
159 | ||
160 | bg::model::box<point_type> envelope; | |
161 | if (bg::is_empty(geometry)) | |
162 | { | |
163 | bg::assign_values(envelope, 0, 0, 1, 1); | |
164 | } | |
165 | else | |
166 | { | |
167 | bg::envelope(geometry, envelope); | |
168 | } | |
169 | ||
170 | std::string join_name = JoinTestProperties<JoinStrategy>::name(); | |
171 | std::string end_name = EndTestProperties<EndStrategy>::name(); | |
172 | ||
173 | if ( BOOST_GEOMETRY_CONDITION(( | |
174 | boost::is_same<tag, bg::point_tag>::value | |
175 | || boost::is_same<tag, bg::multi_point_tag>::value )) ) | |
176 | { | |
177 | join_name.clear(); | |
178 | } | |
179 | ||
180 | std::ostringstream complete; | |
181 | complete | |
182 | << type << "_" | |
183 | << caseid << "_" | |
184 | << string_from_type<coordinate_type>::name() | |
185 | << "_" << join_name | |
186 | << (end_name.empty() ? "" : "_") << end_name | |
187 | << (distance_strategy.negative() ? "_deflate" : "") | |
188 | << (bg::point_order<GeometryOut>::value == bg::counterclockwise ? "_ccw" : "") | |
189 | // << "_" << point_buffer_count | |
190 | ; | |
191 | ||
192 | //std::cout << complete.str() << std::endl; | |
193 | ||
194 | #if defined(TEST_WITH_SVG_PER_TURN) | |
195 | save_turns_visitor<point_type> visitor; | |
196 | #elif defined(TEST_WITH_SVG) | |
197 | ||
198 | buffer_svg_mapper<point_type> buffer_mapper(complete.str()); | |
199 | ||
200 | std::ostringstream filename; | |
201 | filename << "buffer_" << complete.str() << ".svg"; | |
202 | std::ofstream svg(filename.str().c_str()); | |
203 | typedef bg::svg_mapper<point_type> mapper_type; | |
204 | mapper_type mapper(svg, 1000, 800); | |
205 | ||
206 | svg_visitor<mapper_type, bg::model::box<point_type> > visitor(mapper); | |
207 | ||
208 | buffer_mapper.prepare(mapper, visitor, envelope, | |
209 | distance_strategy.negative() | |
210 | ? 1.0 | |
211 | : 1.1 * distance_strategy.max_distance(join_strategy, end_strategy) | |
212 | ); | |
213 | #else | |
214 | bg::detail::buffer::visit_pieces_default_policy visitor; | |
215 | #endif | |
216 | ||
217 | typedef typename bg::point_type<Geometry>::type point_type; | |
218 | typedef typename bg::rescale_policy_type<point_type>::type | |
219 | rescale_policy_type; | |
220 | ||
221 | // Enlarge the box to get a proper rescale policy | |
222 | bg::buffer(envelope, envelope, distance_strategy.max_distance(join_strategy, end_strategy)); | |
223 | ||
224 | rescale_policy_type rescale_policy | |
225 | = bg::get_rescale_policy<rescale_policy_type>(envelope); | |
226 | ||
227 | bg::model::multi_polygon<GeometryOut> buffered; | |
228 | ||
229 | bg::detail::buffer::buffer_inserter<GeometryOut>(geometry, | |
230 | std::back_inserter(buffered), | |
231 | distance_strategy, | |
232 | side_strategy, | |
233 | join_strategy, | |
234 | end_strategy, | |
235 | point_strategy, | |
236 | rescale_policy, | |
237 | visitor); | |
238 | ||
239 | #if defined(TEST_WITH_SVG) | |
240 | buffer_mapper.map_input_output(mapper, geometry, buffered, distance_strategy.negative()); | |
241 | #endif | |
242 | ||
243 | ||
244 | typename bg::default_area_result<GeometryOut>::type area = bg::area(buffered); | |
245 | ||
246 | //Uncomment to create simple CSV to compare/use in tests - adapt precision if necessary | |
247 | //std::cout << complete.str() << "," << std::fixed << std::setprecision(0) << area << std::endl; | |
248 | //return; | |
249 | ||
250 | if (bg::is_empty(buffered) && bg::math::equals(expected_area, 0.0)) | |
251 | { | |
252 | // As expected - don't get rescale policy for output (will be invalid) | |
253 | return; | |
254 | } | |
255 | ||
256 | BOOST_CHECK_MESSAGE | |
257 | ( | |
258 | ! bg::is_empty(buffered), | |
259 | complete.str() << " output is empty (unexpected)." | |
260 | ); | |
261 | ||
262 | bg::model::box<point_type> envelope_output; | |
263 | bg::assign_values(envelope_output, 0, 0, 1, 1); | |
264 | bg::envelope(buffered, envelope_output); | |
265 | rescale_policy_type rescale_policy_output | |
266 | = bg::get_rescale_policy<rescale_policy_type>(envelope_output); | |
267 | ||
268 | // std::cout << caseid << std::endl; | |
269 | // std::cout << "INPUT: " << bg::wkt(geometry) << std::endl; | |
270 | // std::cout << "OUTPUT: " << area << std::endl; | |
271 | // std::cout << "OUTPUT env: " << bg::wkt(envelope_output) << std::endl; | |
272 | // std::cout << bg::wkt(buffered) << std::endl; | |
273 | ||
274 | if (expected_count >= 0) | |
275 | { | |
276 | BOOST_CHECK_MESSAGE | |
277 | ( | |
278 | int(buffered.size()) == expected_count, | |
279 | "#outputs not as expected." | |
280 | << " Expected: " << expected_count | |
281 | << " Detected: " << buffered.size() | |
282 | ); | |
283 | } | |
284 | ||
285 | if (expected_holes_count >= 0) | |
286 | { | |
287 | std::size_t nholes = bg::num_interior_rings(buffered); | |
288 | BOOST_CHECK_MESSAGE | |
289 | ( | |
290 | int(nholes) == expected_holes_count, | |
291 | "#holes not as expected." | |
292 | << " Expected: " << expected_holes_count | |
293 | << " Detected: " << nholes | |
294 | ); | |
295 | } | |
296 | ||
297 | if (expected_area > -0.1) | |
298 | { | |
299 | double const difference = area - expected_area; | |
300 | BOOST_CHECK_MESSAGE | |
301 | ( | |
302 | bg::math::abs(difference) < tolerance, | |
303 | complete.str() << " not as expected. " | |
304 | << std::setprecision(18) | |
305 | << " Expected: " << expected_area | |
306 | << " Detected: " << area | |
307 | << " Diff: " << difference | |
308 | << std::setprecision(3) | |
309 | << " , " << 100.0 * (difference / expected_area) << "%" | |
310 | ); | |
311 | ||
312 | if (check_self_intersections) | |
313 | { | |
314 | ||
315 | try | |
316 | { | |
317 | bool has_self_ips = bg::detail::overlay::has_self_intersections( | |
318 | buffered, rescale_policy_output, false); | |
319 | // Be sure resulting polygon does not contain | |
320 | // self-intersections | |
321 | BOOST_CHECK_MESSAGE | |
322 | ( | |
323 | ! has_self_ips, | |
324 | complete.str() << " output is self-intersecting. " | |
325 | ); | |
326 | } | |
327 | catch(...) | |
328 | { | |
329 | BOOST_CHECK_MESSAGE | |
330 | ( | |
331 | false, | |
332 | "Exception in checking self-intersections" | |
333 | ); | |
334 | } | |
335 | } | |
336 | } | |
337 | ||
338 | #ifdef BOOST_GEOMETRY_BUFFER_TEST_IS_VALID | |
339 | if (! bg::is_valid(buffered)) | |
340 | { | |
341 | std::cout | |
342 | << "NOT VALID: " << complete.str() << std::endl | |
343 | << std::fixed << std::setprecision(16) << bg::wkt(buffered) << std::endl; | |
344 | } | |
345 | // BOOST_CHECK_MESSAGE(bg::is_valid(buffered) == true, complete.str() << " is not valid"); | |
346 | // BOOST_CHECK_MESSAGE(bg::intersects(buffered) == false, complete.str() << " intersects"); | |
347 | #endif | |
348 | ||
349 | #if defined(TEST_WITH_SVG_PER_TURN) | |
350 | { | |
351 | // Create a per turn visitor to map per turn, and buffer again with it | |
352 | per_turn_visitor<point_type> ptv(complete.str(), visitor.get_points()); | |
353 | bg::detail::buffer::buffer_inserter<GeometryOut>(geometry, | |
354 | std::back_inserter(buffered), | |
355 | distance_strategy, | |
356 | side_strategy, | |
357 | join_strategy, | |
358 | end_strategy, | |
359 | point_strategy, | |
360 | rescale_policy, | |
361 | ptv); | |
362 | ptv.map_input_output(geometry, buffered, distance_strategy.negative()); | |
363 | // self_ips NYI here | |
364 | } | |
365 | #elif defined(TEST_WITH_SVG) | |
366 | buffer_mapper.map_self_ips(mapper, buffered, rescale_policy_output); | |
367 | #endif | |
368 | ||
369 | // Check for self-intersections | |
370 | if (self_ip_count != NULL) | |
371 | { | |
372 | std::size_t count = 0; | |
373 | if (bg::detail::overlay::has_self_intersections(buffered, | |
374 | rescale_policy_output, false)) | |
375 | { | |
376 | count = count_self_ips(buffered, rescale_policy_output); | |
377 | } | |
378 | ||
379 | *self_ip_count += count; | |
380 | if (count > 0) | |
381 | { | |
382 | std::cout << complete.str() << " " << count << std::endl; | |
383 | } | |
384 | } | |
385 | } | |
386 | ||
387 | template | |
388 | < | |
389 | typename GeometryOut, | |
390 | typename JoinStrategy, | |
391 | typename EndStrategy, | |
392 | typename DistanceStrategy, | |
393 | typename SideStrategy, | |
394 | typename PointStrategy, | |
395 | typename Geometry | |
396 | > | |
397 | void test_buffer(std::string const& caseid, Geometry const& geometry, | |
398 | JoinStrategy const& join_strategy, | |
399 | EndStrategy const& end_strategy, | |
400 | DistanceStrategy const& distance_strategy, | |
401 | SideStrategy const& side_strategy, | |
402 | PointStrategy const& point_strategy, | |
403 | bool check_self_intersections, | |
404 | double expected_area, | |
405 | double tolerance, | |
406 | std::size_t* self_ip_count) | |
407 | { | |
408 | test_buffer<GeometryOut>(caseid, geometry, | |
409 | join_strategy, end_strategy, distance_strategy, side_strategy, point_strategy, | |
410 | check_self_intersections, -1, -1, expected_area, tolerance, self_ip_count); | |
411 | } | |
412 | ||
413 | #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS | |
414 | static int counter = 0; | |
415 | #endif | |
416 | ||
417 | template | |
418 | < | |
419 | typename Geometry, | |
420 | typename GeometryOut, | |
421 | typename JoinStrategy, | |
422 | typename EndStrategy | |
423 | > | |
424 | void test_one(std::string const& caseid, std::string const& wkt, | |
425 | JoinStrategy const& join_strategy, EndStrategy const& end_strategy, | |
426 | int expected_count, int expected_holes_count, double expected_area, | |
427 | double distance_left, double distance_right = same_distance, | |
428 | bool check_self_intersections = true, | |
429 | double tolerance = 0.01) | |
430 | { | |
431 | namespace bg = boost::geometry; | |
432 | Geometry g; | |
433 | bg::read_wkt(wkt, g); | |
434 | bg::correct(g); | |
435 | ||
436 | ||
437 | #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS | |
438 | std::cout | |
439 | << (counter > 0 ? "union " : "") | |
440 | << "select " << counter++ | |
441 | << ", '" << caseid << "' as caseid" | |
442 | << ", ST_Area(ST_Buffer(ST_GeomFromText('" << wkt << "'), " | |
443 | << distance_left | |
444 | << ", 'endcap=" << end_name << " join=" << join_name << "'))" | |
445 | << ", " << expected_area | |
446 | << std::endl; | |
447 | #endif | |
448 | ||
449 | ||
450 | bg::strategy::buffer::side_straight side_strategy; | |
451 | bg::strategy::buffer::point_circle circle_strategy(88); | |
452 | ||
453 | bg::strategy::buffer::distance_asymmetric | |
454 | < | |
455 | typename bg::coordinate_type<Geometry>::type | |
456 | > distance_strategy(distance_left, | |
457 | bg::math::equals(distance_right, same_distance) | |
458 | ? distance_left : distance_right); | |
459 | ||
460 | test_buffer<GeometryOut> | |
461 | (caseid, g, | |
462 | join_strategy, end_strategy, | |
463 | distance_strategy, side_strategy, circle_strategy, | |
464 | check_self_intersections, | |
465 | expected_count, expected_holes_count, expected_area, | |
466 | tolerance, NULL); | |
467 | ||
468 | #if !defined(BOOST_GEOMETRY_COMPILER_MODE_DEBUG) && defined(BOOST_GEOMETRY_COMPILER_MODE_RELEASE) | |
469 | ||
470 | // Also test symmetric distance strategy if right-distance is not specified | |
471 | // (only in release mode) | |
472 | if (bg::math::equals(distance_right, same_distance)) | |
473 | { | |
474 | bg::strategy::buffer::distance_symmetric | |
475 | < | |
476 | typename bg::coordinate_type<Geometry>::type | |
477 | > sym_distance_strategy(distance_left); | |
478 | ||
479 | test_buffer<GeometryOut> | |
480 | (caseid + "_sym", g, | |
481 | join_strategy, end_strategy, | |
482 | sym_distance_strategy, side_strategy, circle_strategy, | |
483 | check_self_intersections, | |
484 | expected_count, expected_holes_count, expected_area, | |
485 | tolerance, NULL); | |
486 | ||
487 | } | |
488 | #endif | |
489 | } | |
490 | ||
491 | template | |
492 | < | |
493 | typename Geometry, | |
494 | typename GeometryOut, | |
495 | typename JoinStrategy, | |
496 | typename EndStrategy | |
497 | > | |
498 | void test_one(std::string const& caseid, std::string const& wkt, | |
499 | JoinStrategy const& join_strategy, EndStrategy const& end_strategy, | |
500 | double expected_area, | |
501 | double distance_left, double distance_right = same_distance, | |
502 | bool check_self_intersections = true, | |
503 | double tolerance = 0.01) | |
504 | { | |
505 | test_one<Geometry, GeometryOut>(caseid, wkt, join_strategy, end_strategy, | |
506 | -1 ,-1, expected_area, | |
507 | distance_left, distance_right, check_self_intersections, tolerance); | |
508 | } | |
509 | ||
510 | // Version (currently for the Aimes test) counting self-ip's instead of checking | |
511 | template | |
512 | < | |
513 | typename Geometry, | |
514 | typename GeometryOut, | |
515 | typename JoinStrategy, | |
516 | typename EndStrategy | |
517 | > | |
518 | void test_one(std::string const& caseid, std::string const& wkt, | |
519 | JoinStrategy const& join_strategy, EndStrategy const& end_strategy, | |
520 | double expected_area, | |
521 | double distance_left, double distance_right, | |
522 | std::size_t& self_ip_count, | |
523 | double tolerance = 0.01) | |
524 | { | |
525 | namespace bg = boost::geometry; | |
526 | Geometry g; | |
527 | bg::read_wkt(wkt, g); | |
528 | bg::correct(g); | |
529 | ||
530 | bg::strategy::buffer::distance_asymmetric | |
531 | < | |
532 | typename bg::coordinate_type<Geometry>::type | |
533 | > distance_strategy(distance_left, | |
534 | bg::math::equals(distance_right, same_distance) | |
535 | ? distance_left : distance_right); | |
536 | ||
537 | bg::strategy::buffer::point_circle circle_strategy(88); | |
538 | bg::strategy::buffer::side_straight side_strategy; | |
539 | test_buffer<GeometryOut>(caseid, g, | |
540 | join_strategy, end_strategy, | |
541 | distance_strategy, side_strategy, circle_strategy, | |
542 | false, expected_area, | |
543 | tolerance, &self_ip_count); | |
544 | } | |
545 | ||
546 | template | |
547 | < | |
548 | typename Geometry, | |
549 | typename GeometryOut, | |
550 | typename JoinStrategy, | |
551 | typename EndStrategy, | |
552 | typename DistanceStrategy, | |
553 | typename SideStrategy, | |
554 | typename PointStrategy | |
555 | > | |
556 | void test_with_custom_strategies(std::string const& caseid, | |
557 | std::string const& wkt, | |
558 | JoinStrategy const& join_strategy, | |
559 | EndStrategy const& end_strategy, | |
560 | DistanceStrategy const& distance_strategy, | |
561 | SideStrategy const& side_strategy, | |
562 | PointStrategy const& point_strategy, | |
563 | double expected_area, | |
564 | double tolerance = 0.01) | |
565 | { | |
566 | namespace bg = boost::geometry; | |
567 | Geometry g; | |
568 | bg::read_wkt(wkt, g); | |
569 | bg::correct(g); | |
570 | ||
571 | test_buffer<GeometryOut> | |
572 | (caseid, g, | |
573 | join_strategy, end_strategy, | |
574 | distance_strategy, side_strategy, point_strategy, | |
575 | true, expected_area, tolerance, NULL); | |
576 | } | |
577 | ||
578 | ||
579 | ||
580 | #endif |