1use std::collections::HashMap;
2use std::fmt::{Display, Formatter};
3use std::iter::Enumerate;
4
5use bytes::Buf;
6use contour::MvtMultiPolygon;
7pub use contour::{MvtContours, MvtPolygon};
8use galileo_types::cartesian::{CartesianPoint2d, Point2};
9use geozero::mvt::tile::GeomType;
10use geozero::mvt::{Message as GeozeroMessage, Tile};
11use serde::{Deserialize, Serialize};
12use strfmt::DisplayStr;
13
14use crate::error::GalileoMvtError;
15
16mod contour;
17pub mod error;
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct MvtTile {
21 pub layers: Vec<MvtLayer>,
22}
23
24#[derive(Debug, Clone, Serialize, Deserialize)]
25pub struct MvtLayer {
26 pub name: String,
27 pub features: Vec<MvtFeature>,
28 pub properties: Vec<String>,
29 pub size: u32,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct MvtFeature {
34 pub id: Option<u64>,
35 pub properties: HashMap<String, MvtValue>,
36 pub geometry: MvtGeometry,
37}
38
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub enum MvtValue {
41 String(String),
42 Float(f32),
43 Double(f64),
44 Int64(i64),
46 Uint64(u64),
47 Bool(bool),
48 Unknown,
49}
50
51impl Display for MvtValue {
52 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
53 match self {
54 MvtValue::String(v) => write!(f, "{v}"),
55 MvtValue::Float(v) => write!(f, "{v}"),
56 MvtValue::Double(v) => write!(f, "{v}"),
57 MvtValue::Int64(v) => write!(f, "{v}"),
58 MvtValue::Uint64(v) => write!(f, "{v}"),
59 MvtValue::Bool(v) => write!(f, "{v}"),
60 MvtValue::Unknown => write!(f, "<NONE>"),
61 }
62 }
63}
64
65impl DisplayStr for MvtValue {
66 fn display_str(&self, f: &mut strfmt::Formatter) -> strfmt::Result<()> {
67 f.str(&self.to_string())?;
68 Ok(())
69 }
70}
71
72impl MvtValue {
73 pub fn eq_str(&self, str_value: &str) -> bool {
74 match &self {
75 MvtValue::String(s) => s == str_value,
76 MvtValue::Float(v) => str_value.parse::<f32>() == Ok(*v),
77 MvtValue::Double(v) => str_value.parse::<f64>() == Ok(*v),
78 MvtValue::Int64(v) => str_value.parse::<i64>() == Ok(*v),
79 MvtValue::Uint64(v) => str_value.parse::<u64>() == Ok(*v),
80 MvtValue::Bool(v) => str_value.parse::<bool>() == Ok(*v),
81 MvtValue::Unknown => false,
82 }
83 }
84}
85
86pub type Point = Point2<f32>;
87
88#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
89pub enum MvtGeometry {
90 Point(Vec<Point>),
91 LineString(MvtContours),
92 Polygon(MvtMultiPolygon),
93}
94
95impl MvtTile {
96 pub fn decode<B>(buffer: B, skip_recoverable_errors: bool) -> Result<MvtTile, GalileoMvtError>
97 where
98 B: Buf,
99 {
100 let pb = Tile::decode(buffer);
101
102 if let Err(e) = pb {
103 return Err(GalileoMvtError::Proto(e.to_string()));
104 }
105
106 let pb = pb.unwrap();
107
108 let mut layers = vec![];
109 for layer in pb.layers.into_iter() {
110 match MvtLayer::decode(layer, skip_recoverable_errors) {
111 Ok(v) => layers.push(v),
112 Err(e) => {
113 if skip_recoverable_errors {
114 log::warn!("{e:?}");
115 } else {
116 return Err(e);
117 }
118 }
119 }
120 }
121
122 let tile = MvtTile { layers };
123
124 if tile.layers.is_empty() {
125 return Err(GalileoMvtError::Generic(
126 "Tile does not contain any valid layers".into(),
127 ));
128 }
129
130 Ok(tile)
131 }
132}
133
134impl MvtLayer {
135 fn decode(
136 pb_layer: geozero::mvt::tile::Layer,
137 skip_recoverable_errors: bool,
138 ) -> Result<Self, GalileoMvtError> {
139 let geozero::mvt::tile::Layer {
140 name,
141 keys,
142 values,
143 features,
144 version,
145 extent,
146 } = pb_layer;
147 if version != 2 {
148 return Err(GalileoMvtError::Generic(format!(
149 "Invalid version: {version}"
150 )));
151 }
152
153 let mut mvt_values = Vec::with_capacity(values.len());
154 for value in values {
155 match MvtValue::decode(value) {
156 Ok(v) => mvt_values.push(v),
157 Err(e) => {
158 if skip_recoverable_errors {
159 log::warn!("{e:?}");
160 mvt_values.push(MvtValue::Unknown);
161 } else {
162 return Err(e);
163 }
164 }
165 }
166 }
167
168 let mut mvt_features = Vec::with_capacity(features.len());
169 for feature in features {
170 match MvtFeature::decode(feature, extent.unwrap_or(4096), &keys, &mvt_values) {
171 Ok(v) => mvt_features.push(v),
172 Err(e) => {
173 if skip_recoverable_errors {
174 log::warn!("{e:?}");
175 } else {
176 return Err(e);
177 }
178 }
179 }
180 }
181
182 Ok(MvtLayer {
183 name,
184 properties: keys,
185 features: mvt_features,
186 size: pb_layer.extent.unwrap_or(4096),
187 })
188 }
189}
190
191impl MvtValue {
192 fn decode(pb_value: geozero::mvt::tile::Value) -> Result<MvtValue, GalileoMvtError> {
193 let mut present_types = 0;
194 let mut value = MvtValue::Unknown;
195
196 if let Some(v) = pb_value.string_value {
197 value = MvtValue::String(v);
198 present_types += 1;
199 }
200
201 if let Some(v) = pb_value.float_value {
202 value = MvtValue::Float(v);
203 present_types += 1;
204 }
205
206 if let Some(v) = pb_value.double_value {
207 value = MvtValue::Double(v);
208 present_types += 1;
209 }
210
211 if let Some(v) = pb_value.int_value {
212 value = MvtValue::Int64(v);
213 present_types += 1;
214 }
215
216 if let Some(v) = pb_value.uint_value {
217 value = MvtValue::Uint64(v);
218 present_types += 1;
219 }
220
221 if let Some(v) = pb_value.sint_value {
222 value = MvtValue::Int64(v);
223 present_types += 1;
224 }
225
226 if let Some(v) = pb_value.bool_value {
227 value = MvtValue::Bool(v);
228 present_types += 1;
229 }
230
231 if present_types == 0 {
232 Err(GalileoMvtError::Generic("No valid value present".into()))
233 } else if present_types > 1 {
234 Err(GalileoMvtError::Generic(
235 "More than one value present".into(),
236 ))
237 } else {
238 Ok(value)
239 }
240 }
241}
242
243pub fn number_to_geomtype(number: i32) -> GeomType {
244 match number {
245 1 => GeomType::Point,
246 2 => GeomType::Linestring,
247 3 => GeomType::Polygon,
248 _ => GeomType::Unknown,
249 }
250}
251
252pub fn opt_number_to_geomtype(number: Option<i32>) -> GeomType {
253 match number {
254 Some(number) => number_to_geomtype(number),
255 None => GeomType::Unknown,
256 }
257}
258
259impl MvtFeature {
260 fn decode(
261 pb_feature: geozero::mvt::tile::Feature,
262 extent: u32,
263 keys: &[String],
264 values: &[MvtValue],
265 ) -> Result<MvtFeature, GalileoMvtError> {
266 let geozero::mvt::tile::Feature {
267 id,
268 tags,
269 r#type,
270 geometry,
271 } = pb_feature;
272 let pb_type = opt_number_to_geomtype(r#type);
273 let properties = Self::decode_properties(tags, keys, values)?;
274 let geometry = Self::decode_geometry(pb_type, geometry, extent)?;
275
276 Ok(MvtFeature {
277 id,
278 properties,
279 geometry,
280 })
281 }
282 fn decode_properties(
283 tags: Vec<u32>,
284 keys: &[String],
285 values: &[MvtValue],
286 ) -> Result<HashMap<String, MvtValue>, GalileoMvtError> {
287 let mut properties = HashMap::new();
288 if tags.len() % 2 != 0 {
289 return Err(GalileoMvtError::Generic(
290 "Invalid number of tags in feature".into(),
291 ));
292 }
293
294 for tag_pair in tags.chunks(2) {
295 let key = keys
296 .get(tag_pair[0] as usize)
297 .ok_or(GalileoMvtError::Generic("Invalid tag key".into()))?;
298 let value = values
299 .get(tag_pair[1] as usize)
300 .ok_or(GalileoMvtError::Generic("Invalid tag value".into()))?;
301
302 properties.insert(key.clone(), value.clone());
303 }
304
305 Ok(properties)
306 }
307
308 fn decode_geometry(
309 geom_type: GeomType,
310 commands: Vec<u32>,
311 extent: u32,
312 ) -> Result<MvtGeometry, GalileoMvtError> {
313 Ok(match geom_type {
314 GeomType::Unknown => {
315 return Err(GalileoMvtError::Generic("Unknown geometry type".into()))
316 }
317 GeomType::Point => MvtGeometry::Point(Self::decode_point(commands, extent)?),
318 GeomType::Linestring => MvtGeometry::LineString(MvtContours::new(commands, extent)?),
319 GeomType::Polygon => MvtGeometry::Polygon(MvtMultiPolygon::new(commands, extent)?),
320 })
321 }
322
323 fn decode_point(commands: Vec<u32>, extent: u32) -> Result<Vec<Point>, GalileoMvtError> {
324 let mut points = Vec::with_capacity(commands.len() / 2);
325 for command in Self::decode_commands(&commands, extent) {
326 match command? {
327 MvtGeomCommand::MoveTo(p) => points.push(p),
328 _ => {
329 return Err(GalileoMvtError::Generic(
330 "Point geometry cannot have {:?} command".into(),
331 ))
332 }
333 }
334 }
335
336 Ok(points)
337 }
338
339 fn decode_commands(
340 commands: &[u32],
341 extent: u32,
342 ) -> impl Iterator<Item = Result<MvtGeomCommand, GalileoMvtError>> + use<'_> {
343 CommandIterator::new(commands.iter(), extent).map(|res| res.map(|(command, _)| command))
344 }
345}
346
347struct CommandIterator<'a, T: Iterator<Item = &'a u32>> {
348 inner: Enumerate<T>,
349 extent: u32,
350 current_command: Option<(u32, u32, usize)>,
351 can_continue: bool,
352 cursor: Point,
353}
354
355impl<'a, T: Iterator<Item = &'a u32>> CommandIterator<'a, T> {
356 fn new(inner: T, extent: u32) -> Self {
357 Self {
358 inner: inner.enumerate(),
359 extent,
360 current_command: None,
361 can_continue: true,
362 cursor: Point::default(),
363 }
364 }
365
366 fn read_move_to(&mut self) -> Result<MvtGeomCommand, GalileoMvtError> {
367 self.cursor = self.read_point()?;
368 Ok(MvtGeomCommand::MoveTo(self.cursor))
369 }
370
371 fn read_line_to(&mut self) -> Result<MvtGeomCommand, GalileoMvtError> {
372 self.cursor = self.read_point()?;
373 Ok(MvtGeomCommand::LineTo(self.cursor))
374 }
375
376 fn read_point(&mut self) -> Result<Point, GalileoMvtError> {
377 let vals = self.read_vals::<2>()?;
378 Ok(Point::new(
379 self.decode_sint_coord(vals[0]) + self.cursor.x(),
380 self.decode_sint_coord(vals[1]) + self.cursor.y(),
381 ))
382 }
383
384 fn decode_sint_coord(&mut self, val: u32) -> f32 {
385 sint_to_int(val) as f32 / self.extent as f32
386 }
387
388 fn read_vals<const COUNT: usize>(&mut self) -> Result<[u32; COUNT], GalileoMvtError> {
389 let mut result = [0; COUNT];
390 for val in result.iter_mut() {
391 *val = match self.inner.next() {
392 Some((_, v)) => *v,
393 None => {
394 return Err(GalileoMvtError::Generic(
395 "Expected value to be present, but found end of data".into(),
396 ));
397 }
398 };
399 }
400
401 Ok(result)
402 }
403}
404
405fn sint_to_int(sint: u32) -> i32 {
406 if sint == u32::MAX {
407 return i32::MIN;
409 }
410
411 match sint & 1 {
412 0 => (sint >> 1) as i32,
413 1 => -(((sint >> 1) + 1) as i32),
414 _ => unreachable!(),
415 }
416}
417
418impl<'a, T: Iterator<Item = &'a u32>> Iterator for CommandIterator<'a, T> {
419 type Item = Result<(MvtGeomCommand, usize), GalileoMvtError>;
420
421 fn next(&mut self) -> Option<Self::Item> {
422 if !self.can_continue {
423 return None;
424 }
425
426 let (command_id, command_count, index) = match self.current_command {
427 Some((id, count, index)) => (id, count, index),
428 None => {
429 let (index, command_integer) = self.inner.next()?;
430 let command_id = command_integer & 0x7;
431 let command_count = command_integer >> 3;
432
433 (command_id, command_count, index)
434 }
435 };
436
437 self.current_command = match command_count {
438 0 => {
439 self.can_continue = false;
440 return Some(Err(GalileoMvtError::Generic(
441 "Command count cannot be 0".into(),
442 )));
443 }
444 1 => None,
445 v => Some((command_id, v - 1, index)),
446 };
447
448 Some(match command_id {
449 1 => self.read_move_to().map(|command| (command, index)),
450 2 => self.read_line_to().map(|command| (command, index)),
451 7 => {
452 if command_count != 1 {
453 self.can_continue = false;
454 Err(GalileoMvtError::Generic(format!(
455 "ClosePath command must have count 0, but has {command_count}"
456 )))
457 } else {
458 Ok((MvtGeomCommand::ClosePath, index))
459 }
460 }
461 _ => {
462 self.can_continue = false;
463 Err(GalileoMvtError::Generic(format!(
464 "Unknown command id {command_id}"
465 )))
466 }
467 })
468 }
469}
470
471enum MvtGeomCommand {
472 MoveTo(Point),
473 LineTo(Point),
474 ClosePath,
475}
476
477#[cfg(test)]
478mod tests {
479 use std::io::Cursor;
480
481 use galileo_types::{Contour, MultiContour, MultiPolygon, Polygon};
482
483 use super::*;
484
485 #[test]
486 fn sint_to_int_test() {
487 assert_eq!(sint_to_int(0), 0);
488 assert_eq!(sint_to_int(1), -1);
489 assert_eq!(sint_to_int(2), 1);
490 assert_eq!(sint_to_int(3), -2);
491 assert_eq!(sint_to_int(0xfffffffe), 0x7fffffff);
492 assert_eq!(sint_to_int(0xffffffff), i32::MIN);
493 }
494
495 #[test]
496 fn test_protobuf() {
497 let vt = include_bytes!("../test-data/vt.mvt");
498 let tile = MvtTile::decode(&mut Cursor::new(&vt), false).unwrap();
499
500 let layer = tile.layers.iter().find(|l| l.name == "boundary").unwrap();
501
502 let feature205 = layer.features.iter().find(|f| f.id == Some(205)).unwrap();
503 let MvtGeometry::LineString(contours) = &feature205.geometry else {
504 panic!("invalid geometry type");
505 };
506 assert_eq!(contours.contours().count(), 1);
507 assert_eq!(contours.contours().next().unwrap().iter_points().count(), 2);
508
509 let feature681247437 = layer
510 .features
511 .iter()
512 .find(|f| f.id == Some(681247437))
513 .unwrap();
514 let MvtGeometry::LineString(contours) = &feature681247437.geometry else {
515 panic!("invalid geometry type");
516 };
517 assert_eq!(contours.contours().count(), 461);
518 let points = contours
519 .contours()
520 .fold(0, |acc, c| acc + c.iter_points().count());
521 assert_eq!(points, 6608);
522
523 let layer = tile.layers.iter().find(|l| l.name == "water").unwrap();
524 let feature342914 = layer
525 .features
526 .iter()
527 .find(|f| f.id == Some(342914))
528 .unwrap();
529 let MvtGeometry::Polygon(polygons) = &feature342914.geometry else {
530 panic!("invalid geometry type");
531 };
532 assert_eq!(polygons.polygons().count(), 4);
533 let points = polygons
534 .polygons()
535 .flat_map(|p| p.iter_contours())
536 .fold((0, 0), |acc, c| {
537 (acc.0 + 1, acc.1 + c.iter_points_closing().count())
538 });
539 assert_eq!(points, (37, 1092));
540 }
541}