fast-mvt 0.3.0

Fast Mapbox Vector Tile (MVT) reader and writer
Documentation
use std::borrow::Cow;
use std::num::NonZeroU32;

use geo_types::{
    Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
};

pub type MvtExtent = NonZeroU32;
pub type MvtCoord = Coord<i32>;
pub type MvtPoint = Point<i32>;
pub type MvtLineString = LineString<i32>;
pub type MvtPolygon = Polygon<i32>;
pub type MvtMultiPoint = MultiPoint<i32>;
pub type MvtMultiLineString = MultiLineString<i32>;
pub type MvtMultiPolygon = MultiPolygon<i32>;
pub type MvtGeometry = Geometry<i32>;

pub const DEFAULT_EXTENT: MvtExtent = MvtExtent::new(4096).unwrap();

#[derive(Debug, Clone, Default, PartialEq)]
pub struct MvtTile {
    pub layers: Vec<MvtLayer>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MvtLayer {
    pub name: String,
    pub extent: NonZeroU32,
    pub features: Vec<MvtFeature>,
}

#[derive(Debug, Clone, PartialEq)]
pub struct MvtFeature {
    pub id: Option<u64>,
    pub geometry: MvtGeometry,
    pub properties: Vec<(String, MvtValue)>,
}

impl MvtTile {
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_layer(&mut self, layer: MvtLayer) {
        self.layers.push(layer);
    }

    #[cfg(feature = "writer")]
    pub fn encode(self) -> crate::MvtResult<Vec<u8>> {
        crate::writer::encode_tile(self)
    }
}

impl MvtLayer {
    #[must_use]
    pub fn new(name: impl Into<String>, extent: MvtExtent) -> Self {
        Self {
            name: name.into(),
            extent,
            features: Vec::new(),
        }
    }

    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    #[must_use]
    pub fn num_features(&self) -> usize {
        self.features.len()
    }

    pub fn add_feature(&mut self, feature: MvtFeature) {
        self.features.push(feature);
    }
}

impl MvtFeature {
    #[must_use]
    pub fn new(geometry: MvtGeometry) -> Self {
        Self {
            id: None,
            geometry,
            properties: Vec::new(),
        }
    }

    pub fn set_id(&mut self, id: u64) {
        self.id = Some(id);
    }

    #[must_use]
    pub fn num_tags(&self) -> usize {
        self.properties.len()
    }

    pub fn add_tag(&mut self, key: impl Into<String>, value: MvtValue) {
        self.properties.push((key.into(), value));
    }

    pub fn add_tag_string(&mut self, key: impl Into<String>, value: impl Into<String>) {
        self.add_tag(key, MvtValue::String(value.into()));
    }

    pub fn add_tag_float(&mut self, key: impl Into<String>, value: f32) {
        self.add_tag(key, MvtValue::Float(value));
    }

    pub fn add_tag_double(&mut self, key: impl Into<String>, value: f64) {
        self.add_tag(key, MvtValue::Double(value));
    }

    pub fn add_tag_int(&mut self, key: impl Into<String>, value: i64) {
        self.add_tag(key, MvtValue::Int(value));
    }

    pub fn add_tag_uint(&mut self, key: impl Into<String>, value: u64) {
        self.add_tag(key, MvtValue::UInt(value));
    }

    pub fn add_tag_sint(&mut self, key: impl Into<String>, value: i64) {
        self.add_tag(key, MvtValue::SInt(value));
    }

    pub fn add_tag_bool(&mut self, key: impl Into<String>, value: bool) {
        self.add_tag(key, MvtValue::Bool(value));
    }
}

#[derive(Debug, Clone)]
pub enum MvtValue {
    String(String),
    Float(f32),
    Double(f64),
    Int(i64),
    UInt(u64),
    SInt(i64),
    Bool(bool),
    Null,
}

impl From<String> for MvtValue {
    fn from(value: String) -> Self {
        Self::String(value)
    }
}

impl From<&str> for MvtValue {
    fn from(value: &str) -> Self {
        Self::String(value.to_string())
    }
}

impl From<Cow<'_, str>> for MvtValue {
    fn from(value: Cow<'_, str>) -> Self {
        Self::String(value.into_owned())
    }
}

impl From<f32> for MvtValue {
    fn from(value: f32) -> Self {
        Self::Float(value)
    }
}

impl From<f64> for MvtValue {
    fn from(value: f64) -> Self {
        Self::Double(value)
    }
}

impl From<i64> for MvtValue {
    fn from(value: i64) -> Self {
        Self::Int(value)
    }
}

impl From<i32> for MvtValue {
    fn from(value: i32) -> Self {
        Self::Int(i64::from(value))
    }
}

impl From<i16> for MvtValue {
    fn from(value: i16) -> Self {
        Self::Int(i64::from(value))
    }
}

impl From<i8> for MvtValue {
    fn from(value: i8) -> Self {
        Self::Int(i64::from(value))
    }
}

impl From<u64> for MvtValue {
    fn from(value: u64) -> Self {
        Self::UInt(value)
    }
}

impl From<u32> for MvtValue {
    fn from(value: u32) -> Self {
        Self::UInt(u64::from(value))
    }
}

impl From<u16> for MvtValue {
    fn from(value: u16) -> Self {
        Self::UInt(u64::from(value))
    }
}

impl From<u8> for MvtValue {
    fn from(value: u8) -> Self {
        Self::UInt(u64::from(value))
    }
}

impl From<bool> for MvtValue {
    fn from(value: bool) -> Self {
        Self::Bool(value)
    }
}

#[cfg(feature = "json")]
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum MvtJsonValueError {
    #[error("non-finite float cannot be represented as JSON")]
    NonFiniteFloat,
    #[error("invalid JSON number")]
    InvalidJsonNumber,
    #[error("JSON array cannot be represented as an MVT value")]
    UnsupportedJsonArray,
    #[error("JSON object cannot be represented as an MVT value")]
    UnsupportedJsonObject,
}

#[cfg(feature = "json")]
impl TryFrom<MvtValue> for serde_json::Value {
    type Error = MvtJsonValueError;

    fn try_from(value: MvtValue) -> Result<Self, Self::Error> {
        match value {
            MvtValue::String(value) => Ok(Self::String(value)),
            MvtValue::Float(value) => json_number(f64::from(value)),
            MvtValue::Double(value) => json_number(value),
            MvtValue::Int(value) | MvtValue::SInt(value) => Ok(value.into()),
            MvtValue::UInt(value) => Ok(value.into()),
            MvtValue::Bool(value) => Ok(Self::Bool(value)),
            MvtValue::Null => Ok(Self::Null),
        }
    }
}

#[cfg(feature = "json")]
impl TryFrom<serde_json::Value> for MvtValue {
    type Error = MvtJsonValueError;

    fn try_from(value: serde_json::Value) -> Result<Self, Self::Error> {
        use serde_json::Value;

        match value {
            Value::Null => Ok(Self::Null),
            Value::Bool(value) => Ok(Self::Bool(value)),
            Value::Number(value) => {
                if let Some(value) = value.as_i64() {
                    Ok(Self::Int(value))
                } else if let Some(value) = value.as_u64() {
                    Ok(Self::UInt(value))
                } else if let Some(value) = value.as_f64() {
                    Ok(Self::Double(value))
                } else {
                    Err(MvtJsonValueError::InvalidJsonNumber)
                }
            }
            Value::String(value) => Ok(Self::String(value)),
            Value::Array(_) => Err(MvtJsonValueError::UnsupportedJsonArray),
            Value::Object(_) => Err(MvtJsonValueError::UnsupportedJsonObject),
        }
    }
}

#[cfg(feature = "json")]
fn json_number(value: f64) -> Result<serde_json::Value, MvtJsonValueError> {
    serde_json::Number::from_f64(value)
        .map(serde_json::Value::Number)
        .ok_or(MvtJsonValueError::NonFiniteFloat)
}

impl PartialEq for MvtValue {
    fn eq(&self, other: &Self) -> bool {
        match (self, other) {
            (Self::String(a), Self::String(b)) => a == b,
            (Self::Float(a), Self::Float(b)) => a.to_bits() == b.to_bits(),
            (Self::Double(a), Self::Double(b)) => a.to_bits() == b.to_bits(),
            (Self::Int(a), Self::Int(b)) | (Self::SInt(a), Self::SInt(b)) => a == b,
            (Self::UInt(a), Self::UInt(b)) => a == b,
            (Self::Bool(a), Self::Bool(b)) => a == b,
            (Self::Null, Self::Null) => true,
            _ => false,
        }
    }
}

impl Eq for MvtValue {}

impl std::hash::Hash for MvtValue {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
        std::mem::discriminant(self).hash(state);
        match self {
            Self::String(v) => v.hash(state),
            Self::Float(v) => v.to_bits().hash(state),
            Self::Double(v) => v.to_bits().hash(state),
            Self::Int(v) | Self::SInt(v) => v.hash(state),
            Self::UInt(v) => v.hash(state),
            Self::Bool(v) => v.hash(state),
            Self::Null => {}
        }
    }
}

#[cfg(all(test, feature = "json"))]
mod tests_json {
    use serde_json::json;

    use super::*;

    #[test]
    fn mvt_values_convert_to_json_values() {
        assert_eq!(
            serde_json::Value::try_from(MvtValue::String("name".into())),
            Ok(json!("name"))
        );
        assert_eq!(
            serde_json::Value::try_from(MvtValue::Int(-3)),
            Ok(json!(-3))
        );
        assert_eq!(serde_json::Value::try_from(MvtValue::UInt(4)), Ok(json!(4)));
        assert_eq!(
            serde_json::Value::try_from(MvtValue::Double(1.5)),
            Ok(json!(1.5))
        );
        assert_eq!(
            serde_json::Value::try_from(MvtValue::Bool(true)),
            Ok(json!(true))
        );
        assert_eq!(
            serde_json::Value::try_from(MvtValue::Null),
            Ok(serde_json::Value::Null)
        );
        assert_eq!(
            serde_json::Value::try_from(MvtValue::Double(f64::NAN)),
            Err(MvtJsonValueError::NonFiniteFloat)
        );
    }

    #[test]
    fn json_values_convert_to_mvt_values() {
        assert_eq!(
            MvtValue::try_from(json!("name")),
            Ok(MvtValue::String("name".into()))
        );
        assert_eq!(MvtValue::try_from(json!(-3)), Ok(MvtValue::Int(-3)));
        assert_eq!(MvtValue::try_from(json!(1.5)), Ok(MvtValue::Double(1.5)));
        assert_eq!(MvtValue::try_from(json!(true)), Ok(MvtValue::Bool(true)));
        assert_eq!(
            MvtValue::try_from(serde_json::Value::Null),
            Ok(MvtValue::Null)
        );
        assert_eq!(
            MvtValue::try_from(json!([])),
            Err(MvtJsonValueError::UnsupportedJsonArray)
        );
        assert_eq!(
            MvtValue::try_from(json!({})),
            Err(MvtJsonValueError::UnsupportedJsonObject)
        );
    }
}