use std::ops::Mul;
use num_traits::FromPrimitive;
use crate::{coord, Coord, CoordFloat, LineString, MultiLineString, MultiPolygon, Polygon};
pub trait ChaikinSmoothing<T>
where
T: CoordFloat + FromPrimitive,
{
fn chaikin_smoothing(&self, n_iterations: usize) -> Self;
}
impl<T> ChaikinSmoothing<T> for LineString<T>
where
T: CoordFloat + FromPrimitive,
{
fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
if n_iterations == 0 {
self.clone()
} else {
let mut smooth = smoothen_linestring(self);
for _ in 0..(n_iterations - 1) {
smooth = smoothen_linestring(&smooth);
}
smooth
}
}
}
impl<T> ChaikinSmoothing<T> for MultiLineString<T>
where
T: CoordFloat + FromPrimitive,
{
fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
MultiLineString::new(
self.0
.iter()
.map(|ls| ls.chaikin_smoothing(n_iterations))
.collect(),
)
}
}
impl<T> ChaikinSmoothing<T> for Polygon<T>
where
T: CoordFloat + FromPrimitive,
{
fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
Polygon::new(
self.exterior().chaikin_smoothing(n_iterations),
self.interiors()
.iter()
.map(|ls| ls.chaikin_smoothing(n_iterations))
.collect(),
)
}
}
impl<T> ChaikinSmoothing<T> for MultiPolygon<T>
where
T: CoordFloat + FromPrimitive,
{
fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
MultiPolygon::new(
self.0
.iter()
.map(|poly| poly.chaikin_smoothing(n_iterations))
.collect(),
)
}
}
fn smoothen_linestring<T>(linestring: &LineString<T>) -> LineString<T>
where
T: CoordFloat + Mul<T> + FromPrimitive,
{
let mut out_coords: Vec<_> = Vec::with_capacity(linestring.0.len() * 2);
if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
if first != last {
out_coords.push(*first);
}
}
for window_coordinates in linestring.0.windows(2) {
let (q, r) = smoothen_coordinates(window_coordinates[0], window_coordinates[1]);
out_coords.push(q);
out_coords.push(r);
}
if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
if first != last {
out_coords.push(*last);
} else {
if let Some(out_first) = out_coords.first().copied() {
out_coords.push(out_first);
}
}
}
out_coords.into()
}
fn smoothen_coordinates<T>(c0: Coord<T>, c1: Coord<T>) -> (Coord<T>, Coord<T>)
where
T: CoordFloat + Mul<T> + FromPrimitive,
{
let q = coord! {
x: (T::from(0.75).unwrap() * c0.x) + (T::from(0.25).unwrap() * c1.x),
y: (T::from(0.75).unwrap() * c0.y) + (T::from(0.25).unwrap() * c1.y),
};
let r = coord! {
x: (T::from(0.25).unwrap() * c0.x) + (T::from(0.75).unwrap() * c1.x),
y: (T::from(0.25).unwrap() * c0.y) + (T::from(0.75).unwrap() * c1.y),
};
(q, r)
}
#[cfg(test)]
mod test {
use crate::ChaikinSmoothing;
use crate::{LineString, Polygon};
#[test]
fn linestring_open() {
let ls = LineString::from(vec![(3.0, 0.0), (6.0, 3.0), (3.0, 6.0), (0.0, 3.0)]);
let ls_out = ls.chaikin_smoothing(1);
assert_eq!(
ls_out,
LineString::from(vec![
(3.0, 0.0),
(3.75, 0.75),
(5.25, 2.25),
(5.25, 3.75),
(3.75, 5.25),
(2.25, 5.25),
(0.75, 3.75),
(0.0, 3.0),
])
);
}
#[test]
fn linestring_closed() {
let ls = LineString::from(vec![
(3.0, 0.0),
(6.0, 3.0),
(3.0, 6.0),
(0.0, 3.0),
(3.0, 0.0),
]);
let ls_out = ls.chaikin_smoothing(1);
assert_eq!(
ls_out,
LineString::from(vec![
(3.75, 0.75),
(5.25, 2.25),
(5.25, 3.75),
(3.75, 5.25),
(2.25, 5.25),
(0.75, 3.75),
(0.75, 2.25),
(2.25, 0.75),
(3.75, 0.75)
])
);
}
#[test]
fn polygon() {
let poly = Polygon::new(
LineString::from(vec![
(3.0, 0.0),
(6.0, 3.0),
(3.0, 6.0),
(0.0, 3.0),
(3.0, 0.0),
]),
vec![],
);
let poly_out = poly.chaikin_smoothing(1);
assert_eq!(
poly_out.exterior(),
&LineString::from(vec![
(3.75, 0.75),
(5.25, 2.25),
(5.25, 3.75),
(3.75, 5.25),
(2.25, 5.25),
(0.75, 3.75),
(0.75, 2.25),
(2.25, 0.75),
(3.75, 0.75)
])
);
}
}