utiles_core/tile.rs
1//! XYZ-Tile core struct and methods
2use std::cmp::Ordering;
3use std::convert::TryFrom;
4use std::error::Error;
5use std::str::FromStr;
6
7use serde::{Deserialize, Serialize};
8use serde_json::{Map, Value};
9
10use crate::constants::EPSILON;
11use crate::errors::{UtilesCoreError, UtilesCoreResult};
12use crate::fns::{bounds, children, parent, siblings, xy};
13#[cfg(feature = "pmtiles")]
14use crate::pmtiles;
15use crate::projection::Projection;
16use crate::tile_feature::TileFeature;
17use crate::tile_like::TileLike;
18use crate::traits::TileParent;
19use crate::{
20 IsOk, TileChildren1, children_zorder, children1_zorder, quadkey2tile, rmid2xyz,
21 utile, xyz2quadkey,
22};
23
24/// Tile X-Y-Z struct
25#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
26pub struct Tile {
27 /// x value (column)
28 pub x: u32,
29
30 /// y value (row -- flipped in mbtiles)
31 pub y: u32,
32
33 /// z value (zoom level)
34 pub z: u8,
35}
36
37/// Geometry for tile-feature (polygon or linestring)
38#[derive(Debug, Serialize, Deserialize)]
39pub struct TileFeatureGeometry {
40 /// type of geometry (Polygon or `LineString`)
41 #[serde(rename = "type")]
42 pub type_: String,
43
44 /// coordinates for the geometry [ [ [x1, y1], [x2, y2], ... ] ]
45 pub coordinates: Vec<Vec<Vec<f64>>>,
46}
47
48/// Options for creating a tile-feature
49#[derive(Debug, Serialize)]
50pub struct FeatureOptions {
51 /// feature id to use
52 pub fid: Option<String>,
53
54 /// `GeoJSON` properties to use
55 pub props: Option<Map<String, Value>>,
56
57 /// projection to use
58 pub projection: Projection,
59
60 /// buffer size to use
61 pub buffer: Option<f64>,
62
63 /// precision to use (number of decimal places)
64 pub precision: Option<i32>,
65}
66
67impl Default for FeatureOptions {
68 fn default() -> Self {
69 Self {
70 fid: None,
71 props: None,
72 projection: Projection::Geographic,
73 buffer: None,
74 precision: None,
75 }
76 }
77}
78
79impl std::fmt::Display for Tile {
80 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
81 write!(f, "x{}y{}z{}", self.x, self.y, self.z)
82 }
83}
84
85impl PartialOrd<Self> for Tile {
86 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
87 Some(self.cmp(other))
88 }
89
90 fn lt(&self, other: &Self) -> bool {
91 self.cmp(other) == Ordering::Less
92 }
93}
94
95impl Ord for Tile {
96 fn cmp(&self, other: &Self) -> Ordering {
97 (self.z, self.x, self.y).cmp(&(other.z, other.x, other.y))
98 }
99}
100
101impl FromStr for Tile {
102 type Err = Box<dyn Error>;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 // if it starts with '{' assume json obj
106 if s.starts_with('{') {
107 // if '{' assume its an obj
108 let r = Self::from_json_obj(s);
109 match r {
110 Ok(tile) => Ok(tile),
111 Err(_e) => {
112 Err(Box::from(UtilesCoreError::TileParseError(s.to_string())))
113 }
114 }
115 } else if s.starts_with('[') {
116 // if '[' assume its an arr
117 let r = Self::from_json_arr(s);
118 match r {
119 Ok(tile) => Ok(tile),
120 Err(_e) => {
121 Err(Box::from(UtilesCoreError::TileParseError(s.to_string())))
122 }
123 }
124 } else {
125 Err(Box::from(UtilesCoreError::TileParseError(s.to_string())))
126 }
127 }
128}
129impl TileLike for &Tile {
130 fn x(&self) -> u32 {
131 self.x
132 }
133
134 fn y(&self) -> u32 {
135 self.y
136 }
137
138 fn z(&self) -> u8 {
139 self.z
140 }
141}
142
143impl TileLike for Tile {
144 fn x(&self) -> u32 {
145 self.x
146 }
147
148 fn y(&self) -> u32 {
149 self.y
150 }
151
152 fn z(&self) -> u8 {
153 self.z
154 }
155}
156
157impl TileParent for Tile {
158 fn parent(&self, zoom: Option<u8>) -> Option<Self> {
159 self.parent(zoom)
160 }
161
162 fn root() -> Self {
163 utile!(0, 0, 0)
164 }
165}
166
167impl TileChildren1 for Tile {
168 fn children1(&self) -> [Self; 4] {
169 self.children1()
170 }
171}
172
173impl Tile {
174 /// Create a new Tile without checking validity
175 #[must_use]
176 pub fn new(x: u32, y: u32, z: u8) -> Self {
177 debug_assert!(
178 x < (1u32 << z) && y < (1u32 << z),
179 "Tile indices must satisfy 0 <= x < 2^z and 0 <= y < 2^z: (x={x}, y={y}, z={z})",
180 );
181 Self { x, y, z }
182 }
183
184 /// Create a Tile with validity check
185 ///
186 /// # Errors
187 ///
188 /// Returns an error if the tile is invalid (e.g. x or y >= 2^z)
189 pub fn try_new(x: u32, y: u32, z: u8) -> UtilesCoreResult<Self> {
190 let max_xy = 2_u32.pow(u32::from(z));
191 if x >= max_xy || y >= max_xy {
192 Err(UtilesCoreError::InvalidTile(format!(
193 "(x={x},y={y},z={z}) x < 2^z and y < 2^z",
194 )))
195 } else {
196 Ok(Self::new_unchecked(x, y, z))
197 }
198 }
199
200 /// Construct a tile with validity check - returns `Option<Tile>`
201 #[must_use]
202 pub const fn new_checked(x: u32, y: u32, z: u8) -> Option<Self> {
203 if x < (1u32 << z) && y < (1u32 << z) {
204 Some(Self::new_unchecked(x, y, z))
205 } else {
206 None
207 }
208 }
209
210 /// Construct a tile **without** any checking.
211 #[must_use]
212 pub const fn new_unchecked(x: u32, y: u32, z: u8) -> Self {
213 Self { x, y, z }
214 }
215
216 /// flip the y value (row) and return flipped tile
217 #[must_use]
218 pub fn flip(&self) -> Self {
219 Self::new(self.x, self.flipy(), self.z)
220 }
221
222 /// Return bounds tuple (west, south, east, north) for the tile
223 #[must_use]
224 pub fn bounds(&self) -> (f64, f64, f64, f64) {
225 bounds(self.x, self.y, self.z)
226 }
227
228 /// Return tile from row-major tile-id
229 #[must_use]
230 pub fn from_row_major_id(id: u64) -> Self {
231 Self::from(rmid2xyz(id))
232 }
233
234 /// Return tile from row-major tile-id (alias for `from_row_major_id`)
235 #[must_use]
236 pub fn from_rmid(id: u64) -> Self {
237 Self::from_row_major_id(id)
238 }
239
240 /// Return zxy string with optional separator (default is '/')
241 #[must_use]
242 pub fn fmt_zxy(&self, sep: Option<&str>) -> String {
243 sep.map_or_else(
244 || format!("{}/{}/{}", self.z, self.x, self.y),
245 |sep| format!("{}{}{}{}{}", self.z, sep, self.x, sep, self.y),
246 )
247 }
248
249 /// Return zxy string with extension and optional separator (default is '/')
250 ///
251 /// # Examples
252 /// ```
253 /// use utiles_core::Tile;
254 /// let tile = Tile::new(1, 2, 3);
255 /// assert_eq!(tile.fmt_zxy_ext("png", Some("-")), "3-1-2.png");
256 /// assert_eq!(tile.fmt_zxy_ext("png", None), "3/1/2.png");
257 /// ```
258 #[must_use]
259 pub fn fmt_zxy_ext(&self, ext: &str, sep: Option<&str>) -> String {
260 sep.map_or_else(
261 || format!("{}/{}/{}.{}", self.z, self.x, self.y, ext),
262 |sep| format!("{}{}{}{}{}.{}", self.z, sep, self.x, sep, self.y, ext),
263 )
264 }
265
266 /// Convert quadkey string to Tile
267 ///
268 /// # Errors
269 ///
270 /// Returns error on invalid quadkey (e.g. "1234" -- oh no '4' is invalid)
271 pub fn from_quadkey(quadkey: &str) -> UtilesCoreResult<Self> {
272 quadkey2tile(quadkey)
273 }
274
275 /// Convert quadkey string to Tile (alias for `from_quadkey`)
276 ///
277 /// # Errors
278 ///
279 /// Returns error on invalid quadkey (e.g. "1234" -- oh no '4' is invalid)
280 pub fn from_qk(qk: &str) -> UtilesCoreResult<Self> {
281 quadkey2tile(qk)
282 }
283
284 /// Return tile from json-object string (e.g. `{"x": 1, "y": 2, "z": 3}`)
285 ///
286 /// # Errors
287 ///
288 /// Returns error if serde parsing fails
289 ///
290 /// # Examples
291 /// ```
292 /// use utiles_core::Tile;
293 /// let tile = Tile::from_json_obj(r#"{"x": 1, "y": 2, "z": 3}"#).unwrap();
294 /// assert_eq!(tile, Tile::new(1, 2, 3));
295 /// ```
296 pub fn from_json_obj(json: &str) -> UtilesCoreResult<Self> {
297 let res = serde_json::from_str::<Self>(json);
298 match res {
299 Ok(tile) => Ok(tile),
300 Err(_e) => Err(UtilesCoreError::TileParseError(json.to_string())),
301 }
302 }
303
304 /// Return tile from json-array string (e.g. `[1, 2, 3]`)
305 ///
306 /// # Errors
307 ///
308 /// Returns error if serde parsing fails
309 ///
310 /// # Examples
311 /// ```
312 /// use utiles_core::Tile;
313 /// let tile = Tile::from_json_arr("[1, 2, 3]").unwrap();
314 /// assert_eq!(tile, Tile::new(1, 2, 3));
315 /// ```
316 pub fn from_json_arr(json: &str) -> UtilesCoreResult<Self> {
317 let res = serde_json::from_str::<(u32, u32, u8)>(json);
318 match res {
319 Ok((x, y, z)) => Ok(Self::new(x, y, z)),
320 Err(_e) => Err(UtilesCoreError::TileParseError(json.to_string())),
321 }
322 }
323
324 /// Return tile from json string either object or array
325 ///
326 /// # Errors
327 ///
328 /// Returns error if serde parsing fails
329 ///
330 /// # Examples
331 ///
332 /// ```
333 /// use utiles_core::Tile;
334 /// let tile_from_obj = Tile::from_json(r#"{"x": 1, "y": 2, "z": 3}"#).unwrap();
335 /// assert_eq!(tile_from_obj, Tile::new(1, 2, 3));
336 /// let tile_from_arr = Tile::from_json(r#"[1, 2, 3]"#).unwrap();
337 /// assert_eq!(tile_from_arr, Tile::new(1, 2, 3));
338 /// ```
339 pub fn from_json(json: &str) -> Result<Self, UtilesCoreError> {
340 let json_no_space = if json.starts_with(' ') {
341 json.trim()
342 } else {
343 json
344 };
345 if json_no_space.starts_with('{') {
346 Self::from_json_obj(json_no_space)
347 } else {
348 Self::from_json_arr(json_no_space)
349 }
350 }
351
352 /// Return tile from json string either object or array
353 ///
354 /// # Errors
355 ///
356 /// Returns error if unable to parse json string
357 pub fn from_json_loose(json: &str) -> UtilesCoreResult<Self> {
358 let v = serde_json::from_str::<Value>(json)?;
359 let t = Self::try_from(&v)?;
360 Ok(t)
361 }
362
363 /// Return the quadkey for the tile
364 #[must_use]
365 pub fn quadkey(&self) -> String {
366 xyz2quadkey(self.x, self.y, self.z)
367 }
368
369 /// Return the quadkey for the tile (alias for quadkey)
370 #[must_use]
371 pub fn qk(&self) -> String {
372 xyz2quadkey(self.x, self.y, self.z)
373 }
374
375 /// Return new Tile from given (lng, lat, zoom)
376 ///
377 /// # Errors
378 ///
379 /// Returns an error if the conversion fails resulting in invalid tile
380 #[expect(clippy::used_underscore_items)]
381 #[expect(clippy::cast_possible_truncation)]
382 pub fn from_lnglat_zoom(
383 lng: f64,
384 lat: f64,
385 zoom: u8,
386 truncate: Option<bool>,
387 ) -> UtilesCoreResult<Self> {
388 let (x, y) = crate::_xy(lng, lat, truncate)?;
389 let z2 = 2.0_f64.powi(i32::from(zoom));
390 let z2f = z2;
391
392 let xtile = if x <= 0.0 {
393 0
394 } else if x >= 1.0 {
395 u32::try_from((z2 - 1.0).floor() as i64).unwrap_or(0)
396 } else {
397 let xt = (x + EPSILON) * z2f;
398 u32::try_from(xt.floor() as i64).unwrap_or(0)
399 };
400
401 let ytile = if y <= 0.0 {
402 0
403 } else if y >= 1.0 {
404 u32::try_from((z2 - 1.0).floor() as i64).unwrap_or(0)
405 } else {
406 let yt = (y + EPSILON) * z2f;
407 u32::try_from(yt.floor() as i64).unwrap_or(0)
408 };
409
410 Ok(Self {
411 x: xtile,
412 y: ytile,
413 z: zoom,
414 })
415 }
416
417 /// Return the bounding box of the tile
418 #[must_use]
419 pub const fn up(&self) -> Self {
420 Self {
421 x: self.x + 1,
422 y: self.y,
423 z: self.z,
424 }
425 }
426
427 /// Return the tile to the bottom
428 #[must_use]
429 pub const fn down(&self) -> Self {
430 Self {
431 x: self.x - 1,
432 y: self.y,
433 z: self.z,
434 }
435 }
436
437 /// Return the tile to the left
438 #[must_use]
439 pub const fn left(&self) -> Self {
440 Self {
441 x: self.x,
442 y: self.y - 1,
443 z: self.z,
444 }
445 }
446
447 /// Return the tile to the right
448 #[must_use]
449 pub const fn right(&self) -> Self {
450 Self {
451 x: self.x,
452 y: self.y + 1,
453 z: self.z,
454 }
455 }
456
457 /// Return the tile to the top left
458 #[must_use]
459 pub const fn up_left(&self) -> Self {
460 Self {
461 x: self.x + 1,
462 y: self.y - 1,
463 z: self.z,
464 }
465 }
466
467 /// Return the tile to the top right
468 #[must_use]
469 pub const fn up_right(&self) -> Self {
470 Self {
471 x: self.x + 1,
472 y: self.y + 1,
473 z: self.z,
474 }
475 }
476
477 /// Return the tile to the bottom left
478 #[must_use]
479 pub const fn down_left(&self) -> Self {
480 Self {
481 x: self.x - 1,
482 y: self.y - 1,
483 z: self.z,
484 }
485 }
486
487 /// Return the tile to the bottom right
488 #[must_use]
489 pub const fn down_right(&self) -> Self {
490 Self {
491 x: self.x - 1,
492 y: self.y + 1,
493 z: self.z,
494 }
495 }
496
497 /// Return direct children
498 #[must_use]
499 pub fn children1(&self) -> [Self; 4] {
500 children1_zorder(self.x, self.y, self.z)
501 }
502
503 /// Return the children tiles of the tile
504 #[must_use]
505 pub fn children(&self, zoom: Option<u8>) -> Vec<Self> {
506 children(self.x, self.y, self.z, zoom)
507 }
508
509 /// Return the children tiles of the tile
510 #[must_use]
511 pub fn children_zorder(&self, zoom: Option<u8>) -> Vec<Self> {
512 children_zorder(self.x, self.y, self.z, zoom)
513 }
514
515 /// Return the parent tile
516 #[must_use]
517 pub fn parent(&self, zoom: Option<u8>) -> Option<Self> {
518 parent(self.x, self.y, self.z, zoom)
519 }
520
521 /// Return sibling tiles that share the same parent tile (not neighbors)
522 #[must_use]
523 pub fn siblings(&self) -> Vec<Self> {
524 siblings(self.x, self.y, self.z)
525 }
526
527 /// Return a `TileFeature` for the tile
528 ///
529 /// # Errors
530 ///
531 /// Returns an error if the feature creation fails (which may be impossible [2024-08-14])
532 pub fn feature(&self, opts: &FeatureOptions) -> UtilesCoreResult<TileFeature> {
533 let buffer = opts.buffer.unwrap_or(0.0);
534 let precision = opts.precision.unwrap_or(-1);
535 // Compute the bounds
536 let (west, south, east, north) = self.bbox();
537 // Handle projected coordinates
538 let (mut west, mut south, mut east, mut north) = match opts.projection {
539 // Projection::Geographic=> (west, south, east, north),
540 Projection::Mercator => {
541 // let (east_merc, north_merc) = utiles_core::xy(east, north, Some(false));
542 let (west_merc, south_merc) = xy(west, south, None);
543 let (east_merc, north_merc) = xy(east, north, None);
544 (west_merc, south_merc, east_merc, north_merc)
545 }
546 Projection::Geographic => (west, south, east, north),
547 };
548
549 // Apply buffer
550 west -= buffer;
551 south -= buffer;
552 east += buffer;
553 north += buffer;
554
555 // Apply precision
556 if precision >= 0 {
557 let precision_factor = 10_f64.powi(precision);
558 west = (west * precision_factor).round() / precision_factor;
559 south = (south * precision_factor).round() / precision_factor;
560 east = (east * precision_factor).round() / precision_factor;
561 north = (north * precision_factor).round() / precision_factor;
562 }
563
564 // Compute bbox and geometry
565 let bbox = (
566 west.min(east),
567 south.min(north),
568 west.max(east),
569 south.max(north),
570 );
571 let xyz = self.tuple_string();
572 let geometry_coordinates = vec![vec![
573 vec![west, south],
574 vec![east, south],
575 vec![east, north],
576 vec![west, north],
577 vec![west, south],
578 ]];
579 let mut properties: Map<String, Value> = Map::new();
580 properties.insert("title".to_string(), Value::from(format!("XYZ tile {xyz}")));
581 properties.extend(opts.props.clone().unwrap_or_default());
582 let id = opts.fid.clone().unwrap_or(xyz);
583 let tile_feature = TileFeature {
584 id,
585 type_: "Feature".to_string(),
586 geometry: TileFeatureGeometry {
587 type_: "Polygon".to_string(),
588 coordinates: geometry_coordinates,
589 },
590 bbox,
591 properties,
592 };
593 Ok(tile_feature)
594 }
595}
596
597#[cfg(feature = "pmtiles")]
598impl Tile {
599 /// Return pmtile-id for the tile
600 #[must_use]
601 pub fn pmtileid(&self) -> u64 {
602 pmtiles::xyz2pmid(self.x, self.y, self.z)
603 }
604
605 /// Return tile from pmtile-id
606 #[must_use]
607 pub fn from_pmtileid(id: u64) -> Self {
608 pmtiles::pmid2xyz(id).into()
609 }
610
611 /// Return tile from pmtile-id (alias for `from_pmtileid`)
612 #[must_use]
613 pub fn from_pmid(id: u64) -> Self {
614 pmtiles::pmid2xyz(id).into()
615 }
616
617 /// Return the parent tile's pmtile-id
618 #[must_use]
619 pub fn parent_pmtileid(&self) -> Option<u64> {
620 self.parent(None).map(|t| Self::pmtileid(&t))
621 }
622}
623impl IsOk for Tile {
624 fn ok(&self) -> UtilesCoreResult<Self> {
625 if self.z > 30 {
626 Err(UtilesCoreError::InvalidTile(format!(
627 "({},{},{}) 0 <= zoom <= 30",
628 self.x, self.y, self.z
629 )))
630 } else {
631 let z2 = 2_u32.pow(u32::from(self.z));
632 if self.x >= z2 || self.y >= z2 {
633 Err(UtilesCoreError::InvalidTile(format!(
634 "({},{},{}) x < 2^z and y < 2^z",
635 self.x, self.y, self.z
636 )))
637 } else {
638 Ok(*self)
639 }
640 }
641 }
642}
643//
644// impl TryFrom<&Map<String, Value>> for Tile {
645// type Error = UtilesCoreError;
646//
647// fn try_from(map: &Map<String, Value>) -> Result<Self, Self::Error> {
648// let x = u32::try_from(map["x"].as_u64().ok_or_else(|| {
649// UtilesCoreError::InvalidJson(
650// serde_json::to_string(&map).unwrap_or_default(),
651// )
652// })?)
653// .map_err(|_| {
654// UtilesCoreError::InvalidJson(
655// serde_json::to_string(&map).unwrap_or_default(),
656// )
657// })?;
658// let y = u32::try_from(map["y"].as_u64().ok_or_else(|| {
659// UtilesCoreError::InvalidJson(
660// serde_json::to_string(&map).unwrap_or_default(),
661// )
662// })?)
663// .map_err(|_| {
664// UtilesCoreError::InvalidJson(
665// serde_json::to_string(&map).unwrap_or_default(),
666// )
667// })?;
668// let z = u8::try_from(map["z"].as_u64().ok_or_else(|| {
669// UtilesCoreError::InvalidJson(
670// serde_json::to_string(&map).unwrap_or_default(),
671// )
672// })?)
673// .map_err(|_| {
674// UtilesCoreError::InvalidJson(
675// serde_json::to_string(&map).unwrap_or_default(),
676// )
677// })?;
678// Ok(Tile::new(x, y, z))
679// }
680// }
681//
682// impl TryFrom<&Vec<Value>> for Tile {
683// type Error = UtilesCoreError;
684//
685// fn try_from(arr: &Vec<Value>) -> Result<Self, Self::Error> {
686// if arr.len() < 3 {
687// Err(UtilesCoreError::InvalidJson(
688// serde_json::to_string(&arr).unwrap_or_default(),
689// ))
690// } else {
691// let x = arr[0].as_u64().ok_or_else(|| {
692// UtilesCoreError::InvalidJson(
693// serde_json::to_string(&arr).unwrap_or_default(),
694// )
695// })?;
696// let y = arr[1].as_u64().ok_or_else(|| {
697// UtilesCoreError::InvalidJson(
698// serde_json::to_string(&arr).unwrap_or_default(),
699// )
700// })?;
701// let z = arr[2].as_u64().ok_or_else(|| {
702// UtilesCoreError::InvalidJson(
703// serde_json::to_string(&arr).unwrap_or_default(),
704// )
705// })?;
706// Tile::try_from((x, y, z))
707// }
708// }
709// }
710//
711// impl TryFrom<&Value> for Tile {
712// type Error = UtilesCoreError;
713//
714// fn try_from(val: &Value) -> Result<Self, Self::Error> {
715// match val {
716// Value::Array(v) => {
717// let t = Tile::try_from(v)?;
718// Ok(t)
719// }
720// Value::Object(v) => {
721// if v.contains_key("x") && v.contains_key("y") && v.contains_key("z") {
722// let x = v["x"].as_u64().ok_or_else(|| {
723// UtilesCoreError::InvalidJson(
724// serde_json::to_string(&v)
725// .expect("Invalid json object for Tile from Value"),
726// )
727// })?;
728// let y = v["y"].as_u64().ok_or_else(|| {
729// UtilesCoreError::InvalidJson(
730// serde_json::to_string(&v)
731// .expect("Invalid json object for Tile from Value"),
732// )
733// })?;
734// let z = v["z"].as_u64().ok_or_else(|| {
735// UtilesCoreError::InvalidJson(
736// serde_json::to_string(&v)
737// .expect("Invalid json object for Tile from Value"),
738// )
739// })?;
740// Tile::try_from((x, y, z))
741// } else if v.contains_key("tile")
742// && v["tile"].is_array()
743// && v["tile"]
744// .as_array()
745// .expect("Unable to get tile array from Value")
746// .len()
747// == 3
748// {
749// let tuple = serde_json::from_value::<TileTuple>(v["tile"].clone())?;
750// Ok(Tile::from(tuple))
751// } else {
752// Err(UtilesCoreError::InvalidJson(
753// serde_json::to_string(&v)
754// .expect("Invalid json object for Tile from Value"),
755// ))
756// }
757// }
758// _ => Err(UtilesCoreError::InvalidJson(val.to_string())),
759// }
760// }
761// }
762
763// impl From<Value> for Tile {
764// fn from(val: Value) -> Self {
765// Tile::from(&val)
766// }
767// }
768
769#[cfg(test)]
770mod tests {
771 #![expect(clippy::unwrap_used)]
772
773 use super::*;
774
775 #[test]
776 fn parse_json_obj() {
777 let json_obj = r#"{"x": 1, "y": 2, "z": 3}"#;
778 let tile = Tile::from_json_obj(json_obj).unwrap();
779 assert_eq!(tile, Tile::new(1, 2, 3));
780 }
781
782 #[test]
783 fn parse_json_arr() {
784 let json_arr = r"[1, 2, 3]";
785 let tile = Tile::from_json_arr(json_arr).unwrap();
786 assert_eq!(tile, Tile::new(1, 2, 3));
787 }
788
789 #[test]
790 fn parse_quadkey() {
791 let quadkey = "023010203";
792 let tile = Tile::from_quadkey(quadkey).unwrap();
793 assert_eq!(tile, Tile::new(81, 197, 9));
794 }
795
796 #[test]
797 fn tile_from_value_obj() {
798 let json_obj = r#"{"x": 1, "y": 2, "z": 3}"#;
799 let val_obj = serde_json::from_str::<Value>(json_obj).unwrap();
800 let tile_from_obj = Tile::try_from(val_obj).unwrap();
801 assert_eq!(tile_from_obj, Tile::new(1, 2, 3));
802 }
803
804 #[test]
805 fn tile_from_value_arr() {
806 let json_arr = r"[1, 2, 3]";
807 let val_arr = serde_json::from_str::<Value>(json_arr).unwrap();
808 let tile_from_arr = Tile::try_from(val_arr).unwrap();
809 assert_eq!(tile_from_arr, Tile::new(1, 2, 3));
810 }
811
812 #[test]
813 fn tile_from_value_obj_with_array() {
814 let json_obj_with_tile_array = r#"{"tile": [1, 2, 3]}"#;
815 let val_obj_with_tile_array =
816 serde_json::from_str::<Value>(json_obj_with_tile_array).unwrap();
817 let tile_from_obj_with_tile_array =
818 Tile::try_from(val_obj_with_tile_array).unwrap();
819 assert_eq!(tile_from_obj_with_tile_array, Tile::new(1, 2, 3));
820 }
821}