1use std::ops::Mul;
2
3use num_traits::FromPrimitive;
4
5use crate::{
6 coord, Coord, CoordFloat, Geometry, LineString, MultiLineString, MultiPolygon, Polygon,
7};
8
9pub trait ChaikinSmoothing<T>
20where
21 T: CoordFloat + FromPrimitive,
22{
23 fn chaikin_smoothing(&self, n_iterations: usize) -> Self;
26}
27
28impl<T> ChaikinSmoothing<T> for LineString<T>
29where
30 T: CoordFloat + FromPrimitive,
31{
32 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
33 if n_iterations == 0 {
34 self.clone()
35 } else {
36 let mut smooth = smoothen_linestring(self);
37 for _ in 0..(n_iterations - 1) {
38 smooth = smoothen_linestring(&smooth);
39 }
40 smooth
41 }
42 }
43}
44
45impl<T> ChaikinSmoothing<T> for MultiLineString<T>
46where
47 T: CoordFloat + FromPrimitive,
48{
49 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
50 MultiLineString::new(
51 self.0
52 .iter()
53 .map(|ls| ls.chaikin_smoothing(n_iterations))
54 .collect(),
55 )
56 }
57}
58
59impl<T> ChaikinSmoothing<T> for Polygon<T>
60where
61 T: CoordFloat + FromPrimitive,
62{
63 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
64 Polygon::new(
65 self.exterior().chaikin_smoothing(n_iterations),
66 self.interiors()
67 .iter()
68 .map(|ls| ls.chaikin_smoothing(n_iterations))
69 .collect(),
70 )
71 }
72}
73
74impl<T> ChaikinSmoothing<T> for MultiPolygon<T>
75where
76 T: CoordFloat + FromPrimitive,
77{
78 fn chaikin_smoothing(&self, n_iterations: usize) -> Self {
79 MultiPolygon::new(
80 self.0
81 .iter()
82 .map(|poly| poly.chaikin_smoothing(n_iterations))
83 .collect(),
84 )
85 }
86}
87
88macro_rules! blanket_run_chaikin_smoothing {
89 ($geo:expr, $n_iter:expr) => {{
90 let smooth = $geo.chaikin_smoothing($n_iter);
91 let geo: Geometry<T> = smooth.into();
92 geo
93 }};
94}
95
96impl<T> ChaikinSmoothing<T> for Geometry<T>
97where
98 T: CoordFloat + FromPrimitive,
99{
100 fn chaikin_smoothing(&self, n_iterations: usize) -> Geometry<T> {
101 match self {
102 Geometry::LineString(child) => blanket_run_chaikin_smoothing!(child, n_iterations),
103 Geometry::MultiLineString(child) => blanket_run_chaikin_smoothing!(child, n_iterations),
104 Geometry::Polygon(child) => blanket_run_chaikin_smoothing!(child, n_iterations),
105 Geometry::MultiPolygon(child) => blanket_run_chaikin_smoothing!(child, n_iterations),
106 _ => self.clone(),
107 }
108 }
109}
110
111fn smoothen_linestring<T>(linestring: &LineString<T>) -> LineString<T>
112where
113 T: CoordFloat + Mul<T> + FromPrimitive,
114{
115 let mut out_coords: Vec<_> = Vec::with_capacity(linestring.0.len() * 2);
116
117 if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
118 if first != last {
119 out_coords.push(*first);
121 }
122 }
123 for window_coordinates in linestring.0.windows(2) {
124 let (q, r) = smoothen_coordinates(window_coordinates[0], window_coordinates[1]);
125 out_coords.push(q);
126 out_coords.push(r);
127 }
128
129 if let (Some(first), Some(last)) = (linestring.0.first(), linestring.0.last()) {
130 if first != last {
131 out_coords.push(*last);
133 } else {
134 if let Some(out_first) = out_coords.first().copied() {
137 out_coords.push(out_first);
138 }
139 }
140 }
141 out_coords.into()
142}
143
144fn smoothen_coordinates<T>(c0: Coord<T>, c1: Coord<T>) -> (Coord<T>, Coord<T>)
145where
146 T: CoordFloat + Mul<T> + FromPrimitive,
147{
148 let q = coord! {
149 x: (T::from(0.75).unwrap() * c0.x) + (T::from(0.25).unwrap() * c1.x),
150 y: (T::from(0.75).unwrap() * c0.y) + (T::from(0.25).unwrap() * c1.y),
151 };
152 let r = coord! {
153 x: (T::from(0.25).unwrap() * c0.x) + (T::from(0.75).unwrap() * c1.x),
154 y: (T::from(0.25).unwrap() * c0.y) + (T::from(0.75).unwrap() * c1.y),
155 };
156 (q, r)
157}
158
159#[cfg(test)]
160mod test {
161 use crate::ChaikinSmoothing;
162 use crate::{Geometry, LineString, Point, Polygon};
163
164 #[test]
165 fn geometry() {
166 let ls = LineString::from(vec![(3.0, 0.0), (6.0, 3.0), (3.0, 6.0), (0.0, 3.0)]);
168 let ls_geo: Geometry = ls.into();
169 let ls_geo_out = ls_geo.chaikin_smoothing(1);
170 let ls_out: LineString = ls_geo_out.try_into().unwrap();
171 assert_eq!(
172 ls_out,
173 LineString::from(vec![
174 (3.0, 0.0),
175 (3.75, 0.75),
176 (5.25, 2.25),
177 (5.25, 3.75),
178 (3.75, 5.25),
179 (2.25, 5.25),
180 (0.75, 3.75),
181 (0.0, 3.0),
182 ])
183 );
184
185 let pt = Point::from((3.0, 0.0));
187 let pt_geo: Geometry = pt.into();
188 let pt_geo_out = pt_geo.chaikin_smoothing(1);
189 let pt_out: Point = pt_geo_out.try_into().unwrap();
190 assert_eq!(pt_out, Point::from((3.0, 0.0)));
191 }
192
193 #[test]
194 fn linestring_open() {
195 let ls = LineString::from(vec![(3.0, 0.0), (6.0, 3.0), (3.0, 6.0), (0.0, 3.0)]);
196 let ls_out = ls.chaikin_smoothing(1);
197 assert_eq!(
198 ls_out,
199 LineString::from(vec![
200 (3.0, 0.0),
201 (3.75, 0.75),
202 (5.25, 2.25),
203 (5.25, 3.75),
204 (3.75, 5.25),
205 (2.25, 5.25),
206 (0.75, 3.75),
207 (0.0, 3.0),
208 ])
209 );
210 }
211
212 #[test]
213 fn linestring_closed() {
214 let ls = LineString::from(vec![
215 (3.0, 0.0),
216 (6.0, 3.0),
217 (3.0, 6.0),
218 (0.0, 3.0),
219 (3.0, 0.0),
220 ]);
221 let ls_out = ls.chaikin_smoothing(1);
222 assert_eq!(
223 ls_out,
224 LineString::from(vec![
225 (3.75, 0.75),
226 (5.25, 2.25),
227 (5.25, 3.75),
228 (3.75, 5.25),
229 (2.25, 5.25),
230 (0.75, 3.75),
231 (0.75, 2.25),
232 (2.25, 0.75),
233 (3.75, 0.75)
234 ])
235 );
236 }
237
238 #[test]
239 fn polygon() {
240 let poly = Polygon::new(
241 LineString::from(vec![
242 (3.0, 0.0),
243 (6.0, 3.0),
244 (3.0, 6.0),
245 (0.0, 3.0),
246 (3.0, 0.0),
247 ]),
248 vec![],
249 );
250 let poly_out = poly.chaikin_smoothing(1);
251 assert_eq!(
252 poly_out.exterior(),
253 &LineString::from(vec![
254 (3.75, 0.75),
255 (5.25, 2.25),
256 (5.25, 3.75),
257 (3.75, 5.25),
258 (2.25, 5.25),
259 (0.75, 3.75),
260 (0.75, 2.25),
261 (2.25, 0.75),
262 (3.75, 0.75)
263 ])
264 );
265 }
266}