1#[derive(Debug, Clone, Copy, PartialEq)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Coord {
9 pub x: f64,
10 pub y: f64,
11}
12
13impl Coord {
14 pub fn new(x: f64, y: f64) -> Self {
15 Self { x, y }
16 }
17}
18
19#[derive(Debug, Clone, Copy, PartialEq)]
29#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
30pub struct Coord3D {
31 pub x: f64,
32 pub y: f64,
33 pub z: f64,
34}
35
36impl Coord3D {
37 pub fn new(x: f64, y: f64, z: f64) -> Self {
38 Self { x, y, z }
39 }
40}
41
42#[derive(Debug, Clone, Copy, PartialEq)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
52pub struct Bounds {
53 pub min_x: f64,
54 pub min_y: f64,
55 pub max_x: f64,
56 pub max_y: f64,
57}
58
59pub const MAX_BOUNDS_DENSIFY_POINTS: usize = 10_000;
64
65impl Bounds {
66 pub fn new(min_x: f64, min_y: f64, max_x: f64, max_y: f64) -> Self {
67 Self {
68 min_x,
69 min_y,
70 max_x,
71 max_y,
72 }
73 }
74
75 pub fn width(&self) -> f64 {
76 self.max_x - self.min_x
77 }
78
79 pub fn height(&self) -> f64 {
80 self.max_y - self.min_y
81 }
82
83 pub fn is_valid(&self) -> bool {
85 self.min_x.is_finite()
86 && self.min_y.is_finite()
87 && self.max_x.is_finite()
88 && self.max_y.is_finite()
89 && self.min_x <= self.max_x
90 && self.min_y <= self.max_y
91 }
92
93 pub(crate) fn expand_to_include(&mut self, coord: Coord) {
94 self.min_x = self.min_x.min(coord.x);
95 self.min_y = self.min_y.min(coord.y);
96 self.max_x = self.max_x.max(coord.x);
97 self.max_y = self.max_y.max(coord.y);
98 }
99}
100
101impl From<(f64, f64)> for Coord {
102 fn from((x, y): (f64, f64)) -> Self {
103 Self { x, y }
104 }
105}
106
107impl From<Coord> for (f64, f64) {
108 fn from(c: Coord) -> Self {
109 (c.x, c.y)
110 }
111}
112
113impl From<(f64, f64, f64)> for Coord3D {
114 fn from((x, y, z): (f64, f64, f64)) -> Self {
115 Self { x, y, z }
116 }
117}
118
119impl From<Coord3D> for (f64, f64, f64) {
120 fn from(c: Coord3D) -> Self {
121 (c.x, c.y, c.z)
122 }
123}
124
125#[cfg(feature = "geo-types")]
126impl From<geo_types::Coord<f64>> for Coord {
127 fn from(c: geo_types::Coord<f64>) -> Self {
128 Self { x: c.x, y: c.y }
129 }
130}
131
132#[cfg(feature = "geo-types")]
133impl From<Coord> for geo_types::Coord<f64> {
134 fn from(c: Coord) -> Self {
135 geo_types::Coord { x: c.x, y: c.y }
136 }
137}
138
139pub trait Transformable: Sized {
144 fn to_coord(&self) -> Coord;
145 fn from_coord(c: Coord) -> Self;
146}
147
148pub trait Transformable3D: Sized {
155 fn to_coord3d(&self) -> Coord3D;
156 fn from_coord3d(c: Coord3D) -> Self;
157}
158
159impl Transformable for Coord {
160 fn to_coord(&self) -> Coord {
161 *self
162 }
163 fn from_coord(c: Coord) -> Self {
164 c
165 }
166}
167
168impl Transformable for (f64, f64) {
169 fn to_coord(&self) -> Coord {
170 Coord {
171 x: self.0,
172 y: self.1,
173 }
174 }
175 fn from_coord(c: Coord) -> Self {
176 (c.x, c.y)
177 }
178}
179
180impl Transformable3D for Coord3D {
181 fn to_coord3d(&self) -> Coord3D {
182 *self
183 }
184
185 fn from_coord3d(c: Coord3D) -> Self {
186 c
187 }
188}
189
190impl Transformable3D for (f64, f64, f64) {
191 fn to_coord3d(&self) -> Coord3D {
192 Coord3D {
193 x: self.0,
194 y: self.1,
195 z: self.2,
196 }
197 }
198
199 fn from_coord3d(c: Coord3D) -> Self {
200 (c.x, c.y, c.z)
201 }
202}
203
204#[cfg(feature = "geo-types")]
205impl Transformable for geo_types::Coord<f64> {
206 fn to_coord(&self) -> Coord {
207 Coord {
208 x: self.x,
209 y: self.y,
210 }
211 }
212 fn from_coord(c: Coord) -> Self {
213 geo_types::Coord { x: c.x, y: c.y }
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220
221 #[test]
222 fn coord_from_tuple() {
223 let c: Coord = (1.0, 2.0).into();
224 assert_eq!(c.x, 1.0);
225 assert_eq!(c.y, 2.0);
226 }
227
228 #[test]
229 fn tuple_from_coord() {
230 let t: (f64, f64) = Coord::new(3.0, 4.0).into();
231 assert_eq!(t, (3.0, 4.0));
232 }
233
234 #[test]
235 fn coord3d_from_tuple() {
236 let c: Coord3D = (1.0, 2.0, 3.0).into();
237 assert_eq!(c.x, 1.0);
238 assert_eq!(c.y, 2.0);
239 assert_eq!(c.z, 3.0);
240 }
241
242 #[test]
243 fn tuple_from_coord3d() {
244 let t: (f64, f64, f64) = Coord3D::new(3.0, 4.0, 5.0).into();
245 assert_eq!(t, (3.0, 4.0, 5.0));
246 }
247
248 #[test]
249 fn transformable_roundtrip_tuple() {
250 let original = (10.0, 20.0);
251 let coord = original.to_coord();
252 let back = <(f64, f64)>::from_coord(coord);
253 assert_eq!(original, back);
254 }
255
256 #[test]
257 fn transformable3d_roundtrip_tuple() {
258 let original = (10.0, 20.0, 30.0);
259 let coord = original.to_coord3d();
260 let back = <(f64, f64, f64)>::from_coord3d(coord);
261 assert_eq!(original, back);
262 }
263
264 #[test]
265 fn bounds_basics() {
266 let bounds = Bounds::new(-10.0, 20.0, 30.0, 40.0);
267 assert_eq!(bounds.width(), 40.0);
268 assert_eq!(bounds.height(), 20.0);
269 assert!(bounds.is_valid());
270 }
271
272 #[test]
273 fn bounds_invalid_when_non_finite_or_reversed() {
274 assert!(!Bounds::new(f64::NAN, 20.0, 30.0, 40.0).is_valid());
275 assert!(!Bounds::new(-10.0, 20.0, f64::INFINITY, 40.0).is_valid());
276 assert!(!Bounds::new(30.0, 20.0, -10.0, 40.0).is_valid());
277 assert!(!Bounds::new(-10.0, 40.0, 30.0, 20.0).is_valid());
278 }
279}
280
281#[cfg(all(test, feature = "serde"))]
282mod serde_tests {
283 use super::*;
284
285 #[test]
286 fn coordinate_types_roundtrip_through_json() {
287 let coord = Coord::new(-74.006, 40.7128);
288 let json = serde_json::to_string(&coord).unwrap();
289 assert_eq!(serde_json::from_str::<Coord>(&json).unwrap(), coord);
290
291 let coord3d = Coord3D::new(-74.006, 40.7128, 15.0);
292 let json = serde_json::to_string(&coord3d).unwrap();
293 assert_eq!(serde_json::from_str::<Coord3D>(&json).unwrap(), coord3d);
294
295 let bounds = Bounds::new(-75.0, 40.0, -73.0, 41.0);
296 let json = serde_json::to_string(&bounds).unwrap();
297 assert_eq!(serde_json::from_str::<Bounds>(&json).unwrap(), bounds);
298 }
299}