1 // Boost.Geometry (aka GGL, Generic Geometry Library)
2 
3 // Copyright (c) 2015 Barend Gehrels, Amsterdam, the Netherlands.
4 
5 // This file was modified by Oracle on 2017.
6 // Modifications copyright (c) 2017 Oracle and/or its affiliates.
7 
8 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle
9 
10 // Use, modification and distribution is subject to the Boost Software License,
11 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
12 // http://www.boost.org/LICENSE_1_0.txt)
13 
14 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP
15 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP
16 
17 #include <algorithm>
18 #include <map>
19 #include <vector>
20 
21 #include <boost/geometry/algorithms/num_points.hpp>
22 #include <boost/geometry/algorithms/detail/overlay/copy_segment_point.hpp>
23 #include <boost/geometry/algorithms/detail/overlay/get_ring.hpp>
24 #include <boost/geometry/algorithms/detail/direction_code.hpp>
25 #include <boost/geometry/algorithms/detail/overlay/turn_info.hpp>
26 
27 namespace boost { namespace geometry
28 {
29 
30 #ifndef DOXYGEN_NO_DETAIL
31 namespace detail { namespace overlay { namespace sort_by_side
32 {
33 
34 enum direction_type { dir_unknown = -1, dir_from = 0, dir_to = 1 };
35 
36 // Point-wrapper, adding some properties
37 template <typename Point>
38 struct ranked_point
39 {
ranked_pointboost::geometry::detail::overlay::sort_by_side::ranked_point40     ranked_point()
41         : rank(0)
42         , turn_index(-1)
43         , operation_index(-1)
44         , direction(dir_unknown)
45         , count_left(0)
46         , count_right(0)
47         , operation(operation_none)
48     {}
49 
50     template <typename Op>
ranked_pointboost::geometry::detail::overlay::sort_by_side::ranked_point51     ranked_point(const Point& p, signed_size_type ti, int oi,
52                  direction_type d, Op op)
53         : point(p)
54         , rank(0)
55         , zone(-1)
56         , turn_index(ti)
57         , operation_index(oi)
58         , direction(d)
59         , count_left(0)
60         , count_right(0)
61         , operation(op.operation)
62         , seg_id(op.seg_id)
63     {}
64 
65     Point point;
66     std::size_t rank;
67     signed_size_type zone; // index of closed zone, in uu turn there would be 2 zones
68     signed_size_type turn_index;
69     int operation_index; // 0,1
70     direction_type direction;
71     std::size_t count_left;
72     std::size_t count_right;
73     operation_type operation;
74     segment_identifier seg_id;
75 };
76 
77 struct less_by_turn_index
78 {
79     template <typename T>
operator ()boost::geometry::detail::overlay::sort_by_side::less_by_turn_index80     inline bool operator()(const T& first, const T& second) const
81     {
82         return first.turn_index == second.turn_index
83             ? first.index < second.index
84             : first.turn_index < second.turn_index
85             ;
86     }
87 };
88 
89 struct less_by_index
90 {
91     template <typename T>
operator ()boost::geometry::detail::overlay::sort_by_side::less_by_index92     inline bool operator()(const T& first, const T& second) const
93     {
94         // First order by from/to
95         if (first.direction != second.direction)
96         {
97             return first.direction < second.direction;
98         }
99         // All the same, order by turn index (we might consider length too)
100         return first.turn_index < second.turn_index;
101     }
102 };
103 
104 struct less_false
105 {
106     template <typename T>
operator ()boost::geometry::detail::overlay::sort_by_side::less_false107     inline bool operator()(const T&, const T& ) const
108     {
109         return false;
110     }
111 };
112 
113 template <typename Point, typename SideStrategy, typename LessOnSame, typename Compare>
114 struct less_by_side
115 {
less_by_sideboost::geometry::detail::overlay::sort_by_side::less_by_side116     less_by_side(const Point& p1, const Point& p2, SideStrategy const& strategy)
117         : m_p1(p1)
118         , m_p2(p2)
119         , m_strategy(strategy)
120     {}
121 
122     template <typename T>
operator ()boost::geometry::detail::overlay::sort_by_side::less_by_side123     inline bool operator()(const T& first, const T& second) const
124     {
125         LessOnSame on_same;
126         Compare compare;
127 
128         int const side_first = m_strategy.apply(m_p1, m_p2, first.point);
129         int const side_second = m_strategy.apply(m_p1, m_p2, second.point);
130 
131         if (side_first == 0 && side_second == 0)
132         {
133             // Both collinear. They might point into different directions: <------*------>
134             // If so, order the one going backwards as the very first.
135 
136             int const first_code = direction_code(m_p1, m_p2, first.point);
137             int const second_code = direction_code(m_p1, m_p2, second.point);
138 
139             // Order by code, backwards first, then forward.
140             return first_code != second_code
141                 ? first_code < second_code
142                 : on_same(first, second)
143                 ;
144         }
145         else if (side_first == 0
146                 && direction_code(m_p1, m_p2, first.point) == -1)
147         {
148             // First collinear and going backwards.
149             // Order as the very first, so return always true
150             return true;
151         }
152         else if (side_second == 0
153             && direction_code(m_p1, m_p2, second.point) == -1)
154         {
155             // Second is collinear and going backwards
156             // Order as very last, so return always false
157             return false;
158         }
159 
160         // They are not both collinear
161 
162         if (side_first != side_second)
163         {
164             return compare(side_first, side_second);
165         }
166 
167         // They are both left, both right, and/or both collinear (with each other and/or with p1,p2)
168         // Check mutual side
169         int const side_second_wrt_first = m_strategy.apply(m_p2, first.point, second.point);
170 
171         if (side_second_wrt_first == 0)
172         {
173             return on_same(first, second);
174         }
175 
176         int const side_first_wrt_second = -side_second_wrt_first;
177 
178         // Both are on same side, and not collinear
179         // Union: return true if second is right w.r.t. first, so -1,
180         // so other is 1. union has greater as compare functor
181         // Intersection: v.v.
182         return compare(side_first_wrt_second, side_second_wrt_first);
183     }
184 
185 private :
186     Point m_p1, m_p2;
187     SideStrategy const& m_strategy;
188 };
189 
190 // Sorts vectors in counter clockwise order (by default)
191 template
192 <
193     bool Reverse1,
194     bool Reverse2,
195     overlay_type OverlayType,
196     typename Point,
197     typename SideStrategy,
198     typename Compare
199 >
200 struct side_sorter
201 {
202     typedef ranked_point<Point> rp;
203 
204 private :
205     struct include_union
206     {
operator ()boost::geometry::detail::overlay::sort_by_side::side_sorter::include_union207         inline bool operator()(rp const& ranked_point) const
208         {
209             // New candidate if there are no polygons on left side,
210             // but there are on right side
211             return ranked_point.count_left == 0
212                 && ranked_point.count_right > 0;
213         }
214     };
215 
216     struct include_intersection
217     {
operator ()boost::geometry::detail::overlay::sort_by_side::side_sorter::include_intersection218         inline bool operator()(rp const& ranked_point) const
219         {
220             // New candidate if there are two polygons on right side,
221             // and less on the left side
222             return ranked_point.count_left < 2
223                 && ranked_point.count_right >= 2;
224         }
225     };
226 
227 public :
side_sorterboost::geometry::detail::overlay::sort_by_side::side_sorter228     side_sorter(SideStrategy const& strategy)
229         : m_origin_count(0)
230         , m_origin_segment_distance(0)
231         , m_strategy(strategy)
232     {}
233 
234     template <typename Operation, typename Geometry1, typename Geometry2>
addboost::geometry::detail::overlay::sort_by_side::side_sorter235     Point add(Operation const& op, signed_size_type turn_index, signed_size_type op_index,
236             Geometry1 const& geometry1,
237             Geometry2 const& geometry2,
238             bool is_origin)
239     {
240         Point point1, point2, point3;
241         geometry::copy_segment_points<Reverse1, Reverse2>(geometry1, geometry2,
242                 op.seg_id, point1, point2, point3);
243         Point const& point_to = op.fraction.is_one() ? point3 : point2;
244 
245         m_ranked_points.push_back(rp(point1, turn_index, op_index, dir_from, op));
246         m_ranked_points.push_back(rp(point_to, turn_index, op_index, dir_to, op));
247         if (is_origin)
248         {
249             m_origin = point1;
250             m_origin_count++;
251         }
252         return point1;
253     }
254 
255     template <typename Operation, typename Geometry1, typename Geometry2>
addboost::geometry::detail::overlay::sort_by_side::side_sorter256     void add(Operation const& op, signed_size_type turn_index, signed_size_type op_index,
257             segment_identifier const& departure_seg_id,
258             Geometry1 const& geometry1,
259             Geometry2 const& geometry2,
260             bool check_origin)
261     {
262         Point const point1 = add(op, turn_index, op_index, geometry1, geometry2, false);
263 
264         if (check_origin)
265         {
266             bool const is_origin
267                     = op.seg_id.source_index == departure_seg_id.source_index
268                     && op.seg_id.ring_index == departure_seg_id.ring_index
269                     && op.seg_id.multi_index == departure_seg_id.multi_index;
270 
271             if (is_origin)
272             {
273                 int const segment_distance = calculate_segment_distance(op, departure_seg_id, geometry1, geometry2);
274                 if (m_origin_count == 0 ||
275                         segment_distance < m_origin_segment_distance)
276                 {
277                     m_origin = point1;
278                     m_origin_segment_distance = segment_distance;
279                 }
280                 m_origin_count++;
281             }
282         }
283     }
284 
285     template <typename Operation, typename Geometry1, typename Geometry2>
calculate_segment_distanceboost::geometry::detail::overlay::sort_by_side::side_sorter286     static int calculate_segment_distance(Operation const& op,
287             segment_identifier const& departure_seg_id,
288             Geometry1 const& geometry1,
289             Geometry2 const& geometry2)
290     {
291         if (op.seg_id.segment_index >= departure_seg_id.segment_index)
292         {
293             return op.seg_id.segment_index - departure_seg_id.segment_index;
294         }
295         // Take wrap into account
296         // Suppose ring_count=10 (10 points, 9 segments), dep.seg_id=7, op.seg_id=2, then distance=10-9+2
297         // Generic function (is this used somewhere else too?)
298         ring_identifier const rid(op.seg_id.source_index, op.seg_id.multi_index, op.seg_id.ring_index);
299         int const segment_count
300                     (op.seg_id.source_index == 0
301                     ? geometry::num_points(detail::overlay::get_ring<typename geometry::tag<Geometry1>::type>::apply(rid, geometry1))
302                     : geometry::num_points(detail::overlay::get_ring<typename geometry::tag<Geometry2>::type>::apply(rid, geometry2)));
303         return ((segment_count - 1) - departure_seg_id.segment_index) + op.seg_id.segment_index;
304     }
305 
applyboost::geometry::detail::overlay::sort_by_side::side_sorter306     void apply(Point const& turn_point)
307     {
308         // We need three compare functors:
309         // 1) to order clockwise (union) or counter clockwise (intersection)
310         // 2) to order by side, resulting in unique ranks for all points
311         // 3) to order by side, resulting in non-unique ranks
312         //    to give colinear points
313 
314         // Sort by side and assign rank
315         less_by_side<Point, SideStrategy, less_by_index, Compare> less_unique(m_origin, turn_point, m_strategy);
316         less_by_side<Point, SideStrategy, less_false, Compare> less_non_unique(m_origin, turn_point, m_strategy);
317 
318         std::sort(m_ranked_points.begin(), m_ranked_points.end(), less_unique);
319 
320         std::size_t colinear_rank = 0;
321         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
322         {
323             if (i > 0
324                 && less_non_unique(m_ranked_points[i - 1], m_ranked_points[i]))
325             {
326                 // It is not collinear
327                 colinear_rank++;
328             }
329 
330             m_ranked_points[i].rank = colinear_rank;
331         }
332     }
333 
334     template <signed_size_type segment_identifier::*Member, typename Map>
find_open_genericboost::geometry::detail::overlay::sort_by_side::side_sorter335     void find_open_generic(Map& handled, bool check)
336     {
337         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
338         {
339             const rp& ranked = m_ranked_points[i];
340             if (ranked.direction != dir_from)
341             {
342                 continue;
343             }
344 
345             signed_size_type const& index = ranked.seg_id.*Member;
346             if (check && (index < 0 || index > 1))
347             {
348                 // Should not occur
349                 continue;
350             }
351             if (! handled[index])
352             {
353                 find_polygons_for_source<Member>(index, i);
354                 handled[index] = true;
355             }
356         }
357     }
358 
find_openboost::geometry::detail::overlay::sort_by_side::side_sorter359     void find_open()
360     {
361         if (OverlayType == overlay_buffer)
362         {
363             // For buffers, use piece index
364             std::map<signed_size_type, bool> handled;
365             find_open_generic
366                 <
367                     &segment_identifier::piece_index
368                 >(handled, false);
369         }
370         else
371         {
372             // For other operations, by source (there should only source 0,1)
373             bool handled[2] = {false, false};
374             find_open_generic
375                 <
376                     &segment_identifier::source_index
377                 >(handled, true);
378         }
379     }
380 
reverseboost::geometry::detail::overlay::sort_by_side::side_sorter381     void reverse()
382     {
383         if (m_ranked_points.empty())
384         {
385             return;
386         }
387 
388         std::size_t const last = 1 + m_ranked_points.back().rank;
389 
390         // Move iterator after rank==0
391         bool has_first = false;
392         typename container_type::iterator it = m_ranked_points.begin() + 1;
393         for (; it != m_ranked_points.end() && it->rank == 0; ++it)
394         {
395             has_first = true;
396         }
397 
398         if (has_first)
399         {
400             // Reverse first part (having rank == 0), if any,
401             // but skip the very first row
402             std::reverse(m_ranked_points.begin() + 1, it);
403             for (typename container_type::iterator fit = m_ranked_points.begin();
404                  fit != it; ++fit)
405             {
406                 BOOST_ASSERT(fit->rank == 0);
407             }
408         }
409 
410         // Reverse the rest (main rank > 0)
411         std::reverse(it, m_ranked_points.end());
412         for (; it != m_ranked_points.end(); ++it)
413         {
414             BOOST_ASSERT(it->rank > 0);
415             it->rank = last - it->rank;
416         }
417     }
418 
has_originboost::geometry::detail::overlay::sort_by_side::side_sorter419     bool has_origin() const
420     {
421         return m_origin_count > 0;
422     }
423 
424 //private :
425 
426     typedef std::vector<rp> container_type;
427     container_type m_ranked_points;
428     Point m_origin;
429     std::size_t m_origin_count;
430     int m_origin_segment_distance;
431     SideStrategy m_strategy;
432 
433 private :
434 
435     //! Check how many open spaces there are
436     template <typename Include>
open_countboost::geometry::detail::overlay::sort_by_side::side_sorter437     inline std::size_t open_count(Include const& include_functor) const
438     {
439         std::size_t result = 0;
440         std::size_t last_rank = 0;
441         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
442         {
443             rp const& ranked_point = m_ranked_points[i];
444 
445             if (ranked_point.rank > last_rank
446                 && ranked_point.direction == sort_by_side::dir_to
447                 && include_functor(ranked_point))
448             {
449                 result++;
450                 last_rank = ranked_point.rank;
451             }
452         }
453         return result;
454     }
455 
moveboost::geometry::detail::overlay::sort_by_side::side_sorter456     std::size_t move(std::size_t index) const
457     {
458         std::size_t const result = index + 1;
459         return result >= m_ranked_points.size() ? 0 : result;
460     }
461 
462     //! member is pointer to member (source_index or multi_index)
463     template <signed_size_type segment_identifier::*Member>
moveboost::geometry::detail::overlay::sort_by_side::side_sorter464     std::size_t move(signed_size_type member_index, std::size_t index) const
465     {
466         std::size_t result = move(index);
467         while (m_ranked_points[result].seg_id.*Member != member_index)
468         {
469             result = move(result);
470         }
471         return result;
472     }
473 
assign_ranksboost::geometry::detail::overlay::sort_by_side::side_sorter474     void assign_ranks(std::size_t min_rank, std::size_t max_rank, int side_index)
475     {
476         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
477         {
478             rp& ranked = m_ranked_points[i];
479             // Suppose there are 8 ranks, if min=4,max=6: assign 4,5,6
480             // if min=5,max=2: assign from 5,6,7,1,2
481             bool const in_range
482                 = max_rank >= min_rank
483                 ? ranked.rank >= min_rank && ranked.rank <= max_rank
484                 : ranked.rank >= min_rank || ranked.rank <= max_rank
485                 ;
486 
487             if (in_range)
488             {
489                 if (side_index == 1)
490                 {
491                     ranked.count_left++;
492                 }
493                 else if (side_index == 2)
494                 {
495                     ranked.count_right++;
496                 }
497             }
498         }
499     }
500 
501     template <signed_size_type segment_identifier::*Member>
find_polygons_for_sourceboost::geometry::detail::overlay::sort_by_side::side_sorter502     void find_polygons_for_source(signed_size_type the_index,
503                 std::size_t start_index)
504     {
505         bool in_polygon = true; // Because start_index is "from", arrives at the turn
506         rp const& start_rp = m_ranked_points[start_index];
507         std::size_t last_from_rank = start_rp.rank;
508         std::size_t previous_rank = start_rp.rank;
509 
510         for (std::size_t index = move<Member>(the_index, start_index);
511              ;
512              index = move<Member>(the_index, index))
513         {
514             rp& ranked = m_ranked_points[index];
515 
516             if (ranked.rank != previous_rank && ! in_polygon)
517             {
518                 assign_ranks(last_from_rank, previous_rank - 1, 1);
519                 assign_ranks(last_from_rank + 1, previous_rank, 2);
520             }
521 
522             if (index == start_index)
523             {
524                 return;
525             }
526 
527             if (ranked.direction == dir_from)
528             {
529                 last_from_rank = ranked.rank;
530                 in_polygon = true;
531             }
532             else if (ranked.direction == dir_to)
533             {
534                 in_polygon = false;
535             }
536 
537             previous_rank = ranked.rank;
538         }
539     }
540 
541     //! Find closed zones and assign it
542     template <typename Include>
assign_zonesboost::geometry::detail::overlay::sort_by_side::side_sorter543     std::size_t assign_zones(Include const& include_functor)
544     {
545         // Find a starting point (the first rank after an outgoing rank
546         // with no polygons on the left side)
547         std::size_t start_rank = m_ranked_points.size() + 1;
548         std::size_t start_index = 0;
549         std::size_t max_rank = 0;
550         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
551         {
552             rp const& ranked_point = m_ranked_points[i];
553             if (ranked_point.rank > max_rank)
554             {
555                 max_rank = ranked_point.rank;
556             }
557             if (ranked_point.direction == sort_by_side::dir_to
558                 && include_functor(ranked_point))
559             {
560                 start_rank = ranked_point.rank + 1;
561             }
562             if (ranked_point.rank == start_rank && start_index == 0)
563             {
564                 start_index = i;
565             }
566         }
567 
568         // Assign the zones
569         std::size_t const undefined_rank = max_rank + 1;
570         std::size_t zone_id = 0;
571         std::size_t last_rank = 0;
572         std::size_t rank_at_next_zone = undefined_rank;
573         std::size_t index = start_index;
574         for (std::size_t i = 0; i < m_ranked_points.size(); i++)
575         {
576             rp& ranked_point = m_ranked_points[index];
577 
578             // Implement cyclic behavior
579             index++;
580             if (index == m_ranked_points.size())
581             {
582                 index = 0;
583             }
584 
585             if (ranked_point.rank != last_rank)
586             {
587                 if (ranked_point.rank == rank_at_next_zone)
588                 {
589                     zone_id++;
590                     rank_at_next_zone = undefined_rank;
591                 }
592 
593                 if (ranked_point.direction == sort_by_side::dir_to
594                     && include_functor(ranked_point))
595                 {
596                     rank_at_next_zone = ranked_point.rank + 1;
597                     if (rank_at_next_zone > max_rank)
598                     {
599                         rank_at_next_zone = 0;
600                     }
601                 }
602 
603                 last_rank = ranked_point.rank;
604             }
605 
606             ranked_point.zone = zone_id;
607         }
608         return zone_id;
609     }
610 
611 public :
open_countboost::geometry::detail::overlay::sort_by_side::side_sorter612     inline std::size_t open_count(operation_type for_operation) const
613     {
614         return for_operation == operation_union
615             ? open_count(include_union())
616             : open_count(include_intersection())
617             ;
618     }
619 
assign_zonesboost::geometry::detail::overlay::sort_by_side::side_sorter620     inline std::size_t assign_zones(operation_type for_operation)
621     {
622         return for_operation == operation_union
623             ? assign_zones(include_union())
624             : assign_zones(include_intersection())
625             ;
626     }
627 
628 };
629 
630 
631 }}} // namespace detail::overlay::sort_by_side
632 #endif //DOXYGEN_NO_DETAIL
633 
634 
635 }} // namespace boost::geometry
636 
637 #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP
638