1use std::collections::HashMap;
8
9use geozero::mvt::{tile, Message, Tile};
10
11use crate::{Feature, FeatureLayer, Geometry, Polygon, Value};
12
13#[derive(Debug, thiserror::Error)]
14pub enum MvtError {
15 #[error("mvt decode: {0}")]
16 Decode(String),
17}
18
19pub fn decode(bytes: &[u8]) -> Result<DecodedTile, MvtError> {
21 let tile = Tile::decode(bytes).map_err(|e| MvtError::Decode(e.to_string()))?;
22 let layers = tile.layers.into_iter().map(decode_layer).collect();
23 Ok(DecodedTile { layers })
24}
25
26#[derive(Debug)]
27pub struct DecodedTile {
28 pub layers: Vec<FeatureLayer>,
29}
30
31impl DecodedTile {
32 pub fn layer(&self, name: &str) -> Option<&FeatureLayer> {
33 self.layers.iter().find(|l| l.name == name)
34 }
35}
36
37fn decode_layer(layer: tile::Layer) -> FeatureLayer {
38 let extent = layer.extent.unwrap_or(4096);
39 let values: Vec<Value> = layer.values.into_iter().map(value_from_proto).collect();
40 let keys = layer.keys;
41 let features = layer
42 .features
43 .into_iter()
44 .map(|f| feature_from_proto(f, &keys, &values))
45 .collect();
46 FeatureLayer {
47 name: layer.name,
48 extent,
49 features,
50 }
51}
52
53fn value_from_proto(v: tile::Value) -> Value {
54 if let Some(s) = v.string_value {
55 Value::String(s)
56 } else if let Some(f) = v.float_value {
57 Value::Float(f)
58 } else if let Some(d) = v.double_value {
59 Value::Double(d)
60 } else if let Some(i) = v.int_value {
61 Value::Int(i)
62 } else if let Some(u) = v.uint_value {
63 Value::UInt(u)
64 } else if let Some(s) = v.sint_value {
65 Value::SInt(s)
66 } else if let Some(b) = v.bool_value {
67 Value::Bool(b)
68 } else {
69 Value::Null
70 }
71}
72
73fn feature_from_proto(f: tile::Feature, keys: &[String], values: &[Value]) -> Feature {
74 let mut properties = HashMap::with_capacity(f.tags.len() / 2);
75 for chunk in f.tags.chunks_exact(2) {
76 if let (Some(k), Some(v)) = (keys.get(chunk[0] as usize), values.get(chunk[1] as usize)) {
77 properties.insert(k.clone(), v.clone());
78 }
79 }
80 let geom_type = f.r#type();
81 let geometry = decode_geometry(&f.geometry, geom_type);
82 Feature {
83 id: f.id,
84 geometry,
85 properties,
86 }
87}
88
89fn decode_geometry(cmds: &[u32], geom_type: tile::GeomType) -> Geometry {
90 let rings = walk_rings(cmds);
91 let mut g = Geometry::default();
92 match geom_type {
93 tile::GeomType::Point => g.points = rings.into_iter().flatten().collect(),
94 tile::GeomType::Linestring => g.lines = rings,
95 tile::GeomType::Polygon => {
96 for ring in rings {
97 if is_exterior(&ring) {
98 g.polygons.push(Polygon {
99 exterior: ring,
100 holes: Vec::new(),
101 });
102 } else if let Some(last) = g.polygons.last_mut() {
103 last.holes.push(ring);
104 }
105 }
107 }
108 _ => {}
109 }
110 g
111}
112
113fn walk_rings(cmds: &[u32]) -> Vec<Vec<(i32, i32)>> {
115 let mut rings: Vec<Vec<(i32, i32)>> = Vec::new();
116 let mut current: Vec<(i32, i32)> = Vec::new();
117 let mut cx: i32 = 0;
118 let mut cy: i32 = 0;
119 let mut i = 0;
120
121 while i < cmds.len() {
122 let header = cmds[i];
123 i += 1;
124 let id = header & 0x7;
125 let count = (header >> 3) as usize;
126 match id {
127 1 => {
128 if !current.is_empty() {
130 rings.push(std::mem::take(&mut current));
131 }
132 for _ in 0..count {
133 if i + 1 >= cmds.len() {
134 return rings;
135 }
136 cx = cx.wrapping_add(zigzag(cmds[i]));
137 cy = cy.wrapping_add(zigzag(cmds[i + 1]));
138 i += 2;
139 current.push((cx, cy));
140 }
141 }
142 2 => {
143 for _ in 0..count {
145 if i + 1 >= cmds.len() {
146 return rings;
147 }
148 cx = cx.wrapping_add(zigzag(cmds[i]));
149 cy = cy.wrapping_add(zigzag(cmds[i + 1]));
150 i += 2;
151 current.push((cx, cy));
152 }
153 }
154 7 => {
155 }
157 _ => break,
158 }
159 }
160
161 if !current.is_empty() {
162 rings.push(current);
163 }
164 rings
165}
166
167#[inline]
168fn zigzag(v: u32) -> i32 {
169 ((v >> 1) as i32) ^ -((v & 1) as i32)
170}
171
172fn is_exterior(ring: &[(i32, i32)]) -> bool {
174 if ring.len() < 3 {
175 return true;
176 }
177 let mut sum: i64 = 0;
178 for i in 0..ring.len() {
179 let (x1, y1) = ring[i];
180 let (x2, y2) = ring[(i + 1) % ring.len()];
181 sum += (x1 as i64) * (y2 as i64) - (x2 as i64) * (y1 as i64);
182 }
183 sum > 0
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn zigzag_roundtrip() {
192 assert_eq!(zigzag(0), 0);
193 assert_eq!(zigzag(1), -1);
194 assert_eq!(zigzag(2), 1);
195 assert_eq!(zigzag(3), -2);
196 }
197
198 #[test]
199 fn exterior_cw_in_y_down() {
200 let cw = vec![(0, 0), (10, 0), (10, 10), (0, 10)];
202 assert!(is_exterior(&cw));
203 let ccw = vec![(0, 0), (0, 10), (10, 10), (10, 0)];
204 assert!(!is_exterior(&ccw));
205 }
206}