1mod store;
2
3#[expect(
4 clippy::unnecessary_box_returns,
5 reason = "Diplomat requires `Box<T>` returns for opaque constructors"
6)]
7#[expect(
8 clippy::use_self,
9 reason = "Diplomat expands macros using the concrete type name rather than `Self`."
10)]
11#[diplomat::bridge]
12mod ffi {
13 use std::fmt::Write as _;
14
15 use mlt_core::encoder::{EncoderConfig, Network, Profile, Tessellation, WireVersion};
16 use mlt_core::mvt::{mvt_to_tile_layers, tile_layers_to_mvt};
17 use mlt_core::wire::LayerBytes;
18 use mlt_core::{Decoder, Parser};
19
20 use crate::store::{LayerBuffer, MvtGeometryType, StoreError, StoredValue};
21
22 #[derive(Debug, PartialEq, Eq)]
24 pub enum ConvertErrorKind {
25 InvalidInput,
27 EncodingFailed,
29 InvalidFeature,
31 }
32
33 #[diplomat::opaque]
35 #[diplomat::attr(auto, error)]
36 pub struct ConvertError {
37 kind: ConvertErrorKind,
38 message: String,
39 }
40
41 impl ConvertError {
42 #[diplomat::attr(auto, getter = "kind")]
44 pub fn kind(&self) -> ConvertErrorKind {
45 self.kind
46 }
47
48 #[diplomat::attr(auto, getter = "message")]
50 pub fn message(&self, write: &mut DiplomatWrite) {
51 let _ = write.write_str(&self.message);
52 }
53
54 #[cfg(test)]
55 pub(crate) fn message_text(&self) -> &str {
56 &self.message
57 }
58 }
59
60 impl ConvertError {
61 fn new(kind: ConvertErrorKind, source: &impl std::fmt::Display) -> Box<Self> {
62 Box::new(Self {
63 kind,
64 message: source.to_string(),
65 })
66 }
67 }
68
69 fn feature_error(source: &StoreError) -> Box<ConvertError> {
70 ConvertError::new(ConvertErrorKind::InvalidFeature, source)
71 }
72
73 fn invalid_input(source: impl std::fmt::Display) -> Box<ConvertError> {
74 ConvertError::new(ConvertErrorKind::InvalidInput, &source)
75 }
76
77 fn encoding_failed(source: impl std::fmt::Display) -> Box<ConvertError> {
78 ConvertError::new(ConvertErrorKind::EncodingFailed, &source)
79 }
80
81 fn set_value(
82 buffer: &mut LayerBuffer,
83 key: u32,
84 value: StoredValue,
85 ) -> Result<(), Box<ConvertError>> {
86 buffer.set(key, value).map_err(|e| feature_error(&e))
87 }
88
89 #[diplomat::opaque_mut]
94 pub struct MltBuffer(Vec<u8>);
95
96 impl MltBuffer {
97 #[diplomat::attr(auto, constructor)]
99 pub fn new() -> Box<MltBuffer> {
100 Box::new(MltBuffer(Vec::new()))
101 }
102
103 pub fn clear(&mut self) {
105 self.0.clear();
106 }
107
108 #[diplomat::attr(auto, getter = "bytes")]
110 #[expect(
111 clippy::needless_lifetimes,
112 reason = "diplomat requires explicit lifetimes"
113 )]
114 pub fn as_bytes<'a>(&'a self) -> &'a [u8] {
115 &self.0
116 }
117
118 #[diplomat::attr(auto, getter = "len")]
120 pub fn len(&self) -> usize {
121 self.0.len()
122 }
123 }
124
125 #[expect(
127 dead_code,
128 reason = "Diplomat constructs the variants on the foreign side"
129 )]
130 pub enum MltWireVersion {
131 V01,
133 V02,
135 }
136
137 #[diplomat::opaque_mut]
144 pub struct MltEncoderOptions(EncoderConfig);
145
146 impl MltEncoderOptions {
147 #[diplomat::attr(auto, constructor)]
149 pub fn new() -> Box<MltEncoderOptions> {
150 Box::new(MltEncoderOptions(
151 EncoderConfig::from_profile(Profile::Native(Network::Cellular4G))
152 .with_tessellation(Tessellation::Off),
153 ))
154 }
155
156 pub fn set_tessellate(&mut self, enabled: bool) {
158 self.0 = self.0.with_tessellation(if enabled {
159 Tessellation::Always
160 } else {
161 Tessellation::Off
162 });
163 }
164
165 pub fn set_attempt_spatial_morton_sort(&mut self, enabled: bool) {
167 self.0 = self.0.with_spatial_morton_sort(enabled);
168 }
169
170 pub fn set_attempt_spatial_hilbert_sort(&mut self, enabled: bool) {
172 self.0 = self.0.with_spatial_hilbert_sort(enabled);
173 }
174
175 pub fn set_attempt_id_sort(&mut self, enabled: bool) {
177 self.0 = self.0.with_id_sort(enabled);
178 }
179
180 pub fn set_allow_fsst(&mut self, enabled: bool) {
182 self.0 = self.0.with_fsst(enabled);
183 }
184
185 pub fn set_allow_fastpfor(&mut self, enabled: bool) {
187 self.0 = self.0.with_fastpfor(enabled);
188 }
189
190 pub fn set_allow_shared_dict(&mut self, enabled: bool) {
192 self.0 = self.0.with_shared_dict(enabled);
193 }
194
195 pub fn set_wire_version(&mut self, version: MltWireVersion) {
198 let version = match version {
199 MltWireVersion::V01 => WireVersion::V01,
200 MltWireVersion::V02 => WireVersion::V02,
201 };
202 self.0 = self.0.with_wire_version(version);
203 }
204
205 pub fn set_allow_triangles_only(&mut self, enabled: bool) {
209 self.0 = self.0.with_triangles_only(enabled);
210 }
211
212 pub fn set_allow_delta2(&mut self, enabled: bool) {
214 self.0 = self.0.with_delta2(enabled);
215 }
216
217 pub fn set_allow_float_dict(&mut self, enabled: bool) {
219 self.0 = self.0.with_float_dict(enabled);
220 }
221
222 pub fn set_allow_float_alp(&mut self, enabled: bool) {
224 self.0 = self.0.with_float_alp(enabled);
225 }
226
227 pub fn set_allow_packed_dict_codes(&mut self, enabled: bool) {
229 self.0 = self.0.with_packed_dict_codes(enabled);
230 }
231
232 pub fn set_allow_rans_vertices(&mut self, enabled: bool) {
234 self.0 = self.0.with_rans_vertices(enabled);
235 }
236
237 pub fn set_allow_row_shapes(&mut self, enabled: bool) {
239 self.0 = self.0.with_row_shapes(enabled);
240 }
241 }
242
243 impl From<MltMvtGeometryType> for MvtGeometryType {
244 fn from(geometry: MltMvtGeometryType) -> Self {
245 match geometry {
246 MltMvtGeometryType::Point => Self::Point,
247 MltMvtGeometryType::LineString => Self::LineString,
248 MltMvtGeometryType::Polygon => Self::Polygon,
249 }
250 }
251 }
252
253 #[cfg_attr(
255 not(test),
256 expect(
257 dead_code,
258 reason = "Diplomat constructs the variants on the foreign side"
259 )
260 )]
261 pub enum MltMvtGeometryType {
262 Point,
263 LineString,
264 Polygon,
265 }
266
267 #[diplomat::opaque_mut]
269 pub struct MltLayerBuilder(LayerBuffer);
270
271 impl MltLayerBuilder {
272 pub fn new(name: &str, extent: u32) -> Result<Box<MltLayerBuilder>, Box<ConvertError>> {
274 LayerBuffer::new(name, extent)
275 .map(|buffer| Box::new(MltLayerBuilder(buffer)))
276 .map_err(|e| ConvertError::new(ConvertErrorKind::InvalidFeature, &e))
277 }
278
279 pub fn reset(&mut self, name: &str, extent: u32) -> Result<(), Box<ConvertError>> {
281 self.0
282 .reset(name, extent)
283 .map_err(|e| ConvertError::new(ConvertErrorKind::InvalidFeature, &e))
284 }
285
286 pub fn add_property(&mut self, name: &str) -> u32 {
289 self.0.add_property(name)
290 }
291
292 pub fn begin_mvt_feature(
295 &mut self,
296 geometry: MltMvtGeometryType,
297 commands: &[u32],
298 id: Option<u64>,
299 ) -> Result<(), Box<ConvertError>> {
300 self.0
301 .begin_mvt_feature(geometry.into(), commands, id)
302 .map_err(|e| feature_error(&e))
303 }
304
305 pub fn set_bool(&mut self, key: u32, value: bool) -> Result<(), Box<ConvertError>> {
307 set_value(&mut self.0, key, StoredValue::Bool(value))
308 }
309
310 pub fn set_i64(&mut self, key: u32, value: i64) -> Result<(), Box<ConvertError>> {
312 set_value(&mut self.0, key, StoredValue::I64(value))
313 }
314
315 pub fn set_f32(&mut self, key: u32, value: f32) -> Result<(), Box<ConvertError>> {
317 set_value(&mut self.0, key, StoredValue::F32(value))
318 }
319
320 pub fn set_f64(&mut self, key: u32, value: f64) -> Result<(), Box<ConvertError>> {
322 set_value(&mut self.0, key, StoredValue::F64(value))
323 }
324
325 pub fn set_str(&mut self, key: u32, value: &str) -> Result<(), Box<ConvertError>> {
327 self.0.set_str(key, value).map_err(|e| feature_error(&e))
328 }
329
330 pub fn encode_into(
332 &self,
333 options: &MltEncoderOptions,
334 out: &mut MltBuffer,
335 ) -> Result<(), Box<ConvertError>> {
336 let layer = self.0.encode(options.0).map_err(encoding_failed)?;
337 out.0.extend_from_slice(&layer);
338 Ok(())
339 }
340 }
341
342 #[derive(Debug, PartialEq, Eq)]
344 pub struct MltLayerBytes {
345 pub size: u32,
347 pub geometry: u32,
349 pub properties: u32,
351 pub ids: u32,
353 pub metadata: u32,
355 }
356
357 #[diplomat::opaque]
359 pub struct MltTileStats(Vec<(String, LayerBytes)>);
360
361 impl MltTileStats {
362 pub fn from_bytes(mlt: &[u8]) -> Result<Box<MltTileStats>, Box<ConvertError>> {
364 let layers = Parser::default().parse_layers(mlt).map_err(invalid_input)?;
365 let stats = layers
366 .iter()
367 .filter_map(|layer| Some((layer.name()?.to_owned(), layer.bytes()?)))
368 .collect();
369 Ok(Box::new(MltTileStats(stats)))
370 }
371
372 pub fn layer_count(&self) -> usize {
374 self.0.len()
375 }
376
377 pub fn layer_name(&self, i: usize, out: &mut DiplomatWrite) {
379 if let Some((name, _)) = self.0.get(i) {
380 let _ = out.write_str(name);
381 }
382 }
383
384 #[cfg(test)]
385 pub(crate) fn layer_name_text(&self, i: usize) -> &str {
386 &self.0[i].0
387 }
388
389 pub fn layer_bytes(&self, i: usize) -> Option<MltLayerBytes> {
391 let (_, bytes) = self.0.get(i)?;
392 Some(MltLayerBytes {
393 size: bytes.size(),
394 geometry: bytes.geometry(),
395 properties: bytes.properties(),
396 ids: bytes.ids(),
397 metadata: bytes.metadata(),
398 })
399 }
400 }
401
402 fn decode_to_mvt(
403 mlt: &[u8],
404 mut parser: Parser,
405 mut dec: Decoder,
406 ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
407 let layers = parser.parse_layers(mlt).map_err(invalid_input)?;
408 let mut tiles = Vec::new();
409 for layer in layers {
410 let tile = layer.into_tile(&mut dec).map_err(invalid_input)?;
411 tiles.extend(tile);
412 }
413 let out = tile_layers_to_mvt(tiles).map_err(invalid_input)?;
414 Ok(Box::new(MltBuffer(out)))
415 }
416
417 #[diplomat::opaque]
419 pub struct MltConverter;
420
421 impl MltConverter {
422 pub fn mlt_to_mvt(mlt: &[u8]) -> Result<Box<MltBuffer>, Box<ConvertError>> {
424 decode_to_mvt(mlt, Parser::default(), Decoder::default())
425 }
426
427 pub fn mlt_to_mvt_with_limit(
429 mlt: &[u8],
430 max_bytes: u32,
431 ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
432 decode_to_mvt(
433 mlt,
434 Parser::with_max_size(max_bytes),
435 Decoder::with_max_size(max_bytes),
436 )
437 }
438
439 pub fn mvt_to_mlt(
441 mvt: &[u8],
442 options: &MltEncoderOptions,
443 ) -> Result<Box<MltBuffer>, Box<ConvertError>> {
444 let mut out = Vec::new();
445 let layers = mvt_to_tile_layers(mvt).map_err(encoding_failed)?;
446 for tile in layers {
447 let encoded_tile = tile.encode(options.0).map_err(encoding_failed)?;
448 out.extend_from_slice(&encoded_tile);
449 }
450 Ok(Box::new(MltBuffer(out)))
451 }
452 }
453}
454
455#[cfg(test)]
456mod tests {
457 use insta::assert_debug_snapshot;
458 use mlt_core::mvt::mvt_to_tile_layers;
459
460 use super::ffi::{
461 ConvertErrorKind, MltBuffer, MltConverter, MltEncoderOptions, MltLayerBuilder,
462 MltLayerBytes, MltMvtGeometryType, MltTileStats, MltWireVersion,
463 };
464
465 const POINT: &[u8] = include_bytes!("../../../test/fixtures/simple/point-boolean.mvt");
466 const POLYGON: &[u8] = include_bytes!("../../../test/fixtures/simple/polygon-boolean.mvt");
467 const MULTIPOLYGON: &[u8] =
468 include_bytes!("../../../test/fixtures/simple/multipolygon-boolean.mvt");
469
470 #[test]
471 fn v1_encoding_of_a_point_layer_decodes_to_the_same_layers() {
472 let options = MltEncoderOptions::new();
473
474 let mlt = MltConverter::mvt_to_mlt(POINT, &options).ok().unwrap();
475 let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
476
477 assert_eq!(
478 mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
479 mvt_to_tile_layers(POINT).unwrap()
480 );
481 }
482
483 #[test]
484 fn v1_encoding_of_a_multipolygon_layer_decodes_to_the_same_layers() {
485 let options = MltEncoderOptions::new();
486
487 let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
488 .ok()
489 .unwrap();
490 let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
491
492 assert_eq!(
493 mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
494 mvt_to_tile_layers(MULTIPOLYGON).unwrap()
495 );
496 }
497
498 #[test]
499 fn v2_encoding_of_a_multipolygon_layer_decodes_to_the_same_layers() {
500 let mut options = MltEncoderOptions::new();
501 options.set_wire_version(MltWireVersion::V02);
502
503 let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
504 .ok()
505 .unwrap();
506 let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
507
508 assert_eq!(
509 mvt_to_tile_layers(decoded.as_bytes()).unwrap(),
510 mvt_to_tile_layers(MULTIPOLYGON).unwrap()
511 );
512 }
513
514 #[test]
515 fn v2_tessellated_encoding_without_outlines_decodes_a_polygon_as_triangles() {
516 let mut options = MltEncoderOptions::new();
517 options.set_wire_version(MltWireVersion::V02);
518 options.set_tessellate(true);
519 options.set_allow_triangles_only(true);
520
521 let mlt = MltConverter::mvt_to_mlt(POLYGON, &options).ok().unwrap();
522 let decoded = MltConverter::mlt_to_mvt(mlt.as_bytes()).ok().unwrap();
523
524 assert_debug_snapshot!(mvt_to_tile_layers(decoded.as_bytes()).unwrap(), @r#"
525 [
526 TileLayer {
527 name: "layer",
528 extent: Extent(
529 4096,
530 ),
531 property_names: [
532 "key",
533 ],
534 property_kinds: [
535 Bool,
536 ],
537 m_value_names: [],
538 m_value_kinds: [],
539 nested_names: [],
540 nested_kinds: [],
541 z_step: None,
542 features: [
543 TileFeature {
544 id: Some(
545 1,
546 ),
547 geometry: POLYGON((3 6,8 12,20 34,3 6)),
548 properties: [
549 Bool(
550 Some(
551 true,
552 ),
553 ),
554 ],
555 m_values: [],
556 nested: [],
557 z: [],
558 },
559 ],
560 },
561 ]
562 "#);
563 }
564
565 #[test]
566 fn builder_point_layer_with_every_property_kind_round_trips() {
567 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
568 let flag = builder.add_property("flag");
569 let count = builder.add_property("count");
570 let ratio = builder.add_property("ratio");
571 let precise = builder.add_property("precise");
572 let name = builder.add_property("name");
573 add_point(&mut builder, [8, 12], Some(7));
574 builder.set_bool(flag, true).ok().unwrap();
575 builder.set_i64(count, -3).ok().unwrap();
576 builder.set_f32(ratio, 1.5).ok().unwrap();
577 builder.set_f64(precise, 2.25).ok().unwrap();
578 builder.set_str(name, "alpha").ok().unwrap();
579
580 let mut out = MltBuffer::new();
581 builder
582 .encode_into(&MltEncoderOptions::new(), &mut out)
583 .ok()
584 .unwrap();
585
586 let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
587 let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
588 assert_eq!(layers.len(), 1);
589
590 assert_debug_snapshot!(layers[0].features(), @r#"
591 [
592 TileFeature {
593 id: Some(
594 7,
595 ),
596 geometry: POINT(8 12),
597 properties: [
598 Bool(
599 Some(
600 true,
601 ),
602 ),
603 I64(
604 Some(
605 -3,
606 ),
607 ),
608 F32(
609 Some(
610 1.5,
611 ),
612 ),
613 F64(
614 Some(
615 2.25,
616 ),
617 ),
618 Str(
619 Some(
620 "alpha",
621 ),
622 ),
623 ],
624 m_values: [],
625 nested: [],
626 z: [],
627 },
628 ]
629 "#);
630 }
631
632 #[test]
633 fn builder_reset_rejects_an_empty_layer_name() {
634 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
635
636 let error = builder.reset("", 4096).err().unwrap();
637
638 assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
639 }
640
641 #[test]
642 fn builder_reset_rejects_a_zero_extent() {
643 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
644
645 let error = builder.reset("layer", 0).err().unwrap();
646
647 assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
648 }
649
650 #[test]
651 fn builder_layers_appended_to_one_buffer_decode_as_one_tile() {
652 let mut out = MltBuffer::new();
653 let options = MltEncoderOptions::new();
654 let mut builder = MltLayerBuilder::new("first", 4096).ok().unwrap();
655 add_point(&mut builder, [1, 1], None);
656 builder.encode_into(&options, &mut out).ok().unwrap();
657 builder.reset("second", 4096).ok().unwrap();
658 add_point(&mut builder, [2, 2], None);
659 builder.encode_into(&options, &mut out).ok().unwrap();
660
661 let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
662 let names: Vec<_> = mvt_to_tile_layers(mvt.as_bytes())
663 .unwrap()
664 .into_iter()
665 .map(|layer| layer.name().to_owned())
666 .collect();
667
668 assert_eq!(names, ["first", "second"]);
669 }
670
671 fn encode_fifty_points(options: &MltEncoderOptions) -> Vec<u8> {
672 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
673 let key = builder.add_property("key");
674 for i in 0..50_i32 {
675 add_point(&mut builder, [i, 2 * i], Some(u64::from(i.unsigned_abs())));
676 builder.set_i64(key, i64::from(i)).ok().unwrap();
677 }
678 let mut out = MltBuffer::new();
679 builder.encode_into(options, &mut out).ok().unwrap();
680 out.as_bytes().to_vec()
681 }
682
683 #[test]
684 fn encoding_on_two_threads_with_separate_options_matches_encoding_on_one() {
685 let v1_options = MltEncoderOptions::new();
686 let mut v2_options = MltEncoderOptions::new();
687 v2_options.set_wire_version(MltWireVersion::V02);
688 let expected = [
689 encode_fifty_points(&v1_options),
690 encode_fifty_points(&v2_options),
691 ];
692
693 let actual = std::thread::scope(|scope| {
694 let v1 = scope.spawn(|| encode_fifty_points(&v1_options));
695 let v2 = scope.spawn(|| encode_fifty_points(&v2_options));
696 [v1.join().unwrap(), v2.join().unwrap()]
697 });
698
699 assert_eq!(actual, expected);
700 }
701
702 #[test]
703 fn clearing_a_buffer_empties_it() {
704 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
705 add_point(&mut builder, [1, 1], None);
706 let mut out = MltBuffer::new();
707 builder
708 .encode_into(&MltEncoderOptions::new(), &mut out)
709 .ok()
710 .unwrap();
711
712 out.clear();
713
714 assert_eq!(out.len(), 0);
715 }
716
717 #[test]
718 fn setting_a_property_before_a_feature_begins_is_invalid() {
719 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
720
721 let key = builder.add_property("key");
722
723 let error = builder.set_i64(key, 1).err().unwrap();
724
725 assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
726 assert_debug_snapshot!(error.message_text(), @r#""no feature begun""#);
727 }
728
729 #[test]
730 fn setting_an_undeclared_property_key_is_invalid() {
731 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
732 add_point(&mut builder, [1, 1], None);
733
734 let error = builder.set_i64(3, 1).err().unwrap();
735
736 assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
737 assert_debug_snapshot!(error.message_text(), @r#""unknown property key 3""#);
738 }
739
740 #[test]
741 fn integer_and_float_values_of_one_key_widen_to_a_float_column() {
742 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
743 let key = builder.add_property("key");
744 add_point(&mut builder, [1, 1], None);
745 builder.set_i64(key, 3).ok().unwrap();
746 add_point(&mut builder, [2, 2], None);
747 builder.set_f32(key, 1.5).ok().unwrap();
748
749 let mut out = MltBuffer::new();
750 builder
751 .encode_into(&MltEncoderOptions::new(), &mut out)
752 .ok()
753 .unwrap();
754
755 let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
756 let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
757 assert_eq!(layers.len(), 1);
758
759 assert_debug_snapshot!(layers[0].features(), @"
760 [
761 TileFeature {
762 id: None,
763 geometry: POINT(1 1),
764 properties: [
765 F64(
766 Some(
767 3.0,
768 ),
769 ),
770 ],
771 m_values: [],
772 nested: [],
773 z: [],
774 },
775 TileFeature {
776 id: None,
777 geometry: POINT(2 2),
778 properties: [
779 F64(
780 Some(
781 1.5,
782 ),
783 ),
784 ],
785 m_values: [],
786 nested: [],
787 z: [],
788 },
789 ]
790 ");
791 }
792
793 #[test]
794 fn boolean_and_integer_values_of_one_key_become_text() {
795 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
796 let key = builder.add_property("key");
797 add_point(&mut builder, [1, 1], None);
798 builder.set_bool(key, true).ok().unwrap();
799 add_point(&mut builder, [2, 2], None);
800 builder.set_i64(key, 5).ok().unwrap();
801
802 let mut out = MltBuffer::new();
803 builder
804 .encode_into(&MltEncoderOptions::new(), &mut out)
805 .ok()
806 .unwrap();
807
808 let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
809 let layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
810 assert_eq!(layers.len(), 1);
811
812 assert_debug_snapshot!(layers[0].features(), @r#"
813 [
814 TileFeature {
815 id: None,
816 geometry: POINT(1 1),
817 properties: [
818 Str(
819 Some(
820 "true",
821 ),
822 ),
823 ],
824 m_values: [],
825 nested: [],
826 z: [],
827 },
828 TileFeature {
829 id: None,
830 geometry: POINT(2 2),
831 properties: [
832 Str(
833 Some(
834 "5",
835 ),
836 ),
837 ],
838 m_values: [],
839 nested: [],
840 z: [],
841 },
842 ]
843 "#);
844 }
845
846 fn add_point(builder: &mut MltLayerBuilder, [x, y]: [i32; 2], id: Option<u64>) {
847 builder
848 .begin_mvt_feature(MltMvtGeometryType::Point, &[9, zigzag(x), zigzag(y)], id)
849 .ok()
850 .unwrap();
851 }
852
853 fn zigzag(value: i32) -> u32 {
854 ((value << 1) ^ (value >> 31)).cast_unsigned()
855 }
856
857 fn mvt_commands(parts: &[&[i32]], close: bool) -> Vec<u32> {
858 let mut commands = Vec::new();
859 let (mut x, mut y) = (0, 0);
860 for part in parts {
861 for (i, xy) in part.chunks(2).enumerate() {
862 match i {
863 0 => commands.push(9),
864 1 => commands.push(2 | (u32::try_from(part.len() / 2 - 1).unwrap() << 3)),
865 _ => {}
866 }
867 commands.extend([zigzag(xy[0] - x), zigzag(xy[1] - y)]);
868 (x, y) = (xy[0], xy[1]);
869 }
870 if close {
871 commands.push(15);
872 }
873 }
874 commands
875 }
876
877 fn decoded_features(builder: &MltLayerBuilder) -> Vec<mlt_core::TileFeature> {
878 decoded_features_with(builder, &MltEncoderOptions::new())
879 }
880
881 fn decoded_features_with(
882 builder: &MltLayerBuilder,
883 options: &MltEncoderOptions,
884 ) -> Vec<mlt_core::TileFeature> {
885 let mut out = MltBuffer::new();
886 builder.encode_into(options, &mut out).ok().unwrap();
887 let mvt = MltConverter::mlt_to_mvt(out.as_bytes()).ok().unwrap();
888 let mut layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
889 assert_eq!(layers.len(), 1);
890 layers.remove(0).features().to_vec()
891 }
892
893 #[test]
894 fn mvt_point_feature_matches_mvt_conversion_of_the_same_layer() {
895 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
896 let key = builder.add_property("key");
897 builder
898 .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 50, 34], Some(1))
899 .ok()
900 .unwrap();
901 builder.set_bool(key, true).ok().unwrap();
902
903 let from_mvt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
904 .ok()
905 .unwrap();
906 let mvt = MltConverter::mlt_to_mvt(from_mvt.as_bytes()).ok().unwrap();
907 let mvt_layers = mvt_to_tile_layers(mvt.as_bytes()).unwrap();
908 assert_eq!(decoded_features(&builder), mvt_layers[0].features());
909 }
910
911 #[test]
912 fn v2_mvt_multipolygon_with_a_hole_splits_rings_by_winding() {
913 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
914 let commands = mvt_commands(
915 &[
916 &[0, 0, 100, 0, 100, 100, 0, 100],
917 &[20, 20, 20, 40, 40, 40, 40, 20],
918 &[200, 200, 300, 200, 300, 300, 200, 300],
919 ],
920 true,
921 );
922 builder
923 .begin_mvt_feature(MltMvtGeometryType::Polygon, &commands, None)
924 .ok()
925 .unwrap();
926 let mut options = MltEncoderOptions::new();
927 options.set_wire_version(MltWireVersion::V02);
928
929 assert_debug_snapshot!(decoded_features_with(&builder, &options), @"
930 [
931 TileFeature {
932 id: None,
933 geometry: MULTIPOLYGON(((0 0,100 0,100 100,0 100,0 0),(20 20,20 40,40 40,40 20,20 20)),((200 200,300 200,300 300,200 300,200 200))),
934 properties: [],
935 m_values: [],
936 nested: [],
937 z: [],
938 },
939 ]
940 ");
941 }
942
943 #[test]
944 fn mvt_features_take_the_single_or_multi_type_from_their_part_count() {
945 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
946 let mvt = MltMvtGeometryType::LineString;
947 builder
948 .begin_mvt_feature(mvt, &mvt_commands(&[&[0, 0, 10, 10, 20, 5]], false), None)
949 .ok()
950 .unwrap();
951 let lines = mvt_commands(&[&[1, 1, 2, 2], &[5, 5, 6, 7]], false);
952 builder.begin_mvt_feature(mvt, &lines, None).ok().unwrap();
953 builder
954 .begin_mvt_feature(
955 MltMvtGeometryType::Polygon,
956 &mvt_commands(&[&[0, 0, 9, 0, 9, 9]], true),
957 None,
958 )
959 .ok()
960 .unwrap();
961
962 assert_debug_snapshot!(decoded_features(&builder), @"
963 [
964 TileFeature {
965 id: None,
966 geometry: LINESTRING(0 0,10 10,20 5),
967 properties: [],
968 m_values: [],
969 nested: [],
970 z: [],
971 },
972 TileFeature {
973 id: None,
974 geometry: MULTILINESTRING((1 1,2 2),(5 5,6 7)),
975 properties: [],
976 m_values: [],
977 nested: [],
978 z: [],
979 },
980 TileFeature {
981 id: None,
982 geometry: POLYGON((0 0,9 0,9 9,0 0)),
983 properties: [],
984 m_values: [],
985 nested: [],
986 z: [],
987 },
988 ]
989 ");
990 }
991
992 #[test]
993 fn mvt_points_of_one_move_to_become_a_multipoint() {
994 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
995 builder
996 .begin_mvt_feature(MltMvtGeometryType::Point, &[25, 2, 4, 4, 4, 4, 4], None)
997 .ok()
998 .unwrap();
999
1000 assert_debug_snapshot!(decoded_features(&builder), @"
1001 [
1002 TileFeature {
1003 id: None,
1004 geometry: MULTIPOINT(1 2,3 4,5 6),
1005 properties: [],
1006 m_values: [],
1007 nested: [],
1008 z: [],
1009 },
1010 ]
1011 ");
1012 }
1013
1014 #[test]
1015 fn truncated_mvt_commands_are_invalid_and_leave_no_feature_behind() {
1016 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1017 builder
1018 .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 2, 2], None)
1019 .ok()
1020 .unwrap();
1021
1022 let error = builder
1023 .begin_mvt_feature(MltMvtGeometryType::LineString, &[9, 2, 2, 18, 4], None)
1024 .err()
1025 .unwrap();
1026
1027 assert_eq!(error.kind(), ConvertErrorKind::InvalidFeature);
1028 assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: ends inside a command""#);
1029 assert_eq!(decoded_features(&builder).len(), 1);
1030 }
1031
1032 #[test]
1033 fn line_to_in_a_point_geometry_is_invalid() {
1034 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1035
1036 let error = builder
1037 .begin_mvt_feature(MltMvtGeometryType::Point, &[9, 2, 2, 10, 4, 4], None)
1038 .err()
1039 .unwrap();
1040
1041 assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: a LineTo outside a line or ring""#);
1042 }
1043
1044 #[test]
1045 fn empty_mvt_commands_are_invalid() {
1046 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1047
1048 let error = builder
1049 .begin_mvt_feature(MltMvtGeometryType::Polygon, &[], None)
1050 .err()
1051 .unwrap();
1052
1053 assert_debug_snapshot!(error.message_text(), @r#""invalid MVT geometry: no rings""#);
1054 }
1055
1056 #[test]
1057 fn declaring_a_property_name_again_returns_its_key() {
1058 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1059 let first = builder.add_property("name");
1060 builder.add_property("other");
1061
1062 assert_eq!(builder.add_property("name"), first);
1063 }
1064
1065 #[test]
1066 fn reset_forgets_the_declared_property_names() {
1067 let mut builder = MltLayerBuilder::new("layer", 4096).ok().unwrap();
1068 builder.add_property("first");
1069 builder.add_property("second");
1070 builder.reset("layer", 4096).ok().unwrap();
1071
1072 assert_eq!(builder.add_property("second"), 0);
1073 }
1074
1075 #[test]
1076 fn decoding_with_a_tiny_memory_limit_reports_the_budget() {
1077 let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1078 .ok()
1079 .unwrap();
1080
1081 let error = MltConverter::mlt_to_mvt_with_limit(mlt.as_bytes(), 1)
1082 .err()
1083 .unwrap();
1084
1085 assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1086 assert_debug_snapshot!(error.message_text(), @r#""memory limit exceeded: limit=1, used=0, requested=8""#);
1087 }
1088
1089 #[test]
1090 fn decoding_with_a_generous_memory_limit_matches_the_default() {
1091 let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1092 .ok()
1093 .unwrap();
1094
1095 let limited = MltConverter::mlt_to_mvt_with_limit(mlt.as_bytes(), 1 << 30)
1096 .ok()
1097 .unwrap();
1098
1099 assert_eq!(
1100 limited.as_bytes(),
1101 MltConverter::mlt_to_mvt(mlt.as_bytes())
1102 .ok()
1103 .unwrap()
1104 .as_bytes()
1105 );
1106 }
1107
1108 #[test]
1109 fn decoding_garbage_as_mlt_reports_invalid_input_with_the_parser_message() {
1110 let error = MltConverter::mlt_to_mvt(&[0xff]).err().unwrap();
1111
1112 assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1113 assert_debug_snapshot!(error.message_text(), @r#""buffer underflow: needed 2 bytes, but only 1 remain""#);
1114 }
1115
1116 #[test]
1117 fn encoding_garbage_as_mvt_reports_encoding_failed_with_the_parser_message() {
1118 let error = MltConverter::mvt_to_mlt(&[0xff], &MltEncoderOptions::new())
1119 .err()
1120 .unwrap();
1121
1122 assert_eq!(error.kind(), ConvertErrorKind::EncodingFailed);
1123 assert_debug_snapshot!(error.message_text(), @r#""MVT error: protobuf decode error: unexpected end of buffer""#);
1124 }
1125
1126 #[test]
1127 fn tile_stats_split_each_layer_by_column_role() {
1128 let mut out = MltBuffer::new();
1129 let options = MltEncoderOptions::new();
1130 let mut builder = MltLayerBuilder::new("with_id", 4096).ok().unwrap();
1131 let name = builder.add_property("name");
1132 add_point(&mut builder, [1, 1], Some(3));
1133 builder.set_str(name, "alpha").ok().unwrap();
1134 builder.encode_into(&options, &mut out).ok().unwrap();
1135 builder.reset("bare", 4096).ok().unwrap();
1136 add_point(&mut builder, [2, 2], None);
1137 builder.encode_into(&options, &mut out).ok().unwrap();
1138
1139 let stats = MltTileStats::from_bytes(out.as_bytes()).ok().unwrap();
1140
1141 assert_eq!(stats.layer_count(), 2);
1142 assert_eq!(
1143 [stats.layer_name_text(0), stats.layer_name_text(1)],
1144 ["with_id", "bare"]
1145 );
1146 assert_debug_snapshot!([stats.layer_bytes(0), stats.layer_bytes(1)], @"
1147 [
1148 Some(
1149 MltLayerBytes {
1150 size: 52,
1151 geometry: 12,
1152 properties: 15,
1153 ids: 5,
1154 metadata: 20,
1155 },
1156 ),
1157 Some(
1158 MltLayerBytes {
1159 size: 22,
1160 geometry: 12,
1161 properties: 0,
1162 ids: 0,
1163 metadata: 10,
1164 },
1165 ),
1166 ]
1167 ");
1168 }
1169
1170 #[test]
1171 fn tile_stats_sizes_add_up_to_the_tile_without_the_size_varints() {
1172 let mut options = MltEncoderOptions::new();
1173 options.set_wire_version(MltWireVersion::V02);
1174 let mlt = MltConverter::mvt_to_mlt(MULTIPOLYGON, &options)
1175 .ok()
1176 .unwrap();
1177
1178 let stats = MltTileStats::from_bytes(mlt.as_bytes()).ok().unwrap();
1179 let MltLayerBytes {
1180 size,
1181 geometry,
1182 properties,
1183 ids,
1184 metadata,
1185 } = stats.layer_bytes(0).unwrap();
1186
1187 assert_eq!(stats.layer_count(), 1);
1188 assert_eq!(usize::try_from(size).unwrap() + 1, mlt.len());
1189 assert_eq!(geometry + properties + ids + metadata, size);
1190 }
1191
1192 #[test]
1193 fn tile_stats_past_the_last_layer_are_none() {
1194 let mlt = MltConverter::mvt_to_mlt(POINT, &MltEncoderOptions::new())
1195 .ok()
1196 .unwrap();
1197
1198 let stats = MltTileStats::from_bytes(mlt.as_bytes()).ok().unwrap();
1199
1200 assert_eq!(stats.layer_bytes(1), None);
1201 }
1202
1203 #[test]
1204 fn tile_stats_of_garbage_report_invalid_input() {
1205 let error = MltTileStats::from_bytes(&[0xff]).err().unwrap();
1206
1207 assert_eq!(error.kind(), ConvertErrorKind::InvalidInput);
1208 }
1209}