#![no_std]
#![deny(missing_docs)]
extern crate alloc;
use serde::{Serialize, Deserialize, Serializer, Deserializer};
use serde::ser::SerializeTuple;
use serde::de::{self, SeqAccess, Visitor};
use alloc::borrow::ToOwned;
use alloc::collections::BTreeSet;
use alloc::collections::BTreeMap;
use alloc::string::String;
use alloc::vec::Vec;
use alloc::fmt;
#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
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,
}
}
}
#[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(Serialize, Deserialize, Copy, Clone, Debug, PartialEq)]
pub enum DrawType {
Points = 1,
Lines = 2,
Polygons = 3,
Points3D = 4,
Lines3D = 5,
Polygons3D = 6,
}
#[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 {
pub total: u64,
#[serde(rename = "0")]
pub total_0: u64,
#[serde(rename = "1")]
pub total_1: u64,
#[serde(rename = "2")]
pub total_2: u64,
#[serde(rename = "3")]
pub total_3: u64,
#[serde(rename = "4")]
pub total_4: u64,
#[serde(rename = "5")]
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, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum SourceType {
#[default] Vector,
Json,
Raster,
#[serde(rename = "raster-dem")]
RasterDem,
Sensor,
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,
_ => SourceType::Unknown,
}
}
}
#[derive(Serialize, Deserialize, Debug, Default, Clone, PartialEq)]
#[serde(rename_all = "lowercase")]
pub enum Encoding {
Gzip,
#[serde(rename = "br")]
Brotli,
Zstd,
#[default] None,
}
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 String {
fn from(encoding: Encoding) -> Self {
match encoding {
Encoding::Gzip => "gzip".into(),
Encoding::Brotli => "br".into(),
Encoding::Zstd => "zstd".into(),
Encoding::None => "none".into(),
}
}
}
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 String {
fn from(scheme: Scheme) -> Self {
match scheme {
Scheme::Fzxy => "fzxy".into(),
Scheme::Tfzxy => "tfzxy".into(),
Scheme::Xyz => "xyz".into(),
Scheme::Txyz => "txyz".into(),
Scheme::Tms => "tms".into(),
}
}
}
#[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 {
pub s2tilejson: String,
pub version: String,
pub name: String,
pub scheme: Scheme,
pub description: String,
#[serde(rename = "type")]
pub type_: SourceType,
pub extension: String,
pub encoding: Encoding,
pub faces: Vec<Face>,
pub bounds: WMBounds,
pub facesbounds: FaceBounds,
pub minzoom: u8,
pub maxzoom: u8,
pub center: Center,
pub attribution: Attribution,
pub layers: LayersMetaData,
pub tilestats: TileStatsMetadata,
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(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::*;
#[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() }]),
});
}
}