summaryrefslogtreecommitdiff
path: root/boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp
diff options
context:
space:
mode:
Diffstat (limited to 'boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp')
-rw-r--r--boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp557
1 files changed, 444 insertions, 113 deletions
diff --git a/boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp b/boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp
index c02f56de3f..8803efdec9 100644
--- a/boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp
+++ b/boost/geometry/algorithms/detail/buffer/turn_in_piece_visitor.hpp
@@ -9,19 +9,23 @@
#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_TURN_IN_PIECE_VISITOR
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_TURN_IN_PIECE_VISITOR
+
+#include <boost/core/ignore_unused.hpp>
+
#include <boost/range.hpp>
#include <boost/geometry/arithmetic/dot_product.hpp>
+#include <boost/geometry/algorithms/assign.hpp>
+#include <boost/geometry/algorithms/comparable_distance.hpp>
#include <boost/geometry/algorithms/equals.hpp>
#include <boost/geometry/algorithms/expand.hpp>
#include <boost/geometry/algorithms/detail/disjoint/point_box.hpp>
+#include <boost/geometry/algorithms/detail/disjoint/box_box.hpp>
#include <boost/geometry/algorithms/detail/overlay/segment_identifier.hpp>
#include <boost/geometry/algorithms/detail/overlay/get_turn_info.hpp>
#include <boost/geometry/policies/compare.hpp>
#include <boost/geometry/strategies/buffer.hpp>
-#include <boost/geometry/geometries/linestring.hpp>
-#include <boost/geometry/algorithms/comparable_distance.hpp>
namespace boost { namespace geometry
{
@@ -31,6 +35,33 @@ namespace boost { namespace geometry
namespace detail { namespace buffer
{
+struct piece_get_box
+{
+ template <typename Box, typename Piece>
+ static inline void apply(Box& total, Piece const& piece)
+ {
+ geometry::expand(total, piece.robust_envelope);
+ }
+};
+
+struct piece_ovelaps_box
+{
+ template <typename Box, typename Piece>
+ static inline bool apply(Box const& box, Piece const& piece)
+ {
+ if (piece.type == strategy::buffer::buffered_flat_end
+ || piece.type == strategy::buffer::buffered_concave)
+ {
+ // Turns cannot be inside a flat end (though they can be on border)
+ // Neither we need to check if they are inside concave helper pieces
+
+ // Skip all pieces not used as soon as possible
+ return false;
+ }
+
+ return ! geometry::detail::disjoint::disjoint_box_box(box, piece.robust_envelope);
+ }
+};
struct turn_get_box
{
@@ -46,96 +77,422 @@ struct turn_ovelaps_box
template <typename Box, typename Turn>
static inline bool apply(Box const& box, Turn const& turn)
{
- return ! dispatch::disjoint
- <
- typename Turn::robust_point_type,
- Box
- >::apply(turn.robust_point, box);
+ return ! geometry::detail::disjoint::disjoint_point_box(turn.robust_point, box);
}
};
-template <typename Turns, typename Pieces>
-class turn_in_piece_visitor
+
+enum analyse_result
{
- Turns& m_turns; // because partition is currently operating on const input only
- Pieces const& m_pieces; // to check for piece-type
+ analyse_unknown,
+ analyse_continue,
+ analyse_disjoint,
+ analyse_within,
+ analyse_on_original_boundary,
+ analyse_on_offsetted,
+ analyse_near_offsetted
+};
- typedef boost::long_long_type calculation_type;
+template <typename Point>
+inline bool in_box(Point const& previous,
+ Point const& current, Point const& point)
+{
+ // Get its box (TODO: this can be prepared-on-demand later)
+ typedef geometry::model::box<Point> box_type;
+ box_type box;
+ geometry::assign_inverse(box);
+ geometry::expand(box, previous);
+ geometry::expand(box, current);
+
+ return geometry::covered_by(point, box);
+}
+
+template <typename Point, typename Turn>
+inline analyse_result check_segment(Point const& previous,
+ Point const& current, Turn const& turn,
+ bool from_monotonic)
+{
+ typedef typename strategy::side::services::default_strategy
+ <
+ typename cs_tag<Point>::type
+ >::type side_strategy;
+ typedef typename geometry::coordinate_type<Point>::type coordinate_type;
+
+ coordinate_type const twice_area
+ = side_strategy::template side_value
+ <
+ coordinate_type,
+ coordinate_type
+ >(previous, current, turn.robust_point);
- template <typename Point>
- static inline bool projection_on_segment(Point const& subject, Point const& p, Point const& q)
+ if (twice_area == 0)
{
- typedef Point vector_type;
- typedef typename geometry::coordinate_type<Point>::type coordinate_type;
+ // Collinear, only on segment if it is covered by its bbox
+ if (in_box(previous, current, turn.robust_point))
+ {
+ return analyse_on_offsetted;
+ }
+ }
+ else if (twice_area < 0)
+ {
+ // It is in the triangle right-of the segment where the
+ // segment is the hypothenusa. Check if it is close
+ // (within rounding-area)
+ if (twice_area * twice_area < geometry::comparable_distance(previous, current)
+ && in_box(previous, current, turn.robust_point))
+ {
+ return analyse_near_offsetted;
+ }
+ else if (from_monotonic)
+ {
+ return analyse_within;
+ }
+ }
+ else if (twice_area > 0 && from_monotonic)
+ {
+ // Left of segment
+ return analyse_disjoint;
+ }
+
+ // Not monotonic, on left or right side: continue analysing
+ return analyse_continue;
+}
- vector_type v = q;
- vector_type w = subject;
- subtract_point(v, p);
- subtract_point(w, p);
- coordinate_type const zero = coordinate_type();
- coordinate_type const c1 = dot_product(w, v);
+class analyse_turn_wrt_point_piece
+{
+public :
+ template <typename Turn, typename Piece>
+ static inline analyse_result apply(Turn const& turn, Piece const& piece)
+ {
+ typedef typename Piece::section_type section_type;
+ typedef typename Turn::robust_point_type point_type;
+ typedef typename geometry::coordinate_type<point_type>::type coordinate_type;
- if (c1 < zero)
+ coordinate_type const point_y = geometry::get<1>(turn.robust_point);
+
+ typedef strategy::within::winding<point_type> strategy_type;
+
+ typename strategy_type::state_type state;
+ strategy_type strategy;
+ boost::ignore_unused(strategy);
+
+ for (std::size_t s = 0; s < piece.sections.size(); s++)
{
- return false;
+ section_type const& section = piece.sections[s];
+ // If point within vertical range of monotonic section:
+ if (! section.duplicate
+ && section.begin_index < section.end_index
+ && point_y >= geometry::get<min_corner, 1>(section.bounding_box) - 1
+ && point_y <= geometry::get<max_corner, 1>(section.bounding_box) + 1)
+ {
+ for (int i = section.begin_index + 1; i <= section.end_index; i++)
+ {
+ point_type const& previous = piece.robust_ring[i - 1];
+ point_type const& current = piece.robust_ring[i];
+
+ analyse_result code = check_segment(previous, current, turn, false);
+ if (code != analyse_continue)
+ {
+ return code;
+ }
+
+ // Get the state (to determine it is within), we don't have
+ // to cover the on-segment case (covered above)
+ strategy.apply(turn.robust_point, previous, current, state);
+ }
+ }
}
- coordinate_type const c2 = dot_product(v, v);
- if (c2 < c1)
+
+ int const code = strategy.result(state);
+ if (code == 1)
{
- return false;
+ return analyse_within;
+ }
+ else if (code == -1)
+ {
+ return analyse_disjoint;
}
- return true;
+ // Should normally not occur - on-segment is covered
+ return analyse_unknown;
}
- template <typename Point, typename Piece>
- inline bool on_offsetted(Point const& point, Piece const& piece) const
+};
+
+class analyse_turn_wrt_piece
+{
+ template <typename Point, typename Turn>
+ static inline analyse_result check_helper_segment(Point const& s1,
+ Point const& s2, Turn const& turn,
+ bool is_original,
+ Point const& offsetted)
{
typedef typename strategy::side::services::default_strategy
<
typename cs_tag<Point>::type
>::type side_strategy;
- geometry::equal_to<Point> comparator;
+
+ switch(side_strategy::apply(s1, s2, turn.robust_point))
+ {
+ case 1 :
+ return analyse_disjoint; // left of segment
+ case 0 :
+ {
+ // If is collinear, either on segment or before/after
+ typedef geometry::model::box<Point> box_type;
+
+ box_type box;
+ geometry::assign_inverse(box);
+ geometry::expand(box, s1);
+ geometry::expand(box, s2);
+
+ if (geometry::covered_by(turn.robust_point, box))
+ {
+ // It is on the segment
+ if (! is_original
+ && geometry::comparable_distance(turn.robust_point, offsetted) <= 1)
+ {
+ // It is close to the offsetted-boundary, take
+ // any rounding-issues into account
+ return analyse_near_offsetted;
+ }
+
+ // Points on helper-segments are considered as within
+ // Points on original boundary are processed differently
+ return is_original
+ ? analyse_on_original_boundary
+ : analyse_within;
+ }
+
+ // It is collinear but not on the segment. Because these
+ // segments are convex, it is outside
+ // Unless the offsetted ring is collinear or concave w.r.t.
+ // helper-segment but that scenario is not yet supported
+ return analyse_disjoint;
+ }
+ break;
+ }
+
+ // right of segment
+ return analyse_continue;
+ }
+
+ template <typename Turn, typename Piece>
+ static inline analyse_result check_helper_segments(Turn const& turn, Piece const& piece)
+ {
+ typedef typename Turn::robust_point_type point_type;
+ geometry::equal_to<point_type> comparator;
+
+ point_type points[4];
+
+ int helper_count = piece.robust_ring.size() - piece.offsetted_count;
+ if (helper_count == 4)
+ {
+ for (int i = 0; i < 4; i++)
+ {
+ points[i] = piece.robust_ring[piece.offsetted_count + i];
+ }
+ }
+ else if (helper_count == 3)
+ {
+ // Triangular piece, assign points but assign second twice
+ for (int i = 0; i < 4; i++)
+ {
+ int index = i < 2 ? i : i - 1;
+ points[i] = piece.robust_ring[piece.offsetted_count + index];
+ }
+ }
+ else
+ {
+ // Some pieces (e.g. around points) do not have helper segments.
+ // Others should have 3 (join) or 4 (side)
+ return analyse_continue;
+ }
+
+ // First check point-equality
+ point_type const& point = turn.robust_point;
+ if (comparator(point, points[0]) || comparator(point, points[3]))
+ {
+ return analyse_on_offsetted;
+ }
+ if (comparator(point, points[1]) || comparator(point, points[2]))
+ {
+ return analyse_on_original_boundary;
+ }
+
+ // Right side of the piece
+ analyse_result result
+ = check_helper_segment(points[0], points[1], turn,
+ false, points[0]);
+ if (result != analyse_continue)
+ {
+ return result;
+ }
+
+ // Left side of the piece
+ result = check_helper_segment(points[2], points[3], turn,
+ false, points[3]);
+ if (result != analyse_continue)
+ {
+ return result;
+ }
+
+ if (! comparator(points[1], points[2]))
+ {
+ // Side of the piece at side of original geometry
+ result = check_helper_segment(points[1], points[2], turn,
+ true, point);
+ if (result != analyse_continue)
+ {
+ return result;
+ }
+ }
+
+ // We are within the \/ or |_| shaped piece, where the top is the
+ // offsetted ring.
+ if (! geometry::covered_by(point, piece.robust_offsetted_envelope))
+ {
+ // Not in offsetted-area. This makes a cheap check possible
+ typedef typename strategy::side::services::default_strategy
+ <
+ typename cs_tag<point_type>::type
+ >::type side_strategy;
+
+ switch(side_strategy::apply(points[3], points[0], point))
+ {
+ case 1 : return analyse_disjoint;
+ case -1 : return analyse_within;
+ case 0 : return analyse_disjoint;
+ }
+ }
+
+ return analyse_continue;
+ }
+
+ template <typename Turn, typename Piece, typename Compare>
+ static inline analyse_result check_monotonic(Turn const& turn, Piece const& piece, Compare const& compare)
+ {
+ typedef typename Piece::piece_robust_ring_type ring_type;
+ typedef typename ring_type::const_iterator it_type;
+ it_type end = piece.robust_ring.begin() + piece.offsetted_count;
+ it_type it = std::lower_bound(piece.robust_ring.begin(),
+ end,
+ turn.robust_point,
+ compare);
+
+ if (it != end
+ && it != piece.robust_ring.begin())
+ {
+ // iterator points to point larger than point
+ // w.r.t. specified direction, and prev points to a point smaller
+ // We now know if it is inside/outside
+ it_type prev = it - 1;
+ return check_segment(*prev, *it, turn, true);
+ }
+ return analyse_continue;
+ }
+
+public :
+ template <typename Turn, typename Piece>
+ static inline analyse_result apply(Turn const& turn, Piece const& piece)
+ {
+ typedef typename Turn::robust_point_type point_type;
+ analyse_result code = check_helper_segments(turn, piece);
+ if (code != analyse_continue)
+ {
+ return code;
+ }
+
+ geometry::equal_to<point_type> comparator;
+
+ if (piece.offsetted_count > 8)
+ {
+ // If the offset contains some points and is monotonic, we try
+ // to avoid walking all points linearly.
+ // We try it only once.
+ if (piece.is_monotonic_increasing[0])
+ {
+ code = check_monotonic(turn, piece, geometry::less<point_type, 0>());
+ if (code != analyse_continue) return code;
+ }
+ else if (piece.is_monotonic_increasing[1])
+ {
+ code = check_monotonic(turn, piece, geometry::less<point_type, 1>());
+ if (code != analyse_continue) return code;
+ }
+ else if (piece.is_monotonic_decreasing[0])
+ {
+ code = check_monotonic(turn, piece, geometry::greater<point_type, 0>());
+ if (code != analyse_continue) return code;
+ }
+ else if (piece.is_monotonic_decreasing[1])
+ {
+ code = check_monotonic(turn, piece, geometry::greater<point_type, 1>());
+ if (code != analyse_continue) return code;
+ }
+ }
+
+ // It is small or not monotonic, walk linearly through offset
+ // TODO: this will be combined with winding strategy
for (int i = 1; i < piece.offsetted_count; i++)
{
- Point const& previous = piece.robust_ring[i - 1];
- Point const& current = piece.robust_ring[i];
+ point_type const& previous = piece.robust_ring[i - 1];
+ point_type const& current = piece.robust_ring[i];
- // The robust ring contains duplicates, avoid applying side on them (will be 0)
+ // The robust ring can contain duplicates
+ // (on which any side or side-value would return 0)
if (! comparator(previous, current))
{
- int const side = side_strategy::apply(previous, current, point);
- if (side == 0)
+ code = check_segment(previous, current, turn, false);
+ if (code != analyse_continue)
{
- // Collinear, check if projection falls on it
- if (projection_on_segment(point, previous, current))
- {
- return true;
- }
+ return code;
}
}
}
- return false;
+
+ return analyse_unknown;
}
- template <typename Point, typename Piece>
- static inline
- calculation_type comparable_distance_from_offsetted(Point const& point,
- Piece const& piece)
+};
+
+
+template <typename Turns, typename Pieces>
+class turn_in_piece_visitor
+{
+ Turns& m_turns; // because partition is currently operating on const input only
+ Pieces const& m_pieces; // to check for piece-type
+
+ template <typename Operation, typename Piece>
+ inline bool skip(Operation const& op, Piece const& piece) const
{
- // TODO: pass subrange to dispatch to avoid making copy
- geometry::model::linestring<Point> ls;
- std::copy(piece.robust_ring.begin(),
- piece.robust_ring.begin() + piece.offsetted_count,
- std::back_inserter(ls));
- typename default_comparable_distance_result<Point, Point>::type
- const comp = geometry::comparable_distance(point, ls);
-
- return static_cast<calculation_type>(comp);
+ if (op.piece_index == piece.index)
+ {
+ return true;
+ }
+ Piece const& pc = m_pieces[op.piece_index];
+ if (pc.left_index == piece.index || pc.right_index == piece.index)
+ {
+ if (pc.type == strategy::buffer::buffered_flat_end)
+ {
+ // If it is a flat end, don't compare against its neighbor:
+ // it will always be located on one of the helper segments
+ return true;
+ }
+ if (pc.type == strategy::buffer::buffered_concave)
+ {
+ // If it is concave, the same applies: the IP will be
+ // located on one of the helper segments
+ return true;
+ }
+ }
+
+ return false;
}
+
public:
inline turn_in_piece_visitor(Turns& turns, Pieces const& pieces)
@@ -157,81 +514,55 @@ public:
if (piece.type == strategy::buffer::buffered_flat_end
|| piece.type == strategy::buffer::buffered_concave)
{
- // Turns cannot be inside a flat end (though they can be on border)
- // Neither we need to check if they are inside concave helper pieces
+ // Turns cannot be located within flat-end or concave pieces
return;
}
- bool neighbour = false;
- for (int i = 0; i < 2; i++)
- {
- // Don't compare against one of the two source-pieces
- if (turn.operations[i].piece_index == piece.index)
- {
- return;
- }
-
- typename boost::range_value<Pieces>::type const& pc
- = m_pieces[turn.operations[i].piece_index];
- if (pc.left_index == piece.index
- || pc.right_index == piece.index)
- {
- if (pc.type == strategy::buffer::buffered_flat_end)
- {
- // If it is a flat end, don't compare against its neighbor:
- // it will always be located on one of the helper segments
- return;
- }
- neighbour = true;
- }
- }
-
- int geometry_code = detail::within::point_in_geometry(turn.robust_point, piece.robust_ring);
-
- if (geometry_code == -1)
+ if (! geometry::covered_by(turn.robust_point, piece.robust_envelope))
{
+ // Easy check: if the turn is not in the envelope, we can safely return
return;
}
- if (geometry_code == 0 && neighbour)
+
+ if (skip(turn.operations[0], piece) || skip(turn.operations[1], piece))
{
return;
}
+ // TODO: mutable_piece to make some on-demand preparations in analyse
+ analyse_result analyse_code =
+ piece.type == geometry::strategy::buffer::buffered_point
+ ? analyse_turn_wrt_point_piece::apply(turn, piece)
+ : analyse_turn_wrt_piece::apply(turn, piece);
+
Turn& mutable_turn = m_turns[turn.turn_index];
- if (geometry_code == 0)
+ switch(analyse_code)
{
- // If it is on the border and they are not neighbours, it should be
- // on the offsetted ring
-
- if (! on_offsetted(turn.robust_point, piece))
- {
- // It is on the border but not on the offsetted ring.
- // Then it is somewhere on the helper-segments
- // Classify it as "within"
- geometry_code = 1;
- mutable_turn.count_on_helper++; // can still become "near_offsetted"
- }
- else
- {
+ case analyse_disjoint :
+ return;
+ case analyse_on_offsetted :
mutable_turn.count_on_offsetted++; // value is not used anymore
- }
+ return;
+ case analyse_on_original_boundary :
+ mutable_turn.count_on_original_boundary++;
+ return;
+ case analyse_within :
+ mutable_turn.count_within++;
+ return;
+ case analyse_near_offsetted :
+ mutable_turn.count_within_near_offsetted++;
+ return;
+ default :
+ break;
}
+ // TODO: this point_in_geometry is a performance-bottleneck here and
+ // will be replaced completely by extending analyse_piece functionality
+ int geometry_code = detail::within::point_in_geometry(turn.robust_point, piece.robust_ring);
+
if (geometry_code == 1)
{
- calculation_type const distance
- = comparable_distance_from_offsetted(turn.robust_point, piece);
- if (distance >= 4)
- {
- // This is too far from the border, it counts as "really within"
- mutable_turn.count_within++;
- }
- else
- {
- // Other points count as still "on border" because they might be
- // travelled through, but not used as starting point
- mutable_turn.count_within_near_offsetted++;
- }
+ mutable_turn.count_within++;
}
}
};