map_tile_toolkit/tile.rs
1//! Tile addressing on the integer grid.
2//!
3//! A tile of side `extent` covers the closed integer square `[x·extent, x·extent + extent − 1]` on
4//! each axis, so the boundary between tiles `k−1` and `k` sits at `k·extent − 0.5` (between two
5//! integer coordinates). Integer vertices therefore never land exactly on a tile edge — every vertex
6//! belongs to exactly one tile.
7
8use geo_types::Coord;
9
10/// A tile address on the integer grid. Coordinates may be negative.
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
12pub struct TileId {
13 pub x: i32,
14 pub y: i32,
15}
16
17impl TileId {
18 #[must_use]
19 pub fn new(x: i32, y: i32) -> Self {
20 Self { x, y }
21 }
22
23 /// This tile's origin in global coordinates — `self · extent`, the global position its local
24 /// `[0, 0]` corner maps to. **Add** it to a tile-local coordinate to lift it into the global
25 /// frame, or **subtract** it to drop a global coordinate into this tile's local frame.
26 ///
27 /// Returns `None` if `self · extent` overflows `i32` (a tile far outside the representable range
28 /// for this `extent`).
29 ///
30 /// ```
31 /// # use map_tile_toolkit::TileId;
32 /// # use geo_types::Coord;
33 /// let origin = TileId::new(2, 3).origin(25).expect("in range");
34 /// assert_eq!(origin, Coord { x: 50, y: 75 });
35 /// assert_eq!(Coord { x: 4, y: 1 } + origin, Coord { x: 54, y: 76 }); // local → global
36 /// assert_eq!(Coord { x: 54, y: 76 } - origin, Coord { x: 4, y: 1 }); // global → local
37 /// ```
38 #[must_use]
39 pub fn origin(self, extent: u32) -> Option<Coord<i32>> {
40 let e = i64::from(extent);
41 Some(Coord {
42 x: i32::try_from(i64::from(self.x) * e).ok()?,
43 y: i32::try_from(i64::from(self.y) * e).ok()?,
44 })
45 }
46}
47
48impl From<(i32, i32)> for TileId {
49 fn from((x, y): (i32, i32)) -> Self {
50 Self { x, y }
51 }
52}
53
54/// The tile that owns coordinate `c` for the given tile side `extent`.
55pub(crate) fn tile_of(c: Coord<i32>, extent: i32) -> TileId {
56 TileId::new(c.x.div_euclid(extent), c.y.div_euclid(extent))
57}