Skip to main content

fast_mvt/
writer.rs

1use buffa::Message as _;
2use dup_indexer::{DupIndexer, DupIndexerRefs, PtrRead};
3use usize_cast::IntoUsize;
4
5use crate::generated::vector_tile::Tile;
6use crate::generated::vector_tile::tile::{Feature, Layer, Value};
7use crate::geom_writer::encode_geometry;
8use crate::{DEFAULT_EXTENT, MvtError, MvtExtent, MvtGeometry, MvtResult, MvtTile, MvtValue};
9
10#[derive(Debug, Default)]
11pub struct MvtTileBuilder(Tile);
12
13impl MvtTileBuilder {
14    #[must_use]
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    #[must_use]
20    pub fn with_capacity(layers: usize) -> Self {
21        Self(Tile {
22            layers: Vec::with_capacity(layers),
23        })
24    }
25
26    pub fn layer(self, name: impl Into<String>) -> MvtResult<MvtLayerBuilder> {
27        self.layer_with_capacity(name, 0)
28    }
29
30    pub fn layer_with_capacity(
31        self,
32        name: impl Into<String>,
33        features: usize,
34    ) -> MvtResult<MvtLayerBuilder> {
35        let name = name.into();
36        if name.is_empty() {
37            return Err(MvtError::MissingLayerName);
38        }
39        Ok(MvtLayerBuilder::with_tile(self, name, features))
40    }
41
42    #[must_use]
43    pub fn encode(self) -> Vec<u8> {
44        self.0.encode_to_vec()
45    }
46
47    #[must_use]
48    pub fn encoded_len(&self) -> usize {
49        self.0.encoded_len().into_usize()
50    }
51
52    fn push_layer(mut self, layer: Layer) -> Self {
53        self.0.layers.push(layer);
54        self
55    }
56}
57
58pub(crate) fn encode_tile(tile: MvtTile) -> MvtResult<Vec<u8>> {
59    let mut tile_bld = MvtTileBuilder::with_capacity(tile.layers.len());
60    for layer in tile.layers {
61        let mut layer_bld = tile_bld.layer_with_capacity(layer.name, layer.features.len())?;
62        layer_bld.extent(layer.extent);
63        for feature in layer.features {
64            let mut feature_bld = layer_bld.feature(&feature.geometry)?;
65            feature_bld.id(feature.id);
66            for (key, value) in feature.properties {
67                feature_bld.tag(key, value)?;
68            }
69            layer_bld = feature_bld.end();
70        }
71        tile_bld = layer_bld.end();
72    }
73    Ok(tile_bld.encode())
74}
75
76pub(crate) fn encode_tile_ref(tile: &MvtTile) -> MvtResult<Vec<u8>> {
77    let mut tile_bld = MvtTileBuilder::with_capacity(tile.layers.len());
78    for layer in &tile.layers {
79        let mut layer_bld =
80            tile_bld.layer_with_capacity(layer.name.clone(), layer.features.len())?;
81        layer_bld.extent(layer.extent);
82        for feature in &layer.features {
83            let mut feature_bld = layer_bld.feature(&feature.geometry)?;
84            feature_bld.id(feature.id);
85            for (key, value) in &feature.properties {
86                feature_bld.tag(key, value.clone())?;
87            }
88            layer_bld = feature_bld.end();
89        }
90        tile_bld = layer_bld.end();
91    }
92    Ok(tile_bld.encode())
93}
94
95#[derive(Debug)]
96pub struct MvtLayerBuilder {
97    tile: MvtTileBuilder,
98    layer: Layer,
99    keys: DupIndexerRefs<String>,
100    values: DupIndexer<MvtValue>,
101}
102
103impl MvtLayerBuilder {
104    /// Create a standalone layer builder that is not attached to a tile.
105    ///
106    /// This is also a convenient entry point for building a layer directly: add
107    /// features and tags as usual, then either [`end`](Self::end) it into a tile
108    /// or [`encode`](Self::encode) it on its own.
109    ///
110    /// Finishing with [`encode`](Self::encode) yields a framed layer chunk.
111    /// Independently built layer buffers (for example, one per thread) can be
112    /// concatenated to form a complete tile — see the crate-level parallel
113    /// encoding example. Returns [`MvtError::MissingLayerName`] if `name` is empty.
114    pub fn new(name: impl Into<String>) -> MvtResult<Self> {
115        MvtTileBuilder::new().layer(name)
116    }
117
118    /// Like [`MvtLayerBuilder::new`], but preallocates space for `features`.
119    pub fn with_capacity(name: impl Into<String>, features: usize) -> MvtResult<Self> {
120        MvtTileBuilder::new().layer_with_capacity(name, features)
121    }
122
123    fn with_tile(tile: MvtTileBuilder, name: String, features: usize) -> Self {
124        Self {
125            tile,
126            layer: Layer {
127                version: 2,
128                name,
129                features: Vec::with_capacity(features),
130                keys: Vec::new(),
131                values: Vec::new(),
132                extent: Some(DEFAULT_EXTENT.get()),
133            },
134            keys: DupIndexerRefs::new(),
135            values: DupIndexer::new(),
136        }
137    }
138
139    pub fn extent(&mut self, extent: MvtExtent) -> &mut Self {
140        self.layer.extent = Some(extent.get());
141        self
142    }
143
144    #[must_use]
145    pub fn name(&self) -> &str {
146        &self.layer.name
147    }
148
149    #[must_use]
150    pub fn num_features(&self) -> usize {
151        self.layer.features.len()
152    }
153
154    pub fn feature(self, geometry: &MvtGeometry) -> MvtResult<MvtFeatureBuilder> {
155        let (geom_type, geometry) = encode_geometry(geometry)?;
156        Ok(MvtFeatureBuilder {
157            layer: self,
158            feature: Feature {
159                id: None,
160                tags: Vec::new(),
161                r#type: Some(geom_type),
162                geometry,
163            },
164        })
165    }
166
167    #[must_use]
168    pub fn end(self) -> MvtTileBuilder {
169        let Self {
170            tile,
171            mut layer,
172            keys,
173            values,
174        } = self;
175        layer.keys = keys.into_vec();
176        layer.values = values.into_iter().map(value_to_proto).collect();
177        tile.push_layer(layer)
178    }
179
180    /// Commit this layer and start a new one.
181    ///
182    /// This is a shortcut for `self.end().layer(name)` that keeps the chain on
183    /// layer builders without exposing the intermediate [`MvtTileBuilder`].
184    /// Returns [`MvtError::MissingLayerName`] if `name` is empty.
185    pub fn layer(self, name: impl Into<String>) -> MvtResult<Self> {
186        self.end().layer(name)
187    }
188
189    /// Commit this layer and encode the tile built so far.
190    ///
191    /// For a builder created with [`MvtLayerBuilder::new`], the parent tile is
192    /// empty, so this encodes exactly this one layer as a framed chunk — several
193    /// such buffers can be concatenated (for example with `buffers.concat()`)
194    /// into a multi-layer tile. For a builder obtained from
195    /// [`MvtTileBuilder::layer`], the result also includes any previously
196    /// committed layers, making it equivalent to `self.end().encode()`.
197    #[must_use]
198    pub fn encode(self) -> Vec<u8> {
199        self.end().encode()
200    }
201}
202
203#[derive(Debug)]
204#[must_use = "call .end() to commit the feature to the layer"]
205pub struct MvtFeatureBuilder {
206    layer: MvtLayerBuilder,
207    feature: Feature,
208}
209
210impl MvtFeatureBuilder {
211    pub fn id(&mut self, id: Option<u64>) -> &mut Self {
212        self.feature.id = id;
213        self
214    }
215
216    pub fn tag(
217        &mut self,
218        key: impl AsRef<str>,
219        value: impl Into<MvtValue>,
220    ) -> MvtResult<&mut Self> {
221        let value = value.into();
222        if value != MvtValue::Null {
223            let key_idx = u32_index(self.layer.keys.insert_ref(key.as_ref()))?;
224            let value_idx = u32_index(self.layer.values.insert(value))?;
225            self.feature.tags.push(key_idx);
226            self.feature.tags.push(value_idx);
227        }
228        Ok(self)
229    }
230
231    pub fn tag_string(
232        &mut self,
233        key: impl AsRef<str>,
234        value: impl Into<String>,
235    ) -> MvtResult<&mut Self> {
236        self.tag(key, MvtValue::String(value.into()))
237    }
238
239    pub fn tag_float(&mut self, key: impl AsRef<str>, value: f32) -> MvtResult<&mut Self> {
240        self.tag(key, MvtValue::Float(value))
241    }
242
243    pub fn tag_double(&mut self, key: impl AsRef<str>, value: f64) -> MvtResult<&mut Self> {
244        self.tag(key, MvtValue::Double(value))
245    }
246
247    pub fn tag_int(&mut self, key: impl AsRef<str>, value: i64) -> MvtResult<&mut Self> {
248        self.tag(key, MvtValue::Int(value))
249    }
250
251    pub fn tag_uint(&mut self, key: impl AsRef<str>, value: u64) -> MvtResult<&mut Self> {
252        self.tag(key, MvtValue::UInt(value))
253    }
254
255    pub fn tag_sint(&mut self, key: impl AsRef<str>, value: i64) -> MvtResult<&mut Self> {
256        self.tag(key, MvtValue::SInt(value))
257    }
258
259    /// Add an integer tag using the smallest MVT encoding for `value`.
260    ///
261    /// See [`MvtValue::auto_int`] for how the encoding is chosen.
262    pub fn tag_auto_int(
263        &mut self,
264        key: impl AsRef<str>,
265        value: impl Into<i64>,
266    ) -> MvtResult<&mut Self> {
267        self.tag(key, MvtValue::auto_int(value))
268    }
269
270    pub fn tag_bool(&mut self, key: impl AsRef<str>, value: bool) -> MvtResult<&mut Self> {
271        self.tag(key, MvtValue::Bool(value))
272    }
273
274    #[must_use]
275    pub fn num_tags(&self) -> usize {
276        self.feature.tags.len() / 2
277    }
278
279    #[must_use]
280    pub fn end(mut self) -> MvtLayerBuilder {
281        self.layer.layer.features.push(self.feature);
282        self.layer
283    }
284}
285
286// This is safe because all `MvtValue` variants contain only `PtrRead` values
287// (`String`, floats, integers, bools, or no payload).
288unsafe impl PtrRead for MvtValue {}
289
290fn value_to_proto(value: MvtValue) -> Value {
291    match value {
292        MvtValue::String(v) => Value::default().with_string_value(v),
293        MvtValue::Float(v) => Value::default().with_float_value(v),
294        MvtValue::Double(v) => Value::default().with_double_value(v),
295        MvtValue::Int(v) => Value::default().with_int_value(v),
296        MvtValue::UInt(v) => Value::default().with_uint_value(v),
297        MvtValue::SInt(v) => Value::default().with_sint_value(v),
298        MvtValue::Bool(v) => Value::default().with_bool_value(v),
299        MvtValue::Null => Value::default(),
300    }
301}
302
303fn u32_index(value: usize) -> MvtResult<u32> {
304    u32::try_from(value).map_err(|_| MvtError::IndexOverflow(value))
305}
306
307#[cfg(test)]
308mod tests {
309    #![expect(clippy::panic_in_result_fn)]
310
311    use geo_types::point;
312
313    use super::*;
314    use crate::MvtGeometry;
315
316    #[test]
317    fn layer_builder_deduplicates_keys_and_values() {
318        let layer = MvtTileBuilder::new().layer("layer").unwrap();
319        let mut feature = layer
320            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
321            .unwrap();
322        feature.tag("foo", MvtValue::String("bar".into())).unwrap();
323        feature.tag("foo", MvtValue::String("baz".into())).unwrap();
324        feature.tag("bar", MvtValue::String("bar".into())).unwrap();
325        feature.tag("n", MvtValue::Int(1)).unwrap();
326        feature.tag("n", MvtValue::SInt(1)).unwrap();
327        feature.tag("f", MvtValue::Float(f32::NAN)).unwrap();
328        feature.tag("f", MvtValue::Float(f32::NAN)).unwrap();
329
330        assert_eq!(
331            feature.feature.tags,
332            vec![0, 0, 0, 1, 1, 0, 2, 2, 2, 3, 3, 4, 3, 4]
333        );
334    }
335
336    #[test]
337    fn encode_appends_and_validates_tile_metadata() {
338        let tile = MvtTileBuilder::new();
339        let layer = tile.layer("layer").unwrap();
340        let mut feature = layer
341            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
342            .unwrap();
343        feature.id(Some(1));
344        feature.tag("skip", MvtValue::Null).unwrap();
345        let layer = feature.end();
346        let bytes = layer.end().encode();
347        let proto = Tile::decode_from_slice(&bytes).unwrap();
348        assert!(proto.layers[0].keys.is_empty());
349        assert!(proto.layers[0].features[0].tags.is_empty());
350
351        let tile = MvtTileBuilder::new();
352        let layer = tile.layer("layer").unwrap();
353        let mut feature = layer
354            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
355            .unwrap();
356        feature.id(Some(1));
357        let layer = feature.end();
358        let tile = layer.end();
359        let mut out = vec![0xaa];
360        out.extend_from_slice(&tile.encode());
361        assert_eq!(out[0], 0xaa);
362
363        let tile = MvtTileBuilder::new();
364        let tile = tile.layer("same").unwrap().end();
365        let tile = tile.layer("same").unwrap().end();
366        assert!(!tile.encode().is_empty());
367    }
368
369    #[test]
370    fn encode_ref_matches_owned_encode() {
371        let mut feature = crate::MvtFeature::new(MvtGeometry::Point(point! { x: 1, y: 2 }));
372        feature.set_id(7);
373        feature.add_tag_string("name", "Example");
374        feature.add_tag_bool("visible", true);
375
376        let mut layer = crate::MvtLayer::new("places", DEFAULT_EXTENT);
377        layer.add_feature(feature);
378
379        let mut tile = MvtTile::new();
380        tile.add_layer(layer);
381
382        assert_eq!(
383            encode_tile(tile.clone()).unwrap(),
384            encode_tile_ref(&tile).unwrap()
385        );
386    }
387
388    #[test]
389    #[cfg(feature = "reader")]
390    fn standalone_layer_encode_matches_tile_path_and_concatenates() {
391        use crate::reader::MvtReaderRef;
392
393        let build = |name| -> MvtResult<Vec<u8>> {
394            let mut feature =
395                MvtLayerBuilder::new(name)?.feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))?;
396            feature.tag("k", MvtValue::UInt(1))?;
397            Ok(feature.end().encode())
398        };
399
400        // A standalone layer buffer equals the same layer built via the tile path.
401        let via_tile = MvtTileBuilder::new()
402            .layer("roads")
403            .unwrap()
404            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
405            .unwrap();
406        let mut via_tile = via_tile;
407        via_tile.tag("k", MvtValue::UInt(1)).unwrap();
408        let via_tile = via_tile.end().end().encode();
409        assert_eq!(build("roads").unwrap(), via_tile);
410
411        // Concatenated layer buffers form a valid multi-layer tile.
412        let tile = [build("roads").unwrap(), build("water").unwrap()].concat();
413        let reader = MvtReaderRef::new(&tile).unwrap();
414        let names: Vec<_> = reader.layers().map(|l| l.name().to_string()).collect();
415        assert_eq!(names, ["roads", "water"]);
416    }
417
418    #[test]
419    #[cfg(feature = "reader")]
420    fn layer_builder_chains_to_next_layer() -> MvtResult<()> {
421        use crate::reader::MvtReaderRef;
422
423        // Chaining `.layer(..)` keeps the builder on the layer without exposing
424        // the tile, and produces the same tile as the explicit tile path.
425        let chained = MvtLayerBuilder::new("roads")?
426            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))?
427            .end()
428            .layer("water")?
429            .feature(&MvtGeometry::Point(point! { x: 3, y: 4 }))?
430            .end()
431            .encode();
432
433        let reader = MvtReaderRef::new(&chained)?;
434        let names: Vec<_> = reader.layers().map(|l| l.name().to_string()).collect();
435        assert_eq!(names, ["roads", "water"]);
436        Ok(())
437    }
438
439    #[test]
440    fn layer_builder_chain_rejects_empty_name() {
441        let layer = MvtLayerBuilder::new("roads").unwrap();
442        assert!(matches!(layer.layer(""), Err(MvtError::MissingLayerName)));
443    }
444
445    #[test]
446    fn standalone_layer_builder_rejects_empty_name() {
447        assert!(matches!(
448            MvtLayerBuilder::new(""),
449            Err(MvtError::MissingLayerName)
450        ));
451        assert!(matches!(
452            MvtLayerBuilder::with_capacity("", 1),
453            Err(MvtError::MissingLayerName)
454        ));
455    }
456
457    #[test]
458    fn layer_builder_rejects_empty_name() {
459        assert!(matches!(
460            MvtTileBuilder::new().layer(""),
461            Err(MvtError::MissingLayerName)
462        ));
463        assert!(matches!(
464            MvtTileBuilder::new().layer_with_capacity("", 1),
465            Err(MvtError::MissingLayerName)
466        ));
467    }
468
469    #[test]
470    fn builder_encoded_len_matches_encoded_bytes() {
471        let builder = MvtTileBuilder::new()
472            .layer("l")
473            .unwrap()
474            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
475            .unwrap()
476            .end()
477            .end();
478        let len = builder.encoded_len();
479        assert_eq!(len, builder.encode().len());
480    }
481
482    #[test]
483    fn layer_builder_accepts_feature_capacity() {
484        let layer = MvtTileBuilder::new()
485            .layer_with_capacity("layer", 2)
486            .unwrap();
487        assert_eq!(layer.layer.features.capacity(), 2);
488    }
489
490    #[test]
491    #[cfg(feature = "reader")]
492    fn tag_auto_int_round_trips_through_reader() {
493        use crate::reader::{MvtReaderRef, MvtValueRef};
494
495        let tile = MvtTileBuilder::new();
496        let layer = tile.layer("l").unwrap();
497        let mut feature = layer
498            .feature(&MvtGeometry::Point(point! { x: 1, y: 2 }))
499            .unwrap();
500        feature.tag_auto_int("pos", 100_i32).unwrap();
501        feature.tag_auto_int("neg", -100_i16).unwrap();
502        feature.tag_auto_int("zero", 0_i64).unwrap();
503        let bytes = feature.end().end().encode();
504
505        let reader = MvtReaderRef::new(&bytes).unwrap();
506        let layer = reader.layers().next().unwrap();
507        let feature = layer.features().next().unwrap();
508        let props = feature.properties_vec().unwrap();
509
510        // Non-negative -> UInt, negative -> SInt.
511        assert_eq!(props[0].0, "pos");
512        assert_eq!(props[0].1, MvtValueRef::UInt(100));
513        assert_eq!(props[1].0, "neg");
514        assert_eq!(props[1].1, MvtValueRef::SInt(-100));
515        assert_eq!(props[2].0, "zero");
516        assert_eq!(props[2].1, MvtValueRef::UInt(0));
517    }
518
519    #[test]
520    fn auto_int_is_never_larger_than_int_or_sint() {
521        for v in [
522            0_i64,
523            1,
524            63,
525            64,
526            127,
527            128,
528            -1,
529            -64,
530            -100,
531            i64::MIN,
532            i64::MAX,
533        ] {
534            let auto = value_to_proto(MvtValue::auto_int(v)).encoded_len();
535            let int = value_to_proto(MvtValue::Int(v)).encoded_len();
536            let sint = value_to_proto(MvtValue::SInt(v)).encoded_len();
537            assert!(auto <= int, "v={v}: auto {auto} > int {int}");
538            assert!(auto <= sint, "v={v}: auto {auto} > sint {sint}");
539        }
540    }
541
542    #[test]
543    fn value_to_proto_handles_all_variants() {
544        assert_eq!(
545            value_to_proto(MvtValue::String("x".into()))
546                .string_value
547                .as_deref(),
548            Some("x")
549        );
550        assert_eq!(value_to_proto(MvtValue::Float(1.0)).float_value, Some(1.0));
551        assert_eq!(
552            value_to_proto(MvtValue::Double(2.0)).double_value,
553            Some(2.0)
554        );
555        assert_eq!(value_to_proto(MvtValue::Int(-3)).int_value, Some(-3));
556        assert_eq!(value_to_proto(MvtValue::UInt(4)).uint_value, Some(4));
557        assert_eq!(value_to_proto(MvtValue::SInt(-5)).sint_value, Some(-5));
558        assert_eq!(value_to_proto(MvtValue::Bool(true)).bool_value, Some(true));
559        assert_eq!(value_to_proto(MvtValue::Null), Value::default());
560    }
561}