fast-mvt 0.6.2

Fast Mapbox Vector Tile (MVT) reader and writer
Documentation
use std::fmt;

use buffa::MessageView as _;

use super::MvtLayerRef;
use crate::generated::vector_tile::{Tile, TileView};
use crate::types::DEFAULT_EXTENT;
use crate::{MvtError, MvtResult, MvtTile};

impl Tile {
    pub fn from_reader(reader: &MvtReaderRef<'_>) -> MvtResult<Self> {
        let mut tile = reader.to_proto()?;
        for layer in &mut tile.layers {
            layer.extent.get_or_insert(DEFAULT_EXTENT.get());
        }
        Ok(tile)
    }
}

#[derive(Clone)]
pub struct MvtReaderRef<'a>(TileView<'a>);

impl<'a> MvtReaderRef<'a> {
    pub fn new(data: &'a [u8]) -> MvtResult<Self> {
        let tile = TileView::decode_view(data)?;
        for layer in &tile.layers {
            if layer.name.is_empty() {
                return Err(MvtError::MissingLayerName);
            }
            if layer.version < 1 || layer.version > 3 {
                return Err(MvtError::UnsupportedVersion {
                    layer: layer.name.to_string(),
                    version: layer.version,
                });
            }
        }
        Ok(Self(tile))
    }

    #[must_use]
    pub fn layers(&self) -> impl ExactSizeIterator<Item = MvtLayerRef<'_>> {
        self.0.layers.iter().map(MvtLayerRef::new)
    }

    #[must_use]
    pub fn layer_count(&self) -> usize {
        self.0.layers.len()
    }

    pub fn to_tile(&self) -> MvtResult<MvtTile> {
        let mut layers = Vec::with_capacity(self.0.layers.len());
        for layer in self.layers() {
            layers.push(layer.to_layer()?);
        }
        Ok(MvtTile { layers })
    }

    pub fn to_proto(&self) -> MvtResult<Tile> {
        Ok(self.0.to_owned_message()?)
    }
}

impl fmt::Debug for MvtReaderRef<'_> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        for (index, layer) in self.layers().enumerate() {
            if index > 0 {
                writeln!(
                    f,
                    "============================================================="
                )?;
            }
            writeln!(f, "layer: {index}")?;
            // Each layer's `Debug` block is already newline-terminated.
            write!(f, "{layer:?}")?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use geo_types::Geometry;

    use super::*;
    use crate::MvtValue;
    use crate::generated::vector_tile::tile as proto_tile;
    use crate::reader::MvtValueRef;
    use crate::reader::tests::{encode_layer, first_feature};

    #[test]
    fn borrowed_api_reads_accessors_properties_and_repeated_points() {
        let layer = proto_tile::Layer {
            version: 3,
            name: "places".to_string(),
            keys: vec![
                "string".into(),
                "float".into(),
                "double".into(),
                "int".into(),
                "uint".into(),
                "sint".into(),
                "bool".into(),
                "null".into(),
            ],
            values: vec![
                proto_tile::Value {
                    string_value: Some("name".into()),
                    ..Default::default()
                },
                proto_tile::Value {
                    float_value: Some(1.25),
                    ..Default::default()
                },
                proto_tile::Value {
                    double_value: Some(2.5),
                    ..Default::default()
                },
                proto_tile::Value {
                    int_value: Some(-3),
                    ..Default::default()
                },
                proto_tile::Value {
                    uint_value: Some(4),
                    ..Default::default()
                },
                proto_tile::Value {
                    sint_value: Some(-5),
                    ..Default::default()
                },
                proto_tile::Value {
                    bool_value: Some(true),
                    ..Default::default()
                },
                proto_tile::Value::default(),
            ],
            features: vec![proto_tile::Feature {
                id: Some(7),
                tags: (0_u32..8).flat_map(|idx| [idx, idx]).collect(),
                r#type: Some(proto_tile::GeomType::Point),
                geometry: vec![9, 2, 4, 9, 6, 8],
            }],
            ..Default::default()
        };
        let bytes = encode_layer(layer);
        let reader = MvtReaderRef::new(&bytes).unwrap();
        let layer = reader.layers().next().unwrap();

        assert_eq!(reader.layer_count(), 1);
        assert_eq!(layer.name(), "places");
        assert_eq!(layer.version(), 3);
        assert_eq!(layer.extent(), DEFAULT_EXTENT.get());
        assert_eq!(layer.feature_count(), 1);
        assert!(!layer.is_empty());
        assert_eq!(
            layer.keys(),
            [
                "string", "float", "double", "int", "uint", "sint", "bool", "null"
            ]
        );
        assert_eq!(layer.values().len(), 8);

        let feature = layer.features().next().unwrap();
        assert_eq!(feature.id(), Some(7));
        assert!(feature.has_properties());
        assert_eq!(
            feature.tags(),
            [0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7]
        );
        assert_eq!(feature.geometry_commands(), [9, 2, 4, 9, 6, 8]);
        assert_eq!(feature.geom_type(), Some(proto_tile::GeomType::Point));
        assert_eq!(feature.geom_type_value(), Some(1));

        let properties = feature.properties_vec().unwrap();
        assert_eq!(properties[0].1, MvtValueRef::String("name"));
        assert_eq!(properties[1].1, MvtValueRef::Float(1.25));
        assert_eq!(properties[2].1, MvtValueRef::Double(2.5));
        assert_eq!(properties[3].1, MvtValueRef::Int(-3));
        assert_eq!(properties[4].1, MvtValueRef::UInt(4));
        assert_eq!(properties[5].1, MvtValueRef::SInt(-5));
        assert_eq!(properties[6].1, MvtValueRef::Bool(true));
        assert_eq!(properties[7].1, MvtValueRef::Null);
        assert_eq!(
            properties[0].1.into_owned(),
            MvtValue::String("name".into())
        );
        assert_eq!(properties[7].1.into_owned(), MvtValue::Null);

        let Geometry::MultiPoint(points) = feature.geometry().unwrap() else {
            panic!("expected multipoint");
        };
        assert_eq!(points.0.len(), 2);
        assert!(matches!(
            feature.geometry().unwrap(),
            Geometry::MultiPoint(_)
        ));
    }

    #[test]
    fn invalid_versions_and_geometry_types_are_errors() {
        let layer = proto_tile::Layer {
            version: 4,
            name: "bad".into(),
            ..Default::default()
        };
        let bytes = encode_layer(layer);
        assert!(matches!(
            MvtReaderRef::new(&bytes),
            Err(MvtError::UnsupportedVersion { version: 4, .. })
        ));

        let feature = proto_tile::Feature {
            r#type: Some(proto_tile::GeomType::Unknown),
            geometry: vec![9, 0, 0],
            ..Default::default()
        };
        let mut layer = proto_tile::Layer {
            version: 2,
            name: "geometry".into(),
            ..Default::default()
        };
        layer.features = vec![feature];
        let bytes = encode_layer(layer);
        let reader = MvtReaderRef::new(&bytes).unwrap();
        let feature = first_feature(&reader);
        assert!(matches!(feature.geometry(), Err(MvtError::InvalidGeometry)));
    }

    #[test]
    fn debug_separates_multiple_layers() {
        use buffa::Message as _;

        let layer = |name: &str| proto_tile::Layer {
            version: 2,
            name: name.into(),
            ..Default::default()
        };
        let bytes = Tile {
            layers: vec![layer("a"), layer("b")],
        }
        .encode_to_vec();
        let reader = MvtReaderRef::new(&bytes).unwrap();
        insta::assert_debug_snapshot!(reader, @"
        layer: 0
          name: a
          version: 2
          extent: 4096
        =============================================================
        layer: 1
          name: b
          version: 2
          extent: 4096
        ");
    }
}