osm_io/osm/model/
bounding_box.rs1use 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}