s2_tilejson/
lib.rs

1#![no_std]
2#![forbid(unsafe_code)]
3#![deny(missing_docs)]
4#![cfg_attr(docsrs, feature(doc_cfg))]
5
6//! # S2 Tile JSON 🌎 πŸ—ΊοΈ
7//!
8//! ```text                                                                            
9//!                       .d8888. .d888b.                        
10//!                       88'  YP VP  `8D                        
11//!                       `8bo.      odD'                        
12//!                         `Y8b.  .88'                          
13//!                       db   8D j88.                           
14//!                       `8888Y' 888888D                        
15//!                                                              
16//!                                                              
17//!              d888888b d888888b db      d88888b               
18//!              `~~88~~'   `88'   88      88'                   
19//!                 88       88    88      88ooooo               
20//!                 88       88    88      88~~~~~               
21//!                 88      .88.   88booo. 88.                   
22//!                 YP    Y888888P Y88888P Y88888P               
23//!                                                              
24//!                                                              
25//!                 d88b .d8888.  .d88b.  d8b   db               
26//!                 `8P' 88'  YP .8P  Y8. 888o  88               
27//!                  88  `8bo.   88    88 88V8o 88               
28//!                  88    `Y8b. 88    88 88 V8o88               
29//!              db. 88  db   8D `8b  d8' 88  V888               
30//!              Y8888P  `8888Y'  `Y88P'  VP   V8P                                                                                                                               
31//! ```
32//!
33//! ## Install
34//!
35//! ```bash
36//! cargo add s2-tilejson
37//! ```
38//!
39//! ## About
40//!
41//! TileJSON is a [backwards-compatible](https://github.com/mapbox/tilejson-spec) open standard for
42//! representing map tile metadata.
43//!
44//! This specification attempts to create a standard for representing metadata about multiple
45//! types of web-based map layers, to aid clients in configuration and browsing.
46//!
47//! ## Usage
48//!
49//! The Documentation is very thorough in this library. Therefore the best thing to do is follow
50//! the links provided as needed.
51//!
52//! ### Tools
53//!
54//! - [`crate::MetadataBuilder`]: Build a Metadata from scratch. Helper tool when constructing a set of tiles
55//!
56//! ### Top Level Types
57//!
58//! - [`crate::Metadata`]: Represents a TileJSON metadata object for the new S2 spec.
59//! - [`crate::MapboxTileJSONMetadata`]: Represents a TileJSON metadata object for the old Mapbox spec.
60//! - [`crate::UnknownMetadata`]: If we don't know which spec we are reading, we can treat the input as either.
61//!
62//! ### Sub Types
63//!
64//! - [`crate::LonLatBounds`]: Use bounds as floating point numbers for longitude and latitude
65//! - [`crate::TileBounds`]: Use bounds as u64 for the tile index range
66//! - [`crate::DrawType`]: Description of what kind of data is in the tile
67//! - [`crate::LayerMetaData`]: Each layer has metadata associated with it. Defined as blueprints pre-construction of vector data.
68//! - [`crate::TileStatsMetadata`]: Tilestats is simply a tracker to see where most of the tiles live
69//! - [`crate::Attributions`]: Attribution data is stored in an object. The key is the name of the attribution, and the value is the link
70//! - [`crate::FaceBounds`]: Track the S2 tile bounds of each face and zoom
71//! - [`crate::WMBounds`]: Track the WM tile bounds of each zoom `[zoom: number]: BBox`
72//! - [`crate::SourceType`]: Check the source type of the layer
73//! - [`crate::Encoding`]: Store the encoding of the data
74//! - [`crate::VectorLayer`]: Old spec tracks basic vector data
75//! - [`crate::Scheme`]: Default S2 tile scheme is fzxy Default Web Mercator tile scheme is xyz Adding a t prefix to the scheme will change the request to be time sensitive TMS is an oudated version that is not supported by s2maps-gpu
76//! - [`crate::Center`]: Store where the center of the data lives
77
78extern crate alloc;
79
80use alloc::{
81    borrow::ToOwned,
82    boxed::Box,
83    collections::{BTreeMap, BTreeSet},
84    format,
85    string::String,
86    vec::Vec,
87};
88pub use s2json::*;
89use serde::{Deserialize, Deserializer, Serialize, Serializer};
90
91/// Use bounds as floating point numbers for longitude and latitude
92pub type LonLatBounds = BBox<f64>;
93
94/// Use bounds as u64 for the tile index range
95pub type TileBounds = BBox<u64>;
96
97/// 1: points, 2: lines, 3: polys, 4: points3D, 5: lines3D, 6: polys3D
98#[derive(Copy, Clone, Debug, PartialEq)]
99pub enum DrawType {
100    /// Collection of points
101    Points = 1,
102    /// Collection of lines
103    Lines = 2,
104    /// Collection of polygons
105    Polygons = 3,
106    /// Collection of 3D points
107    Points3D = 4,
108    /// Collection of 3D lines
109    Lines3D = 5,
110    /// Collection of 3D polygons
111    Polygons3D = 6,
112    /// Raster data
113    Raster = 7,
114    /// Collection of points
115    Grid = 8,
116}
117impl From<DrawType> for u8 {
118    fn from(draw_type: DrawType) -> Self {
119        draw_type as u8
120    }
121}
122impl From<u8> for DrawType {
123    fn from(draw_type: u8) -> Self {
124        match draw_type {
125            2 => DrawType::Lines,
126            3 => DrawType::Polygons,
127            4 => DrawType::Points3D,
128            5 => DrawType::Lines3D,
129            6 => DrawType::Polygons3D,
130            7 => DrawType::Raster,
131            8 => DrawType::Grid,
132            _ => DrawType::Points, // 1 and default
133        }
134    }
135}
136impl<D: MValueCompatible> From<&VectorGeometry<D>> for DrawType {
137    fn from(geometry: &VectorGeometry<D>) -> DrawType {
138        match geometry {
139            VectorGeometry::Point(p) => {
140                if p.is_3d {
141                    DrawType::Points3D
142                } else {
143                    DrawType::Points
144                }
145            }
146            VectorGeometry::MultiPoint(mp) => {
147                if mp.is_3d {
148                    DrawType::Points3D
149                } else {
150                    DrawType::Points
151                }
152            }
153            VectorGeometry::LineString(l) => {
154                if l.is_3d {
155                    DrawType::Lines3D
156                } else {
157                    DrawType::Lines
158                }
159            }
160            VectorGeometry::MultiLineString(ml) => {
161                if ml.is_3d {
162                    DrawType::Lines3D
163                } else {
164                    DrawType::Lines
165                }
166            }
167            VectorGeometry::Polygon(p) => {
168                if p.is_3d {
169                    DrawType::Polygons3D
170                } else {
171                    DrawType::Polygons
172                }
173            }
174            VectorGeometry::MultiPolygon(mp) => {
175                if mp.is_3d {
176                    DrawType::Polygons3D
177                } else {
178                    DrawType::Polygons
179                }
180            }
181        }
182    }
183}
184impl<M: Clone, P: MValueCompatible, D: MValueCompatible> From<&VectorFeature<M, P, D>>
185    for DrawType
186{
187    fn from(feature: &VectorFeature<M, P, D>) -> DrawType {
188        DrawType::from(&feature.geometry)
189    }
190}
191impl Serialize for DrawType {
192    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
193    where
194        S: Serializer,
195    {
196        // Serialize as u8
197        serializer.serialize_u8(*self as u8)
198    }
199}
200
201impl<'de> Deserialize<'de> for DrawType {
202    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
203    where
204        D: Deserializer<'de>,
205    {
206        // Deserialize from u8 or string
207        let value: u8 = Deserialize::deserialize(deserializer)?;
208        match value {
209            1 => Ok(DrawType::Points),
210            2 => Ok(DrawType::Lines),
211            3 => Ok(DrawType::Polygons),
212            4 => Ok(DrawType::Points3D),
213            5 => Ok(DrawType::Lines3D),
214            6 => Ok(DrawType::Polygons3D),
215            7 => Ok(DrawType::Raster),
216            8 => Ok(DrawType::Grid),
217            _ => Err(serde::de::Error::custom(format!("unknown DrawType variant: {value}"))),
218        }
219    }
220}
221
222/// Each layer has metadata associated with it. Defined as blueprints pre-construction of vector data.
223#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
224pub struct LayerMetaData {
225    /// The description of the layer
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub description: Option<String>,
228    /// the lowest zoom level at which the layer is available
229    pub minzoom: u8,
230    /// the highest zoom level at which the layer is available
231    pub maxzoom: u8,
232    /// The draw types that can be found in this layer
233    pub draw_types: Vec<DrawType>,
234    /// The shape that can be found in this layer
235    pub shape: Shape,
236    /// The shape used inside features that can be found in this layer
237    #[serde(skip_serializing_if = "Option::is_none", rename = "mShape")]
238    pub m_shape: Option<Shape>,
239}
240
241/// Each layer has metadata associated with it. Defined as blueprints pre-construction of vector data.
242pub type LayersMetaData = BTreeMap<String, LayerMetaData>;
243
244/// Tilestats is simply a tracker to see where most of the tiles live
245#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
246pub struct TileStatsMetadata {
247    /// total number of tiles
248    #[serde(default)]
249    pub total: u64,
250    /// number of tiles for face 0
251    #[serde(rename = "0", default)]
252    pub total_0: u64,
253    /// number of tiles for face 1
254    #[serde(rename = "1", default)]
255    pub total_1: u64,
256    /// number of tiles for face 2
257    #[serde(rename = "2", default)]
258    pub total_2: u64,
259    /// number of tiles for face 3
260    #[serde(rename = "3", default)]
261    pub total_3: u64,
262    /// number of tiles for face 4
263    #[serde(rename = "4", default)]
264    pub total_4: u64,
265    /// number of tiles for face 5
266    #[serde(rename = "5", default)]
267    pub total_5: u64,
268}
269impl TileStatsMetadata {
270    /// Access the total number of tiles for a given face
271    pub fn get(&self, face: Face) -> u64 {
272        match face {
273            Face::Face0 => self.total_0,
274            Face::Face1 => self.total_1,
275            Face::Face2 => self.total_2,
276            Face::Face3 => self.total_3,
277            Face::Face4 => self.total_4,
278            Face::Face5 => self.total_5,
279        }
280    }
281
282    /// Increment the total number of tiles for a given face and also the grand total
283    pub fn increment(&mut self, face: Face) {
284        match face {
285            Face::Face0 => self.total_0 += 1,
286            Face::Face1 => self.total_1 += 1,
287            Face::Face2 => self.total_2 += 1,
288            Face::Face3 => self.total_3 += 1,
289            Face::Face4 => self.total_4 += 1,
290            Face::Face5 => self.total_5 += 1,
291        }
292        self.total += 1;
293    }
294}
295
296/// Attribution data is stored in an object.
297/// The key is the name of the attribution, and the value is the link
298pub type Attributions = BTreeMap<String, String>;
299
300/// Track the S2 tile bounds of each face and zoom
301#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
302pub struct FaceBounds {
303    // s2bounds[face][zoom] = [...]
304    /// Tile bounds for face 0 at each zoom
305    #[serde(rename = "0")]
306    pub face0: BTreeMap<u8, TileBounds>,
307    /// Tile bounds for face 1 at each zoom
308    #[serde(rename = "1")]
309    pub face1: BTreeMap<u8, TileBounds>,
310    /// Tile bounds for face 2 at each zoom
311    #[serde(rename = "2")]
312    pub face2: BTreeMap<u8, TileBounds>,
313    /// Tile bounds for face 3 at each zoom
314    #[serde(rename = "3")]
315    pub face3: BTreeMap<u8, TileBounds>,
316    /// Tile bounds for face 4 at each zoom
317    #[serde(rename = "4")]
318    pub face4: BTreeMap<u8, TileBounds>,
319    /// Tile bounds for face 5 at each zoom
320    #[serde(rename = "5")]
321    pub face5: BTreeMap<u8, TileBounds>,
322}
323impl FaceBounds {
324    /// Access the tile bounds for a given face and zoom
325    pub fn get(&self, face: Face) -> &BTreeMap<u8, TileBounds> {
326        match face {
327            Face::Face0 => &self.face0,
328            Face::Face1 => &self.face1,
329            Face::Face2 => &self.face2,
330            Face::Face3 => &self.face3,
331            Face::Face4 => &self.face4,
332            Face::Face5 => &self.face5,
333        }
334    }
335
336    /// Access the mutable tile bounds for a given face and zoom
337    pub fn get_mut(&mut self, face: Face) -> &mut BTreeMap<u8, TileBounds> {
338        match face {
339            Face::Face0 => &mut self.face0,
340            Face::Face1 => &mut self.face1,
341            Face::Face2 => &mut self.face2,
342            Face::Face3 => &mut self.face3,
343            Face::Face4 => &mut self.face4,
344            Face::Face5 => &mut self.face5,
345        }
346    }
347}
348
349/// Track the WM tile bounds of each zoom
350/// `[zoom: number]: BBox`
351pub type WMBounds = BTreeMap<u8, TileBounds>;
352
353/// Check the source type of the layer
354#[derive(Serialize, Debug, Default, Clone, PartialEq)]
355#[serde(rename_all = "lowercase")]
356pub enum SourceType {
357    /// Vector data
358    #[default]
359    Vector,
360    /// Json data
361    Json,
362    /// Raster data
363    Raster,
364    /// Raster DEM data
365    #[serde(rename = "raster-dem")]
366    RasterDem,
367    /// Grid data
368    Grid,
369    /// Marker data
370    Markers,
371    /// Unknown source type
372    Unknown,
373}
374impl From<&str> for SourceType {
375    fn from(source_type: &str) -> Self {
376        match source_type {
377            "vector" => SourceType::Vector,
378            "json" => SourceType::Json,
379            "raster" => SourceType::Raster,
380            "raster-dem" => SourceType::RasterDem,
381            "grid" => SourceType::Grid,
382            "markers" => SourceType::Markers,
383            _ => SourceType::Unknown,
384        }
385    }
386}
387impl<'de> Deserialize<'de> for SourceType {
388    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
389    where
390        D: Deserializer<'de>,
391    {
392        // Deserialize from a string
393        let s: String = Deserialize::deserialize(deserializer)?;
394        Ok(SourceType::from(s.as_str()))
395    }
396}
397
398/// Store the encoding of the data
399#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
400#[serde(rename_all = "lowercase")]
401pub enum Encoding {
402    /// No encoding
403    #[default]
404    None = 0,
405    /// Gzip encoding
406    Gzip = 1,
407    /// Brotli encoding
408    #[serde(rename = "br")]
409    Brotli = 2,
410    /// Zstd encoding
411    Zstd = 3,
412}
413impl From<u8> for Encoding {
414    fn from(encoding: u8) -> Self {
415        match encoding {
416            1 => Encoding::Gzip,
417            2 => Encoding::Brotli,
418            3 => Encoding::Zstd,
419            _ => Encoding::None,
420        }
421    }
422}
423impl From<Encoding> for u8 {
424    fn from(encoding: Encoding) -> Self {
425        match encoding {
426            Encoding::Gzip => 1,
427            Encoding::Brotli => 2,
428            Encoding::Zstd => 3,
429            Encoding::None => 0,
430        }
431    }
432}
433impl From<Encoding> for &str {
434    fn from(encoding: Encoding) -> Self {
435        match encoding {
436            Encoding::Gzip => "gzip",
437            Encoding::Brotli => "br",
438            Encoding::Zstd => "zstd",
439            Encoding::None => "none",
440        }
441    }
442}
443impl From<&str> for Encoding {
444    fn from(encoding: &str) -> Self {
445        match encoding {
446            "gzip" => Encoding::Gzip,
447            "br" => Encoding::Brotli,
448            "zstd" => Encoding::Zstd,
449            _ => Encoding::None,
450        }
451    }
452}
453
454/// Old spec tracks basic vector data
455#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
456pub struct VectorLayer {
457    /// The id of the layer
458    pub id: String,
459    /// The description of the layer
460    #[serde(skip_serializing_if = "Option::is_none")]
461    pub description: Option<String>,
462    /// The min zoom of the layer
463    #[serde(skip_serializing_if = "Option::is_none")]
464    pub minzoom: Option<u8>,
465    /// The max zoom of the layer
466    #[serde(skip_serializing_if = "Option::is_none")]
467    pub maxzoom: Option<u8>,
468    /// Information about each field property
469    pub fields: BTreeMap<String, String>,
470}
471
472/// Default S2 tile scheme is `fzxy`
473/// Default Web Mercator tile scheme is `xyz`
474/// Adding a t prefix to the scheme will change the request to be time sensitive
475/// TMS is an oudated version that is not supported by s2maps-gpu
476#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
477#[serde(rename_all = "lowercase")]
478pub enum Scheme {
479    /// The default scheme with faces (S2)
480    #[default]
481    Fzxy,
482    /// The time sensitive scheme with faces (S2)
483    Tfzxy,
484    /// The basic scheme (Web Mercator)
485    Xyz,
486    /// The time sensitive basic scheme (Web Mercator)
487    Txyz,
488    /// The TMS scheme
489    Tms,
490}
491impl From<&str> for Scheme {
492    fn from(scheme: &str) -> Self {
493        match scheme {
494            "fzxy" => Scheme::Fzxy,
495            "tfzxy" => Scheme::Tfzxy,
496            "xyz" => Scheme::Xyz,
497            "txyz" => Scheme::Txyz,
498            _ => Scheme::Tms,
499        }
500    }
501}
502impl From<Scheme> for &str {
503    fn from(scheme: Scheme) -> Self {
504        match scheme {
505            Scheme::Fzxy => "fzxy",
506            Scheme::Tfzxy => "tfzxy",
507            Scheme::Xyz => "xyz",
508            Scheme::Txyz => "txyz",
509            Scheme::Tms => "tms",
510        }
511    }
512}
513
514/// Store where the center of the data lives
515#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
516pub struct Center {
517    /// The longitude of the center
518    pub lon: f64,
519    /// The latitude of the center
520    pub lat: f64,
521    /// The zoom of the center
522    pub zoom: u8,
523}
524
525/// # S2 TileJSON V1.0.0
526///
527/// ```rs
528/// let meta: Metadata =
529///   serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
530/// ```
531///
532/// Represents a TileJSON metadata object for the new S2 spec.
533///
534/// If you're unsure of which spec you're using, use the [`UnknownMetadata`] type.
535///
536/// ## Links
537/// [S2TileJSON Spec](https://github.com/Open-S2/s2-tilejson/tree/master/s2-tilejson-spec/1.0.0)
538#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
539#[serde(default)]
540pub struct Metadata {
541    /// The version of the s2-tilejson spec. Matches the pattern: `^\d+\.\d+\.\d+\w?[\w\d]*$`.
542    pub s2tilejson: String,
543    /// The version of the data. Matches the pattern: `^\d+\.\d+\.\d+\w?[\w\d]*$`.
544    pub version: String,
545    /// The name of the data
546    pub name: String,
547    /// The scheme of the data
548    pub scheme: Scheme,
549    /// The description of the data
550    pub description: String,
551    /// The type of the data
552    #[serde(rename = "type")]
553    pub r#type: SourceType,
554    /// The extension to use when requesting a tile
555    pub extension: String,
556    /// The encoding of the data
557    pub encoding: Encoding,
558    /// List of faces that have data
559    pub faces: Vec<Face>,
560    /// Bounding box array [west, south, east, north].
561    pub bounds: LonLatBounds,
562    /// WM Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist
563    pub wmbounds: WMBounds,
564    /// S2 Tile fetching bounds. Helpful to not make unecessary requests for tiles we know don't exist
565    pub s2bounds: FaceBounds,
566    /// minzoom at which to request tiles. [default=0]
567    pub minzoom: u8,
568    /// maxzoom at which to request tiles. [default=27]
569    pub maxzoom: u8,
570    /// The center of the data
571    pub centerpoint: Center,
572    /// { ['human readable string']: 'href' }
573    pub attributions: Attributions,
574    /// Track layer metadata
575    pub layers: LayersMetaData,
576    /// Track tile stats for each face and total overall
577    pub tilestats: TileStatsMetadata,
578    /// Old spec but required for functional compatibility, track basic layer metadata
579    pub vector_layers: Vec<VectorLayer>,
580
581    // Old spec
582    /// Version of the TileJSON spec used. Matches the pattern: `^\d+\.\d+\.\d+\w?[\w\d]*$`.
583    pub tilejson: Option<String>,
584    /// Array of tile URL templates.
585    #[serde(skip_serializing_if = "Option::is_none")]
586    pub tiles: Option<Vec<String>>,
587    /// Attribution string.
588    #[serde(skip_serializing_if = "Option::is_none")]
589    pub attribution: Option<String>,
590    /// Fill zoom level. Must be between 0 and 30.
591    #[serde(skip_serializing_if = "Option::is_none")]
592    pub fillzoom: Option<u8>,
593    /// Center coordinate array [longitude, latitude, zoom].
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub center: Option<[f64; 3]>,
596    /// Array of data source URLs.
597    #[serde(skip_serializing_if = "Option::is_none")]
598    pub data: Option<Vec<String>>,
599    /// Array of UTFGrid URL templates.
600    #[serde(skip_serializing_if = "Option::is_none")]
601    pub grids: Option<Vec<String>>,
602    /// Legend of the tileset.
603    #[serde(skip_serializing_if = "Option::is_none")]
604    pub legend: Option<String>,
605    /// Template for interactivity.
606    #[serde(skip_serializing_if = "Option::is_none")]
607    pub template: Option<String>,
608}
609impl Default for Metadata {
610    fn default() -> Self {
611        Self {
612            s2tilejson: "1.0.0".into(),
613            version: "1.0.0".into(),
614            name: "default".into(),
615            scheme: Scheme::default(),
616            description: "Built with s2maps-cli".into(),
617            r#type: SourceType::default(),
618            extension: "pbf".into(),
619            encoding: Encoding::default(),
620            faces: Vec::default(),
621            bounds: BBox::new(-180.0, -90.0, 180.0, 90.0),
622            wmbounds: WMBounds::default(),
623            s2bounds: FaceBounds::default(),
624            minzoom: 0,
625            maxzoom: 27,
626            centerpoint: Center::default(),
627            attributions: BTreeMap::new(),
628            layers: LayersMetaData::default(),
629            tilestats: TileStatsMetadata::default(),
630            vector_layers: Vec::new(),
631            attribution: None,
632            fillzoom: None,
633            center: None,
634            data: None,
635            grids: None,
636            legend: None,
637            template: None,
638            tilejson: None,
639            tiles: None,
640        }
641    }
642}
643
644/// # TileJSON V3.0.0
645///
646/// ## NOTES
647/// You never have to use this. Parsing/conversion will be done for you. by using:
648///
649/// ```rs
650/// let meta: UnknownMetadata =
651///   serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
652/// ```
653///
654/// Represents a TileJSON metadata object for the old Mapbox spec.
655///
656/// ## Links
657/// [TileJSON Spec](https://github.com/mapbox/tilejson-spec/blob/master/3.0.0/schema.json)
658#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
659#[serde(default)]
660pub struct MapboxTileJSONMetadata {
661    /// Version of the TileJSON spec used. Matches the pattern: `\d+\.\d+\.\d+\w?[\w\d]*`.
662    pub tilejson: String,
663    /// Array of tile URL templates.
664    pub tiles: Vec<String>,
665    /// Array of vector layer metadata.
666    pub vector_layers: Vec<VectorLayer>,
667    /// Attribution string.
668    #[serde(skip_serializing_if = "Option::is_none")]
669    pub attribution: Option<String>,
670    /// Bounding box array [west, south, east, north].
671    #[serde(skip_serializing_if = "Option::is_none")]
672    pub bounds: Option<LonLatBounds>,
673    /// Center coordinate array [longitude, latitude, zoom].
674    #[serde(skip_serializing_if = "Option::is_none")]
675    pub center: Option<[f64; 3]>,
676    /// Array of data source URLs.
677    #[serde(skip_serializing_if = "Option::is_none")]
678    pub data: Option<Vec<String>>,
679    /// Description string.
680    #[serde(skip_serializing_if = "Option::is_none")]
681    pub description: Option<String>,
682    /// Fill zoom level. Must be between 0 and 30.
683    #[serde(skip_serializing_if = "Option::is_none")]
684    pub fillzoom: Option<u8>,
685    /// Array of UTFGrid URL templates.
686    #[serde(skip_serializing_if = "Option::is_none")]
687    pub grids: Option<Vec<String>>,
688    /// Legend of the tileset.
689    #[serde(skip_serializing_if = "Option::is_none")]
690    pub legend: Option<String>,
691    /// Maximum zoom level. Must be between 0 and 30.
692    #[serde(skip_serializing_if = "Option::is_none")]
693    pub maxzoom: Option<u8>,
694    /// Minimum zoom level. Must be between 0 and 30.
695    #[serde(skip_serializing_if = "Option::is_none")]
696    pub minzoom: Option<u8>,
697    /// Name of the tileset.
698    #[serde(skip_serializing_if = "Option::is_none")]
699    pub name: Option<String>,
700    /// Tile scheme, e.g., `xyz` or `tms`.
701    #[serde(skip_serializing_if = "Option::is_none")]
702    pub scheme: Option<Scheme>,
703    /// Template for interactivity.
704    #[serde(skip_serializing_if = "Option::is_none")]
705    pub template: Option<String>,
706    /// Version of the tileset. Matches the pattern: `\d+\.\d+\.\d+\w?[\w\d]*`.
707    #[serde(skip_serializing_if = "Option::is_none")]
708    pub version: Option<String>,
709    // NEW SPEC variables hiding here incase UnknownMetadata parses to Mapbox instead
710    /// Added type because it may be included
711    #[serde(skip_serializing_if = "Option::is_none")]
712    pub r#type: Option<SourceType>,
713    /// Extension of the tileset.
714    #[serde(skip_serializing_if = "Option::is_none")]
715    pub extension: Option<String>,
716    /// Encoding of the tileset.
717    #[serde(skip_serializing_if = "Option::is_none")]
718    pub encoding: Option<Encoding>,
719}
720impl MapboxTileJSONMetadata {
721    /// Converts a MapboxTileJSONMetadata to a Metadata
722    pub fn to_metadata(&self) -> Metadata {
723        let [lon, lat, zoom] = self.center.unwrap_or([0.0, 0.0, 0.0]);
724        Metadata {
725            s2tilejson: "1.0.0".into(),
726            version: self.version.clone().unwrap_or("1.0.0".into()),
727            name: self.name.clone().unwrap_or("default".into()),
728            scheme: self.scheme.clone().unwrap_or_default(),
729            description: self.description.clone().unwrap_or("Built with s2maps-cli".into()),
730            r#type: self.r#type.clone().unwrap_or_default(),
731            extension: self.extension.clone().unwrap_or("pbf".into()),
732            faces: Vec::from([Face::Face0]),
733            bounds: self.bounds.unwrap_or_default(),
734            minzoom: self.minzoom.unwrap_or(0),
735            maxzoom: self.maxzoom.unwrap_or(27),
736            centerpoint: Center { lon, lat, zoom: zoom as u8 },
737            center: Some([lon, lat, zoom]),
738            attributions: extract_link_info(self.attribution.as_ref().unwrap_or(&"".into()))
739                .unwrap_or_default(),
740            vector_layers: self.vector_layers.clone(),
741            encoding: self.encoding.clone().unwrap_or(Encoding::None),
742            attribution: self.attribution.clone(),
743            tiles: Some(self.tiles.clone()),
744            data: self.data.clone(),
745            grids: self.grids.clone(),
746            legend: self.legend.clone(),
747            template: self.template.clone(),
748            fillzoom: self.fillzoom,
749            ..Default::default()
750        }
751    }
752}
753
754/// If we don't know which spec we are reading, we can treat the input as either
755#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
756#[serde(untagged)]
757pub enum UnknownMetadata {
758    /// New spec
759    Metadata(Box<Metadata>),
760    /// Old spec
761    Mapbox(Box<MapboxTileJSONMetadata>),
762}
763impl UnknownMetadata {
764    /// Converts a UnknownMetadata to a Metadata
765    pub fn to_metadata(&self) -> Metadata {
766        match self {
767            UnknownMetadata::Metadata(m) => *m.clone(),
768            UnknownMetadata::Mapbox(m) => m.to_metadata(),
769        }
770    }
771}
772
773/// # Metadata Builder
774///
775/// ## Description
776///
777/// Build a Metadata from scratch. Helper tool when constructing a set of tiles
778///
779/// ## Usage
780/// - [`MetadataBuilder::commit`]: Commit the metadata and take ownership of a [`Metadata`]
781/// - [`MetadataBuilder::set_name`]: Set the description of the data
782/// - [`MetadataBuilder::set_scheme`]: Set the scheme of the data. [default=fzxy]
783/// - [`MetadataBuilder::set_extension`]: Set the extension of the data. [default=pbf]
784/// - [`MetadataBuilder::set_type`]: Set the type of the data. [default=vector]
785/// - [`MetadataBuilder::set_version`]: Set the version of the data
786/// - [`MetadataBuilder::set_description`]: Set the description of the data
787/// - [`MetadataBuilder::set_encoding`]: Set the encoding of the data. [default=none]
788/// - [`MetadataBuilder::add_attribution`]: Add an attribution to the data
789/// - [`MetadataBuilder::add_layer`]: Add a layer to the data
790/// - [`MetadataBuilder::add_tile_wm`]: Add the WM tile metadata
791/// - [`MetadataBuilder::add_tile_s2`]: Add the S2 tile metadata
792/// - [`MetadataBuilder::update_center`]: Update the center now that all tiles have been added
793/// - [`MetadataBuilder::add_bounds_wm`]: Add the bounds of the tile for WM data
794/// - [`MetadataBuilder::add_bounds_s2`]: Add the bounds of the tile for S2 data
795/// - [`MetadataBuilder::update_lon_lat_bounds`]: Update the lon-lat bounds so eventually we can find the center point of the data
796#[derive(Debug, Clone)]
797pub struct MetadataBuilder {
798    lon_lat_bounds: LonLatBounds,
799    faces: BTreeSet<Face>,
800    metadata: Metadata,
801}
802impl Default for MetadataBuilder {
803    fn default() -> Self {
804        MetadataBuilder {
805            lon_lat_bounds: BBox {
806                left: f64::INFINITY,
807                bottom: f64::INFINITY,
808                right: -f64::INFINITY,
809                top: -f64::INFINITY,
810            },
811            faces: BTreeSet::new(),
812            metadata: Metadata { minzoom: 30, maxzoom: 0, ..Metadata::default() },
813        }
814    }
815}
816impl MetadataBuilder {
817    /// Commit the metadata and take ownership
818    pub fn commit(&mut self) -> Metadata {
819        // set the center
820        self.update_center();
821        // set the bounds
822        self.metadata.bounds = self.lon_lat_bounds;
823        // set the faces
824        for face in &self.faces {
825            self.metadata.faces.push(*face);
826        }
827        // return the result
828        self.metadata.to_owned()
829    }
830
831    /// Set the name
832    pub fn set_name(&mut self, name: String) {
833        self.metadata.name = name;
834    }
835
836    /// Set the scheme of the data. [default=fzxy]
837    pub fn set_scheme(&mut self, scheme: Scheme) {
838        self.metadata.scheme = scheme;
839    }
840
841    /// Set the extension of the data. [default=pbf]
842    pub fn set_extension(&mut self, extension: String) {
843        self.metadata.extension = extension;
844    }
845
846    /// Set the type of the data. [default=vector]
847    pub fn set_type(&mut self, r#type: SourceType) {
848        self.metadata.r#type = r#type;
849    }
850
851    /// Set the version of the data
852    pub fn set_version(&mut self, version: String) {
853        self.metadata.version = version;
854    }
855
856    /// Set the description of the data
857    pub fn set_description(&mut self, description: String) {
858        self.metadata.description = description;
859    }
860
861    /// Set the encoding of the data. [default=none]
862    pub fn set_encoding(&mut self, encoding: Encoding) {
863        self.metadata.encoding = encoding;
864    }
865
866    /// add an attribution
867    pub fn add_attribution(&mut self, display_name: &str, href: &str) {
868        self.metadata.attributions.insert(display_name.into(), href.into());
869    }
870
871    /// Add the layer metadata
872    pub fn add_layer(&mut self, name: &str, layer: &LayerMetaData) {
873        // Only insert if the key does not exist
874        if self.metadata.layers.entry(name.into()).or_insert(layer.clone()).eq(&layer) {
875            // Also add to vector_layers only if the key was not present and the insert was successful
876            self.metadata.vector_layers.push(VectorLayer {
877                id: name.into(), // No need to clone again; we use the moved value
878                description: layer.description.clone(),
879                minzoom: Some(layer.minzoom),
880                maxzoom: Some(layer.maxzoom),
881                fields: BTreeMap::new(),
882            });
883        }
884        // update minzoom and maxzoom
885        if layer.minzoom < self.metadata.minzoom {
886            self.metadata.minzoom = layer.minzoom;
887        }
888        if layer.maxzoom > self.metadata.maxzoom {
889            self.metadata.maxzoom = layer.maxzoom;
890        }
891    }
892
893    /// Add the WM tile metadata
894    pub fn add_tile_wm(&mut self, zoom: u8, x: u32, y: u32, ll_bounds: &LonLatBounds) {
895        self.metadata.tilestats.total += 1;
896        self.faces.insert(Face::Face0);
897        self.add_bounds_wm(zoom, x, y);
898        self.update_lon_lat_bounds(ll_bounds);
899    }
900
901    /// Add the S2 tile metadata
902    pub fn add_tile_s2(&mut self, face: Face, zoom: u8, x: u32, y: u32, ll_bounds: &LonLatBounds) {
903        self.metadata.tilestats.increment(face);
904        self.faces.insert(face);
905        self.add_bounds_s2(face, zoom, x, y);
906        self.update_lon_lat_bounds(ll_bounds);
907    }
908
909    /// Update the center now that all tiles have been added
910    fn update_center(&mut self) {
911        let Metadata { minzoom, maxzoom, .. } = self.metadata;
912        let BBox { left, bottom, right, top } = self.lon_lat_bounds;
913        self.metadata.centerpoint.lon = (left + right) / 2.0;
914        self.metadata.centerpoint.lat = (bottom + top) / 2.0;
915        self.metadata.centerpoint.zoom = (minzoom + maxzoom) >> 1;
916    }
917
918    /// Add the bounds of the tile for WM data
919    fn add_bounds_wm(&mut self, zoom: u8, x: u32, y: u32) {
920        let x = x as u64;
921        let y = y as u64;
922        let bbox = self.metadata.wmbounds.entry(zoom).or_insert(BBox {
923            left: u64::MAX,
924            bottom: u64::MAX,
925            right: 0,
926            top: 0,
927        });
928
929        bbox.left = bbox.left.min(x);
930        bbox.bottom = bbox.bottom.min(y);
931        bbox.right = bbox.right.max(x);
932        bbox.top = bbox.top.max(y);
933    }
934
935    /// Add the bounds of the tile for S2 data
936    fn add_bounds_s2(&mut self, face: Face, zoom: u8, x: u32, y: u32) {
937        let x = x as u64;
938        let y = y as u64;
939        let bbox = self.metadata.s2bounds.get_mut(face).entry(zoom).or_insert(BBox {
940            left: u64::MAX,
941            bottom: u64::MAX,
942            right: 0,
943            top: 0,
944        });
945
946        bbox.left = bbox.left.min(x);
947        bbox.bottom = bbox.bottom.min(y);
948        bbox.right = bbox.right.max(x);
949        bbox.top = bbox.top.max(y);
950    }
951
952    /// Update the lon-lat bounds so eventually we can find the center point of the data
953    fn update_lon_lat_bounds(&mut self, ll_bounds: &LonLatBounds) {
954        self.lon_lat_bounds.left = ll_bounds.left.min(self.lon_lat_bounds.left);
955        self.lon_lat_bounds.bottom = ll_bounds.bottom.min(self.lon_lat_bounds.bottom);
956        self.lon_lat_bounds.right = ll_bounds.right.max(self.lon_lat_bounds.right);
957        self.lon_lat_bounds.top = ll_bounds.top.max(self.lon_lat_bounds.top);
958    }
959}
960
961/// Extract the link info from the HTML
962fn extract_link_info(html_string: &str) -> Option<Attributions> {
963    // Find the start and end of the 'href' attribute
964    let href_start = html_string.find("href='")?;
965    let href_value_start = href_start + "href='".len();
966    let href_end = html_string[href_value_start..].find("'")?;
967    let href_value = &html_string[href_value_start..href_value_start + href_end];
968
969    // Find the start and end of the link text
970    let text_start = html_string.find(">")?;
971    let text_value_start = text_start + 1;
972    let text_end = html_string.find("</a>")?;
973    let text_value = &html_string[text_value_start..text_end];
974
975    let mut map = BTreeMap::new();
976    map.insert(text_value.into(), href_value.into());
977
978    Some(map)
979}
980
981#[cfg(test)]
982mod tests {
983    use super::*;
984    use alloc::vec;
985    use s2json::{PrimitiveShape, ShapeType};
986
987    #[test]
988    fn it_works() {
989        let mut meta_builder = MetadataBuilder::default();
990
991        // on initial use be sure to update basic metadata:
992        meta_builder.set_name("OSM".into());
993        meta_builder.set_description("A free editable map of the whole world.".into());
994        meta_builder.set_version("1.0.0".into());
995        meta_builder.set_scheme("fzxy".into()); // 'fzxy' | 'tfzxy' | 'xyz' | 'txyz' | 'tms'
996        meta_builder.set_type("vector".into()); // 'vector' | 'json' | 'raster' | 'raster-dem' | 'grid' | 'markers'
997        meta_builder.set_encoding("none".into()); // 'gz' | 'br' | 'none'
998        meta_builder.set_extension("pbf".into());
999        meta_builder.add_attribution("OpenStreetMap", "https://www.openstreetmap.org/copyright/");
1000
1001        // Vector Specific: add layers based on how you want to parse data from a source:
1002        let shape_str = r#"
1003        {
1004            "class": "string",
1005            "offset": "f64",
1006            "info": {
1007                "name": "string",
1008                "value": "i64"
1009            }
1010        }
1011        "#;
1012        let shape: Shape = serde_json::from_str(shape_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1013        let layer = LayerMetaData {
1014            minzoom: 0,
1015            maxzoom: 13,
1016            description: Some("water_lines".into()),
1017            draw_types: Vec::from(&[DrawType::Lines]),
1018            shape: shape.clone(),
1019            m_shape: None,
1020        };
1021        meta_builder.add_layer("water_lines", &layer);
1022
1023        // as you build tiles, add the tiles metadata:
1024        // WM:
1025        meta_builder.add_tile_wm(
1026            0,
1027            0,
1028            0,
1029            &LonLatBounds { left: -60.0, bottom: -20.0, right: 5.0, top: 60.0 },
1030        );
1031        // S2:
1032        meta_builder.add_tile_s2(
1033            Face::Face1,
1034            5,
1035            22,
1036            37,
1037            &LonLatBounds { left: -120.0, bottom: -7.0, right: 44.0, top: 72.0 },
1038        );
1039
1040        // finally to get the resulting metadata:
1041        let resulting_metadata: Metadata = meta_builder.commit();
1042
1043        assert_eq!(
1044            resulting_metadata,
1045            Metadata {
1046                name: "OSM".into(),
1047                description: "A free editable map of the whole world.".into(),
1048                version: "1.0.0".into(),
1049                scheme: "fzxy".into(),
1050                r#type: "vector".into(),
1051                encoding: "none".into(),
1052                extension: "pbf".into(),
1053                attributions: BTreeMap::from([(
1054                    "OpenStreetMap".into(),
1055                    "https://www.openstreetmap.org/copyright/".into()
1056                ),]),
1057                wmbounds: BTreeMap::from([(
1058                    0,
1059                    TileBounds { left: 0, bottom: 0, right: 0, top: 0 }
1060                ),]),
1061                faces: Vec::from(&[Face::Face0, Face::Face1]),
1062                bounds: BBox { left: -120.0, bottom: -20.0, right: 44.0, top: 72.0 },
1063                s2bounds: FaceBounds {
1064                    face0: BTreeMap::new(),
1065                    face1: BTreeMap::from([(
1066                        5,
1067                        TileBounds { left: 22, bottom: 37, right: 22, top: 37 }
1068                    ),]),
1069                    face2: BTreeMap::new(),
1070                    face3: BTreeMap::new(),
1071                    face4: BTreeMap::new(),
1072                    face5: BTreeMap::new(),
1073                },
1074                minzoom: 0,
1075                maxzoom: 13,
1076                // lon: -38.0, lat: 26.0, zoom: 6
1077                centerpoint: Center { lon: -38.0, lat: 26.0, zoom: 6 },
1078                tilestats: TileStatsMetadata {
1079                    total: 2,
1080                    total_0: 0,
1081                    total_1: 1,
1082                    total_2: 0,
1083                    total_3: 0,
1084                    total_4: 0,
1085                    total_5: 0,
1086                },
1087                layers: BTreeMap::from([(
1088                    "water_lines".into(),
1089                    LayerMetaData {
1090                        description: Some("water_lines".into()),
1091                        minzoom: 0,
1092                        maxzoom: 13,
1093                        draw_types: Vec::from(&[DrawType::Lines]),
1094                        shape: Shape::from([
1095                            ("class".into(), ShapeType::Primitive(PrimitiveShape::String)),
1096                            ("offset".into(), ShapeType::Primitive(PrimitiveShape::F64)),
1097                            (
1098                                "info".into(),
1099                                ShapeType::Nested(Shape::from([
1100                                    ("name".into(), ShapeType::Primitive(PrimitiveShape::String)),
1101                                    ("value".into(), ShapeType::Primitive(PrimitiveShape::I64)),
1102                                ]))
1103                            ),
1104                        ]),
1105                        m_shape: None,
1106                    }
1107                )]),
1108                s2tilejson: "1.0.0".into(),
1109                vector_layers: Vec::from([VectorLayer {
1110                    id: "water_lines".into(),
1111                    description: Some("water_lines".into()),
1112                    minzoom: Some(0),
1113                    maxzoom: Some(13),
1114                    fields: BTreeMap::new()
1115                }]),
1116                ..Default::default()
1117            }
1118        );
1119
1120        let meta_str = serde_json::to_string(&resulting_metadata).unwrap();
1121
1122        assert_eq!(
1123            meta_str,
1124            "{\"s2tilejson\":\"1.0.0\",\"version\":\"1.0.0\",\"name\":\"OSM\",\"scheme\":\"fzxy\",\"description\":\"A free editable map of the whole world.\",\"type\":\"vector\",\"extension\":\"pbf\",\"encoding\":\"none\",\"faces\":[0,1],\"bounds\":[-120.0,-20.0,44.0,72.0],\"wmbounds\":{\"0\":[0,0,0,0]},\"s2bounds\":{\"0\":{},\"1\":{\"5\":[22,37,22,37]},\"2\":{},\"3\":{},\"4\":{},\"5\":{}},\"minzoom\":0,\"maxzoom\":13,\"centerpoint\":{\"lon\":-38.0,\"lat\":26.0,\"zoom\":6},\"attributions\":{\"OpenStreetMap\":\"https://www.openstreetmap.org/copyright/\"},\"layers\":{\"water_lines\":{\"description\":\"water_lines\",\"minzoom\":0,\"maxzoom\":13,\"draw_types\":[2],\"shape\":{\"class\":\"string\",\"info\":{\"name\":\"string\",\"value\":\"i64\"},\"offset\":\"f64\"}}},\"tilestats\":{\"total\":2,\"0\":0,\"1\":1,\"2\":0,\"3\":0,\"4\":0,\"5\":0},\"vector_layers\":[{\"id\":\"water_lines\",\"description\":\"water_lines\",\"minzoom\":0,\"maxzoom\":13,\"fields\":{}}],\"tilejson\":null}"
1125        );
1126
1127        let meta_reparsed: Metadata =
1128            serde_json::from_str(&meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1129        assert_eq!(meta_reparsed, resulting_metadata);
1130    }
1131
1132    #[test]
1133    fn test_face() {
1134        assert_eq!(Face::Face0, Face::from(0));
1135        assert_eq!(Face::Face1, Face::from(1));
1136        assert_eq!(Face::Face2, Face::from(2));
1137        assert_eq!(Face::Face3, Face::from(3));
1138        assert_eq!(Face::Face4, Face::from(4));
1139        assert_eq!(Face::Face5, Face::from(5));
1140
1141        assert_eq!(0, u8::from(Face::Face0));
1142        assert_eq!(1, u8::from(Face::Face1));
1143        assert_eq!(2, u8::from(Face::Face2));
1144        assert_eq!(3, u8::from(Face::Face3));
1145        assert_eq!(4, u8::from(Face::Face4));
1146        assert_eq!(5, u8::from(Face::Face5));
1147    }
1148
1149    #[test]
1150    fn test_bbox() {
1151        let bbox: BBox = BBox { left: 0.0, bottom: 0.0, right: 0.0, top: 0.0 };
1152        // serialize to JSON and back
1153        let json = serde_json::to_string(&bbox).unwrap();
1154        assert_eq!(json, r#"[0.0,0.0,0.0,0.0]"#);
1155        let bbox2: BBox = serde_json::from_str(&json).unwrap();
1156        assert_eq!(bbox, bbox2);
1157    }
1158
1159    // TileStatsMetadata
1160    #[test]
1161    fn test_tilestats() {
1162        let mut tilestats = TileStatsMetadata {
1163            total: 2,
1164            total_0: 0,
1165            total_1: 1,
1166            total_2: 0,
1167            total_3: 0,
1168            total_4: 0,
1169            total_5: 0,
1170        };
1171        // serialize to JSON and back
1172        let json = serde_json::to_string(&tilestats).unwrap();
1173        assert_eq!(json, r#"{"total":2,"0":0,"1":1,"2":0,"3":0,"4":0,"5":0}"#);
1174        let tilestats2: TileStatsMetadata = serde_json::from_str(&json).unwrap();
1175        assert_eq!(tilestats, tilestats2);
1176
1177        // get0
1178        assert_eq!(tilestats.get(0.into()), 0);
1179        // increment0
1180        tilestats.increment(0.into());
1181        assert_eq!(tilestats.get(0.into()), 1);
1182
1183        // get 1
1184        assert_eq!(tilestats.get(1.into()), 1);
1185        // increment 1
1186        tilestats.increment(1.into());
1187        assert_eq!(tilestats.get(1.into()), 2);
1188
1189        // get 2
1190        assert_eq!(tilestats.get(2.into()), 0);
1191        // increment 2
1192        tilestats.increment(2.into());
1193        assert_eq!(tilestats.get(2.into()), 1);
1194
1195        // get 3
1196        assert_eq!(tilestats.get(3.into()), 0);
1197        // increment 3
1198        tilestats.increment(3.into());
1199        assert_eq!(tilestats.get(3.into()), 1);
1200
1201        // get 4
1202        assert_eq!(tilestats.get(4.into()), 0);
1203        // increment 4
1204        tilestats.increment(4.into());
1205        assert_eq!(tilestats.get(4.into()), 1);
1206
1207        // get 5
1208        assert_eq!(tilestats.get(5.into()), 0);
1209        // increment 5
1210        tilestats.increment(5.into());
1211        assert_eq!(tilestats.get(5.into()), 1);
1212    }
1213
1214    // FaceBounds
1215    #[test]
1216    fn test_facebounds() {
1217        let mut facebounds = FaceBounds::default();
1218        // get mut
1219        let face0 = facebounds.get_mut(0.into());
1220        face0.insert(0, TileBounds { left: 0, bottom: 0, right: 0, top: 0 });
1221        // get mut 1
1222        let face1 = facebounds.get_mut(1.into());
1223        face1.insert(0, TileBounds { left: 0, bottom: 0, right: 1, top: 1 });
1224        // get mut 2
1225        let face2 = facebounds.get_mut(2.into());
1226        face2.insert(0, TileBounds { left: 0, bottom: 0, right: 2, top: 2 });
1227        // get mut 3
1228        let face3 = facebounds.get_mut(3.into());
1229        face3.insert(0, TileBounds { left: 0, bottom: 0, right: 3, top: 3 });
1230        // get mut 4
1231        let face4 = facebounds.get_mut(4.into());
1232        face4.insert(0, TileBounds { left: 0, bottom: 0, right: 4, top: 4 });
1233        // get mut 5
1234        let face5 = facebounds.get_mut(5.into());
1235        face5.insert(0, TileBounds { left: 0, bottom: 0, right: 5, top: 5 });
1236
1237        // now get for all 5:
1238        // get 0
1239        assert_eq!(
1240            facebounds.get(0.into()).get(&0).unwrap(),
1241            &TileBounds { left: 0, bottom: 0, right: 0, top: 0 }
1242        );
1243        // get 1
1244        assert_eq!(
1245            facebounds.get(1.into()).get(&0).unwrap(),
1246            &TileBounds { left: 0, bottom: 0, right: 1, top: 1 }
1247        );
1248        // get 2
1249        assert_eq!(
1250            facebounds.get(2.into()).get(&0).unwrap(),
1251            &TileBounds { left: 0, bottom: 0, right: 2, top: 2 }
1252        );
1253        // get 3
1254        assert_eq!(
1255            facebounds.get(3.into()).get(&0).unwrap(),
1256            &TileBounds { left: 0, bottom: 0, right: 3, top: 3 }
1257        );
1258        // get 4
1259        assert_eq!(
1260            facebounds.get(4.into()).get(&0).unwrap(),
1261            &TileBounds { left: 0, bottom: 0, right: 4, top: 4 }
1262        );
1263        // get 5
1264        assert_eq!(
1265            facebounds.get(5.into()).get(&0).unwrap(),
1266            &TileBounds { left: 0, bottom: 0, right: 5, top: 5 }
1267        );
1268
1269        // serialize to JSON and back
1270        let json = serde_json::to_string(&facebounds).unwrap();
1271        assert_eq!(
1272            json,
1273            "{\"0\":{\"0\":[0,0,0,0]},\"1\":{\"0\":[0,0,1,1]},\"2\":{\"0\":[0,0,2,2]},\"3\":{\"0\"\
1274             :[0,0,3,3]},\"4\":{\"0\":[0,0,4,4]},\"5\":{\"0\":[0,0,5,5]}}"
1275        );
1276        let facebounds2 = serde_json::from_str(&json).unwrap();
1277        assert_eq!(facebounds, facebounds2);
1278    }
1279
1280    // DrawType
1281    #[test]
1282    fn test_drawtype() {
1283        assert_eq!(DrawType::from(1), DrawType::Points);
1284        assert_eq!(DrawType::from(2), DrawType::Lines);
1285        assert_eq!(DrawType::from(3), DrawType::Polygons);
1286        assert_eq!(DrawType::from(4), DrawType::Points3D);
1287        assert_eq!(DrawType::from(5), DrawType::Lines3D);
1288        assert_eq!(DrawType::from(6), DrawType::Polygons3D);
1289        assert_eq!(DrawType::from(7), DrawType::Raster);
1290        assert_eq!(DrawType::from(8), DrawType::Grid);
1291
1292        assert_eq!(1, u8::from(DrawType::Points));
1293        assert_eq!(2, u8::from(DrawType::Lines));
1294        assert_eq!(3, u8::from(DrawType::Polygons));
1295        assert_eq!(4, u8::from(DrawType::Points3D));
1296        assert_eq!(5, u8::from(DrawType::Lines3D));
1297        assert_eq!(6, u8::from(DrawType::Polygons3D));
1298        assert_eq!(7, u8::from(DrawType::Raster));
1299        assert_eq!(8, u8::from(DrawType::Grid));
1300
1301        // check json is the number value
1302        let json = serde_json::to_string(&DrawType::Points).unwrap();
1303        assert_eq!(json, "1");
1304        let drawtype: DrawType = serde_json::from_str(&json).unwrap();
1305        assert_eq!(drawtype, DrawType::Points);
1306
1307        let drawtype: DrawType = serde_json::from_str("2").unwrap();
1308        assert_eq!(drawtype, DrawType::Lines);
1309
1310        let drawtype: DrawType = serde_json::from_str("3").unwrap();
1311        assert_eq!(drawtype, DrawType::Polygons);
1312
1313        let drawtype: DrawType = serde_json::from_str("4").unwrap();
1314        assert_eq!(drawtype, DrawType::Points3D);
1315
1316        let drawtype: DrawType = serde_json::from_str("5").unwrap();
1317        assert_eq!(drawtype, DrawType::Lines3D);
1318
1319        let drawtype: DrawType = serde_json::from_str("6").unwrap();
1320        assert_eq!(drawtype, DrawType::Polygons3D);
1321
1322        let drawtype: DrawType = serde_json::from_str("7").unwrap();
1323        assert_eq!(drawtype, DrawType::Raster);
1324
1325        let drawtype: DrawType = serde_json::from_str("8").unwrap();
1326        assert_eq!(drawtype, DrawType::Grid);
1327
1328        assert!(serde_json::from_str::<DrawType>("9").is_err());
1329    }
1330
1331    // SourceType
1332    #[test]
1333    fn test_sourcetype() {
1334        // from string
1335        assert_eq!(SourceType::from("vector"), SourceType::Vector);
1336        assert_eq!(SourceType::from("json"), SourceType::Json);
1337        assert_eq!(SourceType::from("raster"), SourceType::Raster);
1338        assert_eq!(SourceType::from("raster-dem"), SourceType::RasterDem);
1339        assert_eq!(SourceType::from("grid"), SourceType::Grid);
1340        assert_eq!(SourceType::from("markers"), SourceType::Markers);
1341        assert_eq!(SourceType::from("overlay"), SourceType::Unknown);
1342
1343        // json vector
1344        let json = serde_json::to_string(&SourceType::Vector).unwrap();
1345        assert_eq!(json, "\"vector\"");
1346        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1347        assert_eq!(sourcetype, SourceType::Vector);
1348
1349        // json json
1350        let json = serde_json::to_string(&SourceType::Json).unwrap();
1351        assert_eq!(json, "\"json\"");
1352        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1353        assert_eq!(sourcetype, SourceType::Json);
1354
1355        // json raster
1356        let json = serde_json::to_string(&SourceType::Raster).unwrap();
1357        assert_eq!(json, "\"raster\"");
1358        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1359        assert_eq!(sourcetype, SourceType::Raster);
1360
1361        // json raster-dem
1362        let json = serde_json::to_string(&SourceType::RasterDem).unwrap();
1363        assert_eq!(json, "\"raster-dem\"");
1364        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1365        assert_eq!(sourcetype, SourceType::RasterDem);
1366
1367        // json grid
1368        let json = serde_json::to_string(&SourceType::Grid).unwrap();
1369        assert_eq!(json, "\"grid\"");
1370        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1371        assert_eq!(sourcetype, SourceType::Grid);
1372
1373        // json markers
1374        let json = serde_json::to_string(&SourceType::Markers).unwrap();
1375        assert_eq!(json, "\"markers\"");
1376        let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
1377        assert_eq!(sourcetype, SourceType::Markers);
1378
1379        // json unknown
1380        let json = serde_json::to_string(&SourceType::Unknown).unwrap();
1381        assert_eq!(json, "\"unknown\"");
1382        let sourcetype: SourceType = serde_json::from_str(r#""overlay""#).unwrap();
1383        assert_eq!(sourcetype, SourceType::Unknown);
1384    }
1385
1386    // Encoding
1387    #[test]
1388    fn test_encoding() {
1389        // from string
1390        assert_eq!(Encoding::from("none"), Encoding::None);
1391        assert_eq!(Encoding::from("gzip"), Encoding::Gzip);
1392        assert_eq!(Encoding::from("br"), Encoding::Brotli);
1393        assert_eq!(Encoding::from("zstd"), Encoding::Zstd);
1394
1395        // to string
1396        assert_eq!(core::convert::Into::<&str>::into(Encoding::None), "none");
1397        assert_eq!(core::convert::Into::<&str>::into(Encoding::Gzip), "gzip");
1398        assert_eq!(core::convert::Into::<&str>::into(Encoding::Brotli), "br");
1399        assert_eq!(core::convert::Into::<&str>::into(Encoding::Zstd), "zstd");
1400
1401        // from u8
1402        assert_eq!(Encoding::from(0), Encoding::None);
1403        assert_eq!(Encoding::from(1), Encoding::Gzip);
1404        assert_eq!(Encoding::from(2), Encoding::Brotli);
1405        assert_eq!(Encoding::from(3), Encoding::Zstd);
1406
1407        // to u8
1408        assert_eq!(u8::from(Encoding::None), 0);
1409        assert_eq!(u8::from(Encoding::Gzip), 1);
1410        assert_eq!(u8::from(Encoding::Brotli), 2);
1411        assert_eq!(u8::from(Encoding::Zstd), 3);
1412
1413        // json gzip
1414        let json = serde_json::to_string(&Encoding::Gzip).unwrap();
1415        assert_eq!(json, "\"gzip\"");
1416        let encoding: Encoding = serde_json::from_str(&json).unwrap();
1417        assert_eq!(encoding, Encoding::Gzip);
1418
1419        // json br
1420        let json = serde_json::to_string(&Encoding::Brotli).unwrap();
1421        assert_eq!(json, "\"br\"");
1422        let encoding: Encoding = serde_json::from_str(&json).unwrap();
1423        assert_eq!(encoding, Encoding::Brotli);
1424
1425        // json none
1426        let json = serde_json::to_string(&Encoding::None).unwrap();
1427        assert_eq!(json, "\"none\"");
1428        let encoding: Encoding = serde_json::from_str(&json).unwrap();
1429        assert_eq!(encoding, Encoding::None);
1430
1431        // json zstd
1432        let json = serde_json::to_string(&Encoding::Zstd).unwrap();
1433        assert_eq!(json, "\"zstd\"");
1434        let encoding: Encoding = serde_json::from_str(&json).unwrap();
1435        assert_eq!(encoding, Encoding::Zstd);
1436    }
1437
1438    // Scheme
1439    #[test]
1440    fn test_scheme() {
1441        // from string
1442        assert_eq!(Scheme::from("fzxy"), Scheme::Fzxy);
1443        assert_eq!(Scheme::from("tfzxy"), Scheme::Tfzxy);
1444        assert_eq!(Scheme::from("xyz"), Scheme::Xyz);
1445        assert_eq!(Scheme::from("txyz"), Scheme::Txyz);
1446        assert_eq!(Scheme::from("tms"), Scheme::Tms);
1447
1448        // to string
1449        assert_eq!(core::convert::Into::<&str>::into(Scheme::Fzxy), "fzxy");
1450        assert_eq!(core::convert::Into::<&str>::into(Scheme::Tfzxy), "tfzxy");
1451        assert_eq!(core::convert::Into::<&str>::into(Scheme::Xyz), "xyz");
1452        assert_eq!(core::convert::Into::<&str>::into(Scheme::Txyz), "txyz");
1453        assert_eq!(core::convert::Into::<&str>::into(Scheme::Tms), "tms");
1454    }
1455
1456    #[test]
1457    fn test_tippecanoe_metadata() {
1458        let meta_str = r#"{
1459            "name": "test_fixture_1.pmtiles",
1460            "description": "test_fixture_1.pmtiles",
1461            "version": "2",
1462            "type": "overlay",
1463            "generator": "tippecanoe v2.5.0",
1464            "generator_options": "./tippecanoe -zg -o test_fixture_1.pmtiles --force",
1465            "vector_layers": [
1466                {
1467                    "id": "test_fixture_1pmtiles",
1468                    "description": "",
1469                    "minzoom": 0,
1470                    "maxzoom": 0,
1471                    "fields": {}
1472                }
1473            ],
1474            "tilestats": {
1475                "layerCount": 1,
1476                "layers": [
1477                    {
1478                        "layer": "test_fixture_1pmtiles",
1479                        "count": 1,
1480                        "geometry": "Polygon",
1481                        "attributeCount": 0,
1482                        "attributes": []
1483                    }
1484                ]
1485            }
1486        }"#;
1487
1488        let _meta: Metadata =
1489            serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1490    }
1491
1492    #[test]
1493    fn test_mapbox_metadata() {
1494        let meta_str = r#"{
1495            "tilejson": "3.0.0",
1496            "name": "OpenStreetMap",
1497            "description": "A free editable map of the whole world.",
1498            "version": "1.0.0",
1499            "attribution": "<a href='https://openstreetmap.org'>OSM contributors</a>",
1500            "scheme": "xyz",
1501            "tiles": [
1502                "https://a.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt",
1503                "https://b.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt",
1504                "https://c.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt"
1505            ],
1506            "minzoom": 0,
1507            "maxzoom": 18,
1508            "bounds": [-180, -85, 180, 85],
1509            "fillzoom": 6,
1510            "something_custom": "this is my unique field",
1511            "vector_layers": [
1512                {
1513                    "id": "telephone",
1514                    "fields": {
1515                        "phone_number": "the phone number",
1516                        "payment": "how to pay"
1517                    }
1518                },
1519                {
1520                    "id": "bicycle_parking",
1521                    "fields": {
1522                        "type": "the type of bike parking",
1523                        "year_installed": "the year the bike parking was installed"
1524                    }
1525                },
1526                {
1527                    "id": "showers",
1528                    "fields": {
1529                        "water_temperature": "the maximum water temperature",
1530                        "wear_sandles": "whether you should wear sandles or not",
1531                        "wheelchair": "is the shower wheelchair friendly?"
1532                    }
1533                }
1534            ]
1535        }"#;
1536
1537        let meta_mapbox: MapboxTileJSONMetadata =
1538            serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1539        let meta_new = meta_mapbox.to_metadata();
1540        assert_eq!(
1541            meta_new,
1542            Metadata {
1543                name: "OpenStreetMap".into(),
1544                description: "A free editable map of the whole world.".into(),
1545                version: "1.0.0".into(),
1546                scheme: Scheme::Xyz,
1547                r#type: "vector".into(),
1548                encoding: Encoding::None, // Changed from "none".into() to None
1549                extension: "pbf".into(),
1550                attributions: BTreeMap::from([(
1551                    "OSM contributors".into(),
1552                    "https://openstreetmap.org".into()
1553                )]),
1554                bounds: BBox::new(-180., -85., 180., 85.),
1555                vector_layers: meta_mapbox.vector_layers.clone(),
1556                maxzoom: 18,
1557                minzoom: 0,
1558                centerpoint: Center { lat: 0.0, lon: 0.0, zoom: 0 },
1559                wmbounds: WMBounds::default(),
1560                faces: vec![Face::Face0],
1561                s2bounds: FaceBounds::default(),
1562                tilestats: TileStatsMetadata::default(),
1563                layers: LayersMetaData::default(),
1564                s2tilejson: "1.0.0".into(),
1565                attribution: Some(
1566                    "<a href='https://openstreetmap.org'>OSM contributors</a>".into()
1567                ),
1568                tiles: Some(meta_mapbox.tiles.clone()),
1569                fillzoom: meta_mapbox.fillzoom,
1570                center: Some([0.0, 0.0, 0.0]),
1571                ..Default::default()
1572            },
1573        );
1574
1575        let meta_mapbox_from_unknown: UnknownMetadata =
1576            serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1577        let meta_new = meta_mapbox_from_unknown.to_metadata();
1578        assert_eq!(
1579            meta_new,
1580            Metadata {
1581                name: "OpenStreetMap".into(),
1582                description: "A free editable map of the whole world.".into(),
1583                version: "1.0.0".into(),
1584                scheme: Scheme::Xyz,
1585                r#type: "vector".into(),
1586                encoding: Encoding::None, // Changed from "none".into() to None
1587                extension: "pbf".into(),
1588                attributions: BTreeMap::default(),
1589                bounds: BBox::new(-180., -85., 180., 85.),
1590                vector_layers: meta_mapbox.vector_layers.clone(),
1591                maxzoom: 18,
1592                minzoom: 0,
1593                centerpoint: Center { lat: 0.0, lon: 0.0, zoom: 0 },
1594                wmbounds: WMBounds::default(),
1595                faces: vec![],
1596                s2bounds: FaceBounds::default(),
1597                tilestats: TileStatsMetadata::default(),
1598                layers: LayersMetaData::default(),
1599                s2tilejson: "1.0.0".into(),
1600                attribution: Some(
1601                    "<a href='https://openstreetmap.org'>OSM contributors</a>".into()
1602                ),
1603                tiles: Some(meta_mapbox.tiles.clone()),
1604                fillzoom: meta_mapbox.fillzoom,
1605                center: None,
1606                tilejson: Some("3.0.0".into()),
1607                ..Default::default()
1608            },
1609        );
1610    }
1611
1612    #[test]
1613    fn test_malformed_metadata() {
1614        let meta_str = r#"{
1615            "s2tilejson": "1.0.0",
1616            "bounds": [
1617                -180,
1618                -85,
1619                180,
1620                85
1621            ],
1622            "name": "Mapbox Satellite",
1623            "scheme": "xyz",
1624            "format": "zxy",
1625            "type": "raster",
1626            "extension": "webp",
1627            "encoding": "gzip",
1628            "minzoom": 0,
1629            "maxzoom": 3
1630        }
1631        "#;
1632
1633        let malformed_success: UnknownMetadata =
1634            serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {e}"));
1635
1636        let meta: Metadata = malformed_success.to_metadata();
1637        assert_eq!(
1638            meta,
1639            Metadata {
1640                s2tilejson: "1.0.0".into(),
1641                version: "1.0.0".into(),
1642                name: "Mapbox Satellite".into(),
1643                scheme: Scheme::Xyz,
1644                description: "Built with s2maps-cli".into(),
1645                r#type: SourceType::Raster,
1646                extension: "webp".into(),
1647                encoding: Encoding::Gzip,
1648                faces: vec![],
1649                bounds: BBox::new(-180., -85., 180., 85.),
1650                wmbounds: BTreeMap::default(),
1651                s2bounds: FaceBounds {
1652                    face0: BTreeMap::default(),
1653                    face1: BTreeMap::default(),
1654                    face2: BTreeMap::default(),
1655                    face3: BTreeMap::default(),
1656                    face4: BTreeMap::default(),
1657                    face5: BTreeMap::default()
1658                },
1659                minzoom: 0,
1660                maxzoom: 3,
1661                centerpoint: Center { lon: 0.0, lat: 0.0, zoom: 0 },
1662                attributions: BTreeMap::default(),
1663                layers: BTreeMap::default(),
1664                tilestats: TileStatsMetadata {
1665                    total: 0,
1666                    total_0: 0,
1667                    total_1: 0,
1668                    total_2: 0,
1669                    total_3: 0,
1670                    total_4: 0,
1671                    total_5: 0
1672                },
1673                vector_layers: vec![],
1674                ..Default::default()
1675            }
1676        );
1677    }
1678
1679    #[test]
1680    fn test_faces() {
1681        let meta = Metadata {
1682            faces: vec![Face::Face0, Face::Face1, Face::Face4, Face::Face5],
1683            ..Default::default()
1684        };
1685
1686        let to_string: String = serde_json::to_string(&meta).unwrap();
1687        let from_string: Metadata = serde_json::from_str(&to_string).unwrap();
1688        assert_eq!(meta, from_string);
1689
1690        let from_string_unknown: UnknownMetadata = serde_json::from_str(&to_string).unwrap();
1691        let from_string: Metadata = from_string_unknown.to_metadata();
1692        assert_eq!(meta, from_string);
1693    }
1694
1695    #[test]
1696    fn test_vector_feature_to_draw_type() {
1697        // Points
1698        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1699            geometry: VectorGeometry::new_point(VectorPoint::from_xy(0., 0.), None),
1700            ..Default::default()
1701        };
1702        assert_eq!(DrawType::Points, (&feature).into());
1703        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1704            geometry: VectorGeometry::new_multipoint(vec![VectorPoint::from_xy(0., 0.)], None),
1705            ..Default::default()
1706        };
1707        assert_eq!(DrawType::Points, (&feature).into());
1708        // Points3D
1709        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1710            geometry: VectorGeometry::new_point(VectorPoint::from_xyz(0., 0., 1.0), None),
1711            ..Default::default()
1712        };
1713        assert_eq!(DrawType::Points3D, (&feature).into());
1714        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1715            geometry: VectorGeometry::new_multipoint(
1716                vec![VectorPoint::from_xyz(0., 0., 1.0)],
1717                None,
1718            ),
1719            ..Default::default()
1720        };
1721        assert_eq!(DrawType::Points3D, (&feature).into());
1722        // Lines
1723        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1724            geometry: VectorGeometry::new_linestring(vec![VectorPoint::from_xy(0., 0.)], None),
1725            ..Default::default()
1726        };
1727        assert_eq!(DrawType::Lines, (&feature).into());
1728        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1729            geometry: VectorGeometry::new_multilinestring(
1730                vec![vec![VectorPoint::from_xy(0., 0.)]],
1731                None,
1732            ),
1733            ..Default::default()
1734        };
1735        assert_eq!(DrawType::Lines, (&feature).into());
1736        // Lines3D
1737        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1738            geometry: VectorGeometry::new_linestring(
1739                vec![VectorPoint::from_xyz(0., 0., 1.0)],
1740                None,
1741            ),
1742            ..Default::default()
1743        };
1744        assert_eq!(DrawType::Lines3D, (&feature).into());
1745        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1746            geometry: VectorGeometry::new_multilinestring(
1747                vec![vec![VectorPoint::from_xyz(0., 0., 1.0)]],
1748                None,
1749            ),
1750            ..Default::default()
1751        };
1752        assert_eq!(DrawType::Lines3D, (&feature).into());
1753        // Polygons
1754        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1755            geometry: VectorGeometry::new_polygon(vec![vec![VectorPoint::from_xy(0., 0.)]], None),
1756            ..Default::default()
1757        };
1758        assert_eq!(DrawType::Polygons, (&feature).into());
1759        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1760            geometry: VectorGeometry::new_multipolygon(
1761                vec![vec![vec![VectorPoint::from_xy(0., 0.)]]],
1762                None,
1763            ),
1764            ..Default::default()
1765        };
1766        assert_eq!(DrawType::Polygons, (&feature).into());
1767        // Polygons3D
1768        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1769            geometry: VectorGeometry::new_polygon(
1770                vec![vec![VectorPoint::from_xyz(0., 0., 1.0)]],
1771                None,
1772            ),
1773            ..Default::default()
1774        };
1775        assert_eq!(DrawType::Polygons3D, (&feature).into());
1776        let feature: VectorFeature<(), Properties, MValue> = VectorFeature {
1777            geometry: VectorGeometry::new_multipolygon(
1778                vec![vec![vec![VectorPoint::from_xyz(0., 0., 1.0)]]],
1779                None,
1780            ),
1781            ..Default::default()
1782        };
1783        assert_eq!(DrawType::Polygons3D, (&feature).into());
1784    }
1785}