martin_tile_utils/
rectangle.rs

1//! Rectangle utilities for working with tile coordinate rectangles.
2//!
3//! This module provides the `TileRect` struct for representing rectangular regions
4//! in tile coordinate space, along with utilities for managing collections of
5//! non-overlapping rectangles.
6
7use serde::Serialize;
8
9/// A rectangular region in tile coordinate space.
10///
11/// Represents a rectangle defined by zoom level and tile coordinates.
12/// The rectangle is inclusive of both min and max coordinates.
13/// Use [`append_rect`] to merge rectangles without overlapping.
14///
15/// # Examples
16///
17/// ```
18/// # use martin_tile_utils::TileRect;
19/// let rect = TileRect::new(10, 0, 0, 255, 255);
20/// assert_eq!(rect.size(), 256 * 256);
21/// ```
22#[derive(Debug, Clone, Copy, PartialEq)]
23pub struct TileRect {
24    /// The zoom level of the tiles
25    pub zoom: u8,
26    /// The minimum X coordinate (inclusive)
27    pub min_x: u32,
28    /// The minimum Y coordinate (inclusive)
29    pub min_y: u32,
30    /// The maximum X coordinate (inclusive)
31    pub max_x: u32,
32    /// The maximum Y coordinate (inclusive)
33    pub max_y: u32,
34}
35
36impl Serialize for TileRect {
37    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
38    where
39        S: serde::ser::Serializer,
40    {
41        serializer.collect_str(&format!(
42            "{}: ({},{}) - ({},{})",
43            self.zoom, self.min_x, self.min_y, self.max_x, self.max_y
44        ))
45    }
46}
47
48impl TileRect {
49    /// Creates a new `TileRect` with the specified coordinates.
50    ///
51    /// # Arguments
52    ///
53    /// * `zoom` - The zoom level
54    /// * `min_x` - The minimum X coordinate (inclusive)
55    /// * `min_y` - The minimum Y coordinate (inclusive)
56    /// * `max_x` - The maximum X coordinate (inclusive)
57    /// * `max_y` - The maximum Y coordinate (inclusive)
58    ///
59    /// # Panics
60    ///
61    /// Panics if `min_x > max_x` or `min_y > max_y`.
62    ///
63    /// # Examples
64    ///
65    /// ```
66    /// # use martin_tile_utils::TileRect;
67    /// let rect = TileRect::new(0, 0, 0, 1, 1);
68    /// assert_eq!(rect.size(), 4);
69    /// ```
70    #[must_use]
71    pub fn new(zoom: u8, min_x: u32, min_y: u32, max_x: u32, max_y: u32) -> Self {
72        assert!(min_x <= max_x);
73        assert!(min_y <= max_y);
74        TileRect {
75            zoom,
76            min_x,
77            min_y,
78            max_x,
79            max_y,
80        }
81    }
82
83    /// Checks if two rectangles overlap.
84    ///
85    /// Two rectangles overlap if
86    /// - they share the same zoom level and
87    /// - their coordinate ranges intersect in both X and Y dimensions.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # use martin_tile_utils::TileRect;
93    /// let rect1 = TileRect::new(0, 0, 0, 1, 1);
94    /// let rect2 = TileRect::new(0, 1, 1, 2, 2);
95    /// assert!(rect1.is_overlapping(&rect2));
96    ///
97    /// let rect3 = TileRect::new(0, 2, 2, 3, 3);
98    /// assert!(!rect1.is_overlapping(&rect3));
99    /// ```
100    #[must_use]
101    pub fn is_overlapping(&self, other: &Self) -> bool {
102        self.zoom == other.zoom
103            && self.min_x <= other.max_x
104            && self.max_x >= other.min_x
105            && self.min_y <= other.max_y
106            && self.max_y >= other.min_y
107    }
108
109    /// Total number of tiles contained in this rectangle.
110    ///
111    /// The size is calculated as `(max_x - min_x + 1) * (max_y - min_y + 1)`.
112    ///
113    /// # Examples
114    ///
115    /// ```
116    /// # use martin_tile_utils::TileRect;
117    /// // x = 0..=2 => 3 tiles
118    /// // y = 0..=3 => 4 tiles
119    /// let rect = TileRect::new(0, 0, 0, 2, 3);
120    /// assert_eq!(rect.size(), 3 * 4);
121    /// ```
122    #[must_use]
123    pub fn size(&self) -> u64 {
124        u64::from(self.max_x - self.min_x + 1) * u64::from(self.max_y - self.min_y + 1)
125    }
126
127    /// Returns up to 4 non-overlapping rectangles that represent the parts of `o`
128    /// that do not overlap with `self`.
129    ///
130    /// This method splits the `o` rectangle into up to 4 parts that extend
131    /// beyond the boundaries of `self`. The parts are: left, right, top, and bottom.
132    /// The `o` rectangle has to be of the same zoom level.
133    fn get_non_overlapping(&self, o: &Self) -> [Option<Self>; 4] {
134        let mut result = [None, None, None, None];
135        assert_eq!(self.zoom, o.zoom);
136        if o.min_x < self.min_x {
137            // take the left part of the other rect, entire height
138            let min_x = self.min_x - 1;
139            result[0] = Some(TileRect::new(o.zoom, o.min_x, o.min_y, min_x, o.max_y));
140        }
141        if o.max_x > self.max_x {
142            // take the right part of the other rect, entire height
143            let max_x = self.max_x + 1;
144            result[1] = Some(TileRect::new(o.zoom, max_x, o.min_y, o.max_x, o.max_y));
145        }
146        if o.min_y < self.min_y {
147            // take the top part of the other rect, width of self
148            let min_x = o.min_x.max(self.min_x);
149            let max_x = o.max_x.min(self.max_x);
150            result[2] = Some(TileRect::new(o.zoom, min_x, o.min_y, max_x, self.min_y - 1));
151        }
152        if o.max_y > self.max_y {
153            // take the bottom part of the other rect, width of self
154            let min_x = o.min_x.max(self.min_x);
155            let max_x = o.max_x.min(self.max_x);
156            result[3] = Some(TileRect::new(o.zoom, min_x, self.max_y + 1, max_x, o.max_y));
157        }
158        result
159    }
160}
161
162/// Appends a new rectangle to a list of rectangles, ensuring no overlaps exist.
163///
164/// If the new rectangle overlaps with any existing rectangle, it will be split
165/// into non-overlapping parts that extend beyond the existing rectangle.
166/// This process is recursive and ensures that the final list contains only
167/// non-overlapping rectangles.
168///
169/// # Examples
170///
171/// ```
172/// # use martin_tile_utils::{TileRect, append_rect};
173/// let mut rectangles = Vec::new();
174/// append_rect(&mut rectangles, TileRect::new(0, 0, 0, 1, 1));
175/// append_rect(&mut rectangles, TileRect::new(0, 1, 1, 2, 2));
176///
177/// // The second rectangle overlaps with the first, so it gets split
178/// assert_eq!(rectangles.len(), 3);
179/// ```
180pub fn append_rect(rectangles: &mut Vec<TileRect>, new_rect: TileRect) {
181    for rect in rectangles.iter() {
182        if rect.is_overlapping(&new_rect) {
183            // add four new non-overlapping rectangles that exceed the existing one
184            for new_rect in rect.get_non_overlapping(&new_rect).into_iter().flatten() {
185                append_rect(rectangles, new_rect);
186            }
187            // new rectangle was split into zero to four non-overlapping rectangles and added
188            return;
189        }
190    }
191    // no overlap, add the new rectangle
192    rectangles.push(new_rect);
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    fn append(rectangles: &mut Vec<TileRect>, new_rect: TileRect) {
200        append_rect(rectangles, new_rect);
201        // make sure none of the rectangles overlap
202        for (i, r1) in rectangles.iter().enumerate() {
203            for (j, r2) in rectangles.iter().enumerate() {
204                if i != j {
205                    assert!(!r1.is_overlapping(r2));
206                }
207            }
208        }
209    }
210
211    #[test]
212    fn test_len() {
213        assert_eq!(1, TileRect::new(0, 0, 0, 0, 0).size());
214        assert_eq!(4, TileRect::new(0, 0, 0, 1, 1).size());
215        assert_eq!(15, TileRect::new(0, 2, 3, 4, 7).size());
216    }
217
218    #[test]
219    fn test_tile_range_is_overlapping() {
220        let r1 = TileRect::new(0, 0, 0, 0, 0);
221        let r2 = TileRect::new(0, 0, 0, 0, 0);
222        assert!(r1.is_overlapping(&r2));
223
224        let r1 = TileRect::new(0, 0, 0, 0, 0);
225        let r2 = TileRect::new(0, 1, 1, 1, 1);
226        assert!(!r1.is_overlapping(&r2));
227
228        let r1 = TileRect::new(0, 0, 0, 1, 1);
229        let r2 = TileRect::new(0, 1, 1, 2, 2);
230        assert!(r1.is_overlapping(&r2));
231
232        let r1 = TileRect::new(0, 0, 0, 2, 2);
233        let r2 = TileRect::new(0, 1, 1, 1, 1);
234        assert!(r1.is_overlapping(&r2));
235
236        let center = TileRect::new(0, 4, 4, 6, 6);
237
238        assert!(center.is_overlapping(&TileRect::new(0, 3, 5, 5, 5)));
239        assert!(center.is_overlapping(&TileRect::new(0, 5, 3, 5, 5)));
240        assert!(center.is_overlapping(&TileRect::new(0, 5, 5, 7, 5)));
241        assert!(center.is_overlapping(&TileRect::new(0, 5, 5, 5, 7)));
242
243        assert!(!center.is_overlapping(&TileRect::new(0, 3, 5, 3, 5)));
244        assert!(!center.is_overlapping(&TileRect::new(0, 5, 3, 5, 3)));
245        assert!(!center.is_overlapping(&TileRect::new(0, 7, 5, 7, 5)));
246        assert!(!center.is_overlapping(&TileRect::new(0, 5, 7, 5, 7)));
247    }
248
249    #[test]
250    fn test_append_single() {
251        let mut rectangles = Vec::new();
252        append(&mut rectangles, TileRect::new(0, 0, 0, 0, 0));
253        assert_eq!(rectangles, vec![TileRect::new(0, 0, 0, 0, 0)]);
254
255        append(&mut rectangles, TileRect::new(0, 0, 0, 0, 0));
256        assert_eq!(rectangles, vec![TileRect::new(0, 0, 0, 0, 0)]);
257
258        append(&mut rectangles, TileRect::new(0, 1, 0, 1, 1));
259        assert_eq!(
260            rectangles,
261            vec![TileRect::new(0, 0, 0, 0, 0), TileRect::new(0, 1, 0, 1, 1),]
262        );
263
264        append(&mut rectangles, TileRect::new(0, 0, 0, 1, 1));
265        assert_eq!(
266            rectangles,
267            vec![
268                TileRect::new(0, 0, 0, 0, 0),
269                TileRect::new(0, 1, 0, 1, 1),
270                TileRect::new(0, 0, 1, 0, 1)
271            ]
272        );
273    }
274
275    #[test]
276    fn test_append_multiple() {
277        let mut rectangles = Vec::new();
278        append(&mut rectangles, TileRect::new(0, 2, 2, 4, 4));
279        assert_eq!(rectangles, vec![TileRect::new(0, 2, 2, 4, 4)]);
280
281        append(&mut rectangles, TileRect::new(0, 1, 3, 3, 3));
282        assert_eq!(
283            rectangles,
284            vec![TileRect::new(0, 2, 2, 4, 4), TileRect::new(0, 1, 3, 1, 3),]
285        );
286
287        append(&mut rectangles, TileRect::new(0, 3, 1, 3, 3));
288        assert_eq!(
289            rectangles,
290            vec![
291                TileRect::new(0, 2, 2, 4, 4),
292                TileRect::new(0, 1, 3, 1, 3),
293                TileRect::new(0, 3, 1, 3, 1),
294            ]
295        );
296
297        append(&mut rectangles, TileRect::new(0, 3, 3, 5, 3));
298        assert_eq!(
299            rectangles,
300            vec![
301                TileRect::new(0, 2, 2, 4, 4),
302                TileRect::new(0, 1, 3, 1, 3),
303                TileRect::new(0, 3, 1, 3, 1),
304                TileRect::new(0, 5, 3, 5, 3),
305            ]
306        );
307
308        append(&mut rectangles, TileRect::new(0, 3, 3, 3, 5));
309        assert_eq!(
310            rectangles,
311            vec![
312                TileRect::new(0, 2, 2, 4, 4),
313                TileRect::new(0, 1, 3, 1, 3),
314                TileRect::new(0, 3, 1, 3, 1),
315                TileRect::new(0, 5, 3, 5, 3),
316                TileRect::new(0, 3, 5, 3, 5),
317            ]
318        );
319
320        append(&mut rectangles, TileRect::new(0, 3, 3, 5, 5));
321        assert_eq!(
322            rectangles,
323            vec![
324                TileRect::new(0, 2, 2, 4, 4),
325                TileRect::new(0, 1, 3, 1, 3),
326                TileRect::new(0, 3, 1, 3, 1),
327                TileRect::new(0, 5, 3, 5, 3),
328                TileRect::new(0, 3, 5, 3, 5),
329                TileRect::new(0, 5, 4, 5, 5),
330                TileRect::new(0, 4, 5, 4, 5),
331            ]
332        );
333
334        append(&mut rectangles, TileRect::new(0, 1, 1, 3, 3));
335        assert_eq!(
336            rectangles,
337            vec![
338                TileRect::new(0, 2, 2, 4, 4),
339                TileRect::new(0, 1, 3, 1, 3),
340                TileRect::new(0, 3, 1, 3, 1),
341                TileRect::new(0, 5, 3, 5, 3),
342                TileRect::new(0, 3, 5, 3, 5),
343                TileRect::new(0, 5, 4, 5, 5),
344                TileRect::new(0, 4, 5, 4, 5),
345                TileRect::new(0, 1, 1, 1, 2),
346                TileRect::new(0, 2, 1, 2, 1),
347            ]
348        );
349    }
350}