tracklib 0.3.0

RWGPS Track Format Library
Documentation
use crate::simplify::Point;
use crate::surface::{RoadClassId, SurfaceTypeId};
use std::convert::TryFrom;

#[derive(Debug)]
pub enum PointField {
    Y,
    X,
    D,
    E,
    S(SurfaceTypeId),
    R(RoadClassId),
}

#[derive(Debug)]
pub struct FieldEncodeOptions {
    field: PointField,
    factor: f64,
}

impl FieldEncodeOptions {
    pub fn new(field: PointField, precision: u32) -> Self {
        Self {
            field,
            factor: f64::from(10_u32.pow(precision)),
        }
    }
}

fn scale(n: f64, factor: f64) -> i64 {
    (n * factor).round() as i64
}

fn encode(current: f64, previous: f64, factor: f64) -> String {
    let current_scaled = scale(current, factor);
    let previous_scaled = scale(previous, factor);
    let diff = current_scaled - previous_scaled;
    let mut v = diff << 1;
    if diff < 0 {
        v = !v;
    }

    let mut output = String::new();
    while v >= 0x20 {
        let from_char = char::from_u32(((0x20 | (v & 0x1f)) + 63) as u32).unwrap();
        output.push(from_char);
        v >>= 5;
    }
    let from_char = char::from_u32((v + 63) as u32).unwrap();
    output.push(from_char);
    output
}

pub(crate) fn polyline_encode(points: &[Point], fields: &[FieldEncodeOptions]) -> String {
    let mut output = String::new();
    let mut prev = &Point{index: 0,
                          x: 0.0,
                          y: 0.0,
                          d: 0.0,
                          e: 0.0,
                          s: Some(0),
                          r: Some(0)};

    for point in points {
        for field in fields {
            match field.field {
                PointField::Y => output.push_str(&encode(point.y, prev.y, field.factor)),
                PointField::X => output.push_str(&encode(point.x, prev.x, field.factor)),
                PointField::D => output.push_str(&encode(point.d, prev.d, field.factor)),
                PointField::E => output.push_str(&encode(point.e, prev.e, field.factor)),
                PointField::S(default_surface_id) => output.push_str(&encode(f64::from(i32::try_from(point.s.unwrap_or(default_surface_id)).unwrap_or(0)),
                                                                             f64::from(i32::try_from(prev.s.unwrap_or(default_surface_id)).unwrap_or(0)),
                                                                             field.factor)),
                PointField::R(default_road_class_id) => output.push_str(&encode(f64::from(i32::try_from(point.r.unwrap_or(default_road_class_id)).unwrap_or(0)),
                                                                                f64::from(i32::try_from(prev.r.unwrap_or(default_road_class_id)).unwrap_or(0)),
                                                                                field.factor)),
            }
        }

        prev = point;
    }

    output
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_polyline() {
        let fields = vec![
            FieldEncodeOptions::new(PointField::Y, 5),
            FieldEncodeOptions::new(PointField::X, 5),
        ];
        assert_eq!(
            polyline_encode(
                &vec![
                    Point {
                        x: -120.200,
                        y: 38.500,
                        ..Default::default()
                    },
                    Point {
                        x: -120.950,
                        y: 40.700,
                        ..Default::default()
                    },
                    Point {
                        x: -126.453,
                        y: 43.252,
                        ..Default::default()
                    }
                ],
                &fields
            ),
            "_p~iF~ps|U_ulLnnqC_mqNvxq`@".to_string()
        );
    }

    #[test]
    fn test_surface_encoding() {
        let fields = vec![
            FieldEncodeOptions::new(PointField::S(99), 5),
            FieldEncodeOptions::new(PointField::R(10), 5),
        ];
        assert_eq!(
            polyline_encode(
                &vec![
                    Point {
                        s: None,
                        r: Some(50),
                        ..Default::default()
                    },
                    Point {
                        s: Some(4),
                        r: None,
                        ..Default::default()
                    },
                ],
                &fields
            ),
            "_}f{Q_sdpH~tybQ~ncsF".to_string()
        );
    }
}