1#![doc = include_str!("../README.md")]
2#![doc(html_playground_url = "https://play.rust-lang.org/")]
3#![deny(warnings, missing_docs)]
4#![allow(clippy::unreadable_literal)]
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Precision {
14 Digits0 = 0,
16 Digits1 = 1,
18 Digits2 = 2,
20 Digits3 = 3,
22 Digits4 = 4,
24 Digits5 = 5,
26 Digits6 = 6,
28 Digits7 = 7,
30 Digits8 = 8,
32 Digits9 = 9,
34 Digits10 = 10,
36 Digits11 = 11,
38 Digits12 = 12,
40 Digits13 = 13,
42 Digits14 = 14,
44 Digits15 = 15,
46}
47
48impl Precision {
49 pub fn from_u32(digits: u32) -> Option<Precision> {
51 match digits {
52 0 => Some(Precision::Digits0),
53 1 => Some(Precision::Digits1),
54 2 => Some(Precision::Digits2),
55 3 => Some(Precision::Digits3),
56 4 => Some(Precision::Digits4),
57 5 => Some(Precision::Digits5),
58 6 => Some(Precision::Digits6),
59 7 => Some(Precision::Digits7),
60 8 => Some(Precision::Digits8),
61 9 => Some(Precision::Digits9),
62 10 => Some(Precision::Digits10),
63 11 => Some(Precision::Digits11),
64 12 => Some(Precision::Digits12),
65 13 => Some(Precision::Digits13),
66 14 => Some(Precision::Digits14),
67 15 => Some(Precision::Digits15),
68 _ => None,
69 }
70 }
71
72 pub fn to_u32(self) -> u32 {
74 self as u32
75 }
76}
77
78#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum Type3d {
81 Level = 1,
83 Altitude = 2,
85 Elevation = 3,
87 Reserved1 = 4,
89 Reserved2 = 5,
91 Custom1 = 6,
93 Custom2 = 7,
95}
96
97#[derive(Debug, Clone, PartialEq)]
99pub enum Polyline {
100 Data2d {
102 coordinates: Vec<(f64, f64)>,
104 precision2d: Precision,
107 },
108 Data3d {
110 coordinates: Vec<(f64, f64, f64)>,
112 precision2d: Precision,
115 precision3d: Precision,
118 type3d: Type3d,
120 },
121}
122
123impl std::fmt::Display for Polyline {
124 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
125 match self {
126 Polyline::Data2d {
127 coordinates,
128 precision2d,
129 } => {
130 let prec_2d = f.precision().unwrap_or(precision2d.to_u32() as usize);
131 write!(f, "{{({}); [", precision2d.to_u32())?;
132 for coord in coordinates {
133 write!(
134 f,
135 "({:.*}, {:.*}), ",
136 { prec_2d },
137 coord.0,
138 { prec_2d },
139 coord.1
140 )?;
141 }
142 write!(f, "]}}")?;
143 }
144 Polyline::Data3d {
145 coordinates,
146 precision2d,
147 precision3d,
148 type3d,
149 } => {
150 let prec_2d = f.precision().unwrap_or(precision2d.to_u32() as usize);
151 let prec_3d = f.precision().unwrap_or(precision3d.to_u32() as usize);
152 write!(
153 f,
154 "{{({}, {}, {}); [",
155 precision2d.to_u32(),
156 precision3d.to_u32(),
157 *type3d as usize
158 )?;
159 for coord in coordinates {
160 write!(
161 f,
162 "({:.*}, {:.*}, {:.*}), ",
163 { prec_2d },
164 coord.0,
165 { prec_2d },
166 coord.1,
167 { prec_3d },
168 coord.2
169 )?;
170 }
171 write!(f, "]}}")?;
172 }
173 }
174 Ok(())
175 }
176}
177
178#[derive(Debug, PartialEq, Eq)]
180#[non_exhaustive]
181pub enum Error {
182 UnsupportedVersion,
184 InvalidPrecision,
186 InvalidEncoding,
188}
189
190impl std::fmt::Display for Error {
191 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
192 match self {
193 Error::UnsupportedVersion => write!(f, "UnsupportedVersion"),
194 Error::InvalidPrecision => write!(f, "InvalidPrecision"),
195 Error::InvalidEncoding => write!(f, "InvalidEncoding"),
196 }
197 }
198}
199
200impl std::error::Error for Error {}
201
202impl Polyline {
203 pub fn encode(&self) -> Result<String, Error> {
208 match self {
209 Polyline::Data2d {
210 coordinates,
211 precision2d,
212 } => encode_2d(coordinates, precision2d.to_u32()),
213 Polyline::Data3d {
214 coordinates,
215 precision2d,
216 precision3d,
217 type3d,
218 } => encode_3d(
219 coordinates,
220 precision2d.to_u32(),
221 precision3d.to_u32(),
222 *type3d as u32,
223 ),
224 }
225 }
226
227 pub fn decode<S: AsRef<str>>(encoded: S) -> Result<Self, Error> {
229 let mut bytes = encoded.as_ref().bytes();
230
231 let (precision2d, precision3d, type3d) = decode_header(&mut bytes)?;
232
233 let type3d = match type3d {
234 1 => Some(Type3d::Level),
235 2 => Some(Type3d::Altitude),
236 3 => Some(Type3d::Elevation),
237 4 => Some(Type3d::Reserved1),
238 5 => Some(Type3d::Reserved2),
239 6 => Some(Type3d::Custom1),
240 7 => Some(Type3d::Custom2),
241 0 => None,
242 _ => panic!(), };
244
245 if let Some(type3d) = type3d {
246 let coordinates = decode3d(bytes, precision2d, precision3d)?;
247 Ok(Polyline::Data3d {
248 coordinates,
249 precision2d: Precision::from_u32(precision2d).ok_or(Error::InvalidPrecision)?,
250 precision3d: Precision::from_u32(precision3d).ok_or(Error::InvalidPrecision)?,
251 type3d,
252 })
253 } else {
254 let coordinates = decode2d(bytes, precision2d)?;
255 Ok(Polyline::Data2d {
256 coordinates,
257 precision2d: Precision::from_u32(precision2d).ok_or(Error::InvalidPrecision)?,
258 })
259 }
260 }
261}
262
263fn precision_to_scale(precision: u32) -> impl Fn(f64) -> i64 {
264 let scale = 10_u64.pow(precision) as f64;
265 move |value: f64| (value * scale).round() as i64
266}
267
268fn precision_to_inverse_scale(precision: u32) -> impl Fn(i64) -> f64 {
269 let scale = 10_u64.pow(precision) as f64;
270 move |value: i64| value as f64 / scale
271}
272
273fn encode_header(
274 precision2d: u32,
275 precision3d: u32,
276 type3d: u32,
277 result: &mut String,
278) -> Result<(), Error> {
279 if precision2d > 15 || precision3d > 15 {
280 return Err(Error::InvalidPrecision);
281 }
282 var_encode_u64(1, result); let header = (precision3d << 7) | (type3d << 4) | precision2d;
284 var_encode_u64(u64::from(header), result);
285 Ok(())
286}
287
288fn encode_2d(coords: &[(f64, f64)], precision2d: u32) -> Result<String, Error> {
289 let mut result = String::with_capacity((coords.len() * 2) + 2);
290
291 encode_header(precision2d, 0, 0, &mut result)?;
292 let scale2d = precision_to_scale(precision2d);
293
294 let mut last_coord = (0, 0);
295 for coord in coords {
296 let scaled_coord = (scale2d(coord.0), scale2d(coord.1));
297 var_encode_i64(scaled_coord.0 - last_coord.0, &mut result);
298 var_encode_i64(scaled_coord.1 - last_coord.1, &mut result);
299 last_coord = scaled_coord;
300 }
301
302 Ok(result)
303}
304
305fn encode_3d(
306 coords: &[(f64, f64, f64)],
307 precision2d: u32,
308 precision3d: u32,
309 type3d: u32,
310) -> Result<String, Error> {
311 let mut result = String::with_capacity((coords.len() * 3) + 2);
312
313 encode_header(precision2d, precision3d, type3d, &mut result)?;
314 let scale2d = precision_to_scale(precision2d);
315 let scale3d = precision_to_scale(precision3d);
316
317 let mut last_coord = (0, 0, 0);
318 for coord in coords {
319 let scaled_coord = (scale2d(coord.0), scale2d(coord.1), scale3d(coord.2));
320 var_encode_i64(scaled_coord.0 - last_coord.0, &mut result);
321 var_encode_i64(scaled_coord.1 - last_coord.1, &mut result);
322 var_encode_i64(scaled_coord.2 - last_coord.2, &mut result);
323 last_coord = scaled_coord;
324 }
325
326 Ok(result)
327}
328
329fn var_encode_i64(value: i64, result: &mut String) {
330 let mut encoded = (value << 1) as u64;
332
333 if value < 0 {
335 encoded = !encoded;
336 }
337
338 var_encode_u64(encoded, result);
339}
340
341fn var_encode_u64(mut value: u64, result: &mut String) {
342 const ENCODING_TABLE: &str = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
343
344 while value > 0x1F {
347 let pos = (value & 0x1F) | 0x20;
348 let c = ENCODING_TABLE.as_bytes()[pos as usize] as char;
349 result.push(c);
350 value >>= 5;
351 }
352 let c = ENCODING_TABLE.as_bytes()[value as usize] as char;
353 result.push(c);
354}
355
356fn decode_header<I: Iterator<Item = u8>>(bytes: &mut I) -> Result<(u32, u32, u32), Error> {
357 let version = var_decode_u64(bytes)?;
358
359 if version != 1 {
360 return Err(Error::UnsupportedVersion);
361 }
362
363 let header = var_decode_u64(bytes)?;
364
365 if header >= (1_u64 << 11) {
366 return Err(Error::InvalidEncoding);
367 }
368 let precision2d = (header & 15) as u32;
369 let type3d = ((header >> 4) & 7) as u32;
370 let precision3d = ((header >> 7) & 15) as u32;
371
372 Ok((precision2d, precision3d, type3d))
373}
374
375fn decode2d<I: ExactSizeIterator<Item = u8>>(
376 mut bytes: I,
377 precision2d: u32,
378) -> Result<Vec<(f64, f64)>, Error> {
379 let mut result = Vec::with_capacity(bytes.len() / 2);
380 let scale2d = precision_to_inverse_scale(precision2d);
381 let mut last_coord = (0, 0);
382 while bytes.len() > 0 {
383 let delta = (var_decode_i64(&mut bytes)?, var_decode_i64(&mut bytes)?);
384 last_coord = (last_coord.0 + delta.0, last_coord.1 + delta.1);
385
386 result.push((scale2d(last_coord.0), scale2d(last_coord.1)));
387 }
388 Ok(result)
389}
390
391fn decode3d<I: ExactSizeIterator<Item = u8>>(
392 mut bytes: I,
393 precision2d: u32,
394 precision3d: u32,
395) -> Result<Vec<(f64, f64, f64)>, Error> {
396 let mut result = Vec::with_capacity(bytes.len() / 2);
397 let scale2d = precision_to_inverse_scale(precision2d);
398 let scale3d = precision_to_inverse_scale(precision3d);
399 let mut last_coord = (0, 0, 0);
400 while bytes.len() > 0 {
401 let delta = (
402 var_decode_i64(&mut bytes)?,
403 var_decode_i64(&mut bytes)?,
404 var_decode_i64(&mut bytes)?,
405 );
406 last_coord = (
407 last_coord.0 + delta.0,
408 last_coord.1 + delta.1,
409 last_coord.2 + delta.2,
410 );
411
412 result.push((
413 scale2d(last_coord.0),
414 scale2d(last_coord.1),
415 scale3d(last_coord.2),
416 ));
417 }
418 Ok(result)
419}
420
421fn var_decode_i64<I: Iterator<Item = u8>>(bytes: &mut I) -> Result<i64, Error> {
422 match var_decode_u64(bytes) {
423 Ok(mut value) => {
424 let negative = (value & 1) != 0;
425 value >>= 1;
426 if negative {
427 value = !value;
428 }
429 Ok(value as i64)
430 }
431 Err(err) => Err(err),
432 }
433}
434
435fn var_decode_u64<I: Iterator<Item = u8>>(bytes: &mut I) -> Result<u64, Error> {
436 #[rustfmt::skip]
437 const DECODING_TABLE: &[i8] = &[
438 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
439 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
440 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
441 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
442 -1, -1, -1, -1, -1, 62, -1, -1, 52, 53,
443 54, 55, 56, 57, 58, 59, 60, 61, -1, -1,
444 -1, -1, -1, -1, -1, 0, 1, 2, 3, 4,
445 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
446 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
447 25, -1, -1, -1, -1, 63, -1, 26, 27, 28,
448 29, 30, 31, 32, 33, 34, 35, 36, 37, 38,
449 39, 40, 41, 42, 43, 44, 45, 46, 47, 48,
450 49, 50, 51, -1, -1, -1, -1, -1, -1, -1,
451 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
452 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
453 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
454 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
455 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
456 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
457 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
458 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
459 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
460 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
461 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
462 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
463 -1, -1, -1, -1, -1, -1,
464 ];
465
466 let mut result: u64 = 0;
467 let mut shift = 0;
468
469 for byte in bytes {
470 let value = DECODING_TABLE[byte as usize];
471 if value < 0 {
472 return Err(Error::InvalidEncoding);
473 }
474
475 let value = value as u64;
476 result |= (value & 0x1F) << shift;
477
478 if (value & 0x20) == 0 {
479 return Ok(result);
480 }
481
482 shift += 5;
483
484 if shift >= 64 {
485 return Err(Error::InvalidEncoding);
486 }
487 }
488
489 Err(Error::InvalidEncoding)
490}
491
492#[cfg(test)]
493mod tests {
494 use super::*;
495
496 #[test]
497 fn test_var_encode_i64() {
498 let mut buf = String::new();
499 var_encode_i64(-17998321, &mut buf);
500 assert_eq!(buf, "h_wqiB");
501 }
502
503 #[test]
504 fn test_encode_2d_example_1() {
505 let coordinates = vec![
506 (50.1022829, 8.6982122),
507 (50.1020076, 8.6956695),
508 (50.1006313, 8.6914960),
509 (50.0987800, 8.6875156),
510 ];
511
512 let expected = "BFoz5xJ67i1B1B7PzIhaxL7Y";
513 assert_eq!(
514 &Polyline::Data2d {
515 coordinates,
516 precision2d: Precision::Digits5
517 }
518 .encode()
519 .unwrap(),
520 expected
521 );
522 }
523
524 #[test]
525 fn test_encode_2d_example_2() {
526 let coordinates = vec![
527 (52.5199356, 13.3866272),
528 (52.5100899, 13.2816896),
529 (52.4351807, 13.1935196),
530 (52.4107285, 13.1964502),
531 (52.3887100, 13.1557798),
532 (52.3727798, 13.1491003),
533 (52.3737488, 13.1154604),
534 (52.3875198, 13.0872202),
535 (52.4029388, 13.0706196),
536 (52.4105797, 13.0755529),
537 ];
538
539 let expected = "BF05xgKuy2xCx9B7vUl0OhnR54EqSzpEl-HxjD3pBiGnyGi2CvwFsgD3nD4vB6e";
540 assert_eq!(
541 &Polyline::Data2d {
542 coordinates,
543 precision2d: Precision::Digits5
544 }
545 .encode()
546 .unwrap(),
547 expected
548 );
549 }
550
551 #[test]
552 fn test_encode_3d_example_1() {
553 let coordinates = vec![
554 (50.1022829, 8.6982122, 10.0),
555 (50.1020076, 8.6956695, 20.0),
556 (50.1006313, 8.6914960, 30.0),
557 (50.0987800, 8.6875156, 40.0),
558 ];
559
560 let expected = "BVoz5xJ67i1BU1B7PUzIhaUxL7YU";
561 assert_eq!(
562 &Polyline::Data3d {
563 coordinates,
564 precision2d: Precision::Digits5,
565 precision3d: Precision::Digits0,
566 type3d: Type3d::Level
567 }
568 .encode()
569 .unwrap(),
570 expected
571 );
572 }
573
574 #[test]
575 fn test_var_decode_i64() -> Result<(), Error> {
576 let mut bytes = "h_wqiB".bytes();
577 let res = var_decode_i64(&mut bytes)?;
578 assert_eq!(res, -17998321);
579 let res = var_decode_i64(&mut bytes);
580 assert!(res.is_err());
581
582 let mut bytes = "hhhhhhhhhhhhhhhhhhh".bytes();
583 let res = var_decode_i64(&mut bytes);
584 assert!(res.is_err());
585 Ok(())
586 }
587
588 #[test]
589 fn test_decode_2d_example_1() -> Result<(), Error> {
590 let polyline = Polyline::decode("BFoz5xJ67i1B1B7PzIhaxL7Y")?;
591 let expected = "{(5); [\
592 (50.102280, 8.698210), \
593 (50.102010, 8.695670), \
594 (50.100630, 8.691500), \
595 (50.098780, 8.687520), \
596 ]}";
597 let result = format!("{:.6}", polyline);
598 assert_eq!(expected, result);
599 Ok(())
600 }
601
602 #[test]
603 fn test_decode_2d_example_2() -> Result<(), Error> {
604 let polyline =
605 Polyline::decode("BF05xgKuy2xCx9B7vUl0OhnR54EqSzpEl-HxjD3pBiGnyGi2CvwFsgD3nD4vB6e")?;
606 let expected = "{(5); [\
607 (52.519940, 13.386630), \
608 (52.510090, 13.281690), \
609 (52.435180, 13.193520), \
610 (52.410730, 13.196450), \
611 (52.388710, 13.155780), \
612 (52.372780, 13.149100), \
613 (52.373750, 13.115460), \
614 (52.387520, 13.087220), \
615 (52.402940, 13.070620), \
616 (52.410580, 13.075550), \
617 ]}";
618
619 let result = format!("{:.6}", polyline);
620 assert_eq!(expected, result);
621 Ok(())
622 }
623
624 #[test]
625 fn test_decode_3d_example_1() -> Result<(), Error> {
626 let polyline = Polyline::decode("BVoz5xJ67i1BU1B7PUzIhaUxL7YU")?;
627 let expected = "{(5, 0, 1); [\
628 (50.102280, 8.698210, 10.000000), \
629 (50.102010, 8.695670, 20.000000), \
630 (50.100630, 8.691500, 30.000000), \
631 (50.098780, 8.687520, 40.000000), \
632 ]}";
633
634 let result = format!("{:.6}", polyline);
635 assert_eq!(expected, result);
636 Ok(())
637 }
638
639 #[test]
640 #[allow(clippy::zero_prefixed_literal)]
641 fn test_encode_decode_2d() -> Result<(), Error> {
642 let coordinate_values: Vec<(u64, u64)> = vec![
643 (96821474666297905, 78334196549606266),
644 (29405294060895017, 70361389340728572),
645 (16173544634348013, 17673855782924183),
646 (22448654820449524, 13005139703027850),
647 (73351231936757857, 78298027377720633),
648 (78008331957098324, 04847613123220218),
649 (62755680515396509, 49165433608990700),
650 (93297154866561429, 52373802822465027),
651 (89973844644540399, 75975762025877533),
652 (48555821719956867, 31591090068957813),
653 ];
654
655 for precision2d in 0..=15 {
656 let to_f64 = |value: &(u64, u64)| {
657 (
658 value.0 as f64 / 10_u64.pow(15) as f64,
659 value.1 as f64 / 10_u64.pow(15) as f64,
660 )
661 };
662
663 let to_rounded_f64 = |value: &(u64, u64)| {
664 let value = to_f64(value);
665 let scale = 10_u64.pow(precision2d) as f64;
666 (
667 (value.0 * scale).round() / scale,
668 (value.1 * scale).round() / scale,
669 )
670 };
671
672 let expected = format!(
673 "{:.*}",
674 precision2d as usize + 1,
675 Polyline::Data2d {
676 coordinates: coordinate_values.iter().map(to_rounded_f64).collect(),
677 precision2d: Precision::from_u32(precision2d).unwrap(),
678 }
679 );
680
681 let encoded = &Polyline::Data2d {
682 coordinates: coordinate_values.iter().map(to_f64).collect(),
683 precision2d: Precision::from_u32(precision2d).unwrap(),
684 }
685 .encode()?;
686
687 let polyline = Polyline::decode(encoded)?;
688 let result = format!("{:.*}", precision2d as usize + 1, polyline);
689 assert_eq!(expected, result);
690 }
691
692 Ok(())
693 }
694
695 #[test]
696 #[allow(clippy::zero_prefixed_literal)]
697 fn test_encode_decode_3d() -> Result<(), Error> {
698 let coordinate_values: Vec<(u64, u64, u64)> = vec![
699 (96821474666297905, 78334196549606266, 23131023979661380),
700 (29405294060895017, 70361389340728572, 81917934930416924),
701 (16173544634348013, 17673855782924183, 86188502094968953),
702 (22448654820449524, 13005139703027850, 68774670569614983),
703 (73351231936757857, 78298027377720633, 52078352171243855),
704 (78008331957098324, 04847613123220218, 06550838806837986),
705 (62755680515396509, 49165433608990700, 39041897671300539),
706 (93297154866561429, 52373802822465027, 67310807938230681),
707 (89973844644540399, 75975762025877533, 66789448009436096),
708 (48555821719956867, 31591090068957813, 49203621966471323),
709 ];
710
711 let precision2d = 5;
712 for precision3d in 0..=15 {
713 for type3d in &[
714 Type3d::Level,
715 Type3d::Altitude,
716 Type3d::Elevation,
717 Type3d::Reserved1,
718 Type3d::Reserved2,
719 Type3d::Custom1,
720 Type3d::Custom2,
721 ] {
722 let to_f64 = |value: &(u64, u64, u64)| {
723 (
724 value.0 as f64 / 10_u64.pow(15) as f64,
725 value.1 as f64 / 10_u64.pow(15) as f64,
726 value.2 as f64 / 10_u64.pow(15) as f64,
727 )
728 };
729
730 let to_rounded_f64 = |value: &(u64, u64, u64)| {
731 let value = to_f64(value);
732 let scale2d = 10_u64.pow(precision2d) as f64;
733 let scale3d = 10_u64.pow(precision3d) as f64;
734 (
735 (value.0 * scale2d).round() / scale2d,
736 (value.1 * scale2d).round() / scale2d,
737 (value.2 * scale3d).round() / scale3d,
738 )
739 };
740
741 let expected = format!(
742 "{:.*}",
743 precision2d.max(precision3d) as usize + 1,
744 Polyline::Data3d {
745 coordinates: coordinate_values.iter().map(to_rounded_f64).collect(),
746 precision2d: Precision::from_u32(precision2d).unwrap(),
747 precision3d: Precision::from_u32(precision3d).unwrap(),
748 type3d: *type3d,
749 }
750 );
751
752 let encoded = Polyline::Data3d {
753 coordinates: coordinate_values.iter().map(to_f64).collect(),
754 precision2d: Precision::from_u32(precision2d).unwrap(),
755 precision3d: Precision::from_u32(precision3d).unwrap(),
756 type3d: *type3d,
757 }
758 .encode()?;
759
760 let polyline = Polyline::decode(&encoded)?;
761 let result = format!("{:.*}", precision2d.max(precision3d) as usize + 1, polyline);
762 assert_eq!(expected, result);
763 }
764 }
765
766 Ok(())
767 }
768}