use num_traits::Float;
use std::ops::AddAssign;
use crate::{
algorithm::euclidean_length::EuclideanLength, CoordinateType, Line, LineString, Point,
};
pub trait LineInterpolatePoint<F: Float> {
type Output;
fn line_interpolate_point(&self, fraction: F) -> Self::Output;
}
impl<T> LineInterpolatePoint<T> for Line<T>
where
T: CoordinateType + Float,
{
type Output = Option<Point<T>>;
fn line_interpolate_point(&self, fraction: T) -> Self::Output {
if (fraction >= T::zero()) && (fraction <= T::one()) {
let diff = self.end - self.start;
let r = self.start + diff * (fraction);
if r.x.is_finite() && r.y.is_finite() {
return Some(r.into());
} else {
return None;
}
} else if fraction < T::zero() {
return self.line_interpolate_point(T::zero());
} else if fraction > T::one() {
return self.line_interpolate_point(T::one());
} else {
debug_assert!(fraction.is_nan());
return None;
}
}
}
impl<T> LineInterpolatePoint<T> for LineString<T>
where
T: CoordinateType + Float + AddAssign + std::fmt::Debug,
Line<T>: EuclideanLength<T>,
LineString<T>: EuclideanLength<T>,
{
type Output = Option<Point<T>>;
fn line_interpolate_point(&self, fraction: T) -> Self::Output {
if (fraction >= T::zero()) && (fraction <= T::one()) {
let total_length = self.euclidean_length();
let fractional_length = total_length * fraction;
let mut cum_length = T::zero();
for segment in self.lines() {
let length = segment.euclidean_length();
if cum_length + length >= fractional_length {
let segment_fraction = (fractional_length - cum_length) / length;
return segment.line_interpolate_point(segment_fraction);
}
cum_length += length;
}
debug_assert!(fractional_length.is_nan() || (self.num_coords() == 0));
return None;
} else if fraction < T::zero() {
return self.line_interpolate_point(T::zero());
} else if fraction > T::one() {
return self.line_interpolate_point(T::one());
} else {
debug_assert!(fraction.is_nan());
return None;
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{
algorithm::{closest_point::ClosestPoint, line_locate_point::LineLocatePoint},
point, Coordinate,
};
#[test]
fn test_line_interpolate_point_line() {
let line = Line::new(
Coordinate { x: -1.0, y: 0.0 },
Coordinate { x: 1.0, y: 0.0 },
);
assert_eq!(
line.line_interpolate_point(-1.0),
Some(point!(x: -1.0, y: 0.0))
);
assert_eq!(
line.line_interpolate_point(0.5),
Some(point!(x: 0.0, y: 0.0))
);
assert_eq!(
line.line_interpolate_point(0.75),
Some(point!(x: 0.5, y: 0.0))
);
assert_eq!(
line.line_interpolate_point(0.0),
Some(point!(x: -1.0, y: 0.0))
);
assert_eq!(
line.line_interpolate_point(1.0),
Some(point!(x: 1.0, y: 0.0))
);
assert_eq!(
line.line_interpolate_point(2.0),
Some(point!(x: 1.0, y: 0.0))
);
assert_eq!(line.line_interpolate_point(Float::nan()), None);
assert_eq!(
line.line_interpolate_point(Float::infinity()),
Some(line.end_point())
);
assert_eq!(
line.line_interpolate_point(Float::neg_infinity()),
Some(line.start_point())
);
let line = Line::new(Coordinate { x: 0.0, y: 0.0 }, Coordinate { x: 1.0, y: 1.0 });
assert_eq!(
line.line_interpolate_point(0.5),
Some(point!(x: 0.5, y: 0.5))
);
let line = Line::new(
Coordinate {
x: Float::nan(),
y: 0.0,
},
Coordinate { x: 1.0, y: 1.0 },
);
assert_eq!(line.line_interpolate_point(0.5), None);
let line = Line::new(
Coordinate {
x: Float::infinity(),
y: 0.0,
},
Coordinate { x: 1.0, y: 1.0 },
);
assert_eq!(line.line_interpolate_point(0.5), None);
let line = Line::new(
Coordinate { x: 0.0, y: 0.0 },
Coordinate {
x: 1.0,
y: Float::infinity(),
},
);
assert_eq!(line.line_interpolate_point(0.5), None);
let line = Line::new(
Coordinate {
x: Float::neg_infinity(),
y: 0.0,
},
Coordinate { x: 1.0, y: 1.0 },
);
assert_eq!(line.line_interpolate_point(0.5), None);
let line = Line::new(
Coordinate { x: 0.0, y: 0.0 },
Coordinate {
x: 1.0,
y: Float::neg_infinity(),
},
);
assert_eq!(line.line_interpolate_point(0.5), None);
}
#[test]
fn test_line_interpolate_point_linestring() {
let linestring: LineString<f64> = vec![[-1.0, 0.0], [0.0, 0.0], [1.0, 0.0]].into();
assert_eq!(
linestring.line_interpolate_point(0.0),
Some(point!(x: -1.0, y: 0.0))
);
assert_eq!(
linestring.line_interpolate_point(0.5),
Some(point!(x: 0.0, y: 0.0))
);
assert_eq!(
linestring.line_interpolate_point(1.0),
Some(point!(x: 1.0, y: 0.0))
);
assert_eq!(
linestring.line_interpolate_point(1.0),
linestring.line_interpolate_point(2.0)
);
assert_eq!(
linestring.line_interpolate_point(0.0),
linestring.line_interpolate_point(-2.0)
);
assert_eq!(
linestring.line_interpolate_point(Float::infinity()),
linestring.points_iter().last()
);
assert_eq!(
linestring.line_interpolate_point(Float::neg_infinity()),
linestring.points_iter().next()
);
assert_eq!(linestring.line_interpolate_point(Float::nan()), None);
let linestring: LineString<f64> = vec![[-1.0, 0.0], [0.0, 0.0], [0.0, 1.0]].into();
assert_eq!(
linestring.line_interpolate_point(0.5),
Some(point!(x: 0.0, y: 0.0))
);
assert_eq!(
linestring.line_interpolate_point(1.5),
Some(point!(x: 0.0, y: 1.0))
);
let linestring: LineString<f64> = vec![[-1.0, 0.0], [0.0, Float::nan()], [0.0, 1.0]].into();
assert_eq!(linestring.line_interpolate_point(0.5), None);
assert_eq!(linestring.line_interpolate_point(1.5), None);
assert_eq!(linestring.line_interpolate_point(-1.0), None);
let linestring: LineString<f64> =
vec![[-1.0, 0.0], [0.0, Float::infinity()], [0.0, 1.0]].into();
assert_eq!(linestring.line_interpolate_point(0.5), None);
assert_eq!(linestring.line_interpolate_point(1.5), None);
assert_eq!(linestring.line_interpolate_point(-1.0), None);
let linestring: LineString<f64> =
vec![[-1.0, 0.0], [0.0, Float::neg_infinity()], [0.0, 1.0]].into();
assert_eq!(linestring.line_interpolate_point(0.5), None);
assert_eq!(linestring.line_interpolate_point(1.5), None);
assert_eq!(linestring.line_interpolate_point(-1.0), None);
let coords: Vec<Point<f64>> = Vec::new();
let linestring: LineString<f64> = coords.into();
assert_eq!(linestring.line_interpolate_point(0.5), None);
}
#[test]
fn test_matches_closest_point() {
let linestring: LineString<f64> = vec![[-1.0, 0.0], [0.5, 1.0], [1.0, 2.0]].into();
let pt = point!(x: 0.7, y: 0.7);
let frac = linestring
.line_locate_point(&pt)
.expect("Should result in fraction between 0 and 1");
println!("{:?}", &frac);
let interpolated_point = linestring
.line_interpolate_point(frac)
.expect("Shouldn't return None");
let closest_point = linestring.closest_point(&pt);
match closest_point {
crate::Closest::SinglePoint(p) => assert_eq!(interpolated_point, p),
_ => panic!("The closest point should be a SinglePoint"),
};
}
}