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