// Boost.Geometry (aka GGL, Generic Geometry Library) // Unit Test // Copyright (c) 2010-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) #ifndef BOOST_GEOMETRY_TEST_BUFFER_HPP #define BOOST_GEOMETRY_TEST_BUFFER_HPP #include #include #include #if defined(TEST_WITH_SVG) #define BOOST_GEOMETRY_BUFFER_USE_HELPER_POINTS #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(TEST_WITH_SVG) #include template void post_map(Geometry const& geometry, Mapper& mapper, RescalePolicy const& rescale_policy) { typedef typename bg::point_type::type point_type; typedef bg::detail::overlay::turn_info < point_type, typename bg::segment_ratio_type::type > turn_info; std::vector turns; bg::detail::self_get_turn_points::no_interrupt_policy policy; bg::self_turns < bg::detail::overlay::assign_null_policy >(geometry, rescale_policy, turns, policy); BOOST_FOREACH(turn_info const& turn, turns) { mapper.map(turn.point, "fill:rgb(255,128,0);stroke:rgb(0,0,100);stroke-width:1", 3); } } template struct svg_visitor { class si { private : bg::segment_identifier m_id; public : inline si(bg::segment_identifier const& id) : m_id(id) {} template inline friend std::basic_ostream& operator<<( std::basic_ostream& os, si const& s) { os << s.m_id.multi_index << "." << s.m_id.segment_index; return os; } }; SvgMapper& m_mapper; svg_visitor(SvgMapper& mapper) : m_mapper(mapper) {} template inline void map_turns(Turns const& turns, bool label_good_turns, bool label_wrong_turns) { namespace bgdb = boost::geometry::detail::buffer; typedef typename boost::range_value::type turn_type; typedef typename turn_type::point_type point_type; typedef typename turn_type::robust_point_type robust_point_type; std::map > offsets; for (typename boost::range_iterator::type it = boost::begin(turns); it != boost::end(turns); ++it) { bool is_good = true; char color = 'g'; std::string fill = "fill:rgb(0,255,0);"; switch(it->location) { case bgdb::inside_buffer : fill = "fill:rgb(255,0,0);"; color = 'r'; is_good = false; break; case bgdb::location_discard : fill = "fill:rgb(0,0,255);"; color = 'b'; is_good = false; break; } if (!it->selectable_start) { fill = "fill:rgb(255,192,0);"; color = 'o'; // orange } if (it->blocked()) { fill = "fill:rgb(128,128,128);"; color = '-'; is_good = false; } fill += "fill-opacity:0.7;"; m_mapper.map(it->point, fill, 4); if ((label_good_turns && is_good) || (label_wrong_turns && ! is_good)) { std::ostringstream out; out << it->turn_index << " " << it->operations[0].piece_index << "/" << it->operations[1].piece_index << " " << si(it->operations[0].seg_id) << "/" << si(it->operations[1].seg_id) // If you want to see travel information << std::endl << " nxt " << it->operations[0].enriched.travels_to_ip_index << "/" << it->operations[1].enriched.travels_to_ip_index << " or " << it->operations[0].enriched.next_ip_index << "/" << it->operations[1].enriched.next_ip_index //<< " frac " << it->operations[0].fraction // If you want to see robust-point coordinates (e.g. to find duplicates) // << std::endl // << " " << bg::get<0>(it->robust_point) << " , " << bg::get<1>(it->robust_point) << std::endl; out << " " << bg::method_char(it->method) << ":" << bg::operation_char(it->operations[0].operation) << "/" << bg::operation_char(it->operations[1].operation); out << " " << (it->count_on_offsetted > 0 ? "b" : "") // b: offsetted border << (it->count_within_near_offsetted > 0 ? "n" : "") << (it->count_within > 0 ? "w" : "") << (it->count_on_helper > 0 ? "h" : "") << (it->count_on_multi > 0 ? "m" : "") ; offsets[it->get_robust_point()] += 10; int offset = offsets[it->get_robust_point()]; m_mapper.text(it->point, out.str(), "fill:rgb(0,0,0);font-family='Arial';font-size:9px;", 5, offset); offsets[it->get_robust_point()] += 25; } } } template inline void map_pieces(Pieces const& pieces, OffsettedRings const& offsetted_rings, bool do_pieces, bool do_indices) { typedef typename boost::range_value::type piece_type; typedef typename boost::range_value::type ring_type; for(typename boost::range_iterator::type it = boost::begin(pieces); it != boost::end(pieces); ++it) { const piece_type& piece = *it; bg::segment_identifier seg_id = piece.first_seg_id; if (seg_id.segment_index < 0) { continue; } ring_type corner; ring_type const& ring = offsetted_rings[seg_id.multi_index]; std::copy(boost::begin(ring) + seg_id.segment_index, boost::begin(ring) + piece.last_segment_index, std::back_inserter(corner)); std::copy(boost::begin(piece.helper_points), boost::end(piece.helper_points), std::back_inserter(corner)); if (corner.empty()) { continue; } if (do_pieces) { std::string style = "opacity:0.3;stroke:rgb(0,0,0);stroke-width:1;"; m_mapper.map(corner, piece.type == bg::strategy::buffer::buffered_segment ? style + "fill:rgb(255,128,0);" : style + "fill:rgb(255,0,0);"); } if (do_indices) { // Label starting piece_index / segment_index typedef typename bg::point_type::type point_type; std::ostringstream out; out << piece.index << "/" << int(piece.type) << "/" << piece.first_seg_id.segment_index << ".." << piece.last_segment_index - 1; point_type label_point = corner.front(); int const mid_offset = piece.offsetted_count / 2 - 1; if (mid_offset >= 0 && mid_offset + 1 < corner.size()) { bg::set<0>(label_point, (bg::get<0>(corner[mid_offset]) + bg::get<0>(corner[mid_offset + 1])) / 2.0); bg::set<1>(label_point, (bg::get<1>(corner[mid_offset]) + bg::get<1>(corner[mid_offset + 1])) / 2.0); } m_mapper.text(label_point, out.str(), "fill:rgb(255,0,0);font-family='Arial';font-size:10px;", 5, 5); } } } template inline void map_traversed_rings(TraversedRings const& traversed_rings) { for(typename boost::range_iterator::type it = boost::begin(traversed_rings); it != boost::end(traversed_rings); ++it) { m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,255,0);stroke-width:2"); } } template inline void map_offsetted_rings(OffsettedRings const& offsetted_rings) { for(typename boost::range_iterator::type it = boost::begin(offsetted_rings); it != boost::end(offsetted_rings); ++it) { if (it->discarded()) { m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(255,0,0);stroke-width:2"); } else { m_mapper.map(*it, "opacity:0.4;fill:none;stroke:rgb(0,0,255);stroke-width:2"); } } } template inline void apply(PieceCollection const& collection, int phase) { // Comment next return if you want to see pieces, turns, etc. return; if(phase == 0) { map_pieces(collection.m_pieces, collection.offsetted_rings, true, true); map_turns(collection.m_turns, true, false); } if (phase == 1) { // map_traversed_rings(collection.traversed_rings); // map_offsetted_rings(collection.offsetted_rings); } } }; #endif //----------------------------------------------------------------------------- template struct JoinTestProperties { }; template<> struct JoinTestProperties { static std::string name() { return "round"; } }; template<> struct JoinTestProperties { static std::string name() { return "miter"; } }; template<> struct JoinTestProperties { static std::string name() { return "divide"; } }; //----------------------------------------------------------------------------- template struct EndTestProperties { }; template<> struct EndTestProperties { static std::string name() { return "round"; } }; template<> struct EndTestProperties { static std::string name() { return "flat"; } }; template std::size_t count_self_ips(Geometry const& geometry, RescalePolicy const& rescale_policy) { typedef typename bg::point_type::type point_type; typedef bg::detail::overlay::turn_info < point_type, typename bg::segment_ratio_type::type > turn_info; std::vector turns; bg::detail::self_get_turn_points::no_interrupt_policy policy; bg::self_turns < bg::detail::overlay::assign_null_policy >(geometry, rescale_policy, turns, policy); return turns.size(); } template < typename GeometryOut, typename JoinStrategy, typename EndStrategy, typename DistanceStrategy, typename SideStrategy, typename PointStrategy, typename Geometry > void 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) { namespace bg = boost::geometry; typedef typename bg::coordinate_type::type coordinate_type; typedef typename bg::point_type::type point_type; typedef typename bg::tag::type tag; // TODO use something different here: std::string type = boost::is_same::value ? "poly" : boost::is_same::value ? "line" : boost::is_same::value ? "point" : boost::is_same::value ? "multipoly" : boost::is_same::value ? "multiline" : boost::is_same::value ? "multipoint" : "" ; bg::model::box envelope; bg::envelope(geometry, envelope); std::string join_name = JoinTestProperties::name(); std::string end_name = EndTestProperties::name(); if (boost::is_same::value || boost::is_same::value) { join_name.clear(); } std::ostringstream complete; complete << type << "_" << caseid << "_" << string_from_type::name() << "_" << join_name << (end_name.empty() ? "" : "_") << end_name << (distance_strategy.negative() ? "_deflate" : "") << (bg::point_order::value == bg::counterclockwise ? "_ccw" : "") // << "_" << point_buffer_count ; //std::cout << complete.str() << std::endl; std::ostringstream filename; filename << "buffer_" << complete.str() << ".svg"; #if defined(TEST_WITH_SVG) std::ofstream svg(filename.str().c_str()); typedef bg::svg_mapper mapper_type; mapper_type mapper(svg, 1000, 1000); { bg::model::box box = envelope; if (distance_strategy.negative()) { bg::buffer(box, box, 1.0); } else { bg::buffer(box, box, 1.1 * distance_strategy.max_distance(join_strategy, end_strategy)); } mapper.add(box); } svg_visitor visitor(mapper); #else bg::detail::buffer::visit_pieces_default_policy visitor; #endif typedef typename bg::point_type::type point_type; typedef typename bg::rescale_policy_type::type rescale_policy_type; // Enlarge the box to get a proper rescale policy bg::buffer(envelope, envelope, distance_strategy.max_distance(join_strategy, end_strategy)); rescale_policy_type rescale_policy = bg::get_rescale_policy(envelope); std::vector buffered; bg::detail::buffer::buffer_inserter(geometry, std::back_inserter(buffered), distance_strategy, side_strategy, join_strategy, end_strategy, point_strategy, rescale_policy, visitor); typename bg::default_area_result::type area = 0; BOOST_FOREACH(GeometryOut const& polygon, buffered) { area += bg::area(polygon); } //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; //} if (expected_area > -0.1) { BOOST_CHECK_MESSAGE ( bg::math::abs(area - expected_area) < tolerance, complete.str() << " not as expected. " << std::setprecision(18) << " Expected: " << expected_area << " Detected: " << area ); if (check_self_intersections) { // 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. " ); } } } #if defined(TEST_WITH_SVG) bool const areal = boost::is_same < typename bg::tag_cast::type, bg::areal_tag >::type::value; // Map input geometry in green if (areal) { mapper.map(geometry, "opacity:0.5;fill:rgb(0,128,0);stroke:rgb(0,64,0);stroke-width:2"); } else { 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); } #endif if (self_ip_count != NULL) { std::size_t count = 0; BOOST_FOREACH(GeometryOut const& polygon, buffered) { if (bg::detail::overlay::has_self_intersections(polygon, rescale_policy, false)) { count += count_self_ips(polygon, rescale_policy); } } *self_ip_count += count; if (count > 0) { std::cout << complete.str() << " " << count << std::endl; } } } #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS static int counter = 0; #endif template < typename Geometry, typename GeometryOut, typename JoinStrategy, typename EndStrategy > void 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 = -999, bool check_self_intersections = true, double tolerance = 0.01) { namespace bg = boost::geometry; Geometry g; bg::read_wkt(wkt, g); bg::correct(g); #ifdef BOOST_GEOMETRY_CHECK_WITH_POSTGIS std::cout << (counter > 0 ? "union " : "") << "select " << counter++ << ", '" << caseid << "' as caseid" << ", ST_Area(ST_Buffer(ST_GeomFromText('" << wkt << "'), " << distance_left << ", 'endcap=" << end_name << " join=" << join_name << "'))" << ", " << expected_area << std::endl; #endif bg::strategy::buffer::side_straight side_strategy; bg::strategy::buffer::point_circle circle_strategy(88); bg::strategy::buffer::distance_asymmetric < typename bg::coordinate_type::type > distance_strategy(distance_left, distance_right > -998 ? distance_right : distance_left); test_buffer (caseid, g, join_strategy, end_strategy, distance_strategy, side_strategy, circle_strategy, check_self_intersections, expected_area, tolerance, NULL); // Also test symmetric distance strategy if right-distance is not specified if (bg::math::equals(distance_right, -999)) { bg::strategy::buffer::distance_symmetric < typename bg::coordinate_type::type > sym_distance_strategy(distance_left); test_buffer (caseid + "_sym", g, join_strategy, end_strategy, sym_distance_strategy, side_strategy, circle_strategy, check_self_intersections, expected_area, tolerance, NULL); } } // Version (currently for the Aimes test) counting self-ip's instead of checking template < typename Geometry, typename GeometryOut, typename JoinStrategy, typename EndStrategy > void 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) { namespace bg = boost::geometry; Geometry g; bg::read_wkt(wkt, g); bg::correct(g); bg::strategy::buffer::distance_asymmetric < typename bg::coordinate_type::type > distance_strategy(distance_left, distance_right > -998 ? distance_right : distance_left); bg::strategy::buffer::point_circle circle_strategy(88); bg::strategy::buffer::side_straight side_strategy; test_buffer(caseid, g, join_strategy, end_strategy, distance_strategy, side_strategy, circle_strategy, false, expected_area, tolerance, &self_ip_count); } template < typename Geometry, typename GeometryOut, typename JoinStrategy, typename EndStrategy, typename DistanceStrategy, typename SideStrategy, typename PointStrategy > void 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) { namespace bg = boost::geometry; Geometry g; bg::read_wkt(wkt, g); bg::correct(g); test_buffer (caseid, g, join_strategy, end_strategy, distance_strategy, side_strategy, point_strategy, true, expected_area, 0.01, NULL); } #endif