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