#![deny(non_camel_case_types)]
#![deny(unused_parens)]
#![deny(non_upper_case_globals)]
#![deny(unused_qualifications)]
#![deny(unused_results)]
#![deny(unused_imports)]
use core::fmt;
use geo::algorithm::intersects::Intersects;
use num_traits::{Float, Zero};
use thiserror::Error;
pub mod algorithm;
#[derive(Error, Debug)]
pub enum IntersectError {
#[error("Something bad happened")]
InternalError(String),
#[error("No NaN, inf etc. are allowed")]
InvalidData(String),
#[error("When searching for intersections in LineStrings the 'ignore_end_point_intersections' parameter must be set to 'true'.")]
InvalidSearchParameter(String),
#[error("Results already taken from the algorithm data struct")]
ResultsAlreadyTaken(String),
}
#[allow(dead_code)]
pub fn to_lines<U, T>(points: &[[U; 4]]) -> Vec<geo::Line<T>>
where
U: num_traits::ToPrimitive + Copy,
T: Float + approx::UlpsEq + geo::CoordFloat,
T::Epsilon: Copy,
{
let mut rv = Vec::with_capacity(points.len());
for p in points.iter() {
rv.push(geo::Line::<T>::new(
geo::Coordinate {
x: T::from(p[0]).unwrap(),
y: T::from(p[1]).unwrap(),
},
geo::Coordinate {
x: T::from(p[2]).unwrap(),
y: T::from(p[3]).unwrap(),
},
));
}
rv
}
pub fn intersect_line_point<T>(
line: &geo::Line<T>,
point: &geo::Coordinate<T>,
) -> Option<Intersection<T>>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
if approx::ulps_eq!(&line.start.x, &point.x) && approx::ulps_eq!(&line.start.y, &point.y) {
return Some(Intersection::Intersection(*point));
}
if approx::ulps_eq!(&line.end.x, &point.x) && approx::ulps_eq!(&line.end.y, &point.y) {
return Some(Intersection::Intersection(*point));
}
let x1 = line.start.x;
let x2 = line.end.x;
let y1 = line.start.y;
let y2 = line.end.y;
let x = point.x;
let y = point.y;
let ab = ((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)).sqrt();
let ap = ((x - x1) * (x - x1) + (y - y1) * (y - y1)).sqrt();
let pb = ((x2 - x) * (x2 - x) + (y2 - y) * (y2 - y)).sqrt();
#[cfg(feature = "console_trace")]
println!("ab={:?}, ap={:?}, pb={:?}, ap+pb={:?}", ab, ap, pb, ap + pb);
if approx::ulps_eq!(&ab, &(ap + pb)) {
return Some(Intersection::Intersection(*point));
}
None
}
#[allow(dead_code)]
pub enum Intersection<T>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
Intersection(geo::Coordinate<T>),
OverLap(geo::Line<T>),
}
impl<T> Intersection<T>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
pub fn single(&self) -> geo::Coordinate<T> {
match self {
Self::OverLap(a) => a.start,
Self::Intersection(a) => *a,
}
}
}
impl<T> fmt::Debug for Intersection<T>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::OverLap(a) => a.fmt(f),
Self::Intersection(a) => a.fmt(f),
}
}
}
#[allow(clippy::many_single_char_names)]
pub fn intersect<T>(one: &geo::Line<T>, other: &geo::Line<T>) -> Option<Intersection<T>>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
#[allow(clippy::suspicious_operation_groupings)]
{
if one.end.x > other.end.x
&& one.end.x > other.start.x
&& one.start.x > other.end.x
&& one.start.x > other.start.x
{
return None;
}
if one.end.x < other.end.x
&& one.end.x < other.start.x
&& one.start.x < other.end.x
&& one.start.x < other.start.x
{
return None;
}
if one.end.y > other.end.y
&& one.end.y > other.start.y
&& one.start.y > other.end.y
&& one.start.y > other.start.y
{
return None;
}
if one.end.y < other.end.y
&& one.end.y < other.start.y
&& one.start.y < other.end.y
&& one.start.y < other.start.y
{
return None;
}
}
let p = one.start;
let q = other.start;
let r = one.end - p;
let s = other.end - q;
let r_cross_s = cross_z(&r, &s);
let q_minus_p = q - p;
let q_minus_p_cross_r = cross_z(&q_minus_p, &r);
if approx::ulps_eq!(&r_cross_s, &T::zero()) {
let one_is_a_point = ulps_eq_c(&one.start, &one.end);
let other_is_a_point = ulps_eq_c(&other.start, &other.end);
if one_is_a_point || other_is_a_point {
if one_is_a_point && other_is_a_point && ulps_eq_c(&one.start, &other.start) {
return Some(Intersection::Intersection(one.start));
}
return if one_is_a_point {
intersect_line_point(other, &one.start)
} else {
intersect_line_point(one, &other.start)
};
}
if approx::ulps_eq!(&q_minus_p_cross_r, &T::zero()) {
let r_dot_r = dot(&r, &r);
let r_div_r_dot_r = div(&r, r_dot_r);
let s_dot_r = dot(&s, &r);
let t0 = dot(&q_minus_p, &r_div_r_dot_r);
let t1 = t0 + s_dot_r / r_dot_r;
Some(Intersection::OverLap(geo::Line::new(
scale_to_coordinate(&p, &r, t0),
scale_to_coordinate(&p, &r, t1),
)))
} else {
None
}
} else {
let t = cross_z(&q_minus_p, &div(&s, r_cross_s));
let u = cross_z(&q_minus_p, &div(&r, r_cross_s));
if T::zero() <= t && t <= T::one() && T::zero() <= u && u <= T::one() {
Some(Intersection::Intersection(scale_to_coordinate(&p, &r, t)))
} else {
None
}
}
}
#[inline(always)]
pub fn scale_to_coordinate<T>(
point: &geo::Coordinate<T>,
vector: &geo::Coordinate<T>,
scale: T,
) -> geo::Coordinate<T>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
geo::Coordinate {
x: point.x + scale * vector.x,
y: point.y + scale * vector.y,
}
}
#[inline(always)]
fn div<T>(a: &geo::Coordinate<T>, b: T) -> geo::Coordinate<T>
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
geo::Coordinate {
x: a.x / b,
y: a.y / b,
}
}
#[inline(always)]
fn cross_z<T>(a: &geo::Coordinate<T>, b: &geo::Coordinate<T>) -> T
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
a.x * b.y - a.y * b.x
}
#[inline(always)]
fn dot<T>(a: &geo::Coordinate<T>, b: &geo::Coordinate<T>) -> T
where
T: Float + Zero + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
a.x * b.x + a.y * b.y
}
pub trait SelfIntersectingExclusive<T>
where
T: Float
+ num_traits::ToPrimitive
+ geo::GeoFloat
+ geo::CoordFloat
+ approx::AbsDiffEq
+ approx::UlpsEq,
T::Epsilon: Copy,
{
fn is_self_intersecting(&self) -> Result<bool, IntersectError>;
#[allow(clippy::type_complexity)]
fn self_intersections<'a>(
&self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
IntersectError,
>
where
T: 'a;
}
pub trait SelfIntersectingInclusive<T>
where
T: Float
+ num_traits::ToPrimitive
+ geo::GeoFloat
+ geo::CoordFloat
+ approx::AbsDiffEq
+ approx::UlpsEq,
T::Epsilon: Copy,
{
fn is_self_intersecting_inclusive(&self) -> Result<bool, IntersectError>;
#[allow(clippy::type_complexity)]
fn self_intersections_inclusive<'a>(
&self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
IntersectError,
>
where
T: 'a;
}
impl<T> SelfIntersectingInclusive<T> for Vec<geo::Line<T>>
where
T: Float
+ num_traits::ToPrimitive
+ geo::GeoFloat
+ geo::CoordFloat
+ approx::AbsDiffEq
+ approx::UlpsEq,
T::Epsilon: Copy,
{
fn is_self_intersecting_inclusive(&self) -> Result<bool, IntersectError> {
if self.len() < 25 {
for l1 in self.iter().enumerate() {
for l2 in self.iter().skip(l1.0 + 1) {
if l1.1.intersects(l2) {
return Ok(true);
}
}
}
Ok(false)
} else {
Ok(algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(false)?
.with_stop_at_first_intersection(true)?
.with_ref_lines(self.iter())?
.compute()?
.next()
.is_some())
}
}
#[allow(clippy::type_complexity)]
fn self_intersections_inclusive<'a>(
&self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
IntersectError,
>
where
T: 'a,
{
if self.len() < 25 {
for a_line in self.iter() {
if !a_line.start.x.is_finite()
|| !a_line.start.y.is_finite()
|| !a_line.end.x.is_finite()
|| !a_line.end.y.is_finite()
{
return Err(IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
}
let mut rv = Vec::<(geo::Coordinate<T>, Vec<usize>)>::new();
for l1 in self.iter().enumerate() {
for l2 in self.iter().enumerate().skip(l1.0 + 1) {
if let Some(i) = intersect(l1.1, l2.1) {
rv.push((i.single(), vec![l1.0, l2.0]));
}
}
}
Ok(Box::new(rv.into_iter()))
} else {
algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(false)?
.with_stop_at_first_intersection(false)?
.with_ref_lines(self.iter())?
.compute()
}
}
}
impl<T> SelfIntersectingExclusive<T> for Vec<geo::Line<T>>
where
T: Float
+ num_traits::ToPrimitive
+ geo::GeoFloat
+ geo::CoordFloat
+ approx::AbsDiffEq
+ approx::UlpsEq,
T::Epsilon: Copy,
{
fn is_self_intersecting(&self) -> Result<bool, IntersectError> {
if self.len() < 25 {
for a_line in self.iter() {
if !a_line.start.x.is_finite()
|| !a_line.start.y.is_finite()
|| !a_line.end.x.is_finite()
|| !a_line.end.y.is_finite()
{
return Err(IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
}
for l1 in self.iter().enumerate() {
for l2 in self.iter().skip(l1.0 + 1) {
if ulps_eq_c(&l1.1.start, &l2.start)
|| ulps_eq_c(&l1.1.start, &l2.end)
|| ulps_eq_c(&l1.1.end, &l2.start)
|| ulps_eq_c(&l1.1.end, &l2.end)
{
continue;
}
if l1.1.intersects(l2) {
return Ok(true);
}
}
}
Ok(false)
} else {
Ok(algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(true)?
.with_stop_at_first_intersection(true)?
.with_ref_lines(self.iter())?
.compute()?
.next()
.is_some())
}
}
#[allow(clippy::type_complexity)]
fn self_intersections<'a>(
&self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
IntersectError,
>
where
T: 'a,
{
if self.len() < 25 {
for a_line in self.iter() {
if !a_line.start.x.is_finite()
|| !a_line.start.y.is_finite()
|| !a_line.end.x.is_finite()
|| !a_line.end.y.is_finite()
{
return Err(IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
}
let mut rv = Vec::<(geo::Coordinate<T>, Vec<usize>)>::new();
for l1 in self.iter().enumerate() {
for l2 in self.iter().enumerate().skip(l1.0 + 1) {
if ulps_eq_c(&l1.1.start, &l2.1.start)
|| ulps_eq_c(&l1.1.start, &l2.1.end)
|| ulps_eq_c(&l1.1.end, &l2.1.start)
|| ulps_eq_c(&l1.1.end, &l2.1.end)
{
continue;
}
if let Some(i) = intersect(l1.1, l2.1) {
rv.push((i.single(), vec![l1.0, l2.0]));
}
}
}
Ok(Box::new(rv.into_iter()))
} else {
algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(true)?
.with_stop_at_first_intersection(false)?
.with_ref_lines(self.iter())?
.compute()
}
}
}
impl<T> SelfIntersectingExclusive<T> for geo::LineString<T>
where
T: Float
+ num_traits::ToPrimitive
+ geo::GeoFloat
+ geo::CoordFloat
+ approx::AbsDiffEq
+ approx::UlpsEq,
T::Epsilon: Copy,
{
fn is_self_intersecting(&self) -> Result<bool, IntersectError> {
if self.0.len() < 25 {
for point in self.points_iter() {
if !point.x().is_finite() || !point.y().is_finite() {
return Err(IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
}
for l1 in self.lines().enumerate() {
for l2 in self.lines().skip(l1.0 + 1) {
if ulps_eq_c(&l1.1.start, &l2.start)
|| ulps_eq_c(&l1.1.start, &l2.end)
|| ulps_eq_c(&l1.1.end, &l2.start)
|| ulps_eq_c(&l1.1.end, &l2.end)
{
continue;
}
if l1.1.intersects(&l2) {
return Ok(true);
}
}
}
Ok(false)
} else {
Ok(algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(true)?
.with_stop_at_first_intersection(true)?
.with_lines(self.lines())?
.compute()?
.next()
.is_some())
}
}
#[allow(clippy::type_complexity)]
fn self_intersections<'a>(
&self,
) -> Result<
Box<dyn ExactSizeIterator<Item = (geo::Coordinate<T>, Vec<usize>)> + 'a>,
IntersectError,
>
where
T: 'a,
{
if self.0.len() < 25 {
for point in self.points_iter() {
if !point.x().is_finite() || !point.y().is_finite() {
return Err(IntersectError::InvalidData(
"Can't check for intersections on non-finite data".to_string(),
));
}
}
let mut rv = Vec::<(geo::Coordinate<T>, Vec<usize>)>::new();
for l1 in self.lines().enumerate() {
for l2 in self.lines().enumerate().skip(l1.0 + 1) {
if ulps_eq_c(&l1.1.start, &l2.1.start)
|| ulps_eq_c(&l1.1.start, &l2.1.end)
|| ulps_eq_c(&l1.1.end, &l2.1.start)
|| ulps_eq_c(&l1.1.end, &l2.1.end)
{
continue;
}
if let Some(i) = intersect(&l1.1, &l2.1) {
rv.push((i.single(), vec![l1.0, l2.0]));
}
}
}
Ok(Box::new(rv.into_iter()))
} else {
algorithm::AlgorithmData::<T>::default()
.with_ignore_end_point_intersections(true)?
.with_stop_at_first_intersection(false)?
.with_lines(self.lines())?
.compute()
}
}
}
#[inline(always)]
pub fn ulps_eq_c<T>(a: &geo::Coordinate<T>, b: &geo::Coordinate<T>) -> bool
where
T: Float + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
approx::ulps_eq!(&a.x, &b.x) && approx::ulps_eq!(&a.y, &b.y)
}
#[inline(always)]
#[allow(dead_code)]
#[deprecated(since = "0.3.2", note = "please use `approx::ulps_eq!` instead")]
pub fn ulps_eq<T>(a: &T, b: &T) -> bool
where
T: Float + geo::CoordFloat + approx::AbsDiffEq + approx::UlpsEq,
T::Epsilon: Copy,
{
approx::ulps_eq!(a, b)
}