fast-mvt 0.3.0

Fast Mapbox Vector Tile (MVT) reader and writer
Documentation
use crate::geom::{Command, signed_area};
use crate::proto::GeomType;
use crate::{MvtCoord, MvtError, MvtGeometry, MvtLineString, MvtPolygon, MvtResult};

pub(crate) fn encode_parameter(value: i32) -> u32 {
    ((value << 1) ^ (value >> 31)).cast_unsigned()
}

pub(crate) fn encode_geometry(geometry: &MvtGeometry) -> MvtResult<(GeomType, Vec<u32>)> {
    match geometry {
        MvtGeometry::Point(point) => {
            let coords = [point.0];
            let mut encoder = GeometryEncoder::with_capacity(coords.len());
            encoder.points(coords)?;
            Ok((GeomType::Point, encoder.into_data()))
        }
        MvtGeometry::MultiPoint(points) => {
            if points.0.is_empty() {
                return Ok((GeomType::Point, Vec::new()));
            }
            let mut encoder = GeometryEncoder::with_capacity(points.0.len());
            encoder.points(points.0.iter().map(|point| point.0))?;
            Ok((GeomType::Point, encoder.into_data()))
        }
        MvtGeometry::LineString(line) => {
            let mut encoder = GeometryEncoder::with_capacity(line.0.len());
            encoder.line(line)?;
            Ok((GeomType::Linestring, encoder.into_data()))
        }
        MvtGeometry::MultiLineString(lines) => {
            if lines.0.is_empty() {
                return Ok((GeomType::Linestring, Vec::new()));
            }
            let capacity = lines.0.iter().map(|line| line.0.len()).sum();
            let mut encoder = GeometryEncoder::with_capacity(capacity);
            for line in &lines.0 {
                encoder.line(line)?;
            }
            Ok((GeomType::Linestring, encoder.into_data()))
        }
        MvtGeometry::Polygon(polygon) => {
            let mut encoder = GeometryEncoder::with_capacity(polygon_vertex_count(polygon));
            encoder.polygon(polygon)?;
            Ok((GeomType::Polygon, encoder.into_data()))
        }
        MvtGeometry::MultiPolygon(polygons) => {
            if polygons.0.is_empty() {
                return Ok((GeomType::Polygon, Vec::new()));
            }
            let capacity = polygons.0.iter().map(polygon_vertex_count).sum();
            let mut encoder = GeometryEncoder::with_capacity(capacity);
            for polygon in &polygons.0 {
                encoder.polygon(polygon)?;
            }
            Ok((GeomType::Polygon, encoder.into_data()))
        }
        MvtGeometry::GeometryCollection(collection) if collection.0.len() == 1 => {
            encode_geometry(&collection.0[0])
        }
        MvtGeometry::GeometryCollection(_) => Err(MvtError::UnsupportedGeometry(
            "GeometryCollection with multiple items",
        )),
        MvtGeometry::Line(_) => Err(MvtError::UnsupportedGeometry("Line")),
        MvtGeometry::Rect(_) => Err(MvtError::UnsupportedGeometry("Rect")),
        MvtGeometry::Triangle(_) => Err(MvtError::UnsupportedGeometry("Triangle")),
    }
}

fn polygon_vertex_count(polygon: &MvtPolygon) -> usize {
    polygon.exterior().0.len()
        + polygon
            .interiors()
            .iter()
            .map(|line| line.0.len())
            .sum::<usize>()
}

struct GeometryEncoder {
    data: Vec<u32>,
    cursor: MvtCoord,
}

impl GeometryEncoder {
    fn with_capacity(coords: usize) -> Self {
        Self {
            data: Vec::with_capacity(1 + coords.saturating_mul(2)),
            cursor: MvtCoord { x: 0, y: 0 },
        }
    }

    fn into_data(self) -> Vec<u32> {
        self.data
    }

    fn points(&mut self, coords: impl IntoIterator<Item = MvtCoord>) -> MvtResult<()> {
        let start = self.data.len();
        self.data.push(0);
        let mut count = 0_u32;
        for coord in coords {
            self.push_delta(coord);
            count += 1;
        }
        if count == 0 {
            return Err(MvtError::InvalidGeometry);
        }
        self.data[start] = Command::MoveTo.encode(count)?;
        Ok(())
    }

    fn line(&mut self, line: &MvtLineString) -> MvtResult<()> {
        let coords = &line.0;
        if coords.is_empty() {
            return Err(MvtError::InvalidGeometry);
        }
        self.data.push(Command::MoveTo.encode(1)?);
        self.push_delta(coords[0]);
        if coords.len() > 1 {
            self.data
                .push(Command::LineTo.encode(u32_index(coords.len() - 1)?)?);
            for &coord in &coords[1..] {
                self.push_delta(coord);
            }
        }
        Ok(())
    }

    fn polygon(&mut self, polygon: &MvtPolygon) -> MvtResult<()> {
        self.ring(polygon.exterior(), true)?;
        for ring in polygon.interiors() {
            self.ring(ring, false)?;
        }
        Ok(())
    }

    fn ring(&mut self, ring: &MvtLineString, exterior: bool) -> MvtResult<()> {
        let coords = without_trailing_duplicate(&ring.0);
        if coords.is_empty() {
            return Err(MvtError::InvalidGeometry);
        }
        let area = signed_area(coords);
        let reverse = area != 0 && (area > 0) != exterior;
        self.data.push(Command::MoveTo.encode(1)?);
        let first = ring_coord(coords, 0, reverse);
        self.push_delta(first);
        if coords.len() > 1 {
            self.data
                .push(Command::LineTo.encode(u32_index(coords.len() - 1)?)?);
            for idx in 1..coords.len() {
                self.push_delta(ring_coord(coords, idx, reverse));
            }
        }
        self.data.push(Command::ClosePath.encode(1)?);
        Ok(())
    }

    fn push_delta(&mut self, coord: MvtCoord) {
        self.data
            .push(encode_parameter(coord.x.saturating_sub(self.cursor.x)));
        self.data
            .push(encode_parameter(coord.y.saturating_sub(self.cursor.y)));
        self.cursor = coord;
    }
}

fn without_trailing_duplicate(coords: &[MvtCoord]) -> &[MvtCoord] {
    if coords.len() >= 2 && coords.first() == coords.last() {
        &coords[..coords.len() - 1]
    } else {
        coords
    }
}

fn ring_coord(coords: &[MvtCoord], idx: usize, reverse: bool) -> MvtCoord {
    if reverse {
        coords[coords.len() - 1 - idx]
    } else {
        coords[idx]
    }
}

fn u32_index(value: usize) -> MvtResult<u32> {
    u32::try_from(value).map_err(|_| MvtError::IndexOverflow(value))
}

#[cfg(test)]
mod tests {
    use geo_types::{
        GeometryCollection, Line, LineString, MultiLineString, MultiPoint, MultiPolygon, Rect,
        Triangle,
    };

    use super::*;

    #[test]
    fn encodes_spec_point() {
        let geometry = MvtGeometry::Point((25, 17).into());
        let (_, data) = encode_geometry(&geometry).unwrap();
        assert_eq!(data, vec![9, 50, 34]);
    }

    #[test]
    fn encodes_spec_linestring() {
        let geometry = MvtGeometry::LineString(LineString(vec![
            (2, 2).into(),
            (2, 10).into(),
            (10, 10).into(),
        ]));
        let (_, data) = encode_geometry(&geometry).unwrap();
        assert_eq!(data, vec![9, 4, 4, 18, 0, 16, 16, 0]);
    }

    #[test]
    fn encodes_spec_polygon() {
        let geometry = MvtGeometry::Polygon(MvtPolygon::new(
            LineString(vec![(3, 6).into(), (8, 12).into(), (20, 34).into()]),
            vec![],
        ));
        let (_, data) = encode_geometry(&geometry).unwrap();
        assert_eq!(data, vec![9, 6, 12, 18, 10, 12, 24, 44, 15]);
    }

    #[test]
    fn encodes_empty_collections_and_geometry_collection_delegate() {
        assert_eq!(
            encode_geometry(&MvtGeometry::MultiPoint(MultiPoint(vec![]))).unwrap(),
            (GeomType::Point, Vec::new())
        );
        assert_eq!(
            encode_geometry(&MvtGeometry::MultiLineString(MultiLineString(vec![]))).unwrap(),
            (GeomType::Linestring, Vec::new())
        );
        assert_eq!(
            encode_geometry(&MvtGeometry::MultiPolygon(MultiPolygon(vec![]))).unwrap(),
            (GeomType::Polygon, Vec::new())
        );
        let collection =
            MvtGeometry::GeometryCollection(GeometryCollection(vec![MvtGeometry::Point(
                (1, 2).into(),
            )]));
        let direct = encode_geometry(&MvtGeometry::Point((1, 2).into())).unwrap();
        assert_eq!(encode_geometry(&collection).unwrap(), direct);
    }

    #[test]
    fn unsupported_and_invalid_geometries_are_errors() {
        let collection = MvtGeometry::GeometryCollection(GeometryCollection(vec![
            MvtGeometry::Point((0, 0).into()),
            MvtGeometry::Point((1, 1).into()),
        ]));
        assert!(matches!(
            encode_geometry(&collection),
            Err(MvtError::UnsupportedGeometry(_))
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::Line(Line::new((0, 0), (1, 1)))),
            Err(MvtError::UnsupportedGeometry("Line"))
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::Rect(Rect::new((0, 0), (1, 1)))),
            Err(MvtError::UnsupportedGeometry("Rect"))
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::Triangle(Triangle(
                (0, 0).into(),
                (1, 0).into(),
                (0, 1).into()
            ))),
            Err(MvtError::UnsupportedGeometry("Triangle"))
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::MultiPoint(MultiPoint(vec![(1, 1).into()]))),
            Ok((GeomType::Point, _))
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::LineString(LineString(vec![]))),
            Err(MvtError::InvalidGeometry)
        ));
        assert!(matches!(
            encode_geometry(&MvtGeometry::Polygon(MvtPolygon::new(
                LineString(vec![]),
                vec![]
            ))),
            Err(MvtError::InvalidGeometry)
        ));
        assert_eq!(signed_area(&[]), 0);
    }
}