#pragma once #define _USE_MATH_DEFINES #include #include #include #include #include #include #include #include #include #include #ifdef DEBUG #include #include #endif namespace mapbox { namespace geometry { namespace wagyu { template struct point_ptr_pair { point_ptr op1; point_ptr op2; constexpr point_ptr_pair(point_ptr o1, point_ptr o2) : op1(o1), op2(o2) { } point_ptr_pair(point_ptr_pair const& p) = default; point_ptr_pair(point_ptr_pair&& p) : op1(std::move(p.op1)), op2(std::move(p.op2)) { } point_ptr_pair& operator=(point_ptr_pair&& p) { op1 = std::move(p.op1); op2 = std::move(p.op2); return *this; } }; #ifdef DEBUG template inline std::basic_ostream& operator<<(std::basic_ostream& out, const std::unordered_multimap, point_ptr_pair>& dupe_ring) { out << " BEGIN CONNECTIONS: " << std::endl; for (auto& r : dupe_ring) { out << " Ring: "; if (r.second.op1->ring) { out << r.second.op1->ring->ring_index; } else { out << "---"; } out << " to "; if (r.second.op2->ring) { out << r.second.op2->ring->ring_index; } else { out << "---"; } out << " ( at " << r.second.op1->x << ", " << r.second.op1->y << " )"; out << " Ring1 ( "; if (r.second.op1->ring) { out << "area: " << r.second.op1->ring->area << " parent: "; if (r.second.op1->ring->parent) { out << r.second.op1->ring->parent->ring_index; } else { out << "---"; } } else { out << "---"; } out << " )"; out << " Ring2 ( "; if (r.second.op2->ring) { out << "area: " << r.second.op2->ring->area << " parent: "; if (r.second.op2->ring->parent) { out << r.second.op2->ring->parent->ring_index; } else { out << "---"; } } else { out << "---"; } out << " )"; out << std::endl; } out << " END CONNECTIONS: " << std::endl; return out; } #endif template bool find_intersect_loop(std::unordered_multimap, point_ptr_pair>& dupe_ring, std::list, point_ptr_pair>>& iList, ring_ptr ring_parent, ring_ptr ring_origin, ring_ptr ring_search, std::set>& visited, point_ptr orig_pt, point_ptr prev_pt, ring_manager& rings) { { auto range = dupe_ring.equal_range(ring_search); // Check for direct connection for (auto& it = range.first; it != range.second;) { ring_ptr it_ring1 = it->second.op1->ring; ring_ptr it_ring2 = it->second.op2->ring; if (!it_ring1 || !it_ring2 || it_ring1 != ring_search || (!ring_is_hole(it_ring1) && !ring_is_hole(it_ring2))) { it = dupe_ring.erase(it); continue; } if (it_ring2 == ring_origin && (ring_parent == it_ring2 || ring_parent == it_ring2->parent) && *prev_pt != *it->second.op2 && *orig_pt != *it->second.op2) { iList.emplace_front(ring_search, it->second); return true; } ++it; } } auto range = dupe_ring.equal_range(ring_search); visited.insert(ring_search); // Check for connection through chain of other intersections for (auto& it = range.first; it != range.second && it != dupe_ring.end() && it->first == ring_search; ++it) { ring_ptr it_ring = it->second.op2->ring; if (visited.count(it_ring) > 0 || it_ring == nullptr || (ring_parent != it_ring && ring_parent != it_ring->parent) || value_is_zero(area(it_ring)) || *prev_pt == *it->second.op2) { continue; } if (find_intersect_loop(dupe_ring, iList, ring_parent, ring_origin, it_ring, visited, orig_pt, it->second.op2, rings)) { iList.emplace_front(ring_search, it->second); return true; } } return false; } template void remove_spikes(point_ptr& pt) { ring_ptr r = pt->ring; while (true) { if (pt->next == pt) { r->points = nullptr; r->area = std::numeric_limits::quiet_NaN(); pt->ring = nullptr; pt = nullptr; break; } else if (*(pt) == *(pt->next)) { point_ptr old_next = pt->next; old_next->next->prev = pt; pt->next = old_next->next; old_next->next = old_next; old_next->prev = old_next; if (r->points == old_next) { r->points = pt; } r->area = std::numeric_limits::quiet_NaN(); old_next->ring = nullptr; } else if (*(pt) == *(pt->prev)) { point_ptr old_prev = pt->prev; old_prev->prev->next = pt; pt->prev = old_prev->prev; old_prev->next = old_prev; old_prev->prev = old_prev; if (r->points == old_prev) { r->points = pt; } r->area = std::numeric_limits::quiet_NaN(); old_prev->ring = nullptr; } else if (*(pt->next) == *(pt->prev)) { point_ptr next = pt->next; point_ptr prev = pt->prev; next->prev = prev; prev->next = next; if (r->points == pt) { r->points = prev; } r->area = std::numeric_limits::quiet_NaN(); pt->ring = nullptr; pt->next = pt; pt->prev = pt; pt = next; } else { break; } } } template void fixup_children_complex(ring_ptr old_ring, ring_ptr new_ring, ring_vector const& grand_children, ring_manager const& rings) { // Tests if any of the children from the old ring are now children of the new ring for (auto r = old_ring->children.begin(); r != old_ring->children.end();) { assert((*r)->points); assert((*r) != old_ring); if ((*r) == new_ring) { ++r; continue; } if (poly2_contains_poly1(*r, new_ring)) { (*r)->parent = new_ring; new_ring->children.push_back((*r)); r = old_ring->children.erase(r); } else if (poly2_contains_poly1(*r, old_ring)) { ++r; } else { // Rare situation where one of old_rings's previous grand children // should actually contain this ring. bool found = false; for (auto const& gc : grand_children) { if (poly2_contains_poly1(*r, gc)) { (*r)->parent = gc; gc->children.push_back((*r)); r = old_ring->children.erase(r); found = true; break; } } if (!found) { if (old_ring->parent == nullptr) { for (auto const& r2 : rings.children) { if (r2 != new_ring && r2 != old_ring && r2->points && r2 != (*r) && poly2_contains_poly1(*r, r2)) { (*r)->parent = r2; r2->children.push_back((*r)); r = old_ring->children.erase(r); found = true; break; } } } else { for (auto const& r2 : old_ring->parent->children) { if (r2 != new_ring && r2 != old_ring && r2->points && r2 != (*r) && poly2_contains_poly1((*r), r2)) { (*r)->parent = r2; r2->children.push_back((*r)); r = old_ring->children.erase(r); found = true; break; } } } } if (!found) { throw std::runtime_error("No proper parent for child ring found"); } } } } template bool fix_intersects(std::unordered_multimap, point_ptr_pair>& dupe_ring, point_ptr op_j, point_ptr op_k, ring_manager& rings, mapbox::geometry::point& rewind_point) { ring_ptr ring_j = op_j->ring; ring_ptr ring_k = op_k->ring; if (ring_j == ring_k) { return false; } if (!ring_is_hole(ring_j) && !ring_is_hole(ring_k)) { // Both are not holes, return nothing to do. return false; } ring_ptr ring_origin; ring_ptr ring_search; ring_ptr ring_parent; point_ptr op_origin_1; point_ptr op_origin_2; if (!ring_is_hole(ring_j)) { ring_origin = ring_j; ring_parent = ring_origin; ring_search = ring_k; op_origin_1 = op_j; op_origin_2 = op_k; } else if (!ring_is_hole(ring_k)) { ring_origin = ring_k; ring_parent = ring_origin; ring_search = ring_j; op_origin_1 = op_k; op_origin_2 = op_j; } else { // both are holes // Order doesn't matter ring_origin = ring_j; ring_parent = ring_origin->parent; ring_search = ring_k; op_origin_1 = op_j; op_origin_2 = op_k; } if (ring_parent != ring_search->parent) { // The two holes do not have the same parent, do not add them // simply return! return false; } bool found = false; std::list, point_ptr_pair>> iList; { auto range = dupe_ring.equal_range(ring_search); // Check for direct connection for (auto& it = range.first; it != range.second;) { if (!it->second.op1->ring) { it = dupe_ring.erase(it); continue; } if (!it->second.op2->ring) { it = dupe_ring.erase(it); continue; } ring_ptr it_ring2 = it->second.op2->ring; if (it_ring2 == ring_origin) { found = true; if (*op_origin_1 != *(it->second.op2)) { iList.emplace_back(ring_search, it->second); break; } } ++it; } } if (iList.empty()) { auto range = dupe_ring.equal_range(ring_search); std::set> visited; visited.insert(ring_search); // Check for connection through chain of other intersections for (auto& it = range.first; it != range.second && it != dupe_ring.end() && it->first == ring_search; ++it) { ring_ptr it_ring = it->second.op2->ring; if (it_ring != ring_search && *op_origin_2 != *it->second.op2 && it_ring != nullptr && (ring_parent == it_ring || ring_parent == it_ring->parent) && !value_is_zero(area(it_ring)) && find_intersect_loop(dupe_ring, iList, ring_parent, ring_origin, it_ring, visited, op_origin_2, it->second.op2, rings)) { found = true; iList.emplace_front(ring_search, it->second); break; } } } if (!found) { point_ptr_pair intPt_origin = { op_origin_1, op_origin_2 }; point_ptr_pair intPt_search = { op_origin_2, op_origin_1 }; dupe_ring.emplace(ring_origin, std::move(intPt_origin)); dupe_ring.emplace(ring_search, std::move(intPt_search)); return false; } if (iList.empty()) { // The situation where both origin and search are holes might have a missing // search condition, we must check if a new pair must be added. bool missing = true; auto rng = dupe_ring.equal_range(ring_origin); // Check for direct connection for (auto& it = rng.first; it != rng.second; ++it) { ring_ptr it_ring2 = it->second.op2->ring; if (it_ring2 == ring_search) { missing = false; } } if (missing) { point_ptr_pair intPt_origin = { op_origin_1, op_origin_2 }; dupe_ring.emplace(ring_origin, std::move(intPt_origin)); } return false; } if (ring_is_hole(ring_origin)) { for (auto& iRing : iList) { ring_ptr ring_itr = iRing.first; if (!ring_is_hole(ring_itr)) { // Make the hole the origin! point_ptr op1 = op_origin_1; op_origin_1 = iRing.second.op1; iRing.second.op1 = op1; point_ptr op2 = op_origin_2; op_origin_2 = iRing.second.op2; iRing.second.op2 = op2; iRing.first = ring_origin; ring_origin = ring_itr; ring_parent = ring_origin; break; } } } // Switch point_ptr op_origin_1_next = op_origin_1->next; point_ptr op_origin_2_next = op_origin_2->next; op_origin_1->next = op_origin_2_next; op_origin_2->next = op_origin_1_next; op_origin_1_next->prev = op_origin_2; op_origin_2_next->prev = op_origin_1; for (auto& iRing : iList) { mapbox::geometry::point possible_rewind_point = find_rewind_point(iRing.second.op2); if (possible_rewind_point.y > rewind_point.y || (possible_rewind_point.y == rewind_point.y && possible_rewind_point.x < rewind_point.x)) { rewind_point.x = possible_rewind_point.x; rewind_point.y = possible_rewind_point.y; } } for (auto& iRing : iList) { point_ptr op_search_1 = iRing.second.op1; point_ptr op_search_2 = iRing.second.op2; point_ptr op_search_1_next = op_search_1->next; point_ptr op_search_2_next = op_search_2->next; op_search_1->next = op_search_2_next; op_search_2->next = op_search_1_next; op_search_1_next->prev = op_search_2; op_search_2_next->prev = op_search_1; } remove_spikes(op_origin_1); remove_spikes(op_origin_2); if (op_origin_1 == nullptr || op_origin_2 == nullptr) { if (op_origin_1 == nullptr && op_origin_2 == nullptr) { // Self destruction! ring_origin->points = nullptr; ring_origin->area = std::numeric_limits::quiet_NaN(); remove_ring(ring_origin, rings); for (auto& iRing : iList) { ring_ptr ring_itr = iRing.first; ring_itr->points = nullptr; ring_itr->area = std::numeric_limits::quiet_NaN(); remove_ring(ring_itr, rings); } } else { if (op_origin_1 == nullptr) { ring_origin->points = op_origin_2; } else { //(op_origin_2 == nullptr) ring_origin->points = op_origin_1; } ring_origin->area = std::numeric_limits::quiet_NaN(); update_points_ring(ring_origin); for (auto& iRing : iList) { ring_ptr ring_itr = iRing.first; ring_itr->points = nullptr; ring_itr->area = std::numeric_limits::quiet_NaN(); ring_itr->bottom_point = nullptr; if (ring_is_hole(ring_origin)) { ring1_replaces_ring2(ring_origin, ring_itr, rings); } else { ring1_replaces_ring2(ring_origin->parent, ring_itr, rings); } } } } else { ring_ptr ring_new = create_new_ring(rings); std::size_t size_1 = 0; std::size_t size_2 = 0; double area_1 = area_from_point(op_origin_1, size_1); double area_2 = area_from_point(op_origin_2, size_2); if (ring_is_hole(ring_origin) && ((area_1 < 0.0))) { ring_origin->points = op_origin_1; ring_origin->area = area_1; ring_origin->size = size_1; ring_new->points = op_origin_2; ring_new->area = area_2; ring_new->size = size_2; } else { ring_origin->points = op_origin_2; ring_origin->area = area_2; ring_origin->size = size_2; ring_new->points = op_origin_1; ring_new->area = area_1; ring_new->size = size_1; } update_points_ring(ring_origin); update_points_ring(ring_new); ring_origin->bottom_point = nullptr; ring_vector grand_children; for (auto& iRing : iList) { ring_ptr ring_itr = iRing.first; ring_itr->points = nullptr; ring_itr->area = std::numeric_limits::quiet_NaN(); ring_itr->bottom_point = nullptr; for (auto& c : ring_itr->children) { grand_children.push_back(c); } if (ring_is_hole(ring_origin)) { ring1_replaces_ring2(ring_origin, ring_itr, rings); } else { ring1_replaces_ring2(ring_origin->parent, ring_itr, rings); } } if (ring_is_hole(ring_origin)) { ring_new->parent = ring_origin; ring_new->parent->children.push_back(ring_new); fixup_children_complex(ring_origin, ring_new, grand_children, rings); fixup_children_complex(ring_parent, ring_new, grand_children, rings); } else { ring_new->parent = ring_origin->parent; if (ring_new->parent == nullptr) { rings.children.push_back(ring_new); } else { ring_new->parent->children.push_back(ring_new); } fixup_children_complex(ring_origin, ring_new, grand_children, rings); } } std::list, point_ptr_pair>> move_list; for (auto& iRing : iList) { auto range_itr = dupe_ring.equal_range(iRing.first); if (range_itr.first != range_itr.second) { for (auto& it = range_itr.first; it != range_itr.second; ++it) { ring_ptr it_ring = it->second.op1->ring; ring_ptr it_ring2 = it->second.op2->ring; if (it_ring == nullptr || it_ring2 == nullptr || it_ring == it_ring2) { continue; } if ((ring_is_hole(it_ring) || ring_is_hole(it_ring2))) { move_list.emplace_back(it_ring, it->second); } } dupe_ring.erase(iRing.first); } } auto range_itr = dupe_ring.equal_range(ring_origin); for (auto& it = range_itr.first; it != range_itr.second;) { ring_ptr it_ring = it->second.op1->ring; ring_ptr it_ring2 = it->second.op2->ring; if (it_ring == nullptr || it_ring2 == nullptr || it_ring == it_ring2) { it = dupe_ring.erase(it); continue; } if (it_ring != ring_origin) { if ((ring_is_hole(it_ring) || ring_is_hole(it_ring2))) { move_list.emplace_back(it_ring, it->second); } it = dupe_ring.erase(it); } else { if ((ring_is_hole(it_ring) || ring_is_hole(it_ring2))) { ++it; } else { it = dupe_ring.erase(it); } } } if (!move_list.empty()) { dupe_ring.insert(move_list.begin(), move_list.end()); } return true; } template struct point_ptr_cmp { inline bool operator()(point_ptr op1, point_ptr op2) { if (op1->y != op2->y) { return (op1->y > op2->y); } else if (op1->x != op2->x) { return (op1->x < op2->x); } else { std::size_t depth_1 = ring_depth(op1->ring); std::size_t depth_2 = ring_depth(op2->ring); return depth_1 > depth_2; } } }; template struct point_ptr_depth_cmp { inline bool operator()(point_ptr op1, point_ptr op2) { std::size_t depth_1 = ring_depth(op1->ring); std::size_t depth_2 = ring_depth(op2->ring); return depth_1 > depth_2; } }; template void update_duplicate_point_entries( ring_ptr ring, std::unordered_multimap, point_ptr_pair>& dupe_ring) { auto range = dupe_ring.equal_range(ring); std::list, point_ptr_pair>> move_list; for (auto& it = range.first; it != range.second;) { ring_ptr it_ring = it->second.op1->ring; ring_ptr it_ring_2 = it->second.op2->ring; if (it_ring == nullptr || it_ring_2 == nullptr) { it = dupe_ring.erase(it); continue; } if (it_ring != ring) { if ((ring_is_hole(it_ring) || ring_is_hole(it_ring_2))) { move_list.emplace_back(it_ring, it->second); } it = dupe_ring.erase(it); } else { if ((ring_is_hole(it_ring) || ring_is_hole(it_ring_2))) { ++it; } else { it = dupe_ring.erase(it); } } } if (!move_list.empty()) { dupe_ring.insert(move_list.begin(), move_list.end()); } } template bool parent_in_tree(ring_ptr r, ring_ptr possible_parent) { ring_ptr current_ring = r->parent; while (current_ring != nullptr) { if (current_ring == possible_parent) { return true; } current_ring = current_ring->parent; } return false; } template void fixup_children_sibling(ring_ptr old_ring, ring_ptr new_ring) { for (auto r = old_ring->children.begin(); r != old_ring->children.end();) { if ((*r) == new_ring) { ++r; continue; } if (poly2_contains_poly1(*r, new_ring)) { (*r)->parent = new_ring; new_ring->children.push_back((*r)); r = old_ring->children.erase(r); } else { ++r; } } } template void fixup_parents_siblings(ring_ptr parent, ring_ptr ring, ring_manager& rings) { bool parent_ring_area_is_positive = area(parent) > 0.0; if (parent->parent == nullptr) { for (auto r = rings.children.begin(); r != rings.children.end();) { assert((*r)->points); bool ring_area_is_positive = area((*r)) > 0.0; if ((*r) != parent && ring_area_is_positive == parent_ring_area_is_positive && poly2_contains_poly1(ring, (*r))) { ring->parent = (*r); (*r)->children.push_back(ring); parent->children.remove(ring); break; } else { ++r; } } } else { ring_ptr grand_parent = parent->parent; for (auto r = grand_parent->children.begin(); r != grand_parent->children.end();) { assert((*r)->points); bool ring_area_is_positive = area((*r)) > 0.0; if ((*r) != parent && ring_area_is_positive == parent_ring_area_is_positive && poly2_contains_poly1(ring, (*r))) { ring->parent = (*r); (*r)->children.push_back(ring); parent->children.remove(ring); break; } else { ++r; } } } } template void fixup_children_new_interior_ring(ring_ptr old_ring, ring_ptr new_ring, ring_manager& rings) { bool old_ring_area_is_positive = area(old_ring) > 0.0; // Now we must search the siblings of the old ring // This is slow, but there is no other way I know of currently // to solve these problems. if (old_ring->parent == nullptr) { for (auto r = rings.children.begin(); r != rings.children.end();) { assert((*r)->points); bool ring_area_is_positive = area((*r)) > 0.0; if ((*r) != new_ring && ring_area_is_positive == old_ring_area_is_positive && poly2_contains_poly1((*r), new_ring)) { (*r)->parent = new_ring; new_ring->children.push_back((*r)); r = rings.children.erase(r); } else { ++r; } } } else { ring_ptr parent = old_ring->parent; for (auto r = parent->children.begin(); r != parent->children.end();) { assert((*r)->points); assert((*r) != parent); bool ring_area_is_positive = area((*r)) > 0.0; if ((*r) != new_ring && ring_area_is_positive == old_ring_area_is_positive && poly2_contains_poly1((*r), new_ring)) { (*r)->parent = new_ring; new_ring->children.push_back((*r)); r = parent->children.erase(r); } else { ++r; } } } } #ifdef DEBUG template void check_if_intersections_cross(point_ptr p1, point_ptr p2) { // LCOV_EXCL_START point_ptr p1_next = p1->next; point_ptr p2_next = p2->next; point_ptr p1_prev = p1->prev; point_ptr p2_prev = p2->prev; while (*p1_next == *p1) { if (p1_next == p1) { return; } p1_next = p1_next->next; } while (*p2_next == *p2) { if (p2_next == p2) { return; } p2_next = p2_next->next; } while (*p1_prev == *p1) { if (p1_prev == p1) { return; } p1_prev = p1_prev->prev; } while (*p2_prev == *p2) { if (p2_prev == p2) { return; } p2_prev = p2_prev->prev; } double a1_p1 = std::atan2(static_cast(p1_prev->y - p1->y), static_cast(p1_prev->x - p1->x)); double a2_p1 = std::atan2(static_cast(p1_next->y - p1->y), static_cast(p1_next->x - p1->x)); double a1_p2 = std::atan2(static_cast(p2_prev->y - p2->y), static_cast(p2_prev->x - p2->x)); double a2_p2 = std::atan2(static_cast(p2_next->y - p2->y), static_cast(p2_next->x - p2->x)); double min_p1 = std::min(a1_p1, a2_p1); double max_p1 = std::max(a1_p1, a2_p1); double min_p2 = std::min(a1_p2, a2_p2); double max_p2 = std::max(a1_p2, a2_p2); if ((min_p1 < max_p2 && min_p1 > min_p2 && max_p1 > max_p2) || (min_p2 < max_p1 && min_p2 > min_p1 && max_p2 > max_p1)) { throw std::runtime_error("Paths are found to be crossing"); } // LCOV_EXCL_END } #endif template void handle_self_intersections(point_ptr op, point_ptr op2, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings) { // Check that are same ring assert(op->ring == op2->ring); ring_ptr ring = op->ring; double original_area = area(ring); bool original_is_positive = (original_area > 0.0); #ifdef DEBUG check_if_intersections_cross(op, op2); #endif // split the polygon into two ... point_ptr op3 = op->prev; point_ptr op4 = op2->prev; op->prev = op4; op4->next = op; op2->prev = op3; op3->next = op2; remove_spikes(op); remove_spikes(op2); if (op == nullptr && op2 == nullptr) { // Self destruction! // I am not positive that it could ever reach this point -- but leaving // the logic in here for now. ring->points = nullptr; ring->area = std::numeric_limits::quiet_NaN(); remove_ring(ring, rings); update_duplicate_point_entries(ring, dupe_ring); return; } else if (op == nullptr) { ring->points = op2; ring->area = std::numeric_limits::quiet_NaN(); update_duplicate_point_entries(ring, dupe_ring); return; } else if (op2 == nullptr) { ring->points = op; ring->area = std::numeric_limits::quiet_NaN(); update_duplicate_point_entries(ring, dupe_ring); return; } ring_ptr new_ring = create_new_ring(rings); std::size_t size_1 = 0; std::size_t size_2 = 0; double area_1 = area_from_point(op, size_1); double area_2 = area_from_point(op2, size_2); bool area_1_is_positive = (area_1 > 0.0); bool area_2_is_positive = (area_2 > 0.0); bool area_1_is_zero = value_is_zero(area_1); bool area_2_is_zero = value_is_zero(area_2); // Situation # 1 - Orientations are NOT the same: // - One ring contains the other and MUST be a child of that ring // - The one that changed orientation is the child of the other ring // // Situation # 2 - Orientations are the same // - The rings are now split, such a new ring of the same orientation // must be created. // - If the new ring is WITHIN the old ring: // * It WILL be the child of a hole of that ring (this ring may not yet be created) // or possible the child of a child of a child of the ring (an so on)... // - If the new ring is OUTSIDE the old ring: // * It may contain any of the children of the old ring. if (area_2_is_zero || area_1_is_zero || area_1_is_positive != area_2_is_positive) { // Situation #1 - new_ring is contained by ring ... if (area_2_is_zero || (!area_1_is_zero && area_1_is_positive == original_is_positive)) { ring->points = op; ring->area = area_1; ring->size = size_1; new_ring->points = op2; new_ring->area = area_2; new_ring->size = size_2; } else { ring->points = op2; ring->area = area_2; ring->size = size_2; new_ring->points = op; new_ring->area = area_1; new_ring->size = size_1; } update_points_ring(ring); update_points_ring(new_ring); new_ring->parent = ring; new_ring->parent->children.push_back(new_ring); fixup_children_new_interior_ring(ring, new_ring, rings); } else { // Situation #2 - create new ring // The largest absolute area is the parent if (std::fabs(area_1) > std::fabs(area_2)) { ring->points = op; ring->area = area_1; ring->size = size_1; new_ring->points = op2; new_ring->area = area_2; new_ring->size = size_2; } else { ring->points = op2; ring->area = area_2; ring->size = size_2; new_ring->points = op; new_ring->area = area_1; new_ring->size = size_1; } update_points_ring(ring); update_points_ring(new_ring); // Polygons are completely seperate new_ring->parent = ring->parent; if (new_ring->parent == nullptr) { rings.children.push_back(new_ring); } else { new_ring->parent->children.push_back(new_ring); } fixup_children_sibling(ring, new_ring); // The parent might need to be reassigned in rare situations if (ring->parent) { if (!poly2_contains_poly1(ring, ring->parent)) { // Parent ring split on outside - search parent's siblings fixup_parents_siblings(ring->parent, ring, rings); } else if (!poly2_contains_poly1(new_ring, ring->parent)) { // child ring split on the outside fixup_parents_siblings(ring->parent, new_ring, rings); } } } update_duplicate_point_entries(ring, dupe_ring); } template mapbox::geometry::point find_rewind_point(point_ptr pt) { mapbox::geometry::point rewind; rewind.x = pt->x; rewind.y = pt->y; point_ptr itr = pt->next; while (pt != itr) { if (itr->y > rewind.y || (itr->y == rewind.y && itr->x < rewind.x)) { rewind.x = itr->x; rewind.y = itr->y; } itr = itr->next; } return rewind; } template bool handle_collinear_edges(point_ptr pt1, point_ptr pt2, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings, mapbox::geometry::point& rewind_point) { ring_ptr ring1 = pt1->ring; ring_ptr ring2 = pt2->ring; if (ring1 == ring2) { return false; } bool valid = (ring1 != ring2 && (ring1->parent == ring2->parent || ring2->parent == ring1 || ring1->parent == ring2)); if (!valid) { return false; } if (*(pt1->next) != *(pt2->prev) && *(pt2->next) != *(pt1->prev)) { return false; } if (ring1->parent == ring2) { // switch ring1 and ring2 std::swap(pt1, pt2); std::swap(ring1, ring2); } mapbox::geometry::point rewind_1 = find_rewind_point(pt1); mapbox::geometry::point rewind_2 = find_rewind_point(pt2); // The lower right of the two points is the rewind point. mapbox::geometry::point possible_rewind; if (rewind_1.y > rewind_2.y) { possible_rewind = rewind_2; } else if (rewind_1.y < rewind_2.y) { possible_rewind = rewind_1; } else if (rewind_1.x > rewind_2.x) { possible_rewind = rewind_1; } else { possible_rewind = rewind_2; } if (possible_rewind.y > rewind_point.y || (possible_rewind.y == rewind_point.y && possible_rewind.x < rewind_point.x)) { rewind_point.x = possible_rewind.x; rewind_point.y = possible_rewind.y; } // swap points point_ptr pt3 = pt1->prev; point_ptr pt4 = pt2->prev; pt1->prev = pt4; pt4->next = pt1; pt2->prev = pt3; pt3->next = pt2; // remove spikes remove_spikes(pt1); if (!pt1) { // rings self destructed ring1->points = nullptr; ring1->area = std::numeric_limits::quiet_NaN(); remove_ring(ring1, rings); ring2->points = nullptr; ring2->area = std::numeric_limits::quiet_NaN(); remove_ring(ring2, rings); return false; } if (pt2->ring) { remove_spikes(pt2); if (!pt2) { // rings self destructed // Not sure this logic is reachable, but leaving it in for now. ring1->points = nullptr; ring1->area = std::numeric_limits::quiet_NaN(); remove_ring(ring1, rings); ring2->points = nullptr; ring2->area = std::numeric_limits::quiet_NaN(); remove_ring(ring2, rings); return false; } // We could have removed pt1 during this process.. // if we did we need to reassign pt2 to pt1 if (!pt1->ring) { pt1 = pt2; } } ring1->points = pt1; ring2->points = nullptr; ring1->area = std::numeric_limits::quiet_NaN(); ring2->area = std::numeric_limits::quiet_NaN(); if (ring2->parent == ring1) { ring1_replaces_ring2(ring1->parent, ring2, rings); } else { ring1_replaces_ring2(ring1, ring2, rings); } update_points_ring(ring1); update_duplicate_point_entries(ring2, dupe_ring); return true; } template bool point_2_is_between_point_1_and_point_3(point_ptr pt1, point_ptr pt2, point_ptr pt3) { if ((*pt1 == *pt3) || (*pt1 == *pt2) || (*pt3 == *pt2)) { return false; } else if (pt1->x != pt3->x) { return (pt2->x > pt1->x) == (pt2->x < pt3->x); } else { return (pt2->y > pt1->y) == (pt2->y < pt3->y); } } enum orientation_type : std::uint8_t { orientation_collinear_spike = 0, orientation_clockwise, orientation_collinear_line, orientation_counter_clockwise }; // To find orientation of ordered triplet (p, q, r) // Orientation between q and r with respect to p. template inline orientation_type orientation_of_points(point_ptr p, point_ptr q, point_ptr r) { T val = (q->y - p->y) * (r->x - q->x) - (q->x - p->x) * (r->y - q->y); if (val == 0) { if (point_2_is_between_point_1_and_point_3(q, p, r)) { return orientation_collinear_line; } else { return orientation_collinear_spike; } } return (val > 0) ? orientation_clockwise : orientation_counter_clockwise; } // Self intersection point vector template using si_point_vector = std::vector, bool>>; #ifdef DEBUG template std::string output_si_angles(point_ptr pt) { // LCOV_EXCL_START std::ostringstream out; double prev_angle = std::atan2(static_cast(pt->prev->y - pt->y), static_cast(pt->prev->x - pt->x)); prev_angle = (180.0 / M_PI) * prev_angle; if (prev_angle < 0.0) { prev_angle += 360.0; } double next_angle = std::atan2(static_cast(pt->next->y - pt->y), static_cast(pt->next->x - pt->x)); next_angle = (180.0 / M_PI) * next_angle; if (next_angle < 0.0) { next_angle += 360.0; } out << " angles: " << prev_angle << " , " << next_angle; out << " - [[" << pt->prev->x << "," << pt->prev->y << "],["; out << pt->x << "," << pt->y << "],["; out << pt->next->x << "," << pt->next->y << "]]"; return out.str(); // LCOV_EXCL_END } template std::string output_compare_si_angles(point_ptr pt, point_ptr compare) { std::ostringstream out; double cmp_prev_angle = std::atan2(static_cast(compare->prev->y - compare->y), static_cast(compare->prev->x - compare->x)); double prev_angle = std::atan2(static_cast(pt->prev->y - pt->y), static_cast(pt->prev->x - pt->x)); prev_angle = (180.0 / M_PI) * prev_angle; if (prev_angle < 0.0) { prev_angle += 360.0; } cmp_prev_angle = (180.0 / M_PI) * cmp_prev_angle; if (cmp_prev_angle < 0.0) { cmp_prev_angle += 360.0; } double cmp_next_angle = std::atan2(static_cast(compare->next->y - compare->y), static_cast(compare->next->x - compare->x)); double next_angle = std::atan2(static_cast(pt->next->y - pt->y), static_cast(pt->next->x - pt->x)); next_angle = (180.0 / M_PI) * next_angle; if (next_angle < 0.0) { next_angle += 360.0; } cmp_next_angle = (180.0 / M_PI) * cmp_next_angle; if (cmp_next_angle < 0.0) { cmp_next_angle += 360.0; } out << " compared to prev: " << prev_angle - cmp_prev_angle << ", " << next_angle - cmp_prev_angle << std::endl; out << " compared to next: " << prev_angle - cmp_next_angle << ", " << next_angle - cmp_next_angle << std::endl; return out.str(); } template inline std::basic_ostream& operator<<(std::basic_ostream& out, const si_point_vector& pts) { out << "Self Intersection Point Vector:" << std::endl; for (auto& pt : pts) { out << output_si_angles(pt.first); if (pt.second) { out << " - clockwise" << std::endl; } else { out << " - counter clockwise" << std::endl; } } return out; } template inline std::basic_ostream& operator<<(std::basic_ostream& out, const orientation_type& ot) { switch (ot) { case orientation_collinear_spike: out << "collinear - spike"; break; case orientation_clockwise: out << "clockwise"; break; case orientation_collinear_line: out << "collinear - line"; break; case orientation_counter_clockwise: out << "counter clockwise"; break; }; return out; } #endif template bool clockwise_of_next(point_ptr const& origin, point_ptr pt) { // Determine if the prev and next of pt is either clockwise of next from // origin (and therefore ccw of previous) or if it is ccw of next and clockwise // of previous // First inspect orientation of points, keep in mind this is the orientations of // the three points orientation_type ot_origin = orientation_of_points(origin, origin->next, origin->prev); if (ot_origin == orientation_collinear_spike) { return true; } else if (ot_origin == orientation_clockwise) { orientation_type ot_prev_next = orientation_of_points(origin, origin->next, pt->prev); if (ot_prev_next == orientation_collinear_spike) { orientation_type ot_next_next = orientation_of_points(origin, origin->next, pt->next); if (ot_next_next == orientation_collinear_spike) { // Pt forms a spike on origin next // so lets assume it is CW. // I am not sure that we would ever encounter a spike so this will // be missing from coverage, but left in code. return true; } else if (ot_next_next == orientation_clockwise) { // We need to check which is after "origin->next" traveling // clockwise -- origin->prev or pt->next orientation_type ot_next_prev = orientation_of_points(origin, origin->prev, pt->next); if (ot_next_prev == orientation_collinear_spike) { // Both origin and pt follow the same paths. // so we will call this clockwise return true; } else if (ot_next_prev == orientation_clockwise) { // pt->next is clockwise of prev return false; } else if (ot_next_prev == orientation_collinear_line) { // This shouldn't happen // LCOV_EXCL_START throw std::runtime_error("Impossible situation reached in clockwise_of_next"); // LCOV_EXCL_END } else { // Pt next is counter clockwise of origin prev return true; } } else { // ot_next_next == orientation_collinear_line // ot_next_next == orientation_counter_clockwise return false; } } else if (ot_prev_next == orientation_clockwise) { // We need to check which is after "origin->next" traveling // clockwise -- origin->prev or pt->prev orientation_type ot_prev_prev = orientation_of_points(origin, origin->prev, pt->prev); if (ot_prev_prev == orientation_clockwise || ot_prev_prev == orientation_collinear_spike) { // pt->prev is before this, so.. between the two. return false; } else { // ot_prev_prev == orientation_clockwise // ot_prev_prev == orientation_collinear_line return true; } } else { // ot_prev_next == orientation_collinear_line // ot_prev_next == orientation_counter_clockwise return false; } } else if (ot_origin == orientation_collinear_line) { orientation_type ot_prev_next = orientation_of_points(origin, origin->next, pt->prev); if (ot_prev_next == orientation_collinear_spike || ot_prev_next == orientation_collinear_line) { // prev and next on top of each other orientation_type ot_next_next = orientation_of_points(origin, origin->next, pt->next); if (ot_next_next == orientation_counter_clockwise) { return false; } else { // ot_next_next == orientation_collinear_spike // ot_next_next == orientation_clockwise // ot_next_next == orientation_collinear_line return true; } } else if (ot_prev_next == orientation_clockwise) { return true; } else { // ot_prev_next == orientation_counter_clockwise return false; } } else { // orientation_counter_clockwise orientation_type ot_prev_next = orientation_of_points(origin, origin->next, pt->prev); if (ot_prev_next == orientation_collinear_spike) { orientation_type ot_next_next = orientation_of_points(origin, origin->next, pt->next); if (ot_next_next == orientation_collinear_spike) { // Pt forms a spike on origin next // so lets assume it is CW. // I am not sure that we would ever encounter a spike so this will // be missing from coverage, but left in code. return true; } else if (ot_next_next == orientation_counter_clockwise) { // We need to check which is after "origin->next" traveling // counter clockwise -- origin->prev or pt->next orientation_type ot_next_prev = orientation_of_points(origin, origin->prev, pt->next); if (ot_next_prev == orientation_collinear_spike) { // Both origin and pt follow the same paths. // so we will call this clockwise return true; } else if (ot_next_prev == orientation_clockwise) { // pt->next is clockwise of prev return false; } else if (ot_next_prev == orientation_collinear_line) { // This shouldn't happen? // LCOV_EXCL_START throw std::runtime_error( "Impossible situation reached in clockwise_of_next - 2"); // LCOV_EXCL_END } else { // Pt next is counter clockwise of origin prev return true; } } else { // ot_next_next == orientation_clockwise // ot_next_next == orientation_collinear_line return true; } } else if (ot_prev_next == orientation_counter_clockwise) { // We need to check which is after "origin->next" traveling // counter clockwise -- origin->prev or pt->prev orientation_type ot_prev_prev = orientation_of_points(origin, origin->prev, pt->prev); if (ot_prev_prev == orientation_clockwise || ot_prev_prev == orientation_collinear_spike) { // pt->prev is before this, so.. between the two. return false; } else { // ot_prev_prev == orientation_counter_clockwise // ot_prev_prev == orientation_collinear_line return true; } } else { // ot_prev_next == orientation_collinear_line // ot_prev_next == orientation_clockwise return true; } } } template inline bool cw_p1p2_prev_collinear_spike(point_ptr const& origin, point_ptr const& next, point_ptr const& p1, point_ptr const& p2) { // we must compare the nexts to determine the order between the two. orientation_type ot_p1_next = orientation_of_points(origin, next, p1->next); orientation_type ot_p2_next = orientation_of_points(origin, next, p2->next); if (ot_p1_next == orientation_collinear_spike) { // p1 is a spike on origin next if (ot_p2_next == orientation_collinear_spike) { // I am not sure that a spike will ever occur at this point in the processing! return true; } else { return false; } } else if (ot_p1_next == orientation_clockwise) { if (ot_p2_next == orientation_collinear_spike) { return true; } else if (ot_p2_next == orientation_clockwise) { // Both are clockwise we have to compare. orientation_type ot = orientation_of_points(origin, p1->next, p2->next); if (ot == orientation_collinear_spike) { return true; } else if (ot == orientation_clockwise) { return false; } else if (ot == orientation_collinear_line) { return false; } else { return true; } } else { // ot_p2_next == orienation_collinear_line // ot_p2_next == orienation_counter_clockwise return false; } } else if (ot_p1_next == orientation_collinear_line) { if (ot_p2_next == orientation_counter_clockwise) { return false; } else { // ot_p2_next == orienation_collinear_spike // ot_p2_next == orienation_clockwise // ot_p2_next == orienation_collinear_line return true; } } else { // ot_p1_next == orientation_counter_clockwise if (ot_p2_next == orientation_counter_clockwise) { // Both are counter clockwise we have to compare. orientation_type ot = orientation_of_points(origin, p1->next, p2->next); if (ot == orientation_collinear_spike) { return true; } else if (ot == orientation_clockwise) { return false; } else if (ot == orientation_collinear_line) { return false; } else { return true; } } else { // ot_p2_next == orienation_collinear_spike // ot_p2_next == orienation_clockwise // ot_p2_next == orienation_collinear_line return true; } } } template inline bool ccw_p1p2_prev_collinear_spike(point_ptr const& origin, point_ptr const& next, point_ptr const& p1, point_ptr const& p2) { // we must compare the nexts to determine the order between the two. orientation_type ot_p1_next = orientation_of_points(origin, next, p1->next); orientation_type ot_p2_next = orientation_of_points(origin, next, p2->next); if (ot_p1_next == orientation_collinear_spike) { // p1 is a spike on origin next if (ot_p2_next == orientation_collinear_spike) { return true; } else { return false; } } else if (ot_p1_next == orientation_clockwise) { if (ot_p2_next == orientation_collinear_spike) { return false; } else if (ot_p2_next == orientation_clockwise) { // Both are clockwise we have to compare. orientation_type ot = orientation_of_points(origin, p1->next, p2->next); if (ot == orientation_collinear_spike) { return true; } else if (ot == orientation_clockwise) { return true; } else if (ot == orientation_collinear_line) { return true; } else { return false; } } else { // ot_p2_next == orienation_collinear_line // ot_p2_next == orienation_counter_clockwise return true; } } else if (ot_p1_next == orientation_collinear_line) { if (ot_p2_next == orientation_clockwise) { return false; } else { // ot_p2_next == orienation_collinear_spike // ot_p2_next == orienation_counter_clockwise // ot_p2_next == orienation_collinear_line return true; } } else { // ot_p1_next == orientation_counter_clockwise if (ot_p2_next == orientation_counter_clockwise) { // Both are counter clockwise we have to compare. orientation_type ot = orientation_of_points(origin, p1->next, p2->next); if (ot == orientation_collinear_spike) { return true; } else if (ot == orientation_clockwise) { return true; } else if (ot == orientation_collinear_line) { return true; } else { return false; } } else { // ot_p2_next == orienation_collinear_spike // ot_p2_next == orienation_clockwise // ot_p2_next == orienation_collinear_line return false; } } } template struct si_point_sorter { point_ptr origin; point_ptr next; si_point_sorter(point_ptr origin_) : origin(origin_), next(origin_->next) { } // Sorting order // // Primary Sort: // * Left of next, right of previous // * Right of next, left of previous // // Secondary Sort: // * Magnitude of angle (direction based on primary sort) // between item's previous and origin's next inline bool operator()(std::pair, bool> const& pp1, std::pair, bool> const& pp2) { // Because a next must be paired with a previous, we are only // caring first about previous segments for ordering point_ptr p1 = pp1.first; point_ptr p2 = pp2.first; if (pp1.second != pp2.second) { return pp1.second; } orientation_type ot_p1 = orientation_of_points(origin, next, p1->prev); orientation_type ot_p2 = orientation_of_points(origin, next, p2->prev); if (pp1.second) { if (ot_p1 == orientation_collinear_spike) { // p1 prev lines up with origin next if (ot_p2 != orientation_collinear_spike) { // ot_p2 == orienation_clockwise // ot_p2 == orienation_collinear_line // ot_p2 == orienation_counter_clockwise return true; } else { // ot_p2 == orientation_collinear_spike return cw_p1p2_prev_collinear_spike(origin, next, p1, p2); } } else if (ot_p1 == orientation_clockwise) { if (ot_p2 == orientation_collinear_spike) { return false; } else if (ot_p2 == orientation_clockwise) { orientation_type ot_p1p2 = orientation_of_points(origin, p1->prev, p2->prev); if (ot_p1p2 == orientation_collinear_spike) { // Both p1 prev and p2 prev are on top of each other return cw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else if (ot_p1p2 == orientation_clockwise) { return true; } else { // ot_p1p2 == orientation_counter_clockwise // ot_p1p2 == orientation_collinear_line return false; } } else { // ot_p2 == orientation_collinear_line // ot_p2 == orientation_counter_clockwise return true; } } else if (ot_p1 == orientation_collinear_line) { if (ot_p2 == orientation_collinear_spike || ot_p2 == orientation_clockwise) { return false; } else if (ot_p2 == orientation_collinear_line) { return cw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else { // ot_p2 == orientation_counter_clockwise return true; } } else { // ot_p1 == orientation_counter_clockwise if (ot_p2 == orientation_counter_clockwise) { orientation_type ot_p1p2 = orientation_of_points(origin, p1->prev, p2->prev); if (ot_p1p2 == orientation_collinear_spike) { // Both p1 prev and p2 prev are on top of each other return cw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else if (ot_p1p2 == orientation_clockwise) { return true; } else { // ot_p1p2 == orientation_counter_clockwise // ot_p1p2 == orientation_collinear_line return false; } } else { // ot_p2 == orientation_collinear_spike // ot_p2 == orientation_counter_clockwise // ot_p2 == orientation_collinear_line return false; } } } else { if (ot_p1 == orientation_collinear_spike) { // p1 prev lines up with origin next if (ot_p2 != orientation_collinear_spike) { // ot_p2 == orienation_clockwise // ot_p2 == orienation_collinear_line // ot_p2 == orienation_counter_clockwise return true; } else { // ot_p2 == orientation_collinear_spike return ccw_p1p2_prev_collinear_spike(origin, next, p1, p2); } } else if (ot_p1 == orientation_clockwise) { if (ot_p2 == orientation_collinear_spike) { return false; } else if (ot_p2 == orientation_clockwise) { orientation_type ot_p1p2 = orientation_of_points(origin, p1->prev, p2->prev); if (ot_p1p2 == orientation_collinear_spike) { // Both p1 prev and p2 prev are on top of each other return ccw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else if (ot_p1p2 == orientation_counter_clockwise) { return true; } else { // ot_p1p2 == orientation_clockwise // ot_p1p2 == orientation_collinear_line return false; } } else { // ot_p2 == orientation_collinear_line // ot_p2 == orientation_counter_clockwise return false; } } else if (ot_p1 == orientation_collinear_line) { if (ot_p2 == orientation_collinear_spike || ot_p2 == orientation_counter_clockwise) { return false; } else if (ot_p2 == orientation_collinear_line) { return ccw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else { // ot_p2 == orientation_clockwise return true; } } else { // ot_p1 == orientation_counter_clockwise if (ot_p2 == orientation_collinear_spike) { return false; } else if (ot_p2 == orientation_counter_clockwise) { orientation_type ot_p1p2 = orientation_of_points(origin, p1->prev, p2->prev); if (ot_p1p2 == orientation_collinear_spike) { // Both p1 prev and p2 prev are on top of each other return ccw_p1p2_prev_collinear_spike(origin, next, p1, p2); } else if (ot_p1p2 == orientation_counter_clockwise) { return true; } else { // ot_p1p2 == orientation_clockwise // ot_p1p2 == orientation_collinear_line return false; } } else { // ot_p2 == orientation_clockwise // ot_p2 == orientation_collinear_line return true; } } } } }; template si_point_vector build_si_point_vector(std::size_t first_index, std::size_t last_index, std::size_t current_index, ring_ptr match_ring, ring_manager& rings) { si_point_vector point_vec; point_ptr origin = rings.all_points[current_index]; for (std::size_t j = first_index; j <= last_index; ++j) { if (j == current_index) { continue; } point_ptr op_j = rings.all_points[j]; if (op_j->ring == match_ring) { bool clockwise = clockwise_of_next(origin, op_j); point_vec.emplace_back(op_j, clockwise); } } return point_vec; } template bool process_repeated_point_set(std::size_t first_index, std::size_t last_index, std::size_t current_index, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings) { point_ptr point_1 = rings.all_points[current_index]; if (point_1->ring == nullptr) { return false; } // Build point vector si_point_vector vec = build_si_point_vector(first_index, last_index, current_index, point_1->ring, rings); if (vec.empty()) { return false; } // Sort points in vector std::stable_sort(vec.begin(), vec.end(), si_point_sorter(point_1)); auto vec_itr = vec.begin(); point_ptr point_2 = vec_itr->first; // If there are collinear sets of lines, we might not be able to just pick // the first point in the sorted list (and we will have to do special processing). if (vec.size() > 2) { ++vec_itr; point_ptr point_3 = vec_itr->first; orientation_type ot_next = orientation_of_points(point_2, point_2->next, point_3->next); if (ot_next == orientation_collinear_spike) { orientation_type ot_prev = orientation_of_points(point_2, point_2->prev, point_3->prev); if (ot_prev == orientation_collinear_spike) { // Start at point_1 and we will travel around the circle until we find another point // at // the same location. We will measure its area, and then continue till the next // point at // the same location. The smallest absolute value of area is the one we will use. point_ptr point_a = point_1; point_ptr min_a = nullptr; point_ptr min_b = nullptr; point_ptr pt = point_1->next; double area = 0.0; double min_area = std::numeric_limits::max(); while (pt != point_1) { area += static_cast(pt->prev->x + pt->x) * static_cast(pt->prev->y - pt->y); if (*pt == *point_1) { if (std::fabs(area) < min_area) { min_area = std::fabs(area); min_a = point_a; min_b = pt; } point_a = pt; area = 0.0; } pt = pt->next; } if (point_a == point_1) { // LCOV_EXCL_START throw std::runtime_error("No other point was between point_1 on the path"); // LCOV_EXCL_END } area += static_cast(pt->prev->x + pt->x) * static_cast(pt->prev->y - pt->y); if (std::fabs(area) < min_area) { min_area = std::fabs(area); min_a = point_a; min_b = pt; } handle_self_intersections(min_a, min_b, dupe_ring, rings); return true; } } } handle_self_intersections(point_1, point_2, dupe_ring, rings); return true; } template void process_repeated_points(std::size_t first_index, std::size_t last_index, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings) { for (std::size_t j = first_index; j <= last_index; ++j) { while (process_repeated_point_set(first_index, last_index, j, dupe_ring, rings)) ; } #ifdef DEBUG // LCOV_EXCL_START for (std::size_t j = first_index; j <= last_index; ++j) { point_ptr op_j = rings.all_points[j]; if (!op_j->ring) { continue; } double ring_area = area(op_j->ring); bool ring_is_positive = ring_area > 0.0; bool ring_is_zero = value_is_zero(ring_area); if (!ring_is_zero) { if (op_j->ring->parent) { double parent_area = area(op_j->ring->parent); bool parent_is_positive = parent_area > 0.0; bool parent_is_zero = value_is_zero(parent_area); if (!parent_is_zero && ring_is_positive == parent_is_positive) { throw std::runtime_error( "Created a ring with a parent having the same orientation (sign of area)"); } } for (auto& c : op_j->ring->children) { double c_area = area(c); bool c_is_positive = c_area > 0.0; bool c_is_zero = value_is_zero(c_area); if (!c_is_zero && ring_is_positive == c_is_positive) { throw std::runtime_error( "Created a ring with a child having the same orientation (sign of area)"); } } } } // LCOV_EXCL_END #endif } template bool process_chains(std::size_t first_index, std::size_t last_index, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings, mapbox::geometry::point& rewind_point) { bool rewind = false; for (std::size_t j = first_index; j <= last_index; ++j) { point_ptr op_j = rings.all_points[j]; if (!op_j->ring) { continue; } for (std::size_t k = j + 1; k <= last_index; ++k) { point_ptr op_k = rings.all_points[k]; if (!op_k->ring || !op_j->ring) { continue; } if (fix_intersects(dupe_ring, op_j, op_k, rings, rewind_point)) { rewind = true; } } } return rewind; } template bool process_collinear_edges(std::size_t first_index, std::size_t last_index, std::unordered_multimap, point_ptr_pair>& dupe_ring, ring_manager& rings, mapbox::geometry::point& rewind_point) { bool rewind = false; for (std::size_t j = first_index; j <= last_index; ++j) { point_ptr op_j = rings.all_points[j]; if (!op_j->ring) { continue; } for (std::size_t k = j + 1; k <= last_index; ++k) { point_ptr op_k = rings.all_points[k]; if (!op_k->ring || !op_j->ring) { continue; } if (handle_collinear_edges(op_j, op_k, dupe_ring, rings, rewind_point)) { rewind = true; } } } return rewind; } template bool index_is_after_point(std::size_t const& i, mapbox::geometry::point const& pt, ring_manager const& rings) { if (i == 0) { return false; } if (rings.all_points[i]->y == pt.y) { return rings.all_points[i]->x >= pt.x; } else { return rings.all_points[i]->y < pt.y; } } template void rewind_to_point(std::size_t& i, mapbox::geometry::point const& pt, ring_manager const& rings) { if (i >= rings.rings.size()) { i = rings.rings.size() - 1; } while (index_is_after_point(i, pt, rings)) { --i; } } template void remove_spikes_in_polygons(ring_ptr r, ring_manager& rings) { point_ptr first_point = r->points; remove_spikes(first_point); if (!first_point) { r->points = nullptr; r->area = std::numeric_limits::quiet_NaN(); remove_ring(r, rings); return; } point_ptr p = first_point->next; while (p != first_point) { remove_spikes(p); if (!p) { r->points = nullptr; r->area = std::numeric_limits::quiet_NaN(); remove_ring(r, rings); return; } if (p->ring && !first_point->ring) { first_point = p; } p = p->next; } } template void fixup_out_polygon(ring& ring, ring_manager& rings) { // FixupOutPolygon() - removes duplicate points and simplifies consecutive // parallel edges by removing the middle vertex. point_ptr lastOK = nullptr; ring.bottom_point = nullptr; point_ptr pp = ring.points; for (;;) { if (pp->prev == pp || pp->prev == pp->next) { // We now need to make sure any children rings to this are promoted and their hole // status is changed // promote_children_of_removed_ring(&ring, rings); remove_ring(&ring, rings); dispose_out_points(pp); ring.points = nullptr; return; } // test for duplicate points and collinear edges ... if ((*pp == *pp->next) || (*pp == *pp->prev) || (slopes_equal(*pp->prev, *pp, *pp->next))) { lastOK = nullptr; point_ptr tmp = pp; pp->prev->next = pp->next; pp->next->prev = pp->prev; pp = pp->prev; tmp->ring = nullptr; tmp->next = tmp; tmp->prev = tmp; } else if (pp == lastOK) { break; } else { if (!lastOK) { lastOK = pp; } pp = pp->next; } } ring.points = pp; } template void do_simple_polygons(ring_manager& rings) { // fix orientations ... for (auto& r : rings.rings) { if (!r.points) { continue; } std::size_t s = 0; if (ring_is_hole(&r) == (area_from_point(r.points, s) > 0)) { reverse_ring(r.points); } remove_spikes_in_polygons(&r, rings); r.area = std::numeric_limits::quiet_NaN(); } std::stable_sort(rings.all_points.begin(), rings.all_points.end(), point_ptr_cmp()); std::unordered_multimap, point_ptr_pair> dupe_ring; dupe_ring.reserve(rings.rings.size()); // Find sets of repeated points and process them std::size_t count = 0; for (std::size_t i = 1; i < rings.all_points.size(); ++i) { if (*rings.all_points[i] == *rings.all_points[i - 1]) { ++count; if (i < (rings.all_points.size() - 1)) { continue; } else { ++i; } } if (count == 0) { continue; } std::size_t first_index = i - count - 1; std::size_t last_index = i - 1; auto sort_begin = rings.all_points.begin(); std::advance(sort_begin, first_index); auto sort_end = rings.all_points.begin(); std::advance(sort_end, i); std::stable_sort(sort_begin, sort_end, point_ptr_depth_cmp()); process_repeated_points(first_index, last_index, dupe_ring, rings); mapbox::geometry::point rewind_point(std::numeric_limits::min(), std::numeric_limits::min()); bool do_rewind = false; if (process_chains(first_index, last_index, dupe_ring, rings, rewind_point)) { do_rewind = true; } if (process_collinear_edges(first_index, last_index, dupe_ring, rings, rewind_point)) { do_rewind = true; } if (do_rewind) { rewind_to_point(i, rewind_point, rings); } count = 0; } #if DEBUG // LCOV_EXCL_START for (auto& r : rings.rings) { if (!r.points) { continue; } double stored_area = area(&r); std::size_t s = 0; double calculated_area = area_from_point(r.points, s); if (!values_near_equal(stored_area, calculated_area)) { throw std::runtime_error("Difference in stored area vs calculated area!"); } } // LCOV_EXCL_END #endif for (auto& r : rings.rings) { if (!r.points) { continue; } fixup_out_polygon(r, rings); } } } } }