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