Skip to main content

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.4.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//! - `protoc`: Enables the use of `prost-build` to compile the protobuf definition sources from `vector_tile.proto`. This is useful for development and testing purposes, but it is not required for using the library in production. If the `protoc` feature is not enabled, the library will use pre-generated Rust code for the protobuf definitions.
49//!
50//! To enable the `wasm` feature, add the following to your `Cargo.toml` file:
51//!
52//! ```toml
53//! [dependencies]
54//! mvt-reader = { version = "2.4.0", features = ["wasm"] }
55//! ```
56//! 
57//! To enable the `protoc` feature, add the following to your `Cargo.toml` file:
58//! 
59//! ```toml
60//! [dependencies]
61//! mvt-reader = { version = "2.4.0", features = ["protoc"] }
62//! ```
63//!
64//! # License
65//!
66//! This project is licensed under the [MIT License](https://github.com/codeart1st/mvt-reader/blob/main/LICENSE).
67
68pub mod error;
69pub mod feature;
70pub mod layer;
71
72mod vector_tile;
73
74use feature::{Feature, Value};
75use geo_types::{
76  Coord, CoordNum, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
77};
78use layer::Layer;
79use num_traits::NumCast;
80use prost::{Message, bytes::Bytes};
81use vector_tile::{Tile, tile::GeomType};
82
83/// The dimension used for the vector tile.
84const DIMENSION: u32 = 2;
85
86/// Reader for decoding and accessing vector tile data.
87pub struct Reader {
88  tile: Tile,
89}
90
91impl Reader {
92  /// Creates a new `Reader` instance with the provided vector tile data.
93  ///
94  /// # Arguments
95  ///
96  /// * `data` - The vector tile data as a byte vector.
97  ///
98  /// # Returns
99  ///
100  /// A result containing the `Reader` instance if successful, or a `DecodeError` if decoding the vector tile data fails.
101  ///
102  /// # Examples
103  ///
104  /// ```
105  /// use mvt_reader::Reader;
106  ///
107  /// let data = vec![/* Vector tile data */];
108  /// let reader = Reader::new(data);
109  /// ```
110  pub fn new(data: Vec<u8>) -> Result<Self, error::ParserError> {
111    let tile = Tile::decode(Bytes::from(data))?;
112    Ok(Self { tile })
113  }
114
115  /// Retrieves the names of the layers in the vector tile.
116  ///
117  /// # Returns
118  ///
119  /// A result containing a vector of layer names if successful, or a `ParserError` if there is an error parsing the tile.
120  ///
121  /// # Examples
122  ///
123  /// ```
124  /// use mvt_reader::Reader;
125  ///
126  /// let data = vec![/* Vector tile data */];
127  /// let reader = Reader::new(data).unwrap();
128  ///
129  /// match reader.get_layer_names() {
130  ///   Ok(layer_names) => {
131  ///     for name in layer_names {
132  ///       println!("Layer: {}", name);
133  ///     }
134  ///   }
135  ///   Err(error) => {
136  ///     todo!();
137  ///   }
138  /// }
139  /// ```
140  pub fn get_layer_names(&self) -> Result<Vec<String>, error::ParserError> {
141    process_layers(&self.tile.layers, |layer, _| layer.name.clone())
142  }
143
144  /// Retrieves metadata about the layers in the vector tile.
145  ///
146  /// # Returns
147  ///
148  /// A result containing a vector of `Layer` structs if successful, or a `ParserError` if there is an error parsing the tile.
149  ///
150  /// # Examples
151  ///
152  /// ```
153  /// use mvt_reader::Reader;
154  ///
155  /// let data = vec![/* Vector tile data */];
156  /// let reader = Reader::new(data).unwrap();
157  ///
158  /// match reader.get_layer_metadata() {
159  ///   Ok(layers) => {
160  ///     for layer in layers {
161  ///       println!("Layer: {}", layer.name);
162  ///       println!("Extent: {}", layer.extent);
163  ///     }
164  ///   }
165  ///   Err(error) => {
166  ///     todo!();
167  ///   }
168  /// }
169  /// ```
170  pub fn get_layer_metadata(&self) -> Result<Vec<Layer>, error::ParserError> {
171    process_layers(&self.tile.layers, |layer, index| Layer {
172      layer_index: index,
173      version: layer.version,
174      name: layer.name.clone(),
175      feature_count: layer.features.len(),
176      extent: layer.extent.unwrap_or(4096),
177    })
178  }
179
180  /// Retrieves the features of a specific layer in the vector tile.
181  ///
182  /// # Arguments
183  ///
184  /// * `layer_index` - The index of the layer.
185  ///
186  /// # Returns
187  ///
188  /// A result containing a vector of features if successful, or a `ParserError` if there is an error parsing the tile or accessing the layer.
189  ///
190  /// # Examples
191  ///
192  /// ```
193  /// use mvt_reader::Reader;
194  ///
195  /// let data = vec![/* Vector tile data */];
196  /// let reader = Reader::new(data).unwrap();
197  ///
198  /// match reader.get_features(0) {
199  ///   Ok(features) => {
200  ///     for feature in features {
201  ///       todo!();
202  ///     }
203  ///   }
204  ///   Err(error) => {
205  ///     todo!();
206  ///   }
207  /// }
208  /// ```
209  pub fn get_features(&self, layer_index: usize) -> Result<Vec<Feature>, error::ParserError> {
210    self.get_features_as::<f32>(layer_index)
211  }
212
213  /// Retrieves the features of a specific layer with geometry coordinates in the specified numeric type.
214  ///
215  /// This is a generic version of [`get_features`](Reader::get_features) that allows you to choose
216  /// the coordinate type for the geometry. Supported types include `f32` (default), `i32`, and `i16`.
217  ///
218  /// # Arguments
219  ///
220  /// * `layer_index` - The index of the layer.
221  ///
222  /// # Type Parameters
223  ///
224  /// * `T` - The numeric type for geometry coordinates (e.g. `f32`, `i32`, `i16`).
225  ///
226  /// # Returns
227  ///
228  /// A result containing a vector of features if successful, or a `ParserError` if there is an error parsing the tile or accessing the layer.
229  ///
230  /// # Examples
231  ///
232  /// ```
233  /// use mvt_reader::Reader;
234  ///
235  /// let data = vec![/* Vector tile data */];
236  /// let reader = Reader::new(data).unwrap();
237  ///
238  /// // Get features with i32 coordinates
239  /// let features = reader.get_features_as::<i32>(0);
240  ///
241  /// // Get features with i16 coordinates
242  /// let features = reader.get_features_as::<i16>(0);
243  /// ```
244  pub fn get_features_as<T: CoordNum>(&self, layer_index: usize) -> Result<Vec<Feature<T>>, error::ParserError> {
245    let layer = self.tile.layers.get(layer_index);
246    match layer {
247      Some(layer) => {
248        let mut features = Vec::with_capacity(layer.features.len());
249        for feature in layer.features.iter() {
250          if let Some(geom_type) = feature.r#type {
251            let geom_type = GeomType::try_from(geom_type).map_err(|_| error::ParserError::InvalidGeometry)?;
252            let parsed_geometry = parse_geometry::<T>(&feature.geometry, geom_type)?;
253            let parsed_tags = parse_tags(&feature.tags, &layer.keys, &layer.values)?;
254
255            features.push(Feature {
256              geometry: parsed_geometry,
257              id: feature.id,
258              properties: Some(parsed_tags),
259            });
260          }
261        }
262        Ok(features)
263      }
264      None => Ok(vec![]),
265    }
266  }
267}
268
269fn process_layers<T, F>(
270  layers: &[vector_tile::tile::Layer],
271  mut processor: F,
272) -> Result<Vec<T>, error::ParserError>
273where
274  F: FnMut(&vector_tile::tile::Layer, usize) -> T,
275{
276  let mut results = Vec::with_capacity(layers.len());
277  for (index, layer) in layers.iter().enumerate() {
278    match layer.version {
279      1 | 2 => results.push(processor(layer, index)),
280      _ => {
281        return Err(error::ParserError::UnsupportedVersion {
282          layer_name: layer.name.clone(),
283          version: layer.version,
284        });
285      }
286    }
287  }
288  Ok(results)
289}
290
291fn parse_tags(
292  tags: &[u32],
293  keys: &[String],
294  values: &[vector_tile::tile::Value],
295) -> Result<std::collections::HashMap<String, Value>, error::ParserError> {
296  let mut result = std::collections::HashMap::new();
297  for item in tags.chunks(2) {
298    if item.len() != 2
299      || item[0] as usize >= keys.len()
300      || item[1] as usize >= values.len()
301    {
302      return Err(error::ParserError::InvalidTags);
303    }
304    result.insert(
305      keys[item[0] as usize].clone(),
306      map_value(values[item[1] as usize].clone()),
307    );
308  }
309  Ok(result)
310}
311
312fn map_value(value: vector_tile::tile::Value) -> Value {
313  if let Some(s) = value.string_value {
314    return Value::String(s);
315  }
316  if let Some(f) = value.float_value {
317    return Value::Float(f);
318  }
319  if let Some(d) = value.double_value {
320    return Value::Double(d);
321  }
322  if let Some(i) = value.int_value {
323    return Value::Int(i);
324  }
325  if let Some(u) = value.uint_value {
326    return Value::UInt(u);
327  }
328  if let Some(s) = value.sint_value {
329    return Value::SInt(s);
330  }
331  if let Some(b) = value.bool_value {
332    return Value::Bool(b);
333  }
334  Value::Null
335}
336
337fn shoelace_formula<T: CoordNum>(points: &[Point<T>]) -> f32 {
338  let mut area: f32 = 0.0;
339  let n = points.len();
340  let mut v1 = points[n - 1];
341  for v2 in points.iter().take(n) {
342    let v2y: f32 = NumCast::from(v2.y()).unwrap_or(0.0);
343    let v1y: f32 = NumCast::from(v1.y()).unwrap_or(0.0);
344    let v2x: f32 = NumCast::from(v2.x()).unwrap_or(0.0);
345    let v1x: f32 = NumCast::from(v1.x()).unwrap_or(0.0);
346    area += (v2y - v1y) * (v2x + v1x);
347    v1 = *v2;
348  }
349  area * 0.5
350}
351
352fn parse_geometry<T: CoordNum>(
353  geometry_data: &[u32],
354  geom_type: GeomType,
355) -> Result<Geometry<T>, error::ParserError> {
356  if geom_type == GeomType::Unknown {
357    return Err(error::ParserError::InvalidGeometry);
358  }
359
360  // worst case capacity to prevent reallocation. not needed to be exact.
361  let mut coordinates: Vec<Coord<T>> = Vec::with_capacity(geometry_data.len());
362  let mut polygons: Vec<Polygon<T>> = Vec::new();
363  let mut linestrings: Vec<LineString<T>> = Vec::new();
364
365  let mut cursor: [i32; 2] = [0, 0];
366  let mut parameter_count: u32 = 0;
367
368  for value in geometry_data.iter() {
369    if parameter_count == 0 {
370      let command_integer = value;
371      let id = (command_integer & 0x7) as u8;
372      match id {
373        1 => {
374          // MoveTo
375          parameter_count = (command_integer >> 3) * DIMENSION;
376          if geom_type == GeomType::Linestring && !coordinates.is_empty() {
377            linestrings.push(LineString::new(coordinates));
378            // start with a new linestring
379            coordinates = Vec::with_capacity(geometry_data.len());
380          }
381        }
382        2 => {
383          // LineTo
384          parameter_count = (command_integer >> 3) * DIMENSION;
385        }
386        7 => {
387          // ClosePath
388          let first_coordinate = match coordinates.first() {
389            Some(coord) => coord.to_owned(),
390            None => {
391              return Err(error::ParserError::InvalidGeometry);
392            }
393          };
394          coordinates.push(first_coordinate);
395
396          let ring = LineString::new(coordinates);
397
398          let area = shoelace_formula(&ring.clone().into_points());
399
400          if area > 0.0 {
401            // exterior ring
402            if !linestrings.is_empty() {
403              // finish previous geometry
404              polygons.push(Polygon::new(
405                linestrings[0].clone(),
406                linestrings[1..].into(),
407              ));
408              linestrings = Vec::new();
409            }
410          }
411
412          linestrings.push(ring);
413          // start a new sequence
414          coordinates = Vec::with_capacity(geometry_data.len());
415        }
416        _ => (),
417      }
418    } else {
419      let parameter_integer = value;
420      let integer_value = ((parameter_integer >> 1) as i32) ^ -((parameter_integer & 1) as i32);
421      if parameter_count.is_multiple_of(DIMENSION) {
422        cursor[0] = match cursor[0].checked_add(integer_value) {
423          Some(result) => result,
424          None => i32::MAX, // clip value
425        };
426      } else {
427        cursor[1] = match cursor[1].checked_add(integer_value) {
428          Some(result) => result,
429          None => i32::MAX, // clip value
430        };
431        let x = NumCast::from(cursor[0])
432          .ok_or(error::ParserError::CoordinateOverflow { value: cursor[0] })?;
433        let y = NumCast::from(cursor[1])
434          .ok_or(error::ParserError::CoordinateOverflow { value: cursor[1] })?;
435        coordinates.push(Coord { x, y });
436      }
437      parameter_count -= 1;
438    }
439  }
440
441  match geom_type {
442    GeomType::Linestring => {
443      // the last linestring is in coordinates vec
444      if !linestrings.is_empty() {
445        linestrings.push(LineString::new(coordinates));
446        return Ok(MultiLineString::new(linestrings).into());
447      }
448      Ok(LineString::new(coordinates).into())
449    }
450    GeomType::Point => Ok(
451      MultiPoint(
452        coordinates
453          .iter()
454          .map(|coord| Point::new(coord.x, coord.y))
455          .collect(),
456      )
457      .into(),
458    ),
459    GeomType::Polygon => {
460      if !linestrings.is_empty() {
461        // finish pending polygon
462        polygons.push(Polygon::new(
463          linestrings[0].clone(),
464          linestrings[1..].into(),
465        ));
466        return Ok(MultiPolygon::new(polygons).into());
467      }
468      match polygons.first() {
469        Some(polygon) => Ok(polygon.to_owned().into()),
470        None => Err(error::ParserError::InvalidGeometry),
471      }
472    }
473    GeomType::Unknown => Err(error::ParserError::InvalidGeometry),
474  }
475}
476
477#[cfg(feature = "wasm")]
478pub mod wasm {
479
480  use crate::feature::Value;
481  use geojson::{Feature, GeoJson, JsonObject, JsonValue, feature::Id};
482  use serde::ser::{Serialize, SerializeStruct};
483  use serde_wasm_bindgen::Serializer;
484  use wasm_bindgen::prelude::*;
485
486  /// Converts a `Value` into a `serde_json::Value`.
487  impl From<Value> for JsonValue {
488    fn from(value: Value) -> Self {
489      match value {
490        Value::Null => JsonValue::Null,
491        Value::Bool(b) => JsonValue::from(b),
492        Value::Int(i) => JsonValue::from(i),
493        Value::UInt(u) => JsonValue::from(u),
494        Value::SInt(s) => JsonValue::from(s),
495        Value::Float(f) => JsonValue::from(f),
496        Value::Double(d) => JsonValue::from(d),
497        Value::String(s) => JsonValue::from(s),
498      }
499    }
500  }
501
502  /// Converts a `super::feature::Feature` into a `wasm_bindgen::JsValue`.
503  impl From<super::feature::Feature> for wasm_bindgen::JsValue {
504    fn from(feature: super::feature::Feature) -> Self {
505      let properties: Option<JsonObject> = feature.properties.as_ref().map(|props| {
506        props
507          .clone()
508          .into_iter()
509          .map(|(k, v)| (k, v.into()))
510          .collect()
511      });
512
513      let geojson = GeoJson::Feature(Feature {
514        bbox: None,
515        geometry: Some(feature.get_geometry().into()),
516        id: feature.id.map(|id| Id::Number(id.into())),
517        properties,
518        foreign_members: None,
519      });
520
521      geojson.serialize(&Serializer::json_compatible()).unwrap()
522    }
523  }
524
525  /// Converts a `super::layer::Layer` into a `wasm_bindgen::JsValue`.
526  impl From<super::layer::Layer> for wasm_bindgen::JsValue {
527    fn from(layer: super::layer::Layer) -> Self {
528      layer.serialize(&Serializer::json_compatible()).unwrap()
529    }
530  }
531
532  impl Serialize for super::layer::Layer {
533    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
534    where
535      S: serde::ser::Serializer,
536    {
537      let mut state = serializer.serialize_struct("Layer", 5)?;
538      state.serialize_field("layer_index", &self.layer_index)?;
539      state.serialize_field("version", &self.version)?;
540      state.serialize_field("name", &self.name)?;
541      state.serialize_field("feature_count", &self.feature_count)?;
542      state.serialize_field("extent", &self.extent)?;
543      state.end()
544    }
545  }
546
547  /// Reader for decoding and accessing vector tile data in WebAssembly.
548  #[wasm_bindgen]
549  pub struct Reader {
550    reader: Option<super::Reader>,
551  }
552
553  #[wasm_bindgen]
554  impl Reader {
555    /// Creates a new `Reader` instance with the provided vector tile data.
556    ///
557    /// # Arguments
558    ///
559    /// * `data` - The vector tile data as a `Vec<u8>`.
560    /// * `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.
561    ///
562    /// # Examples
563    ///
564    /// ```
565    /// let tileData = getVectorTileData();
566    /// let reader = new Reader(tileData, handleErrors);
567    /// ```
568    #[wasm_bindgen(constructor)]
569    pub fn new(data: Vec<u8>, error_callback: Option<js_sys::Function>) -> Reader {
570      let reader = match super::Reader::new(data) {
571        Ok(reader) => Some(reader),
572        Err(error) => {
573          if let Some(callback) = error_callback {
574            callback
575              .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
576              .unwrap();
577          }
578          None
579        }
580      };
581      Reader { reader }
582    }
583
584    /// Retrieves the layer names present in the vector tile.
585    ///
586    /// # Arguments
587    ///
588    /// * `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.
589    ///
590    /// # Returns
591    ///
592    /// A JavaScript array containing the layer names as strings.
593    ///
594    /// # Examples
595    ///
596    /// ```
597    /// let layerNames = reader.getLayerNames(handleErrors);
598    /// for (let i = 0; i < layerNames.length; i++) {
599    ///   console.log(layerNames[i]);
600    /// }
601    /// ```
602    #[wasm_bindgen(js_name = getLayerNames)]
603    pub fn get_layer_names(&self, error_callback: Option<js_sys::Function>) -> JsValue {
604      self.handle_result(|reader| reader.get_layer_names(), error_callback)
605    }
606
607    /// Retrieves the layer metadata present in the vector tile.
608    ///
609    /// # Arguments
610    ///
611    /// * `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.
612    ///
613    /// # Returns
614    ///
615    /// A JavaScript array containing the layer metadata as objects.
616    ///
617    /// # Examples
618    ///
619    /// ```
620    /// let layers = reader.getLayerMetadata(handleErrors);
621    /// for (let i = 0; i < layers.length; i++) {
622    ///   console.log(layers[i].name);
623    /// }
624    /// ```
625    #[wasm_bindgen(js_name = getLayerMetadata)]
626    pub fn get_layer_metadata(&self, error_callback: Option<js_sys::Function>) -> JsValue {
627      self.handle_result(|reader| reader.get_layer_metadata(), error_callback)
628    }
629
630    /// Retrieves the features of a specific layer in the vector tile.
631    ///
632    /// # Arguments
633    ///
634    /// * `layer_index` - The index of the layer to retrieve features from.
635    /// * `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.
636    ///
637    /// # Returns
638    ///
639    /// A JavaScript array containing the features as GeoJSON objects.
640    ///
641    /// # Examples
642    ///
643    /// ```
644    /// let features = reader.getFeatures(0, handleErrors);
645    /// for (let i = 0; i < features.length; i++) {
646    ///   console.log(features[i]);
647    /// }
648    /// ```
649    #[wasm_bindgen(js_name = getFeatures)]
650    pub fn get_features(
651      &self,
652      layer_index: usize,
653      error_callback: Option<js_sys::Function>,
654    ) -> JsValue {
655      self.handle_result(|reader| reader.get_features(layer_index), error_callback)
656    }
657
658    fn handle_result<T, F>(&self, operation: F, error_callback: Option<js_sys::Function>) -> JsValue
659    where
660      T: IntoIterator,
661      T::Item: Into<JsValue>,
662      F: FnOnce(&super::Reader) -> Result<T, super::error::ParserError>,
663    {
664      match &self.reader {
665        Some(reader) => match operation(reader) {
666          Ok(result) => result
667            .into_iter()
668            .map(Into::into)
669            .collect::<js_sys::Array>()
670            .into(),
671          Err(error) => {
672            if let Some(callback) = error_callback {
673              callback
674                .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
675                .unwrap();
676            }
677            JsValue::NULL
678          }
679        },
680        None => JsValue::NULL,
681      }
682    }
683  }
684}