osm_io/osm/model/
bounding_box.rs

1use std::fmt::{Display, Formatter};
2use std::str::FromStr;
3
4use anyhow::anyhow;
5
6use crate::osm::model::coordinate::Coordinate;
7
8#[derive(Debug, Clone)]
9pub struct BoundingBox {
10    left: f64,
11    bottom: f64,
12    right: f64,
13    top: f64,
14}
15
16impl BoundingBox {
17    pub fn new(left: f64, bottom: f64, right: f64, top: f64) -> BoundingBox {
18        BoundingBox {
19            left,
20            bottom,
21            right,
22            top,
23        }
24    }
25
26    pub fn from_point(c: &Coordinate) -> BoundingBox {
27        BoundingBox::new(c.lon(), c.lat(), c.lon(), c.lat())
28    }
29
30    pub fn merge_point(&mut self, coordinate: &Coordinate) {
31        if coordinate.lon() < self.left {
32            self.left = coordinate.lon();
33        }
34
35        if coordinate.lon() > self.right {
36            self.right = coordinate.lon();
37        }
38
39        if coordinate.lat() > self.top {
40            self.top = coordinate.lat();
41        }
42
43        if coordinate.lat() < self.bottom {
44            self.bottom = coordinate.lat();
45        }
46    }
47
48    pub fn merge_bounding_box(&mut self, other: &BoundingBox) {
49        if other.left < self.left {
50            self.left = other.left;
51        }
52
53        if other.right > self.right {
54            self.right = other.right;
55        }
56
57        if other.top > self.top {
58            self.top = other.top;
59        }
60
61        if other.bottom < self.bottom {
62            self.bottom = other.bottom;
63        }
64    }
65
66    pub fn left(&self) -> f64 {
67        self.left
68    }
69
70    pub fn right(&self) -> f64 {
71        self.right
72    }
73
74    pub fn top(&self) -> f64 {
75        self.top
76    }
77
78    pub fn bottom(&self) -> f64 {
79        self.bottom
80    }
81}
82
83impl Display for BoundingBox {
84    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
85        write!(f, "left: {}, bottom: {}, right: {}, top: {}", self.left, self.bottom, self.right, self.top)
86    }
87}
88
89impl FromStr for BoundingBox {
90    type Err = anyhow::Error;
91
92    fn from_str(s: &str) -> Result<Self, Self::Err> {
93        let e = anyhow!("Bounding box string must be in the form of 'left,bottom,right,top' as in -180.0, -90.0, 180.0, 90.0 with optional white space around commas. Got {} instead", s);
94        let parts: Vec<&str> = s.split(',')
95            .map(|s| s.trim())
96            .collect();
97        if parts.len() < 4 {
98            Err(e)
99        } else {
100            let left = f64::from_str(parts[0])?;
101            let bottom = f64::from_str(parts[1])?;
102            let right = f64::from_str(parts[2])?;
103            let top = f64::from_str(parts[3])?;
104            if !(-180.0..=180.0).contains(&left)
105                || !(-90.0..=90.0).contains(&bottom)
106                || !(-180.0..=180.0).contains(&right)
107                || !(-90.0..=90.0).contains(&top)
108            {
109                Err(e)
110            } else {
111                Ok(BoundingBox::new(left, bottom, right, top))
112            }
113        }
114    }
115}
116
117impl Default for BoundingBox {
118    fn default() -> Self {
119        BoundingBox::new(-180.0, -90.0, 180.0, 90.0)
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use std::str::FromStr;
126
127    use crate::osm::model::bounding_box::BoundingBox;
128
129    #[test]
130    fn test_from_str() -> Result<(), anyhow::Error> {
131        let bounding_box = BoundingBox::from_str("-180.0, -90.0, 180.0, 90.0")?;
132        assert_eq!(bounding_box.left, -180.0);
133        assert_eq!(bounding_box.bottom, -90.0);
134        assert_eq!(bounding_box.right, 180.0);
135        assert_eq!(bounding_box.top, 90.0);
136        Ok(())
137    }
138
139    #[test]
140    #[should_panic]
141    fn test_invalid_values() {
142        BoundingBox::from_str("-180.1, -90.0, 180.0, 90.0").unwrap();
143    }
144}