1use serde::{Deserialize, Serialize};
3
4use crate::lnglat::LngLat;
5use crate::parsing::parse_bbox;
6use crate::tile::Tile;
7use crate::tile_like::TileLike;
8use crate::{Point2d, xy};
9
10#[derive(Debug, Clone, Copy, PartialEq, Deserialize, Serialize)]
12pub struct BBoxTuple(f64, f64, f64, f64);
13
14#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
16pub struct BBox {
17 pub west: f64,
19 pub south: f64,
21 pub east: f64,
23 pub north: f64,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct WebBBox {
30 min: Point2d<f64>,
32
33 max: Point2d<f64>,
35}
36
37pub enum BBoxContainable {
39 LngLat(LngLat),
41 BBox(BBox),
43 Tile(Tile),
45}
46
47impl From<(f64, f64, f64, f64)> for BBox {
48 fn from(bbox: (f64, f64, f64, f64)) -> Self {
49 Self::new(bbox.0, bbox.1, bbox.2, bbox.3)
50 }
51}
52
53impl From<BBox> for (f64, f64, f64, f64) {
54 fn from(bbox: BBox) -> Self {
55 (bbox.west, bbox.south, bbox.east, bbox.north)
56 }
57}
58
59impl From<(i32, i32, i32, i32)> for BBox {
60 fn from(bbox: (i32, i32, i32, i32)) -> Self {
61 (
63 f64::from(bbox.0),
64 f64::from(bbox.1),
65 f64::from(bbox.2),
66 f64::from(bbox.3),
67 )
68 .into()
69 }
76}
77
78impl BBox {
79 #[must_use]
81 pub const fn new(west: f64, south: f64, east: f64, north: f64) -> Self {
82 Self {
83 west,
84 south,
85 east,
86 north,
87 }
88 }
89
90 #[must_use]
92 pub const fn world_planet() -> Self {
93 Self {
94 west: -180.0,
95 south: -90.0,
96 east: 180.0,
97 north: 90.0,
98 }
99 }
100
101 #[must_use]
103 pub const fn world_web() -> Self {
104 Self {
105 west: -180.0,
106 south: -85.051_129,
107 east: 180.0,
108 north: 85.051_129,
109 }
110 }
111
112 #[must_use]
113 pub const fn clamp_web(&self) -> Self {
114 Self {
115 west: self.west.max(-180.0),
116 south: self.south.max(-85.051_129),
117 east: self.east.min(180.0),
118 north: self.north.min(85.051_129),
119 }
120 }
121
122 #[must_use]
123 pub const fn clamp(&self, o: &Self) -> Self {
124 Self {
125 west: self.west.max(o.west),
126 south: self.south.max(o.south),
127 east: self.east.min(o.east),
128 north: self.north.min(o.north),
129 }
130 }
131
132 #[must_use]
133 pub fn geo_wrap(&self) -> Self {
134 let east = LngLat::geo_wrap_lng(self.east);
135 let west = LngLat::geo_wrap_lng(self.west);
136
137 Self {
138 west,
139 south: self.south,
140 east,
141 north: self.north,
142 }
143 }
144
145 #[must_use]
146 pub fn is_antimeridian(&self) -> bool {
147 self.west > self.east
148 }
149
150 #[must_use]
152 pub fn crosses_antimeridian(&self) -> bool {
153 self.west > self.east
154 }
155
156 #[must_use]
158 pub const fn tuple(&self) -> (f64, f64, f64, f64) {
159 (self.west(), self.south(), self.east(), self.north())
160 }
161
162 #[must_use]
164 pub const fn north(&self) -> f64 {
165 self.north
166 }
167
168 #[must_use]
170 pub const fn south(&self) -> f64 {
171 self.south
172 }
173
174 #[must_use]
176 pub const fn east(&self) -> f64 {
177 self.east
178 }
179
180 #[must_use]
182 pub const fn west(&self) -> f64 {
183 self.west
184 }
185
186 #[must_use]
188 pub const fn top(&self) -> f64 {
189 self.north
190 }
191
192 #[must_use]
194 pub const fn bottom(&self) -> f64 {
195 self.south
196 }
197
198 #[must_use]
200 pub const fn right(&self) -> f64 {
201 self.east
202 }
203
204 #[must_use]
206 pub const fn left(&self) -> f64 {
207 self.west
208 }
209
210 #[must_use]
212 #[inline]
213 pub fn json_arr(&self) -> String {
214 format!(
215 "[{},{},{},{}]",
216 self.west(),
217 self.south(),
218 self.east(),
219 self.north()
220 )
221 }
222
223 #[must_use]
225 #[inline]
226 pub fn projwin_str(&self) -> String {
227 format!(
228 "{} {} {} {}",
229 self.west(),
230 self.north(),
231 self.east(),
232 self.south()
233 )
234 }
235
236 #[must_use]
238 pub fn contains_lnglat(&self, lnglat: &LngLat) -> bool {
239 let lng = lnglat.lng();
240 let lat = lnglat.lat();
241 if self.crosses_antimeridian() {
242 if (lng >= self.west || lng <= self.east)
243 && lat >= self.south
244 && lat <= self.north
245 {
246 return true;
247 }
248 } else if lng >= self.west
249 && lng <= self.east
250 && lat >= self.south
251 && lat <= self.north
252 {
253 return true;
254 }
255 false
256 }
257
258 #[must_use]
260 pub fn contains_tile(&self, tile: &Tile) -> bool {
261 let bbox = tile.bbox();
262 self.contains_bbox(&bbox.into())
263 }
264
265 #[must_use]
267 pub fn contains_bbox(&self, other: &Self) -> bool {
268 if self.is_antimeridian() {
269 if other.is_antimeridian() {
271 if self.west <= other.west && self.east >= other.east {
273 self.south <= other.south && self.north >= other.north
275 } else {
276 false
277 }
278 } else if self.west <= other.west || self.east >= other.east {
280 self.south <= other.south && self.north >= other.north
282 } else {
283 false
284 }
285 } else {
286 self.north >= other.north
287 && self.south <= other.south
288 && self.east >= other.east
289 && self.west <= other.west
290 }
291 }
292
293 #[must_use]
295 pub fn contains(&self, other: &BBoxContainable) -> bool {
296 match other {
297 BBoxContainable::LngLat(lnglat) => self.contains_lnglat(lnglat),
298 BBoxContainable::BBox(bbox) => self.contains_bbox(bbox),
299 BBoxContainable::Tile(tile) => self.contains_tile(tile),
300 }
301 }
302
303 #[must_use]
305 pub fn is_within(&self, other: &Self) -> bool {
306 self.north <= other.north
307 && self.south >= other.south
308 && self.east <= other.east
309 && self.west >= other.west
310 }
311
312 #[must_use]
314 pub fn intersects(&self, other: &Self) -> bool {
315 self.north >= other.south
316 && self.south <= other.north
317 && self.east >= other.west
318 && self.west <= other.east
319 }
320
321 #[must_use]
347 pub fn bboxes(&self) -> Vec<Self> {
348 if self.crosses_antimeridian() {
349 vec![
350 Self {
351 north: self.north,
352 south: self.south,
353 east: 180.0,
354 west: self.west,
355 },
356 Self {
357 north: self.north,
358 south: self.south,
359 east: self.east,
360 west: -180.0,
361 },
362 ]
363 } else {
364 vec![*self]
365 }
366 }
367
368 #[must_use]
370 pub const fn ul(&self) -> LngLat {
371 LngLat::new(self.west, self.north)
372 }
373
374 #[must_use]
376 pub const fn ur(&self) -> LngLat {
377 LngLat::new(self.east, self.north)
378 }
379
380 #[must_use]
382 pub const fn lr(&self) -> LngLat {
383 LngLat::new(self.east, self.south)
384 }
385
386 #[must_use]
388 pub const fn ll(&self) -> LngLat {
389 LngLat::new(self.west, self.south)
390 }
391
392 #[must_use]
394 pub fn mbt_bounds(&self) -> String {
395 format!("{},{},{},{}", self.west, self.south, self.east, self.north)
396 }
397}
398
399#[must_use]
401pub fn geobbox_merge(bboxes: &[BBox]) -> BBox {
402 if bboxes.is_empty() {
403 return BBox::world_planet();
404 }
405 if bboxes.len() == 1 {
406 return bboxes[0];
407 }
408 let mut west = f64::INFINITY;
411 let mut south = f64::INFINITY;
412 let mut east = f64::NEG_INFINITY;
413 let mut north = f64::NEG_INFINITY;
414 for bbox in bboxes {
415 if bbox.west < west {
416 west = bbox.west;
417 }
418 if bbox.south < south {
419 south = bbox.south;
420 }
421 if bbox.east > east {
422 east = bbox.east;
423 }
424 if bbox.north > north {
425 north = bbox.north;
426 }
427 }
428 BBox::new(west, south, east, north).geo_wrap()
429}
430
431impl From<BBox> for BBoxTuple {
432 fn from(bbox: BBox) -> Self {
433 Self(bbox.west, bbox.south, bbox.east, bbox.north)
434 }
435}
436
437impl From<BBoxTuple> for BBox {
438 fn from(tuple: BBoxTuple) -> Self {
439 Self::new(tuple.0, tuple.1, tuple.2, tuple.3)
440 }
441}
442
443impl From<&String> for BBox {
444 fn from(s: &String) -> Self {
445 let s = s.trim_matches('"');
447 parse_bbox(s).unwrap_or_else(|_e| Self::world_planet())
448 }
449}
450
451impl TryFrom<&str> for BBox {
452 type Error = &'static str;
453
454 fn try_from(s: &str) -> Result<Self, Self::Error> {
455 parse_bbox(s).map_err(|_| "Failed to parse BBox")
456 }
457}
458
459impl WebBBox {
460 #[must_use]
461 pub const fn new(left: f64, bottom: f64, right: f64, top: f64) -> Self {
462 Self {
463 min: Point2d::new(left, bottom),
464 max: Point2d::new(right, top),
465 }
466 }
467
468 #[must_use]
469 #[inline]
470 pub const fn min(&self) -> Point2d<f64> {
471 self.min
472 }
473
474 #[must_use]
475 #[inline]
476 pub const fn max(&self) -> Point2d<f64> {
477 self.max
478 }
479
480 #[must_use]
481 #[inline]
482 pub const fn left(&self) -> f64 {
483 self.min.x
484 }
485
486 #[must_use]
487 #[inline]
488 pub const fn bottom(&self) -> f64 {
489 self.min.y
490 }
491
492 #[must_use]
493 #[inline]
494 pub const fn right(&self) -> f64 {
495 self.max.x
496 }
497
498 #[must_use]
499 #[inline]
500 pub const fn top(&self) -> f64 {
501 self.max.y
502 }
503
504 #[must_use]
505 #[inline]
506 pub fn width(&self) -> f64 {
507 self.max.x - self.min.x
508 }
509
510 #[must_use]
511 #[inline]
512 pub const fn west(&self) -> f64 {
513 self.min.x
514 }
515
516 #[must_use]
517 #[inline]
518 pub const fn south(&self) -> f64 {
519 self.min.y
520 }
521
522 #[must_use]
523 #[inline]
524 pub const fn east(&self) -> f64 {
525 self.max.x
526 }
527
528 #[must_use]
529 #[inline]
530 pub const fn north(&self) -> f64 {
531 self.max.y
532 }
533
534 #[must_use]
536 #[inline]
537 pub fn json_arr(&self) -> String {
538 format!(
539 "[{},{},{},{}]",
540 self.west(),
541 self.south(),
542 self.east(),
543 self.north()
544 )
545 }
546
547 #[must_use]
549 #[inline]
550 pub fn projwin_str(&self) -> String {
551 format!(
552 "{} {} {} {}",
553 self.west(),
554 self.north(),
555 self.east(),
556 self.south()
557 )
558 }
559}
560
561impl From<BBox> for WebBBox {
562 fn from(bbox: BBox) -> Self {
563 let (west_merc, south_merc) = xy(bbox.west(), bbox.south(), None);
564 let (east_merc, north_merc) = xy(bbox.east(), bbox.north(), None);
565 Self::new(west_merc, south_merc, east_merc, north_merc)
566 }
567}
568
569impl From<Tile> for WebBBox {
570 fn from(tile: Tile) -> Self {
571 let bbox = tile.geobbox();
572 Self::new(bbox.west, bbox.south, bbox.east, bbox.north)
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_merge_bboxes_non_crossing() {
582 let bboxes = vec![
583 BBox::new(-100.0, -10.0, -90.0, 10.0), BBox::new(-120.0, -5.0, -100.0, 5.0), ];
586
587 let expected = BBox::new(-120.0, -10.0, -90.0, 10.0);
588 let result = geobbox_merge(&bboxes);
589
590 assert_eq!(result, expected);
591 }
592
593 }