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