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}