use vector_traits::num_traits::Float;
mod impls;
#[cfg(test)]
mod tests;
use crate::{LinestringError, linestring_2d::LineString2};
use itertools::Itertools;
use rayon::prelude::*;
use std::cmp::Ordering;
use vector_traits::{approx::*, prelude::*};
#[derive(PartialEq, Clone, Copy)]
pub enum Orientation {
Left,
Right,
Collinear,
}
impl Orientation {
#[inline(always)]
pub fn is_left_or_collinear(self) -> bool {
self == Orientation::Left || self == Orientation::Collinear
}
#[inline(always)]
pub fn is_left(self) -> bool {
self == Orientation::Left
}
}
fn find_lowest_x<T: GenericVector2>(vertices: &[T]) -> Result<(usize, T), LinestringError> {
if vertices.is_empty() {
return Err(LinestringError::InvalidData(
"No points to compare".to_string(),
));
}
let mut min_idx = 0;
let mut min_val = vertices[0];
for (idx, point) in vertices.iter().enumerate().skip(1) {
match point.x().partial_cmp(&min_val.x()) {
Some(Ordering::Less) => {
min_idx = idx;
min_val = *point;
}
Some(Ordering::Equal) => {
if point.y() < min_val.y() {
min_idx = idx;
min_val = *point;
}
}
Some(Ordering::Greater) => (),
_ => {
return Err(LinestringError::InvalidData(
format!(
"Comparison failed for x-coordinate ({},{}) vs ({},{})",
point.x(),
point.y(),
min_val.x(),
min_val.y()
)
.to_string(),
));
}
}
}
Ok((min_idx, min_val))
}
fn indexed_find_lowest_x<T: GenericVector2>(
vertices: &[T],
indices: &[u32],
) -> Result<(u32, T), LinestringError> {
if vertices.is_empty() || indices.is_empty() {
return Err(LinestringError::InvalidData(
"No points to compare".to_string(),
));
}
let mut min_idx_idx = 0_u32;
let mut min_val = vertices[indices[min_idx_idx as usize] as usize];
for (index_index, index) in indices.iter().enumerate().skip(1) {
let point = vertices[*index as usize];
match point.x().partial_cmp(&min_val.x()) {
Some(Ordering::Less) => {
min_idx_idx = index_index as u32;
min_val = point;
}
Some(Ordering::Equal) => {
if point.y() < min_val.y() {
min_idx_idx = index_index as u32;
min_val = point;
}
}
Some(Ordering::Greater) => (),
_ => {
return Err(LinestringError::InvalidData(
format!(
"Comparison failed for x-coordinate ({},{}) vs ({},{})",
point.x(),
point.y(),
min_val.x(),
min_val.y()
)
.to_string(),
));
}
}
}
Ok((min_idx_idx, min_val))
}
#[inline(always)]
pub fn point_orientation<T: GenericVector2>(a: T, b: T, c: T) -> Orientation {
let value = cross_2d(a, b, c);
if value == T::Scalar::ZERO || ulps_eq!(value, T::Scalar::ZERO) {
Orientation::Collinear
} else if value > T::Scalar::ZERO {
Orientation::Left
} else {
Orientation::Right
}
}
#[inline(always)]
pub fn is_point_left<T: GenericVector2>(a: T, b: T, c: T) -> bool {
cross_2d(a, b, c) > T::Scalar::ZERO
}
#[inline(always)]
pub fn is_point_left_allow_collinear<T: GenericVector2>(a: T, b: T, c: T) -> bool {
let result = cross_2d(a, b, c);
result >= T::Scalar::ZERO || ulps_eq!(result, T::Scalar::ZERO)
}
#[inline(always)]
pub fn cross_2d<T: GenericVector2>(a: T, b: T, c: T) -> T::Scalar {
(b - a).perp_dot(c - a)
}
pub fn gift_wrap<T: GenericVector2>(mut vertices: &[T]) -> Result<Vec<T>, LinestringError> {
if vertices.is_empty() {
return Ok(Vec::with_capacity(0));
}
if vertices[0] == vertices[vertices.len() - 1] {
vertices = &vertices[1..];
}
if vertices.len() <= 2 {
return Ok(vertices.to_vec());
}
if vertices.len() == 3 {
return if is_point_left(vertices[0], vertices[1], vertices[2]) {
Ok(vertices.to_vec())
} else {
Ok(vec![vertices[0], vertices[2], vertices[1]])
};
}
let starting_point = find_lowest_x(vertices)?.0;
let mut hull = Vec::with_capacity(vertices.len() / 2);
let mut visited: vob::Vob<u32> = vob::Vob::new_with_storage_type(vertices.len());
visited.resize(vertices.len(), false);
let mut point_on_hull = starting_point;
loop {
hull.push(vertices[point_on_hull]);
if point_on_hull != starting_point {
let _ = visited.set(point_on_hull, true);
}
let mut end_point = (point_on_hull + 1) % vertices.len();
for j in 0..vertices.len() {
if j == point_on_hull || j == end_point || visited[j] {
continue;
}
let orient =
point_orientation(vertices[point_on_hull], vertices[end_point], vertices[j]);
if orient == Orientation::Right {
end_point = j;
} else if orient == Orientation::Collinear {
let distance_sq_candidate = vertices[point_on_hull].distance_sq(vertices[j]);
let distance_sq_end_point =
vertices[point_on_hull].distance_sq(vertices[end_point]);
if distance_sq_candidate > distance_sq_end_point {
end_point = j;
} else if j != starting_point {
let _ = visited.set(j, true);
}
}
}
point_on_hull = end_point;
if end_point == starting_point {
hull.push(vertices[end_point]);
break;
}
}
Ok(hull)
}
pub fn indexed_gift_wrap<T: GenericVector2>(
vertices: &[T],
indices: &[u32],
) -> Result<Vec<u32>, LinestringError> {
let mut hull = indexed_gift_wrap_no_loop(vertices, indices)?;
if hull.len() > 1 {
let first = hull.first().unwrap();
hull.push(*first);
}
Ok(hull)
}
#[inline]
fn indexed_gift_wrap_no_loop<T: GenericVector2>(
vertices: &[T],
mut indices: &[u32],
) -> Result<Vec<u32>, LinestringError> {
if indices.len() <= 1 {
return Ok(indices.to_vec());
}
if indices.first().unwrap() == indices.last().unwrap() {
indices = &indices[0..indices.len() - 1];
}
if indices.len() <= 2 {
return Ok(indices.to_vec());
}
if indices.len() == 3 {
return if is_point_left(
vertices[indices[0] as usize],
vertices[indices[1] as usize],
vertices[indices[2] as usize],
) {
Ok(indices.to_vec())
} else {
Ok(vec![indices[0], indices[2], indices[1]])
};
}
let starting_index = indexed_find_lowest_x(vertices, indices)?.0;
let mut hull = Vec::with_capacity(indices.len() / 4);
let mut visited: vob::Vob<u32> = vob::Vob::new_with_storage_type(indices.len());
visited.resize(indices.len(), false);
let mut point_on_hull = starting_index;
loop {
hull.push(indices[point_on_hull as usize]);
if point_on_hull != starting_index {
let _ = visited.set(point_on_hull as usize, true);
}
let mut end_point = (point_on_hull + 1) % indices.len() as u32;
for j in 0..indices.len() as u32 {
if j == point_on_hull || j == end_point || visited[j as usize] {
continue;
}
let orient = point_orientation(
vertices[indices[point_on_hull as usize] as usize],
vertices[indices[end_point as usize] as usize],
vertices[indices[j as usize] as usize],
);
if orient == Orientation::Right {
end_point = j;
} else if orient == Orientation::Collinear {
let distance_sq_candidate = vertices[indices[point_on_hull as usize] as usize]
.distance_sq(vertices[indices[j as usize] as usize]);
let distance_sq_end_point = vertices[indices[point_on_hull as usize] as usize]
.distance_sq(vertices[indices[end_point as usize] as usize]);
if distance_sq_candidate > distance_sq_end_point {
end_point = j;
} else if j != starting_index {
let _ = visited.set(j as usize, true);
}
}
}
point_on_hull = end_point;
if end_point == starting_index {
break;
}
}
Ok(hull)
}
pub fn graham_scan<T: GenericVector2>(mut input_points: &[T]) -> Result<Vec<T>, LinestringError> {
if input_points.is_empty() {
return Ok(Vec::with_capacity(0));
}
if !input_points.is_empty() && (input_points[0] == input_points[input_points.len() - 1]) {
input_points = &input_points[1..];
}
if input_points.len() <= 2 {
return Ok(input_points.to_vec());
}
if input_points.len() == 3 {
return if is_point_left(input_points[0], input_points[1], input_points[2]) {
Ok(input_points.to_vec())
} else {
Ok(vec![input_points[0], input_points[2], input_points[1]])
};
}
let (_, starting_point) = find_lowest_x(input_points)?;
let mut input_points: Vec<(T, T::Scalar, T::Scalar)> = input_points
.iter()
.map(|&p| {
let diff = p - starting_point;
let angle = diff.y().atan2(diff.x());
let distance = starting_point.distance_sq(p);
(p, angle, distance)
})
.collect();
input_points.sort_unstable_by(|a, b| {
a.1.partial_cmp(&b.1)
.unwrap_or(Ordering::Equal)
.then_with(|| a.2.partial_cmp(&b.2).unwrap_or(Ordering::Equal))
});
let mut hull = Vec::<T>::with_capacity(input_points.len());
hull.push(starting_point);
for (p, _, _) in input_points.iter() {
if *p == starting_point {
continue;
}
while hull.len() > 1 {
let last = hull.len() - 1;
let cross = cross_2d(hull[last - 1], hull[last], *p);
if T::Scalar::ZERO > cross || ulps_eq!(cross, T::Scalar::ZERO) {
let _ = hull.pop();
} else {
break;
}
}
hull.push(*p);
}
if hull.len() > 1 {
hull.push(*hull.first().unwrap());
}
Ok(hull)
}
pub fn indexed_graham_scan<T: GenericVector2>(
vertices: &[T],
indices: &[u32],
) -> Result<Vec<u32>, LinestringError> {
let mut hull = indexed_graham_scan_no_loop(vertices, indices, None)?;
if hull.len() > 1 {
hull.push(*hull.first().unwrap());
}
Ok(hull)
}
#[inline]
pub fn indexed_graham_scan_no_loop<T: GenericVector2>(
vertices: &[T],
mut indices: &[u32],
start_index: Option<u32>,
) -> Result<Vec<u32>, LinestringError> {
if indices.len() <= 1 {
return Ok(indices.to_vec());
}
if indices.first().unwrap() == indices.last().unwrap() {
indices = &indices[0..indices.len() - 1];
}
if indices.len() <= 2 {
return Ok(indices.to_vec());
}
if indices.len() == 3 {
return if is_point_left(
vertices[indices[0] as usize],
vertices[indices[1] as usize],
vertices[indices[2] as usize],
) {
Ok(indices.to_vec())
} else {
Ok(vec![indices[0], indices[2], indices[1]])
};
}
let (start_index, start_point) = if let Some(start_index) = start_index {
(start_index, vertices[start_index as usize])
} else {
let (start_index_index, start_point) = indexed_find_lowest_x(vertices, indices)?;
(indices[start_index_index as usize], start_point)
};
let sorted_indices: Vec<u32> = indices
.iter()
.map(|i| {
let p = vertices[*i as usize];
let diff = p - start_point;
let angle = diff.y().atan2(diff.x());
let distance = start_point.distance_sq(p);
(*i, angle, distance)
})
.sorted_unstable_by(|a, b| {
a.1.partial_cmp(&b.1)
.unwrap_or(Ordering::Equal)
.then_with(|| a.2.partial_cmp(&b.2).unwrap_or(Ordering::Equal))
})
.map(|a| a.0)
.collect();
let mut hull = Vec::<u32>::with_capacity(vertices.len());
hull.push(start_index);
for i in sorted_indices.iter() {
if *i == start_index {
continue;
}
let p = vertices[*i as usize];
while hull.len() > 1 {
let last_in_hull = hull.len() - 1;
let cross = cross_2d(
vertices[hull[last_in_hull - 1] as usize],
vertices[hull[last_in_hull] as usize],
p,
);
if T::Scalar::ZERO > cross || ulps_eq!(cross, T::Scalar::ZERO) {
let _ = hull.pop();
} else {
break;
}
}
hull.push(*i);
}
Ok(hull)
}
#[derive(Clone)]
struct VertexIndex {
from_a: bool,
index: u32,
index_index: u32,
}
fn combine_indexed_convex_hull<T: GenericVector2>(
vertices: &[T],
indices_a: Result<Vec<u32>, LinestringError>,
indices_b: Result<Vec<u32>, LinestringError>,
) -> Result<Vec<u32>, LinestringError> {
let indices_a = indices_a?;
let indices_b = indices_b?;
if indices_a.is_empty() {
return Ok(indices_b);
}
if indices_b.is_empty() {
return Ok(indices_a);
}
let starting_index = VertexIndex {
from_a: true,
index: indices_a[0],
index_index: 0,
};
let mut hull = Vec::with_capacity((indices_a.len() + indices_b.len()) / 4);
let mut point_on_hull = starting_index.clone();
loop {
hull.push(point_on_hull.index);
if point_on_hull.index != starting_index.index {
}
let mut end_point = {
if point_on_hull.from_a {
let index_index = (point_on_hull.index_index + 1) % indices_a.len() as u32;
let index = indices_a[index_index as usize];
if index != point_on_hull.index {
VertexIndex {
from_a: true,
index: indices_a[index_index as usize],
index_index,
}
} else {
VertexIndex {
from_a: false,
index: indices_b[0],
index_index: 0,
}
}
} else {
let index_index = (point_on_hull.index_index + 1) % indices_b.len() as u32;
let index = indices_b[index_index as usize];
if index != point_on_hull.index {
VertexIndex {
from_a: false,
index: indices_b[index_index as usize],
index_index,
}
} else {
VertexIndex {
from_a: true,
index: indices_a[0],
index_index: 0,
}
}
}
};
if point_on_hull.from_a {
let mut found_a_legit_b_point = false;
for (jj, j) in indices_b.iter().enumerate() {
let j = *j;
let jj = jj as u32;
if j == point_on_hull.index || j == end_point.index
{
continue;
}
let orient = point_orientation(
vertices[point_on_hull.index as usize],
vertices[end_point.index as usize],
vertices[j as usize],
);
if orient == Orientation::Right {
end_point.index_index = jj;
end_point.index = j;
end_point.from_a = false;
found_a_legit_b_point = true;
} else if orient == Orientation::Collinear {
let distance_sq_candidate =
vertices[point_on_hull.index as usize].distance_sq(vertices[j as usize]);
let distance_sq_end_point = vertices[point_on_hull.index as usize]
.distance_sq(vertices[end_point.index as usize]);
if distance_sq_candidate > distance_sq_end_point {
end_point.index_index = jj;
end_point.index = j;
end_point.from_a = false;
found_a_legit_b_point = true;
} else if j != starting_index.index {
}
} else {
if found_a_legit_b_point {
break;
}
}
}
} else {
let mut found_a_legit_a_point = false;
for (jj, j) in indices_a.iter().enumerate() {
let j = *j;
let jj = jj as u32;
if j == point_on_hull.index || j == end_point.index
{
continue;
}
let orient = point_orientation(
vertices[point_on_hull.index as usize],
vertices[end_point.index as usize],
vertices[j as usize],
);
if orient == Orientation::Right {
end_point.index_index = jj;
end_point.index = j;
end_point.from_a = true;
found_a_legit_a_point = true;
} else if orient == Orientation::Collinear {
let distance_sq_candidate =
vertices[point_on_hull.index as usize].distance_sq(vertices[j as usize]);
let distance_sq_end_point = vertices[point_on_hull.index as usize]
.distance_sq(vertices[end_point.index as usize]);
if distance_sq_candidate > distance_sq_end_point {
end_point.index_index = jj;
end_point.index = j;
end_point.from_a = true;
found_a_legit_a_point = true;
} else if j != starting_index.index {
}
} else {
if found_a_legit_a_point {
break;
}
}
}
}
point_on_hull = end_point.clone();
if end_point.index == starting_index.index {
break;
}
}
Ok(hull)
}
pub fn convex_hull_par<T: GenericVector2>(
vertices: &[T],
mut indices: &[u32],
per_thread_chunk_size: usize,
) -> Result<Vec<u32>, LinestringError> {
if indices.len() <= 1 {
return Ok(indices.to_vec());
}
if indices.first().unwrap() == indices.last().unwrap() {
indices = &indices[0..indices.len() - 1];
}
if indices.len() <= 2 {
return Ok(indices.to_vec());
}
if indices.len() == 3 {
return if is_point_left(
vertices[indices[0] as usize],
vertices[indices[1] as usize],
vertices[indices[2] as usize],
) {
Ok(indices.to_vec())
} else {
Ok(vec![indices[0], indices[2], indices[1]])
};
}
let chunks: Vec<_> = indices.chunks(per_thread_chunk_size).collect();
let start_index = {
let start_indexes = chunks
.par_iter()
.map(|chunk| {
indexed_find_lowest_x(vertices, chunk)
.map(|(index_index, _)| chunk[index_index as usize])
})
.collect::<Result<Vec<u32>, LinestringError>>()?;
start_indexes[indexed_find_lowest_x(vertices, &start_indexes)?.0 as usize]
};
let mut hull = chunks
.into_par_iter()
.map(|chunk| indexed_graham_scan_no_loop(vertices, chunk, None))
.reduce(
|| Ok(vec![start_index]),
|result, partial_hull| {
match result {
Ok(indices) => combine_indexed_convex_hull(vertices, Ok(indices), partial_hull),
Err(some_err) => Err(some_err), }
},
)?;
if hull.len() > 1 {
hull.push(*hull.first().unwrap());
}
Ok(hull)
}
pub fn contains_convex_hull<T: GenericVector2>(a: &Vec<T>, b: &[T]) -> bool {
if a.point_count() <= 1 {
return false;
}
if b.is_empty() {
return true;
}
let a_midpoint = a.len() / 2;
let b_midpoint = b.len() / 2;
let a_mid_edge = (a[a_midpoint - 1], a[a_midpoint]);
if !is_point_left_allow_collinear(a_mid_edge.0, a_mid_edge.1, b[0])
|| !is_point_left_allow_collinear(a_mid_edge.0, a_mid_edge.1, b[b_midpoint])
{
return false;
}
b.iter().all(|p| contains_point_inclusive(a, *p))
}
pub fn contains_convex_hull_par<T: GenericVector2>(a: &Vec<T>, b: &Vec<T>) -> bool {
if a.point_count() <= 1 {
return false;
}
if b.is_empty() {
return true;
}
b.par_iter().all(|p| contains_point_inclusive(a, *p))
}
pub fn contains_point_exclusive<T: GenericVector2>(input: &[T], p: T) -> bool {
match input.len() {
0 => return false,
1 => return p == input[0],
2 => {
if input[0] != input[1] {
return false;
}
}
_ => (),
};
let (mut slice, input_offset) = if input[0] == input[input.len() - 1] {
(&input[1..], 1)
} else {
(input, 0)
};
let mut retain_index_0 = false;
while (slice.len() > 3) || (retain_index_0 && slice.len() > 2) {
let mid = if retain_index_0 {
slice.len().div_ceil(2) - 1
} else {
slice.len() / 2
};
if !is_point_left_allow_collinear(slice[mid - 1], slice[mid], p) {
return false;
}
if (retain_index_0 && is_point_left_allow_collinear(input[input_offset], slice[mid], p))
|| (!retain_index_0 && is_point_left_allow_collinear(slice[0], slice[mid], p))
{
slice = &slice[mid..];
retain_index_0 = true;
} else {
slice = &slice[..mid + 1];
}
}
if retain_index_0 {
if !is_point_left(input[input_offset], slice[0], p) {
return false;
}
for window in slice.windows(2) {
if !is_point_left(window[0], window[1], p) {
return false;
}
}
if !is_point_left(slice[slice.len() - 1], input[input_offset], p) {
return false;
}
} else {
for window in slice.windows(2) {
if !is_point_left(window[0], window[1], p) {
return false;
}
}
if !is_point_left(slice[slice.len() - 1], slice[0], p) {
return false;
}
}
true
}
pub fn contains_point_inclusive<T: GenericVector2>(input: &[T], p: T) -> bool {
match input.len() {
0 => return false,
1 => return p == input[0],
2 => {
if input[0] != input[1] {
return false;
}
}
_ => (),
};
let (mut slice, input_offset) = if input[0] == input[input.len() - 1] {
(&input[1..], 1)
} else {
(input, 0)
};
let mut retain_index_0 = false;
while (slice.len() > 3) || (retain_index_0 && slice.len() > 2) {
let mid = if retain_index_0 {
slice.len().div_ceil(2) - 1
} else {
slice.len() / 2
};
if !is_point_left_allow_collinear(slice[mid - 1], slice[mid], p) {
return false;
}
if (retain_index_0 && is_point_left_allow_collinear(input[input_offset], slice[mid], p))
|| (!retain_index_0 && is_point_left_allow_collinear(slice[0], slice[mid], p))
{
slice = &slice[mid..];
retain_index_0 = true;
} else {
slice = &slice[..mid + 1];
}
}
if retain_index_0 {
if !is_point_left_allow_collinear(input[input_offset], slice[0], p) {
return false;
}
for window in slice.windows(2) {
if !is_point_left_allow_collinear(window[0], window[1], p) {
return false;
}
}
if !is_point_left_allow_collinear(slice[slice.len() - 1], input[input_offset], p) {
return false;
}
} else {
for window in slice.windows(2) {
if !is_point_left_allow_collinear(window[0], window[1], p) {
return false;
}
}
if !is_point_left_allow_collinear(slice[slice.len() - 1], slice[0], p) {
return false;
}
}
true
}