Skip to main content

oxigdal_services/
mvt.rs

1//! Mapbox Vector Tile (MVT) generation
2//!
3//! Implements MVT spec v2.1 (<https://github.com/mapbox/vector-tile-spec>)
4//! using a hand-rolled Protocol Buffers encoder — no external `protobuf` crate
5//! required, keeping the dependency tree lean.
6//!
7//! # Wire encoding
8//!
9//! Protocol Buffers uses a tagged binary format with **wire types**:
10//! - 0 (Varint): variable-length integer
11//! - 1 (64-bit): fixed 64-bit value
12//! - 2 (Length-delimited): length prefix followed by bytes
13//! - 5 (32-bit): fixed 32-bit value
14//!
15//! # MVT structure
16//!
17//! ```text
18//! Tile
19//!   └── Layer  (field 3, repeated)
20//!         ├── version   (field 15)
21//!         ├── name      (field 1)
22//!         ├── Feature   (field 2, repeated)
23//!         │     ├── id            (field 1, optional)
24//!         │     ├── tags          (field 2, packed varint)
25//!         │     ├── type          (field 3, varint)
26//!         │     └── geometry      (field 4, packed sint32 zigzag)
27//!         ├── keys      (field 3, repeated string)
28//!         ├── values    (field 4, repeated Value)
29//!         └── extent    (field 5)
30//! ```
31//!
32//! # Geometry commands
33//!
34//! Geometry is encoded as a sequence of drawing commands (MoveTo, LineTo, ClosePath).
35//! Coordinates are delta-encoded relative to the previous cursor position, then
36//! zigzag-encoded as varints to keep small signed values compact.
37
38use crate::error::ServiceError;
39
40// ─────────────────────────────────────────────────────────────────────────────
41// Protocol Buffer primitives
42// ─────────────────────────────────────────────────────────────────────────────
43
44/// Encode an unsigned integer as a LEB-128 (Protocol Buffers varint).
45pub fn encode_varint(mut value: u64) -> Vec<u8> {
46    let mut buf = Vec::with_capacity(10);
47    loop {
48        if value < 0x80 {
49            buf.push(value as u8);
50            return buf;
51        }
52        buf.push((value as u8 & 0x7F) | 0x80);
53        value >>= 7;
54    }
55}
56
57/// Zigzag-encode a signed integer to an unsigned integer.
58///
59/// Maps small negative values to small positive integers:
60/// 0 → 0, -1 → 1, 1 → 2, -2 → 3, 2 → 4, …
61pub fn encode_zigzag(value: i64) -> u64 {
62    ((value << 1) ^ (value >> 63)) as u64
63}
64
65/// Decode a zigzag-encoded value back to a signed integer.
66pub fn decode_zigzag(value: u64) -> i64 {
67    ((value >> 1) as i64) ^ (-((value & 1) as i64))
68}
69
70/// Encode a field tag: `(field_number << 3) | wire_type`.
71fn encode_tag(field: u32, wire_type: u8) -> Vec<u8> {
72    encode_varint(((field << 3) | wire_type as u32) as u64)
73}
74
75/// Encode a varint field (wire type 0).
76pub(crate) fn encode_varint_field(field: u32, value: u64) -> Vec<u8> {
77    let mut buf = encode_tag(field, 0);
78    buf.extend_from_slice(&encode_varint(value));
79    buf
80}
81
82/// Encode a length-delimited field (wire type 2).
83pub(crate) fn encode_len_delimited(field: u32, data: &[u8]) -> Vec<u8> {
84    let mut buf = encode_tag(field, 2);
85    buf.extend_from_slice(&encode_varint(data.len() as u64));
86    buf.extend_from_slice(data);
87    buf
88}
89
90/// Encode a string field (wire type 2, UTF-8 bytes).
91pub(crate) fn encode_string_field(field: u32, s: &str) -> Vec<u8> {
92    encode_len_delimited(field, s.as_bytes())
93}
94
95// ─────────────────────────────────────────────────────────────────────────────
96// MVT geometry types
97// ─────────────────────────────────────────────────────────────────────────────
98
99/// Geometry type as defined in the MVT spec.
100#[derive(Debug, Clone, PartialEq, Eq)]
101pub enum MvtGeometryType {
102    /// Unspecified / unknown geometry
103    Unknown = 0,
104    /// One or more points
105    Point = 1,
106    /// One or more line strings
107    LineString = 2,
108    /// One or more polygons
109    Polygon = 3,
110}
111
112impl MvtGeometryType {
113    fn as_u64(&self) -> u64 {
114        match self {
115            Self::Unknown => 0,
116            Self::Point => 1,
117            Self::LineString => 2,
118            Self::Polygon => 3,
119        }
120    }
121}
122
123// ─────────────────────────────────────────────────────────────────────────────
124// MVT property values
125// ─────────────────────────────────────────────────────────────────────────────
126
127/// A typed property value stored in an MVT layer value table.
128///
129/// Each variant maps to a distinct field number in the `Value` proto message.
130#[derive(Debug, Clone)]
131pub enum MvtValue {
132    /// UTF-8 string (field 1)
133    String(String),
134    /// 32-bit floating point (field 2, wire type 5)
135    Float(f32),
136    /// 64-bit floating point (field 3, wire type 1)
137    Double(f64),
138    /// Signed integer, stored as int64 (field 4)
139    Int(i64),
140    /// Unsigned integer (field 5)
141    UInt(u64),
142    /// Signed integer, stored as sint64 (zigzag, field 6)
143    Sint(i64),
144    /// Boolean (field 7)
145    Bool(bool),
146}
147
148impl MvtValue {
149    /// Encode this value as a protobuf `Value` message body.
150    pub fn encode(&self) -> Vec<u8> {
151        match self {
152            Self::String(s) => encode_string_field(1, s),
153            Self::Float(v) => {
154                // field 2, wire type 5 (32-bit fixed)
155                let mut buf = encode_tag(2, 5);
156                buf.extend_from_slice(&v.to_le_bytes());
157                buf
158            }
159            Self::Double(v) => {
160                // field 3, wire type 1 (64-bit fixed)
161                let mut buf = encode_tag(3, 1);
162                buf.extend_from_slice(&v.to_le_bytes());
163                buf
164            }
165            Self::Int(v) => encode_varint_field(4, *v as u64),
166            Self::UInt(v) => encode_varint_field(5, *v),
167            Self::Sint(v) => encode_varint_field(6, encode_zigzag(*v)),
168            Self::Bool(v) => encode_varint_field(7, u64::from(*v)),
169        }
170    }
171}
172
173// ─────────────────────────────────────────────────────────────────────────────
174// MVT feature
175// ─────────────────────────────────────────────────────────────────────────────
176
177/// A single geographic feature in an MVT layer.
178///
179/// `tags` is a flat array of `[key_idx, value_idx, key_idx, value_idx, …]` pairs
180/// into the parent layer's key and value tables.
181#[derive(Debug, Clone)]
182pub struct MvtFeature {
183    /// Optional numeric feature ID
184    pub id: Option<u64>,
185    /// Geometry type
186    pub geometry_type: MvtGeometryType,
187    /// Encoded geometry drawing commands (already command-encoded, delta, zigzag-ready integers)
188    pub geometry: Vec<i32>,
189    /// Flat array of (key_index, value_index) pairs
190    pub tags: Vec<u32>,
191}
192
193impl MvtFeature {
194    /// Encode this feature as a protobuf `Feature` message body.
195    pub fn encode(&self) -> Vec<u8> {
196        let mut buf = Vec::new();
197
198        // id (field 1, optional)
199        if let Some(id) = self.id {
200            buf.extend_from_slice(&encode_varint_field(1, id));
201        }
202
203        // tags (field 2, packed varint)
204        if !self.tags.is_empty() {
205            let mut packed = Vec::with_capacity(self.tags.len() * 2);
206            for &tag in &self.tags {
207                packed.extend_from_slice(&encode_varint(tag as u64));
208            }
209            buf.extend_from_slice(&encode_len_delimited(2, &packed));
210        }
211
212        // type (field 3, varint)
213        buf.extend_from_slice(&encode_varint_field(3, self.geometry_type.as_u64()));
214
215        // geometry (field 4, packed sint32 via zigzag)
216        if !self.geometry.is_empty() {
217            let mut packed = Vec::with_capacity(self.geometry.len() * 2);
218            for &cmd in &self.geometry {
219                packed.extend_from_slice(&encode_varint(encode_zigzag(cmd as i64)));
220            }
221            buf.extend_from_slice(&encode_len_delimited(4, &packed));
222        }
223
224        buf
225    }
226}
227
228// ─────────────────────────────────────────────────────────────────────────────
229// MVT layer
230// ─────────────────────────────────────────────────────────────────────────────
231
232/// A named collection of features sharing a coordinate space.
233///
234/// All coordinates in the layer are in tile pixel space [0, `extent`).
235pub struct MvtLayer {
236    /// Layer name (e.g. "roads", "buildings")
237    pub name: String,
238    /// MVT version (must be 2)
239    pub version: u32,
240    /// Tile coordinate extent (typically 4096)
241    pub extent: u32,
242    /// De-duplicated list of property key strings
243    pub keys: Vec<String>,
244    /// List of property values (not de-duplicated by default)
245    pub values: Vec<MvtValue>,
246    /// Features in this layer
247    pub features: Vec<MvtFeature>,
248}
249
250impl MvtLayer {
251    /// Create a new layer with the given name, version=2, extent=4096.
252    pub fn new(name: impl Into<String>) -> Self {
253        Self {
254            name: name.into(),
255            version: 2,
256            extent: 4096,
257            keys: Vec::new(),
258            values: Vec::new(),
259            features: Vec::new(),
260        }
261    }
262
263    /// Create a new layer with a custom extent.
264    pub fn with_extent(name: impl Into<String>, extent: u32) -> Self {
265        Self {
266            extent,
267            ..Self::new(name)
268        }
269    }
270
271    /// Get or insert a key, returning its index.
272    ///
273    /// Keys are de-duplicated: the same string always maps to the same index.
274    pub fn key_index(&mut self, key: &str) -> u32 {
275        if let Some(i) = self.keys.iter().position(|k| k == key) {
276            return i as u32;
277        }
278        self.keys.push(key.to_string());
279        (self.keys.len() - 1) as u32
280    }
281
282    /// Append a value and return its index.
283    ///
284    /// Values are not de-duplicated by default for simplicity and performance.
285    pub fn value_index(&mut self, value: MvtValue) -> u32 {
286        self.values.push(value);
287        (self.values.len() - 1) as u32
288    }
289
290    /// Add a feature to this layer.
291    pub fn add_feature(&mut self, feature: MvtFeature) {
292        self.features.push(feature);
293    }
294
295    /// Encode this layer as a protobuf `Layer` message body.
296    pub fn encode(&self) -> Vec<u8> {
297        let mut buf = Vec::new();
298
299        // version (field 15)
300        buf.extend_from_slice(&encode_varint_field(15, self.version as u64));
301
302        // name (field 1)
303        buf.extend_from_slice(&encode_string_field(1, &self.name));
304
305        // features (field 2, repeated)
306        for feature in &self.features {
307            buf.extend_from_slice(&encode_len_delimited(2, &feature.encode()));
308        }
309
310        // keys (field 3, repeated string)
311        for key in &self.keys {
312            buf.extend_from_slice(&encode_string_field(3, key));
313        }
314
315        // values (field 4, repeated Value message)
316        for value in &self.values {
317            buf.extend_from_slice(&encode_len_delimited(4, &value.encode()));
318        }
319
320        // extent (field 5)
321        buf.extend_from_slice(&encode_varint_field(5, self.extent as u64));
322
323        buf
324    }
325
326    /// Return the number of features in this layer.
327    pub fn feature_count(&self) -> usize {
328        self.features.len()
329    }
330}
331
332// ─────────────────────────────────────────────────────────────────────────────
333// Geometry command helpers
334// ─────────────────────────────────────────────────────────────────────────────
335
336/// Produce a **MoveTo** command for one point (absolute delta from cursor).
337///
338/// Command encoding: `(1 << 3) | 1 = 9` (id=1, count=1).
339///
340/// # Arguments
341/// * `dx` / `dy` – delta from the current cursor position
342pub fn move_to(dx: i32, dy: i32) -> Vec<i32> {
343    // CommandInteger: (1 << 3) | 1 = 9
344    vec![9, dx, dy]
345}
346
347/// Produce a **LineTo** command for one or more points.
348///
349/// Command encoding: `(count << 3) | 2`.
350///
351/// # Arguments
352/// * `coords` – slice of `(dx, dy)` deltas
353pub fn line_to(coords: &[(i32, i32)]) -> Vec<i32> {
354    let count = coords.len() as i32;
355    // CommandInteger: (count << 3) | 2
356    let mut cmds = Vec::with_capacity(1 + coords.len() * 2);
357    cmds.push((count << 3) | 2);
358    for &(dx, dy) in coords {
359        cmds.push(dx);
360        cmds.push(dy);
361    }
362    cmds
363}
364
365/// Produce a **ClosePath** command.
366///
367/// Command encoding: `(1 << 3) | 7 = 15`.
368pub fn close_path() -> Vec<i32> {
369    vec![15]
370}
371
372/// Encode a point geometry (single MoveTo).
373pub fn point_geometry(dx: i32, dy: i32) -> Vec<i32> {
374    move_to(dx, dy)
375}
376
377/// Encode a linestring geometry (MoveTo first point, then LineTo for the rest).
378///
379/// All coordinates are deltas from the previous position.
380/// The caller is responsible for computing deltas.
381pub fn linestring_geometry(coords: &[(i32, i32)]) -> Vec<i32> {
382    if coords.is_empty() {
383        return Vec::new();
384    }
385    let mut cmds = move_to(coords[0].0, coords[0].1);
386    if coords.len() > 1 {
387        cmds.extend_from_slice(&line_to(&coords[1..]));
388    }
389    cmds
390}
391
392/// Encode a polygon ring geometry (MoveTo, LineTo, ClosePath).
393///
394/// All coordinates are deltas from the previous position.
395pub fn polygon_ring_geometry(coords: &[(i32, i32)]) -> Vec<i32> {
396    if coords.is_empty() {
397        return Vec::new();
398    }
399    let mut cmds = move_to(coords[0].0, coords[0].1);
400    if coords.len() > 1 {
401        cmds.extend_from_slice(&line_to(&coords[1..]));
402    }
403    cmds.extend_from_slice(&close_path());
404    cmds
405}
406
407// ─────────────────────────────────────────────────────────────────────────────
408// MVT tile
409// ─────────────────────────────────────────────────────────────────────────────
410
411/// A complete MVT tile containing zero or more named layers.
412///
413/// Encode with [`MvtTile::encode`] to obtain the binary payload for HTTP
414/// responses, PMTiles storage, or MBTiles embedding.
415pub struct MvtTile {
416    /// Layers in this tile (each layer = field 3 in Tile proto)
417    pub layers: Vec<MvtLayer>,
418}
419
420impl MvtTile {
421    /// Create an empty tile.
422    pub fn new() -> Self {
423        Self { layers: Vec::new() }
424    }
425
426    /// Add a layer to this tile.
427    pub fn add_layer(&mut self, layer: MvtLayer) {
428        self.layers.push(layer);
429    }
430
431    /// Encode this tile to the binary MVT protobuf format.
432    ///
433    /// The result can be served directly as `application/vnd.mapbox-vector-tile`.
434    pub fn encode(&self) -> Vec<u8> {
435        let mut buf = Vec::new();
436        for layer in &self.layers {
437            // Each layer is wrapped in field 3 (length-delimited)
438            buf.extend_from_slice(&encode_len_delimited(3, &layer.encode()));
439        }
440        buf
441    }
442
443    /// Return the total number of features across all layers.
444    pub fn total_feature_count(&self) -> usize {
445        self.layers.iter().map(|l| l.feature_count()).sum()
446    }
447}
448
449impl Default for MvtTile {
450    fn default() -> Self {
451        Self::new()
452    }
453}
454
455// ─────────────────────────────────────────────────────────────────────────────
456// Coordinate projection helpers
457// ─────────────────────────────────────────────────────────────────────────────
458
459/// Scale a WGS84 longitude/latitude coordinate to tile pixel space [0, extent).
460///
461/// `tile_bbox` is `[west, south, east, north]` in degrees.
462/// Returns `(pixel_x, pixel_y)` clamped to `[0, extent − 1]`.
463pub fn scale_to_tile(lon: f64, lat: f64, tile_bbox: [f64; 4], extent: u32) -> (i32, i32) {
464    let [west, south, east, north] = tile_bbox;
465    let x_raw = (lon - west) / (east - west) * extent as f64;
466    let y_raw = (north - lat) / (north - south) * extent as f64;
467    let x = x_raw as i32;
468    let y = y_raw as i32;
469    let max = extent as i32 - 1;
470    (x.clamp(0, max), y.clamp(0, max))
471}
472
473/// Compute delta coordinates for a sequence of absolute tile-space coordinates.
474///
475/// MVT geometry commands use delta encoding relative to the previous position.
476/// The returned `Vec` has the same length as the input.
477pub fn delta_encode(coords: &[(i32, i32)]) -> Vec<(i32, i32)> {
478    let mut deltas = Vec::with_capacity(coords.len());
479    let mut cursor = (0i32, 0i32);
480    for &(x, y) in coords {
481        deltas.push((x - cursor.0, y - cursor.1));
482        cursor = (x, y);
483    }
484    deltas
485}
486
487// ─────────────────────────────────────────────────────────────────────────────
488// High-level builder helpers
489// ─────────────────────────────────────────────────────────────────────────────
490
491/// Builder for constructing an [`MvtLayer`] from GeoJSON-like feature data.
492///
493/// Handles coordinate scaling, delta encoding, and property table management.
494pub struct MvtLayerBuilder {
495    layer: MvtLayer,
496    tile_bbox: [f64; 4],
497}
498
499impl MvtLayerBuilder {
500    /// Create a new builder for the given layer name, tile bbox, and extent.
501    pub fn new(name: impl Into<String>, tile_bbox: [f64; 4], extent: u32) -> Self {
502        Self {
503            layer: MvtLayer::with_extent(name, extent),
504            tile_bbox,
505        }
506    }
507
508    /// Add a point feature with a set of string properties.
509    ///
510    /// # Errors
511    /// Returns [`ServiceError::InvalidParameter`] if `properties` keys/values are invalid.
512    pub fn add_point(
513        &mut self,
514        lon: f64,
515        lat: f64,
516        id: Option<u64>,
517        properties: &[(&str, MvtValue)],
518    ) -> Result<(), ServiceError> {
519        let (px, py) = scale_to_tile(lon, lat, self.tile_bbox, self.layer.extent);
520        let geom = point_geometry(px, py);
521
522        let mut tags = Vec::with_capacity(properties.len() * 2);
523        for (key, value) in properties {
524            let ki = self.layer.key_index(key);
525            let vi = self.layer.value_index(value.clone());
526            tags.push(ki);
527            tags.push(vi);
528        }
529
530        self.layer.add_feature(MvtFeature {
531            id,
532            geometry_type: MvtGeometryType::Point,
533            geometry: geom,
534            tags,
535        });
536        Ok(())
537    }
538
539    /// Add a linestring feature from a sequence of WGS84 coordinates.
540    pub fn add_linestring(
541        &mut self,
542        coords: &[(f64, f64)],
543        id: Option<u64>,
544        properties: &[(&str, MvtValue)],
545    ) -> Result<(), ServiceError> {
546        if coords.is_empty() {
547            return Err(ServiceError::InvalidParameter(
548                "coords".into(),
549                "linestring must have at least one coordinate".into(),
550            ));
551        }
552
553        let pixel_coords: Vec<(i32, i32)> = coords
554            .iter()
555            .map(|&(lon, lat)| scale_to_tile(lon, lat, self.tile_bbox, self.layer.extent))
556            .collect();
557        let deltas = delta_encode(&pixel_coords);
558        let geom = linestring_geometry(&deltas);
559
560        let mut tags = Vec::with_capacity(properties.len() * 2);
561        for (key, value) in properties {
562            let ki = self.layer.key_index(key);
563            let vi = self.layer.value_index(value.clone());
564            tags.push(ki);
565            tags.push(vi);
566        }
567
568        self.layer.add_feature(MvtFeature {
569            id,
570            geometry_type: MvtGeometryType::LineString,
571            geometry: geom,
572            tags,
573        });
574        Ok(())
575    }
576
577    /// Add a polygon feature from a sequence of WGS84 ring coordinates.
578    pub fn add_polygon(
579        &mut self,
580        ring: &[(f64, f64)],
581        id: Option<u64>,
582        properties: &[(&str, MvtValue)],
583    ) -> Result<(), ServiceError> {
584        if ring.len() < 3 {
585            return Err(ServiceError::InvalidParameter(
586                "ring".into(),
587                "polygon ring must have at least 3 coordinates".into(),
588            ));
589        }
590
591        let pixel_coords: Vec<(i32, i32)> = ring
592            .iter()
593            .map(|&(lon, lat)| scale_to_tile(lon, lat, self.tile_bbox, self.layer.extent))
594            .collect();
595        let deltas = delta_encode(&pixel_coords);
596        let geom = polygon_ring_geometry(&deltas);
597
598        let mut tags = Vec::with_capacity(properties.len() * 2);
599        for (key, value) in properties {
600            let ki = self.layer.key_index(key);
601            let vi = self.layer.value_index(value.clone());
602            tags.push(ki);
603            tags.push(vi);
604        }
605
606        self.layer.add_feature(MvtFeature {
607            id,
608            geometry_type: MvtGeometryType::Polygon,
609            geometry: geom,
610            tags,
611        });
612        Ok(())
613    }
614
615    /// Consume the builder and return the completed layer.
616    pub fn build(self) -> MvtLayer {
617        self.layer
618    }
619}
620
621#[cfg(test)]
622mod tests {
623    use super::*;
624
625    // ── varint encoding ──────────────────────────────────────────────────────
626
627    #[test]
628    fn test_encode_varint_zero() {
629        assert_eq!(encode_varint(0), vec![0x00]);
630    }
631
632    #[test]
633    fn test_encode_varint_one() {
634        assert_eq!(encode_varint(1), vec![0x01]);
635    }
636
637    #[test]
638    fn test_encode_varint_127() {
639        assert_eq!(encode_varint(127), vec![0x7F]);
640    }
641
642    #[test]
643    fn test_encode_varint_128() {
644        // 128 = 0x80 requires two bytes: [0x80, 0x01]
645        assert_eq!(encode_varint(128), vec![0x80, 0x01]);
646    }
647
648    #[test]
649    fn test_encode_varint_300() {
650        // 300 = 256 + 44 = 0x12C
651        // varint: 0xAC 0x02
652        assert_eq!(encode_varint(300), vec![0xAC, 0x02]);
653    }
654
655    #[test]
656    fn test_encode_varint_large() {
657        // 2^14 = 16384 → 3 bytes
658        let enc = encode_varint(16384);
659        assert_eq!(enc.len(), 3);
660    }
661
662    #[test]
663    fn test_encode_varint_max_u32() {
664        let enc = encode_varint(u32::MAX as u64);
665        assert!(!enc.is_empty());
666        assert!(enc.len() <= 5);
667    }
668
669    // ── zigzag encoding ──────────────────────────────────────────────────────
670
671    #[test]
672    fn test_encode_zigzag_zero() {
673        assert_eq!(encode_zigzag(0), 0);
674    }
675
676    #[test]
677    fn test_encode_zigzag_minus_one() {
678        assert_eq!(encode_zigzag(-1), 1);
679    }
680
681    #[test]
682    fn test_encode_zigzag_plus_one() {
683        assert_eq!(encode_zigzag(1), 2);
684    }
685
686    #[test]
687    fn test_encode_zigzag_minus_two() {
688        assert_eq!(encode_zigzag(-2), 3);
689    }
690
691    #[test]
692    fn test_encode_zigzag_plus_two() {
693        assert_eq!(encode_zigzag(2), 4);
694    }
695
696    #[test]
697    fn test_decode_zigzag_roundtrip() {
698        for v in [-100i64, -1, 0, 1, 100, 4095, -4096] {
699            assert_eq!(
700                decode_zigzag(encode_zigzag(v)),
701                v,
702                "roundtrip failed for {}",
703                v
704            );
705        }
706    }
707
708    // ── geometry command encoding ────────────────────────────────────────────
709
710    #[test]
711    fn test_move_to_encoding() {
712        let cmds = move_to(10, 20);
713        // [CommandInteger=9, dx=10, dy=20]
714        assert_eq!(cmds, vec![9, 10, 20]);
715    }
716
717    #[test]
718    fn test_move_to_origin() {
719        let cmds = move_to(0, 0);
720        assert_eq!(cmds, vec![9, 0, 0]);
721    }
722
723    #[test]
724    fn test_close_path_encoding() {
725        let cmds = close_path();
726        // CommandInteger: (1 << 3) | 7 = 15
727        assert_eq!(cmds, vec![15]);
728    }
729
730    #[test]
731    fn test_line_to_two_points() {
732        let cmds = line_to(&[(5, 3), (10, 8)]);
733        // CommandInteger: (2 << 3) | 2 = 18
734        assert_eq!(cmds[0], 18, "command integer for LineTo count=2");
735        assert_eq!(cmds[1], 5);
736        assert_eq!(cmds[2], 3);
737        assert_eq!(cmds[3], 10);
738        assert_eq!(cmds[4], 8);
739        assert_eq!(cmds.len(), 5);
740    }
741
742    #[test]
743    fn test_line_to_one_point() {
744        let cmds = line_to(&[(7, 7)]);
745        // CommandInteger: (1 << 3) | 2 = 10
746        assert_eq!(cmds[0], 10);
747        assert_eq!(cmds.len(), 3);
748    }
749
750    #[test]
751    fn test_polygon_ring_geometry_has_close_path() {
752        let ring = [(0, 0), (100, 0), (100, 100), (0, 100)];
753        let cmds = polygon_ring_geometry(&ring);
754        // Last element should be the ClosePath command (15)
755        assert_eq!(*cmds.last().expect("non-empty"), 15);
756    }
757
758    #[test]
759    fn test_linestring_geometry_starts_with_move_to() {
760        let coords = [(5, 10), (15, 20), (25, 30)];
761        let cmds = linestring_geometry(&coords);
762        // First element is MoveTo CommandInteger (9 for count=1)
763        assert_eq!(cmds[0], 9, "should start with MoveTo");
764        assert_eq!(cmds[1], 5);
765        assert_eq!(cmds[2], 10);
766    }
767
768    // ── MvtLayer construction ────────────────────────────────────────────────
769
770    #[test]
771    fn test_mvt_layer_new_defaults() {
772        let layer = MvtLayer::new("roads");
773        assert_eq!(layer.name, "roads");
774        assert_eq!(layer.version, 2);
775        assert_eq!(layer.extent, 4096);
776        assert!(layer.keys.is_empty());
777        assert!(layer.values.is_empty());
778        assert!(layer.features.is_empty());
779    }
780
781    #[test]
782    fn test_mvt_layer_key_index_insert() {
783        let mut layer = MvtLayer::new("test");
784        let i0 = layer.key_index("name");
785        let i1 = layer.key_index("type");
786        assert_eq!(i0, 0);
787        assert_eq!(i1, 1);
788        assert_eq!(layer.keys.len(), 2);
789    }
790
791    #[test]
792    fn test_mvt_layer_key_index_dedup() {
793        let mut layer = MvtLayer::new("test");
794        let i0 = layer.key_index("name");
795        let i1 = layer.key_index("name");
796        assert_eq!(i0, i1, "duplicate key should return same index");
797        assert_eq!(layer.keys.len(), 1);
798    }
799
800    #[test]
801    fn test_mvt_layer_value_index_append() {
802        let mut layer = MvtLayer::new("test");
803        let i0 = layer.value_index(MvtValue::String("road".into()));
804        let i1 = layer.value_index(MvtValue::Int(42));
805        assert_eq!(i0, 0);
806        assert_eq!(i1, 1);
807    }
808
809    #[test]
810    fn test_mvt_layer_encode_non_empty() {
811        let mut layer = MvtLayer::new("buildings");
812        layer.add_feature(MvtFeature {
813            id: Some(1),
814            geometry_type: MvtGeometryType::Point,
815            geometry: move_to(100, 200),
816            tags: vec![],
817        });
818        let encoded = layer.encode();
819        assert!(!encoded.is_empty(), "encoded layer should not be empty");
820    }
821
822    // ── MvtFeature encoding ──────────────────────────────────────────────────
823
824    #[test]
825    fn test_mvt_feature_encode_has_geometry_type() {
826        let f = MvtFeature {
827            id: None,
828            geometry_type: MvtGeometryType::Point,
829            geometry: move_to(0, 0),
830            tags: vec![],
831        };
832        let encoded = f.encode();
833        // Geometry type field (field 3) must appear
834        // field tag for field 3 wire 0 = (3<<3)|0 = 24 = 0x18
835        assert!(
836            encoded.windows(2).any(|w| w[0] == 0x18),
837            "geometry type field (0x18) not found in encoded feature"
838        );
839    }
840
841    #[test]
842    fn test_mvt_feature_encode_with_id() {
843        let f = MvtFeature {
844            id: Some(42),
845            geometry_type: MvtGeometryType::LineString,
846            geometry: vec![],
847            tags: vec![],
848        };
849        let encoded = f.encode();
850        // field 1 varint tag = (1<<3)|0 = 8 = 0x08
851        assert!(
852            encoded.first() == Some(&0x08),
853            "id field tag (0x08) should be first byte"
854        );
855    }
856
857    #[test]
858    fn test_mvt_feature_encode_with_tags() {
859        let f = MvtFeature {
860            id: None,
861            geometry_type: MvtGeometryType::Polygon,
862            geometry: vec![],
863            tags: vec![0, 0, 1, 1],
864        };
865        let encoded = f.encode();
866        assert!(!encoded.is_empty());
867        // tag field (field 2) tag = (2<<3)|2 = 18 = 0x12
868        assert!(
869            encoded.contains(&0x12),
870            "tags field (0x12) should be present"
871        );
872    }
873
874    // ── MvtTile ──────────────────────────────────────────────────────────────
875
876    #[test]
877    fn test_mvt_tile_empty_encode() {
878        let tile = MvtTile::new();
879        let encoded = tile.encode();
880        assert!(
881            encoded.is_empty(),
882            "empty tile should encode to empty bytes"
883        );
884    }
885
886    #[test]
887    fn test_mvt_tile_wraps_layer_in_field3() {
888        let mut tile = MvtTile::new();
889        let layer = MvtLayer::new("test");
890        tile.add_layer(layer);
891        let encoded = tile.encode();
892        assert!(!encoded.is_empty());
893        // Field 3, wire type 2: tag = (3<<3)|2 = 26 = 0x1A
894        assert_eq!(
895            encoded[0], 0x1A,
896            "tile should start with layer field tag 0x1A"
897        );
898    }
899
900    #[test]
901    fn test_mvt_tile_multiple_layers() {
902        let mut tile = MvtTile::new();
903        tile.add_layer(MvtLayer::new("roads"));
904        tile.add_layer(MvtLayer::new("buildings"));
905        assert_eq!(tile.layers.len(), 2);
906        let encoded = tile.encode();
907        // Both layers should produce field-3 entries
908        let count = encoded.windows(1).filter(|w| w[0] == 0x1A).count();
909        assert_eq!(count, 2, "should have 2 layer field tags");
910    }
911
912    #[test]
913    fn test_mvt_tile_total_feature_count() {
914        let mut tile = MvtTile::new();
915        let mut layer1 = MvtLayer::new("a");
916        layer1.add_feature(MvtFeature {
917            id: None,
918            geometry_type: MvtGeometryType::Point,
919            geometry: move_to(0, 0),
920            tags: vec![],
921        });
922        let mut layer2 = MvtLayer::new("b");
923        layer2.add_feature(MvtFeature {
924            id: None,
925            geometry_type: MvtGeometryType::Point,
926            geometry: move_to(10, 10),
927            tags: vec![],
928        });
929        layer2.add_feature(MvtFeature {
930            id: None,
931            geometry_type: MvtGeometryType::Point,
932            geometry: move_to(20, 20),
933            tags: vec![],
934        });
935        tile.add_layer(layer1);
936        tile.add_layer(layer2);
937        assert_eq!(tile.total_feature_count(), 3);
938    }
939
940    // ── scale_to_tile ────────────────────────────────────────────────────────
941
942    #[test]
943    fn test_scale_to_tile_origin() {
944        let bbox = [-10.0f64, -10.0, 10.0, 10.0];
945        let (x, y) = scale_to_tile(-10.0, 10.0, bbox, 4096);
946        assert_eq!(x, 0);
947        assert_eq!(y, 0);
948    }
949
950    #[test]
951    fn test_scale_to_tile_max_corner() {
952        let bbox = [0.0f64, 0.0, 1.0, 1.0];
953        // Far corner: (1,0) in lon/lat → (extent-1, 0) in pixels
954        let (x, y) = scale_to_tile(1.0, 1.0, bbox, 4096);
955        assert_eq!(x, 4095);
956        assert_eq!(y, 0);
957    }
958
959    #[test]
960    fn test_scale_to_tile_clamps_negative() {
961        let bbox = [0.0f64, 0.0, 1.0, 1.0];
962        let (x, y) = scale_to_tile(-1.0, 2.0, bbox, 4096);
963        assert_eq!(x, 0);
964        assert_eq!(y, 0);
965    }
966
967    #[test]
968    fn test_scale_to_tile_clamps_overflow() {
969        let bbox = [0.0f64, 0.0, 1.0, 1.0];
970        let (x, y) = scale_to_tile(2.0, -1.0, bbox, 4096);
971        assert_eq!(x, 4095);
972        assert_eq!(y, 4095);
973    }
974
975    #[test]
976    fn test_scale_to_tile_center() {
977        let bbox = [-180.0f64, -85.0, 180.0, 85.0];
978        let (x, y) = scale_to_tile(0.0, 0.0, bbox, 4096);
979        assert_eq!(x, 2048);
980        // y ≈ 2048 (equator is near the middle for this symmetric bbox)
981        assert!((y - 2048).abs() <= 2, "y={}", y);
982    }
983
984    // ── delta_encode ─────────────────────────────────────────────────────────
985
986    #[test]
987    fn test_delta_encode_basic() {
988        let coords = [(10, 20), (15, 25), (5, 30)];
989        let deltas = delta_encode(&coords);
990        assert_eq!(deltas[0], (10, 20));
991        assert_eq!(deltas[1], (5, 5));
992        assert_eq!(deltas[2], (-10, 5));
993    }
994
995    #[test]
996    fn test_delta_encode_empty() {
997        let deltas = delta_encode(&[]);
998        assert!(deltas.is_empty());
999    }
1000
1001    // ── MvtLayerBuilder ──────────────────────────────────────────────────────
1002
1003    #[test]
1004    fn test_layer_builder_add_point() {
1005        let bbox = [-180.0f64, -85.0, 180.0, 85.0];
1006        let mut builder = MvtLayerBuilder::new("poi", bbox, 4096);
1007        builder
1008            .add_point(
1009                0.0,
1010                0.0,
1011                Some(1),
1012                &[("name", MvtValue::String("origin".into()))],
1013            )
1014            .expect("add_point should succeed");
1015        let layer = builder.build();
1016        assert_eq!(layer.feature_count(), 1);
1017        assert_eq!(layer.keys.len(), 1);
1018        assert_eq!(layer.keys[0], "name");
1019    }
1020
1021    #[test]
1022    fn test_layer_builder_add_linestring() {
1023        let bbox = [-180.0f64, -85.0, 180.0, 85.0];
1024        let mut builder = MvtLayerBuilder::new("roads", bbox, 4096);
1025        let coords = [(-10.0f64, 20.0), (0.0, 30.0), (10.0, 40.0)];
1026        builder
1027            .add_linestring(
1028                &coords,
1029                None,
1030                &[("highway", MvtValue::String("motorway".into()))],
1031            )
1032            .expect("add_linestring should succeed");
1033        let layer = builder.build();
1034        assert_eq!(layer.feature_count(), 1);
1035        let f = &layer.features[0];
1036        assert_eq!(f.geometry_type, MvtGeometryType::LineString);
1037    }
1038
1039    #[test]
1040    fn test_layer_builder_add_linestring_empty_error() {
1041        let bbox = [-180.0f64, -85.0, 180.0, 85.0];
1042        let mut builder = MvtLayerBuilder::new("roads", bbox, 4096);
1043        let result = builder.add_linestring(&[], None, &[]);
1044        assert!(result.is_err(), "empty linestring should return error");
1045    }
1046
1047    #[test]
1048    fn test_layer_builder_add_polygon() {
1049        let bbox = [-1.0f64, -1.0, 1.0, 1.0];
1050        let mut builder = MvtLayerBuilder::new("buildings", bbox, 4096);
1051        let ring = [(-0.5f64, -0.5), (0.5, -0.5), (0.5, 0.5), (-0.5, 0.5)];
1052        builder
1053            .add_polygon(&ring, Some(99), &[("height", MvtValue::Int(10))])
1054            .expect("add_polygon should succeed");
1055        let layer = builder.build();
1056        let f = &layer.features[0];
1057        assert_eq!(f.geometry_type, MvtGeometryType::Polygon);
1058        assert_eq!(f.id, Some(99));
1059        // Polygon geometry ends with ClosePath (15 in zigzag = 30 in varint)
1060        assert_eq!(*f.geometry.last().expect("non-empty"), 15);
1061    }
1062
1063    // ── Full round-trip ──────────────────────────────────────────────────────
1064
1065    #[test]
1066    fn test_full_roundtrip_tile_encode_non_empty() {
1067        let bbox = [-180.0f64, -90.0, 180.0, 90.0];
1068        let mut builder = MvtLayerBuilder::new("countries", bbox, 4096);
1069        builder
1070            .add_point(
1071                139.6917,
1072                35.6895,
1073                Some(1),
1074                &[
1075                    ("name", MvtValue::String("Tokyo".into())),
1076                    ("pop", MvtValue::Int(13_960_000)),
1077                ],
1078            )
1079            .expect("add Tokyo point");
1080        builder
1081            .add_point(
1082                -0.1276,
1083                51.5074,
1084                Some(2),
1085                &[
1086                    ("name", MvtValue::String("London".into())),
1087                    ("pop", MvtValue::Int(8_982_000)),
1088                ],
1089            )
1090            .expect("add London point");
1091
1092        let layer = builder.build();
1093        assert_eq!(layer.feature_count(), 2);
1094
1095        let mut tile = MvtTile::new();
1096        tile.add_layer(layer);
1097
1098        let encoded = tile.encode();
1099        assert!(!encoded.is_empty(), "encoded tile must not be empty");
1100        // Must start with layer field tag (0x1A)
1101        assert_eq!(encoded[0], 0x1A);
1102    }
1103
1104    #[test]
1105    fn test_mvt_value_string_encode() {
1106        let v = MvtValue::String("hello".into());
1107        let enc = v.encode();
1108        // field 1, wire 2: tag = 0x0A, length=5, then "hello"
1109        assert_eq!(enc[0], 0x0A);
1110        assert_eq!(enc[1], 5);
1111    }
1112
1113    #[test]
1114    fn test_mvt_value_bool_true_encode() {
1115        let v = MvtValue::Bool(true);
1116        let enc = v.encode();
1117        assert!(!enc.is_empty());
1118    }
1119
1120    #[test]
1121    fn test_mvt_value_double_encode_length() {
1122        let v = MvtValue::Double(std::f64::consts::PI);
1123        let enc = v.encode();
1124        // tag(1 byte) + 8 bytes = 9 bytes minimum
1125        assert!(enc.len() >= 9);
1126    }
1127}