toolbox_rs/
bounding_box.rs

1use crate::geometry::primitives::FPCoordinate;
2
3#[derive(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.lat.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 is_valid(&self) -> bool {
57        self.min.lat <= self.max.lat && self.min.lon <= self.max.lon
58    }
59}
60
61impl From<&BoundingBox> for geojson::Bbox {
62    fn from(bbox: &BoundingBox) -> geojson::Bbox {
63        let result = vec![
64            bbox.min.lon as f64 / 1000000.,
65            bbox.min.lat as f64 / 1000000.,
66            bbox.max.lon as f64 / 1000000.,
67            bbox.max.lat as f64 / 1000000.,
68        ];
69        result
70    }
71}
72
73#[cfg(test)]
74pub mod tests {
75    use crate::{bounding_box::BoundingBox, geometry::primitives::FPCoordinate};
76
77    #[test]
78    fn grid() {
79        let mut coordinates: Vec<FPCoordinate> = Vec::new();
80        for i in 0..100 {
81            coordinates.push(FPCoordinate::new(i / 10, i % 10));
82        }
83
84        let expected = BoundingBox {
85            min: FPCoordinate::new(0, 0),
86            max: FPCoordinate::new(9, 9),
87        };
88        assert!(expected.is_valid());
89        let result = BoundingBox::from_coordinates(&coordinates);
90        assert_eq!(expected, result);
91    }
92
93    #[test]
94    fn center() {
95        let bbox = BoundingBox {
96            min: FPCoordinate::new_from_lat_lon(33.406637, -115.000801),
97            max: FPCoordinate::new_from_lat_lon(33.424732, -114.905286),
98        };
99        assert!(bbox.is_valid());
100        let center = bbox.center();
101        assert_eq!(center, FPCoordinate::new(33415684, -114953044));
102    }
103
104    #[test]
105    fn center_with_rounding() {
106        let bbox = BoundingBox {
107            min: FPCoordinate::new(0, 0),
108            max: FPCoordinate::new(9, 9),
109        };
110        assert!(bbox.is_valid());
111        let center = bbox.center();
112        assert_eq!(center, FPCoordinate::new(4, 4));
113    }
114
115    #[test]
116    fn center_without_rounding() {
117        let bbox = BoundingBox {
118            min: FPCoordinate::new(0, 0),
119            max: FPCoordinate::new(100, 100),
120        };
121        assert!(bbox.is_valid());
122        let center = bbox.center();
123        assert_eq!(center, FPCoordinate::new(50, 50));
124    }
125
126    #[test]
127    fn invalid() {
128        let bbox = BoundingBox::invalid();
129        assert!(bbox.min.lat > bbox.max.lat);
130        assert!(bbox.min.lon > bbox.max.lon);
131    }
132
133    #[test]
134    fn extend_with_extend_invalid() {
135        let mut c1 = BoundingBox::invalid();
136        let c2 =
137            BoundingBox::from_coordinates(&[FPCoordinate::new(11, 50), FPCoordinate::new(50, 37)]);
138        c1.extend_with(&c2);
139        assert!(c1.is_valid());
140
141        assert_eq!(c2.min, FPCoordinate::new(11, 37));
142        assert_eq!(c2.max, FPCoordinate::new(50, 50));
143    }
144
145    #[test]
146    fn geojson_conversion() {
147        let b1 =
148            BoundingBox::from_coordinates(&[FPCoordinate::new(11, 50), FPCoordinate::new(50, 37)]);
149        let g1 = geojson::Bbox::from(&b1);
150        assert_eq!(4, g1.len());
151
152        assert_eq!(b1.min.lon as f64 / 1000000., g1[0]);
153        assert_eq!(b1.min.lat as f64 / 1000000., g1[1]);
154        assert_eq!(b1.max.lon as f64 / 1000000., g1[2]);
155        assert_eq!(b1.max.lat as f64 / 1000000., g1[3]);
156    }
157}