1use crate::hilbert::HilbertIterator;
2use crate::quadkey::check_quadkey_support;
3use crate::tile::{BoundingBox, Coords, Xyz};
4use crate::tile_matrix_set::{ordered_axes_inverted, TileMatrixSetOps};
5use crate::tms_iterator::XyzIterator;
6use crate::transform::{merc_tile_ul, Error::TransformationUnsupported, Transform, Transformer};
7use ogcapi_types::common::Crs;
8use ogcapi_types::tiles::{
9 BoundingBox2D, CornerOfOrigin, OrderedAxes, TileMatrix, TileMatrixSet, TitleDescriptionKeywords,
10};
11use std::convert::AsRef;
12use std::f64::consts::PI;
13use std::num::{NonZeroU16, NonZeroU64};
14
15#[derive(Debug)]
17pub struct Tms {
18 pub tms: TileMatrixSet,
19 pub is_quadtree: bool,
20 data_crs: Crs,
22 geographic_crs: Crs, to_geographic: Option<Transformer>,
24 from_geographic: Option<Transformer>,
25}
26
27#[derive(thiserror::Error, Debug)]
28pub enum TmsError {
29 #[error("Invalid tile zoom identifier: `{0}`")]
30 InvalidZoomId(String),
31 #[error("Invalid zoom level: `{0}`")]
32 InvalidZoom(u8),
33 #[error("Point ({0}, {1}) is outside bounds {2:?}")]
34 PointOutsideBounds(f64, f64, BoundingBox),
35 #[error(transparent)]
36 TransformationError(#[from] crate::transform::Error),
37 #[error("Zero width or height")]
38 NonZeroError,
39 }
50
51pub type Result<T> = std::result::Result<T, TmsError>;
52
53impl Clone for Tms {
54 fn clone(&self) -> Tms {
56 Tms::init(&self.tms).expect("Repeating initialization")
57 }
58}
59
60pub enum Matrix<'a> {
61 Predefined(&'a TileMatrix),
62 Calculated(TileMatrix),
63}
64
65impl AsRef<TileMatrix> for Matrix<'_> {
66 fn as_ref(&self) -> &TileMatrix {
67 match self {
68 Matrix::Predefined(m) => m,
69 Matrix::Calculated(m) => m,
70 }
71 }
72}
73
74pub enum ZoomLevelStrategy {
75 Lower,
76 Upper,
77 Auto,
78}
79
80impl Tms {
81 pub(crate) fn init(data: &TileMatrixSet) -> Result<Self> {
83 let is_quadtree = check_quadkey_support(&data.tile_matrices);
84 let data_crs = data.crs.clone();
85 let geographic_crs = Crs::default(); let to_geographic = Transformer::from_crs(&data_crs, &geographic_crs, true).ok();
87 let from_geographic = Transformer::from_crs(&geographic_crs, &data_crs, true).ok();
88 let mut tms = data.clone();
89 Self::sort_tile_matrices(&mut tms)?;
90 if let Some(bounding_box) = &tms.bounding_box {
92 if let Some(crs) = &bounding_box.crs {
93 if *crs != tms.crs {
94 let _transform = Transformer::from_crs(crs, &tms.crs, true)?;
95 }
96 }
97 }
98 Ok(Self {
99 tms,
100 is_quadtree,
101 data_crs,
102 geographic_crs,
103 to_geographic,
104 from_geographic,
105 })
106 }
107
108 fn sort_tile_matrices(tms: &mut TileMatrixSet) -> Result<()> {
110 for m in &tms.tile_matrices {
112 m.id.parse::<u8>()
113 .map_err(|_e| TmsError::InvalidZoomId(m.id.clone()))?;
114 }
115 tms.tile_matrices.sort_by(|a, b| {
116 a.id.parse::<u8>()
117 .unwrap()
118 .cmp(&b.id.parse::<u8>().unwrap())
119 });
120 Ok(())
121 }
122
123 pub fn matrices(&self) -> &Vec<TileMatrix> {
125 &self.tms.tile_matrices
126 }
127
128 pub fn crs(&self) -> &Crs {
130 &self.tms.crs
131 }
132
133 pub fn minzoom(&self) -> u8 {
135 self.tms.tile_matrices[0].id.parse::<u8>().unwrap()
136 }
137 pub fn maxzoom(&self) -> u8 {
139 self.tms.tile_matrices[self.tms.tile_matrices.len() - 1]
140 .id
141 .parse::<u8>()
142 .unwrap()
143 }
144
145 fn invert_axis(&self) -> bool {
147 self.tms.crs_axis_inverted()
148 }
149
150 #[allow(clippy::too_many_arguments)]
170 pub fn custom(
171 extent: Vec<f64>,
172 crs: &Crs,
173 tile_width: u16, tile_height: u16, matrix_scale: Option<Vec<u8>>, extent_crs: Option<&Crs>, minzoom: u8, maxzoom: u8, title: &str, id: &str, ordered_axes: Option<OrderedAxes>,
182 geographic_crs: &Crs, ) -> Result<Self> {
184 let matrix_scale = matrix_scale.unwrap_or(vec![1, 1]);
185 let bbox = transformed_bbox(&extent, crs, extent_crs)?;
186 let width = (bbox.right - bbox.left).abs();
187 let height = (bbox.top - bbox.bottom).abs();
188 let resolutions: Vec<f64> = (minzoom..=maxzoom)
189 .map(|zoom| {
190 f64::max(
191 width
192 / (tile_width as f64 * matrix_scale[0] as f64)
193 / 2_u64.pow(zoom as u32) as f64,
194 height
195 / (tile_height as f64 * matrix_scale[1] as f64)
196 / 2_u64.pow(zoom as u32) as f64,
197 )
198 })
199 .collect();
200 Self::custom_resolutions(
201 extent,
202 crs,
203 tile_width,
204 tile_height,
205 extent_crs,
206 resolutions,
207 title,
208 id,
209 ordered_axes,
210 geographic_crs,
211 )
212 }
213
214 #[allow(clippy::too_many_arguments)]
216 pub fn custom_resolutions(
217 extent: Vec<f64>,
218 crs: &Crs,
219 tile_width: u16,
220 tile_height: u16,
221 extent_crs: Option<&Crs>,
222 resolutions: Vec<f64>,
223 title: &str,
224 id: &str,
225 ordered_axes: Option<OrderedAxes>,
226 geographic_crs: &Crs,
227 ) -> Result<Self> {
228 let mut tms = TileMatrixSet {
229 title_description_keywords: TitleDescriptionKeywords {
230 title: Some(title.to_string()),
231 description: None,
232 keywords: None,
233 },
234 id: id.to_string(),
235 uri: None,
236 crs: crs.clone(),
237 ordered_axes: ordered_axes.clone(),
238 well_known_scale_set: None,
239 bounding_box: None,
240 tile_matrices: Vec::with_capacity(resolutions.len()),
241 };
242
243 let is_inverted = if let Some(ordered_axes) = &ordered_axes {
244 ordered_axes_inverted(ordered_axes)
245 } else {
246 tms.crs_axis_inverted()
247 };
248
249 tms.bounding_box = Some(if is_inverted {
250 BoundingBox2D {
251 crs: Some(extent_crs.unwrap_or(crs).clone()),
252 ordered_axes: ordered_axes.clone(),
253 lower_left: [extent[1], extent[0]],
254 upper_right: [extent[3], extent[2]],
255 }
256 } else {
257 BoundingBox2D {
258 crs: Some(extent_crs.unwrap_or(crs).clone()),
259 ordered_axes: ordered_axes.clone(),
260 lower_left: [extent[0], extent[1]],
261 upper_right: [extent[2], extent[3]],
262 }
263 });
264
265 let bbox = transformed_bbox(&extent, crs, extent_crs)?;
266
267 let x_origin = if !is_inverted { bbox.left } else { bbox.top };
268 let y_origin = if !is_inverted { bbox.top } else { bbox.left };
269 let corner_of_origin = if !is_inverted {
270 None
271 } else {
272 Some(CornerOfOrigin::BottomLeft)
273 };
274
275 let mpu = meters_per_unit(crs);
276 for (zoom, res) in resolutions.iter().enumerate() {
277 let unitheight = tile_height as f64 * res;
278 let unitwidth = tile_width as f64 * res;
279 let maxy = ((bbox.top - bbox.bottom - 0.01 * unitheight) / unitheight).ceil() as u64;
280 let maxx = ((bbox.right - bbox.left - 0.01 * unitwidth) / unitwidth).ceil() as u64;
281 tms.tile_matrices.push(TileMatrix {
282 title_description_keywords: TitleDescriptionKeywords {
283 title: None,
284 description: None,
285 keywords: None,
286 },
287 id: zoom.to_string(),
288 scale_denominator: res * mpu / 0.00028,
289 cell_size: *res,
290 corner_of_origin: corner_of_origin.clone(),
291 point_of_origin: [x_origin, y_origin],
292 tile_width: NonZeroU16::new(tile_width).ok_or(TmsError::NonZeroError)?,
293 tile_height: NonZeroU16::new(tile_height).ok_or(TmsError::NonZeroError)?,
294 matrix_width: NonZeroU64::new(maxx).ok_or(TmsError::NonZeroError)?,
295 matrix_height: NonZeroU64::new(maxy).ok_or(TmsError::NonZeroError)?,
296 variable_matrix_widths: None,
297 });
298 }
299
300 let mut tms = Tms::init(&tms)?;
301 tms.geographic_crs = geographic_crs.clone();
302 Ok(tms)
303 }
304
305 pub fn matrix_z(&self, zoom: u8) -> Option<&TileMatrix> {
307 self.tms
308 .tile_matrices
309 .iter()
310 .find(|&m| m.id == zoom.to_string())
311 }
312
313 pub fn matrix(&self, zoom: u8) -> Matrix<'_> {
315 if let Some(m) = self.matrix_z(zoom) {
316 return Matrix::Predefined(m);
317 }
318
319 let matrix_scale = (1..self.tms.tile_matrices.len())
320 .map(|idx| {
321 (self.tms.tile_matrices[idx].scale_denominator
322 / self.tms.tile_matrices[idx - 1].scale_denominator)
323 .round() })
325 .collect::<Vec<_>>();
326 if matrix_scale.len() > 1 {
327 }
333
334 let mut tile_matrix = self.tms.tile_matrices.last().unwrap().clone();
335 let factor = 1.0 / matrix_scale[0];
336 while tile_matrix.id != zoom.to_string() {
337 tile_matrix = TileMatrix {
338 title_description_keywords: TitleDescriptionKeywords {
339 title: None,
340 description: None,
341 keywords: None,
342 },
343 id: (tile_matrix.id.parse::<u8>().unwrap() + 1).to_string(),
344 scale_denominator: tile_matrix.scale_denominator / factor,
345 cell_size: tile_matrix.cell_size, corner_of_origin: tile_matrix.corner_of_origin,
347 point_of_origin: tile_matrix.point_of_origin,
348 tile_width: tile_matrix.tile_width,
349 tile_height: tile_matrix.tile_height,
350 matrix_width: NonZeroU64::new(
351 (u64::from(tile_matrix.matrix_width) as f64 * factor).round() as u64,
352 )
353 .unwrap(),
354 matrix_height: NonZeroU64::new(
355 (u64::from(tile_matrix.matrix_height) as f64 * factor).round() as u64,
356 )
357 .unwrap(),
358 variable_matrix_widths: None,
359 }
360 }
361
362 Matrix::Calculated(tile_matrix)
363 }
364
365 pub fn resolution(&self, matrix: &TileMatrix) -> f64 {
371 matrix.scale_denominator * 0.28e-3 / meters_per_unit(self.crs())
372 }
373
374 pub fn resolution_z(&self, zoom: u8) -> Option<f64> {
376 self.matrix_z(zoom).map(|m| self.resolution(m))
377 }
378
379 pub fn zoom_for_res(
394 &self,
395 res: f64,
396 max_z: Option<u8>,
397 zoom_level_strategy: &ZoomLevelStrategy,
398 min_z: Option<u8>,
399 ) -> Result<u8> {
400 let max_z = max_z.unwrap_or(self.maxzoom());
401 let min_z = min_z.unwrap_or(self.minzoom());
402 let mut zoom_level = min_z;
403 let mut matrix_res = 0.0;
404 for z in min_z..=max_z {
405 zoom_level = z;
406 matrix_res = self.resolution(self.matrix(zoom_level).as_ref());
407 if res > matrix_res || (res - matrix_res).abs() / matrix_res <= 1e-8 {
408 break;
409 }
410 }
411 if zoom_level > 0 && (res - matrix_res).abs() / matrix_res > 1e-8 {
412 match zoom_level_strategy {
413 ZoomLevelStrategy::Lower => {
414 zoom_level = u8::max(zoom_level - 1, min_z);
415 }
416 ZoomLevelStrategy::Upper => {
417 zoom_level = u8::min(zoom_level, max_z);
418 }
419 ZoomLevelStrategy::Auto => {
420 if (self.resolution(self.matrix(u8::max(zoom_level - 1, min_z)).as_ref()) / res)
421 < (res / matrix_res)
422 {
423 zoom_level = u8::max(zoom_level - 1, min_z);
424 }
425 }
426 }
427 }
428 Ok(zoom_level)
429 }
430
431 fn lnglat(&self, x: f64, y: f64, truncate: bool ) -> Result<Coords> {
433 let Some(transformer) = &self.to_geographic else {
434 return Err(self.transform_error_to_geographic());
435 };
436 point_in_bbox(Coords::new(x, y), self.xy_bbox(), DEFAULT_BBOX_PREC)?;
437 let (mut lng, mut lat) = transformer.transform(x, y)?;
438
439 if truncate {
440 (lng, lat) = self.truncate_lnglat(lng, lat)?;
441 }
442
443 Ok(Coords::new(lng, lat))
444 }
445
446 pub fn xy(&self, lng: f64, lat: f64) -> Result<Coords> {
448 let Some(transformer) = &self.from_geographic else {
449 return Err(self.transform_error_from_geographic());
450 };
451 point_in_bbox(Coords::new(lng, lat), self.xy_bbox(), DEFAULT_BBOX_PREC)?;
452
453 let (x, y) = transformer.transform(lng, lat)?;
454
455 Ok(Coords::new(x, y))
456 }
457
458 pub fn xy_truncated(&self, lng: f64, lat: f64) -> Result<Coords> {
460 let (lng, lat) = self.truncate_lnglat(lng, lat)?;
461 self.xy(lng, lat)
462 }
463
464 pub fn truncate_lnglat(&self, lng: f64, lat: f64) -> Result<(f64, f64)> {
468 let mut lng = lng;
469 let mut lat = lat;
470 let bbox = self.bbox()?;
471 if lng > bbox.right {
472 lng = bbox.right;
473 } else if lng < bbox.left {
474 lng = bbox.left;
475 }
476
477 if lat > bbox.top {
478 lat = bbox.top;
479 } else if lat < bbox.bottom {
480 lat = bbox.bottom;
481 }
482
483 Ok((lng, lat))
484 }
485
486 pub fn xy_tile(&self, xcoord: f64, ycoord: f64, zoom: u8) -> Xyz {
492 let m = self.matrix(zoom);
493 let matrix = m.as_ref();
494 let res = self.resolution(matrix);
495
496 let origin_x: f64 = if self.invert_axis() {
497 matrix.point_of_origin[1]
498 } else {
499 matrix.point_of_origin[0]
500 };
501 let origin_y = if self.invert_axis() {
502 matrix.point_of_origin[0]
503 } else {
504 matrix.point_of_origin[1]
505 };
506
507 let xtile = if !xcoord.is_infinite() {
508 ((xcoord - origin_x) / (res * u16::from(matrix.tile_width) as f64)).floor()
509 } else {
510 0.0
511 };
512 let ytile = if !ycoord.is_infinite() {
513 ((origin_y - ycoord) / (res * u16::from(matrix.tile_height) as f64)).floor()
514 } else {
515 0.0
516 };
517
518 let xtile = if xtile < 0.0 { 0 } else { xtile as u64 };
520
521 let ytile = if ytile < 0.0 { 0 } else { ytile as u64 };
522
523 let xtile = if xtile > matrix.matrix_width.into() {
524 matrix.matrix_width.into()
525 } else {
526 xtile
527 };
528
529 let ytile = if ytile > matrix.matrix_height.into() {
530 matrix.matrix_height.into()
531 } else {
532 ytile
533 };
534
535 Xyz::new(xtile, ytile, zoom)
536 }
537
538 pub fn tile(&self, lng: f64, lat: f64, zoom: u8) -> Result<Xyz> {
544 let xy = self.xy(lng, lat)?;
545 Ok(self.xy_tile(xy.x, xy.y, zoom))
546 }
547
548 pub fn tile_truncated(&self, lng: f64, lat: f64, zoom: u8) -> Result<Xyz> {
554 let xy = self.xy_truncated(lng, lat)?;
555 Ok(self.xy_tile(xy.x, xy.y, zoom))
556 }
557
558 pub fn xy_ul(&self, tile: &Xyz) -> Coords {
563 let m = self.matrix(tile.z);
564 let matrix = m.as_ref();
565 let res = self.resolution(matrix);
566
567 let origin_x = if self.invert_axis() {
568 matrix.point_of_origin[1]
569 } else {
570 matrix.point_of_origin[0]
571 };
572 let origin_y = if self.invert_axis() {
573 matrix.point_of_origin[0]
574 } else {
575 matrix.point_of_origin[1]
576 };
577
578 let xcoord = origin_x + tile.x as f64 * res * u16::from(matrix.tile_width) as f64;
579 let ycoord = origin_y - tile.y as f64 * res * u16::from(matrix.tile_height) as f64;
580 Coords::new(xcoord, ycoord)
581 }
582
583 pub fn xy_bounds(&self, tile: &Xyz) -> BoundingBox {
588 let top_left = self.xy_ul(tile);
589 let bottom_right = self.xy_ul(&Xyz::new(tile.x + 1, tile.y + 1, tile.z));
590 BoundingBox::new(top_left.x, bottom_right.y, bottom_right.x, top_left.y)
591 }
592
593 pub fn ul(&self, tile: &Xyz) -> Result<Coords> {
598 let coords = if self.data_crs.as_srid() == 3857 && self.geographic_crs.as_srid() == 4326 {
599 let (lon, lat) = merc_tile_ul(tile.x as u32, tile.y as u32, tile.z);
600 Coords::new(lon, lat)
601 } else {
602 let xy = self.xy_ul(tile);
603 self.lnglat(xy.x, xy.y, false)?
604 };
605 Ok(coords)
606 }
607
608 pub fn bounds(&self, tile: &Xyz) -> Result<BoundingBox> {
613 let top_left = self.ul(tile)?;
614 let bottom_right = self.ul(&Xyz::new(tile.x + 1, tile.y + 1, tile.z))?;
615 Ok(BoundingBox::new(
616 top_left.x,
617 bottom_right.y,
618 bottom_right.x,
619 top_left.y,
620 ))
621 }
622
623 pub fn xy_bbox(&self) -> BoundingBox {
625 let (left, bottom, right, top) = if let Some(bounding_box) = &self.tms.bounding_box {
626 let (left, bottom) = if self.invert_axis() {
627 (&bounding_box.lower_left[1], &bounding_box.lower_left[0])
628 } else {
629 (&bounding_box.lower_left[0], &bounding_box.lower_left[1])
630 };
631 let (right, top) = if self.invert_axis() {
632 (&bounding_box.upper_right[1], &bounding_box.upper_right[0])
633 } else {
634 (&bounding_box.upper_right[0], &bounding_box.upper_right[1])
635 };
636 if let Some(crs) = &bounding_box.crs {
637 if crs != self.crs() {
638 let transform =
640 Transformer::from_crs(crs, self.crs(), true).expect("Transformer");
641 let (left, bottom, right, top) = transform
642 .transform_bounds(*left, *bottom, *right, *top )
643 .expect("Transformer");
644 (left, bottom, right, top)
645 } else {
646 (*left, *bottom, *right, *top)
647 }
648 } else {
649 (*left, *bottom, *right, *top)
650 }
651 } else {
652 let zoom = self.minzoom();
653 let m = self.matrix(zoom);
654 let matrix = m.as_ref();
655 let top_left = self.xy_ul(&Xyz::new(0, 0, zoom));
656 let bottom_right = self.xy_ul(&Xyz::new(
657 u64::from(matrix.matrix_width),
658 u64::from(matrix.matrix_height),
659 zoom,
660 ));
661 (top_left.x, bottom_right.y, bottom_right.x, top_left.y)
662 };
663 BoundingBox {
664 left,
665 bottom,
666 right,
667 top,
668 }
669 }
670
671 pub fn bbox(&self) -> Result<BoundingBox> {
673 let Some(transformer) = &self.to_geographic else {
674 return Err(self.transform_error_to_geographic());
675 };
676 let xy_bbox = self.xy_bbox();
677 let bbox = transformer.transform_bounds(
678 xy_bbox.left,
679 xy_bbox.bottom,
680 xy_bbox.right,
681 xy_bbox.top,
682 )?;
683 Ok(BoundingBox::new(bbox.0, bbox.1, bbox.2, bbox.3))
684 }
685
686 pub fn intersect_tms(&self, bbox: &BoundingBox) -> bool {
688 let tms_bounds = self.xy_bbox();
689 bbox.left < tms_bounds.right
690 && bbox.right > tms_bounds.left
691 && bbox.top > tms_bounds.bottom
692 && bbox.bottom < tms_bounds.top
693 }
694
695 pub fn tiles(
708 &self,
709 west: f64,
710 south: f64,
711 east: f64,
712 north: f64,
713 zooms: &[u8],
714 truncate: bool, ) -> Result<impl Iterator<Item = Xyz>> {
716 let mut tiles: Vec<Xyz> = Vec::new();
717 let bbox = self.bbox()?;
718 let bboxes = if west > east {
719 vec![
720 (bbox.left, south, east, north),
721 (west, south, bbox.right, north),
722 ]
723 } else {
724 vec![(west, south, east, north)]
725 };
726 let get_tile = if truncate {
727 Tms::tile_truncated
728 } else {
729 Tms::tile
730 };
731 for bb in bboxes {
732 let w = bb.0.max(bbox.left);
733 let s = bb.1.max(bbox.bottom);
734 let e = bb.2.min(bbox.right);
735 let n = bb.3.min(bbox.top);
736 for z in zooms {
737 let ul_tile = get_tile(self, w + LL_EPSILON, n - LL_EPSILON, *z)?;
738 let lr_tile = get_tile(self, e - LL_EPSILON, s + LL_EPSILON, *z)?;
739 for i in ul_tile.x..=lr_tile.x {
740 for j in ul_tile.y..=lr_tile.y {
741 tiles.push(Xyz::new(i, j, *z));
742 }
743 }
744 }
745 }
746 Ok(tiles.into_iter())
747 }
748
749 fn extent_limits(
751 &self,
752 extend: &BoundingBox,
753 minzoom: u8,
754 maxzoom: u8,
755 truncate: bool, ) -> Result<Vec<MinMax>> {
757 if extend.left > extend.right || minzoom > maxzoom {
758 return Ok(Vec::new()); }
760 let bbox = self.bbox()?;
761 let get_tile = if truncate {
762 Tms::tile_truncated
763 } else {
764 Tms::tile
765 };
766 let w = extend.left.max(bbox.left);
767 let s = extend.bottom.max(bbox.bottom);
768 let e = extend.right.min(bbox.right);
769 let n = extend.top.min(bbox.top);
770 let limits = (minzoom..=maxzoom)
771 .map(|z| {
772 let ul_tile = get_tile(self, w + LL_EPSILON, n - LL_EPSILON, z)?;
773 let lr_tile = get_tile(self, e - LL_EPSILON, s + LL_EPSILON, z)?;
774 Ok(MinMax {
775 x_min: ul_tile.x,
776 x_max: lr_tile.x,
777 y_min: ul_tile.y,
778 y_max: lr_tile.y,
779 })
780 })
781 .collect::<Result<Vec<MinMax>>>()?;
782 Ok(limits)
783 }
784
785 fn extent_limits_xy(&self, extend: &BoundingBox, minzoom: u8, maxzoom: u8) -> Vec<MinMax> {
787 if extend.left > extend.right || minzoom > maxzoom {
788 return Vec::new(); }
790 let bbox = self.xy_bbox();
791 let w = extend.left.max(bbox.left);
792 let s = extend.bottom.max(bbox.bottom);
793 let e = extend.right.min(bbox.right);
794 let n = extend.top.min(bbox.top);
795 (minzoom..=maxzoom)
796 .map(|z| {
797 let res = self.resolution(self.matrix(z).as_ref()) / 10.0;
798 let ul_tile = self.xy_tile(w + res, n - res, z);
799 let lr_tile = self.xy_tile(e - res, s + res, z);
800 MinMax {
801 x_min: ul_tile.x,
802 x_max: lr_tile.x,
803 y_min: ul_tile.y,
804 y_max: lr_tile.y,
805 }
806 })
807 .collect()
808 }
809
810 pub fn xyz_iterator_geographic(
812 &self,
813 extend: &BoundingBox,
814 minzoom: u8,
815 maxzoom: u8,
816 ) -> Result<XyzIterator> {
817 let limits = self.extent_limits(extend, minzoom, maxzoom, false)?;
818 Ok(XyzIterator::new(minzoom, maxzoom, limits))
819 }
820
821 pub fn xyz_iterator(&self, extend: &BoundingBox, minzoom: u8, maxzoom: u8) -> XyzIterator {
823 let limits = self.extent_limits_xy(extend, minzoom, maxzoom);
824 XyzIterator::new(minzoom, maxzoom, limits)
825 }
826
827 pub fn hilbert_iterator(&self, minzoom: u8, maxzoom: u8) -> HilbertIterator {
829 HilbertIterator::new(minzoom, maxzoom)
830 }
831
832 fn minmax(&self, zoom: u8) -> MinMax {
924 let matrix = self.matrix(zoom);
925 let m = matrix.as_ref();
926 MinMax {
927 x_min: 0,
928 x_max: u64::from(m.matrix_width).saturating_sub(1),
929 y_min: 0,
930 y_max: u64::from(m.matrix_height).saturating_sub(1),
931 }
932 }
933
934 pub fn is_valid(&self, tile: &Xyz) -> bool {
936 if tile.z < self.minzoom() {
937 return false;
938 }
939
940 let extrema = self.minmax(tile.z);
941 let validx = extrema.x_min <= tile.x && tile.x <= extrema.x_max;
942 let validy = extrema.y_min <= tile.y && tile.y <= extrema.y_max;
943
944 validx && validy
945 }
946
947 pub fn neighbors(&self, tile: &Xyz) -> Vec<Xyz> {
958 let extrema = self.minmax(tile.z);
959
960 let mut tiles = Vec::new();
961 for x in tile.x.saturating_sub(1)..=tile.x.saturating_add(1) {
962 for y in tile.y.saturating_sub(1)..=tile.y.saturating_add(1) {
963 if x == tile.x && y == tile.y
964 || x < extrema.x_min
965 || y < extrema.y_min
966 || x > extrema.x_max
967 || y > extrema.y_max
968 {
969 continue;
970 }
971
972 tiles.push(Xyz::new(x, y, tile.z));
973 }
974 }
975
976 tiles
977 }
978
979 pub fn parent(&self, tile: &Xyz, zoom: Option<u8> ) -> Result<Vec<Xyz>> {
989 if tile.z == self.minzoom() {
990 return Ok(vec![]);
991 }
992
993 if let Some(zoom) = zoom {
994 if tile.z <= zoom {
995 return Err(TmsError::InvalidZoom(zoom));
997 }
998 } else if tile.z == 0 {
999 return Err(TmsError::InvalidZoom(0));
1000 }
1001
1002 let target_zoom = match zoom {
1003 Some(zoom) => zoom,
1004 None => tile.z - 1,
1005 };
1006
1007 let res = self.resolution(self.matrix(tile.z).as_ref()) / 10.0;
1008
1009 let bbox = self.xy_bounds(tile);
1010 let ul_tile = self.xy_tile(bbox.left + res, bbox.top - res, target_zoom);
1011 let lr_tile = self.xy_tile(bbox.right - res, bbox.bottom + res, target_zoom);
1012
1013 let mut tiles = Vec::new();
1014 for i in ul_tile.x..=lr_tile.x {
1015 for j in ul_tile.y..=lr_tile.y {
1016 tiles.push(Xyz::new(i, j, target_zoom));
1017 }
1018 }
1019
1020 Ok(tiles)
1021 }
1022
1023 pub fn children(&self, tile: &Xyz, zoom: Option<u8>) -> Result<Vec<Xyz>> {
1032 let mut tiles = Vec::new();
1033
1034 if let Some(zoom) = zoom {
1035 if tile.z > zoom {
1036 return Err(TmsError::InvalidZoom(zoom));
1038 }
1039 }
1040
1041 let target_zoom = match zoom {
1042 Some(z) => z,
1043 None => tile.z + 1,
1044 };
1045
1046 let bbox = self.xy_bounds(tile);
1047 let res = self.resolution(self.matrix(tile.z).as_ref()) / 10.0;
1048
1049 let ul_tile = self.xy_tile(bbox.left + res, bbox.top - res, target_zoom);
1050 let lr_tile = self.xy_tile(bbox.right - res, bbox.bottom + res, target_zoom);
1051
1052 for i in ul_tile.x..=lr_tile.x {
1053 for j in ul_tile.y..=lr_tile.y {
1054 tiles.push(Xyz::new(i, j, target_zoom));
1055 }
1056 }
1057
1058 Ok(tiles)
1059 }
1060
1061 fn transform_error_to_geographic(&self) -> TmsError {
1062 TransformationUnsupported(self.data_crs.clone(), self.geographic_crs.clone()).into()
1063 }
1064
1065 fn transform_error_from_geographic(&self) -> TmsError {
1066 TransformationUnsupported(self.geographic_crs.clone(), self.data_crs.clone()).into()
1067 }
1068}
1069
1070#[derive(Debug)]
1071pub(crate) struct MinMax {
1072 pub x_min: u64,
1073 pub x_max: u64,
1074 pub y_min: u64,
1075 pub y_max: u64,
1076}
1077
1078impl TryFrom<&TileMatrixSet> for Tms {
1079 type Error = TmsError;
1080 fn try_from(tms: &TileMatrixSet) -> Result<Tms> {
1081 Tms::init(tms)
1082 }
1083}
1084
1085fn transformed_bbox(extent: &[f64], crs: &Crs, extent_crs: Option<&Crs>) -> Result<BoundingBox> {
1086 let (mut left, mut bottom, mut right, mut top) = (extent[0], extent[1], extent[2], extent[3]);
1087 if let Some(extent_crs) = extent_crs {
1088 if extent_crs != crs {
1089 let transform = Transformer::from_crs(extent_crs, crs, true)?;
1090 (left, bottom, right, top) =
1091 transform.transform_bounds(left, bottom, right, top )?;
1092 }
1093 }
1094 Ok(BoundingBox::new(left, bottom, right, top))
1095}
1096
1097pub fn meters_per_unit(crs: &Crs) -> f64 {
1106 const SEMI_MAJOR_METRE: f64 = 6378137.0; let unit_name = if crs.as_srid() == 4326 {
1108 "degree" } else {
1110 "metre"
1111 };
1112 match unit_name {
1113 "metre" => 1.0,
1114 "degree" => 2.0 * PI * SEMI_MAJOR_METRE / 360.0,
1115 "foot" => 0.3048,
1116 "US survey foot" => 0.30480060960121924,
1117 _ => panic!(
1118 "CRS {crs:?} with Unit Name `{}` is not supported",
1119 unit_name
1120 ),
1121 }
1122}
1123
1124const LL_EPSILON: f64 = 1e-11;
1125
1126pub const DEFAULT_BBOX_PREC: u8 = 5;
1127
1128pub fn point_in_bbox(point: Coords, bbox: BoundingBox, precision: u8 ) -> Result<()> {
1130 fn round_to_prec(number: f64, precision: u8) -> f64 {
1131 let factor = 10.0_f64.powi(precision as i32);
1132 (number * factor).round() / factor
1133 }
1134 let inside = round_to_prec(point.x, precision) >= round_to_prec(bbox.left, precision)
1135 && round_to_prec(point.x, precision) <= round_to_prec(bbox.right, precision)
1136 && round_to_prec(point.y, precision) >= round_to_prec(bbox.bottom, precision)
1137 && round_to_prec(point.y, precision) <= round_to_prec(bbox.top, precision);
1138 if inside {
1139 Ok(())
1140 } else {
1141 Err(TmsError::PointOutsideBounds(point.x, point.y, bbox))
1142 }
1143}