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