1#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct Coord {
8 pub x: f64,
9 pub y: f64,
10}
11
12impl Coord {
13 pub fn new(x: f64, y: f64) -> Self {
14 Self { x, y }
15 }
16}
17
18#[derive(Debug, Clone, Copy, PartialEq)]
25pub struct Coord3D {
26 pub x: f64,
27 pub y: f64,
28 pub z: f64,
29}
30
31impl Coord3D {
32 pub fn new(x: f64, y: f64, z: f64) -> Self {
33 Self { x, y, z }
34 }
35}
36
37#[derive(Debug, Clone, Copy, PartialEq)]
46pub struct Bounds {
47 pub min_x: f64,
48 pub min_y: f64,
49 pub max_x: f64,
50 pub max_y: f64,
51}
52
53pub const MAX_BOUNDS_DENSIFY_POINTS: usize = 10_000;
58
59impl Bounds {
60 pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
61 Self {
62 min_x,
63 min_y,
64 max_x,
65 max_y,
66 }
67 }
68
69 pub fn width(&self) -> f64 {
70 self.max_x - self.min_x
71 }
72
73 pub fn height(&self) -> f64 {
74 self.max_y - self.min_y
75 }
76
77 pub fn is_valid(&self) -> bool {
79 self.min_x.is_finite()
80 && self.min_y.is_finite()
81 && self.max_x.is_finite()
82 && self.max_y.is_finite()
83 && self.min_x <= self.max_x
84 && self.min_y <= self.max_y
85 }
86
87 pub(crate) fn expand_to_include(&mut self, coord: Coord) {
88 self.min_x = self.min_x.min(coord.x);
89 self.min_y = self.min_y.min(coord.y);
90 self.max_x = self.max_x.max(coord.x);
91 self.max_y = self.max_y.max(coord.y);
92 }
93}
94
95impl From<(f64, f64)> for Coord {
96 fn from((x, y): (f64, f64)) -> Self {
97 Self { x, y }
98 }
99}
100
101impl From<Coord> for (f64, f64) {
102 fn from(c: Coord) -> Self {
103 (c.x, c.y)
104 }
105}
106
107impl From<(f64, f64, f64)> for Coord3D {
108 fn from((x, y, z): (f64, f64, f64)) -> Self {
109 Self { x, y, z }
110 }
111}
112
113impl From<Coord3D> for (f64, f64, f64) {
114 fn from(c: Coord3D) -> Self {
115 (c.x, c.y, c.z)
116 }
117}
118
119#[cfg(feature = "geo-types")]
120impl From<geo_types::Coord<f64>> for Coord {
121 fn from(c: geo_types::Coord<f64>) -> Self {
122 Self { x: c.x, y: c.y }
123 }
124}
125
126#[cfg(feature = "geo-types")]
127impl From<Coord> for geo_types::Coord<f64> {
128 fn from(c: Coord) -> Self {
129 geo_types::Coord { x: c.x, y: c.y }
130 }
131}
132
133pub trait Transformable: Sized {
138 fn into_coord(self) -> Coord;
139 fn from_coord(c: Coord) -> Self;
140}
141
142pub trait Transformable3D: Sized {
148 fn into_coord3d(self) -> Coord3D;
149 fn from_coord3d(c: Coord3D) -> Self;
150}
151
152impl Transformable for Coord {
153 fn into_coord(self) -> Coord {
154 self
155 }
156 fn from_coord(c: Coord) -> Self {
157 c
158 }
159}
160
161impl Transformable for (f64, f64) {
162 fn into_coord(self) -> Coord {
163 Coord {
164 x: self.0,
165 y: self.1,
166 }
167 }
168 fn from_coord(c: Coord) -> Self {
169 (c.x, c.y)
170 }
171}
172
173impl Transformable3D for Coord3D {
174 fn into_coord3d(self) -> Coord3D {
175 self
176 }
177
178 fn from_coord3d(c: Coord3D) -> Self {
179 c
180 }
181}
182
183impl Transformable3D for (f64, f64, f64) {
184 fn into_coord3d(self) -> Coord3D {
185 Coord3D {
186 x: self.0,
187 y: self.1,
188 z: self.2,
189 }
190 }
191
192 fn from_coord3d(c: Coord3D) -> Self {
193 (c.x, c.y, c.z)
194 }
195}
196
197#[cfg(feature = "geo-types")]
198impl Transformable for geo_types::Coord<f64> {
199 fn into_coord(self) -> Coord {
200 Coord {
201 x: self.x,
202 y: self.y,
203 }
204 }
205 fn from_coord(c: Coord) -> Self {
206 geo_types::Coord { x: c.x, y: c.y }
207 }
208}
209
210#[cfg(test)]
211mod tests {
212 use super::*;
213
214 #[test]
215 fn coord_from_tuple() {
216 let c: Coord = (1.0, 2.0).into();
217 assert_eq!(c.x, 1.0);
218 assert_eq!(c.y, 2.0);
219 }
220
221 #[test]
222 fn tuple_from_coord() {
223 let t: (f64, f64) = Coord::new(3.0, 4.0).into();
224 assert_eq!(t, (3.0, 4.0));
225 }
226
227 #[test]
228 fn coord3d_from_tuple() {
229 let c: Coord3D = (1.0, 2.0, 3.0).into();
230 assert_eq!(c.x, 1.0);
231 assert_eq!(c.y, 2.0);
232 assert_eq!(c.z, 3.0);
233 }
234
235 #[test]
236 fn tuple_from_coord3d() {
237 let t: (f64, f64, f64) = Coord3D::new(3.0, 4.0, 5.0).into();
238 assert_eq!(t, (3.0, 4.0, 5.0));
239 }
240
241 #[test]
242 fn transformable_roundtrip_tuple() {
243 let original = (10.0, 20.0);
244 let coord = original.into_coord();
245 let back = <(f64, f64)>::from_coord(coord);
246 assert_eq!(original, back);
247 }
248
249 #[test]
250 fn transformable3d_roundtrip_tuple() {
251 let original = (10.0, 20.0, 30.0);
252 let coord = original.into_coord3d();
253 let back = <(f64, f64, f64)>::from_coord3d(coord);
254 assert_eq!(original, back);
255 }
256
257 #[test]
258 fn bounds_basics() {
259 let bounds = Bounds::new(-10.0, 20.0, 30.0, 40.0);
260 assert_eq!(bounds.width(), 40.0);
261 assert_eq!(bounds.height(), 20.0);
262 assert!(bounds.is_valid());
263 }
264
265 #[test]
266 fn bounds_invalid_when_non_finite_or_reversed() {
267 assert!(!Bounds::new(f64::NAN, 20.0, 30.0, 40.0).is_valid());
268 assert!(!Bounds::new(-10.0, 20.0, f64::INFINITY, 40.0).is_valid());
269 assert!(!Bounds::new(30.0, 20.0, -10.0, 40.0).is_valid());
270 assert!(!Bounds::new(-10.0, 40.0, 30.0, 20.0).is_valid());
271 }
272}