1use crate::geometry::Geometry;
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
16pub struct BoundingBox {
17 pub min_lng: f64,
18 pub min_lat: f64,
19 pub max_lng: f64,
20 pub max_lat: f64,
21}
22
23impl BoundingBox {
24 pub fn new(min_lng: f64, min_lat: f64, max_lng: f64, max_lat: f64) -> Self {
25 Self {
26 min_lng,
27 min_lat,
28 max_lng,
29 max_lat,
30 }
31 }
32
33 pub fn from_point(lng: f64, lat: f64) -> Self {
34 Self {
35 min_lng: lng,
36 min_lat: lat,
37 max_lng: lng,
38 max_lat: lat,
39 }
40 }
41
42 pub fn crosses_antimeridian(&self) -> bool {
43 self.min_lng > self.max_lng
44 }
45
46 pub fn area(&self) -> f64 {
48 let lat_span = self.max_lat - self.min_lat;
49 let lng_span = if self.crosses_antimeridian() {
50 (180.0 - self.min_lng) + (self.max_lng + 180.0)
51 } else {
52 self.max_lng - self.min_lng
53 };
54 lat_span * lng_span
55 }
56
57 pub fn margin(&self) -> f64 {
59 let lat_span = self.max_lat - self.min_lat;
60 let lng_span = if self.crosses_antimeridian() {
61 (180.0 - self.min_lng) + (self.max_lng + 180.0)
62 } else {
63 self.max_lng - self.min_lng
64 };
65 lat_span + lng_span
66 }
67
68 pub fn contains_point(&self, lng: f64, lat: f64) -> bool {
69 if lat < self.min_lat || lat > self.max_lat {
70 return false;
71 }
72 if self.crosses_antimeridian() {
73 lng >= self.min_lng || lng <= self.max_lng
74 } else {
75 lng >= self.min_lng && lng <= self.max_lng
76 }
77 }
78
79 pub fn contains_bbox(&self, other: &BoundingBox) -> bool {
80 if other.min_lat < self.min_lat || other.max_lat > self.max_lat {
81 return false;
82 }
83 match (self.crosses_antimeridian(), other.crosses_antimeridian()) {
84 (false, false) => other.min_lng >= self.min_lng && other.max_lng <= self.max_lng,
85 (true, false) => other.min_lng >= self.min_lng || other.max_lng <= self.max_lng,
86 (false, true) => false,
87 (true, true) => other.min_lng >= self.min_lng && other.max_lng <= self.max_lng,
88 }
89 }
90
91 pub fn intersects(&self, other: &BoundingBox) -> bool {
92 if self.max_lat < other.min_lat || self.min_lat > other.max_lat {
93 return false;
94 }
95 match (self.crosses_antimeridian(), other.crosses_antimeridian()) {
96 (false, false) => self.min_lng <= other.max_lng && self.max_lng >= other.min_lng,
97 (true, false) => other.max_lng >= self.min_lng || other.min_lng <= self.max_lng,
98 (false, true) => self.max_lng >= other.min_lng || self.min_lng <= other.max_lng,
99 (true, true) => true,
100 }
101 }
102
103 pub fn union(&self, other: &BoundingBox) -> BoundingBox {
104 let min_lat = self.min_lat.min(other.min_lat);
105 let max_lat = self.max_lat.max(other.max_lat);
106 let (min_lng, max_lng) =
107 union_longitude(self.min_lng, self.max_lng, other.min_lng, other.max_lng);
108 BoundingBox {
109 min_lng,
110 min_lat,
111 max_lng,
112 max_lat,
113 }
114 }
115
116 pub fn extend_point(&mut self, lng: f64, lat: f64) {
117 self.min_lat = self.min_lat.min(lat);
118 self.max_lat = self.max_lat.max(lat);
119 if self.crosses_antimeridian() {
120 let in_east = lng >= self.min_lng;
121 let in_west = lng <= self.max_lng;
122 if !in_east && !in_west {
123 if self.min_lng - lng < lng - self.max_lng {
124 self.min_lng = lng;
125 } else {
126 self.max_lng = lng;
127 }
128 }
129 } else {
130 self.min_lng = self.min_lng.min(lng);
131 self.max_lng = self.max_lng.max(lng);
132 }
133 }
134
135 pub fn expand_meters(&self, meters: f64) -> BoundingBox {
137 let dlat = meters / 110_540.0;
138 let center_lat = (self.min_lat + self.max_lat) / 2.0;
139 let dlng = meters / (111_320.0 * center_lat.to_radians().cos());
140 BoundingBox {
141 min_lng: self.min_lng - dlng,
142 min_lat: self.min_lat - dlat,
143 max_lng: self.max_lng + dlng,
144 max_lat: self.max_lat + dlat,
145 }
146 }
147
148 pub fn overlap_area(&self, other: &BoundingBox) -> f64 {
149 if !self.intersects(other) {
150 return 0.0;
151 }
152 let lat_overlap = self.max_lat.min(other.max_lat) - self.min_lat.max(other.min_lat);
153 if lat_overlap <= 0.0 {
154 return 0.0;
155 }
156 let lng_overlap =
157 lng_overlap_span(self.min_lng, self.max_lng, other.min_lng, other.max_lng);
158 lat_overlap * lng_overlap
159 }
160
161 pub fn enlargement(&self, other: &BoundingBox) -> f64 {
162 self.union(other).area() - self.area()
163 }
164}
165
166fn lng_overlap_span(a_min: f64, a_max: f64, b_min: f64, b_max: f64) -> f64 {
167 let a_wraps = a_min > a_max;
168 let b_wraps = b_min > b_max;
169 match (a_wraps, b_wraps) {
170 (false, false) => (a_max.min(b_max) - a_min.max(b_min)).max(0.0),
171 (true, false) => {
172 let east = (180.0_f64.min(b_max) - a_min.max(b_min)).max(0.0);
173 let west = (a_max.min(b_max) - (-180.0_f64).max(b_min)).max(0.0);
174 east + west
175 }
176 (false, true) => lng_overlap_span(b_min, b_max, a_min, a_max),
177 (true, true) => {
178 let east = (180.0 - a_min.max(b_min)).max(0.0);
179 let west = (a_max.min(b_max) + 180.0).max(0.0);
180 east + west
181 }
182 }
183}
184
185fn union_longitude(a_min: f64, a_max: f64, b_min: f64, b_max: f64) -> (f64, f64) {
186 let a_wraps = a_min > a_max;
187 let b_wraps = b_min > b_max;
188 match (a_wraps, b_wraps) {
189 (false, false) => {
190 let simple_min = a_min.min(b_min);
191 let simple_max = a_max.max(b_max);
192 let simple_span = simple_max - simple_min;
193 let gap = (b_min - a_max).max(0.0).min((a_min - b_max).max(0.0));
194 if gap <= 0.0 {
195 return (simple_min, simple_max);
196 }
197 let wrap_min = a_min.max(b_min);
198 let wrap_max = a_max.min(b_max);
199 let wrap_span = (180.0 - wrap_min) + (wrap_max + 180.0);
200 if wrap_span < simple_span {
201 (wrap_min, wrap_max)
202 } else {
203 (simple_min, simple_max)
204 }
205 }
206 (true, false) => {
207 if b_min >= a_min || b_max <= a_max {
208 (a_min, a_max)
209 } else {
210 let extend_east = a_min - b_min;
211 let extend_west = b_max - a_max;
212 if extend_east <= extend_west {
213 (b_min, a_max)
214 } else {
215 (a_min, b_max)
216 }
217 }
218 }
219 (false, true) => union_longitude(b_min, b_max, a_min, a_max),
220 (true, true) => (a_min.min(b_min), a_max.max(b_max)),
221 }
222}
223
224pub fn geometry_bbox(geom: &Geometry) -> BoundingBox {
226 match geom {
227 Geometry::Point { coordinates } => BoundingBox::from_point(coordinates[0], coordinates[1]),
228 Geometry::LineString { coordinates } => bbox_from_coords(coordinates),
229 Geometry::Polygon { coordinates } => {
230 let all: Vec<[f64; 2]> = coordinates.iter().flat_map(|r| r.iter().copied()).collect();
231 bbox_from_coords(&all)
232 }
233 Geometry::MultiPoint { coordinates } => bbox_from_coords(coordinates),
234 Geometry::MultiLineString { coordinates } => {
235 let all: Vec<[f64; 2]> = coordinates
236 .iter()
237 .flat_map(|ls| ls.iter().copied())
238 .collect();
239 bbox_from_coords(&all)
240 }
241 Geometry::MultiPolygon { coordinates } => {
242 let all: Vec<[f64; 2]> = coordinates
243 .iter()
244 .flat_map(|poly| poly.iter().flat_map(|ring| ring.iter().copied()))
245 .collect();
246 bbox_from_coords(&all)
247 }
248 Geometry::GeometryCollection { geometries } => {
249 if geometries.is_empty() {
250 return BoundingBox::new(0.0, 0.0, 0.0, 0.0);
251 }
252 let mut result = geometry_bbox(&geometries[0]);
253 for geom in &geometries[1..] {
254 result = result.union(&geometry_bbox(geom));
255 }
256 result
257 }
258 }
259}
260
261fn bbox_from_coords(coords: &[[f64; 2]]) -> BoundingBox {
262 if coords.is_empty() {
263 return BoundingBox::new(0.0, 0.0, 0.0, 0.0);
264 }
265 let mut min_lng = coords[0][0];
266 let mut min_lat = coords[0][1];
267 let mut max_lng = coords[0][0];
268 let mut max_lat = coords[0][1];
269 for c in &coords[1..] {
270 min_lng = min_lng.min(c[0]);
271 min_lat = min_lat.min(c[1]);
272 max_lng = max_lng.max(c[0]);
273 max_lat = max_lat.max(c[1]);
274 }
275 BoundingBox {
276 min_lng,
277 min_lat,
278 max_lng,
279 max_lat,
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn point_bbox() {
289 let bb = geometry_bbox(&Geometry::point(10.0, 20.0));
290 assert_eq!(bb.min_lng, 10.0);
291 assert_eq!(bb.max_lng, 10.0);
292 assert_eq!(bb.area(), 0.0);
293 }
294
295 #[test]
296 fn polygon_bbox() {
297 let poly = Geometry::polygon(vec![vec![
298 [-10.0, -5.0],
299 [10.0, -5.0],
300 [10.0, 5.0],
301 [-10.0, 5.0],
302 [-10.0, -5.0],
303 ]]);
304 let bb = geometry_bbox(&poly);
305 assert_eq!(bb.min_lng, -10.0);
306 assert_eq!(bb.max_lng, 10.0);
307 }
308
309 #[test]
310 fn contains_point_normal() {
311 let bb = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
312 assert!(bb.contains_point(5.0, 5.0));
313 assert!(!bb.contains_point(11.0, 5.0));
314 }
315
316 #[test]
317 fn contains_point_antimeridian() {
318 let bb = BoundingBox::new(170.0, -10.0, -170.0, 10.0);
319 assert!(bb.crosses_antimeridian());
320 assert!(bb.contains_point(175.0, 0.0));
321 assert!(bb.contains_point(-175.0, 0.0));
322 assert!(!bb.contains_point(0.0, 0.0));
323 }
324
325 #[test]
326 fn intersects_normal() {
327 let a = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
328 let b = BoundingBox::new(5.0, 5.0, 15.0, 15.0);
329 assert!(a.intersects(&b));
330 let c = BoundingBox::new(20.0, 20.0, 30.0, 30.0);
331 assert!(!a.intersects(&c));
332 }
333
334 #[test]
335 fn intersects_antimeridian() {
336 let a = BoundingBox::new(170.0, -5.0, -170.0, 5.0);
337 let b = BoundingBox::new(175.0, 0.0, -175.0, 3.0);
338 assert!(a.intersects(&b));
339 let c = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
340 assert!(!a.intersects(&c));
341 }
342
343 #[test]
344 fn union_normal() {
345 let a = BoundingBox::new(0.0, 0.0, 5.0, 5.0);
346 let b = BoundingBox::new(3.0, 3.0, 10.0, 10.0);
347 let u = a.union(&b);
348 assert_eq!(u.min_lng, 0.0);
349 assert_eq!(u.max_lng, 10.0);
350 }
351
352 #[test]
353 fn overlap_area_partial() {
354 let a = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
355 let b = BoundingBox::new(5.0, 5.0, 15.0, 15.0);
356 assert!((a.overlap_area(&b) - 25.0).abs() < 1e-10);
357 }
358
359 #[test]
360 fn expand_meters_basic() {
361 let bb = BoundingBox::from_point(0.0, 0.0);
362 let expanded = bb.expand_meters(1000.0);
363 assert!((expanded.min_lat - (-0.00905)).abs() < 0.001);
364 }
365
366 #[test]
367 fn geometry_collection_bbox() {
368 let gc = Geometry::GeometryCollection {
369 geometries: vec![Geometry::point(0.0, 0.0), Geometry::point(10.0, 10.0)],
370 };
371 let bb = geometry_bbox(&gc);
372 assert_eq!(bb.min_lng, 0.0);
373 assert_eq!(bb.max_lng, 10.0);
374 }
375}