1 // Boost.Geometry (aka GGL, Generic Geometry Library) 2 3 // Copyright (c) 2015 Barend Gehrels, Amsterdam, the Netherlands. 4 // Copyright (c) 2017 Adam Wulkiewicz, Lodz, Poland. 5 6 // This file was modified by Oracle on 2017, 2019. 7 // Modifications copyright (c) 2017, 2019 Oracle and/or its affiliates. 8 9 // Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle 10 11 // Use, modification and distribution is subject to the Boost Software License, 12 // Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at 13 // http://www.boost.org/LICENSE_1_0.txt) 14 15 #ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP 16 #define BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP 17 18 #include <algorithm> 19 #include <map> 20 #include <vector> 21 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 #include <boost/geometry/util/condition.hpp> 28 29 namespace boost { namespace geometry 30 { 31 32 #ifndef DOXYGEN_NO_DETAIL 33 namespace detail { namespace overlay { namespace sort_by_side 34 { 35 36 enum direction_type { dir_unknown = -1, dir_from = 0, dir_to = 1 }; 37 38 typedef signed_size_type rank_type; 39 40 41 // Point-wrapper, adding some properties 42 template <typename Point> 43 struct ranked_point 44 { ranked_pointboost::geometry::detail::overlay::sort_by_side::ranked_point45 ranked_point() 46 : rank(0) 47 , turn_index(-1) 48 , operation_index(-1) 49 , direction(dir_unknown) 50 , count_left(0) 51 , count_right(0) 52 , operation(operation_none) 53 {} 54 ranked_pointboost::geometry::detail::overlay::sort_by_side::ranked_point55 ranked_point(Point const& p, signed_size_type ti, int oi, 56 direction_type d, operation_type op, segment_identifier const& si) 57 : point(p) 58 , rank(0) 59 , zone(-1) 60 , turn_index(ti) 61 , operation_index(oi) 62 , direction(d) 63 , count_left(0) 64 , count_right(0) 65 , operation(op) 66 , seg_id(si) 67 {} 68 69 Point point; 70 rank_type rank; 71 signed_size_type zone; // index of closed zone, in uu turn there would be 2 zones 72 signed_size_type turn_index; 73 int operation_index; // 0,1 74 direction_type direction; 75 std::size_t count_left; 76 std::size_t count_right; 77 operation_type operation; 78 segment_identifier seg_id; 79 }; 80 81 struct less_by_turn_index 82 { 83 template <typename T> operator ()boost::geometry::detail::overlay::sort_by_side::less_by_turn_index84 inline bool operator()(const T& first, const T& second) const 85 { 86 return first.turn_index == second.turn_index 87 ? first.index < second.index 88 : first.turn_index < second.turn_index 89 ; 90 } 91 }; 92 93 struct less_by_index 94 { 95 template <typename T> operator ()boost::geometry::detail::overlay::sort_by_side::less_by_index96 inline bool operator()(const T& first, const T& second) const 97 { 98 // Length might be considered too 99 // First order by from/to 100 if (first.direction != second.direction) 101 { 102 return first.direction < second.direction; 103 } 104 // Then by turn index 105 if (first.turn_index != second.turn_index) 106 { 107 return first.turn_index < second.turn_index; 108 } 109 // This can also be the same (for example in buffer), but seg_id is 110 // never the same 111 return first.seg_id < second.seg_id; 112 } 113 }; 114 115 struct less_false 116 { 117 template <typename T> operator ()boost::geometry::detail::overlay::sort_by_side::less_false118 inline bool operator()(const T&, const T& ) const 119 { 120 return false; 121 } 122 }; 123 124 template <typename Point, typename SideStrategy, typename LessOnSame, typename Compare> 125 struct less_by_side 126 { less_by_sideboost::geometry::detail::overlay::sort_by_side::less_by_side127 less_by_side(const Point& p1, const Point& p2, SideStrategy const& strategy) 128 : m_origin(p1) 129 , m_turn_point(p2) 130 , m_strategy(strategy) 131 {} 132 133 template <typename T> operator ()boost::geometry::detail::overlay::sort_by_side::less_by_side134 inline bool operator()(const T& first, const T& second) const 135 { 136 typedef typename SideStrategy::cs_tag cs_tag; 137 138 LessOnSame on_same; 139 Compare compare; 140 141 int const side_first = m_strategy.apply(m_origin, m_turn_point, first.point); 142 int const side_second = m_strategy.apply(m_origin, m_turn_point, second.point); 143 144 if (side_first == 0 && side_second == 0) 145 { 146 // Both collinear. They might point into different directions: <------*------> 147 // If so, order the one going backwards as the very first. 148 149 int const first_code = direction_code<cs_tag>(m_origin, m_turn_point, first.point); 150 int const second_code = direction_code<cs_tag>(m_origin, m_turn_point, second.point); 151 152 // Order by code, backwards first, then forward. 153 return first_code != second_code 154 ? first_code < second_code 155 : on_same(first, second) 156 ; 157 } 158 else if (side_first == 0 159 && direction_code<cs_tag>(m_origin, m_turn_point, first.point) == -1) 160 { 161 // First collinear and going backwards. 162 // Order as the very first, so return always true 163 return true; 164 } 165 else if (side_second == 0 166 && direction_code<cs_tag>(m_origin, m_turn_point, second.point) == -1) 167 { 168 // Second is collinear and going backwards 169 // Order as very last, so return always false 170 return false; 171 } 172 173 // They are not both collinear 174 175 if (side_first != side_second) 176 { 177 return compare(side_first, side_second); 178 } 179 180 // They are both left, both right, and/or both collinear (with each other and/or with p1,p2) 181 // Check mutual side 182 int const side_second_wrt_first = m_strategy.apply(m_turn_point, first.point, second.point); 183 184 if (side_second_wrt_first == 0) 185 { 186 return on_same(first, second); 187 } 188 189 int const side_first_wrt_second = m_strategy.apply(m_turn_point, second.point, first.point); 190 if (side_second_wrt_first != -side_first_wrt_second) 191 { 192 // (FP) accuracy error in side calculation, the sides are not opposite. 193 // In that case they can be handled as collinear. 194 // If not, then the sort-order might not be stable. 195 return on_same(first, second); 196 } 197 198 // Both are on same side, and not collinear 199 // Union: return true if second is right w.r.t. first, so -1, 200 // so other is 1. union has greater as compare functor 201 // Intersection: v.v. 202 return compare(side_first_wrt_second, side_second_wrt_first); 203 } 204 205 private : 206 Point const& m_origin; 207 Point const& m_turn_point; 208 SideStrategy const& m_strategy; 209 }; 210 211 // Sorts vectors in counter clockwise order (by default) 212 template 213 < 214 bool Reverse1, 215 bool Reverse2, 216 overlay_type OverlayType, 217 typename Point, 218 typename SideStrategy, 219 typename Compare 220 > 221 struct side_sorter 222 { 223 typedef ranked_point<Point> rp; 224 225 private : 226 struct include_union 227 { operator ()boost::geometry::detail::overlay::sort_by_side::side_sorter::include_union228 inline bool operator()(rp const& ranked_point) const 229 { 230 // New candidate if there are no polygons on left side, 231 // but there are on right side 232 return ranked_point.count_left == 0 233 && ranked_point.count_right > 0; 234 } 235 }; 236 237 struct include_intersection 238 { operator ()boost::geometry::detail::overlay::sort_by_side::side_sorter::include_intersection239 inline bool operator()(rp const& ranked_point) const 240 { 241 // New candidate if there are two polygons on right side, 242 // and less on the left side 243 return ranked_point.count_left < 2 244 && ranked_point.count_right >= 2; 245 } 246 }; 247 248 public : side_sorterboost::geometry::detail::overlay::sort_by_side::side_sorter249 side_sorter(SideStrategy const& strategy) 250 : m_origin_count(0) 251 , m_origin_segment_distance(0) 252 , m_strategy(strategy) 253 {} 254 add_segment_fromboost::geometry::detail::overlay::sort_by_side::side_sorter255 void add_segment_from(signed_size_type turn_index, int op_index, 256 Point const& point_from, 257 operation_type op, segment_identifier const& si, 258 bool is_origin) 259 { 260 m_ranked_points.push_back(rp(point_from, turn_index, op_index, dir_from, op, si)); 261 if (is_origin) 262 { 263 m_origin = point_from; 264 m_origin_count++; 265 } 266 } 267 add_segment_toboost::geometry::detail::overlay::sort_by_side::side_sorter268 void add_segment_to(signed_size_type turn_index, int op_index, 269 Point const& point_to, 270 operation_type op, segment_identifier const& si) 271 { 272 m_ranked_points.push_back(rp(point_to, turn_index, op_index, dir_to, op, si)); 273 } 274 add_segmentboost::geometry::detail::overlay::sort_by_side::side_sorter275 void add_segment(signed_size_type turn_index, int op_index, 276 Point const& point_from, Point const& point_to, 277 operation_type op, segment_identifier const& si, 278 bool is_origin) 279 { 280 add_segment_from(turn_index, op_index, point_from, op, si, is_origin); 281 add_segment_to(turn_index, op_index, point_to, op, si); 282 } 283 284 template <typename Operation, typename Geometry1, typename Geometry2> addboost::geometry::detail::overlay::sort_by_side::side_sorter285 Point add(Operation const& op, signed_size_type turn_index, int op_index, 286 Geometry1 const& geometry1, 287 Geometry2 const& geometry2, 288 bool is_origin) 289 { 290 Point point1, point2, point3; 291 geometry::copy_segment_points<Reverse1, Reverse2>(geometry1, geometry2, 292 op.seg_id, point1, point2, point3); 293 Point const& point_to = op.fraction.is_one() ? point3 : point2; 294 add_segment(turn_index, op_index, point1, point_to, op.operation, op.seg_id, is_origin); 295 return point1; 296 } 297 298 template <typename Operation, typename Geometry1, typename Geometry2> addboost::geometry::detail::overlay::sort_by_side::side_sorter299 void add(Operation const& op, signed_size_type turn_index, int op_index, 300 segment_identifier const& departure_seg_id, 301 Geometry1 const& geometry1, 302 Geometry2 const& geometry2, 303 bool check_origin) 304 { 305 Point const point1 = add(op, turn_index, op_index, geometry1, geometry2, false); 306 307 if (check_origin) 308 { 309 bool const is_origin 310 = op.seg_id.source_index == departure_seg_id.source_index 311 && op.seg_id.ring_index == departure_seg_id.ring_index 312 && op.seg_id.multi_index == departure_seg_id.multi_index; 313 314 if (is_origin) 315 { 316 signed_size_type const segment_distance = calculate_segment_distance(op, departure_seg_id, geometry1, geometry2); 317 if (m_origin_count == 0 || 318 segment_distance < m_origin_segment_distance) 319 { 320 m_origin = point1; 321 m_origin_segment_distance = segment_distance; 322 } 323 m_origin_count++; 324 } 325 } 326 } 327 328 template <typename Operation, typename Geometry1, typename Geometry2> calculate_segment_distanceboost::geometry::detail::overlay::sort_by_side::side_sorter329 static signed_size_type calculate_segment_distance(Operation const& op, 330 segment_identifier const& departure_seg_id, 331 Geometry1 const& geometry1, 332 Geometry2 const& geometry2) 333 { 334 if (op.seg_id.segment_index >= departure_seg_id.segment_index) 335 { 336 // dep.seg_id=5, op.seg_id=7, distance=2, being segments 5,6 337 return op.seg_id.segment_index - departure_seg_id.segment_index; 338 } 339 // Take wrap into account 340 // Suppose point_count=10 (10 points, 9 segments), dep.seg_id=7, op.seg_id=2, 341 // then distance=9-7+2=4, being segments 7,8,0,1 342 std::size_t const segment_count 343 = op.seg_id.source_index == 0 344 ? segment_count_on_ring(geometry1, op.seg_id) 345 : segment_count_on_ring(geometry2, op.seg_id); 346 return segment_count - departure_seg_id.segment_index + op.seg_id.segment_index; 347 } 348 applyboost::geometry::detail::overlay::sort_by_side::side_sorter349 void apply(Point const& turn_point) 350 { 351 // We need three compare functors: 352 // 1) to order clockwise (union) or counter clockwise (intersection) 353 // 2) to order by side, resulting in unique ranks for all points 354 // 3) to order by side, resulting in non-unique ranks 355 // to give colinear points 356 357 // Sort by side and assign rank 358 less_by_side<Point, SideStrategy, less_by_index, Compare> less_unique(m_origin, turn_point, m_strategy); 359 less_by_side<Point, SideStrategy, less_false, Compare> less_non_unique(m_origin, turn_point, m_strategy); 360 361 std::sort(m_ranked_points.begin(), m_ranked_points.end(), less_unique); 362 363 std::size_t colinear_rank = 0; 364 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 365 { 366 if (i > 0 367 && less_non_unique(m_ranked_points[i - 1], m_ranked_points[i])) 368 { 369 // It is not collinear 370 colinear_rank++; 371 } 372 373 m_ranked_points[i].rank = colinear_rank; 374 } 375 } 376 find_open_by_piece_indexboost::geometry::detail::overlay::sort_by_side::side_sorter377 void find_open_by_piece_index() 378 { 379 // For buffers, use piece index 380 std::set<signed_size_type> handled; 381 382 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 383 { 384 const rp& ranked = m_ranked_points[i]; 385 if (ranked.direction != dir_from) 386 { 387 continue; 388 } 389 390 signed_size_type const& index = ranked.seg_id.piece_index; 391 if (handled.count(index) > 0) 392 { 393 continue; 394 } 395 find_polygons_for_source<&segment_identifier::piece_index>(index, i); 396 handled.insert(index); 397 } 398 } 399 find_open_by_source_indexboost::geometry::detail::overlay::sort_by_side::side_sorter400 void find_open_by_source_index() 401 { 402 // Check for source index 0 and 1 403 bool handled[2] = {false, false}; 404 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 405 { 406 const rp& ranked = m_ranked_points[i]; 407 if (ranked.direction != dir_from) 408 { 409 continue; 410 } 411 412 signed_size_type const& index = ranked.seg_id.source_index; 413 if (index < 0 || index > 1 || handled[index]) 414 { 415 continue; 416 } 417 find_polygons_for_source<&segment_identifier::source_index>(index, i); 418 handled[index] = true; 419 } 420 } 421 find_openboost::geometry::detail::overlay::sort_by_side::side_sorter422 void find_open() 423 { 424 if (BOOST_GEOMETRY_CONDITION(OverlayType == overlay_buffer)) 425 { 426 find_open_by_piece_index(); 427 } 428 else 429 { 430 find_open_by_source_index(); 431 } 432 } 433 reverseboost::geometry::detail::overlay::sort_by_side::side_sorter434 void reverse() 435 { 436 if (m_ranked_points.empty()) 437 { 438 return; 439 } 440 441 std::size_t const last = 1 + m_ranked_points.back().rank; 442 443 // Move iterator after rank==0 444 bool has_first = false; 445 typename container_type::iterator it = m_ranked_points.begin() + 1; 446 for (; it != m_ranked_points.end() && it->rank == 0; ++it) 447 { 448 has_first = true; 449 } 450 451 if (has_first) 452 { 453 // Reverse first part (having rank == 0), if any, 454 // but skip the very first row 455 std::reverse(m_ranked_points.begin() + 1, it); 456 for (typename container_type::iterator fit = m_ranked_points.begin(); 457 fit != it; ++fit) 458 { 459 BOOST_ASSERT(fit->rank == 0); 460 } 461 } 462 463 // Reverse the rest (main rank > 0) 464 std::reverse(it, m_ranked_points.end()); 465 for (; it != m_ranked_points.end(); ++it) 466 { 467 BOOST_ASSERT(it->rank > 0); 468 it->rank = last - it->rank; 469 } 470 } 471 has_originboost::geometry::detail::overlay::sort_by_side::side_sorter472 bool has_origin() const 473 { 474 return m_origin_count > 0; 475 } 476 477 //private : 478 479 typedef std::vector<rp> container_type; 480 container_type m_ranked_points; 481 Point m_origin; 482 std::size_t m_origin_count; 483 signed_size_type m_origin_segment_distance; 484 SideStrategy m_strategy; 485 486 private : 487 488 //! Check how many open spaces there are 489 template <typename Include> open_countboost::geometry::detail::overlay::sort_by_side::side_sorter490 inline std::size_t open_count(Include const& include_functor) const 491 { 492 std::size_t result = 0; 493 rank_type last_rank = 0; 494 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 495 { 496 rp const& ranked_point = m_ranked_points[i]; 497 498 if (ranked_point.rank > last_rank 499 && ranked_point.direction == sort_by_side::dir_to 500 && include_functor(ranked_point)) 501 { 502 result++; 503 last_rank = ranked_point.rank; 504 } 505 } 506 return result; 507 } 508 moveboost::geometry::detail::overlay::sort_by_side::side_sorter509 std::size_t move(std::size_t index) const 510 { 511 std::size_t const result = index + 1; 512 return result >= m_ranked_points.size() ? 0 : result; 513 } 514 515 //! member is pointer to member (source_index or multi_index) 516 template <signed_size_type segment_identifier::*Member> moveboost::geometry::detail::overlay::sort_by_side::side_sorter517 std::size_t move(signed_size_type member_index, std::size_t index) const 518 { 519 std::size_t result = move(index); 520 while (m_ranked_points[result].seg_id.*Member != member_index) 521 { 522 result = move(result); 523 } 524 return result; 525 } 526 assign_ranksboost::geometry::detail::overlay::sort_by_side::side_sorter527 void assign_ranks(rank_type min_rank, rank_type max_rank, int side_index) 528 { 529 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 530 { 531 rp& ranked = m_ranked_points[i]; 532 // Suppose there are 8 ranks, if min=4,max=6: assign 4,5,6 533 // if min=5,max=2: assign from 5,6,7,1,2 534 bool const in_range 535 = max_rank >= min_rank 536 ? ranked.rank >= min_rank && ranked.rank <= max_rank 537 : ranked.rank >= min_rank || ranked.rank <= max_rank 538 ; 539 540 if (in_range) 541 { 542 if (side_index == 1) 543 { 544 ranked.count_left++; 545 } 546 else if (side_index == 2) 547 { 548 ranked.count_right++; 549 } 550 } 551 } 552 } 553 554 template <signed_size_type segment_identifier::*Member> find_polygons_for_sourceboost::geometry::detail::overlay::sort_by_side::side_sorter555 void find_polygons_for_source(signed_size_type the_index, 556 std::size_t start_index) 557 { 558 bool in_polygon = true; // Because start_index is "from", arrives at the turn 559 rp const& start_rp = m_ranked_points[start_index]; 560 rank_type last_from_rank = start_rp.rank; 561 rank_type previous_rank = start_rp.rank; 562 563 for (std::size_t index = move<Member>(the_index, start_index); 564 ; 565 index = move<Member>(the_index, index)) 566 { 567 rp& ranked = m_ranked_points[index]; 568 569 if (ranked.rank != previous_rank && ! in_polygon) 570 { 571 assign_ranks(last_from_rank, previous_rank - 1, 1); 572 assign_ranks(last_from_rank + 1, previous_rank, 2); 573 } 574 575 if (index == start_index) 576 { 577 return; 578 } 579 580 if (ranked.direction == dir_from) 581 { 582 last_from_rank = ranked.rank; 583 in_polygon = true; 584 } 585 else if (ranked.direction == dir_to) 586 { 587 in_polygon = false; 588 } 589 590 previous_rank = ranked.rank; 591 } 592 } 593 594 //! Find closed zones and assign it 595 template <typename Include> assign_zonesboost::geometry::detail::overlay::sort_by_side::side_sorter596 std::size_t assign_zones(Include const& include_functor) 597 { 598 // Find a starting point (the first rank after an outgoing rank 599 // with no polygons on the left side) 600 rank_type start_rank = m_ranked_points.size() + 1; 601 std::size_t start_index = 0; 602 rank_type max_rank = 0; 603 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 604 { 605 rp const& ranked_point = m_ranked_points[i]; 606 if (ranked_point.rank > max_rank) 607 { 608 max_rank = ranked_point.rank; 609 } 610 if (ranked_point.direction == sort_by_side::dir_to 611 && include_functor(ranked_point)) 612 { 613 start_rank = ranked_point.rank + 1; 614 } 615 if (ranked_point.rank == start_rank && start_index == 0) 616 { 617 start_index = i; 618 } 619 } 620 621 // Assign the zones 622 rank_type const undefined_rank = max_rank + 1; 623 std::size_t zone_id = 0; 624 rank_type last_rank = 0; 625 rank_type rank_at_next_zone = undefined_rank; 626 std::size_t index = start_index; 627 for (std::size_t i = 0; i < m_ranked_points.size(); i++) 628 { 629 rp& ranked_point = m_ranked_points[index]; 630 631 // Implement cyclic behavior 632 index++; 633 if (index == m_ranked_points.size()) 634 { 635 index = 0; 636 } 637 638 if (ranked_point.rank != last_rank) 639 { 640 if (ranked_point.rank == rank_at_next_zone) 641 { 642 zone_id++; 643 rank_at_next_zone = undefined_rank; 644 } 645 646 if (ranked_point.direction == sort_by_side::dir_to 647 && include_functor(ranked_point)) 648 { 649 rank_at_next_zone = ranked_point.rank + 1; 650 if (rank_at_next_zone > max_rank) 651 { 652 rank_at_next_zone = 0; 653 } 654 } 655 656 last_rank = ranked_point.rank; 657 } 658 659 ranked_point.zone = zone_id; 660 } 661 return zone_id; 662 } 663 664 public : open_countboost::geometry::detail::overlay::sort_by_side::side_sorter665 inline std::size_t open_count(operation_type for_operation) const 666 { 667 return for_operation == operation_union 668 ? open_count(include_union()) 669 : open_count(include_intersection()) 670 ; 671 } 672 assign_zonesboost::geometry::detail::overlay::sort_by_side::side_sorter673 inline std::size_t assign_zones(operation_type for_operation) 674 { 675 return for_operation == operation_union 676 ? assign_zones(include_union()) 677 : assign_zones(include_intersection()) 678 ; 679 } 680 681 }; 682 683 684 //! Metafunction to define side_order (clockwise, ccw) by operation_type 685 template <operation_type OpType> 686 struct side_compare {}; 687 688 template <> 689 struct side_compare<operation_union> 690 { 691 typedef std::greater<int> type; 692 }; 693 694 template <> 695 struct side_compare<operation_intersection> 696 { 697 typedef std::less<int> type; 698 }; 699 700 701 }}} // namespace detail::overlay::sort_by_side 702 #endif //DOXYGEN_NO_DETAIL 703 704 705 }} // namespace boost::geometry 706 707 #endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_OVERLAY_SORT_BY_SIDE_HPP 708