#![no_std]
#![deny(missing_docs)]
extern crate alloc;
use serde::de::{self, SeqAccess, Visitor};
use serde::ser::SerializeTuple;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use alloc::borrow::ToOwned;
use alloc::collections::BTreeMap;
use alloc::collections::BTreeSet;
use alloc::fmt;
use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
#[repr(u8)] pub enum Face {
Face0 = 0,
Face1 = 1,
Face2 = 2,
Face3 = 3,
Face4 = 4,
Face5 = 5,
}
impl From<Face> for u8 {
fn from(face: Face) -> Self {
face as u8
}
}
impl From<u8> for Face {
fn from(face: u8) -> Self {
match face {
1 => Face::Face1,
2 => Face::Face2,
3 => Face::Face3,
4 => Face::Face4,
5 => Face::Face5,
_ => Face::Face0,
}
}
}
impl serde::Serialize for Face {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
impl<'de> serde::Deserialize<'de> for Face {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let value = u8::deserialize(deserializer)?;
match value {
0 => Ok(Face::Face0),
1 => Ok(Face::Face1),
2 => Ok(Face::Face2),
3 => Ok(Face::Face3),
4 => Ok(Face::Face4),
5 => Ok(Face::Face5),
_ => Err(serde::de::Error::custom("Invalid face value")),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct BBox<T = f64> {
pub left: T,
pub bottom: T,
pub right: T,
pub top: T,
}
impl<T> Serialize for BBox<T>
where
T: Serialize + Copy,
{
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_tuple(4)?;
seq.serialize_element(&self.left)?;
seq.serialize_element(&self.bottom)?;
seq.serialize_element(&self.right)?;
seq.serialize_element(&self.top)?;
seq.end()
}
}
impl<'de, T> Deserialize<'de> for BBox<T>
where
T: Deserialize<'de> + Copy,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct BBoxVisitor<T> {
marker: core::marker::PhantomData<T>,
}
impl<'de, T> Visitor<'de> for BBoxVisitor<T>
where
T: Deserialize<'de> + Copy,
{
type Value = BBox<T>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a sequence of four numbers")
}
fn visit_seq<V>(self, mut seq: V) -> Result<BBox<T>, V::Error>
where
V: SeqAccess<'de>,
{
let left =
seq.next_element()?.ok_or_else(|| de::Error::invalid_length(0, &self))?;
let bottom =
seq.next_element()?.ok_or_else(|| de::Error::invalid_length(1, &self))?;
let right =
seq.next_element()?.ok_or_else(|| de::Error::invalid_length(2, &self))?;
let top = seq.next_element()?.ok_or_else(|| de::Error::invalid_length(3, &self))?;
Ok(BBox { left, bottom, right, top })
}
}
deserializer.deserialize_tuple(4, BBoxVisitor { marker: core::marker::PhantomData })
}
}
pub type LonLatBounds = BBox<f64>;
pub type TileBounds = BBox<u64>;
#[derive(Copy, Clone, Debug, PartialEq)]
pub enum DrawType {
Points = 1,
Lines = 2,
Polygons = 3,
Points3D = 4,
Lines3D = 5,
Polygons3D = 6,
Raster = 7,
}
impl From<DrawType> for u8 {
fn from(draw_type: DrawType) -> Self {
draw_type as u8
}
}
impl From<u8> for DrawType {
fn from(draw_type: u8) -> Self {
match draw_type {
1 => DrawType::Points,
2 => DrawType::Lines,
3 => DrawType::Polygons,
4 => DrawType::Points3D,
5 => DrawType::Lines3D,
6 => DrawType::Polygons3D,
7 => DrawType::Raster,
_ => DrawType::Points,
}
}
}
impl Serialize for DrawType {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_u8(*self as u8)
}
}
impl<'de> Deserialize<'de> for DrawType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let value: u8 = Deserialize::deserialize(deserializer)?;
match value {
1 => Ok(DrawType::Points),
2 => Ok(DrawType::Lines),
3 => Ok(DrawType::Polygons),
4 => Ok(DrawType::Points3D),
5 => Ok(DrawType::Lines3D),
6 => Ok(DrawType::Polygons3D),
7 => Ok(DrawType::Raster),
_ => Err(serde::de::Error::custom(format!("unknown DrawType variant: {}", value))),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum PrimitiveShape {
String,
U64,
I64,
F32,
F64,
Bool,
Null,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum ShapePrimitiveType {
Primitive(PrimitiveShape),
NestedPrimitive(BTreeMap<String, PrimitiveShape>),
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
#[serde(untagged)]
pub enum ShapeType {
Primitive(PrimitiveShape),
Array(Vec<ShapePrimitiveType>),
Nested(Shape),
}
pub type Shape = BTreeMap<String, ShapeType>;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct LayerMetaData {
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
pub minzoom: u8,
pub maxzoom: u8,
pub draw_types: Vec<DrawType>,
pub shape: Shape,
#[serde(skip_serializing_if = "Option::is_none", rename = "mShape")]
pub m_shape: Option<Shape>,
}
pub type LayersMetaData = BTreeMap<String, LayerMetaData>;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct TileStatsMetadata {
#[serde(default)]
pub total: u64,
#[serde(rename = "0", default)]
pub total_0: u64,
#[serde(rename = "1", default)]
pub total_1: u64,
#[serde(rename = "2", default)]
pub total_2: u64,
#[serde(rename = "3", default)]
pub total_3: u64,
#[serde(rename = "4", default)]
pub total_4: u64,
#[serde(rename = "5", default)]
pub total_5: u64,
}
impl TileStatsMetadata {
pub fn get(&self, face: Face) -> u64 {
match face {
Face::Face0 => self.total_0,
Face::Face1 => self.total_1,
Face::Face2 => self.total_2,
Face::Face3 => self.total_3,
Face::Face4 => self.total_4,
Face::Face5 => self.total_5,
}
}
pub fn increment(&mut self, face: Face) {
match face {
Face::Face0 => self.total_0 += 1,
Face::Face1 => self.total_1 += 1,
Face::Face2 => self.total_2 += 1,
Face::Face3 => self.total_3 += 1,
Face::Face4 => self.total_4 += 1,
Face::Face5 => self.total_5 += 1,
}
self.total += 1;
}
}
pub type Attribution = BTreeMap<String, String>;
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct FaceBounds {
#[serde(rename = "0")]
pub face0: BTreeMap<u8, TileBounds>,
#[serde(rename = "1")]
pub face1: BTreeMap<u8, TileBounds>,
#[serde(rename = "2")]
pub face2: BTreeMap<u8, TileBounds>,
#[serde(rename = "3")]
pub face3: BTreeMap<u8, TileBounds>,
#[serde(rename = "4")]
pub face4: BTreeMap<u8, TileBounds>,
#[serde(rename = "5")]
pub face5: BTreeMap<u8, TileBounds>,
}
impl FaceBounds {
pub fn get(&self, face: Face) -> &BTreeMap<u8, TileBounds> {
match face {
Face::Face0 => &self.face0,
Face::Face1 => &self.face1,
Face::Face2 => &self.face2,
Face::Face3 => &self.face3,
Face::Face4 => &self.face4,
Face::Face5 => &self.face5,
}
}
pub fn get_mut(&mut self, face: Face) -> &mut BTreeMap<u8, TileBounds> {
match face {
Face::Face0 => &mut self.face0,
Face::Face1 => &mut self.face1,
Face::Face2 => &mut self.face2,
Face::Face3 => &mut self.face3,
Face::Face4 => &mut self.face4,
Face::Face5 => &mut self.face5,
}
}
}
pub type WMBounds = BTreeMap<u8, TileBounds>;
#[derive(Serialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SourceType {
#[default]
Vector,
Json,
Raster,
#[serde(rename = "raster-dem")]
RasterDem,
Sensor,
Markers,
Unknown,
}
impl From<&str> for SourceType {
fn from(source_type: &str) -> Self {
match source_type {
"vector" => SourceType::Vector,
"json" => SourceType::Json,
"raster" => SourceType::Raster,
"raster-dem" => SourceType::RasterDem,
"sensor" => SourceType::Sensor,
"markers" => SourceType::Markers,
_ => SourceType::Unknown,
}
}
}
impl<'de> Deserialize<'de> for SourceType {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let s: String = Deserialize::deserialize(deserializer)?;
Ok(SourceType::from(s.as_str()))
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Encoding {
#[default]
None = 0,
Gzip = 1,
#[serde(rename = "br")]
Brotli = 2,
Zstd = 3,
}
impl From<u8> for Encoding {
fn from(encoding: u8) -> Self {
match encoding {
1 => Encoding::Gzip,
2 => Encoding::Brotli,
3 => Encoding::Zstd,
_ => Encoding::None,
}
}
}
impl From<Encoding> for u8 {
fn from(encoding: Encoding) -> Self {
match encoding {
Encoding::Gzip => 1,
Encoding::Brotli => 2,
Encoding::Zstd => 3,
Encoding::None => 0,
}
}
}
impl From<Encoding> for &str {
fn from(encoding: Encoding) -> Self {
match encoding {
Encoding::Gzip => "gzip",
Encoding::Brotli => "br",
Encoding::Zstd => "zstd",
Encoding::None => "none",
}
}
}
impl From<&str> for Encoding {
fn from(encoding: &str) -> Self {
match encoding {
"gzip" => Encoding::Gzip,
"br" => Encoding::Brotli,
"zstd" => Encoding::Zstd,
_ => Encoding::None,
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct VectorLayer {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minzoom: Option<u8>,
#[serde(skip_serializing_if = "Option::is_none")]
pub maxzoom: Option<u8>,
pub fields: BTreeMap<String, String>,
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Scheme {
#[default]
Fzxy,
Tfzxy,
Xyz,
Txyz,
Tms,
}
impl From<&str> for Scheme {
fn from(scheme: &str) -> Self {
match scheme {
"fzxy" => Scheme::Fzxy,
"tfzxy" => Scheme::Tfzxy,
"xyz" => Scheme::Xyz,
"txyz" => Scheme::Txyz,
_ => Scheme::Tms,
}
}
}
impl From<Scheme> for &str {
fn from(scheme: Scheme) -> Self {
match scheme {
Scheme::Fzxy => "fzxy",
Scheme::Tfzxy => "tfzxy",
Scheme::Xyz => "xyz",
Scheme::Txyz => "txyz",
Scheme::Tms => "tms",
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
pub struct Center {
pub lon: f64,
pub lat: f64,
pub zoom: u8,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct Metadata {
#[serde(default)]
pub s2tilejson: String,
#[serde(default)]
pub version: String,
#[serde(default)]
pub name: String,
#[serde(default)]
pub scheme: Scheme,
#[serde(default)]
pub description: String,
#[serde(rename = "type", default)]
pub type_: SourceType,
#[serde(default)]
pub extension: String,
#[serde(default)]
pub encoding: Encoding,
#[serde(default)]
pub faces: Vec<Face>,
#[serde(default)]
pub bounds: WMBounds,
#[serde(default)]
pub facesbounds: FaceBounds,
#[serde(default)]
pub minzoom: u8,
#[serde(default)]
pub maxzoom: u8,
#[serde(default)]
pub center: Center,
#[serde(default)]
pub attribution: Attribution,
#[serde(default)]
pub layers: LayersMetaData,
#[serde(default)]
pub tilestats: TileStatsMetadata,
#[serde(default)]
pub vector_layers: Vec<VectorLayer>,
}
impl Default for Metadata {
fn default() -> Self {
Self {
s2tilejson: "1.0.0".into(),
version: "1.0.0".into(),
name: "default".into(),
scheme: Scheme::default(),
description: "Built with s2maps-cli".into(),
type_: SourceType::default(),
extension: "pbf".into(),
encoding: Encoding::default(),
faces: Vec::new(),
bounds: WMBounds::default(),
facesbounds: FaceBounds::default(),
minzoom: 0,
maxzoom: 27,
center: Center::default(),
attribution: BTreeMap::new(),
layers: LayersMetaData::default(),
tilestats: TileStatsMetadata::default(),
vector_layers: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
pub struct MapboxTileJSONMetadata {
pub tilejson: String,
pub tiles: Vec<String>,
pub vector_layers: Vec<VectorLayer>,
pub attribution: Option<String>,
pub bounds: Option<BBox>,
pub center: Option<[f64; 3]>,
pub data: Option<Vec<String>>,
pub description: Option<String>,
pub fillzoom: Option<u8>,
pub grids: Option<Vec<String>>,
pub legend: Option<String>,
pub maxzoom: Option<u8>,
pub minzoom: Option<u8>,
pub name: Option<String>,
pub scheme: Option<Scheme>,
pub template: Option<String>,
pub version: Option<String>,
}
impl MapboxTileJSONMetadata {
pub fn to_metadata(&self) -> Metadata {
Metadata {
s2tilejson: "1.0.0".into(),
version: self.version.clone().unwrap_or("1.0.0".into()),
name: self.name.clone().unwrap_or("default".into()),
scheme: self.scheme.clone().unwrap_or_default(),
description: self.description.clone().unwrap_or("Built with s2maps-cli".into()),
type_: SourceType::default(),
extension: "pbf".into(),
faces: Vec::from([Face::Face0]),
bounds: WMBounds::default(),
facesbounds: FaceBounds::default(),
minzoom: self.minzoom.unwrap_or(0),
maxzoom: self.maxzoom.unwrap_or(27),
center: Center {
lon: self.center.unwrap_or([0.0, 0.0, 0.0])[0],
lat: self.center.unwrap_or([0.0, 0.0, 0.0])[1],
zoom: self.center.unwrap_or([0.0, 0.0, 0.0])[2] as u8,
},
attribution: BTreeMap::new(),
layers: LayersMetaData::default(),
tilestats: TileStatsMetadata::default(),
vector_layers: self.vector_layers.clone(),
encoding: Encoding::default(),
}
}
}
#[derive(Debug, Clone)]
pub struct MetadataBuilder {
lon_lat_bounds: LonLatBounds,
faces: BTreeSet<Face>,
metadata: Metadata,
}
impl Default for MetadataBuilder {
fn default() -> Self {
MetadataBuilder {
lon_lat_bounds: BBox {
left: f64::INFINITY,
bottom: f64::INFINITY,
right: -f64::INFINITY,
top: -f64::INFINITY,
},
faces: BTreeSet::new(),
metadata: Metadata { minzoom: 30, maxzoom: 0, ..Metadata::default() },
}
}
}
impl MetadataBuilder {
pub fn commit(&mut self) -> Metadata {
self.update_center();
for face in &self.faces {
self.metadata.faces.push(*face);
}
self.metadata.to_owned()
}
pub fn set_name(&mut self, name: String) {
self.metadata.name = name;
}
pub fn set_scheme(&mut self, scheme: Scheme) {
self.metadata.scheme = scheme;
}
pub fn set_extension(&mut self, extension: String) {
self.metadata.extension = extension;
}
pub fn set_type(&mut self, type_: SourceType) {
self.metadata.type_ = type_;
}
pub fn set_version(&mut self, version: String) {
self.metadata.version = version;
}
pub fn set_description(&mut self, description: String) {
self.metadata.description = description;
}
pub fn set_encoding(&mut self, encoding: Encoding) {
self.metadata.encoding = encoding;
}
pub fn add_attribution(&mut self, display_name: &str, href: &str) {
self.metadata.attribution.insert(display_name.into(), href.into());
}
pub fn add_layer(&mut self, name: &str, layer: &LayerMetaData) {
if self.metadata.layers.entry(name.into()).or_insert(layer.clone()).eq(&layer) {
self.metadata.vector_layers.push(VectorLayer {
id: name.into(), description: layer.description.clone(),
minzoom: Some(layer.minzoom),
maxzoom: Some(layer.maxzoom),
fields: BTreeMap::new(),
});
}
if layer.minzoom < self.metadata.minzoom {
self.metadata.minzoom = layer.minzoom;
}
if layer.maxzoom > self.metadata.maxzoom {
self.metadata.maxzoom = layer.maxzoom;
}
}
pub fn add_tile_wm(&mut self, zoom: u8, x: u32, y: u32, ll_bounds: &LonLatBounds) {
self.metadata.tilestats.total += 1;
self.faces.insert(Face::Face0);
self.add_bounds_wm(zoom, x, y);
self.update_lon_lat_bounds(ll_bounds);
}
pub fn add_tile_s2(&mut self, face: Face, zoom: u8, x: u32, y: u32, ll_bounds: &LonLatBounds) {
self.metadata.tilestats.increment(face);
self.faces.insert(face);
self.add_bounds_s2(face, zoom, x, y);
self.update_lon_lat_bounds(ll_bounds);
}
fn update_center(&mut self) {
let Metadata { minzoom, maxzoom, .. } = self.metadata;
let BBox { left, bottom, right, top } = self.lon_lat_bounds;
self.metadata.center.lon = (left + right) / 2.0;
self.metadata.center.lat = (bottom + top) / 2.0;
self.metadata.center.zoom = (minzoom + maxzoom) >> 1;
}
fn add_bounds_wm(&mut self, zoom: u8, x: u32, y: u32) {
let x = x as u64;
let y = y as u64;
let bbox = self.metadata.bounds.entry(zoom).or_insert(BBox {
left: u64::MAX,
bottom: u64::MAX,
right: 0,
top: 0,
});
bbox.left = bbox.left.min(x);
bbox.bottom = bbox.bottom.min(y);
bbox.right = bbox.right.max(x);
bbox.top = bbox.top.max(y);
}
fn add_bounds_s2(&mut self, face: Face, zoom: u8, x: u32, y: u32) {
let x = x as u64;
let y = y as u64;
let bbox = self.metadata.facesbounds.get_mut(face).entry(zoom).or_insert(BBox {
left: u64::MAX,
bottom: u64::MAX,
right: 0,
top: 0,
});
bbox.left = bbox.left.min(x);
bbox.bottom = bbox.bottom.min(y);
bbox.right = bbox.right.max(x);
bbox.top = bbox.top.max(y);
}
fn update_lon_lat_bounds(&mut self, ll_bounds: &LonLatBounds) {
self.lon_lat_bounds.left = ll_bounds.left.min(self.lon_lat_bounds.left);
self.lon_lat_bounds.bottom = ll_bounds.bottom.min(self.lon_lat_bounds.bottom);
self.lon_lat_bounds.right = ll_bounds.right.max(self.lon_lat_bounds.right);
self.lon_lat_bounds.top = ll_bounds.top.max(self.lon_lat_bounds.top);
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
#[test]
fn it_works() {
let mut meta_builder = MetadataBuilder::default();
meta_builder.set_name("OSM".into());
meta_builder.set_description("A free editable map of the whole world.".into());
meta_builder.set_version("1.0.0".into());
meta_builder.set_scheme("fzxy".into()); meta_builder.set_type("vector".into()); meta_builder.set_encoding("none".into()); meta_builder.set_extension("pbf".into());
meta_builder.add_attribution("OpenStreetMap", "https://www.openstreetmap.org/copyright/");
let shape_str = r#"
{
"class": "string",
"offset": "f64",
"info": {
"name": "string",
"value": "i64"
}
}
"#;
let shape: Shape =
serde_json::from_str(shape_str).unwrap_or_else(|e| panic!("ERROR: {}", e));
let layer = LayerMetaData {
minzoom: 0,
maxzoom: 13,
description: Some("water_lines".into()),
draw_types: Vec::from(&[DrawType::Lines]),
shape: shape.clone(),
m_shape: None,
};
meta_builder.add_layer("water_lines", &layer);
meta_builder.add_tile_wm(
0,
0,
0,
&LonLatBounds { left: -60.0, bottom: -20.0, right: 5.0, top: 60.0 },
);
meta_builder.add_tile_s2(
Face::Face1,
5,
22,
37,
&LonLatBounds { left: -120.0, bottom: -7.0, right: 44.0, top: 72.0 },
);
let resulting_metadata: Metadata = meta_builder.commit();
assert_eq!(
resulting_metadata,
Metadata {
name: "OSM".into(),
description: "A free editable map of the whole world.".into(),
version: "1.0.0".into(),
scheme: "fzxy".into(),
type_: "vector".into(),
encoding: "none".into(),
extension: "pbf".into(),
attribution: BTreeMap::from([(
"OpenStreetMap".into(),
"https://www.openstreetmap.org/copyright/".into()
),]),
bounds: BTreeMap::from([(0, TileBounds { left: 0, bottom: 0, right: 0, top: 0 }),]),
faces: Vec::from(&[Face::Face0, Face::Face1]),
facesbounds: FaceBounds {
face0: BTreeMap::new(),
face1: BTreeMap::from([(
5,
TileBounds { left: 22, bottom: 37, right: 22, top: 37 }
),]),
face2: BTreeMap::new(),
face3: BTreeMap::new(),
face4: BTreeMap::new(),
face5: BTreeMap::new(),
},
minzoom: 0,
maxzoom: 13,
center: Center { lon: -38.0, lat: 26.0, zoom: 6 },
tilestats: TileStatsMetadata {
total: 2,
total_0: 0,
total_1: 1,
total_2: 0,
total_3: 0,
total_4: 0,
total_5: 0,
},
layers: BTreeMap::from([(
"water_lines".into(),
LayerMetaData {
description: Some("water_lines".into()),
minzoom: 0,
maxzoom: 13,
draw_types: Vec::from(&[DrawType::Lines]),
shape: BTreeMap::from([
("class".into(), ShapeType::Primitive(PrimitiveShape::String)),
("offset".into(), ShapeType::Primitive(PrimitiveShape::F64)),
(
"info".into(),
ShapeType::Nested(BTreeMap::from([
("name".into(), ShapeType::Primitive(PrimitiveShape::String)),
("value".into(), ShapeType::Primitive(PrimitiveShape::I64)),
]))
),
]),
m_shape: None,
}
)]),
s2tilejson: "1.0.0".into(),
vector_layers: Vec::from([VectorLayer {
id: "water_lines".into(),
description: Some("water_lines".into()),
minzoom: Some(0),
maxzoom: Some(13),
fields: BTreeMap::new()
}]),
}
);
let meta_str = serde_json::to_string(&resulting_metadata).unwrap();
assert_eq!(meta_str, "{\"s2tilejson\":\"1.0.0\",\"version\":\"1.0.0\",\"name\":\"OSM\",\"scheme\":\"fzxy\",\"description\":\"A free editable map of the whole world.\",\"type\":\"vector\",\"extension\":\"pbf\",\"encoding\":\"none\",\"faces\":[0,1],\"bounds\":{\"0\":[0,0,0,0]},\"facesbounds\":{\"0\":{},\"1\":{\"5\":[22,37,22,37]},\"2\":{},\"3\":{},\"4\":{},\"5\":{}},\"minzoom\":0,\"maxzoom\":13,\"center\":{\"lon\":-38.0,\"lat\":26.0,\"zoom\":6},\"attribution\":{\"OpenStreetMap\":\"https://www.openstreetmap.org/copyright/\"},\"layers\":{\"water_lines\":{\"description\":\"water_lines\",\"minzoom\":0,\"maxzoom\":13,\"draw_types\":[2],\"shape\":{\"class\":\"string\",\"info\":{\"name\":\"string\",\"value\":\"i64\"},\"offset\":\"f64\"}}},\"tilestats\":{\"total\":2,\"0\":0,\"1\":1,\"2\":0,\"3\":0,\"4\":0,\"5\":0},\"vector_layers\":[{\"id\":\"water_lines\",\"description\":\"water_lines\",\"minzoom\":0,\"maxzoom\":13,\"fields\":{}}]}");
let meta_reparsed: Metadata =
serde_json::from_str(&meta_str).unwrap_or_else(|e| panic!("ERROR: {}", e));
assert_eq!(meta_reparsed, resulting_metadata);
}
#[test]
fn test_face() {
assert_eq!(Face::Face0, Face::from(0));
assert_eq!(Face::Face1, Face::from(1));
assert_eq!(Face::Face2, Face::from(2));
assert_eq!(Face::Face3, Face::from(3));
assert_eq!(Face::Face4, Face::from(4));
assert_eq!(Face::Face5, Face::from(5));
assert_eq!(0, u8::from(Face::Face0));
assert_eq!(1, u8::from(Face::Face1));
assert_eq!(2, u8::from(Face::Face2));
assert_eq!(3, u8::from(Face::Face3));
assert_eq!(4, u8::from(Face::Face4));
assert_eq!(5, u8::from(Face::Face5));
}
#[test]
fn test_bbox() {
let bbox: BBox = BBox { left: 0.0, bottom: 0.0, right: 0.0, top: 0.0 };
let json = serde_json::to_string(&bbox).unwrap();
assert_eq!(json, r#"[0.0,0.0,0.0,0.0]"#);
let bbox2: BBox = serde_json::from_str(&json).unwrap();
assert_eq!(bbox, bbox2);
}
#[test]
fn test_tilestats() {
let mut tilestats = TileStatsMetadata {
total: 2,
total_0: 0,
total_1: 1,
total_2: 0,
total_3: 0,
total_4: 0,
total_5: 0,
};
let json = serde_json::to_string(&tilestats).unwrap();
assert_eq!(json, r#"{"total":2,"0":0,"1":1,"2":0,"3":0,"4":0,"5":0}"#);
let tilestats2: TileStatsMetadata = serde_json::from_str(&json).unwrap();
assert_eq!(tilestats, tilestats2);
assert_eq!(tilestats.get(0.into()), 0);
tilestats.increment(0.into());
assert_eq!(tilestats.get(0.into()), 1);
assert_eq!(tilestats.get(1.into()), 1);
tilestats.increment(1.into());
assert_eq!(tilestats.get(1.into()), 2);
assert_eq!(tilestats.get(2.into()), 0);
tilestats.increment(2.into());
assert_eq!(tilestats.get(2.into()), 1);
assert_eq!(tilestats.get(3.into()), 0);
tilestats.increment(3.into());
assert_eq!(tilestats.get(3.into()), 1);
assert_eq!(tilestats.get(4.into()), 0);
tilestats.increment(4.into());
assert_eq!(tilestats.get(4.into()), 1);
assert_eq!(tilestats.get(5.into()), 0);
tilestats.increment(5.into());
assert_eq!(tilestats.get(5.into()), 1);
}
#[test]
fn test_facebounds() {
let mut facebounds = FaceBounds::default();
let face0 = facebounds.get_mut(0.into());
face0.insert(0, TileBounds { left: 0, bottom: 0, right: 0, top: 0 });
let face1 = facebounds.get_mut(1.into());
face1.insert(0, TileBounds { left: 0, bottom: 0, right: 1, top: 1 });
let face2 = facebounds.get_mut(2.into());
face2.insert(0, TileBounds { left: 0, bottom: 0, right: 2, top: 2 });
let face3 = facebounds.get_mut(3.into());
face3.insert(0, TileBounds { left: 0, bottom: 0, right: 3, top: 3 });
let face4 = facebounds.get_mut(4.into());
face4.insert(0, TileBounds { left: 0, bottom: 0, right: 4, top: 4 });
let face5 = facebounds.get_mut(5.into());
face5.insert(0, TileBounds { left: 0, bottom: 0, right: 5, top: 5 });
assert_eq!(
facebounds.get(0.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 0, top: 0 }
);
assert_eq!(
facebounds.get(1.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 1, top: 1 }
);
assert_eq!(
facebounds.get(2.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 2, top: 2 }
);
assert_eq!(
facebounds.get(3.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 3, top: 3 }
);
assert_eq!(
facebounds.get(4.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 4, top: 4 }
);
assert_eq!(
facebounds.get(5.into()).get(&0).unwrap(),
&TileBounds { left: 0, bottom: 0, right: 5, top: 5 }
);
let json = serde_json::to_string(&facebounds).unwrap();
assert_eq!(
json,
"{\"0\":{\"0\":[0,0,0,0]},\"1\":{\"0\":[0,0,1,1]},\"2\":{\"0\":[0,0,2,2]},\"3\":{\"0\"\
:[0,0,3,3]},\"4\":{\"0\":[0,0,4,4]},\"5\":{\"0\":[0,0,5,5]}}"
);
let facebounds2 = serde_json::from_str(&json).unwrap();
assert_eq!(facebounds, facebounds2);
}
#[test]
fn test_drawtype() {
assert_eq!(DrawType::from(1), DrawType::Points);
assert_eq!(DrawType::from(2), DrawType::Lines);
assert_eq!(DrawType::from(3), DrawType::Polygons);
assert_eq!(DrawType::from(4), DrawType::Points3D);
assert_eq!(DrawType::from(5), DrawType::Lines3D);
assert_eq!(DrawType::from(6), DrawType::Polygons3D);
assert_eq!(DrawType::from(7), DrawType::Raster);
assert_eq!(DrawType::from(8), DrawType::Points);
assert_eq!(1, u8::from(DrawType::Points));
assert_eq!(2, u8::from(DrawType::Lines));
assert_eq!(3, u8::from(DrawType::Polygons));
assert_eq!(4, u8::from(DrawType::Points3D));
assert_eq!(5, u8::from(DrawType::Lines3D));
assert_eq!(6, u8::from(DrawType::Polygons3D));
assert_eq!(7, u8::from(DrawType::Raster));
let json = serde_json::to_string(&DrawType::Points).unwrap();
assert_eq!(json, "1");
let drawtype: DrawType = serde_json::from_str(&json).unwrap();
assert_eq!(drawtype, DrawType::Points);
let drawtype: DrawType = serde_json::from_str("2").unwrap();
assert_eq!(drawtype, DrawType::Lines);
let drawtype: DrawType = serde_json::from_str("3").unwrap();
assert_eq!(drawtype, DrawType::Polygons);
let drawtype: DrawType = serde_json::from_str("4").unwrap();
assert_eq!(drawtype, DrawType::Points3D);
let drawtype: DrawType = serde_json::from_str("5").unwrap();
assert_eq!(drawtype, DrawType::Lines3D);
let drawtype: DrawType = serde_json::from_str("6").unwrap();
assert_eq!(drawtype, DrawType::Polygons3D);
let drawtype: DrawType = serde_json::from_str("7").unwrap();
assert_eq!(drawtype, DrawType::Raster);
assert!(serde_json::from_str::<DrawType>("8").is_err());
}
#[test]
fn test_sourcetype() {
assert_eq!(SourceType::from("vector"), SourceType::Vector);
assert_eq!(SourceType::from("json"), SourceType::Json);
assert_eq!(SourceType::from("raster"), SourceType::Raster);
assert_eq!(SourceType::from("raster-dem"), SourceType::RasterDem);
assert_eq!(SourceType::from("sensor"), SourceType::Sensor);
assert_eq!(SourceType::from("markers"), SourceType::Markers);
assert_eq!(SourceType::from("overlay"), SourceType::Unknown);
let json = serde_json::to_string(&SourceType::Vector).unwrap();
assert_eq!(json, "\"vector\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::Vector);
let json = serde_json::to_string(&SourceType::Json).unwrap();
assert_eq!(json, "\"json\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::Json);
let json = serde_json::to_string(&SourceType::Raster).unwrap();
assert_eq!(json, "\"raster\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::Raster);
let json = serde_json::to_string(&SourceType::RasterDem).unwrap();
assert_eq!(json, "\"raster-dem\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::RasterDem);
let json = serde_json::to_string(&SourceType::Sensor).unwrap();
assert_eq!(json, "\"sensor\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::Sensor);
let json = serde_json::to_string(&SourceType::Markers).unwrap();
assert_eq!(json, "\"markers\"");
let sourcetype: SourceType = serde_json::from_str(&json).unwrap();
assert_eq!(sourcetype, SourceType::Markers);
let json = serde_json::to_string(&SourceType::Unknown).unwrap();
assert_eq!(json, "\"unknown\"");
let sourcetype: SourceType = serde_json::from_str(r#""overlay""#).unwrap();
assert_eq!(sourcetype, SourceType::Unknown);
}
#[test]
fn test_encoding() {
assert_eq!(Encoding::from("none"), Encoding::None);
assert_eq!(Encoding::from("gzip"), Encoding::Gzip);
assert_eq!(Encoding::from("br"), Encoding::Brotli);
assert_eq!(Encoding::from("zstd"), Encoding::Zstd);
assert_eq!(core::convert::Into::<&str>::into(Encoding::None), "none");
assert_eq!(core::convert::Into::<&str>::into(Encoding::Gzip), "gzip");
assert_eq!(core::convert::Into::<&str>::into(Encoding::Brotli), "br");
assert_eq!(core::convert::Into::<&str>::into(Encoding::Zstd), "zstd");
assert_eq!(Encoding::from(0), Encoding::None);
assert_eq!(Encoding::from(1), Encoding::Gzip);
assert_eq!(Encoding::from(2), Encoding::Brotli);
assert_eq!(Encoding::from(3), Encoding::Zstd);
assert_eq!(u8::from(Encoding::None), 0);
assert_eq!(u8::from(Encoding::Gzip), 1);
assert_eq!(u8::from(Encoding::Brotli), 2);
assert_eq!(u8::from(Encoding::Zstd), 3);
let json = serde_json::to_string(&Encoding::Gzip).unwrap();
assert_eq!(json, "\"gzip\"");
let encoding: Encoding = serde_json::from_str(&json).unwrap();
assert_eq!(encoding, Encoding::Gzip);
let json = serde_json::to_string(&Encoding::Brotli).unwrap();
assert_eq!(json, "\"br\"");
let encoding: Encoding = serde_json::from_str(&json).unwrap();
assert_eq!(encoding, Encoding::Brotli);
let json = serde_json::to_string(&Encoding::None).unwrap();
assert_eq!(json, "\"none\"");
let encoding: Encoding = serde_json::from_str(&json).unwrap();
assert_eq!(encoding, Encoding::None);
let json = serde_json::to_string(&Encoding::Zstd).unwrap();
assert_eq!(json, "\"zstd\"");
let encoding: Encoding = serde_json::from_str(&json).unwrap();
assert_eq!(encoding, Encoding::Zstd);
}
#[test]
fn test_scheme() {
assert_eq!(Scheme::from("fzxy"), Scheme::Fzxy);
assert_eq!(Scheme::from("tfzxy"), Scheme::Tfzxy);
assert_eq!(Scheme::from("xyz"), Scheme::Xyz);
assert_eq!(Scheme::from("txyz"), Scheme::Txyz);
assert_eq!(Scheme::from("tms"), Scheme::Tms);
assert_eq!(core::convert::Into::<&str>::into(Scheme::Fzxy), "fzxy");
assert_eq!(core::convert::Into::<&str>::into(Scheme::Tfzxy), "tfzxy");
assert_eq!(core::convert::Into::<&str>::into(Scheme::Xyz), "xyz");
assert_eq!(core::convert::Into::<&str>::into(Scheme::Txyz), "txyz");
assert_eq!(core::convert::Into::<&str>::into(Scheme::Tms), "tms");
}
#[test]
fn test_tippecanoe_metadata() {
let meta_str = r#"{
"name": "test_fixture_1.pmtiles",
"description": "test_fixture_1.pmtiles",
"version": "2",
"type": "overlay",
"generator": "tippecanoe v2.5.0",
"generator_options": "./tippecanoe -zg -o test_fixture_1.pmtiles --force",
"vector_layers": [
{
"id": "test_fixture_1pmtiles",
"description": "",
"minzoom": 0,
"maxzoom": 0,
"fields": {}
}
],
"tilestats": {
"layerCount": 1,
"layers": [
{
"layer": "test_fixture_1pmtiles",
"count": 1,
"geometry": "Polygon",
"attributeCount": 0,
"attributes": []
}
]
}
}"#;
let _meta: Metadata =
serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {}", e));
}
#[test]
fn test_mapbox_metadata() {
let meta_str = r#"{
"tilejson": "3.0.0",
"name": "OpenStreetMap",
"description": "A free editable map of the whole world.",
"version": "1.0.0",
"attribution": "(c) OpenStreetMap contributors, CC-BY-SA",
"scheme": "xyz",
"tiles": [
"https://a.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt",
"https://b.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt",
"https://c.tile.custom-osm-tiles.org/{z}/{x}/{y}.mvt"
],
"minzoom": 0,
"maxzoom": 18,
"bounds": [-180, -85, 180, 85],
"fillzoom": 6,
"something_custom": "this is my unique field",
"vector_layers": [
{
"id": "telephone",
"fields": {
"phone_number": "the phone number",
"payment": "how to pay"
}
},
{
"id": "bicycle_parking",
"fields": {
"type": "the type of bike parking",
"year_installed": "the year the bike parking was installed"
}
},
{
"id": "showers",
"fields": {
"water_temperature": "the maximum water temperature",
"wear_sandles": "whether you should wear sandles or not",
"wheelchair": "is the shower wheelchair friendly?"
}
}
]
}"#;
let meta_mapbox: MapboxTileJSONMetadata =
serde_json::from_str(meta_str).unwrap_or_else(|e| panic!("ERROR: {}", e));
let meta_new = meta_mapbox.to_metadata();
assert_eq!(
meta_new,
Metadata {
name: "OpenStreetMap".into(),
description: "A free editable map of the whole world.".into(),
version: "1.0.0".into(),
scheme: Scheme::Xyz,
type_: "vector".into(),
encoding: "none".into(),
extension: "pbf".into(),
attribution: BTreeMap::new(),
vector_layers: meta_mapbox.vector_layers.clone(),
maxzoom: 18,
minzoom: 0,
center: Center { lat: 0.0, lon: 0.0, zoom: 0 },
bounds: WMBounds::default(),
faces: vec![Face::Face0],
facesbounds: FaceBounds::default(),
tilestats: TileStatsMetadata::default(),
layers: LayersMetaData::default(),
s2tilejson: "1.0.0".into(),
},
);
}
}