Skip to main content

fast_mvt/reader/
feature.rs

1use std::fmt::{self, Write as _};
2
3use buffa::Enumeration as _;
4use geo_types::{Coord, Geometry, LineString, Point, Polygon};
5
6use super::property::{MvtPropertyIter, MvtValueRef};
7use crate::generated::vector_tile::tile as proto_tile;
8use crate::geom_reader::decode_geometry;
9use crate::{MvtFeature, MvtGeometry, MvtResult};
10
11#[derive(Copy, Clone)]
12pub struct MvtFeatureRef<'a> {
13    layer: &'a proto_tile::LayerView<'a>,
14    feature: &'a proto_tile::FeatureView<'a>,
15}
16
17impl<'a> MvtFeatureRef<'a> {
18    pub(crate) fn new(
19        layer: &'a proto_tile::LayerView<'a>,
20        feature: &'a proto_tile::FeatureView<'a>,
21    ) -> Self {
22        Self { layer, feature }
23    }
24
25    #[must_use]
26    pub fn id(self) -> Option<u64> {
27        self.feature.id
28    }
29
30    #[must_use]
31    pub fn tags(self) -> &'a [u32] {
32        &self.feature.tags
33    }
34
35    #[must_use]
36    pub fn geometry_commands(self) -> &'a [u32] {
37        &self.feature.geometry
38    }
39
40    #[must_use]
41    pub fn geom_type(self) -> Option<proto_tile::GeomType> {
42        self.feature.r#type
43    }
44
45    #[must_use]
46    pub fn geom_type_value(self) -> Option<i32> {
47        self.feature.r#type.map(|v| v.to_i32())
48    }
49
50    #[must_use]
51    pub fn has_properties(self) -> bool {
52        !self.feature.tags.is_empty()
53    }
54
55    #[must_use]
56    pub fn properties(self) -> MvtPropertyIter<'a> {
57        MvtPropertyIter::new(
58            &self.layer.keys,
59            &self.layer.values,
60            self.feature.tags.chunks(2),
61        )
62    }
63
64    pub fn properties_vec(self) -> MvtResult<Vec<(&'a str, MvtValueRef<'a>)>> {
65        self.properties().collect()
66    }
67
68    pub fn geometry(self) -> MvtResult<MvtGeometry> {
69        decode_geometry(self.geom_type(), &self.feature.geometry)
70    }
71
72    pub fn to_feature(self) -> MvtResult<MvtFeature> {
73        let properties = self
74            .properties()
75            .map(|property| property.map(|(key, value)| (key.to_string(), value.into_owned())))
76            .collect::<MvtResult<Vec<_>>>()?;
77        Ok(MvtFeature {
78            id: self.id(),
79            geometry: self.geometry()?,
80            properties,
81        })
82    }
83}
84
85impl fmt::Debug for MvtFeatureRef<'_> {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        match self.id() {
88            Some(id) => writeln!(f, "    id: {id}")?,
89            None => writeln!(f, "    id: (none)")?,
90        }
91        match self.geometry() {
92            Ok(geometry) => fmt_geometry(f, &geometry)?,
93            Err(error) => writeln!(f, "    geometry: <invalid geometry: {error}>")?,
94        }
95        write!(f, "    properties:")?;
96        if self.has_properties() {
97            writeln!(f)?;
98            for property in self.properties() {
99                match property {
100                    Ok((key, value)) => writeln!(f, "      {key} = {value:?}")?,
101                    Err(error) => writeln!(f, "      <invalid property: {error}>")?,
102                }
103            }
104        } else {
105            writeln!(f, " (none)")?;
106        }
107        Ok(())
108    }
109}
110
111/// Renders the geometry after the `geometry:` label. A geometry that is a
112/// single element (one point, linestring, or polygon ring) is printed inline;
113/// anything with multiple elements is broken onto indented lines.
114fn fmt_geometry(f: &mut fmt::Formatter<'_>, geometry: &MvtGeometry) -> fmt::Result {
115    let mut lines = Vec::new();
116    match geometry {
117        Geometry::Point(point) => lines.push(point_line(*point)),
118        Geometry::MultiPoint(points) => lines.extend(points.iter().map(|p| point_line(*p))),
119        Geometry::LineString(line) => lines.push(line_line(line)),
120        Geometry::MultiLineString(strings) => lines.extend(strings.iter().map(line_line)),
121        Geometry::Polygon(polygon) => push_polygon(&mut lines, polygon),
122        Geometry::MultiPolygon(p) => p.iter().for_each(|v| push_polygon(&mut lines, v)),
123        other => lines.push(format!("{other:?}")),
124    }
125
126    if let [single] = lines.as_slice() {
127        writeln!(f, "    geometry: {single}")
128    } else {
129        writeln!(f, "    geometry:")?;
130        lines.iter().try_for_each(|v| writeln!(f, "      {v}"))
131    }
132}
133
134fn point_line(point: Point<i32>) -> String {
135    format!("POINT({},{})", point.x(), point.y())
136}
137
138fn line_line(line: &LineString<i32>) -> String {
139    format!("LINESTRING[count={}]({})", line.0.len(), Coords(&line.0))
140}
141
142fn push_polygon(lines: &mut Vec<String>, polygon: &Polygon<i32>) {
143    lines.push(ring_line(polygon.exterior(), "OUTER"));
144    lines.extend(polygon.interiors().iter().map(|r| ring_line(r, "INNER")));
145}
146
147fn ring_line(ring: &LineString<i32>, winding: &str) -> String {
148    format!(
149        "RING[count={}]({})[{winding}]",
150        ring.0.len(),
151        Coords(&ring.0)
152    )
153}
154
155/// Renders a coordinate slice as `x y,x y,...`.
156struct Coords<'a>(&'a [Coord<i32>]);
157
158impl fmt::Display for Coords<'_> {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        for (index, coord) in self.0.iter().enumerate() {
161            if index > 0 {
162                f.write_char(',')?;
163            }
164            write!(f, "{} {}", coord.x, coord.y)?;
165        }
166        Ok(())
167    }
168}
169
170#[cfg(test)]
171mod tests {
172    use geo_types::{Geometry, MultiLineString, MultiPoint, MultiPolygon};
173
174    use super::*;
175    use crate::reader::tests::{encode_layer, first_feature};
176    use crate::{MvtError, MvtReaderRef};
177
178    #[test]
179    fn empty_geometries_keep_declared_type() {
180        let layer = proto_tile::Layer {
181            version: 2,
182            name: "empty".into(),
183            extent: Some(crate::DEFAULT_EXTENT.get()),
184            features: vec![],
185            ..Default::default()
186        };
187        for (geom_type, expected) in [
188            (
189                proto_tile::GeomType::Point,
190                Geometry::MultiPoint(MultiPoint(vec![])),
191            ),
192            (
193                proto_tile::GeomType::Linestring,
194                Geometry::MultiLineString(MultiLineString(vec![])),
195            ),
196            (
197                proto_tile::GeomType::Polygon,
198                Geometry::MultiPolygon(MultiPolygon(vec![])),
199            ),
200        ] {
201            let feature = proto_tile::Feature {
202                r#type: Some(geom_type),
203                geometry: vec![],
204                ..Default::default()
205            };
206            let mut layer = layer.clone();
207            layer.features = vec![feature];
208            let bytes = encode_layer(layer);
209            let reader = MvtReaderRef::new(&bytes).unwrap();
210            let feature = first_feature(&reader);
211            assert_eq!(feature.geometry().unwrap(), expected);
212        }
213
214        let feature = proto_tile::Feature {
215            r#type: Some(proto_tile::GeomType::Unknown),
216            geometry: vec![],
217            ..Default::default()
218        };
219        let mut layer = layer.clone();
220        layer.features = vec![feature];
221        let bytes = encode_layer(layer);
222        let reader = MvtReaderRef::new(&bytes).unwrap();
223        let feature = first_feature(&reader);
224        assert!(!feature.has_properties());
225        assert!(matches!(feature.geometry(), Err(MvtError::InvalidGeometry)));
226    }
227
228    #[test]
229    fn debug_renders_invalid_geometry_and_property_markers() {
230        let layer = proto_tile::Layer {
231            version: 2,
232            name: "x".into(),
233            features: vec![proto_tile::Feature {
234                r#type: Some(proto_tile::GeomType::Unknown),
235                geometry: vec![9, 0, 0],
236                tags: vec![0],
237                ..Default::default()
238            }],
239            ..Default::default()
240        };
241        let bytes = encode_layer(layer);
242        let reader = MvtReaderRef::new(&bytes).unwrap();
243        insta::assert_debug_snapshot!(reader, @"
244        layer: 0
245          name: x
246          version: 2
247          extent: 4096
248          feature: 0
249            id: (none)
250            geometry: <invalid geometry: invalid geometry command stream>
251            properties:
252              <invalid property: invalid feature tags length: 1>
253        ");
254    }
255}