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 = "1.6.0-alpha.1"
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 = "1.6.0-alpha.1"
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;
67use geo_types::{
68  Coord, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon,
69};
70use prost::{bytes::Bytes, Message};
71use vector_tile::{tile::GeomType, Tile};
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, String>, 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      get_string_value((*values.get(item[1] as usize).expect("item not found")).clone()),
241    );
242  }
243  Ok(result)
244}
245
246fn get_string_value(value: vector_tile::tile::Value) -> String {
247  if value.string_value.is_some() {
248    return value.string_value.unwrap();
249  }
250  if value.float_value.is_some() {
251    return value.float_value.unwrap().to_string();
252  }
253  if value.double_value.is_some() {
254    return value.double_value.unwrap().to_string();
255  }
256  if value.int_value.is_some() {
257    return value.int_value.unwrap().to_string();
258  }
259  if value.uint_value.is_some() {
260    return value.uint_value.unwrap().to_string();
261  }
262  if value.sint_value.is_some() {
263    return value.sint_value.unwrap().to_string();
264  }
265  if value.bool_value.is_some() {
266    return value.bool_value.unwrap().to_string();
267  }
268  String::new()
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 geojson::{feature::Id, Feature, GeoJson, JsonObject};
407  use serde::Serialize;
408  use serde_wasm_bindgen::Serializer;
409  use wasm_bindgen::prelude::*;
410
411  /// Converts a `super::feature::Feature` into a `wasm_bindgen::JsValue`.
412  impl From<super::feature::Feature> for wasm_bindgen::JsValue {
413    fn from(feature: super::feature::Feature) -> Self {
414      let properties: Option<JsonObject> = feature.properties.as_ref().map(|props| {
415        props
416          .clone()
417          .into_iter()
418          .map(|(k, v)| (k, v.into()))
419          .collect()
420      });
421
422      let geojson = GeoJson::Feature(Feature {
423        bbox: None,
424        geometry: Some(feature.get_geometry().into()),
425        id: feature.id.map(|id| Id::Number(id.into())),
426        properties,
427        foreign_members: None,
428      });
429
430      geojson.serialize(&Serializer::json_compatible()).unwrap()
431    }
432  }
433
434  /// Reader for decoding and accessing vector tile data in WebAssembly.
435  #[wasm_bindgen]
436  pub struct Reader {
437    reader: Option<super::Reader>,
438  }
439
440  #[wasm_bindgen]
441  impl Reader {
442    /// Creates a new `Reader` instance with the provided vector tile data.
443    ///
444    /// # Arguments
445    ///
446    /// * `data` - The vector tile data as a `Vec<u8>`.
447    /// * `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.
448    ///
449    /// # Examples
450    ///
451    /// ```
452    /// let tileData = getVectorTileData();
453    /// let reader = new Reader(tileData, handleErrors);
454    /// ```
455    #[wasm_bindgen(constructor)]
456    pub fn new(data: Vec<u8>, error_callback: Option<js_sys::Function>) -> Reader {
457      let reader = match super::Reader::new(data) {
458        Ok(reader) => Some(reader),
459        Err(error) => {
460          if let Some(callback) = error_callback {
461            callback
462              .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
463              .unwrap();
464          }
465          None
466        }
467      };
468      Reader { reader }
469    }
470
471    /// Retrieves the layer names present in the vector tile.
472    ///
473    /// # Arguments
474    ///
475    /// * `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.
476    ///
477    /// # Returns
478    ///
479    /// A JavaScript array containing the layer names as strings.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// let layerNames = reader.getLayerNames(handleErrors);
485    /// for (let i = 0; i < layerNames.length; i++) {
486    ///   console.log(layerNames[i]);
487    /// }
488    /// ```
489    #[wasm_bindgen(js_name = getLayerNames)]
490    pub fn get_layer_names(&self, error_callback: Option<js_sys::Function>) -> JsValue {
491      match &self.reader {
492        Some(reader) => match reader.get_layer_names() {
493          Ok(layer_names) => JsValue::from(
494            layer_names
495              .into_iter()
496              .map(JsValue::from)
497              .collect::<js_sys::Array>(),
498          ),
499          Err(error) => {
500            if let Some(callback) = error_callback {
501              callback
502                .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
503                .unwrap();
504            }
505            JsValue::NULL
506          }
507        },
508        None => JsValue::NULL,
509      }
510    }
511
512    /// Retrieves the features of a specific layer in the vector tile.
513    ///
514    /// # Arguments
515    ///
516    /// * `layer_index` - The index of the layer to retrieve features from.
517    /// * `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.
518    ///
519    /// # Returns
520    ///
521    /// A JavaScript array containing the features as GeoJSON objects.
522    ///
523    /// # Examples
524    ///
525    /// ```
526    /// let features = reader.getFeatures(0, handleErrors);
527    /// for (let i = 0; i < features.length; i++) {
528    ///   console.log(features[i]);
529    /// }
530    /// ```
531    #[wasm_bindgen(js_name = getFeatures)]
532    pub fn get_features(
533      &self,
534      layer_index: usize,
535      error_callback: Option<js_sys::Function>,
536    ) -> JsValue {
537      match &self.reader {
538        Some(reader) => match reader.get_features(layer_index) {
539          Ok(features) => JsValue::from(
540            features
541              .into_iter()
542              .map(JsValue::from)
543              .collect::<js_sys::Array>(),
544          ),
545          Err(error) => {
546            if let Some(callback) = error_callback {
547              callback
548                .call1(&JsValue::NULL, &JsValue::from_str(&format!("{:?}", error)))
549                .unwrap();
550            }
551            JsValue::NULL
552          }
553        },
554        None => JsValue::NULL,
555      }
556    }
557  }
558}