1use std::ops::Mul;
2
3use num_traits::FromPrimitive;
4
5use crate::{coord, Coord, CoordFloat, LineString, MultiLineString, MultiPolygon, Polygon};
6
7pub trait ChaikinSmoothing<T>
18where
19 T: CoordFloat + FromPrimitive,
20{
21 fn chaikin_smoothing(&self, n_iterations: usize) -> Self;
24}
25
26impl<T> ChaikinSmoothing<T> for LineString<T>
27where
28 T: CoordFloat + FromPrimitive,
29{
30 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
31 if n_iterations == 0 {
32 self.clone()
33 } else {
34 let mut smooth = smoothen_linestring(self);
35 for _ in 0..(n_iterations - 1) {
36 smooth = smoothen_linestring(&smooth);
37 }
38 smooth
39 }
40 }
41}
42
43impl<T> ChaikinSmoothing<T> for MultiLineString<T>
44where
45 T: CoordFloat + FromPrimitive,
46{
47 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
48 MultiLineString::new(
49 self.0
50 .iter()
51 .map(|ls| ls.chaikin_smoothing(n_iterations))
52 .collect(),
53 )
54 }
55}
56
57impl<T> ChaikinSmoothing<T> for Polygon<T>
58where
59 T: CoordFloat + FromPrimitive,
60{
61 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
62 Polygon::new(
63 self.exterior().chaikin_smoothing(n_iterations),
64 self.interiors()
65 .iter()
66 .map(|ls| ls.chaikin_smoothing(n_iterations))
67 .collect(),
68 )
69 }
70}
71
72impl<T> ChaikinSmoothing<T> for MultiPolygon<T>
73where
74 T: CoordFloat + FromPrimitive,
75{
76 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
77 MultiPolygon::new(
78 self.0
79 .iter()
80 .map(|poly| poly.chaikin_smoothing(n_iterations))
81 .collect(),
82 )
83 }
84}
85
86fn smoothen_linestring<T>(linestring: &LineString<T>) -> LineString<T>
87where
88 T: CoordFloat + Mul<T> + FromPrimitive,
89{
90 let mut out_coords: Vec<_> = Vec::with_capacity(linestring.0.len() * 2);
91
92 if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
93 if first != last {
94 out_coords.push(*first);
96 }
97 }
98 for window_coordinates in linestring.0.windows(2) {
99 let (q, r) = smoothen_coordinates(window_coordinates[0], window_coordinates[1]);
100 out_coords.push(q);
101 out_coords.push(r);
102 }
103
104 if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
105 if first != last {
106 out_coords.push(*last);
108 } else {
109 if let Some(out_first) = out_coords.first().copied() {
112 out_coords.push(out_first);
113 }
114 }
115 }
116 out_coords.into()
117}
118
119fn smoothen_coordinates<T>(c0: Coord<T>, c1: Coord<T>) -> (Coord<T>, Coord<T>)
120where
121 T: CoordFloat + Mul<T> + FromPrimitive,
122{
123 let q = coord! {
124 x: (T::from(0.75).unwrap() * c0.x) + (T::from(0.25).unwrap() * c1.x),
125 y: (T::from(0.75).unwrap() * c0.y) + (T::from(0.25).unwrap() * c1.y),
126 };
127 let r = coord! {
128 x: (T::from(0.25).unwrap() * c0.x) + (T::from(0.75).unwrap() * c1.x),
129 y: (T::from(0.25).unwrap() * c0.y) + (T::from(0.75).unwrap() * c1.y),
130 };
131 (q, r)
132}
133
134#[cfg(test)]
135mod test {
136 use crate::ChaikinSmoothing;
137 use crate::{LineString, Polygon};
138
139 #[test]
140 fn linestring_open() {
141 let ls = LineString::from(vec![(3.0, 0.0), (6.0, 3.0), (3.0, 6.0), (0.0, 3.0)]);
142 let ls_out = ls.chaikin_smoothing(1);
143 assert_eq!(
144 ls_out,
145 LineString::from(vec![
146 (3.0, 0.0),
147 (3.75, 0.75),
148 (5.25, 2.25),
149 (5.25, 3.75),
150 (3.75, 5.25),
151 (2.25, 5.25),
152 (0.75, 3.75),
153 (0.0, 3.0),
154 ])
155 );
156 }
157
158 #[test]
159 fn linestring_closed() {
160 let ls = LineString::from(vec![
161 (3.0, 0.0),
162 (6.0, 3.0),
163 (3.0, 6.0),
164 (0.0, 3.0),
165 (3.0, 0.0),
166 ]);
167 let ls_out = ls.chaikin_smoothing(1);
168 assert_eq!(
169 ls_out,
170 LineString::from(vec![
171 (3.75, 0.75),
172 (5.25, 2.25),
173 (5.25, 3.75),
174 (3.75, 5.25),
175 (2.25, 5.25),
176 (0.75, 3.75),
177 (0.75, 2.25),
178 (2.25, 0.75),
179 (3.75, 0.75)
180 ])
181 );
182 }
183
184 #[test]
185 fn polygon() {
186 let poly = Polygon::new(
187 LineString::from(vec![
188 (3.0, 0.0),
189 (6.0, 3.0),
190 (3.0, 6.0),
191 (0.0, 3.0),
192 (3.0, 0.0),
193 ]),
194 vec![],
195 );
196 let poly_out = poly.chaikin_smoothing(1);
197 assert_eq!(
198 poly_out.exterior(),
199 &LineString::from(vec![
200 (3.75, 0.75),
201 (5.25, 2.25),
202 (5.25, 3.75),
203 (3.75, 5.25),
204 (2.25, 5.25),
205 (0.75, 3.75),
206 (0.75, 2.25),
207 (2.25, 0.75),
208 (3.75, 0.75)
209 ])
210 );
211 }
212}