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