mirror of
https://github.com/mapbox/tippecanoe.git
synced 2025-02-02 09:18:21 +00:00
1988 lines
74 KiB
C++
1988 lines
74 KiB
C++
#pragma once
|
|
|
|
#define _USE_MATH_DEFINES
|
|
#include <cmath>
|
|
|
|
#include <algorithm>
|
|
#include <list>
|
|
#include <map>
|
|
#include <set>
|
|
#include <unordered_map>
|
|
#include <utility>
|
|
|
|
#include <mapbox/geometry/wagyu/config.hpp>
|
|
#include <mapbox/geometry/wagyu/ring.hpp>
|
|
#include <mapbox/geometry/wagyu/ring_util.hpp>
|
|
|
|
#ifdef DEBUG
|
|
#include <iostream>
|
|
#include <sstream>
|
|
#endif
|
|
|
|
namespace mapbox {
|
|
namespace geometry {
|
|
namespace wagyu {
|
|
|
|
template <typename T>
|
|
struct point_ptr_pair {
|
|
point_ptr<T> op1;
|
|
point_ptr<T> op2;
|
|
|
|
constexpr point_ptr_pair(point_ptr<T> o1, point_ptr<T> o2) : op1(o1), op2(o2) {
|
|
}
|
|
|
|
point_ptr_pair(point_ptr_pair<T> const& p) = default;
|
|
|
|
point_ptr_pair(point_ptr_pair<T>&& p) : op1(std::move(p.op1)), op2(std::move(p.op2)) {
|
|
}
|
|
|
|
point_ptr_pair& operator=(point_ptr_pair<T>&& p) {
|
|
op1 = std::move(p.op1);
|
|
op2 = std::move(p.op2);
|
|
return *this;
|
|
}
|
|
};
|
|
|
|
#ifdef DEBUG
|
|
|
|
template <class charT, class traits, typename T>
|
|
inline std::basic_ostream<charT, traits>&
|
|
operator<<(std::basic_ostream<charT, traits>& out,
|
|
const std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& 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 <typename T>
|
|
bool find_intersect_loop(std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
std::list<std::pair<ring_ptr<T>, point_ptr_pair<T>>>& iList,
|
|
ring_ptr<T> ring_parent,
|
|
ring_ptr<T> ring_origin,
|
|
ring_ptr<T> ring_search,
|
|
std::set<ring_ptr<T>>& visited,
|
|
point_ptr<T> orig_pt,
|
|
point_ptr<T> prev_pt,
|
|
ring_manager<T>& rings) {
|
|
{
|
|
auto range = dupe_ring.equal_range(ring_search);
|
|
// Check for direct connection
|
|
for (auto& it = range.first; it != range.second;) {
|
|
ring_ptr<T> it_ring1 = it->second.op1->ring;
|
|
ring_ptr<T> 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<T> 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 <typename T>
|
|
void remove_spikes(point_ptr<T>& pt) {
|
|
ring_ptr<T> r = pt->ring;
|
|
while (true) {
|
|
if (pt->next == pt) {
|
|
r->points = nullptr;
|
|
r->area = std::numeric_limits<double>::quiet_NaN();
|
|
pt->ring = nullptr;
|
|
pt = nullptr;
|
|
break;
|
|
} else if (*(pt) == *(pt->next)) {
|
|
point_ptr<T> 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<double>::quiet_NaN();
|
|
old_next->ring = nullptr;
|
|
} else if (*(pt) == *(pt->prev)) {
|
|
point_ptr<T> 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<double>::quiet_NaN();
|
|
old_prev->ring = nullptr;
|
|
} else if (*(pt->next) == *(pt->prev)) {
|
|
point_ptr<T> next = pt->next;
|
|
point_ptr<T> prev = pt->prev;
|
|
next->prev = prev;
|
|
prev->next = next;
|
|
if (r->points == pt) {
|
|
r->points = prev;
|
|
}
|
|
r->area = std::numeric_limits<double>::quiet_NaN();
|
|
pt->ring = nullptr;
|
|
pt->next = pt;
|
|
pt->prev = pt;
|
|
pt = next;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void fixup_children_complex(ring_ptr<T> old_ring,
|
|
ring_ptr<T> new_ring,
|
|
ring_vector<T> const& grand_children,
|
|
ring_manager<T> 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 <typename T>
|
|
bool fix_intersects(std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
point_ptr<T> op_j,
|
|
point_ptr<T> op_k,
|
|
ring_manager<T>& rings,
|
|
mapbox::geometry::point<T>& rewind_point) {
|
|
ring_ptr<T> ring_j = op_j->ring;
|
|
ring_ptr<T> 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<T> ring_origin;
|
|
ring_ptr<T> ring_search;
|
|
ring_ptr<T> ring_parent;
|
|
point_ptr<T> op_origin_1;
|
|
point_ptr<T> 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<std::pair<ring_ptr<T>, point_ptr_pair<T>>> 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<T> 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<ring_ptr<T>> 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<T> 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<T> intPt_origin = { op_origin_1, op_origin_2 };
|
|
point_ptr_pair<T> 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<T> it_ring2 = it->second.op2->ring;
|
|
if (it_ring2 == ring_search) {
|
|
missing = false;
|
|
}
|
|
}
|
|
if (missing) {
|
|
point_ptr_pair<T> 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<T> ring_itr = iRing.first;
|
|
if (!ring_is_hole(ring_itr)) {
|
|
// Make the hole the origin!
|
|
point_ptr<T> op1 = op_origin_1;
|
|
op_origin_1 = iRing.second.op1;
|
|
iRing.second.op1 = op1;
|
|
point_ptr<T> 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<T> op_origin_1_next = op_origin_1->next;
|
|
point_ptr<T> 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<T> 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<T> op_search_1 = iRing.second.op1;
|
|
point_ptr<T> op_search_2 = iRing.second.op2;
|
|
point_ptr<T> op_search_1_next = op_search_1->next;
|
|
point_ptr<T> 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<double>::quiet_NaN();
|
|
remove_ring(ring_origin, rings);
|
|
for (auto& iRing : iList) {
|
|
ring_ptr<T> ring_itr = iRing.first;
|
|
ring_itr->points = nullptr;
|
|
ring_itr->area = std::numeric_limits<double>::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<double>::quiet_NaN();
|
|
update_points_ring(ring_origin);
|
|
for (auto& iRing : iList) {
|
|
ring_ptr<T> ring_itr = iRing.first;
|
|
ring_itr->points = nullptr;
|
|
ring_itr->area = std::numeric_limits<double>::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<T> 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<T> grand_children;
|
|
for (auto& iRing : iList) {
|
|
ring_ptr<T> ring_itr = iRing.first;
|
|
ring_itr->points = nullptr;
|
|
ring_itr->area = std::numeric_limits<double>::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<std::pair<ring_ptr<T>, point_ptr_pair<T>>> 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<T> it_ring = it->second.op1->ring;
|
|
ring_ptr<T> 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<T> it_ring = it->second.op1->ring;
|
|
ring_ptr<T> 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 <typename T>
|
|
struct point_ptr_cmp {
|
|
inline bool operator()(point_ptr<T> op1, point_ptr<T> 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 <typename T>
|
|
struct point_ptr_depth_cmp {
|
|
inline bool operator()(point_ptr<T> op1, point_ptr<T> 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 <typename T>
|
|
void update_duplicate_point_entries(
|
|
ring_ptr<T> ring, std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring) {
|
|
auto range = dupe_ring.equal_range(ring);
|
|
std::list<std::pair<ring_ptr<T>, point_ptr_pair<T>>> move_list;
|
|
for (auto& it = range.first; it != range.second;) {
|
|
ring_ptr<T> it_ring = it->second.op1->ring;
|
|
ring_ptr<T> 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 <typename T>
|
|
bool parent_in_tree(ring_ptr<T> r, ring_ptr<T> possible_parent) {
|
|
|
|
ring_ptr<T> current_ring = r->parent;
|
|
while (current_ring != nullptr) {
|
|
if (current_ring == possible_parent) {
|
|
return true;
|
|
}
|
|
current_ring = current_ring->parent;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
template <typename T>
|
|
void fixup_children_sibling(ring_ptr<T> old_ring, ring_ptr<T> 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 <typename T>
|
|
void fixup_parents_siblings(ring_ptr<T> parent, ring_ptr<T> ring, ring_manager<T>& 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<T> 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 <typename T>
|
|
void fixup_children_new_interior_ring(ring_ptr<T> old_ring,
|
|
ring_ptr<T> new_ring,
|
|
ring_manager<T>& 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<T> 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 <typename T>
|
|
void check_if_intersections_cross(point_ptr<T> p1, point_ptr<T> p2) {
|
|
// LCOV_EXCL_START
|
|
point_ptr<T> p1_next = p1->next;
|
|
point_ptr<T> p2_next = p2->next;
|
|
point_ptr<T> p1_prev = p1->prev;
|
|
point_ptr<T> 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<double>(p1_prev->y - p1->y),
|
|
static_cast<double>(p1_prev->x - p1->x));
|
|
double a2_p1 = std::atan2(static_cast<double>(p1_next->y - p1->y),
|
|
static_cast<double>(p1_next->x - p1->x));
|
|
double a1_p2 = std::atan2(static_cast<double>(p2_prev->y - p2->y),
|
|
static_cast<double>(p2_prev->x - p2->x));
|
|
double a2_p2 = std::atan2(static_cast<double>(p2_next->y - p2->y),
|
|
static_cast<double>(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 <typename T>
|
|
void handle_self_intersections(point_ptr<T> op,
|
|
point_ptr<T> op2,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& rings) {
|
|
// Check that are same ring
|
|
assert(op->ring == op2->ring);
|
|
ring_ptr<T> 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<T> op3 = op->prev;
|
|
point_ptr<T> 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<double>::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<double>::quiet_NaN();
|
|
update_duplicate_point_entries(ring, dupe_ring);
|
|
return;
|
|
} else if (op2 == nullptr) {
|
|
ring->points = op;
|
|
ring->area = std::numeric_limits<double>::quiet_NaN();
|
|
update_duplicate_point_entries(ring, dupe_ring);
|
|
return;
|
|
}
|
|
|
|
ring_ptr<T> 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 <typename T>
|
|
mapbox::geometry::point<T> find_rewind_point(point_ptr<T> pt) {
|
|
mapbox::geometry::point<T> rewind;
|
|
rewind.x = pt->x;
|
|
rewind.y = pt->y;
|
|
point_ptr<T> 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 <typename T>
|
|
bool handle_collinear_edges(point_ptr<T> pt1,
|
|
point_ptr<T> pt2,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& rings,
|
|
mapbox::geometry::point<T>& rewind_point) {
|
|
ring_ptr<T> ring1 = pt1->ring;
|
|
ring_ptr<T> 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<T> rewind_1 = find_rewind_point(pt1);
|
|
mapbox::geometry::point<T> rewind_2 = find_rewind_point(pt2);
|
|
|
|
// The lower right of the two points is the rewind point.
|
|
mapbox::geometry::point<T> 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<T> pt3 = pt1->prev;
|
|
point_ptr<T> 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<double>::quiet_NaN();
|
|
remove_ring(ring1, rings);
|
|
ring2->points = nullptr;
|
|
ring2->area = std::numeric_limits<double>::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<double>::quiet_NaN();
|
|
remove_ring(ring1, rings);
|
|
ring2->points = nullptr;
|
|
ring2->area = std::numeric_limits<double>::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<double>::quiet_NaN();
|
|
ring2->area = std::numeric_limits<double>::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 <typename T>
|
|
bool point_2_is_between_point_1_and_point_3(point_ptr<T> pt1, point_ptr<T> pt2, point_ptr<T> 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 <typename T>
|
|
inline orientation_type orientation_of_points(point_ptr<T> p, point_ptr<T> q, point_ptr<T> 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 <typename T>
|
|
using si_point_vector = std::vector<std::pair<point_ptr<T>, bool>>;
|
|
|
|
#ifdef DEBUG
|
|
|
|
template <typename T>
|
|
std::string output_si_angles(point_ptr<T> pt) {
|
|
// LCOV_EXCL_START
|
|
std::ostringstream out;
|
|
double prev_angle = std::atan2(static_cast<double>(pt->prev->y - pt->y),
|
|
static_cast<double>(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<double>(pt->next->y - pt->y),
|
|
static_cast<double>(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 <typename T>
|
|
std::string output_compare_si_angles(point_ptr<T> pt, point_ptr<T> compare) {
|
|
std::ostringstream out;
|
|
double cmp_prev_angle = std::atan2(static_cast<double>(compare->prev->y - compare->y),
|
|
static_cast<double>(compare->prev->x - compare->x));
|
|
double prev_angle = std::atan2(static_cast<double>(pt->prev->y - pt->y),
|
|
static_cast<double>(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<double>(compare->next->y - compare->y),
|
|
static_cast<double>(compare->next->x - compare->x));
|
|
double next_angle = std::atan2(static_cast<double>(pt->next->y - pt->y),
|
|
static_cast<double>(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 <class charT, class traits, typename T>
|
|
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& out,
|
|
const si_point_vector<T>& 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 <class charT, class traits>
|
|
inline std::basic_ostream<charT, traits>& operator<<(std::basic_ostream<charT, traits>& 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 <typename T>
|
|
bool clockwise_of_next(point_ptr<T> const& origin, point_ptr<T> 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 <typename T>
|
|
inline bool cw_p1p2_prev_collinear_spike(point_ptr<T> const& origin,
|
|
point_ptr<T> const& next,
|
|
point_ptr<T> const& p1,
|
|
point_ptr<T> 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 <typename T>
|
|
inline bool ccw_p1p2_prev_collinear_spike(point_ptr<T> const& origin,
|
|
point_ptr<T> const& next,
|
|
point_ptr<T> const& p1,
|
|
point_ptr<T> 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 <typename T>
|
|
struct si_point_sorter {
|
|
|
|
point_ptr<T> origin;
|
|
point_ptr<T> next;
|
|
|
|
si_point_sorter(point_ptr<T> 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<point_ptr<T>, bool> const& pp1,
|
|
std::pair<point_ptr<T>, bool> const& pp2) {
|
|
// Because a next must be paired with a previous, we are only
|
|
// caring first about previous segments for ordering
|
|
point_ptr<T> p1 = pp1.first;
|
|
point_ptr<T> 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 <typename T>
|
|
si_point_vector<T> build_si_point_vector(std::size_t first_index,
|
|
std::size_t last_index,
|
|
std::size_t current_index,
|
|
ring_ptr<T> match_ring,
|
|
ring_manager<T>& rings) {
|
|
si_point_vector<T> point_vec;
|
|
point_ptr<T> origin = rings.all_points[current_index];
|
|
for (std::size_t j = first_index; j <= last_index; ++j) {
|
|
if (j == current_index) {
|
|
continue;
|
|
}
|
|
point_ptr<T> 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 <typename T>
|
|
bool process_repeated_point_set(std::size_t first_index,
|
|
std::size_t last_index,
|
|
std::size_t current_index,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& rings) {
|
|
point_ptr<T> point_1 = rings.all_points[current_index];
|
|
if (point_1->ring == nullptr) {
|
|
return false;
|
|
}
|
|
|
|
// Build point vector
|
|
si_point_vector<T> 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<T>(point_1));
|
|
|
|
auto vec_itr = vec.begin();
|
|
point_ptr<T> 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<T> 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<T> point_a = point_1;
|
|
point_ptr<T> min_a = nullptr;
|
|
point_ptr<T> min_b = nullptr;
|
|
point_ptr<T> pt = point_1->next;
|
|
double area = 0.0;
|
|
double min_area = std::numeric_limits<double>::max();
|
|
while (pt != point_1) {
|
|
area += static_cast<double>(pt->prev->x + pt->x) *
|
|
static_cast<double>(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<double>(pt->prev->x + pt->x) *
|
|
static_cast<double>(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 <typename T>
|
|
void process_repeated_points(std::size_t first_index,
|
|
std::size_t last_index,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& 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<T> 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 <typename T>
|
|
bool process_chains(std::size_t first_index,
|
|
std::size_t last_index,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& rings,
|
|
mapbox::geometry::point<T>& rewind_point) {
|
|
bool rewind = false;
|
|
for (std::size_t j = first_index; j <= last_index; ++j) {
|
|
point_ptr<T> op_j = rings.all_points[j];
|
|
if (!op_j->ring) {
|
|
continue;
|
|
}
|
|
for (std::size_t k = j + 1; k <= last_index; ++k) {
|
|
point_ptr<T> 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 <typename T>
|
|
bool process_collinear_edges(std::size_t first_index,
|
|
std::size_t last_index,
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>>& dupe_ring,
|
|
ring_manager<T>& rings,
|
|
mapbox::geometry::point<T>& rewind_point) {
|
|
bool rewind = false;
|
|
for (std::size_t j = first_index; j <= last_index; ++j) {
|
|
point_ptr<T> op_j = rings.all_points[j];
|
|
if (!op_j->ring) {
|
|
continue;
|
|
}
|
|
for (std::size_t k = j + 1; k <= last_index; ++k) {
|
|
point_ptr<T> 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 <typename T>
|
|
bool index_is_after_point(std::size_t const& i,
|
|
mapbox::geometry::point<T> const& pt,
|
|
ring_manager<T> 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 <typename T>
|
|
void rewind_to_point(std::size_t& i,
|
|
mapbox::geometry::point<T> const& pt,
|
|
ring_manager<T> const& rings) {
|
|
if (i >= rings.rings.size()) {
|
|
i = rings.rings.size() - 1;
|
|
}
|
|
while (index_is_after_point(i, pt, rings)) {
|
|
--i;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void remove_spikes_in_polygons(ring_ptr<T> r, ring_manager<T>& rings) {
|
|
|
|
point_ptr<T> first_point = r->points;
|
|
remove_spikes(first_point);
|
|
if (!first_point) {
|
|
r->points = nullptr;
|
|
r->area = std::numeric_limits<double>::quiet_NaN();
|
|
remove_ring(r, rings);
|
|
return;
|
|
}
|
|
point_ptr<T> p = first_point->next;
|
|
while (p != first_point) {
|
|
remove_spikes(p);
|
|
if (!p) {
|
|
r->points = nullptr;
|
|
r->area = std::numeric_limits<double>::quiet_NaN();
|
|
remove_ring(r, rings);
|
|
return;
|
|
}
|
|
if (p->ring && !first_point->ring) {
|
|
first_point = p;
|
|
}
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
void fixup_out_polygon(ring<T>& ring, ring_manager<T>& rings) {
|
|
// FixupOutPolygon() - removes duplicate points and simplifies consecutive
|
|
// parallel edges by removing the middle vertex.
|
|
point_ptr<T> lastOK = nullptr;
|
|
ring.bottom_point = nullptr;
|
|
point_ptr<T> 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<T> 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 <typename T>
|
|
void do_simple_polygons(ring_manager<T>& 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<double>::quiet_NaN();
|
|
}
|
|
|
|
std::stable_sort(rings.all_points.begin(), rings.all_points.end(), point_ptr_cmp<T>());
|
|
std::unordered_multimap<ring_ptr<T>, point_ptr_pair<T>> 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<T>());
|
|
process_repeated_points(first_index, last_index, dupe_ring, rings);
|
|
mapbox::geometry::point<T> rewind_point(std::numeric_limits<T>::min(),
|
|
std::numeric_limits<T>::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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|