1use crate::geometry::FPCoordinate;
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4pub struct BoundingBox {
5 min: FPCoordinate,
6 max: FPCoordinate,
7}
8
9impl BoundingBox {
10 pub fn from_coordinates(coordinates: &[FPCoordinate]) -> BoundingBox {
11 debug_assert!(!coordinates.is_empty());
12 let mut min_coordinate = FPCoordinate::max();
13 let mut max_coordinate = FPCoordinate::min();
14
15 coordinates.iter().for_each(|coordinate| {
16 min_coordinate.lat = min_coordinate.lat.min(coordinate.lat);
17 min_coordinate.lon = min_coordinate.lon.min(coordinate.lon);
18 max_coordinate.lat = max_coordinate.lat.max(coordinate.lat);
19 max_coordinate.lon = max_coordinate.lon.max(coordinate.lon);
20 });
21
22 BoundingBox {
23 min: min_coordinate,
24 max: max_coordinate,
25 }
26 }
27
28 pub fn invalid() -> BoundingBox {
29 BoundingBox {
30 min: FPCoordinate::max(),
31 max: FPCoordinate::min(),
32 }
33 }
34
35 pub fn extend_with(&mut self, other: &BoundingBox) {
36 self.min.lat = self.min.lat.min(other.min.lat);
37 self.min.lon = self.min.lon.min(other.min.lon);
38
39 self.max.lat = self.max.lat.max(other.max.lat);
40 self.max.lon = self.max.lon.max(other.max.lon);
41 }
42
43 pub fn center(&self) -> FPCoordinate {
44 debug_assert!(self.min.lat <= self.max.lat);
45 debug_assert!(self.min.lon <= self.max.lon);
46
47 let lat_diff = self.max.lat - self.min.lat;
48 let lon_diff = self.max.lon - self.min.lon;
49
50 FPCoordinate {
51 lat: self.min.lat + lat_diff / 2,
52 lon: self.min.lon + lon_diff / 2,
53 }
54 }
55
56 pub fn contains(&self, coordinate: &FPCoordinate) -> bool {
82 coordinate.lat >= self.min.lat
83 && coordinate.lat <= self.max.lat
84 && coordinate.lon >= self.min.lon
85 && coordinate.lon <= self.max.lon
86 }
87
88 pub fn min_distance(&self, coordinate: &FPCoordinate) -> f64 {
118 if self.contains(coordinate) {
119 return 0.;
120 }
121
122 let c1 = self.max;
123 let c2 = self.min;
124 let c3 = FPCoordinate::new(c1.lat, c2.lon);
125 let c4 = FPCoordinate::new(c2.lat, c1.lon);
126
127 c1.distance_to(coordinate)
128 .min(c2.distance_to(coordinate))
129 .min(c3.distance_to(coordinate))
130 .min(c4.distance_to(coordinate))
131 }
132
133 pub fn is_valid(&self) -> bool {
134 self.min.lat <= self.max.lat && self.min.lon <= self.max.lon
135 }
136
137 pub fn from_coordinate(coordinate: &FPCoordinate) -> BoundingBox {
138 BoundingBox {
139 min: *coordinate,
140 max: *coordinate,
141 }
142 }
143}
144
145impl From<&BoundingBox> for geojson::Bbox {
146 fn from(bbox: &BoundingBox) -> geojson::Bbox {
147 let result = vec![
148 bbox.min.lon as f64 / 1000000.,
149 bbox.min.lat as f64 / 1000000.,
150 bbox.max.lon as f64 / 1000000.,
151 bbox.max.lat as f64 / 1000000.,
152 ];
153 result
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use crate::{bounding_box::BoundingBox, geometry::FPCoordinate};
160
161 #[test]
162 fn grid() {
163 let mut coordinates: Vec<FPCoordinate> = Vec::new();
164 for i in 0..100 {
165 coordinates.push(FPCoordinate::new(i / 10, i % 10));
166 }
167
168 let expected = BoundingBox {
169 min: FPCoordinate::new(0, 0),
170 max: FPCoordinate::new(9, 9),
171 };
172 assert!(expected.is_valid());
173 let result = BoundingBox::from_coordinates(&coordinates);
174 assert_eq!(expected, result);
175 }
176
177 #[test]
178 fn center() {
179 let bbox = BoundingBox {
180 min: FPCoordinate::new_from_lat_lon(33.406637, -115.000801),
181 max: FPCoordinate::new_from_lat_lon(33.424732, -114.905286),
182 };
183 assert!(bbox.is_valid());
184 let center = bbox.center();
185 assert_eq!(center, FPCoordinate::new(33415684, -114953044));
186 }
187
188 #[test]
189 fn center_with_rounding() {
190 let bbox = BoundingBox {
191 min: FPCoordinate::new(0, 0),
192 max: FPCoordinate::new(9, 9),
193 };
194 assert!(bbox.is_valid());
195 let center = bbox.center();
196 assert_eq!(center, FPCoordinate::new(4, 4));
197 }
198
199 #[test]
200 fn center_without_rounding() {
201 let bbox = BoundingBox {
202 min: FPCoordinate::new(0, 0),
203 max: FPCoordinate::new(100, 100),
204 };
205 assert!(bbox.is_valid());
206 let center = bbox.center();
207 assert_eq!(center, FPCoordinate::new(50, 50));
208 }
209
210 #[test]
211 fn invalid() {
212 let bbox = BoundingBox::invalid();
213 assert!(bbox.min.lat > bbox.max.lat);
214 assert!(bbox.min.lon > bbox.max.lon);
215 }
216
217 #[test]
218 fn extend_with_extend_invalid() {
219 let mut c1 = BoundingBox::invalid();
220 let c2 =
221 BoundingBox::from_coordinates(&[FPCoordinate::new(11, 50), FPCoordinate::new(50, 37)]);
222 c1.extend_with(&c2);
223 assert!(c1.is_valid());
224
225 assert_eq!(c2.min, FPCoordinate::new(11, 37));
226 assert_eq!(c2.max, FPCoordinate::new(50, 50));
227 }
228
229 #[test]
230 fn extend_with_merge_two_valid() {
231 let mut b1 =
232 BoundingBox::from_coordinates(&[FPCoordinate::new(10, 10), FPCoordinate::new(20, 20)]);
233
234 let b2 =
235 BoundingBox::from_coordinates(&[FPCoordinate::new(15, 15), FPCoordinate::new(25, 25)]);
236
237 b1.extend_with(&b2);
238
239 assert_eq!(b1.min, FPCoordinate::new(10, 10));
240 assert_eq!(b1.max, FPCoordinate::new(25, 25));
241
242 println!("{:?}", b1);
243
244 assert!(b1.is_valid());
245 }
246
247 #[test]
248 fn geojson_conversion() {
249 let b1 =
250 BoundingBox::from_coordinates(&[FPCoordinate::new(11, 50), FPCoordinate::new(50, 37)]);
251 let g1 = geojson::Bbox::from(&b1);
252 assert_eq!(4, g1.len());
253
254 assert_eq!(b1.min.lon as f64 / 1000000., g1[0]);
255 assert_eq!(b1.min.lat as f64 / 1000000., g1[1]);
256 assert_eq!(b1.max.lon as f64 / 1000000., g1[2]);
257 assert_eq!(b1.max.lat as f64 / 1000000., g1[3]);
258 }
259
260 #[test]
261 fn extend_with_longitude_extension() {
262 let mut b1 = BoundingBox::from_coordinates(&[
263 FPCoordinate::new(10, -20), FPCoordinate::new(15, -10), ]);
266
267 let b2 = BoundingBox::from_coordinates(&[
268 FPCoordinate::new(12, 0), FPCoordinate::new(14, 10), ]);
271
272 assert_eq!(b1.max.lon, -10);
274
275 b1.extend_with(&b2);
277
278 assert_eq!(b1.min.lon, -20); assert_eq!(b1.max.lon, 10); assert!(b1.is_valid());
283 }
284
285 #[test]
286 fn test_contains() {
287 let bbox =
288 BoundingBox::from_coordinates(&[FPCoordinate::new(10, 10), FPCoordinate::new(20, 20)]);
289
290 assert!(bbox.contains(&FPCoordinate::new(15, 15)));
292 assert!(bbox.contains(&FPCoordinate::new(10, 10))); assert!(bbox.contains(&FPCoordinate::new(20, 20))); assert!(!bbox.contains(&FPCoordinate::new(9, 15))); assert!(!bbox.contains(&FPCoordinate::new(21, 15))); assert!(!bbox.contains(&FPCoordinate::new(15, 9))); assert!(!bbox.contains(&FPCoordinate::new(15, 21))); }
301
302 #[test]
303 fn test_min_distance() {
304 let bbox =
305 BoundingBox::from_coordinates(&[FPCoordinate::new(10, 10), FPCoordinate::new(20, 20)]);
306
307 assert_eq!(bbox.min_distance(&FPCoordinate::new(15, 15)), 0.0);
309
310 let corner_point = FPCoordinate::new(10, 10);
312 let distance_to_corner = bbox.min_distance(&FPCoordinate::new(5, 5));
313 assert!(distance_to_corner > 0.0);
314 assert_eq!(
315 distance_to_corner,
316 corner_point.distance_to(&FPCoordinate::new(5, 5))
317 );
318
319 let east_point = FPCoordinate::new(15, 25);
321 let distance_east = bbox.min_distance(&east_point);
322 assert!(distance_east > 0.0);
323 assert_eq!(
324 distance_east,
325 FPCoordinate::new(20, 20).distance_to(&east_point)
326 );
327 }
328
329 #[test]
330 fn test_from_coordinate() {
331 let coord = FPCoordinate::new(15, 25);
332 let bbox = BoundingBox::from_coordinate(&coord);
333
334 assert_eq!(bbox.min, coord);
335 assert_eq!(bbox.max, coord);
336 assert!(bbox.is_valid());
337 assert!(bbox.contains(&coord));
338 assert_eq!(bbox.min_distance(&coord), 0.0);
339 }
340}