1#![deny(clippy::missing_const_for_fn)]
3use std::f64::consts::PI;
4use std::num::FpCategory;
5
6use crate::bbox::{BBox, WebBBox};
7use crate::constants::{DEG2RAD, EARTH_CIRCUMFERENCE, EARTH_RADIUS, LL_EPSILON};
8use crate::errors::UtilesCoreResult;
9use crate::point2d;
10use crate::sibling_relationship::SiblingRelationship;
11use crate::tile_zbox::{TileZBox, TileZBoxes};
12use crate::utile;
13use crate::zoom::ZoomOrZooms;
14use crate::Point2d;
15use crate::{LngLat, Tile, UtilesCoreError};
16
17#[must_use]
19pub fn ult(x: u32, y: u32, z: u8) -> (f64, f64) {
20 let z2 = f64::from(2_u32.pow(u32::from(z)));
21 let lon_deg = (f64::from(x) / z2) * 360.0 - 180.0;
22 let lat_rad = ((1.0 - 2.0 * f64::from(y) / z2) * PI).sinh().atan();
23 (lon_deg, lat_rad.to_degrees())
24}
25
26#[must_use]
28pub fn ul(x: u32, y: u32, z: u8) -> LngLat {
29 let (lon_deg, lat_deg) = ult(x, y, z);
30 LngLat::new(lon_deg, lat_deg)
31}
32
33#[must_use]
35pub fn ll(x: u32, y: u32, z: u8) -> LngLat {
36 ul(x, y + 1, z)
37}
38
39#[must_use]
41pub fn ur(x: u32, y: u32, z: u8) -> LngLat {
42 ul(x + 1, y, z)
43}
44
45#[must_use]
47pub fn lr(x: u32, y: u32, z: u8) -> LngLat {
48 ul(x + 1, y + 1, z)
49}
50
51#[must_use]
53pub fn minmax(zoom: u8) -> (u32, u32) {
54 (0, 2_u32.pow(u32::from(zoom)) - 1)
55}
56
57#[must_use]
59pub fn valid(x: u32, y: u32, z: u8) -> bool {
60 let (minx, maxx) = minmax(z);
61 let (miny, maxy) = minmax(z);
62 x >= minx && x <= maxx && y >= miny && y <= maxy
63}
64
65#[must_use]
67#[inline]
68pub fn flipy(y: u32, z: u8) -> u32 {
69 2_u32.pow(u32::from(z)) - 1 - y
70}
71
72#[must_use]
74#[inline]
75pub fn yflip(y: u32, z: u8) -> u32 {
76 flipy(y, z)
77}
78
79#[must_use]
84#[inline]
85pub const fn int_2_offset_zoom(i: u64) -> (u64, u8) {
86 if i == 0 {
87 return (0, 0);
88 }
89 let mut acc: u64 = 0;
90 let mut z: u8 = 0;
91 loop {
92 let num_tiles: u64 = (1 << z) * (1 << z);
93 if acc + num_tiles > i {
94 return (i - acc, z);
95 }
96 acc += num_tiles;
97 z += 1;
98 }
99}
100
101#[must_use]
153pub fn xyz2rmid(x: u32, y: u32, z: u8) -> u64 {
154 if z == 0 {
155 return u64::from(x) + u64::from(y) * 2u64.pow(u32::from(z));
156 }
157 let base_id: u64 = (4u64.pow(u32::from(z)) - 1) / 3;
158 base_id + u64::from(x) + u64::from(y) * 2u64.pow(u32::from(z))
159}
160
161#[must_use]
206#[allow(clippy::cast_possible_truncation)]
207pub fn rmid2xyz(i: u64) -> (u32, u32, u8) {
208 if i == 0 {
209 return (0, 0, 0);
210 }
211 let (i_o, z) = int_2_offset_zoom(i);
212 let pow_z = 2u64.pow(u32::from(z));
213 let x = i_o % pow_z;
214 let y = i_o / pow_z;
215 (x as u32, y as u32, z)
216}
217
218#[must_use]
220pub fn bbox2zoom(bbox: (u32, u32, u32, u32)) -> u8 {
221 let max_zoom = 28;
222 let (west, south, east, north) = bbox;
223 for z in 0..max_zoom {
224 let mask = 1 << (32 - (z + 1));
225 if (west & mask) != (east & mask) || (south & mask) != (north & mask) {
226 return z;
227 }
228 }
229 max_zoom
230}
231
232#[must_use]
234pub fn bounds(x: u32, y: u32, z: u8) -> (f64, f64, f64, f64) {
235 let ul_corner = ul(x, y, z);
236 let lr_corner = ul(x + 1, y + 1, z);
237 (
238 ul_corner.lng(),
239 lr_corner.lat(),
240 lr_corner.lng(),
241 ul_corner.lat(),
242 )
243}
244
245#[must_use]
247pub fn truncate_lng(lng: f64) -> f64 {
248 lng.clamp(-180.0, 180.0)
249}
250
251#[must_use]
253pub fn truncate_lat(lat: f64) -> f64 {
254 lat.clamp(-90.0, 90.0)
255}
256
257#[must_use]
259pub fn truncate_lnglat(lnglat: &LngLat) -> LngLat {
260 LngLat::new(truncate_lng(lnglat.lng()), truncate_lat(lnglat.lat()))
261}
262
263#[must_use]
265pub fn parent(x: u32, y: u32, z: u8, n: Option<u8>) -> Option<Tile> {
266 let n = n.unwrap_or(0);
267 if n == 0 {
268 if z == 0 {
269 None
270 } else {
271 Some(utile!(x >> 1, y >> 1, z - 1))
272 }
273 } else {
274 parent(x >> 1, y >> 1, z - 1, Some(n - 1))
275 }
276}
277#[must_use]
279pub fn children1_zorder(x: u32, y: u32, z: u8) -> [Tile; 4] {
280 [
281 utile!(x * 2, y * 2, z + 1), utile!(x * 2 + 1, y * 2, z + 1), utile!(x * 2, y * 2 + 1, z + 1), utile!(x * 2 + 1, y * 2 + 1, z + 1), ]
286}
287#[must_use]
328pub fn children_zorder(x: u32, y: u32, z: u8, zoom: Option<u8>) -> Vec<Tile> {
329 let zoom = zoom.unwrap_or(z + 1);
330 let tile = utile!(x, y, z);
331 let mut tiles = vec![tile];
332 while tiles[0].z < zoom {
333 let (xtile, ytile, ztile) = (tiles[0].x, tiles[0].y, tiles[0].z);
334 tiles.append(&mut vec![
335 utile!(xtile * 2, ytile * 2, ztile + 1), utile!(xtile * 2 + 1, ytile * 2, ztile + 1), utile!(xtile * 2, ytile * 2 + 1, ztile + 1), utile!(xtile * 2 + 1, ytile * 2 + 1, ztile + 1), ]);
340 tiles.remove(0);
341 }
342 tiles
343}
344
345#[must_use]
362pub fn children(x: u32, y: u32, z: u8, zoom: Option<u8>) -> Vec<Tile> {
363 let zoom = zoom.unwrap_or(z + 1);
364 let tile = utile!(x, y, z);
365 let mut tiles = vec![tile];
366 while tiles[0].z < zoom {
367 let (xtile, ytile, ztile) = (tiles[0].x, tiles[0].y, tiles[0].z);
368 tiles.append(&mut vec![
369 utile!(xtile * 2, ytile * 2, ztile + 1), utile!(xtile * 2 + 1, ytile * 2, ztile + 1), utile!(xtile * 2 + 1, ytile * 2 + 1, ztile + 1), utile!(xtile * 2, ytile * 2 + 1, ztile + 1), ]);
374 tiles.remove(0);
375 }
376 tiles
377}
378
379#[must_use]
383pub fn siblings(x: u32, y: u32, z: u8) -> Vec<Tile> {
384 let sibrel = SiblingRelationship::from((x, y));
385 match sibrel {
386 SiblingRelationship::UpperLeft => vec![
387 utile!(x + 1, y, z),
388 utile!(x, y + 1, z),
389 utile!(x + 1, y + 1, z),
390 ],
391 SiblingRelationship::UpperRight => vec![
392 utile!(x - 1, y, z),
393 utile!(x, y + 1, z),
394 utile!(x - 1, y + 1, z),
395 ],
396 SiblingRelationship::LowerLeft => vec![
397 utile!(x + 1, y, z),
398 utile!(x, y - 1, z),
399 utile!(x + 1, y - 1, z),
400 ],
401 SiblingRelationship::LowerRight => vec![
402 utile!(x - 1, y, z),
403 utile!(x, y - 1, z),
404 utile!(x - 1, y - 1, z),
405 ],
406 }
407}
408
409#[must_use]
411pub fn bbox_truncate(
412 west: f64,
413 south: f64,
414 east: f64,
415 north: f64,
416 truncate: Option<bool>,
417) -> (f64, f64, f64, f64) {
418 let trunc = truncate.unwrap_or(false);
419 let mut west = west;
420 let mut east = east;
421 let mut south = south;
422 let mut north = north;
423 if trunc {
424 if west < -180.0 {
425 west = -180.0;
426 }
427 if east > 180.0 {
428 east = 180.0;
429 }
430 if south < -90.0 {
431 south = -90.0;
432 }
433 if north > 90.0 {
434 north = 90.0;
435 }
436 }
437 (west, south, east, north)
438}
439
440pub fn _xy(lng: f64, lat: f64, truncate: Option<bool>) -> UtilesCoreResult<(f64, f64)> {
446 let (lng, lat) = if truncate.unwrap_or(false) {
447 (truncate_lng(lng), truncate_lat(lat))
448 } else {
449 (lng, lat)
450 };
451 let sinlat = lat.to_radians().sin();
452 let yish = (1.0 + sinlat) / (1.0 - sinlat);
453 match yish.classify() {
454 FpCategory::Infinite | FpCategory::Nan => {
455 Err(UtilesCoreError::LngLat2WebMercator(
456 "Y can not be computed: lat={lat}".to_string(),
457 ))
458 }
459 _ => {
460 let y = 0.5 - 0.25 * (yish.ln()) / PI;
461 let x = lng / 360.0 + 0.5;
462 Ok((x, y))
463 }
464 }
465}
466
467#[must_use]
469pub fn lnglat2webmercator(lng: f64, lat: f64) -> (f64, f64) {
470 let x = EARTH_RADIUS * lng.to_radians();
471 let y = if (lat - 90.0).abs() < f64::EPSILON {
472 f64::INFINITY
473 } else if (lat + 90.0).abs() < f64::EPSILON {
474 f64::NEG_INFINITY
475 } else {
476 EARTH_RADIUS * (PI * 0.25 + 0.5 * lat.to_radians()).tan().ln()
477 };
478 (x, y)
479}
480
481#[must_use]
492#[inline]
493pub fn webmercator2lnglat(x: f64, y: f64) -> (f64, f64) {
494 let lng = x / EARTH_RADIUS * 180.0 / PI;
495 let lat = (2.0 * (y / EARTH_RADIUS).exp().atan() - PI * 0.5) * 180.0 / PI;
496 (lng, lat)
497}
498
499#[must_use]
503pub fn xy(lng: f64, lat: f64, truncate: Option<bool>) -> (f64, f64) {
504 let (lng, lat) = if truncate.unwrap_or(false) {
505 (truncate_lng(lng), truncate_lat(lat))
506 } else {
507 (lng, lat)
508 };
509 lnglat2webmercator(lng, lat)
510}
511
512#[must_use]
514pub fn lnglat(x: f64, y: f64, truncate: Option<bool>) -> LngLat {
515 let (lng, lat) = webmercator2lnglat(x, y);
516 if truncate.is_some() {
517 truncate_lnglat(&LngLat::new(lng, lat))
518 } else {
519 LngLat::new(lng, lat)
520 }
521}
522
523enum TileEdgeInfo {
524 Bottom,
525 BottomLeft,
526 BottomRight,
527 Left,
528 Right,
529 Top,
530 TopLeft,
531 TopRight,
532 Middle,
533}
534
535fn tile_edge_info(x: u32, y: u32, z: u8) -> TileEdgeInfo {
536 if x == 0 && y == 0 {
537 return TileEdgeInfo::TopLeft;
538 }
539 let max_xy = 2u32.pow(u32::from(z));
540 if x == max_xy && y == max_xy {
541 return TileEdgeInfo::BottomRight;
542 }
543 match (x, y) {
544 (max, 0) if max == max_xy => TileEdgeInfo::TopRight,
545 (0, max) if max == max_xy => TileEdgeInfo::BottomLeft,
546 (0, _) => TileEdgeInfo::Left,
547 (max, _) if max == max_xy => TileEdgeInfo::Right,
548 (_, 0) => TileEdgeInfo::Top,
549 (_, max) if max == max_xy => TileEdgeInfo::Bottom,
550 _ => TileEdgeInfo::Middle,
551 }
552}
553
554fn neighbors_middle_tile(x: u32, y: u32, z: u8) -> Vec<Tile> {
555 vec![
556 utile!(x + 1, y, z),
557 utile!(x, y + 1, z),
558 utile!(x + 1, y + 1, z),
559 utile!(x - 1, y, z),
560 utile!(x, y - 1, z),
561 utile!(x - 1, y - 1, z),
562 utile!(x + 1, y - 1, z),
563 utile!(x - 1, y + 1, z),
564 ]
565}
566
567#[must_use]
569pub fn neighbors(x: u32, y: u32, z: u8) -> Vec<Tile> {
570 if z == 0 {
571 return Vec::new();
572 }
573 let edge_info = tile_edge_info(x, y, z);
574 match edge_info {
575 TileEdgeInfo::Middle => neighbors_middle_tile(x, y, z),
576 TileEdgeInfo::TopLeft => vec![
577 utile!(x + 1, y, z),
578 utile!(x, y + 1, z),
579 utile!(x + 1, y + 1, z),
580 ],
581 TileEdgeInfo::TopRight => vec![
582 utile!(x - 1, y, z),
583 utile!(x, y + 1, z),
584 utile!(x - 1, y + 1, z),
585 ],
586 TileEdgeInfo::BottomLeft => vec![
587 utile!(x + 1, y, z),
588 utile!(x, y - 1, z),
589 utile!(x + 1, y - 1, z),
590 ],
591 TileEdgeInfo::BottomRight => vec![
592 utile!(x - 1, y, z),
593 utile!(x, y - 1, z),
594 utile!(x - 1, y - 1, z),
595 ],
596 TileEdgeInfo::Left => vec![
597 utile!(x + 1, y, z),
598 utile!(x, y + 1, z),
599 utile!(x + 1, y + 1, z),
600 utile!(x, y - 1, z),
601 utile!(x + 1, y - 1, z),
602 ],
603 TileEdgeInfo::Right => vec![
604 utile!(x - 1, y, z),
605 utile!(x, y + 1, z),
606 utile!(x - 1, y + 1, z),
607 utile!(x, y - 1, z),
608 utile!(x - 1, y - 1, z),
609 ],
610 TileEdgeInfo::Top => vec![
611 utile!(x + 1, y, z),
612 utile!(x, y + 1, z),
613 utile!(x + 1, y + 1, z),
614 utile!(x - 1, y, z),
615 utile!(x - 1, y + 1, z),
616 ],
617 TileEdgeInfo::Bottom => vec![
618 utile!(x + 1, y, z),
619 utile!(x, y - 1, z),
620 utile!(x + 1, y - 1, z),
621 utile!(x - 1, y, z),
622 utile!(x - 1, y - 1, z),
623 ],
624 }
625}
626
627pub fn tile(
633 lng: f64,
634 lat: f64,
635 zoom: u8,
636 truncate: Option<bool>,
637) -> Result<Tile, UtilesCoreError> {
638 Tile::from_lnglat_zoom(lng, lat, zoom, truncate)
639}
640
641#[must_use]
652pub fn lnglat2tile_frac(lng: f64, lat: f64, z: u8) -> (f64, f64, u8) {
653 let sin = (lat * DEG2RAD).sin();
654 let z2 = 2f64.powi(i32::from(z));
655 let mut x = z2 * (lng / 360.0 + 0.5);
656 let y = z2 * (0.5 - (0.25 * ((1.0 + sin) / (1.0 - sin)).ln()) / PI);
657
658 x = x.rem_euclid(z2);
660
661 (x, y, z)
662}
663
664pub fn bounding_tile(
670 bbox: BBox,
671 truncate: Option<bool>,
672) -> Result<Tile, UtilesCoreError> {
673 let (west, south, east, north) =
674 bbox_truncate(bbox.west, bbox.south, bbox.east, bbox.north, truncate);
675 let tmin = tile(west, north, 32, truncate)?;
676 let tmax = tile(east - LL_EPSILON, south + LL_EPSILON, 32, truncate)?;
677
678 let cell = (tmin.x, tmin.y, tmax.x, tmax.y);
679 let z = bbox2zoom(cell);
680 if z == 0 {
681 Ok(utile!(0, 0, 0))
682 } else {
683 let x = cell.0 >> (32 - z);
684 let y = cell.1 >> (32 - z);
685 Ok(utile!(x, y, z))
686 }
687}
688
689#[must_use]
691pub fn xyz2bbox(x: u32, y: u32, z: u8) -> WebBBox {
692 let tile_size = EARTH_CIRCUMFERENCE / 2.0_f64.powi(i32::from(z));
693 let left = f64::from(x) * tile_size - EARTH_CIRCUMFERENCE / 2.0;
694 let top = EARTH_CIRCUMFERENCE / 2.0 - f64::from(y) * tile_size;
695
696 WebBBox::new(left, top - tile_size, left + tile_size, top)
697}
698
699#[must_use]
701pub fn as_zooms(zoom_or_zooms: ZoomOrZooms) -> Vec<u8> {
702 match zoom_or_zooms {
703 ZoomOrZooms::Zoom(zoom) => {
704 vec![zoom]
705 }
706 ZoomOrZooms::Zooms(zooms) => zooms,
707 }
708}
709
710fn tiles_range_zoom(
711 minx: u32,
712 maxx: u32,
713 miny: u32,
714 maxy: u32,
715 zoom: u8,
716) -> impl Iterator<Item = (u32, u32, u8)> {
717 (minx..=maxx).flat_map(move |i| (miny..=maxy).map(move |j| (i, j, zoom)))
718}
719
720pub fn tile_ranges(
726 bounds: (f64, f64, f64, f64),
727 zooms: ZoomOrZooms,
728) -> Result<TileZBoxes, UtilesCoreError> {
729 let zooms = as_zooms(zooms);
730 let bboxes: Vec<BBox> = BBox::from(bounds)
731 .bboxes()
732 .into_iter()
733 .map(|bbox| {
734 BBox {
736 north: bbox.north.min(85.051_129),
737 south: bbox.south.max(-85.051_129),
738 east: bbox.east.min(180.0),
739 west: bbox.west.max(-180.0),
740 }
741 })
742 .collect();
743 let ranges: Vec<TileZBox> = bboxes
744 .into_iter()
745 .flat_map(move |bbox| {
746 let zooms = zooms.clone();
747 zooms.into_iter().map(move |zoom| {
748 let upper_left_lnglat = LngLat {
749 xy: point2d! { x: bbox.west, y: bbox.north },
750 };
751 let lower_right_lnglat = LngLat {
752 xy: point2d! { x: bbox.east, y: bbox.south },
753 };
754 let top_left_tile = Tile::from_lnglat_zoom(
755 upper_left_lnglat.lng(),
756 upper_left_lnglat.lat(),
757 zoom,
758 Some(false),
759 )?;
760 let bottom_right_tile = Tile::from_lnglat_zoom(
761 lower_right_lnglat.lng() - LL_EPSILON,
762 lower_right_lnglat.lat() + LL_EPSILON,
763 zoom,
764 Some(false),
765 )?;
766 Ok(TileZBox::new(
767 top_left_tile.x,
768 bottom_right_tile.x,
769 top_left_tile.y,
770 bottom_right_tile.y,
771 zoom,
772 ))
773 })
774 })
775 .collect::<Result<Vec<TileZBox>, UtilesCoreError>>()?;
776 Ok(TileZBoxes::from(ranges))
777}
778
779pub fn tiles_count(
785 bounds: (f64, f64, f64, f64),
786 zooms: ZoomOrZooms,
787) -> Result<u64, UtilesCoreError> {
788 let ranges = tile_ranges(bounds, zooms)?;
789 Ok(ranges.length())
790}
791
792pub fn tiles(
794 bounds: (f64, f64, f64, f64),
795 zooms: ZoomOrZooms,
796) -> impl Iterator<Item = Tile> {
797 let zooms = as_zooms(zooms);
798 let bboxthing = BBox {
799 north: bounds.3,
800 south: bounds.1,
801 east: bounds.2,
802 west: bounds.0,
803 };
804 let bboxes: Vec<BBox> = bboxthing
805 .bboxes()
806 .into_iter()
807 .map(|bbox| {
808 BBox {
810 north: bbox.north.min(85.051_129),
811 south: bbox.south.max(-85.051_129),
812 east: bbox.east.min(180.0),
813 west: bbox.west.max(-180.0),
814 }
815 })
816 .collect();
817 bboxes.into_iter().flat_map(move |bbox| {
818 let zooms = zooms.clone();
819 zooms.into_iter().flat_map(move |zoom| {
820 let upper_left_lnglat = LngLat {
821 xy: point2d! { x: bbox.west, y: bbox.north },
822 };
823 let lower_right_lnglat = LngLat {
824 xy: point2d! { x: bbox.east, y: bbox.south },
825 };
826 let top_left_tile = Tile::from_lnglat_zoom(
827 upper_left_lnglat.lng(),
828 upper_left_lnglat.lat(),
829 zoom,
830 Some(false),
831 );
832 let bottom_right_tile = Tile::from_lnglat_zoom(
833 lower_right_lnglat.lng() - LL_EPSILON,
834 lower_right_lnglat.lat() + LL_EPSILON,
835 zoom,
836 Some(false),
837 );
838
839 match (top_left_tile, bottom_right_tile) {
840 (Ok(top_left), Ok(bottom_right)) => tiles_range_zoom(
841 top_left.x,
842 bottom_right.x,
843 top_left.y,
844 bottom_right.y,
845 zoom,
846 )
847 .map(move |(x, y, z)| Tile { x, y, z })
848 .collect::<Vec<_>>()
849 .into_iter(),
850 _ => Vec::new().into_iter(),
851 }
852 })
853 })
854}
855
856#[must_use]
858pub fn to_id(x: u32, y: u32, z: u8) -> u64 {
859 let dim = 2u64 * (1u64 << z);
860 ((dim * u64::from(y) + u64::from(x)) * 32u64) + u64::from(z)
861}
862
863#[allow(clippy::cast_possible_truncation)]
869#[must_use]
870pub fn from_id(id: u64) -> Tile {
871 let z = (id % 32) as u8;
872 let dim = 2u64 * (1u64 << z);
873 let xy = (id - u64::from(z)) / 32u64;
874 let x = u32::try_from(xy % dim).expect("should never happen");
875 let y = ((xy - u64::from(x)) / dim) as u32;
876 utile!(x, y, z)
877}