flexpolyline/
lib.rs

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/// Coordinate precision in the polyline
7///
8/// Represents how many digits are to be encoded after the decimal point, e.g.
9/// precision 3 would encode 4.456787 as 4.457.
10///
11/// Supported values: `[0,16)`
12#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
13pub enum Precision {
14    /// 0 decimal digits
15    Digits0 = 0,
16    /// 1 decimal digits
17    Digits1 = 1,
18    /// 2 decimal digits
19    Digits2 = 2,
20    /// 3 decimal digits
21    Digits3 = 3,
22    /// 4 decimal digits
23    Digits4 = 4,
24    /// 5 decimal digits
25    Digits5 = 5,
26    /// 6 decimal digits
27    Digits6 = 6,
28    /// 7 decimal digits
29    Digits7 = 7,
30    /// 8 decimal digits
31    Digits8 = 8,
32    /// 9 decimal digits
33    Digits9 = 9,
34    /// 10 decimal digits
35    Digits10 = 10,
36    /// 11 decimal digits
37    Digits11 = 11,
38    /// 12 decimal digits
39    Digits12 = 12,
40    /// 13 decimal digits
41    Digits13 = 13,
42    /// 14 decimal digits
43    Digits14 = 14,
44    /// 15 decimal digits
45    Digits15 = 15,
46}
47
48impl Precision {
49    /// Converts `u32` to precision.
50    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    /// Converts precision to `u32`.
73    pub fn to_u32(self) -> u32 {
74        self as u32
75    }
76}
77
78/// Informs about the type of the 3rd dimension of a 3D coordinate vector
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub enum Type3d {
81    /// E.g. floor of a building
82    Level = 1,
83    /// E.g. altitude (in the air) relative to ground level or mean sea level
84    Altitude = 2,
85    /// E.g. elevation above mean-sea-level
86    Elevation = 3,
87    /// Reserved for future types
88    Reserved1 = 4,
89    /// Reserved for future types
90    Reserved2 = 5,
91    /// Reserved for custom types
92    Custom1 = 6,
93    /// Reserved for custom types
94    Custom2 = 7,
95}
96
97/// 2- or 3-dimensional polyline
98#[derive(Debug, Clone, PartialEq)]
99pub enum Polyline {
100    /// 2-dimensional polyline
101    Data2d {
102        /// List of 2D coordinates making up this polyline
103        coordinates: Vec<(f64, f64)>,
104        /// Precision of the coordinates (e.g. used for encoding,
105        /// or to report the precision supplied in encoded data)
106        precision2d: Precision,
107    },
108    /// 3-dimensional polyline
109    Data3d {
110        /// List of 3D coordinates making up this polyline
111        coordinates: Vec<(f64, f64, f64)>,
112        /// Precision of the 2D part of the coordinates (e.g. used for encoding,
113        /// or to report the precision supplied in encoded data)
114        precision2d: Precision,
115        /// Precision of the 3D part of the coordinates (e.g. used for encoding,
116        /// or to report the precision supplied in encoded data)
117        precision3d: Precision,
118        /// Type of the 3D component
119        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/// Error reported when encoding or decoding polylines
179#[derive(Debug, PartialEq, Eq)]
180#[non_exhaustive]
181pub enum Error {
182    /// Data is encoded with unsupported version
183    UnsupportedVersion,
184    /// Precision is not supported by encoding
185    InvalidPrecision,
186    /// Encoding is corrupt
187    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    /// Encodes a polyline into a string.
204    ///
205    /// The precision of the polyline is used to round coordinates, so the transformation is lossy
206    /// in nature.
207    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    /// Decodes an encoded polyline.
228    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!(), // impossible, we only decoded 3 bits
243        };
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); // Version 1
283    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    // make room on lowest bit
331    let mut encoded = (value << 1) as u64;
332
333    // invert bits if the value is negative
334    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    // var-length encode the number in chunks of 5 bits starting with the least significant
345    // to the most significant
346    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}