oxigdal_streaming/tile/
pyramid.rs1use super::protocol::TileCoordinate;
4use crate::error::{Result, StreamingError};
5use oxigdal_core::types::BoundingBox;
6use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
10pub struct ZoomLevel {
11 pub level: u8,
13
14 pub matrix_width: u32,
16
17 pub matrix_height: u32,
19
20 pub resolution: f64,
22
23 pub scale_denominator: f64,
25}
26
27impl ZoomLevel {
28 pub fn new(level: u8, resolution: f64) -> Self {
30 let tiles = 1u32 << level;
31 Self {
32 level,
33 matrix_width: tiles,
34 matrix_height: tiles,
35 resolution,
36 scale_denominator: resolution * 111_319.49079327358, }
38 }
39
40 pub fn num_tiles(&self) -> u64 {
42 (self.matrix_width as u64) * (self.matrix_height as u64)
43 }
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct TileMatrix {
49 pub identifier: String,
51
52 pub bbox: BoundingBox,
54
55 pub tile_width: u32,
57
58 pub tile_height: u32,
60
61 pub zoom_levels: Vec<ZoomLevel>,
63}
64
65impl TileMatrix {
66 pub fn new(identifier: String, bbox: BoundingBox, tile_width: u32, tile_height: u32) -> Self {
68 Self {
69 identifier,
70 bbox,
71 tile_width,
72 tile_height,
73 zoom_levels: Vec::new(),
74 }
75 }
76
77 pub fn add_zoom_level(&mut self, level: ZoomLevel) {
79 self.zoom_levels.push(level);
80 }
81
82 pub fn get_zoom_level(&self, level: u8) -> Option<&ZoomLevel> {
84 self.zoom_levels.iter().find(|z| z.level == level)
85 }
86
87 pub fn tile_bbox(&self, coord: &TileCoordinate) -> Result<BoundingBox> {
89 let zoom = self.get_zoom_level(coord.z).ok_or_else(|| {
90 StreamingError::InvalidOperation(format!("Zoom level {} not found", coord.z))
91 })?;
92
93 let width = self.bbox.width() / (zoom.matrix_width as f64);
94 let height = self.bbox.height() / (zoom.matrix_height as f64);
95
96 let min_x = self.bbox.min_x + (coord.x as f64) * width;
97 let max_y = self.bbox.max_y - (coord.y as f64) * height;
98 let max_x = min_x + width;
99 let min_y = max_y - height;
100
101 BoundingBox::new(min_x, min_y, max_x, max_y).map_err(StreamingError::Core)
102 }
103}
104
105pub struct TilePyramid {
107 matrices: Vec<TileMatrix>,
109
110 min_zoom: u8,
112
113 max_zoom: u8,
115}
116
117impl TilePyramid {
118 pub fn new(min_zoom: u8, max_zoom: u8) -> Self {
120 Self {
121 matrices: Vec::new(),
122 min_zoom,
123 max_zoom,
124 }
125 }
126
127 pub fn web_mercator(max_zoom: u8) -> Result<Self> {
129 let bbox =
130 BoundingBox::new(-180.0, -85.0511, 180.0, 85.0511).map_err(StreamingError::Core)?;
131
132 let mut pyramid = Self::new(0, max_zoom);
133
134 for z in 0..=max_zoom {
135 let mut matrix = TileMatrix::new(format!("WebMercator:{}", z), bbox, 256, 256);
136
137 let resolution = 360.0 / (256.0 * (1u32 << z) as f64);
138 matrix.add_zoom_level(ZoomLevel::new(z, resolution));
139
140 pyramid.add_matrix(matrix);
141 }
142
143 Ok(pyramid)
144 }
145
146 pub fn add_matrix(&mut self, matrix: TileMatrix) {
148 self.matrices.push(matrix);
149 }
150
151 pub fn get_matrix(&self, identifier: &str) -> Option<&TileMatrix> {
153 self.matrices.iter().find(|m| m.identifier == identifier)
154 }
155
156 pub fn tiles_for_zoom(&self, zoom: u8) -> Vec<TileCoordinate> {
158 if zoom < self.min_zoom || zoom > self.max_zoom {
159 return Vec::new();
160 }
161
162 let num_tiles = 1u32 << zoom;
163 let mut tiles = Vec::with_capacity((num_tiles * num_tiles) as usize);
164
165 for y in 0..num_tiles {
166 for x in 0..num_tiles {
167 tiles.push(TileCoordinate::new(zoom, x, y));
168 }
169 }
170
171 tiles
172 }
173
174 pub fn tiles_for_bbox(&self, bbox: &BoundingBox, zoom: u8) -> Result<Vec<TileCoordinate>> {
176 if zoom < self.min_zoom || zoom > self.max_zoom {
177 return Ok(Vec::new());
178 }
179
180 let matrix = self.matrices.get(zoom as usize).ok_or_else(|| {
181 StreamingError::InvalidOperation(format!("Matrix for zoom {} not found", zoom))
182 })?;
183
184 let zoom_level = matrix.get_zoom_level(zoom).ok_or_else(|| {
185 StreamingError::InvalidOperation(format!("Zoom level {} not found", zoom))
186 })?;
187
188 let width = matrix.bbox.width() / (zoom_level.matrix_width as f64);
189 let height = matrix.bbox.height() / (zoom_level.matrix_height as f64);
190
191 let min_x = ((bbox.min_x - matrix.bbox.min_x) / width).floor().max(0.0) as u32;
192 let max_x = ((bbox.max_x - matrix.bbox.min_x) / width)
193 .ceil()
194 .min(zoom_level.matrix_width as f64) as u32;
195 let min_y = ((matrix.bbox.max_y - bbox.max_y) / height).floor().max(0.0) as u32;
196 let max_y = ((matrix.bbox.max_y - bbox.min_y) / height)
197 .ceil()
198 .min(zoom_level.matrix_height as f64) as u32;
199
200 let mut tiles = Vec::new();
201 for y in min_y..max_y {
202 for x in min_x..max_x {
203 tiles.push(TileCoordinate::new(zoom, x, y));
204 }
205 }
206
207 Ok(tiles)
208 }
209
210 pub fn zoom_levels(&self) -> (u8, u8) {
212 (self.min_zoom, self.max_zoom)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_zoom_level() {
222 let zoom = ZoomLevel::new(10, 0.0001);
223 assert_eq!(zoom.level, 10);
224 assert_eq!(zoom.matrix_width, 1024);
225 assert_eq!(zoom.matrix_height, 1024);
226 assert_eq!(zoom.num_tiles(), 1024 * 1024);
227 }
228
229 #[test]
230 fn test_web_mercator_pyramid() {
231 let pyramid = TilePyramid::web_mercator(18);
232 assert!(pyramid.is_ok());
233
234 if let Ok(pyramid) = pyramid {
235 assert_eq!(pyramid.zoom_levels(), (0, 18));
236 assert_eq!(pyramid.matrices.len(), 19);
237 }
238 }
239
240 #[test]
241 fn test_tiles_for_zoom() {
242 let pyramid = TilePyramid::new(0, 10);
243 let tiles = pyramid.tiles_for_zoom(2);
244 assert_eq!(tiles.len(), 16); }
246}