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