1use std::{
4 f64::consts::PI,
5 fmt,
6 fmt::{Display, Formatter},
7};
8
9use bytemuck_derive::{Pod, Zeroable};
10use cgmath::{AbsDiffEq, Matrix4, Point3, Vector3, Vector4};
11use serde::{Deserialize, Serialize};
12
13use crate::{
14 style::source::TileAddressingScheme,
15 util::{
16 math::{div_floor, Aabb2},
17 SignificantlyDifferent,
18 },
19};
20
21pub const EXTENT_UINT: u32 = 4096;
22pub const EXTENT_SINT: i32 = EXTENT_UINT as i32;
23pub const EXTENT: f64 = EXTENT_UINT as f64;
24const TOP_LEFT_EXTENT: Vector4<f64> = Vector4::new(0.0, 0.0, 0.0, 1.0);
25const BOTTOM_RIGHT_EXTENT: Vector4<f64> = Vector4::new(EXTENT, EXTENT, 0.0, 1.0);
26
27pub const TILE_SIZE: f64 = 512.0;
28pub const MAX_ZOOM: usize = 32;
29
30pub const ZOOM_BOUNDS: [u32; MAX_ZOOM] = create_zoom_bounds::<MAX_ZOOM>();
33
34const fn create_zoom_bounds<const DIM: usize>() -> [u32; DIM] {
35 let mut result: [u32; DIM] = [0; DIM];
36 let mut i = 0;
37 while i < DIM {
38 result[i] = 2u32.pow(i as u32);
39 i += 1;
40 }
41 result
42}
43
44#[derive(Ord, PartialOrd, Eq, PartialEq, Clone, Copy)]
50pub struct Quadkey([ZoomLevel; MAX_ZOOM]);
51
52impl Quadkey {
53 pub fn new(quad_encoded: &[ZoomLevel]) -> Self {
54 let mut key = [ZoomLevel::default(); MAX_ZOOM];
55 key[0] = (quad_encoded.len() as u8).into();
56 for (i, part) in quad_encoded.iter().enumerate() {
57 key[i + 1] = *part;
58 }
59 Self(key)
60 }
61}
62
63impl fmt::Debug for Quadkey {
64 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
65 let key = self.0;
66 let ZoomLevel(level) = key[0];
67 let len = level as usize;
68 for part in &self.0[0..len] {
69 write!(f, "{part:?}")?;
70 }
71 Ok(())
72 }
73}
74
75#[derive(
77 Ord,
78 PartialOrd,
79 Eq,
80 PartialEq,
81 Hash,
82 Copy,
83 Clone,
84 Debug,
85 Default,
86 Serialize,
87 Deserialize,
88 Pod,
89 Zeroable,
90)]
91#[repr(C)]
92pub struct ZoomLevel(u8);
93
94impl ZoomLevel {
95 pub const fn new(z: u8) -> Self {
96 ZoomLevel(z)
97 }
98 pub fn is_root(self) -> bool {
99 self.0 == 0
100 }
101}
102
103impl std::ops::Add<u8> for ZoomLevel {
104 type Output = ZoomLevel;
105
106 fn add(self, rhs: u8) -> Self::Output {
107 let zoom_level = self.0.checked_add(rhs).expect("zoom level overflowed");
108 ZoomLevel(zoom_level)
109 }
110}
111
112impl std::ops::Sub<u8> for ZoomLevel {
113 type Output = ZoomLevel;
114
115 fn sub(self, rhs: u8) -> Self::Output {
116 let zoom_level = self.0.checked_sub(rhs).expect("zoom level underflowed");
117 ZoomLevel(zoom_level)
118 }
119}
120
121impl Display for ZoomLevel {
122 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
123 write!(f, "{}", self.0)
124 }
125}
126
127impl From<u8> for ZoomLevel {
128 fn from(zoom_level: u8) -> Self {
129 ZoomLevel(zoom_level)
130 }
131}
132
133impl From<ZoomLevel> for u8 {
134 fn from(val: ZoomLevel) -> Self {
135 val.0
136 }
137}
138
139#[derive(Copy, Clone, Debug)]
140pub struct LatLon {
141 pub latitude: f64,
142 pub longitude: f64,
143}
144
145impl LatLon {
146 pub fn new(latitude: f64, longitude: f64) -> Self {
147 LatLon {
148 latitude,
149 longitude,
150 }
151 }
152
153 const EARTH_RADIUS: f64 = 6371008.8;
157
158 const EARTH_CIRCUMFRENCE: f64 = 2.0 * PI * Self::EARTH_RADIUS; fn circumference_at_latitude(&self) -> f64 {
164 Self::EARTH_CIRCUMFRENCE * (self.latitude * PI / 180.0).cos()
165 }
166
167 fn mercator_x_from_lng(&self) -> f64 {
168 (180.0 + self.longitude) / 360.0
169 }
170
171 fn mercator_y_from_lat(&self) -> f64 {
172 (180.0 - (180.0 / PI * ((PI / 4.0 + self.latitude * PI / 360.0).tan()).ln())) / 360.0
173 }
174
175 fn mercator_z_from_altitude(&self, altitude: f64) -> f64 {
176 altitude / self.circumference_at_latitude()
177 }
178}
179
180impl Default for LatLon {
181 fn default() -> Self {
182 LatLon {
183 latitude: 0.0,
184 longitude: 0.0,
185 }
186 }
187}
188
189impl Display for LatLon {
190 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
191 write!(f, "{},{}", self.latitude, self.longitude)
192 }
193}
194
195#[derive(Copy, Clone, Debug)]
198pub struct Zoom(f64);
199
200impl Zoom {
201 pub fn new(zoom: f64) -> Self {
202 Zoom(zoom)
203 }
204
205 pub fn level(&self) -> f32 {
206 self.0 as f32
207 }
208}
209
210impl Zoom {
211 pub fn from(zoom_level: ZoomLevel) -> Self {
212 Zoom(zoom_level.0 as f64)
213 }
214}
215
216impl Default for Zoom {
217 fn default() -> Self {
218 Zoom(0.0)
219 }
220}
221
222impl Display for Zoom {
223 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
224 write!(f, "{}", (self.0 * 100.0).round() / 100.0)
225 }
226}
227
228impl std::ops::Add for Zoom {
229 type Output = Zoom;
230
231 fn add(self, rhs: Self) -> Self::Output {
232 Zoom(self.0 + rhs.0)
233 }
234}
235
236impl std::ops::Sub for Zoom {
237 type Output = Zoom;
238
239 fn sub(self, rhs: Self) -> Self::Output {
240 Zoom(self.0 - rhs.0)
241 }
242}
243
244impl Zoom {
245 pub fn scale_to_tile(&self, coords: &WorldTileCoords) -> f64 {
246 2.0_f64.powf(coords.z.0 as f64 - self.0)
247 }
248
249 pub fn scale_to_zoom_level(&self, z: ZoomLevel) -> f64 {
250 2.0_f64.powf(z.0 as f64 - self.0)
251 }
252
253 pub fn scale_delta(&self, zoom: &Zoom) -> f64 {
254 2.0_f64.powf(zoom.0 - self.0)
255 }
256
257 pub fn zoom_level(&self, tile_size: f64) -> ZoomLevel {
267 let z = (self.0 + (TILE_SIZE / tile_size).ln() / 2.0_f64.ln()).floor() as u8;
269 return ZoomLevel(z.max(0));
270 }
271}
272
273impl SignificantlyDifferent for Zoom {
274 type Epsilon = f64;
275
276 fn ne(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
277 self.0.abs_diff_eq(&other.0, epsilon)
278 }
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Default)]
288pub struct InnerCoords {
289 pub x: f64,
290 pub y: f64,
291}
292
293#[derive(Clone, Copy, Debug, Hash, Eq, PartialEq, Default)]
300pub struct TileCoords {
301 pub x: u32,
302 pub y: u32,
303 pub z: ZoomLevel,
304}
305
306impl TileCoords {
307 pub fn into_world_tile(self, scheme: TileAddressingScheme) -> Option<WorldTileCoords> {
315 let bounds = ZOOM_BOUNDS[self.z.0 as usize] as i32;
318 let x = self.x as i32;
319 let y = self.y as i32;
320
321 if x >= bounds || y >= bounds {
322 return None;
323 }
324
325 Some(match scheme {
326 TileAddressingScheme::XYZ => WorldTileCoords { x, y, z: self.z },
327 TileAddressingScheme::TMS => WorldTileCoords {
328 x,
329 y: bounds - 1 - y,
330 z: self.z,
331 },
332 })
333 }
334}
335
336impl From<(u32, u32, ZoomLevel)> for TileCoords {
337 fn from(tuple: (u32, u32, ZoomLevel)) -> Self {
338 TileCoords {
339 x: tuple.0,
340 y: tuple.1,
341 z: tuple.2,
342 }
343 }
344}
345
346#[derive(
355 Clone,
356 Copy,
357 Debug,
358 PartialEq,
359 Eq,
360 PartialOrd,
361 Ord,
362 Hash,
363 Default,
364 Serialize,
365 Deserialize,
366 Zeroable,
367)]
368#[repr(C)]
369pub struct WorldTileCoords {
370 pub x: i32,
371 pub y: i32,
372 pub z: ZoomLevel,
373}
374
375impl WorldTileCoords {
376 pub fn into_tile(self, scheme: TileAddressingScheme) -> Option<TileCoords> {
384 let bounds = ZOOM_BOUNDS[self.z.0 as usize];
386 let x = self.x as u32;
387 let y = self.y as u32;
388
389 if x >= bounds || y >= bounds {
390 return None;
391 }
392
393 Some(match scheme {
394 TileAddressingScheme::XYZ => TileCoords { x, y, z: self.z },
395 TileAddressingScheme::TMS => TileCoords {
396 x,
397 y: bounds - 1 - y,
398 z: self.z,
399 },
400 })
401 }
402
403 #[tracing::instrument(skip_all)]
406 pub fn transform_for_zoom(&self, zoom: Zoom) -> Matrix4<f64> {
407 let tile_scale = TILE_SIZE * Zoom::from(self.z).scale_delta(&zoom);
416
417 let translate = Matrix4::from_translation(Vector3::new(
418 self.x as f64 * tile_scale,
419 self.y as f64 * tile_scale,
420 0.0,
421 ));
422
423 let normalize_and_scale =
426 Matrix4::from_nonuniform_scale(tile_scale / EXTENT, tile_scale / EXTENT, 1.0);
427 translate * normalize_and_scale
428 }
429
430 pub fn into_aligned(self) -> AlignedWorldTileCoords {
431 AlignedWorldTileCoords(WorldTileCoords {
432 x: div_floor(self.x, 2) * 2,
433 y: div_floor(self.y, 2) * 2,
434 z: self.z,
435 })
436 }
437
438 pub fn build_quad_key(&self) -> Option<Quadkey> {
440 let bounds = ZOOM_BOUNDS[self.z.0 as usize];
441 let x = self.x as u32;
442 let y = self.y as u32;
443
444 if x >= bounds || y >= bounds {
445 return None;
446 }
447
448 let mut key = [ZoomLevel::default(); MAX_ZOOM];
449
450 key[0] = self.z;
451
452 for z in 1..self.z.0 + 1 {
453 let mut b = 0;
454 let mask: i32 = 1 << (z - 1);
455 if (self.x & mask) != 0 {
456 b += 1u8;
457 }
458 if (self.y & mask) != 0 {
459 b += 2u8;
460 }
461 key[z as usize] = ZoomLevel::from(b);
462 }
463 Some(Quadkey(key))
464 }
465
466 pub fn get_children(&self) -> [WorldTileCoords; 4] {
468 [
469 WorldTileCoords {
470 x: self.x * 2,
471 y: self.y * 2,
472 z: self.z + 1,
473 },
474 WorldTileCoords {
475 x: self.x * 2 + 1,
476 y: self.y * 2,
477 z: self.z + 1,
478 },
479 WorldTileCoords {
480 x: self.x * 2 + 1,
481 y: self.y * 2 + 1,
482 z: self.z + 1,
483 },
484 WorldTileCoords {
485 x: self.x * 2,
486 y: self.y * 2 + 1,
487 z: self.z + 1,
488 },
489 ]
490 }
491
492 pub fn get_parent(&self) -> Option<WorldTileCoords> {
494 if self.z.is_root() {
495 return None;
496 }
497
498 Some(WorldTileCoords {
499 x: self.x >> 1,
500 y: self.y >> 1,
501 z: self.z - 1,
502 })
503 }
504
505 pub fn stencil_reference_value_3d(&self) -> u8 {
509 const CASES: u8 = 4;
510 let z = u8::from(self.z);
511 match (self.x % 2 == 0, self.y % 2 == 0) {
512 (true, true) => z * CASES,
513 (true, false) => 1 + z * CASES,
514 (false, true) => 2 + z * CASES,
515 (false, false) => 3 + z * CASES,
516 }
517 }
518}
519
520impl From<(i32, i32, ZoomLevel)> for WorldTileCoords {
521 fn from(tuple: (i32, i32, ZoomLevel)) -> Self {
522 WorldTileCoords {
523 x: tuple.0,
524 y: tuple.1,
525 z: tuple.2,
526 }
527 }
528}
529
530pub struct AlignedWorldTileCoords(pub WorldTileCoords);
539
540impl AlignedWorldTileCoords {
541 pub fn upper_left(self) -> WorldTileCoords {
542 self.0
543 }
544
545 pub fn upper_right(&self) -> WorldTileCoords {
546 WorldTileCoords {
547 x: self.0.x + 1,
548 y: self.0.y,
549 z: self.0.z,
550 }
551 }
552
553 pub fn lower_left(&self) -> WorldTileCoords {
554 WorldTileCoords {
555 x: self.0.x,
556 y: self.0.y - 1,
557 z: self.0.z,
558 }
559 }
560
561 pub fn lower_right(&self) -> WorldTileCoords {
562 WorldTileCoords {
563 x: self.0.x + 1,
564 y: self.0.y + 1,
565 z: self.0.z,
566 }
567 }
568}
569
570#[derive(Clone, Copy, Debug, PartialEq, Default)]
578pub struct WorldCoords {
579 pub x: f64,
580 pub y: f64,
581}
582
583impl WorldCoords {
584 pub fn from_lat_lon(lat_lon: LatLon, zoom: Zoom) -> WorldCoords {
585 let tile_size = TILE_SIZE * 2.0_f64.powf(zoom.0);
586 let x = (lat_lon.longitude + 180.0) * (tile_size / 360.0);
588
589 let lat_rad = (lat_lon.latitude * PI) / 180.0;
591
592 let merc_n = f64::ln(f64::tan((PI / 4.0) + (lat_rad / 2.0)));
594 let y = (tile_size / 2.0) - (tile_size * merc_n / (2.0 * PI));
595
596 WorldCoords { x, y }
597 }
598
599 pub fn at_ground(x: f64, y: f64) -> Self {
600 Self { x, y }
601 }
602
603 pub fn into_world_tile(self, z: ZoomLevel, zoom: Zoom) -> WorldTileCoords {
604 let tile_scale = zoom.scale_to_zoom_level(z) / TILE_SIZE; let x = self.x * tile_scale;
606 let y = self.y * tile_scale;
607
608 WorldTileCoords {
609 x: x as i32,
610 y: y as i32,
611 z,
612 }
613 }
614}
615
616impl From<(f32, f32)> for WorldCoords {
617 fn from(tuple: (f32, f32)) -> Self {
618 WorldCoords {
619 x: tuple.0 as f64,
620 y: tuple.1 as f64,
621 }
622 }
623}
624
625impl From<(f64, f64)> for WorldCoords {
626 fn from(tuple: (f64, f64)) -> Self {
627 WorldCoords {
628 x: tuple.0,
629 y: tuple.1,
630 }
631 }
632}
633
634impl From<Point3<f64>> for WorldCoords {
635 fn from(point: Point3<f64>) -> Self {
636 WorldCoords {
637 x: point.x,
638 y: point.y,
639 }
640 }
641}
642
643#[derive(Debug)]
645pub struct ViewRegion {
646 min_tile: WorldTileCoords,
647 max_tile: WorldTileCoords,
648 zoom_level: ZoomLevel,
650 padding: i32,
652 max_n_tiles: usize,
654}
655
656impl ViewRegion {
657 pub fn new(
658 view_region: Aabb2<f64>,
659 padding: i32,
660 max_n_tiles: usize,
661 zoom: Zoom,
662 z: ZoomLevel,
663 ) -> Self {
664 let min_world: WorldCoords = WorldCoords::at_ground(view_region.min.x, view_region.min.y);
665 let min_world_tile: WorldTileCoords = min_world.into_world_tile(z, zoom);
666 let max_world: WorldCoords = WorldCoords::at_ground(view_region.max.x, view_region.max.y);
667 let max_world_tile: WorldTileCoords = max_world.into_world_tile(z, zoom);
668
669 Self {
670 min_tile: min_world_tile,
671 max_tile: max_world_tile,
672 zoom_level: z,
673 max_n_tiles,
674 padding,
675 }
676 }
677
678 pub fn zoom_level(&self) -> ZoomLevel {
679 self.zoom_level
680 }
681
682 pub fn is_in_view(&self, &world_coords: &WorldTileCoords) -> bool {
683 world_coords.x <= self.max_tile.x + self.padding
684 && world_coords.y <= self.max_tile.y + self.padding
685 && world_coords.x >= self.min_tile.x - self.padding
686 && world_coords.y >= self.min_tile.y - self.padding
687 && world_coords.z == self.zoom_level
688 }
689
690 pub fn iter(&self) -> impl Iterator<Item = WorldTileCoords> + '_ {
691 (self.min_tile.x - self.padding..self.max_tile.x + 1 + self.padding)
692 .flat_map(move |x| {
693 (self.min_tile.y - self.padding..self.max_tile.y + 1 + self.padding).map(move |y| {
694 let tile_coord: WorldTileCoords = (x, y, self.zoom_level).into();
695 tile_coord
696 })
697 })
698 .take(self.max_n_tiles)
699 }
700}
701
702impl Display for TileCoords {
703 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
704 write!(
705 f,
706 "T(x={x},y={y},z={z})",
707 x = self.x,
708 y = self.y,
709 z = self.z
710 )
711 }
712}
713
714impl Display for WorldTileCoords {
715 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
716 write!(
717 f,
718 "WT(x={x},y={y},z={z})",
719 x = self.x,
720 y = self.y,
721 z = self.z
722 )
723 }
724}
725impl Display for WorldCoords {
726 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
727 write!(f, "W(x={x},y={y})", x = self.x, y = self.y,)
728 }
729}
730
731#[cfg(test)]
732mod tests {
733 use cgmath::Point2;
734
735 use crate::{
736 coords::{
737 Quadkey, TileCoords, ViewRegion, WorldCoords, WorldTileCoords, Zoom, ZoomLevel,
738 BOTTOM_RIGHT_EXTENT, TOP_LEFT_EXTENT,
739 },
740 render::tile_view_pattern::DEFAULT_TILE_SIZE,
741 style::source::TileAddressingScheme,
742 util::math::Aabb2,
743 };
744
745 fn to_from_world(tile: (i32, i32, ZoomLevel), zoom: Zoom) {
746 let tile = WorldTileCoords::from(tile);
747 let p1 = tile.transform_for_zoom(zoom) * TOP_LEFT_EXTENT;
748 let p2 = tile.transform_for_zoom(zoom) * BOTTOM_RIGHT_EXTENT;
749 println!("{p1:?}\n{p2:?}");
750
751 assert_eq!(
752 WorldCoords::from((p1.x, p1.y))
753 .into_world_tile(zoom.zoom_level(DEFAULT_TILE_SIZE), zoom),
754 tile
755 );
756 }
757
758 #[test]
759 fn world_coords_tests() {
760 to_from_world((1, 0, ZoomLevel::from(1)), Zoom::new(1.0));
761 to_from_world((67, 42, ZoomLevel::from(7)), Zoom::new(7.0));
762 to_from_world((17421, 11360, ZoomLevel::from(15)), Zoom::new(15.0));
763 }
764
765 #[test]
766 fn test_quad_key() {
767 assert_eq!(
768 TileCoords {
769 x: 0,
770 y: 0,
771 z: ZoomLevel::from(1)
772 }
773 .into_world_tile(TileAddressingScheme::TMS)
774 .unwrap()
775 .build_quad_key(),
776 Some(Quadkey::new(&[ZoomLevel::from(2)]))
777 );
778 assert_eq!(
779 TileCoords {
780 x: 0,
781 y: 1,
782 z: ZoomLevel::from(1)
783 }
784 .into_world_tile(TileAddressingScheme::TMS)
785 .unwrap()
786 .build_quad_key(),
787 Some(Quadkey::new(&[ZoomLevel::from(0)]))
788 );
789 assert_eq!(
790 TileCoords {
791 x: 1,
792 y: 1,
793 z: ZoomLevel::from(1)
794 }
795 .into_world_tile(TileAddressingScheme::TMS)
796 .unwrap()
797 .build_quad_key(),
798 Some(Quadkey::new(&[ZoomLevel::from(1)]))
799 );
800 assert_eq!(
801 TileCoords {
802 x: 1,
803 y: 0,
804 z: ZoomLevel::from(1)
805 }
806 .into_world_tile(TileAddressingScheme::TMS)
807 .unwrap()
808 .build_quad_key(),
809 Some(Quadkey::new(&[ZoomLevel::from(3)]))
810 );
811 }
812
813 #[test]
814 fn test_view_region() {
815 for tile_coords in ViewRegion::new(
816 Aabb2::new(Point2::new(0.0, 0.0), Point2::new(2000.0, 2000.0)),
817 1,
818 32,
819 Zoom::default(),
820 ZoomLevel::default(),
821 )
822 .iter()
823 {
824 println!("{tile_coords}");
825 }
826 }
827}