utiles_core/
tile_zbox.rs

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