// Boost.Geometry (aka GGL, Generic Geometry Library) // Copyright (c) 2012-2020 Barend Gehrels, Amsterdam, the Netherlands. // Copyright (c) 2022-2023 Adam Wulkiewicz, Lodz, Poland. // This file was modified by Oracle on 2017-2022. // Modifications copyright (c) 2017-2022 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_ALGORITHMS_DETAIL_BUFFER_BUFFER_INSERTER_HPP #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_BUFFER_INSERTER_HPP #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace boost { namespace geometry { #ifndef DOXYGEN_NO_DETAIL namespace detail { namespace buffer { template inline void simplify_input(RangeIn const& range, DistanceStrategy const& distance, RangeOut& simplified, Strategies const& strategies) { // We have to simplify the ring before to avoid very small-scaled // features in the original (convex/concave/convex) being enlarged // in a very large scale and causing issues (IP's within pieces). // This might be reconsidered later. Simplifying with a very small // distance (1%% of the buffer) will never be visible in the result, // if it is using round joins. For miter joins they are even more // sensitive to small scale input features, however the result will // look better. // It also gets rid of duplicate points geometry::detail::simplify::simplify_range<2>::apply(range, simplified, distance.simplify_distance(), detail::simplify::douglas_peucker(), strategies); } template struct buffer_range { typedef typename point_type::type output_point_type; typedef typename coordinate_type::type coordinate_type; template < typename Collection, typename Point, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename RobustPolicy, typename Strategies > static inline void add_join(Collection& collection, Point const& penultimate_input, Point const& previous_input, output_point_type const& prev_perp1, output_point_type const& prev_perp2, Point const& input, output_point_type const& perp1, output_point_type const& perp2, geometry::strategy::buffer::buffer_side_selector side, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, RobustPolicy const& , Strategies const& strategies) { geometry::strategy::buffer::join_selector const join = get_join_type(penultimate_input, previous_input, input, strategies); switch(join) { case geometry::strategy::buffer::join_continue : // No join, we get two consecutive sides break; case geometry::strategy::buffer::join_concave : { std::vector range_out; range_out.push_back(prev_perp2); range_out.push_back(previous_input); collection.add_piece(geometry::strategy::buffer::buffered_concave, previous_input, range_out); range_out.clear(); range_out.push_back(previous_input); range_out.push_back(perp1); collection.add_piece(geometry::strategy::buffer::buffered_concave, previous_input, range_out); } break; case geometry::strategy::buffer::join_spike : { // For linestrings, only add spike at one side to avoid // duplicates std::vector range_out; end_strategy.apply(penultimate_input, prev_perp2, previous_input, perp1, side, distance, range_out); collection.add_endcap(end_strategy, range_out, previous_input); collection.set_current_ring_concave(); } break; case geometry::strategy::buffer::join_convex : { // The corner is convex, we create a join // TODO (future) - avoid a separate vector, add the piece directly output_point_type intersection_point; if (line_line_intersection::apply(prev_perp1, prev_perp2, perp1, perp2, previous_input, segment_strategy.equidistant(), intersection_point)) { std::vector range_out; if (join_strategy.apply(intersection_point, previous_input, prev_perp2, perp1, distance.apply(previous_input, input, side), range_out)) { collection.add_piece(geometry::strategy::buffer::buffered_join, previous_input, range_out); } } } break; } } // Returns true if collinear point p2 continues after p0 and p1. // If it turns back (spike), it returns false. static inline bool same_direction(output_point_type const& p0, output_point_type const& p1, output_point_type const& p2) { typedef typename cs_tag::type cs_tag; return direction_code(p0, p1, p2) == 1; } template static inline geometry::strategy::buffer::join_selector get_join_type( output_point_type const& p0, output_point_type const& p1, output_point_type const& p2, Strategies const& strategies) { int const side = strategies.side().apply(p0, p1, p2); return side == -1 ? geometry::strategy::buffer::join_convex : side == 1 ? geometry::strategy::buffer::join_concave : same_direction(p0, p1, p2) ? geometry::strategy::buffer::join_continue : geometry::strategy::buffer::join_spike; } template < typename Collection, typename Iterator, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code iterate(Collection& collection, Iterator begin, Iterator end, geometry::strategy::buffer::buffer_side_selector side, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, RobustPolicy const& robust_policy, Strategies const& strategies, bool linear, output_point_type& first_p1, output_point_type& first_p2, output_point_type& last_p1, output_point_type& last_p2) { boost::ignore_unused(segment_strategy); typedef typename std::iterator_traits < Iterator >::value_type point_type; point_type second_point, penultimate_point, ultimate_point; // last two points from begin/end /* * last.p1 last.p2 these are the "previous (last) perpendicular points" * -------------- * | | * *------------*____ <- *prev * pup | | p1 "current perpendicular point 1" * | | * | | this forms a "side", a side is a piece * | | * *____| p2 * * ^ * *it * * pup: penultimate_point */ bool const mark_flat = linear && end_strategy.get_piece_type() == geometry::strategy::buffer::buffered_flat_end; geometry::strategy::buffer::result_code result = geometry::strategy::buffer::result_no_output; bool first = true; Iterator it = begin; std::vector generated_side; generated_side.reserve(2); for (Iterator prev = it++; it != end; ++it) { generated_side.clear(); geometry::strategy::buffer::result_code error_code = segment_strategy.apply(*prev, *it, side, distance_strategy, generated_side); if (error_code == geometry::strategy::buffer::result_no_output) { // Because input is simplified, this is improbable, // but it can happen for degenerate geometries // Further handling of this side is skipped continue; } else if (error_code == geometry::strategy::buffer::result_error_numerical) { return error_code; } BOOST_GEOMETRY_ASSERT(! generated_side.empty()); result = geometry::strategy::buffer::result_normal; if (! first) { add_join(collection, penultimate_point, *prev, last_p1, last_p2, *it, generated_side.front(), generated_side.back(), side, distance_strategy, segment_strategy, join_strategy, end_strategy, robust_policy, strategies); } collection.add_side_piece(*prev, *it, generated_side, first, distance_strategy.empty(side)); if (first && mark_flat) { collection.mark_flat_start(*prev); } penultimate_point = *prev; ultimate_point = *it; last_p1 = generated_side.front(); last_p2 = generated_side.back(); prev = it; if (first) { first = false; second_point = *it; first_p1 = generated_side.front(); first_p2 = generated_side.back(); } } if (mark_flat) { collection.mark_flat_end(ultimate_point); } return result; } }; template < typename Multi, typename PolygonOutput, typename Policy > struct buffer_multi { template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline void apply(Multi const& multi, Collection& collection, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { for (auto it = boost::begin(multi); it != boost::end(multi); ++it) { Policy::apply(*it, collection, distance_strategy, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); } } }; struct visit_pieces_default_policy { template static inline void apply(Collection const&, int) {} }; template < typename OutputPointType, typename Point, typename Collection, typename DistanceStrategy, typename PointStrategy > inline void buffer_point(Point const& point, Collection& collection, DistanceStrategy const& distance_strategy, PointStrategy const& point_strategy) { collection.start_new_ring(false); std::vector range_out; point_strategy.apply(point, distance_strategy, range_out); collection.add_piece(geometry::strategy::buffer::buffered_point, range_out, false); collection.set_piece_center(point); collection.finish_ring(geometry::strategy::buffer::result_normal); } }} // namespace detail::buffer #endif // DOXYGEN_NO_DETAIL #ifndef DOXYGEN_NO_DISPATCH namespace dispatch { template < typename Tag, typename RingInput, typename RingOutput > struct buffer_inserter {}; template < typename Point, typename RingOutput > struct buffer_inserter { template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline void apply(Point const& point, Collection& collection, DistanceStrategy const& distance_strategy, SegmentStrategy const& , JoinStrategy const& , EndStrategy const& , PointStrategy const& point_strategy, RobustPolicy const& , Strategies const& ) { detail::buffer::buffer_point < typename point_type::type >(point, collection, distance_strategy, point_strategy); } }; // Not a specialization, but called from specializations of ring and of polygon. // Calling code starts/finishes ring before/after apply template < typename RingInput, typename RingOutput > struct buffer_inserter_ring { using output_point_type = typename point_type::type; template < typename Collection, typename Iterator, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code iterate(Collection& collection, Iterator begin, Iterator end, geometry::strategy::buffer::buffer_side_selector side, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { output_point_type first_p1, first_p2, last_p1, last_p2; typedef detail::buffer::buffer_range buffer_range; geometry::strategy::buffer::result_code result = buffer_range::iterate(collection, begin, end, side, distance_strategy, segment_strategy, join_strategy, end_strategy, robust_policy, strategies, false, first_p1, first_p2, last_p1, last_p2); // Generate closing join if (result == geometry::strategy::buffer::result_normal) { buffer_range::add_join(collection, *(end - 2), *(end - 1), last_p1, last_p2, *(begin + 1), first_p1, first_p2, side, distance_strategy, segment_strategy, join_strategy, end_strategy, robust_policy, strategies); } // Buffer is closed automatically by last closing corner return result; } template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code apply(RingInput const& ring, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { // Use helper geometry to support non-mutable input Rings using simplified_ring_t = model::ring < output_point_type, point_order::value != counterclockwise, closure::value != open >; simplified_ring_t simplified; detail::buffer::simplify_input(ring, distance, simplified, strategies); geometry::strategy::buffer::result_code code = geometry::strategy::buffer::result_no_output; std::size_t n = boost::size(simplified); std::size_t const min_points = core_detail::closure::minimum_ring_size < geometry::closure::value >::value; if (n >= min_points) { detail::closed_clockwise_view view(simplified); if (distance.negative()) { // Walk backwards (rings will be reversed afterwards) code = iterate(collection, boost::rbegin(view), boost::rend(view), geometry::strategy::buffer::buffer_side_right, distance, segment_strategy, join_strategy, end_strategy, robust_policy, strategies); } else { code = iterate(collection, boost::begin(view), boost::end(view), geometry::strategy::buffer::buffer_side_left, distance, segment_strategy, join_strategy, end_strategy, robust_policy, strategies); } } if (code == geometry::strategy::buffer::result_no_output && n >= 1) { // Use point_strategy to buffer degenerated ring detail::buffer::buffer_point ( geometry::range::front(simplified), collection, distance, point_strategy ); } return code; } }; template < typename RingInput, typename RingOutput > struct buffer_inserter { template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code apply(RingInput const& ring, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { collection.start_new_ring(distance.negative()); geometry::strategy::buffer::result_code const code = buffer_inserter_ring::apply(ring, collection, distance, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); collection.finish_ring(code, ring, false, false); return code; } }; template < typename Linestring, typename Polygon > struct buffer_inserter { using output_ring_type = typename ring_type::type; using output_point_type = typename point_type::type; template < typename Collection, typename Iterator, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code iterate(Collection& collection, Iterator begin, Iterator end, geometry::strategy::buffer::buffer_side_selector side, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, RobustPolicy const& robust_policy, Strategies const& strategies, output_point_type& first_p1) { output_point_type const& ultimate_point = *(end - 1); output_point_type const& penultimate_point = *(end - 2); // For the end-cap, we need to have the last perpendicular point on the // other side of the linestring. If it is the second pass (right), // we have it already from the first phase (left). // But for the first pass, we have to generate it output_point_type reverse_p1; if (side == geometry::strategy::buffer::buffer_side_right) { reverse_p1 = first_p1; } else { std::vector generated_side; geometry::strategy::buffer::result_code code = segment_strategy.apply(ultimate_point, penultimate_point, geometry::strategy::buffer::buffer_side_right, distance_strategy, generated_side); if (code != geometry::strategy::buffer::result_normal) { // No output or numerical error return code; } reverse_p1 = generated_side.front(); } output_point_type first_p2, last_p1, last_p2; geometry::strategy::buffer::result_code result = detail::buffer::buffer_range::iterate(collection, begin, end, side, distance_strategy, segment_strategy, join_strategy, end_strategy, robust_policy, strategies, true, first_p1, first_p2, last_p1, last_p2); if (result == geometry::strategy::buffer::result_normal) { std::vector range_out; end_strategy.apply(penultimate_point, last_p2, ultimate_point, reverse_p1, side, distance_strategy, range_out); collection.add_endcap(end_strategy, range_out, ultimate_point); } return result; } template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline geometry::strategy::buffer::result_code apply(Linestring const& linestring, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { // Use helper geometry to support non-mutable input Linestrings model::linestring simplified; detail::buffer::simplify_input(linestring, distance, simplified, strategies); geometry::strategy::buffer::result_code code = geometry::strategy::buffer::result_no_output; std::size_t n = boost::size(simplified); if (n > 1) { collection.start_new_ring(false); output_point_type first_p1; code = iterate(collection, boost::begin(simplified), boost::end(simplified), geometry::strategy::buffer::buffer_side_left, distance, segment_strategy, join_strategy, end_strategy, robust_policy, strategies, first_p1); if (code == geometry::strategy::buffer::result_normal) { code = iterate(collection, boost::rbegin(simplified), boost::rend(simplified), geometry::strategy::buffer::buffer_side_right, distance, segment_strategy, join_strategy, end_strategy, robust_policy, strategies, first_p1); } collection.finish_ring(code); } if (code == geometry::strategy::buffer::result_no_output && n >= 1) { // Use point_strategy to buffer degenerated linestring detail::buffer::buffer_point ( geometry::range::front(simplified), collection, distance, point_strategy ); } return code; } }; template < typename PolygonInput, typename PolygonOutput > struct buffer_inserter { private: typedef typename ring_type::type input_ring_type; typedef typename ring_type::type output_ring_type; typedef buffer_inserter_ring policy; template < typename Iterator, typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline void iterate(Iterator begin, Iterator end, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies, bool is_interior) { for (Iterator it = begin; it != end; ++it) { // For exterior rings, it deflates if distance is negative. // For interior rings, it is vice versa bool const deflate = is_interior ? ! distance.negative() : distance.negative(); collection.start_new_ring(deflate); geometry::strategy::buffer::result_code const code = policy::apply(*it, collection, distance, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); collection.finish_ring(code, *it, is_interior, false); } } template < typename InteriorRings, typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline void apply_interior_rings(InteriorRings const& interior_rings, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { iterate(boost::begin(interior_rings), boost::end(interior_rings), collection, distance, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies, true); } public: template < typename Collection, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename RobustPolicy, typename Strategies > static inline void apply(PolygonInput const& polygon, Collection& collection, DistanceStrategy const& distance, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, RobustPolicy const& robust_policy, Strategies const& strategies) { { collection.start_new_ring(distance.negative()); geometry::strategy::buffer::result_code const code = policy::apply(exterior_ring(polygon), collection, distance, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); collection.finish_ring(code, exterior_ring(polygon), false, geometry::num_interior_rings(polygon) > 0u); } apply_interior_rings(interior_rings(polygon), collection, distance, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); } }; template < typename Multi, typename PolygonOutput > struct buffer_inserter : public detail::buffer::buffer_multi < Multi, PolygonOutput, dispatch::buffer_inserter < typename single_tag_of < typename tag::type >::type, typename boost::range_value::type, typename geometry::ring_type::type > > {}; } // namespace dispatch #endif // DOXYGEN_NO_DISPATCH #ifndef DOXYGEN_NO_DETAIL namespace detail { namespace buffer { template < typename GeometryOutput, typename GeometryInput, typename OutputIterator, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename Strategies, typename RobustPolicy, typename VisitPiecesPolicy > inline void buffer_inserter(GeometryInput const& geometry_input, OutputIterator out, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, Strategies const& strategies, RobustPolicy const& robust_policy, VisitPiecesPolicy& visit_pieces_policy ) { boost::ignore_unused(visit_pieces_policy); using collection_type = detail::buffer::buffered_piece_collection < typename geometry::ring_type::type, Strategies, DistanceStrategy, RobustPolicy >; collection_type collection(strategies, distance_strategy, robust_policy); collection_type const& const_collection = collection; static constexpr bool areal = util::is_areal::value; dispatch::buffer_inserter < typename tag_cast < typename tag::type, multi_tag >::type, GeometryInput, GeometryOutput >::apply(geometry_input, collection, distance_strategy, segment_strategy, join_strategy, end_strategy, point_strategy, robust_policy, strategies); collection.get_turns(); if BOOST_GEOMETRY_CONSTEXPR (areal) { collection.check_turn_in_original(); } collection.handle_colocations(); collection.check_turn_in_pieces(); collection.make_traversable_consistent_per_cluster(); // Visit the piece collection. This does nothing (by default), but // optionally a debugging tool can be attached (e.g. console or svg), // or the piece collection can be unit-tested // phase 0: turns (before discarded) visit_pieces_policy.apply(const_collection, 0); collection.discard_rings(); collection.block_turns(); collection.enrich(); // phase 1: turns (after enrichment/clustering) visit_pieces_policy.apply(const_collection, 1); if BOOST_GEOMETRY_CONSTEXPR (areal) { collection.deflate_check_turns(); } collection.traverse(); // Reverse all offsetted rings / traversed rings if: // - they were generated on the negative side (deflate) of polygons // - the output is counter clockwise // and avoid reversing twice bool reverse = distance_strategy.negative() && areal; if BOOST_GEOMETRY_CONSTEXPR (geometry::point_order::value == counterclockwise) { reverse = ! reverse; } if (reverse) { collection.reverse(); } if BOOST_GEOMETRY_CONSTEXPR (areal) { if (distance_strategy.negative()) { collection.discard_nonintersecting_deflated_rings(); } } collection.template assign(out); // Visit collection again // phase 2: rings (after traversing) visit_pieces_policy.apply(const_collection, 2); } template < typename GeometryOutput, typename GeometryInput, typename OutputIterator, typename DistanceStrategy, typename SegmentStrategy, typename JoinStrategy, typename EndStrategy, typename PointStrategy, typename Strategies, typename RobustPolicy > inline void buffer_inserter(GeometryInput const& geometry_input, OutputIterator out, DistanceStrategy const& distance_strategy, SegmentStrategy const& segment_strategy, JoinStrategy const& join_strategy, EndStrategy const& end_strategy, PointStrategy const& point_strategy, Strategies const& strategies, RobustPolicy const& robust_policy) { detail::buffer::visit_pieces_default_policy visitor; buffer_inserter(geometry_input, out, distance_strategy, segment_strategy, join_strategy, end_strategy, point_strategy, strategies, robust_policy, visitor); } #endif // DOXYGEN_NO_DETAIL }} // namespace detail::buffer }} // namespace boost::geometry #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_BUFFER_INSERTER_HPP