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