diff --git a/test/algorithms/buffer/multi_point_buffer.cpp b/test/algorithms/buffer/multi_point_buffer.cpp index da0efc3a7..d18f57ff6 100644 --- a/test/algorithms/buffer/multi_point_buffer.cpp +++ b/test/algorithms/buffer/multi_point_buffer.cpp @@ -14,12 +14,14 @@ static std::string const simplex = "MULTIPOINT((5 5),(7 7))"; static std::string const three = "MULTIPOINT((5 8),(9 8),(7 11))"; -// Generates error (extra polygon on top of rest) at distance 14.0: +// Generated error (extra polygon on top of rest) at distance 14.0: static std::string const multipoint_a = "MULTIPOINT((39 44),(38 37),(41 29),(15 33),(58 39))"; // Just one with holes at distance ~ 15 static std::string const multipoint_b = "MULTIPOINT((5 56),(98 67),(20 7),(58 60),(10 4),(75 68),(61 68),(75 62),(92 26),(74 6),(67 54),(20 43),(63 30),(45 7))"; +// Grid, U-form, generates error for square point at 0.54 (top cells to control rescale) +static std::string const grid_a = "MULTIPOINT(5 0,6 0,7 0, 5 1,7 1, 0 13,8 13)"; template void test_all() @@ -46,6 +48,29 @@ void test_all() test_one("multipoint_b", multipoint_b, join_miter, end_flat, 7109.88, 15.0, 15.0); test_one("multipoint_b1", multipoint_b, join_miter, end_flat, 6911.89, 14.7, 14.7); test_one("multipoint_b2", multipoint_b, join_miter, end_flat, 7174.79, 15.1, 15.1); + + + // Grid tests + { + bg::strategy::buffer::point_square point_strategy; + bg::strategy::buffer::side_straight side_strategy; + + typedef bg::strategy::buffer::distance_symmetric + < + typename bg::coordinate_type

::type + > distance_strategy; + + test_with_custom_strategies("grid_a50", + grid_a, join_miter, end_flat, + distance_strategy(0.5), side_strategy, point_strategy, 7.0); +#if defined(BOOST_GEOMETRY_BUFFER_INCLUDE_FAILING_TESTS) + test_with_custom_strategies("grid_a54", + grid_a, join_miter, end_flat, + distance_strategy(0.54), side_strategy, point_strategy, 99); +#endif + } + + } template diff --git a/test/algorithms/buffer/test_buffer.hpp b/test/algorithms/buffer/test_buffer.hpp index 6e4838899..d34ed96c0 100644 --- a/test/algorithms/buffer/test_buffer.hpp +++ b/test/algorithms/buffer/test_buffer.hpp @@ -16,8 +16,19 @@ #if defined(TEST_WITH_SVG) #define BOOST_GEOMETRY_BUFFER_USE_HELPER_POINTS + +// Uncomment next lines if you want to have a zoomed view +// #define BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + +// If possible define box before including this unit with the right view +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX +# ifndef BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX +# define BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX "BOX(0 0,100 100)" +# endif #endif +#endif // TEST_WITH_SVG + #include #include @@ -26,24 +37,20 @@ #include #include #include +#include +#include +#include #include #include +#include #include #include -#include -#include -#include - -#include - #include - - #include @@ -91,9 +98,13 @@ void post_map(Geometry const& geometry, Mapper& mapper, RescalePolicy const& res } } -template +template struct svg_visitor { +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + Box m_alternate_box; +#endif + class si { private : @@ -134,6 +145,13 @@ struct svg_visitor for (typename boost::range_iterator::type it = boost::begin(turns); it != boost::end(turns); ++it) { +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + if (bg::disjoint(it->point, m_alternate_box)) + { + continue; + } +#endif + bool is_good = true; char color = 'g'; std::string fill = "fill:rgb(0,255,0);"; @@ -242,6 +260,12 @@ struct svg_visitor { continue; } +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + if (bg::disjoint(corner, m_alternate_box)) + { + continue; + } +#endif if (do_pieces) { @@ -311,11 +335,13 @@ struct svg_visitor map_pieces(collection.m_pieces, collection.offsetted_rings, true, true); map_turns(collection.m_turns, true, false); } +#if !defined(BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX) if (phase == 1) { // map_traversed_rings(collection.traversed_rings); // map_offsetted_rings(collection.offsetted_rings); } +#endif } }; @@ -451,6 +477,18 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, typedef bg::svg_mapper mapper_type; mapper_type mapper(svg, 1000, 1000); + svg_visitor, tag> visitor(mapper); + +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + // Create a zoomed-in view + bg::model::box alternate_box; + bg::read_wkt(BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX, alternate_box); + mapper.add(alternate_box); + + // Take care non-visible elements are skipped + visitor.m_alternate_box = alternate_box; +#else + { bg::model::box box = envelope; if (distance_strategy.negative()) @@ -463,8 +501,8 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, } mapper.add(box); } +#endif - svg_visitor visitor(mapper); #else bg::detail::buffer::visit_pieces_default_policy visitor; #endif @@ -479,7 +517,7 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, rescale_policy_type rescale_policy = bg::get_rescale_policy(envelope); - std::vector buffered; + bg::model::multi_polygon buffered; bg::detail::buffer::buffer_inserter(geometry, std::back_inserter(buffered), @@ -491,19 +529,12 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, rescale_policy, visitor); - typename bg::default_area_result::type area = 0; - BOOST_FOREACH(GeometryOut const& polygon, buffered) - { - area += bg::area(polygon); - } + typename bg::default_area_result::type area = bg::area(buffered); //std::cout << caseid << " " << distance_left << std::endl; //std::cout << "INPUT: " << bg::wkt(geometry) << std::endl; //std::cout << "OUTPUT: " << area << std::endl; - //BOOST_FOREACH(GeometryOut const& polygon, buffered) - //{ - // std::cout << bg::wkt(polygon) << std::endl; - //} + //std::cout << bg::wkt(buffered) << std::endl; if (expected_area > -0.1) @@ -521,18 +552,26 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, { // Be sure resulting polygon does not contain // self-intersections - BOOST_FOREACH(GeometryOut const& polygon, buffered) - { - BOOST_CHECK_MESSAGE - ( - ! bg::detail::overlay::has_self_intersections(polygon, - rescale_policy, false), - complete.str() << " output is self-intersecting. " - ); - } + BOOST_CHECK_MESSAGE + ( + ! bg::detail::overlay::has_self_intersections(buffered, + rescale_policy, false), + complete.str() << " output is self-intersecting. " + ); } } +#ifdef BOOST_GEOMETRY_BUFFER_TEST_IS_VALID + if (! bg::is_valid(buffered)) + { + std::cout + << "NOT VALID: " << complete.str() << std::endl + << std::fixed << std::setprecision(16) << bg::wkt(buffered) << std::endl; + } +// BOOST_CHECK_MESSAGE(bg::is_valid(buffered) == true, complete.str() << " is not valid"); +// BOOST_CHECK_MESSAGE(bg::intersects(buffered) == false, complete.str() << " intersects"); +#endif + #if defined(TEST_WITH_SVG) bool const areal = boost::is_same < @@ -542,38 +581,46 @@ void test_buffer(std::string const& caseid, Geometry const& geometry, // Map input geometry in green if (areal) { +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX_FOR_INPUT + // Assuming input is areal + bg::model::multi_polygon clipped; + bg::intersection(geometry, alternate_box, clipped); + mapper.map(clipped, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2"); +#else mapper.map(geometry, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2"); +#endif } else { + // TODO: clip input points/linestring mapper.map(geometry, "opacity:0.5;stroke:rgb(0,128,0);stroke-width:10"); } - // Map buffer in yellow (inflate) and with orange-dots (deflate) - BOOST_FOREACH(GeometryOut const& polygon, buffered) { - if (distance_strategy.negative()) - { - mapper.map(polygon, "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3"); - } - else - { - mapper.map(polygon, "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3"); - } - post_map(polygon, mapper, rescale_policy); - } + // Map buffer in yellow (inflate) and with orange-dots (deflate) + std::string style = distance_strategy.negative() + ? "opacity:0.4;fill:rgb(255,255,192);stroke:rgb(255,128,0);stroke-width:3" + : "opacity:0.4;fill:rgb(255,255,128);stroke:rgb(0,0,0);stroke-width:3"; + +#ifdef BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX + // Clip output multi-polygon with box + bg::model::multi_polygon clipped; + bg::intersection(buffered, alternate_box, clipped); + mapper.map(clipped, style); +#else + mapper.map(buffered, style); #endif + } + post_map(buffered, mapper, rescale_policy); +#endif // TEST_WITH_SVG if (self_ip_count != NULL) { std::size_t count = 0; - BOOST_FOREACH(GeometryOut const& polygon, buffered) + if (bg::detail::overlay::has_self_intersections(buffered, + rescale_policy, false)) { - if (bg::detail::overlay::has_self_intersections(polygon, - rescale_policy, false)) - { - count += count_self_ips(polygon, rescale_policy); - } + count = count_self_ips(buffered, rescale_policy); } *self_ip_count += count; @@ -708,7 +755,8 @@ void test_with_custom_strategies(std::string const& caseid, DistanceStrategy const& distance_strategy, SideStrategy const& side_strategy, PointStrategy const& point_strategy, - double expected_area) + double expected_area, + double tolerance = 0.01) { namespace bg = boost::geometry; Geometry g; @@ -719,7 +767,7 @@ void test_with_custom_strategies(std::string const& caseid, (caseid, g, join_strategy, end_strategy, distance_strategy, side_strategy, point_strategy, - true, expected_area, 0.01, NULL); + true, expected_area, tolerance, NULL); } diff --git a/test/robustness/overlay/buffer/many_ring_buffer.cpp b/test/robustness/overlay/buffer/many_ring_buffer.cpp new file mode 100644 index 000000000..82226a087 --- /dev/null +++ b/test/robustness/overlay/buffer/many_ring_buffer.cpp @@ -0,0 +1,301 @@ +// Boost.Geometry (aka GGL, Generic Geometry Library) +// Unit Test + +// Copyright (c) 2012-2014 Barend Gehrels, Amsterdam, the Netherlands. + +// Use, modification and distribution is subject to the Boost Software License, +// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#define BOOST_GEOMETRY_BUFFER_TEST_SVG_USE_ALTERNATE_BOX_FOR_INPUT +#define BOOST_GEOMETRY_BUFFER_TEST_SVG_ALTERNATE_BOX "BOX(179 4, 180 5)" + + +#include + +#include +#include + +#include +#include +#include +#include + + + +const int point_count = 90; // points for a full circle + +// Function to let buffer-distance depend on alpha, e.g.: +inline double corrected_distance(double distance, double alpha) +{ + return distance * 1.0 + 0.2 * sin(alpha * 6.0); +} + +class buffer_point_strategy_sample +{ +public : + + template + < + typename Point, + typename OutputRange, + typename DistanceStrategy + > + void apply(Point const& point, + DistanceStrategy const& distance_strategy, + OutputRange& output_range) const + { + double const distance = distance_strategy.apply(point, point, + bg::strategy::buffer::buffer_side_left); + + double const angle_increment = 2.0 * M_PI / double(point_count); + + double alpha = 0; + for (std::size_t i = 0; i <= point_count; i++, alpha -= angle_increment) + { + double const cd = corrected_distance(distance, alpha); + + typename boost::range_value::type output_point; + bg::set<0>(output_point, bg::get<0>(point) + cd * cos(alpha)); + bg::set<1>(output_point, bg::get<1>(point) + cd * sin(alpha)); + output_range.push_back(output_point); + } + } +}; + +class buffer_join_strategy_sample +{ +private : + template + < + typename Point, + typename DistanceType, + typename RangeOut + > + inline void generate_points(Point const& vertex, + Point const& perp1, Point const& perp2, + DistanceType const& buffer_distance, + RangeOut& range_out) const + { + double dx1 = bg::get<0>(perp1) - bg::get<0>(vertex); + double dy1 = bg::get<1>(perp1) - bg::get<1>(vertex); + double dx2 = bg::get<0>(perp2) - bg::get<0>(vertex); + double dy2 = bg::get<1>(perp2) - bg::get<1>(vertex); + + // Assuming the corner is convex, angle2 < angle1 + double const angle1 = atan2(dy1, dx1); + double angle2 = atan2(dy2, dx2); + + while (angle2 > angle1) + { + angle2 -= 2 * M_PI; + } + + double const angle_increment = 2.0 * M_PI / double(point_count); + double alpha = angle1 - angle_increment; + + for (int i = 0; alpha >= angle2 && i < point_count; i++, alpha -= angle_increment) + { + double cd = corrected_distance(buffer_distance, alpha); + + Point p; + bg::set<0>(p, bg::get<0>(vertex) + cd * cos(alpha)); + bg::set<1>(p, bg::get<1>(vertex) + cd * sin(alpha)); + range_out.push_back(p); + } + } + +public : + + template + inline bool apply(Point const& ip, Point const& vertex, + Point const& perp1, Point const& perp2, + DistanceType const& buffer_distance, + RangeOut& range_out) const + { + generate_points(vertex, perp1, perp2, buffer_distance, range_out); + return true; + } + + template + static inline NumericType max_distance(NumericType const& distance) + { + return distance; + } + +}; + +class buffer_side_sample +{ +public : + template + < + typename Point, + typename OutputRange, + typename DistanceStrategy + > + static inline void apply( + Point const& input_p1, Point const& input_p2, + bg::strategy::buffer::buffer_side_selector side, + DistanceStrategy const& distance, + OutputRange& output_range) + { + // Generate a block along (left or right of) the segment + + double const dx = bg::get<0>(input_p2) - bg::get<0>(input_p1); + double const dy = bg::get<1>(input_p2) - bg::get<1>(input_p1); + + // For normalization [0,1] (=dot product d.d, sqrt) + double const length = bg::math::sqrt(dx * dx + dy * dy); + + if (bg::math::equals(length, 0)) + { + return; + } + + // Generate the normalized perpendicular p, to the left (ccw) + double const px = -dy / length; + double const py = dx / length; + + // Both vectors perpendicular to input p1 and input p2 have same angle + double const alpha = atan2(py, px); + + double const d = distance.apply(input_p1, input_p2, side); + + double const cd = corrected_distance(d, alpha); + + output_range.resize(2); + + bg::set<0>(output_range.front(), bg::get<0>(input_p1) + px * cd); + bg::set<1>(output_range.front(), bg::get<1>(input_p1) + py * cd); + bg::set<0>(output_range.back(), bg::get<0>(input_p2) + px * cd); + bg::set<1>(output_range.back(), bg::get<1>(input_p2) + py * cd); + } +}; + +#ifdef TEST_WITH_SVG +template +void create_svg(std::string const& filename, Geometry1 const& original, Geometry2 const& buffer, std::string const& color) +{ + typedef typename bg::point_type::type point_type; + std::ofstream svg(filename.c_str()); + + bg::svg_mapper mapper(svg, 800, 800); + mapper.add(buffer); + + mapper.map(original, "fill-opacity:0.3;fill:rgb(255,0,0);stroke:rgb(0,0,0);stroke-width:1"); + + std::string style = "fill-opacity:0.3;fill:"; + style += color; + style += ";stroke:rgb(0,0,0);stroke-width:1"; + mapper.map(buffer, style); +} +#endif + + +void test_many_rings(int imn, int jmx, int count, + double expected_area_exterior, + double expected_area_interior) +{ + typedef bg::model::point point; + typedef bg::model::polygon polygon_type; + typedef bg::model::multi_polygon multi_polygon_type; + + // Predefined strategies + bg::strategy::buffer::distance_symmetric distance_strategy(1.3); + bg::strategy::buffer::end_flat end_strategy; // not effectively used + + // Own strategies + buffer_join_strategy_sample join_strategy; + buffer_point_strategy_sample point_strategy; + buffer_side_sample side_strategy; + + // Declare output + + bg::model::multi_point mp; + + // Use a bit of random disturbance in the otherwise too regular grid + typedef boost::minstd_rand base_generator_type; + base_generator_type generator(12345); + boost::uniform_real<> random_range(0.0, 0.5); + boost::variate_generator + < + base_generator_type&, + boost::uniform_real<> + > random(generator, random_range); + + for (int i = 0; i < count; i++) + { + for (int j = 0; j < count; j++) + { + double x = i * 3.0 + random(); + double y = j * 3.0 + random(); + //if (i > 30 && j < 30) + if (i > imn && j < jmx) + { + point p(x, y); + mp.push_back(p); + } + } + } + + multi_polygon_type many_rings; + // Create the buffer of a multi-point + bg::buffer(mp, many_rings, + distance_strategy, side_strategy, + join_strategy, end_strategy, point_strategy); + + bg::model::box envelope; + bg::envelope(many_rings, envelope); + bg::buffer(envelope, envelope, 1.0); + + multi_polygon_type many_interiors; + bg::difference(envelope, many_rings, many_interiors); + +#ifdef TEST_WITH_SVG + create_svg("/tmp/many_interiors.svg", mp, many_interiors, "rgb(51,51,153)"); + create_svg("/tmp/buffer.svg", mp, many_rings, "rgb(51,51,153)"); +#endif + + bg::strategy::buffer::join_round join_round(100); + bg::strategy::buffer::end_flat end_flat; + + { + std::ostringstream out; + out << "many_rings_" << count; + out << "_" << imn << "_" << jmx; + std::ostringstream wkt; + wkt << std::setprecision(12) << bg::wkt(many_rings); + + boost::timer t; + test_one(out.str(), wkt.str(), join_round, end_flat, expected_area_exterior, 0.3); + std::cout << "Exterior " << count << " " << t.elapsed() << std::endl; + } + + return; + { + std::ostringstream out; + out << "many_interiors_" << count; + std::ostringstream wkt; + wkt << std::setprecision(12) << bg::wkt(many_interiors); + + boost::timer t; + test_one(out.str(), wkt.str(), join_round, end_flat, expected_area_interior, 0.3); + std::cout << "Interior " << count << " " << t.elapsed() << std::endl; + } +} + +int test_main(int, char* []) +{ +// test_many_rings(10, 795.70334, 806.7609); +// test_many_rings(30, 7136.7098, 6174.4496); + test_many_rings(30, 30, 70, 38764.2721, 31910.3280); +// for (int i = 30; i < 60; i++) +// { +// for (int j = 5; j <= 30; j++) +// { +// test_many_rings(i, j, 70, 38764.2721, 31910.3280); +// } +// } + return 0; +}