Skip to main content

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}