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