Skip to main content

s2_tilejson/
lib.rs

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