geometry_algorithm/
densify.rs1use geometry_strategy::{CartesianDensify, DensifyStrategy};
9
10#[inline]
25#[must_use]
26pub fn densify<G>(g: &G, max_distance: f64) -> <CartesianDensify as DensifyStrategy<G>>::Output
27where
28 CartesianDensify: DensifyStrategy<G>,
29{
30 assert!(
31 max_distance > 0.0,
32 "densify: max_distance must be positive, got {max_distance} \
33 (Boost throws invalid_input_exception for max_distance <= 0)"
34 );
35 CartesianDensify.densify(g, max_distance)
36}
37
38#[cfg(test)]
39#[allow(
40 clippy::float_cmp,
41 reason = "Densified coordinates are exact literals."
42)]
43mod tests {
44 use super::densify;
49 use crate::{length, num_points};
50 use geometry_cs::Cartesian;
51 use geometry_model::{Linestring, Point2D, linestring};
52 use geometry_strategy::{DistanceStrategy, Pythagoras};
53
54 type Pt = Point2D<f64, Cartesian>;
55
56 #[test]
57 fn segment_of_length_10_max_2_5_yields_6_points() {
58 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.)];
61 let out = densify(&ls, 2.5);
62 assert_eq!(num_points(&out), 6);
63 let xs: alloc::vec::Vec<f64> = out.0.iter().map(geometry_trait::Point::get::<0>).collect();
64 assert_eq!(xs, alloc::vec![0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
65 }
66
67 #[test]
68 fn length_preserved_through_densify() {
69 let ls: Linestring<Pt> = linestring![(0., 0.), (3., 4.), (6., 0.)];
70 let original = length(&ls);
71 let out = densify(&ls, 1.0);
72 assert!((length(&out) - original).abs() < 1e-9);
73 }
74
75 #[test]
76 fn no_output_segment_exceeds_max_distance() {
77 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.), (10., 7.)];
78 let out = densify(&ls, 1.0);
79 let pts: alloc::vec::Vec<&Pt> = geometry_trait::Linestring::points(&out).collect();
80 for w in pts.windows(2) {
81 assert!(Pythagoras.distance(w[0], w[1]) <= 1.0 + 1e-9);
82 }
83 }
84
85 #[test]
88 fn degenerate_inputs_copy_through() {
89 let empty: Linestring<Pt> = linestring![];
90 assert_eq!(num_points(&densify(&empty, 1.0)), 0);
91
92 let single: Linestring<Pt> = linestring![(3., 4.)];
93 let out = densify(&single, 1.0);
94 assert_eq!(num_points(&out), 1);
95
96 let zero_len: Linestring<Pt> = linestring![(0., 0.), (0., 0.)];
97 assert_eq!(num_points(&densify(&zero_len, 1.0)), 2);
98 }
99
100 #[test]
103 fn three_d_segment_interpolates_z() {
104 use geometry_model::Point3D;
105 type P3 = Point3D<f64, Cartesian>;
106 let ls: Linestring<P3> =
107 Linestring::from_vec(alloc::vec![P3::new(0., 0., 0.), P3::new(10., 0., 10.)]);
108 let out = densify(&ls, 5.1);
110 let zs: alloc::vec::Vec<f64> = out.0.iter().map(geometry_trait::Point::get::<2>).collect();
111 assert_eq!(zs.len(), 4);
112 assert!((zs[1] - 10.0 / 3.0).abs() < 1e-9);
113 assert!((zs[2] - 20.0 / 3.0).abs() < 1e-9);
114 }
115
116 #[test]
118 #[allow(clippy::float_cmp, reason = "midpoint ordinates are exact literals")]
119 fn four_d_segment_interpolates_all_ordinates() {
120 use geometry_model::Point;
121 use geometry_trait::{Point as _, PointMut as _};
122 type P4 = Point<f64, 4, Cartesian>;
123 let mut a = P4::default();
124 a.set::<3>(0.0);
125 let mut b = P4::default();
126 b.set::<0>(10.0);
127 b.set::<3>(4.0);
128 let ls: Linestring<P4> = Linestring::from_vec(alloc::vec![a, b]);
129 let out = densify(&ls, 6.0);
131 assert_eq!(num_points(&out), 3);
132 assert_eq!(out.0[1].get::<0>(), 5.0);
133 assert_eq!(out.0[1].get::<3>(), 2.0);
134 }
135
136 #[test]
137 #[should_panic(expected = "max_distance must be positive")]
138 fn zero_max_distance_panics() {
139 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.)];
140 let _ = densify(&ls, 0.0);
141 }
142
143 #[test]
144 #[should_panic(expected = "max_distance must be positive")]
145 fn negative_max_distance_panics() {
146 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.)];
147 let _ = densify(&ls, -1.0);
148 }
149
150 #[test]
151 #[should_panic(expected = "max_distance must be positive")]
152 fn nan_max_distance_panics() {
153 let ls: Linestring<Pt> = linestring![(0., 0.), (10., 0.)];
154 let _ = densify(&ls, f64::NAN);
155 }
156}