Skip to main content

mlt_wasm/
lib.rs

1//! WebAssembly bindings for the `MapLibre` Tile (MLT) format.
2//!
3//! # Design
4//!
5//! A single `MltTile` struct owns all decoded [`mlt_core::TileLayer`] data for
6//! every layer in the tile.  No per-layer or per-feature WASM objects are
7//! created; every accessor takes explicit `(layer_idx, feature_idx)` arguments
8//! so the JavaScript side can keep plain numeric indices rather than
9//! heap-allocated wrapper objects.
10//!
11//! ## Geometry
12//!
13//! `MltTile::layer_geometry` returns a `LayerGeometry`
14//! whose typed-array getters expose the raw offset and vertex buffers.
15//! JS walks these directly - zero WASM boundary crossings per feature.
16//!
17//! ## IDs
18//!
19//! `MltTile::layer_ids` returns a `Float64Array` - one `f64` per
20//! feature.  Absent IDs are `NaN` (≡ `undefined` after the JS wrapper checks
21//! `isNaN`).  IDs above `Number.MAX_SAFE_INTEGER` lose precision.
22//!
23//! ## Properties
24//!
25//! `MltTile::layer_property_keys` and `MltTile::layer_properties`
26//! expose all property columns as typed arrays built once per layer.  JS reads
27//! any feature's property with a single array index - zero WASM calls during
28//! traversal.
29
30mod geometry;
31mod layer;
32mod properties;
33mod tile;
34
35use js_sys::Uint8Array;
36use layer::DecodedLayer;
37use mlt_core::{Decoder, GeometryType, MltError, Parser};
38use tile::MltTile;
39use wasm_bindgen::prelude::*;
40
41/// Decode a raw MLT tile blob and return an `MltTile`.
42///
43/// All geometry, IDs and properties are decoded eagerly into row-oriented
44/// [`mlt_core::TileLayer`] values.
45#[wasm_bindgen]
46pub fn decode_tile(data: &[u8]) -> Result<MltTile, JsError> {
47    let mut parser = Parser::default();
48    let raw_layers = parser.parse_layers(data).map_err(|e| to_js_err(&e))?;
49    let mut dec = Decoder::default();
50    let mut layers = Vec::with_capacity(raw_layers.len());
51
52    for raw_layer in raw_layers {
53        // Skip non-Tag01 layers.
54        let mlt_core::Layer::Tag01(layer01) = raw_layer else {
55            continue;
56        };
57
58        // Decode all columns at once, then extract geometry arrays before consuming into tile.
59        let parsed_layer = layer01.decode_all(&mut dec).map_err(|e| to_js_err(&e))?;
60
61        // Clone geometry values for building WASM typed arrays (zero wire-decode overhead:
62        // geometry is already in columnar form from decode_all).
63        let parsed_geometry = parsed_layer.geometry_values().clone();
64
65        let (types_bytes, mlt_types_bytes): (Vec<u8>, Vec<u8>) = parsed_geometry
66            .vector_types()
67            .iter()
68            .map(|t| {
69                let mvt = match t {
70                    GeometryType::Point | GeometryType::MultiPoint => 1,
71                    GeometryType::LineString | GeometryType::MultiLineString => 2,
72                    GeometryType::Polygon | GeometryType::MultiPolygon => 3,
73                    #[allow(unreachable_patterns)]
74                    _ => 0,
75                };
76                (mvt, *t as u8)
77            })
78            .unzip();
79        let types_array = Uint8Array::from(types_bytes.as_slice());
80        let mlt_types_array = Uint8Array::from(mlt_types_bytes.as_slice());
81
82        let tile = parsed_layer
83            .into_tile(&mut dec)
84            .map_err(|e| to_js_err(&e))?;
85
86        layers.push(DecodedLayer {
87            tile,
88            types_array,
89            mlt_types_array,
90            geometry: parsed_geometry,
91        });
92    }
93
94    Ok(MltTile { layers })
95}
96
97pub(crate) fn to_js_err(e: &MltError) -> JsError {
98    JsError::new(&e.to_string())
99}