• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 
3 // Copyright (c) 2020 Barend Gehrels, Amsterdam, the Netherlands.
4 
5 // Use, modification and distribution is subject to the Boost Software License,
6 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
7 // http://www.boost.org/LICENSE_1_0.txt)
8 
9 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_PIECE_BORDER_HPP
10 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_PIECE_BORDER_HPP
11 
12 
13 #include <boost/array.hpp>
14 #include <boost/core/addressof.hpp>
15 
16 #include <boost/geometry/core/assert.hpp>
17 #include <boost/geometry/core/config.hpp>
18 
19 #include <boost/geometry/algorithms/assign.hpp>
20 #include <boost/geometry/algorithms/comparable_distance.hpp>
21 #include <boost/geometry/algorithms/equals.hpp>
22 #include <boost/geometry/algorithms/expand.hpp>
23 #include <boost/geometry/algorithms/detail/buffer/buffer_box.hpp>
24 #include <boost/geometry/algorithms/detail/buffer/buffer_policies.hpp>
25 #include <boost/geometry/algorithms/detail/expand_by_epsilon.hpp>
26 #include <boost/geometry/strategies/cartesian/turn_in_ring_winding.hpp>
27 #include <boost/geometry/geometries/box.hpp>
28 #include <boost/geometry/geometries/segment.hpp>
29 
30 
31 namespace boost { namespace geometry
32 {
33 
34 #ifndef DOXYGEN_NO_DETAIL
35 namespace detail
36 {
37 template <typename It, typename T, typename Compare>
get_range_around(It begin,It end,T const & value,Compare const & compare,It & lower,It & upper)38 inline bool get_range_around(It begin, It end, T const& value, Compare const& compare, It& lower, It& upper)
39 {
40     lower = end;
41     upper = end;
42 
43     // Get first element not smaller than value
44     if (begin == end)
45     {
46         return false;
47     }
48     if (compare(value, *begin))
49     {
50         // The value is smaller than the first item, therefore not in range
51         return false;
52     }
53     // *(begin + std::distance(begin, end) - 1))
54     if (compare(*(end - 1), value))
55     {
56         // The last item is larger than the value, therefore not in range
57         return false;
58     }
59 
60     // Assign the iterators.
61     // lower >= begin and lower < end
62     // upper > lower and upper <= end
63     // lower_bound points to first element NOT LESS than value - but because
64     // we want the first value LESS than value, we decrease it
65     lower = std::lower_bound(begin, end, value, compare);
66     // upper_bound points to first element of which value is LESS
67     upper = std::upper_bound(begin, end, value, compare);
68 
69     if (lower != begin)
70     {
71         --lower;
72     }
73     if (upper != end)
74     {
75         ++upper;
76     }
77     return true;
78 }
79 
80 }
81 
82 
83 namespace detail { namespace buffer
84 {
85 
86 //! Contains the border of the piece, consisting of 4 parts:
87 //! 1: the part of the offsetted ring (referenced, not copied)
88 //! 2: the part of the original (one or two points)
89 //! 3: the left part (from original to offsetted)
90 //! 4: the right part (from offsetted to original)
91 //! Besides that, it contains some properties of the piece(border);
92 //!   - convexity
93 //!   - envelope
94 //!   - monotonicity of the offsetted ring
95 //!   - min/max radius of a point buffer
96 //!   - if it is a "reversed" piece (linear features with partly negative buffers)
97 template <typename Ring, typename Point>
98 struct piece_border
99 {
100     typedef typename geometry::coordinate_type<Point>::type coordinate_type;
101     typedef typename default_comparable_distance_result<Point>::type radius_type;
102     typedef typename geometry::strategy::buffer::turn_in_ring_winding<coordinate_type>::state_type state_type;
103 
104     bool m_reversed;
105 
106     // Points from the offsetted ring. They are not copied, this structure
107     // refers to those points
108     Ring const* m_ring;
109     std::size_t m_begin;
110     std::size_t m_end;
111 
112     // Points from the original (one or two, depending on piece shape)
113     // Note, if there are 2 points, they are REVERSED w.r.t. the original
114     // Therefore here we can walk in its order.
115     boost::array<Point, 2> m_originals;
116     std::size_t m_original_size;
117 
118     geometry::model::box<Point> m_envelope;
119     bool m_has_envelope;
120 
121     // True if piece is determined as "convex"
122     bool m_is_convex;
123 
124     // True if offsetted part is monotonically changing in x-direction
125     bool m_is_monotonic_increasing;
126     bool m_is_monotonic_decreasing;
127 
128     radius_type m_min_comparable_radius;
129     radius_type m_max_comparable_radius;
130 
piece_borderboost::geometry::detail::buffer::piece_border131     piece_border()
132         : m_reversed(false)
133         , m_ring(NULL)
134         , m_begin(0)
135         , m_end(0)
136         , m_original_size(0)
137         , m_has_envelope(false)
138         , m_is_convex(false)
139         , m_is_monotonic_increasing(false)
140         , m_is_monotonic_decreasing(false)
141         , m_min_comparable_radius(0)
142         , m_max_comparable_radius(0)
143     {
144     }
145 
146     // Only used for debugging (SVG)
get_full_ringboost::geometry::detail::buffer::piece_border147     Ring get_full_ring() const
148     {
149         Ring result;
150         if (ring_or_original_empty())
151         {
152             return result;
153         }
154         std::copy(m_ring->begin() + m_begin,
155                   m_ring->begin() + m_end,
156                   std::back_inserter(result));
157         std::copy(m_originals.begin(),
158                   m_originals.begin() + m_original_size,
159                   std::back_inserter(result));
160         // Add the closing point
161         result.push_back(*(m_ring->begin() + m_begin));
162 
163         return result;
164     }
165 
get_properties_of_borderboost::geometry::detail::buffer::piece_border166     void get_properties_of_border(bool is_point_buffer, Point const& center)
167     {
168         m_has_envelope = calculate_envelope(m_envelope);
169         if (m_has_envelope)
170         {
171             // Take roundings into account, enlarge box
172             geometry::detail::expand_by_epsilon(m_envelope);
173         }
174         if (! ring_or_original_empty() && is_point_buffer)
175         {
176             // Determine min/max radius
177             calculate_radii(center, m_ring->begin() + m_begin, m_ring->begin() + m_end);
178         }
179     }
180 
181     template <typename SideStrategy>
get_properties_of_offsetted_ring_partboost::geometry::detail::buffer::piece_border182     void get_properties_of_offsetted_ring_part(SideStrategy const& strategy)
183     {
184         if (! ring_or_original_empty())
185         {
186             m_is_convex = is_convex(strategy);
187             check_monotonicity(m_ring->begin() + m_begin, m_ring->begin() + m_end);
188         }
189     }
190 
set_offsettedboost::geometry::detail::buffer::piece_border191     void set_offsetted(Ring const& ring, std::size_t begin, std::size_t end)
192     {
193         BOOST_GEOMETRY_ASSERT(begin <= end);
194         BOOST_GEOMETRY_ASSERT(begin < boost::size(ring));
195         BOOST_GEOMETRY_ASSERT(end <= boost::size(ring));
196 
197         m_ring = boost::addressof(ring);
198         m_begin = begin;
199         m_end = end;
200     }
201 
add_original_pointboost::geometry::detail::buffer::piece_border202     void add_original_point(Point const& point)
203     {
204         BOOST_GEOMETRY_ASSERT(m_original_size < 2);
205         m_originals[m_original_size++] = point;
206     }
207 
208     template <typename Box>
calculate_envelopeboost::geometry::detail::buffer::piece_border209     bool calculate_envelope(Box& envelope) const
210     {
211         geometry::assign_inverse(envelope);
212         if (ring_or_original_empty())
213         {
214             return false;
215         }
216         expand_envelope(envelope, m_ring->begin() + m_begin, m_ring->begin() + m_end);
217         expand_envelope(envelope, m_originals.begin(), m_originals.begin() + m_original_size);
218         return true;
219     }
220 
221 
222     // Whatever the return value, the state should be checked.
223     template <typename TurnPoint, typename State>
point_on_pieceboost::geometry::detail::buffer::piece_border224     bool point_on_piece(TurnPoint const& point,
225                         bool one_sided, bool is_linear_end_point,
226                         State& state) const
227     {
228         if (ring_or_original_empty())
229         {
230             return false;
231         }
232 
233         // Walk over the different parts of the ring, in clockwise order
234         // For performance reasons: start with the helper part (one segment)
235         // then the original part (one segment, if any), then the other helper
236         // part (one segment), and only then the offsetted part
237         // (probably more segments, check monotonicity)
238         geometry::strategy::buffer::turn_in_ring_winding<coordinate_type> tir;
239 
240         Point const offsetted_front = *(m_ring->begin() + m_begin);
241         Point const offsetted_back = *(m_ring->begin() + m_end - 1);
242 
243         // For onesided buffers, or turns colocated with linear end points,
244         // the place on the ring is changed to offsetted (because of colocation)
245         geometry::strategy::buffer::place_on_ring_type const por_original
246             = adapted_place_on_ring(geometry::strategy::buffer::place_on_ring_original,
247                                     one_sided, is_linear_end_point);
248         geometry::strategy::buffer::place_on_ring_type const por_from_offsetted
249             = adapted_place_on_ring(geometry::strategy::buffer::place_on_ring_from_offsetted,
250                                     one_sided, is_linear_end_point);
251         geometry::strategy::buffer::place_on_ring_type const por_to_offsetted
252             = adapted_place_on_ring(geometry::strategy::buffer::place_on_ring_to_offsetted,
253                                     one_sided, is_linear_end_point);
254 
255         bool continue_processing = true;
256         if (m_original_size == 1)
257         {
258             // One point. Walk from last offsetted to point, and from point to first offsetted
259             continue_processing = step(point, offsetted_back, m_originals[0], tir, por_from_offsetted, state)
260                 && step(point, m_originals[0], offsetted_front, tir, por_to_offsetted, state);
261         }
262         else if (m_original_size == 2)
263         {
264             // Two original points. Walk from last offsetted point to first original point,
265             // then along original, then from second oginal to first offsetted point
266             continue_processing = step(point, offsetted_back, m_originals[0], tir, por_from_offsetted, state)
267                     && step(point, m_originals[0], m_originals[1], tir, por_original, state)
268                     && step(point, m_originals[1], offsetted_front, tir, por_to_offsetted, state);
269         }
270 
271         if (continue_processing)
272         {
273             // Check the offsetted ring (in rounded joins, these might be
274             // several segments)
275             walk_offsetted(point, m_ring->begin() + m_begin, m_ring->begin() + m_end,
276                            tir, state);
277         }
278 
279         return true;
280     }
281 
282     //! Returns true if empty (no ring, or no points, or no original)
ring_or_original_emptyboost::geometry::detail::buffer::piece_border283     bool ring_or_original_empty() const
284     {
285         return m_ring == NULL || m_begin >= m_end || m_original_size == 0;
286     }
287 
288 private :
289 
290     static geometry::strategy::buffer::place_on_ring_type
adapted_place_on_ringboost::geometry::detail::buffer::piece_border291         adapted_place_on_ring(geometry::strategy::buffer::place_on_ring_type target,
292                               bool one_sided, bool is_linear_end_point)
293     {
294         return one_sided || is_linear_end_point
295                ? geometry::strategy::buffer::place_on_ring_offsetted
296                : target;
297     }
298 
299     template <typename TurnPoint, typename Iterator, typename Strategy, typename State>
walk_offsettedboost::geometry::detail::buffer::piece_border300     bool walk_offsetted(TurnPoint const& point, Iterator begin, Iterator end, Strategy const & strategy, State& state) const
301     {
302         Iterator it = begin;
303         Iterator beyond = end;
304 
305         // Move iterators if the offsetted ring is monotonic increasing or decreasing
306         if (m_is_monotonic_increasing)
307         {
308             if (! get_range_around(begin, end, point, geometry::less<Point, 0>(), it, beyond))
309             {
310                 return true;
311             }
312         }
313         else if (m_is_monotonic_decreasing)
314         {
315             if (! get_range_around(begin, end, point, geometry::greater<Point, 0>(), it, beyond))
316             {
317                 return true;
318             }
319         }
320 
321         for (Iterator previous = it++ ; it != beyond ; ++previous, ++it )
322         {
323             if (! step(point, *previous, *it, strategy,
324                  geometry::strategy::buffer::place_on_ring_offsetted, state))
325             {
326                 return false;
327             }
328         }
329         return true;
330     }
331 
332     template <typename TurnPoint, typename Strategy, typename State>
stepboost::geometry::detail::buffer::piece_border333     bool step(TurnPoint const& point, Point const& p1, Point const& p2, Strategy const & strategy,
334               geometry::strategy::buffer::place_on_ring_type place_on_ring, State& state) const
335     {
336         // A step between original/offsetted ring is always convex
337         // (unless the join strategy generates points left of it -
338         //  future: convexity might be added to the buffer-join-strategy)
339         // Therefore, if the state count > 0, it means the point is left of it,
340         // and because it is convex, we can stop
341 
342         typedef typename geometry::coordinate_type<Point>::type coordinate_type;
343         typedef geometry::detail::distance_measure<coordinate_type> dm_type;
344         dm_type const dm = geometry::detail::get_distance_measure(point, p1, p2);
345         if (m_is_convex && dm.measure > 0)
346         {
347             // The point is left of this segment of a convex piece
348             state.m_count = 0;
349             return false;
350         }
351         // Call strategy, and if it is on the border, return false
352         // to stop further processing.
353         return strategy.apply(point, p1, p2, dm, place_on_ring, state);
354     }
355 
356     template <typename It, typename Box>
expand_envelopeboost::geometry::detail::buffer::piece_border357     void expand_envelope(Box& envelope, It begin, It end) const
358     {
359         typedef typename strategy::expand::services::default_strategy
360             <
361                 point_tag, typename cs_tag<Box>::type
362             >::type expand_strategy_type;
363 
364         for (It it = begin; it != end; ++it)
365         {
366             geometry::expand(envelope, *it, expand_strategy_type());
367         }
368     }
369 
370     template <typename SideStrategy>
is_convexboost::geometry::detail::buffer::piece_border371     bool is_convex(SideStrategy const& strategy) const
372     {
373         if (ring_or_original_empty())
374         {
375             // Convexity is undetermined, and for this case it does not matter,
376             // because it is only used for optimization in point_on_piece,
377             // but that is not called if the piece border is not valid
378             return false;
379         }
380 
381         if (m_end - m_begin <= 2)
382         {
383             // The offsetted ring part of this piece has only two points.
384             // If this is true, and the original ring part has only one point,
385             // a triangle and it is convex. If the original ring part has two
386             // points, it is a rectangle and theoretically could be concave,
387             // but because of the way the buffer is generated, that is never
388             // the case.
389             return true;
390         }
391 
392         // The offsetted ring part of thie piece has at least three points
393         // (this is often the case in a piece marked as "join")
394 
395         // We can assume all points of the offset ring are different, and also
396         // that all points on the original are different, and that the offsetted
397         // ring is different from the original(s)
398         Point const offsetted_front = *(m_ring->begin() + m_begin);
399         Point const offsetted_second = *(m_ring->begin() + m_begin + 1);
400 
401         // These two points will be reassigned in every is_convex call
402         Point previous = offsetted_front;
403         Point current = offsetted_second;
404 
405         // Verify the offsetted range (from the second point on), the original,
406         // and loop through the first two points of the offsetted range
407         bool const result = is_convex(previous, current, m_ring->begin() + m_begin + 2, m_ring->begin() + m_end, strategy)
408             && is_convex(previous, current, m_originals.begin(), m_originals.begin() + m_original_size, strategy)
409             && is_convex(previous, current, offsetted_front, strategy)
410             && is_convex(previous, current, offsetted_second, strategy);
411 
412         return result;
413     }
414 
415     template <typename It, typename SideStrategy>
is_convexboost::geometry::detail::buffer::piece_border416     bool is_convex(Point& previous, Point& current, It begin, It end, SideStrategy const& strategy) const
417     {
418         for (It it = begin; it != end; ++it)
419         {
420             if (! is_convex(previous, current, *it, strategy))
421             {
422                 return false;
423             }
424         }
425         return true;
426     }
427 
428     template <typename SideStrategy>
is_convexboost::geometry::detail::buffer::piece_border429     bool is_convex(Point& previous, Point& current, Point const& next, SideStrategy const& strategy) const
430     {
431         typename SideStrategy::equals_point_point_strategy_type const
432             eq_pp_strategy = strategy.get_equals_point_point_strategy();
433 
434         int const side = strategy.apply(previous, current, next);
435         if (side == 1)
436         {
437             // Next is on the left side of clockwise ring: piece is not convex
438             return false;
439         }
440         if (! equals::equals_point_point(current, next, eq_pp_strategy))
441         {
442             previous = current;
443             current = next;
444         }
445         return true;
446     }
447 
448     template <int Direction>
step_for_monotonicityboost::geometry::detail::buffer::piece_border449     inline void step_for_monotonicity(Point const& current, Point const& next)
450     {
451         if (geometry::get<Direction>(current) >= geometry::get<Direction>(next))
452         {
453             m_is_monotonic_increasing = false;
454         }
455         if (geometry::get<Direction>(current) <= geometry::get<Direction>(next))
456         {
457             m_is_monotonic_decreasing = false;
458         }
459     }
460 
461     template <typename It>
check_monotonicityboost::geometry::detail::buffer::piece_border462     void check_monotonicity(It begin, It end)
463     {
464         m_is_monotonic_increasing = true;
465         m_is_monotonic_decreasing = true;
466 
467         if (begin == end || begin + 1 == end)
468         {
469             return;
470         }
471 
472         It it = begin;
473         for (It previous = it++; it != end; ++previous, ++it)
474         {
475             step_for_monotonicity<0>(*previous, *it);
476         }
477     }
478 
479     template <typename It>
calculate_radiiboost::geometry::detail::buffer::piece_border480     inline void calculate_radii(Point const& center, It begin, It end)
481     {
482         typedef geometry::model::referring_segment<Point const> segment_type;
483 
484         bool first = true;
485 
486         // An offsetted point-buffer ring around a point is supposed to be closed,
487         // therefore walking from start to end is fine.
488         It it = begin;
489         for (It previous = it++; it != end; ++previous, ++it)
490         {
491             segment_type const s(*previous, *it);
492             radius_type const d = geometry::comparable_distance(center, s);
493 
494             if (first || d < m_min_comparable_radius)
495             {
496                 m_min_comparable_radius = d;
497             }
498             if (first || d > m_max_comparable_radius)
499             {
500                 m_max_comparable_radius = d;
501             }
502             first = false;
503         }
504     }
505 
506 };
507 
508 }} // namespace detail::buffer
509 #endif // DOXYGEN_NO_DETAIL
510 
511 
512 }} // namespace boost::geometry
513 
514 #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_BUFFER_PIECE_BORDER_HPP
515