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
111fn 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
155struct 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}