1use crate::geometry::{
53 Feature, FeatureCollection, Geometry, LineString, MultiLineString, MultiPoint, MultiPolygon,
54 Point, Polygon, PropertyValue,
55};
56use rustial_math::TileId;
57use std::collections::HashMap;
58use std::fmt;
59
60#[derive(Debug, Clone, Default)]
66pub struct MvtDecodeOptions {
67 pub layer_filter: Vec<String>,
69}
70
71#[derive(Debug, Clone, PartialEq, Eq)]
73pub enum MvtError {
74 TruncatedPayload,
76 UnsupportedWireType(u8),
78 InvalidGeometryCommand(u32),
80 DecodeError(String),
82}
83
84impl fmt::Display for MvtError {
85 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
86 match self {
87 MvtError::TruncatedPayload => write!(f, "truncated MVT payload"),
88 MvtError::UnsupportedWireType(wt) => {
89 write!(f, "unsupported protobuf wire type: {wt}")
90 }
91 MvtError::InvalidGeometryCommand(cmd) => {
92 write!(f, "invalid MVT geometry command: {cmd}")
93 }
94 MvtError::DecodeError(msg) => write!(f, "MVT decode error: {msg}"),
95 }
96 }
97}
98
99impl std::error::Error for MvtError {}
100
101pub type DecodedVectorTile = HashMap<String, FeatureCollection>;
103
104pub fn decode_mvt(
110 bytes: &[u8],
111 tile_id: &TileId,
112 options: &MvtDecodeOptions,
113) -> Result<DecodedVectorTile, MvtError> {
114 let tile_bounds = tile_geo_bounds(tile_id);
115 let mut result = DecodedVectorTile::new();
116 let mut reader = PbReader::new(bytes);
117
118 while reader.remaining() > 0 {
119 let (field_number, wire_type) = reader.read_tag()?;
120 match (field_number, wire_type) {
121 (3, WIRE_LEN) => {
123 let layer_bytes = reader.read_bytes()?;
124 match decode_layer(layer_bytes, &tile_bounds, options) {
125 Ok(Some((name, features))) => {
126 result
127 .entry(name)
128 .or_insert_with(|| FeatureCollection {
129 features: Vec::new(),
130 })
131 .features
132 .extend(features.features);
133 }
134 Ok(None) => {} Err(e) => {
136 log::warn!("skipping malformed MVT layer: {e}");
137 }
138 }
139 }
140 _ => {
141 reader.skip_field(wire_type)?;
142 }
143 }
144 }
145
146 Ok(result)
147}
148
149struct TileGeoBounds {
155 west: f64,
156 south: f64,
157 east: f64,
158 north: f64,
159}
160
161fn tile_geo_bounds(tile: &TileId) -> TileGeoBounds {
162 let n = (1u64 << tile.zoom) as f64;
163 let west = tile.x as f64 / n * 360.0 - 180.0;
164 let east = (tile.x as f64 + 1.0) / n * 360.0 - 180.0;
165
166 let north_rad = std::f64::consts::PI * (1.0 - 2.0 * tile.y as f64 / n);
167 let south_rad = std::f64::consts::PI * (1.0 - 2.0 * (tile.y as f64 + 1.0) / n);
168
169 let north = north_rad.sinh().atan().to_degrees();
170 let south = south_rad.sinh().atan().to_degrees();
171
172 TileGeoBounds {
173 west,
174 south,
175 east,
176 north,
177 }
178}
179
180#[inline]
182fn tile_coord_to_geo(
183 x: i32,
184 y: i32,
185 extent: u32,
186 bounds: &TileGeoBounds,
187) -> rustial_math::GeoCoord {
188 let extent_f = extent as f64;
189 let lon = bounds.west + (x as f64 / extent_f) * (bounds.east - bounds.west);
190 let lat = bounds.north + (y as f64 / extent_f) * (bounds.south - bounds.north);
191 rustial_math::GeoCoord::from_lat_lon(lat, lon)
192}
193
194fn decode_layer(
199 bytes: &[u8],
200 bounds: &TileGeoBounds,
201 options: &MvtDecodeOptions,
202) -> Result<Option<(String, FeatureCollection)>, MvtError> {
203 let mut reader = PbReader::new(bytes);
204 let mut name = String::new();
205 let mut keys: Vec<String> = Vec::new();
206 let mut values: Vec<PropertyValue> = Vec::new();
207 let mut features_bytes: Vec<&[u8]> = Vec::new();
208 let mut extent: u32 = 4096;
209
210 while reader.remaining() > 0 {
211 let (field_number, wire_type) = reader.read_tag()?;
212 match (field_number, wire_type) {
213 (1, WIRE_LEN) => {
215 name = reader.read_string()?;
216 }
217 (2, WIRE_LEN) => {
219 features_bytes.push(reader.read_bytes()?);
220 }
221 (3, WIRE_LEN) => {
223 keys.push(reader.read_string()?);
224 }
225 (4, WIRE_LEN) => {
227 let val_bytes = reader.read_bytes()?;
228 values.push(decode_value(val_bytes)?);
229 }
230 (5, WIRE_VARINT) => {
232 extent = reader.read_varint()? as u32;
233 if extent == 0 {
234 extent = 4096;
235 }
236 }
237 (15, WIRE_VARINT) => {
239 let _ = reader.read_varint()?;
240 }
241 _ => {
242 reader.skip_field(wire_type)?;
243 }
244 }
245 }
246
247 if !options.layer_filter.is_empty() && !options.layer_filter.iter().any(|f| f == &name) {
249 return Ok(None);
250 }
251
252 let mut features = Vec::with_capacity(features_bytes.len());
253 for feat_bytes in features_bytes {
254 match decode_feature(feat_bytes, &keys, &values, extent, bounds) {
255 Ok(feature) => features.push(feature),
256 Err(e) => {
257 log::debug!("skipping malformed MVT feature in layer '{name}': {e}");
258 }
259 }
260 }
261
262 Ok(Some((name, FeatureCollection { features })))
263}
264
265fn decode_value(bytes: &[u8]) -> Result<PropertyValue, MvtError> {
270 let mut reader = PbReader::new(bytes);
271 let mut result = PropertyValue::Null;
272
273 while reader.remaining() > 0 {
274 let (field_number, wire_type) = reader.read_tag()?;
275 match (field_number, wire_type) {
276 (1, WIRE_LEN) => {
278 result = PropertyValue::String(reader.read_string()?);
279 }
280 (2, WIRE_32) => {
282 result = PropertyValue::Number(reader.read_fixed32_f32()? as f64);
283 }
284 (3, WIRE_64) => {
286 result = PropertyValue::Number(reader.read_fixed64_f64()?);
287 }
288 (4, WIRE_VARINT) => {
290 result = PropertyValue::Number(reader.read_varint()? as f64);
291 }
292 (5, WIRE_VARINT) => {
294 result = PropertyValue::Number(reader.read_varint()? as f64);
295 }
296 (6, WIRE_VARINT) => {
298 let raw = reader.read_varint()?;
299 let decoded = zigzag_decode(raw);
300 result = PropertyValue::Number(decoded as f64);
301 }
302 (7, WIRE_VARINT) => {
304 result = PropertyValue::Bool(reader.read_varint()? != 0);
305 }
306 _ => {
307 reader.skip_field(wire_type)?;
308 }
309 }
310 }
311
312 Ok(result)
313}
314
315const GEOM_UNKNOWN: u32 = 0;
321const GEOM_POINT: u32 = 1;
322const GEOM_LINESTRING: u32 = 2;
323const GEOM_POLYGON: u32 = 3;
324
325fn decode_feature(
326 bytes: &[u8],
327 keys: &[String],
328 values: &[PropertyValue],
329 extent: u32,
330 bounds: &TileGeoBounds,
331) -> Result<Feature, MvtError> {
332 let mut reader = PbReader::new(bytes);
333 let mut geom_type: u32 = GEOM_UNKNOWN;
334 let mut geometry_bytes: &[u8] = &[];
335 let mut tags_bytes: &[u8] = &[];
336 let mut feature_id: Option<u64> = None;
337
338 while reader.remaining() > 0 {
339 let (field_number, wire_type) = reader.read_tag()?;
340 match (field_number, wire_type) {
341 (1, WIRE_VARINT) => {
343 feature_id = Some(reader.read_varint()?);
344 }
345 (2, WIRE_LEN) => {
347 tags_bytes = reader.read_bytes()?;
348 }
349 (3, WIRE_VARINT) => {
351 geom_type = reader.read_varint()? as u32;
352 }
353 (4, WIRE_LEN) => {
355 geometry_bytes = reader.read_bytes()?;
356 }
357 _ => {
358 reader.skip_field(wire_type)?;
359 }
360 }
361 }
362
363 let geometry = decode_geometry(geom_type, geometry_bytes, extent, bounds)?;
364 let properties = decode_tags(tags_bytes, keys, values)?;
365
366 let mut props = properties;
367 if let Some(id) = feature_id {
368 props.insert("$id".to_owned(), PropertyValue::Number(id as f64));
369 }
370
371 Ok(Feature {
372 geometry,
373 properties: props,
374 })
375}
376
377fn decode_tags(
378 bytes: &[u8],
379 keys: &[String],
380 values: &[PropertyValue],
381) -> Result<HashMap<String, PropertyValue>, MvtError> {
382 let mut props = HashMap::new();
383 if bytes.is_empty() {
384 return Ok(props);
385 }
386
387 let mut reader = PbReader::new(bytes);
388 while reader.remaining() > 0 {
389 let key_idx = reader.read_varint()? as usize;
390 if reader.remaining() == 0 {
391 break;
392 }
393 let val_idx = reader.read_varint()? as usize;
394
395 if let (Some(key), Some(val)) = (keys.get(key_idx), values.get(val_idx)) {
396 props.insert(key.clone(), val.clone());
397 }
398 }
399
400 Ok(props)
401}
402
403const CMD_MOVE_TO: u32 = 1;
409const CMD_LINE_TO: u32 = 2;
410const CMD_CLOSE_PATH: u32 = 7;
411
412fn decode_geometry(
413 geom_type: u32,
414 bytes: &[u8],
415 extent: u32,
416 bounds: &TileGeoBounds,
417) -> Result<Geometry, MvtError> {
418 let commands = decode_commands(bytes)?;
419 let rings = commands_to_rings(&commands, extent, bounds);
420
421 match geom_type {
422 GEOM_POINT => geometry_from_points(rings),
423 GEOM_LINESTRING => geometry_from_linestrings(rings),
424 GEOM_POLYGON => geometry_from_polygons(rings),
425 _ => Err(MvtError::DecodeError(format!(
426 "unknown geometry type: {geom_type}"
427 ))),
428 }
429}
430
431struct GeomCommand {
432 id: u32,
433 params: Vec<(i32, i32)>,
434}
435
436fn decode_commands(bytes: &[u8]) -> Result<Vec<GeomCommand>, MvtError> {
437 let mut reader = PbReader::new(bytes);
438 let mut commands = Vec::new();
439 let mut cursor_x: i32 = 0;
440 let mut cursor_y: i32 = 0;
441
442 while reader.remaining() > 0 {
443 let cmd_int = reader.read_varint()? as u32;
444 let cmd_id = cmd_int & 0x7;
445 let cmd_count = cmd_int >> 3;
446
447 if cmd_id == CMD_CLOSE_PATH {
448 commands.push(GeomCommand {
449 id: CMD_CLOSE_PATH,
450 params: Vec::new(),
451 });
452 continue;
453 }
454
455 if cmd_id != CMD_MOVE_TO && cmd_id != CMD_LINE_TO {
456 return Err(MvtError::InvalidGeometryCommand(cmd_int));
457 }
458
459 let mut params = Vec::with_capacity(cmd_count as usize);
460 for _ in 0..cmd_count {
461 if reader.remaining() < 2 {
462 break;
463 }
464 let dx = zigzag_decode(reader.read_varint()?) as i32;
465 let dy = zigzag_decode(reader.read_varint()?) as i32;
466 cursor_x += dx;
467 cursor_y += dy;
468 params.push((cursor_x, cursor_y));
469 }
470
471 commands.push(GeomCommand { id: cmd_id, params });
472 }
473
474 Ok(commands)
475}
476
477fn commands_to_rings(
478 commands: &[GeomCommand],
479 extent: u32,
480 bounds: &TileGeoBounds,
481) -> Vec<Vec<rustial_math::GeoCoord>> {
482 let mut rings: Vec<Vec<rustial_math::GeoCoord>> = Vec::new();
483 let mut current_ring: Vec<rustial_math::GeoCoord> = Vec::new();
484
485 for cmd in commands {
486 match cmd.id {
487 CMD_MOVE_TO => {
488 if !current_ring.is_empty() {
489 rings.push(std::mem::take(&mut current_ring));
490 }
491 for &(x, y) in &cmd.params {
492 current_ring.push(tile_coord_to_geo(x, y, extent, bounds));
493 }
494 }
495 CMD_LINE_TO => {
496 for &(x, y) in &cmd.params {
497 current_ring.push(tile_coord_to_geo(x, y, extent, bounds));
498 }
499 }
500 CMD_CLOSE_PATH => {
501 if let Some(&first) = current_ring.first() {
502 current_ring.push(first);
503 }
504 rings.push(std::mem::take(&mut current_ring));
505 }
506 _ => {}
507 }
508 }
509
510 if !current_ring.is_empty() {
511 rings.push(current_ring);
512 }
513
514 rings
515}
516
517fn geometry_from_points(rings: Vec<Vec<rustial_math::GeoCoord>>) -> Result<Geometry, MvtError> {
518 let points: Vec<Point> = rings
519 .into_iter()
520 .flat_map(|ring| ring.into_iter().map(|coord| Point { coord }))
521 .collect();
522
523 match points.len() {
524 0 => Err(MvtError::DecodeError("empty point geometry".into())),
525 1 => Ok(Geometry::Point(points.into_iter().next().expect("len==1"))),
526 _ => Ok(Geometry::MultiPoint(MultiPoint { points })),
527 }
528}
529
530fn geometry_from_linestrings(
531 rings: Vec<Vec<rustial_math::GeoCoord>>,
532) -> Result<Geometry, MvtError> {
533 let lines: Vec<LineString> = rings
534 .into_iter()
535 .filter(|r| r.len() >= 2)
536 .map(|coords| LineString { coords })
537 .collect();
538
539 match lines.len() {
540 0 => Err(MvtError::DecodeError("empty linestring geometry".into())),
541 1 => Ok(Geometry::LineString(
542 lines.into_iter().next().expect("len==1"),
543 )),
544 _ => Ok(Geometry::MultiLineString(MultiLineString { lines })),
545 }
546}
547
548fn geometry_from_polygons(rings: Vec<Vec<rustial_math::GeoCoord>>) -> Result<Geometry, MvtError> {
549 if rings.is_empty() {
550 return Err(MvtError::DecodeError("empty polygon geometry".into()));
551 }
552
553 let mut polygons: Vec<Polygon> = Vec::new();
554
555 for ring in rings {
556 if ring.len() < 4 {
557 continue;
558 }
559
560 let area = signed_ring_area(&ring);
561
562 if area > 0.0 {
563 polygons.push(Polygon {
567 exterior: ring,
568 interiors: Vec::new(),
569 });
570 } else if area < 0.0 {
571 if let Some(last) = polygons.last_mut() {
573 last.interiors.push(ring);
574 } else {
575 let mut reversed = ring;
577 reversed.reverse();
578 polygons.push(Polygon {
579 exterior: reversed,
580 interiors: Vec::new(),
581 });
582 }
583 }
584 }
586
587 match polygons.len() {
588 0 => Err(MvtError::DecodeError("no valid polygon rings found".into())),
589 1 => Ok(Geometry::Polygon(
590 polygons.into_iter().next().expect("len==1"),
591 )),
592 _ => Ok(Geometry::MultiPolygon(MultiPolygon { polygons })),
593 }
594}
595
596fn signed_ring_area(ring: &[rustial_math::GeoCoord]) -> f64 {
600 let mut area = 0.0f64;
601 let n = ring.len();
602 if n < 3 {
603 return 0.0;
604 }
605 for i in 0..n {
606 let j = (i + 1) % n;
607 area += ring[i].lon * ring[j].lat;
609 area -= ring[j].lon * ring[i].lat;
610 }
611 area / 2.0
612}
613
614const WIRE_VARINT: u8 = 0;
620const WIRE_64: u8 = 1;
621const WIRE_LEN: u8 = 2;
622const WIRE_32: u8 = 5;
623
624struct PbReader<'a> {
626 data: &'a [u8],
627 pos: usize,
628}
629
630impl<'a> PbReader<'a> {
631 fn new(data: &'a [u8]) -> Self {
632 Self { data, pos: 0 }
633 }
634
635 #[inline]
636 fn remaining(&self) -> usize {
637 self.data.len().saturating_sub(self.pos)
638 }
639
640 fn read_byte(&mut self) -> Result<u8, MvtError> {
641 if self.pos >= self.data.len() {
642 return Err(MvtError::TruncatedPayload);
643 }
644 let b = self.data[self.pos];
645 self.pos += 1;
646 Ok(b)
647 }
648
649 fn read_varint(&mut self) -> Result<u64, MvtError> {
650 let mut result: u64 = 0;
651 let mut shift: u32 = 0;
652 loop {
653 let b = self.read_byte()?;
654 result |= ((b & 0x7F) as u64) << shift;
655 if b & 0x80 == 0 {
656 return Ok(result);
657 }
658 shift += 7;
659 if shift >= 64 {
660 return Err(MvtError::DecodeError("varint too long".into()));
661 }
662 }
663 }
664
665 fn read_tag(&mut self) -> Result<(u32, u8), MvtError> {
666 let varint = self.read_varint()? as u32;
667 let field_number = varint >> 3;
668 let wire_type = (varint & 0x7) as u8;
669 Ok((field_number, wire_type))
670 }
671
672 fn read_bytes(&mut self) -> Result<&'a [u8], MvtError> {
673 let len = self.read_varint()? as usize;
674 if self.pos + len > self.data.len() {
675 return Err(MvtError::TruncatedPayload);
676 }
677 let slice = &self.data[self.pos..self.pos + len];
678 self.pos += len;
679 Ok(slice)
680 }
681
682 fn read_string(&mut self) -> Result<String, MvtError> {
683 let bytes = self.read_bytes()?;
684 String::from_utf8(bytes.to_vec())
685 .map_err(|e| MvtError::DecodeError(format!("invalid UTF-8: {e}")))
686 }
687
688 fn read_fixed32_f32(&mut self) -> Result<f32, MvtError> {
689 if self.remaining() < 4 {
690 return Err(MvtError::TruncatedPayload);
691 }
692 let bytes = [
693 self.data[self.pos],
694 self.data[self.pos + 1],
695 self.data[self.pos + 2],
696 self.data[self.pos + 3],
697 ];
698 self.pos += 4;
699 Ok(f32::from_le_bytes(bytes))
700 }
701
702 fn read_fixed64_f64(&mut self) -> Result<f64, MvtError> {
703 if self.remaining() < 8 {
704 return Err(MvtError::TruncatedPayload);
705 }
706 let mut bytes = [0u8; 8];
707 bytes.copy_from_slice(&self.data[self.pos..self.pos + 8]);
708 self.pos += 8;
709 Ok(f64::from_le_bytes(bytes))
710 }
711
712 fn skip_field(&mut self, wire_type: u8) -> Result<(), MvtError> {
713 match wire_type {
714 WIRE_VARINT => {
715 let _ = self.read_varint()?;
716 }
717 WIRE_64 => {
718 if self.remaining() < 8 {
719 return Err(MvtError::TruncatedPayload);
720 }
721 self.pos += 8;
722 }
723 WIRE_LEN => {
724 let _ = self.read_bytes()?;
725 }
726 WIRE_32 => {
727 if self.remaining() < 4 {
728 return Err(MvtError::TruncatedPayload);
729 }
730 self.pos += 4;
731 }
732 other => return Err(MvtError::UnsupportedWireType(other)),
733 }
734 Ok(())
735 }
736}
737
738#[inline]
740fn zigzag_decode(n: u64) -> i64 {
741 ((n >> 1) as i64) ^ -((n & 1) as i64)
742}
743
744#[cfg(test)]
749mod tests {
750 use super::*;
751
752 #[test]
755 fn zigzag_decode_positive() {
756 assert_eq!(zigzag_decode(0), 0);
757 assert_eq!(zigzag_decode(2), 1);
758 assert_eq!(zigzag_decode(4), 2);
759 assert_eq!(zigzag_decode(100), 50);
760 }
761
762 #[test]
763 fn zigzag_decode_negative() {
764 assert_eq!(zigzag_decode(1), -1);
765 assert_eq!(zigzag_decode(3), -2);
766 assert_eq!(zigzag_decode(5), -3);
767 assert_eq!(zigzag_decode(99), -50);
768 }
769
770 #[test]
773 fn pb_reader_read_varint_single_byte() {
774 let data = [0x08]; let mut reader = PbReader::new(&data);
776 assert_eq!(reader.read_varint().unwrap(), 8);
777 }
778
779 #[test]
780 fn pb_reader_read_varint_multi_byte() {
781 let data = [0xAC, 0x02]; let mut reader = PbReader::new(&data);
783 assert_eq!(reader.read_varint().unwrap(), 300);
784 }
785
786 #[test]
787 fn pb_reader_read_tag() {
788 let data = [26];
790 let mut reader = PbReader::new(&data);
791 let (field, wire) = reader.read_tag().unwrap();
792 assert_eq!(field, 3);
793 assert_eq!(wire, WIRE_LEN);
794 }
795
796 #[test]
797 fn pb_reader_read_string() {
798 let data = [5, b'h', b'e', b'l', b'l', b'o'];
800 let mut reader = PbReader::new(&data);
801 assert_eq!(reader.read_string().unwrap(), "hello");
802 }
803
804 #[test]
805 fn pb_reader_truncated_payload() {
806 let data = [0xFF]; let mut reader = PbReader::new(&data);
808 assert!(reader.read_varint().is_err());
809 }
810
811 #[test]
814 fn tile_geo_bounds_zoom_0() {
815 let bounds = tile_geo_bounds(&TileId::new(0, 0, 0));
816 assert!((bounds.west - (-180.0)).abs() < 1e-6);
817 assert!((bounds.east - 180.0).abs() < 1e-6);
818 assert!(bounds.north > 85.0);
819 assert!(bounds.south < -85.0);
820 }
821
822 #[test]
823 fn tile_geo_bounds_higher_zoom() {
824 let bounds = tile_geo_bounds(&TileId::new(1, 0, 0));
825 assert!((bounds.west - (-180.0)).abs() < 1e-6);
826 assert!((bounds.east - 0.0).abs() < 1e-6);
827 assert!(bounds.north > 85.0);
828 assert!(bounds.south > -1.0); }
830
831 #[test]
834 fn signed_ring_area_ccw_positive() {
835 use rustial_math::GeoCoord;
836 let ring = vec![
838 GeoCoord::from_lat_lon(0.0, 0.0),
839 GeoCoord::from_lat_lon(0.0, 1.0),
840 GeoCoord::from_lat_lon(1.0, 1.0),
841 GeoCoord::from_lat_lon(1.0, 0.0),
842 GeoCoord::from_lat_lon(0.0, 0.0),
843 ];
844 let area = signed_ring_area(&ring);
845 assert!(area > 0.0, "CCW ring should have positive area, got {area}");
846 }
847
848 #[test]
849 fn signed_ring_area_cw_negative() {
850 use rustial_math::GeoCoord;
851 let ring = vec![
853 GeoCoord::from_lat_lon(0.0, 0.0),
854 GeoCoord::from_lat_lon(1.0, 0.0),
855 GeoCoord::from_lat_lon(1.0, 1.0),
856 GeoCoord::from_lat_lon(0.0, 1.0),
857 GeoCoord::from_lat_lon(0.0, 0.0),
858 ];
859 let area = signed_ring_area(&ring);
860 assert!(area < 0.0, "CW ring should have negative area, got {area}");
861 }
862
863 #[test]
866 fn tile_coord_to_geo_corners() {
867 let bounds = tile_geo_bounds(&TileId::new(0, 0, 0));
868 let nw = tile_coord_to_geo(0, 0, 4096, &bounds);
869 assert!((nw.lon - (-180.0)).abs() < 0.01);
870 assert!(nw.lat > 85.0);
871
872 let se = tile_coord_to_geo(4096, 4096, 4096, &bounds);
873 assert!((se.lon - 180.0).abs() < 0.01);
874 assert!(se.lat < -85.0);
875 }
876
877 fn encode_varint(mut val: u64) -> Vec<u8> {
880 let mut buf = Vec::new();
881 loop {
882 let mut byte = (val & 0x7F) as u8;
883 val >>= 7;
884 if val != 0 {
885 byte |= 0x80;
886 }
887 buf.push(byte);
888 if val == 0 {
889 break;
890 }
891 }
892 buf
893 }
894
895 fn encode_tag(field_number: u32, wire_type: u8) -> Vec<u8> {
896 encode_varint(((field_number as u64) << 3) | wire_type as u64)
897 }
898
899 fn encode_len_delimited(field_number: u32, data: &[u8]) -> Vec<u8> {
900 let mut buf = encode_tag(field_number, WIRE_LEN);
901 buf.extend(encode_varint(data.len() as u64));
902 buf.extend_from_slice(data);
903 buf
904 }
905
906 fn encode_string_field(field_number: u32, s: &str) -> Vec<u8> {
907 encode_len_delimited(field_number, s.as_bytes())
908 }
909
910 fn encode_varint_field(field_number: u32, val: u64) -> Vec<u8> {
911 let mut buf = encode_tag(field_number, WIRE_VARINT);
912 buf.extend(encode_varint(val));
913 buf
914 }
915
916 fn zigzag_encode(n: i32) -> u32 {
917 ((n << 1) ^ (n >> 31)) as u32
918 }
919
920 fn encode_geometry_commands(commands: &[(u32, &[(i32, i32)])]) -> Vec<u8> {
921 let mut buf = Vec::new();
922 for &(cmd_id, params) in commands {
923 let count = if cmd_id == CMD_CLOSE_PATH {
924 1u32
925 } else {
926 params.len() as u32
927 };
928 buf.extend(encode_varint(((count as u64) << 3) | cmd_id as u64));
929 for &(dx, dy) in params {
930 buf.extend(encode_varint(zigzag_encode(dx) as u64));
931 buf.extend(encode_varint(zigzag_encode(dy) as u64));
932 }
933 }
934 buf
935 }
936
937 fn build_mvt_value_string(s: &str) -> Vec<u8> {
938 encode_string_field(1, s)
939 }
940
941 fn build_mvt_value_double(v: f64) -> Vec<u8> {
942 let mut buf = encode_tag(3, WIRE_64);
943 buf.extend(v.to_le_bytes());
944 buf
945 }
946
947 fn build_mvt_feature(
948 id: Option<u64>,
949 geom_type: u32,
950 tags: &[u32],
951 geometry: &[u8],
952 ) -> Vec<u8> {
953 let mut buf = Vec::new();
954 if let Some(id) = id {
955 buf.extend(encode_varint_field(1, id));
956 }
957 if !tags.is_empty() {
958 let mut tags_buf = Vec::new();
959 for &t in tags {
960 tags_buf.extend(encode_varint(t as u64));
961 }
962 buf.extend(encode_len_delimited(2, &tags_buf));
963 }
964 buf.extend(encode_varint_field(3, geom_type as u64));
965 buf.extend(encode_len_delimited(4, geometry));
966 buf
967 }
968
969 fn build_mvt_layer(
970 name: &str,
971 features: &[Vec<u8>],
972 keys: &[&str],
973 values: &[Vec<u8>],
974 extent: u32,
975 ) -> Vec<u8> {
976 let mut buf = Vec::new();
977 buf.extend(encode_string_field(1, name));
978 for feat in features {
979 buf.extend(encode_len_delimited(2, feat));
980 }
981 for key in keys {
982 buf.extend(encode_string_field(3, key));
983 }
984 for val in values {
985 buf.extend(encode_len_delimited(4, val));
986 }
987 buf.extend(encode_varint_field(5, extent as u64));
988 buf.extend(encode_varint_field(15, 2)); buf
990 }
991
992 fn build_mvt_tile(layers: &[Vec<u8>]) -> Vec<u8> {
993 let mut buf = Vec::new();
994 for layer in layers {
995 buf.extend(encode_len_delimited(3, layer));
996 }
997 buf
998 }
999
1000 #[test]
1003 fn decode_empty_tile() {
1004 let bytes = build_mvt_tile(&[]);
1005 let result = decode_mvt(&bytes, &TileId::new(0, 0, 0), &MvtDecodeOptions::default());
1006 assert!(result.is_ok());
1007 assert!(result.unwrap().is_empty());
1008 }
1009
1010 #[test]
1011 fn decode_point_feature() {
1012 let geom = encode_geometry_commands(&[(CMD_MOVE_TO, &[(2048, 2048)])]);
1014 let feature = build_mvt_feature(Some(42), GEOM_POINT, &[], &geom);
1015 let layer = build_mvt_layer("points", &[feature], &[], &[], 4096);
1016 let tile = build_mvt_tile(&[layer]);
1017
1018 let tile_id = TileId::new(0, 0, 0);
1019 let result = decode_mvt(&tile, &tile_id, &MvtDecodeOptions::default()).unwrap();
1020
1021 assert_eq!(result.len(), 1);
1022 let features = &result["points"];
1023 assert_eq!(features.len(), 1);
1024
1025 match &features.features[0].geometry {
1026 Geometry::Point(pt) => {
1027 assert!(pt.coord.lon.abs() < 1.0);
1029 assert!(pt.coord.lat.abs() < 1.0);
1030 }
1031 other => panic!("expected Point, got {}", other.type_name()),
1032 }
1033
1034 let id_prop = features.features[0].property("$id");
1036 assert_eq!(id_prop.and_then(|v| v.as_f64()), Some(42.0));
1037 }
1038
1039 #[test]
1040 fn decode_linestring_feature() {
1041 let geom = encode_geometry_commands(&[
1042 (CMD_MOVE_TO, &[(0, 0)]),
1043 (CMD_LINE_TO, &[(4096, 0), (0, 4096)]),
1044 ]);
1045 let feature = build_mvt_feature(None, GEOM_LINESTRING, &[], &geom);
1046 let layer = build_mvt_layer("roads", &[feature], &[], &[], 4096);
1047 let tile = build_mvt_tile(&[layer]);
1048
1049 let tile_id = TileId::new(0, 0, 0);
1050 let result = decode_mvt(&tile, &tile_id, &MvtDecodeOptions::default()).unwrap();
1051
1052 let features = &result["roads"];
1053 assert_eq!(features.len(), 1);
1054 match &features.features[0].geometry {
1055 Geometry::LineString(ls) => {
1056 assert_eq!(ls.coords.len(), 3);
1057 }
1058 other => panic!("expected LineString, got {}", other.type_name()),
1059 }
1060 }
1061
1062 #[test]
1063 fn decode_polygon_feature() {
1064 let geom = encode_geometry_commands(&[
1066 (CMD_MOVE_TO, &[(0, 0)]),
1067 (CMD_LINE_TO, &[(4096, 0), (0, 4096), (-4096, 0)]),
1068 (CMD_CLOSE_PATH, &[]),
1069 ]);
1070 let feature = build_mvt_feature(None, GEOM_POLYGON, &[], &geom);
1071 let layer = build_mvt_layer("water", &[feature], &[], &[], 4096);
1072 let tile = build_mvt_tile(&[layer]);
1073
1074 let tile_id = TileId::new(0, 0, 0);
1075 let result = decode_mvt(&tile, &tile_id, &MvtDecodeOptions::default()).unwrap();
1076
1077 let features = &result["water"];
1078 assert_eq!(features.len(), 1);
1079 match &features.features[0].geometry {
1080 Geometry::Polygon(poly) => {
1081 assert!(poly.exterior.len() >= 4, "polygon should have 4+ vertices");
1082 assert!(poly.interiors.is_empty());
1083 }
1084 other => panic!("expected Polygon, got {}", other.type_name()),
1085 }
1086 }
1087
1088 #[test]
1089 fn decode_feature_with_properties() {
1090 let geom = encode_geometry_commands(&[(CMD_MOVE_TO, &[(100, 100)])]);
1091 let keys = &["name", "population"];
1092 let values = &[
1093 build_mvt_value_string("Springfield"),
1094 build_mvt_value_double(12345.0),
1095 ];
1096 let feature = build_mvt_feature(None, GEOM_POINT, &[0, 0, 1, 1], &geom);
1098 let layer = build_mvt_layer("places", &[feature], keys, values, 4096);
1099 let tile = build_mvt_tile(&[layer]);
1100
1101 let tile_id = TileId::new(1, 0, 0);
1102 let result = decode_mvt(&tile, &tile_id, &MvtDecodeOptions::default()).unwrap();
1103
1104 let features = &result["places"];
1105 assert_eq!(features.len(), 1);
1106 let props = &features.features[0].properties;
1107 assert_eq!(
1108 props.get("name").and_then(|v| v.as_str()),
1109 Some("Springfield")
1110 );
1111 assert_eq!(
1112 props.get("population").and_then(|v| v.as_f64()),
1113 Some(12345.0)
1114 );
1115 }
1116
1117 #[test]
1118 fn decode_with_layer_filter() {
1119 let geom = encode_geometry_commands(&[(CMD_MOVE_TO, &[(100, 100)])]);
1120 let feat = build_mvt_feature(None, GEOM_POINT, &[], &geom);
1121 let layer_a = build_mvt_layer("water", std::slice::from_ref(&feat), &[], &[], 4096);
1122 let layer_b = build_mvt_layer("roads", &[feat], &[], &[], 4096);
1123 let tile = build_mvt_tile(&[layer_a, layer_b]);
1124
1125 let options = MvtDecodeOptions {
1126 layer_filter: vec!["water".into()],
1127 };
1128 let tile_id = TileId::new(0, 0, 0);
1129 let result = decode_mvt(&tile, &tile_id, &options).unwrap();
1130
1131 assert_eq!(result.len(), 1);
1132 assert!(result.contains_key("water"));
1133 assert!(!result.contains_key("roads"));
1134 }
1135
1136 #[test]
1137 fn decode_multi_point_feature() {
1138 let geom = encode_geometry_commands(&[(CMD_MOVE_TO, &[(100, 100), (200, 200)])]);
1139 let feature = build_mvt_feature(None, GEOM_POINT, &[], &geom);
1140 let layer = build_mvt_layer("multi", &[feature], &[], &[], 4096);
1141 let tile = build_mvt_tile(&[layer]);
1142
1143 let tile_id = TileId::new(0, 0, 0);
1144 let result = decode_mvt(&tile, &tile_id, &MvtDecodeOptions::default()).unwrap();
1145 match &result["multi"].features[0].geometry {
1146 Geometry::MultiPoint(mp) => {
1147 assert_eq!(mp.points.len(), 2);
1148 }
1149 other => panic!("expected MultiPoint, got {}", other.type_name()),
1150 }
1151 }
1152
1153 #[test]
1154 fn mvt_error_display() {
1155 assert!(MvtError::TruncatedPayload.to_string().contains("truncated"));
1156 assert!(MvtError::UnsupportedWireType(6).to_string().contains("6"));
1157 assert!(MvtError::InvalidGeometryCommand(99)
1158 .to_string()
1159 .contains("99"));
1160 }
1161}