galileo_mvt/
lib.rs

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    // For both Int and Sint variants of protobuf values
45    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        // Edge case. Operation below will overflow with this value.
408        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}