mvt_reader/
lib.rs

1//! # mvt-reader
2//!
3//! `mvt-reader` is a Rust library for decoding and reading Mapbox vector tiles.
4//!
5//! It provides the `Reader` struct, which allows you to read vector tiles and access their layers and features.
6//!
7//! # Usage
8//!
9//! To use the `mvt-reader` library in your Rust project, add the following to your `Cargo.toml` file:
10//!
11//! ```toml
12//! [dependencies]
13//! mvt-reader = "2.0.0"
14//! ```
15//!
16//! Then, you can import and use the library in your code:
17//!
18//! ```rust
19//! use mvt_reader::{Reader, error::{ParserError}};
20//!
21//! fn main() -> Result<(), ParserError> {
22//!   // Read a vector tile from file or data
23//!   let data = vec![/* Vector tile data */];
24//!   let reader = Reader::new(data)?;
25//!
26//!   // Get layer names
27//!   let layer_names = reader.get_layer_names()?;
28//!   for name in layer_names {
29//!     println!("Layer: {}", name);
30//!   }
31//!
32//!   // Get features for a specific layer
33//!   let layer_index = 0;
34//!   let features = reader.get_features(layer_index)?;
35//!   for feature in features {
36//!     todo!()
37//!   }
38//!
39//!   Ok(())
40//! }
41//! ```
42//!
43//! # Features
44//!
45//! The `mvt-reader` library provides the following features:
46//!
47//! - `wasm`: Enables the compilation of the library as a WebAssembly module, allowing usage in JavaScript/TypeScript projects.
48//!
49//! To enable the `wasm` feature, add the following to your `Cargo.toml` file:
50//!
51//! ```toml
52//! [dependencies.mvt-reader]
53//! version = "2.0.0"
54//! features = ["wasm"]
55//! ```
56//!
57//! # License
58//!
59//! This project is licensed under the [MIT License](https://github.com/codeart1st/mvt-reader/blob/main/LICENSE).
60
61pub mod error;
62pub mod feature;
63
64mod vector_tile;
65
66use feature::{Feature, Value};
67use geo_types::{
68  Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
69};
70use prost::{Message, bytes::Bytes};
71use vector_tile::{Tile, tile::GeomType};
72
73/// The dimension used for the vector tile.
74const DIMENSION: u32 = 2;
75
76/// Reader for decoding and accessing vector tile data.
77pub struct Reader {
78  tile: Tile,
79}
80
81impl Reader {
82  /// Creates a new `Reader` instance with the provided vector tile data.
83  ///
84  /// # Arguments
85  ///
86  /// * `data` - The vector tile data as a byte vector.
87  ///
88  /// # Returns
89  ///
90  /// A result containing the `Reader` instance if successful, or a `DecodeError` if decoding the vector tile data fails.
91  ///
92  /// # Examples
93  ///
94  /// ```
95  /// use mvt_reader::Reader;
96  ///
97  /// let data = vec![/* Vector tile data */];
98  /// let reader = Reader::new(data);
99  /// ```
100  pub fn new(data: Vec<u8>) -> Result<Self, error::ParserError> {
101    match Tile::decode(Bytes::from(data)) {
102      Ok(tile) => Ok(Self { tile }),
103      Err(error) => Err(error::ParserError::new(error::DecodeError::new(Box::new(
104        error,
105      )))),
106    }
107  }
108
109  /// Retrieves the names of the layers in the vector tile.
110  ///
111  /// # Returns
112  ///
113  /// A result containing a vector of layer names if successful, or a `ParserError` if there is an error parsing the tile.
114  ///
115  /// # Examples
116  ///
117  /// ```
118  /// use mvt_reader::Reader;
119  ///
120  /// let data = vec![/* Vector tile data */];
121  /// let reader = Reader::new(data).unwrap();
122  ///
123  /// match reader.get_layer_names() {
124  ///   Ok(layer_names) => {
125  ///     for name in layer_names {
126  ///       println!("Layer: {}", name);
127  ///     }
128  ///   }
129  ///   Err(error) => {
130  ///     todo!();
131  ///   }
132  /// }
133  /// ```
134  pub fn get_layer_names(&self) -> Result<Vec<String>, error::ParserError> {
135    let mut layer_names = Vec::with_capacity(self.tile.layers.len());
136    for layer in self.tile.layers.iter() {
137      match layer.version {
138        1 | 2 => {
139          layer_names.push(layer.name.clone());
140        }
141        _ => {
142          return Err(error::ParserError::new(error::VersionError::new(
143            layer.name.clone(),
144            layer.version,
145          )));
146        }
147      }
148    }
149    Ok(layer_names)
150  }
151
152  /// Retrieves the features of a specific layer in the vector tile.
153  ///
154  /// # Arguments
155  ///
156  /// * `layer_index` - The index of the layer.
157  ///
158  /// # Returns
159  ///
160  /// A result containing a vector of features if successful, or a `ParserError` if there is an error parsing the tile or accessing the layer.
161  ///
162  /// # Examples
163  ///
164  /// ```
165  /// use mvt_reader::Reader;
166  ///
167  /// let data = vec![/* Vector tile data */];
168  /// let reader = Reader::new(data).unwrap();
169  ///
170  /// match reader.get_features(0) {
171  ///   Ok(features) => {
172  ///     for feature in features {
173  ///       todo!();
174  ///     }
175  ///   }
176  ///   Err(error) => {
177  ///     todo!();
178  ///   }
179  /// }
180  /// ```
181  pub fn get_features(&self, layer_index: usize) -> Result<Vec<Feature>, error::ParserError> {
182    let layer = self.tile.layers.get(layer_index);
183    match layer {
184      Some(layer) => {
185        let mut features = Vec::with_capacity(layer.features.len());
186        for feature in layer.features.iter() {
187          if let Some(geom_type) = feature.r#type {
188            match GeomType::try_from(geom_type) {
189              Ok(geom_type) => {
190                let parsed_geometry = match parse_geometry(&feature.geometry, geom_type) {
191                  Ok(parsed_geometry) => parsed_geometry,
192                  Err(error) => {
193                    return Err(error);
194                  }
195                };
196
197                let parsed_tags = match parse_tags(&feature.tags, &layer.keys, &layer.values) {
198                  Ok(parsed_tags) => parsed_tags,
199                  Err(error) => {
200                    return Err(error);
201                  }
202                };
203
204                features.push(Feature {
205                  geometry: parsed_geometry,
206                  id: feature.id,
207                  properties: Some(parsed_tags),
208                });
209              }
210              Err(error) => {
211                return Err(error::ParserError::new(error::DecodeError::new(Box::new(
212                  error,
213                ))));
214              }
215            }
216          }
217        }
218        Ok(features)
219      }
220      None => Ok(vec![]),
221    }
222  }
223}
224
225fn parse_tags(
226  tags: &[u32],
227  keys: &[String],
228  values: &[vector_tile::tile::Value],
229) -> Result<std::collections::HashMap<String, Value>, error::ParserError> {
230  let mut result = std::collections::HashMap::new();
231  for item in tags.chunks(2) {
232    if item.len() != 2
233      || item[0] > keys.len().try_into().unwrap()
234      || item[1] > values.len().try_into().unwrap()
235    {
236      return Err(error::ParserError::new(error::TagsError::new()));
237    }
238    result.insert(
239      (*keys.get(item[0] as usize).expect("item not found")).clone(),
240      map_value((*values.get(item[1] as usize).expect("item not found")).clone()),
241    );
242  }
243  Ok(result)
244}
245
246fn map_value(value: vector_tile::tile::Value) -> Value {
247  if let Some(s) = value.string_value {
248    return Value::String(s);
249  }
250  if let Some(f) = value.float_value {
251    return Value::Float(f);
252  }
253  if let Some(d) = value.double_value {
254    return Value::Double(d);
255  }
256  if let Some(i) = value.int_value {
257    return Value::Int(i);
258  }
259  if let Some(u) = value.uint_value {
260    return Value::UInt(u);
261  }
262  if let Some(s) = value.sint_value {
263    return Value::SInt(s);
264  }
265  if let Some(b) = value.bool_value {
266    return Value::Bool(b);
267  }
268  Value::Null
269}
270
271fn shoelace_formula(points: &[Point<f32>]) -> f32 {
272  let mut area: f32 = 0.0;
273  let n = points.len();
274  let mut v1 = points[n - 1];
275  for v2 in points.iter().take(n) {
276    area += (v2.y() - v1.y()) * (v2.x() + v1.x());
277    v1 = *v2;
278  }
279  area * 0.5
280}
281
282fn parse_geometry(
283  geometry_data: &[u32],
284  geom_type: GeomType,
285) -> Result<Geometry<f32>, error::ParserError> {
286  if geom_type == GeomType::Unknown {
287    return Err(error::ParserError::new(error::GeometryError::new()));
288  }
289
290  // worst case capacity to prevent reallocation. not needed to be exact.
291  let mut coordinates: Vec<Coord<f32>> = Vec::with_capacity(geometry_data.len());
292  let mut polygons: Vec<Polygon<f32>> = Vec::new();
293  let mut linestrings: Vec<LineString<f32>> = Vec::new();
294
295  let mut cursor: [i32; 2] = [0, 0];
296  let mut parameter_count: u32 = 0;
297
298  for value in geometry_data.iter() {
299    if parameter_count == 0 {
300      let command_integer = value;
301      let id = (command_integer & 0x7) as u8;
302      match id {
303        1 => {
304          // MoveTo
305          parameter_count = (command_integer >> 3) * DIMENSION;
306          if geom_type == GeomType::Linestring && !coordinates.is_empty() {
307            linestrings.push(LineString::new(coordinates));
308            // start with a new linestring
309            coordinates = Vec::with_capacity(geometry_data.len());
310          }
311        }
312        2 => {
313          // LineTo
314          parameter_count = (command_integer >> 3) * DIMENSION;
315        }
316        7 => {
317          // ClosePath
318          let first_coordinate = match coordinates.first() {
319            Some(coord) => coord.to_owned(),
320            None => {
321              return Err(error::ParserError::new(error::GeometryError::new()));
322            }
323          };
324          coordinates.push(first_coordinate);
325
326          let ring = LineString::new(coordinates);
327
328          let area = shoelace_formula(&ring.clone().into_points());
329
330          if area > 0.0 {
331            // exterior ring
332            if !linestrings.is_empty() {
333              // finish previous geometry
334              polygons.push(Polygon::new(
335                linestrings[0].clone(),
336                linestrings[1..].into(),
337              ));
338              linestrings = Vec::new();
339            }
340          }
341
342          linestrings.push(ring);
343          // start a new sequence
344          coordinates = Vec::with_capacity(geometry_data.len());
345        }
346        _ => (),
347      }
348    } else {
349      let parameter_integer = value;
350      let integer_value = ((parameter_integer >> 1) as i32) ^ -((parameter_integer & 1) as i32);
351      if parameter_count % DIMENSION == 0 {
352        cursor[0] = match cursor[0].checked_add(integer_value) {
353          Some(result) => result,
354          None => i32::MAX, // clip value
355        };
356      } else {
357        cursor[1] = match cursor[1].checked_add(integer_value) {
358          Some(result) => result,
359          None => i32::MAX, // clip value
360        };
361        coordinates.push(Coord {
362          x: cursor[0] as f32,
363          y: cursor[1] as f32,
364        });
365      }
366      parameter_count -= 1;
367    }
368  }
369
370  match geom_type {
371    GeomType::Linestring => {
372      // the last linestring is in coordinates vec
373      if !linestrings.is_empty() {
374        linestrings.push(LineString::new(coordinates));
375        return Ok(MultiLineString::new(linestrings).into());
376      }
377      Ok(LineString::new(coordinates).into())
378    }
379    GeomType::Point => Ok(
380      MultiPoint(
381        coordinates
382          .iter()
383          .map(|coord| Point::new(coord.x, coord.y))
384          .collect(),
385      )
386      .into(),
387    ),
388    GeomType::Polygon => {
389      if !linestrings.is_empty() {
390        // finish pending polygon
391        polygons.push(Polygon::new(
392          linestrings[0].clone(),
393          linestrings[1..].into(),
394        ));
395        return Ok(MultiPolygon::new(polygons).into());
396      }
397      Ok(polygons.first().unwrap().to_owned().into())
398    }
399    GeomType::Unknown => Err(error::ParserError::new(error::GeometryError::new())),
400  }
401}
402
403#[cfg(feature = "wasm")]
404pub mod wasm {
405
406  use crate::feature::Value;
407  use geojson::{Feature, GeoJson, JsonObject, JsonValue, feature::Id};
408  use serde::Serialize;
409  use serde_wasm_bindgen::Serializer;
410  use wasm_bindgen::prelude::*;
411
412  impl From<Value> for JsonValue {
413    fn from(value: Value) -> Self {
414      match value {
415        Value::Null => JsonValue::Null,
416        Value::Bool(b) => JsonValue::from(b),
417        Value::Int(i) => JsonValue::from(i),
418        Value::UInt(u) => JsonValue::from(u),
419        Value::SInt(s) => JsonValue::from(s),
420        Value::Float(f) => JsonValue::from(f),
421        Value::Double(d) => JsonValue::from(d),
422        Value::String(s) => JsonValue::from(s),
423      }
424    }
425  }
426
427  /// Converts a `super::feature::Feature` into a `wasm_bindgen::JsValue`.
428  impl From<super::feature::Feature> for wasm_bindgen::JsValue {
429    fn from(feature: super::feature::Feature) -> Self {
430      let properties: Option<JsonObject> = feature.properties.as_ref().map(|props| {
431        props
432          .clone()
433          .into_iter()
434          .map(|(k, v)| (k, v.into()))
435          .collect()
436      });
437
438      let geojson = GeoJson::Feature(Feature {
439        bbox: None,
440        geometry: Some(feature.get_geometry().into()),
441        id: feature.id.map(|id| Id::Number(id.into())),
442        properties,
443        foreign_members: None,
444      });
445
446      geojson.serialize(&Serializer::json_compatible()).unwrap()
447    }
448  }
449
450  /// Reader for decoding and accessing vector tile data in WebAssembly.
451  #[wasm_bindgen]
452  pub struct Reader {
453    reader: Option<super::Reader>,
454  }
455
456  #[wasm_bindgen]
457  impl Reader {
458    /// Creates a new `Reader` instance with the provided vector tile data.
459    ///
460    /// # Arguments
461    ///
462    /// * `data` - The vector tile data as a `Vec<u8>`.
463    /// * `error_callback` - An optional JavaScript callback function to handle errors. It should accept a single parameter which will contain the error message as a string.
464    ///
465    /// # Examples
466    ///
467    /// ```
468    /// let tileData = getVectorTileData();
469    /// let reader = new Reader(tileData, handleErrors);
470    /// ```
471    #[wasm_bindgen(constructor)]
472    pub fn new(data: Vec<u8>, error_callback: Option<js_sys::Function>) -> Reader {
473      let reader = match super::Reader::new(data) {
474        Ok(reader) => Some(reader),
475        Err(error) => {
476          if let Some(callback) = error_callback {
477            callback
478              .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
479              .unwrap();
480          }
481          None
482        }
483      };
484      Reader { reader }
485    }
486
487    /// Retrieves the layer names present in the vector tile.
488    ///
489    /// # Arguments
490    ///
491    /// * `error_callback` - An optional JavaScript callback function to handle errors. It should accept a single parameter which will contain the error message as a string.
492    ///
493    /// # Returns
494    ///
495    /// A JavaScript array containing the layer names as strings.
496    ///
497    /// # Examples
498    ///
499    /// ```
500    /// let layerNames = reader.getLayerNames(handleErrors);
501    /// for (let i = 0; i < layerNames.length; i++) {
502    ///   console.log(layerNames[i]);
503    /// }
504    /// ```
505    #[wasm_bindgen(js_name = getLayerNames)]
506    pub fn get_layer_names(&self, error_callback: Option<js_sys::Function>) -> JsValue {
507      match &self.reader {
508        Some(reader) => match reader.get_layer_names() {
509          Ok(layer_names) => JsValue::from(
510            layer_names
511              .into_iter()
512              .map(JsValue::from)
513              .collect::<js_sys::Array>(),
514          ),
515          Err(error) => {
516            if let Some(callback) = error_callback {
517              callback
518                .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
519                .unwrap();
520            }
521            JsValue::NULL
522          }
523        },
524        None => JsValue::NULL,
525      }
526    }
527
528    /// Retrieves the features of a specific layer in the vector tile.
529    ///
530    /// # Arguments
531    ///
532    /// * `layer_index` - The index of the layer to retrieve features from.
533    /// * `error_callback` - An optional JavaScript callback function to handle errors. It should accept a single parameter which will contain the error message as a string.
534    ///
535    /// # Returns
536    ///
537    /// A JavaScript array containing the features as GeoJSON objects.
538    ///
539    /// # Examples
540    ///
541    /// ```
542    /// let features = reader.getFeatures(0, handleErrors);
543    /// for (let i = 0; i < features.length; i++) {
544    ///   console.log(features[i]);
545    /// }
546    /// ```
547    #[wasm_bindgen(js_name = getFeatures)]
548    pub fn get_features(
549      &self,
550      layer_index: usize,
551      error_callback: Option<js_sys::Function>,
552    ) -> JsValue {
553      match &self.reader {
554        Some(reader) => match reader.get_features(layer_index) {
555          Ok(features) => JsValue::from(
556            features
557              .into_iter()
558              .map(JsValue::from)
559              .collect::<js_sys::Array>(),
560          ),
561          Err(error) => {
562            if let Some(callback) = error_callback {
563              callback
564                .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
565                .unwrap();
566            }
567            JsValue::NULL
568          }
569        },
570        None => JsValue::NULL,
571      }
572    }
573  }
574}