// Boost.Geometry (aka GGL, Generic Geometry Library) // Unit Test // Copyright (c) 2023 Adam Wulkiewicz, Lodz, Poland. // Copyright (c) 2007-2015 Barend Gehrels, Amsterdam, the Netherlands. // This file was modified by Oracle on 2016-2021. // Modifications copyright (c) 2016-2021, Oracle and/or its affiliates. // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle // 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_INTERSECTION_HPP #define BOOST_GEOMETRY_TEST_INTERSECTION_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(TEST_WITH_SVG) # include #endif #include #include #include #include #include "../setop_output_type.hpp" struct ut_settings : ut_base_settings { double percentage; bool debug{false}; bool test_point_count{false}; bool debug_wkt{false}; bool debug_dsv{false}; explicit ut_settings(double p = 0.0001, bool tv = true) : ut_base_settings(tv) , percentage(p) {} }; template void check_result(IntersectionOutput const& intersection_output, std::string const& caseid, G1 const& g1, G2 const& g2, count_set const& expected_count, count_set const& expected_hole_count, int expected_point_count, expectation_limits const& expected_length_or_area, ut_settings const& settings) { boost::ignore_unused(expected_point_count); typedef typename boost::range_value::type OutputType; bool const is_line = bg::geometry_id::type::value == 2; typename bg::default_area_result::type length_or_area = 0; int n = 0; std::size_t nholes = 0; for (auto it = intersection_output.begin(); it != intersection_output.end(); ++it) { if (! expected_count.empty()) { // here n should rather be of type std::size_t, but expected_point_count // is set to -1 in some test cases so type int was left for now n += static_cast(bg::num_points(*it, false)); } if (! expected_hole_count.empty()) { nholes += bg::num_interior_rings(*it); } // instead of specialization we check it run-time here length_or_area += is_line ? bg::length(*it) : bg::area(*it); if (settings.debug_wkt) { std::cout << std::setprecision(20) << bg::wkt(*it) << std::endl; } if (settings.debug_dsv) { // Write as DSV (which, by default, does not add a closing point) std::cout << std::setprecision(20) << bg::dsv(*it) << std::endl; } } if (settings.test_validity()) { std::string message; bool const valid = check_validity ::apply(intersection_output, caseid, g1, g2, message); BOOST_CHECK_MESSAGE(valid, "intersection: " << caseid << " not valid: " << message << " type: " << (type_for_assert_message())); } #if ! defined(BOOST_GEOMETRY_NO_BOOST_TEST) // Only test if explicitly mentioned if (settings.test_point_count) { BOOST_CHECK_MESSAGE(n == expected_point_count, "intersection: " << caseid << " #points expected: " << expected_point_count << " detected: " << n << " type: " << (type_for_assert_message()) ); } if (! expected_count.empty()) { BOOST_CHECK_MESSAGE(expected_count.has(intersection_output.size()), "intersection: " << caseid << " #outputs expected: " << expected_count << " detected: " << intersection_output.size() << " type: " << (type_for_assert_message()) ); } if (! expected_hole_count.empty()) { BOOST_CHECK_MESSAGE(expected_hole_count.has(nholes), "intersection: " << caseid << " #holes expected: " << expected_hole_count << " detected: " << nholes << " type: " << (type_for_assert_message()) ); } BOOST_CHECK_MESSAGE(expected_length_or_area.contains(length_or_area, settings.percentage), "intersection: " << caseid << std::setprecision(20) << " #area expected: " << expected_length_or_area << " detected: " << length_or_area << " type: " << (type_for_assert_message())); #endif } template typename bg::default_area_result::type test_intersection(std::string const& caseid, G1 const& g1, G2 const& g2, count_set const& expected_count = count_set(), count_set const& expected_hole_count = count_set(), int expected_point_count = 0, expectation_limits const& expected_length_or_area = 0, ut_settings const& settings = ut_settings()) { using coordinate_type = typename bg::coordinate_type::type; constexpr bool is_ccw = bg::point_order::value == bg::counterclockwise || bg::point_order::value == bg::counterclockwise; constexpr bool is_open = bg::closure::value == bg::open || bg::closure::value == bg::open; if (settings.debug) { std::cout << std::endl << "case " << caseid << " " << string_from_type::name() << (is_ccw ? " ccw" : "") << (is_open ? " open" : "") << std::endl; } typedef typename setop_output_type::type result_type; typedef typename bg::point_type::type point_type; boost::ignore_unused(); #if ! defined(BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE) if (! settings.debug) { // Check inserter behaviour with stratey using strategy_type = typename bg::strategies::relate::services::default_strategy::type; result_type clip; bg::detail::intersection::intersection_insert(g1, g2, std::back_inserter(clip), strategy_type()); } #endif typename bg::default_area_result::type length_or_area = 0; // Check normal behaviour result_type intersection_output; bg::intersection(g1, g2, intersection_output); check_result(intersection_output, caseid, g1, g2, expected_count, expected_hole_count, expected_point_count, expected_length_or_area, settings); #if ! defined(BOOST_GEOMETRY_TEST_ONLY_ONE_TYPE) // Check variant behaviour intersection_output.clear(); bg::intersection(boost::variant(g1), g2, intersection_output); check_result(intersection_output, caseid, g1, g2, expected_count, expected_hole_count, expected_point_count, expected_length_or_area, settings); intersection_output.clear(); bg::intersection(g1, boost::variant(g2), intersection_output); check_result(intersection_output, caseid, g1, g2, expected_count, expected_hole_count, expected_point_count, expected_length_or_area, settings); intersection_output.clear(); bg::intersection(boost::variant(g1), boost::variant(g2), intersection_output); check_result(intersection_output, caseid, g1, g2, expected_count, expected_hole_count, expected_point_count, expected_length_or_area, settings); #endif #if defined(TEST_WITH_SVG) { bool const is_line = bg::geometry_id::type::value == 2; std::ostringstream filename; filename << "intersection_" << caseid << "_" << string_from_type::name() << string_from_type::name() << (is_ccw ? "_ccw" : "") << (is_open ? "_open" : "") << ".svg"; std::ofstream svg(filename.str().c_str()); bg::svg_mapper mapper(svg, 500, 500); mapper.add(g1); mapper.add(g2); mapper.map(g1, is_line ? "opacity:0.6;stroke:rgb(0,255,0);stroke-width:5" : "fill-opacity:0.5;fill:rgb(153,204,0);" "stroke:rgb(153,204,0);stroke-width:3"); mapper.map(g2, "fill-opacity:0.3;fill:rgb(51,51,153);" "stroke:rgb(51,51,153);stroke-width:3"); for (auto const& item : intersection_output) { mapper.map(item, "fill-opacity:0.2;stroke-opacity:0.4;fill:rgb(255,0,0);" "stroke:rgb(255,0,255);stroke-width:8"); } } #endif if (settings.debug) { std::cout << "end case " << caseid << std::endl; } return length_or_area; } template typename bg::default_area_result::type test_intersection(std::string const& caseid, G1 const& g1, G2 const& g2, count_set const& expected_count = count_set(), int expected_point_count = 0, expectation_limits const& expected_length_or_area = 0, ut_settings const& settings = ut_settings()) { return test_intersection( caseid, g1, g2, expected_count, count_set(), expected_point_count, expected_length_or_area, settings ); } // Version with expected hole count template typename bg::default_area_result::type test_one(std::string const& caseid, std::string const& wkt1, std::string const& wkt2, count_set const& expected_count, count_set const& expected_hole_count, int expected_point_count, expectation_limits const& expected_length_or_area, ut_settings const& settings = ut_settings()) { G1 g1; bg::read_wkt(wkt1, g1); G2 g2; bg::read_wkt(wkt2, g2); // Reverse if necessary bg::correct(g1); bg::correct(g2); return test_intersection(caseid, g1, g2, expected_count, expected_hole_count, expected_point_count, expected_length_or_area, settings); } // Version without expected hole count template typename bg::default_area_result::type test_one(std::string const& caseid, std::string const& wkt1, std::string const& wkt2, count_set const& expected_count, int expected_point_count, expectation_limits const& expected_length_or_area, ut_settings const& settings = ut_settings()) { return test_one(caseid, wkt1, wkt2, expected_count, count_set(), expected_point_count, expected_length_or_area, settings); } template void test_one_lp(std::string const& caseid, std::string const& wkt_areal, std::string const& wkt_linear, count_set const& expected_count = count_set(), int expected_point_count = 0, expectation_limits const& expected_length = 0, ut_settings const& settings = ut_settings()) { #ifdef BOOST_GEOMETRY_TEST_DEBUG std::cout << caseid << " -- start" << std::endl; #endif Areal areal; bg::read_wkt(wkt_areal, areal); bg::correct(areal); Linear linear; bg::read_wkt(wkt_linear, linear); test_intersection(caseid, areal, linear, expected_count, expected_point_count, expected_length, settings); // A linestring reversed should deliver exactly the same. bg::reverse(linear); test_intersection(caseid + "_rev", areal, linear, expected_count, expected_point_count, expected_length, settings); #ifdef BOOST_GEOMETRY_TEST_DEBUG std::cout << caseid << " -- end" << std::endl; #endif } template void test_point_output(std::string const& wkt1, std::string const& wkt2, unsigned int expected_count) { Geometry1 g1; bg::read_wkt(wkt1, g1); bg::correct(g1); Geometry2 g2; bg::read_wkt(wkt2, g2); bg::correct(g2); bg::model::multi_point::type> points; bg::intersection(g1, g2, points); BOOST_CHECK_EQUAL(points.size(), expected_count); } #endif