Skip to main content

utiles_core/
tile_zbox.rs

1//! `TileZBox` - zoom-x-y bounding box
2
3use fns::flipy;
4
5use crate::{Point2d, Tile, TileLike, UtilesCoreError, UtilesCoreResult, fns};
6
7/// A struct representing a bbox of tiles at a specific zoom level
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub struct TileZBox {
10    pub zoom: u8,
11    pub min: Point2d<u32>,
12    pub max: Point2d<u32>,
13}
14
15/// A struct representing a set of `TileZBox`es
16#[derive(Debug)]
17pub struct TileZBoxes {
18    pub ranges: Vec<TileZBox>,
19}
20
21/// An iterator over a `TileZBox` that yields tiles
22#[derive(Debug)]
23pub struct TileZBoxIterator {
24    range: TileZBox,
25    cur_x: u32,
26    cur_y: u32,
27}
28
29impl TileZBox {
30    /// Create a new `TileZBox`
31    #[must_use]
32    pub const fn new(min_x: u32, max_x: u32, min_y: u32, max_y: u32, zoom: u8) -> Self {
33        Self {
34            zoom,
35            min: Point2d::new(min_x, min_y),
36            max: Point2d::new(max_x, max_y),
37        }
38    }
39
40    /// Return the minimum x value
41    #[must_use]
42    pub const fn minx(&self) -> u32 {
43        self.min.x
44    }
45
46    /// Return the maximum x value
47    #[must_use]
48    pub const fn maxx(&self) -> u32 {
49        self.max.x
50    }
51
52    /// Return the minimum y value
53    #[must_use]
54    pub const fn miny(&self) -> u32 {
55        self.min.y
56    }
57
58    /// Return the maximum y value
59    #[must_use]
60    pub const fn maxy(&self) -> u32 {
61        self.max.y
62    }
63
64    #[must_use]
65    pub const fn dx(&self) -> u32 {
66        self.max.x - self.min.x
67    }
68
69    #[must_use]
70    pub const fn width(&self) -> u32 {
71        self.dx()
72    }
73
74    #[must_use]
75    pub const fn dy(&self) -> u32 {
76        self.max.y - self.min.y
77    }
78
79    #[must_use]
80    pub const fn height(&self) -> u32 {
81        self.dy()
82    }
83
84    #[must_use]
85    pub const fn dxdy(&self) -> (u32, u32) {
86        (self.max.x - self.min.x, self.max.y - self.min.y)
87    }
88
89    /// Return the zoom level
90    #[must_use]
91    pub const fn z(&self) -> u8 {
92        self.zoom
93    }
94
95    /// Return the zoom level
96    #[must_use]
97    pub const fn zoom(&self) -> u8 {
98        self.zoom
99    }
100
101    /// Return dimensions of the `TileZBox`
102    #[must_use]
103    pub const fn dimensions(&self) -> (u32, u32) {
104        (self.max.x - self.min.x + 1, self.max.y - self.min.y + 1)
105    }
106
107    /// Return the number of tiles contained by the `TileZBox`
108    #[must_use]
109    pub fn length(&self) -> u64 {
110        u64::from((self.max.x - self.min.x + 1) * (self.max.y - self.min.y + 1))
111    }
112
113    /// Return the size of the `TileZBox` in tiles
114    #[must_use]
115    pub fn size(&self) -> u64 {
116        self.length()
117    }
118
119    /// Return a new `TileZBox` with the y values flipped for the given zoom level
120    #[must_use]
121    pub fn flipy(&self) -> Self {
122        Self {
123            min: Point2d::new(self.min.x, flipy(self.max.y, self.zoom)),
124            max: Point2d::new(self.max.x, flipy(self.min.y, self.zoom)),
125            zoom: self.zoom,
126        }
127    }
128
129    /// Return whether the `TileZBox` contains the given tile-like input
130    #[must_use]
131    pub fn contains_tile<T: TileLike>(&self, tile: &T) -> bool {
132        tile.z() == self.zoom
133            && tile.x() >= self.min.x
134            && tile.x() <= self.max.x
135            && tile.y() >= self.min.y
136            && tile.y() <= self.max.y
137    }
138
139    /// Return the SQL `WHERE` clause for tms mbtiles like db with optional prefix for column names
140    #[must_use]
141    pub fn mbtiles_sql_where_prefix(&self, prefix: Option<&str>) -> String {
142        let col_prefix = prefix.unwrap_or_default();
143        // classic mbtiles sqlite query:
144        // 'SELECT tile_data FROM tiles WHERE zoom_level = ? AND tile_column = ? AND tile_row = ?',
145        let miny = flipy(self.min.y, self.zoom);
146        let maxy = flipy(self.max.y, self.zoom);
147        format!(
148            "(zoom_level = {} AND {}tile_column >= {} AND {}tile_column <= {} AND {}tile_row >= {} AND {}tile_row <= {})",
149            self.zoom,
150            col_prefix,
151            self.min.x,
152            col_prefix,
153            self.max.x,
154            col_prefix,
155            maxy,
156            col_prefix,
157            miny
158        )
159    }
160
161    /// Return the SQL `WHERE` clause for an mbtiles database
162    #[must_use]
163    pub fn mbtiles_sql_where(&self) -> String {
164        self.mbtiles_sql_where_prefix(None)
165    }
166
167    /// Create zbox from tile
168    #[must_use]
169    pub fn from_tile<T: TileLike + ?Sized>(tile: &T) -> Self {
170        Self {
171            zoom: tile.z(),
172            min: Point2d::new(tile.x(), tile.y()),
173            max: Point2d::new(tile.x(), tile.y()),
174        }
175    }
176
177    /// Returns zbox struct from ref to array of tiles
178    ///
179    /// # Errors
180    ///
181    /// Error when there are no tiles in the array or zoom level(s) of tiles
182    /// do not match
183    pub fn from_tiles(tiles: &[Tile]) -> UtilesCoreResult<Self> {
184        if tiles.is_empty() {
185            return Err(UtilesCoreError::AdHoc("No tiles provided".to_string()));
186        }
187
188        let expected_zoom = tiles[0].z;
189        let mut xmin = u32::MAX;
190        let mut xmax = u32::MIN;
191        let mut ymin = u32::MAX;
192        let mut ymax = u32::MIN;
193
194        for tile in tiles {
195            // Check if all tiles have the same zoom level
196            if tile.z != expected_zoom {
197                return Err(UtilesCoreError::AdHoc(
198                    "Not all tiles have the same zoom level".to_string(),
199                ));
200            }
201
202            let x = tile.x();
203            let y = tile.y();
204
205            // Update min and max values for x
206            if x < xmin {
207                xmin = x;
208            }
209            if x > xmax {
210                xmax = x;
211            }
212
213            // Update min and max values for y
214            if y < ymin {
215                ymin = y;
216            }
217            if y > ymax {
218                ymax = y;
219            }
220        }
221        Ok(Self {
222            zoom: expected_zoom,
223            min: Point2d::new(xmin, ymin),
224            max: Point2d::new(xmax, ymax),
225        })
226    }
227
228    /// Return new zbox one zoom level higher/down z2 -> z3
229    #[must_use]
230    pub const fn zoom_in(&self) -> Self {
231        Self {
232            zoom: self.zoom + 1,
233            min: Point2d::new(self.min.x * 2, self.min.y * 2),
234            max: Point2d::new(self.max.x * 2 + 1, self.max.y * 2 + 1),
235        }
236    }
237
238    /// Return new zbox one zoom level lower/up z3 -> z2
239    #[must_use]
240    pub const fn zoom_depth(&self, depth: u8) -> Self {
241        let target_zoom = self.zoom + depth;
242        let mut zbox = *self;
243        while zbox.zoom < target_zoom {
244            zbox = zbox.zoom_in();
245        }
246        zbox
247    }
248}
249
250impl TileZBoxIterator {
251    /// Create a new `TileZBoxIterator`
252    #[must_use]
253    pub const fn new(minx: u32, maxx: u32, miny: u32, maxy: u32, zoom: u8) -> Self {
254        Self {
255            range: TileZBox::new(minx, maxx, miny, maxy, zoom),
256            cur_x: minx,
257            cur_y: miny,
258        }
259    }
260}
261
262impl From<TileZBox> for TileZBoxIterator {
263    fn from(range: TileZBox) -> Self {
264        Self::new(
265            range.minx(),
266            range.maxx(),
267            range.miny(),
268            range.maxy(),
269            range.zoom(),
270        )
271    }
272}
273
274impl Iterator for TileZBoxIterator {
275    type Item = (u32, u32, u8);
276
277    fn next(&mut self) -> Option<Self::Item> {
278        if self.cur_x > self.range.max.x {
279            self.cur_x = self.range.min.x;
280            self.cur_y += 1;
281        }
282        if self.cur_y > self.range.max.y {
283            return None;
284        }
285        let tile = (self.cur_x, self.cur_y, self.range.zoom);
286        self.cur_x += 1;
287        Some(tile)
288    }
289
290    fn size_hint(&self) -> (usize, Option<usize>) {
291        let size = ((self.range.max.x - self.range.min.x + 1)
292            * (self.range.max.y - self.range.min.y + 1)) as usize;
293        (size, Some(size))
294    }
295}
296
297impl IntoIterator for TileZBox {
298    type Item = (u32, u32, u8);
299    type IntoIter = TileZBoxIterator;
300
301    fn into_iter(self) -> Self::IntoIter {
302        TileZBoxIterator::from(self)
303    }
304}
305
306impl IntoIterator for TileZBoxes {
307    type Item = (u32, u32, u8);
308    type IntoIter = std::vec::IntoIter<Self::Item>;
309
310    fn into_iter(self) -> Self::IntoIter {
311        self.ranges
312            .into_iter()
313            .flat_map(std::iter::IntoIterator::into_iter)
314            .collect::<Vec<Self::Item>>()
315            .into_iter()
316    }
317}
318
319impl TileZBoxes {
320    /// Create a new `TileZBoxes` from a single `TileZBox`
321    #[must_use]
322    pub fn new(minx: u32, maxx: u32, miny: u32, maxy: u32, zoom: u8) -> Self {
323        Self {
324            ranges: vec![TileZBox::new(minx, maxx, miny, maxy, zoom)],
325        }
326    }
327
328    /// Create a new `TileZBoxes` from a single `TileZBox`
329    #[must_use]
330    pub fn flipy(&self) -> Self {
331        Self {
332            ranges: self.ranges.iter().map(TileZBox::flipy).collect(),
333        }
334    }
335
336    /// Return the number of tiles contained by the `TileZBoxes`
337    #[must_use]
338    pub fn length(&self) -> u64 {
339        self.ranges.iter().map(TileZBox::length).sum()
340    }
341
342    /// Return the size of the `TileZBoxes` in tiles
343    #[must_use]
344    pub fn mbtiles_sql_where(&self, prefix: Option<&str>) -> String {
345        self.ranges
346            .iter()
347            .map(move |r| r.mbtiles_sql_where_prefix(prefix))
348            .collect::<Vec<String>>()
349            .join(" OR ")
350    }
351}
352
353impl From<TileZBox> for TileZBoxes {
354    fn from(range: TileZBox) -> Self {
355        Self {
356            ranges: vec![range],
357        }
358    }
359}
360
361impl From<Vec<TileZBox>> for TileZBoxes {
362    fn from(ranges: Vec<TileZBox>) -> Self {
363        Self { ranges }
364    }
365}