s2_tilejson/
lib.rs

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