use std::fmt::{self, Write as _};
use buffa::Enumeration as _;
use geo_types::{Coord, Geometry, LineString, Point, Polygon};
use super::property::{MvtPropertyIter, MvtValueRef};
use crate::generated::vector_tile::tile as proto_tile;
use crate::geom_reader::decode_geometry;
use crate::{MvtFeature, MvtGeometry, MvtResult};
#[derive(Copy, Clone)]
pub struct MvtFeatureRef<'a> {
layer: &'a proto_tile::LayerView<'a>,
feature: &'a proto_tile::FeatureView<'a>,
}
impl<'a> MvtFeatureRef<'a> {
pub(crate) fn new(
layer: &'a proto_tile::LayerView<'a>,
feature: &'a proto_tile::FeatureView<'a>,
) -> Self {
Self { layer, feature }
}
#[must_use]
pub fn id(self) -> Option<u64> {
self.feature.id
}
#[must_use]
pub fn tags(self) -> &'a [u32] {
&self.feature.tags
}
#[must_use]
pub fn geometry_commands(self) -> &'a [u32] {
&self.feature.geometry
}
#[must_use]
pub fn geom_type(self) -> Option<proto_tile::GeomType> {
self.feature.r#type
}
#[must_use]
pub fn geom_type_value(self) -> Option<i32> {
self.feature.r#type.map(|v| v.to_i32())
}
#[must_use]
pub fn has_properties(self) -> bool {
!self.feature.tags.is_empty()
}
#[must_use]
pub fn properties(self) -> MvtPropertyIter<'a> {
MvtPropertyIter::new(
&self.layer.keys,
&self.layer.values,
self.feature.tags.chunks(2),
)
}
pub fn properties_vec(self) -> MvtResult<Vec<(&'a str, MvtValueRef<'a>)>> {
self.properties().collect()
}
pub fn geometry(self) -> MvtResult<MvtGeometry> {
decode_geometry(self.geom_type(), &self.feature.geometry)
}
pub fn to_feature(self) -> MvtResult<MvtFeature> {
let properties = self
.properties()
.map(|property| property.map(|(key, value)| (key.to_string(), value.into_owned())))
.collect::<MvtResult<Vec<_>>>()?;
Ok(MvtFeature {
id: self.id(),
geometry: self.geometry()?,
properties,
})
}
}
impl fmt::Debug for MvtFeatureRef<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.id() {
Some(id) => writeln!(f, " id: {id}")?,
None => writeln!(f, " id: (none)")?,
}
match self.geometry() {
Ok(geometry) => fmt_geometry(f, &geometry)?,
Err(error) => writeln!(f, " geometry: <invalid geometry: {error}>")?,
}
write!(f, " properties:")?;
if self.has_properties() {
writeln!(f)?;
for property in self.properties() {
match property {
Ok((key, value)) => writeln!(f, " {key} = {value:?}")?,
Err(error) => writeln!(f, " <invalid property: {error}>")?,
}
}
} else {
writeln!(f, " (none)")?;
}
Ok(())
}
}
fn fmt_geometry(f: &mut fmt::Formatter<'_>, geometry: &MvtGeometry) -> fmt::Result {
let mut lines = Vec::new();
match geometry {
Geometry::Point(point) => lines.push(point_line(*point)),
Geometry::MultiPoint(points) => lines.extend(points.iter().map(|p| point_line(*p))),
Geometry::LineString(line) => lines.push(line_line(line)),
Geometry::MultiLineString(strings) => lines.extend(strings.iter().map(line_line)),
Geometry::Polygon(polygon) => push_polygon(&mut lines, polygon),
Geometry::MultiPolygon(p) => p.iter().for_each(|v| push_polygon(&mut lines, v)),
other => lines.push(format!("{other:?}")),
}
if let [single] = lines.as_slice() {
writeln!(f, " geometry: {single}")
} else {
writeln!(f, " geometry:")?;
lines.iter().try_for_each(|v| writeln!(f, " {v}"))
}
}
fn point_line(point: Point<i32>) -> String {
format!("POINT({},{})", point.x(), point.y())
}
fn line_line(line: &LineString<i32>) -> String {
format!("LINESTRING[count={}]({})", line.0.len(), Coords(&line.0))
}
fn push_polygon(lines: &mut Vec<String>, polygon: &Polygon<i32>) {
lines.push(ring_line(polygon.exterior(), "OUTER"));
lines.extend(polygon.interiors().iter().map(|r| ring_line(r, "INNER")));
}
fn ring_line(ring: &LineString<i32>, winding: &str) -> String {
format!(
"RING[count={}]({})[{winding}]",
ring.0.len(),
Coords(&ring.0)
)
}
struct Coords<'a>(&'a [Coord<i32>]);
impl fmt::Display for Coords<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
for (index, coord) in self.0.iter().enumerate() {
if index > 0 {
f.write_char(',')?;
}
write!(f, "{} {}", coord.x, coord.y)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use geo_types::{Geometry, MultiLineString, MultiPoint, MultiPolygon};
use super::*;
use crate::reader::tests::{encode_layer, first_feature};
use crate::{MvtError, MvtReaderRef};
#[test]
fn empty_geometries_keep_declared_type() {
let layer = proto_tile::Layer {
version: 2,
name: "empty".into(),
extent: Some(crate::DEFAULT_EXTENT.get()),
features: vec![],
..Default::default()
};
for (geom_type, expected) in [
(
proto_tile::GeomType::Point,
Geometry::MultiPoint(MultiPoint(vec![])),
),
(
proto_tile::GeomType::Linestring,
Geometry::MultiLineString(MultiLineString(vec![])),
),
(
proto_tile::GeomType::Polygon,
Geometry::MultiPolygon(MultiPolygon(vec![])),
),
] {
let feature = proto_tile::Feature {
r#type: Some(geom_type),
geometry: vec![],
..Default::default()
};
let mut layer = layer.clone();
layer.features = vec![feature];
let bytes = encode_layer(layer);
let reader = MvtReaderRef::new(&bytes).unwrap();
let feature = first_feature(&reader);
assert_eq!(feature.geometry().unwrap(), expected);
}
let feature = proto_tile::Feature {
r#type: Some(proto_tile::GeomType::Unknown),
geometry: vec![],
..Default::default()
};
let mut layer = layer.clone();
layer.features = vec![feature];
let bytes = encode_layer(layer);
let reader = MvtReaderRef::new(&bytes).unwrap();
let feature = first_feature(&reader);
assert!(!feature.has_properties());
assert!(matches!(feature.geometry(), Err(MvtError::InvalidGeometry)));
}
#[test]
fn debug_renders_invalid_geometry_and_property_markers() {
let layer = proto_tile::Layer {
version: 2,
name: "x".into(),
features: vec![proto_tile::Feature {
r#type: Some(proto_tile::GeomType::Unknown),
geometry: vec![9, 0, 0],
tags: vec![0],
..Default::default()
}],
..Default::default()
};
let bytes = encode_layer(layer);
let reader = MvtReaderRef::new(&bytes).unwrap();
insta::assert_debug_snapshot!(reader, @"
layer: 0
name: x
version: 2
extent: 4096
feature: 0
id: (none)
geometry: <invalid geometry: invalid geometry command stream>
properties:
<invalid property: invalid feature tags length: 1>
");
}
}