//! Version 1 of the stored form. It is frozen: the engine types may change, these may not (a test //! compares the bytes they give with the ones of a fixture). A change of the form is a new //! version, next to this one, that the decoders convert from. use std::collections::HashMap; use serde::{Deserialize, Serialize}; use uuid::Uuid; use crate::{ Author, Categories, File, FileInfo, Link, LngLat, TrackInfo, Trackpoint, TrackpointCategories, Waypoint, WaypointId, }; #[derive(Serialize, Deserialize)] pub struct Link1 { href: String, text: Option, } impl From<&Link> for Link1 { fn from(link: &Link) -> Self { Self { href: link.href.clone(), text: link.text.clone(), } } } impl From for Link { fn from(link: Link1) -> Self { Self { href: link.href, text: link.text, } } } #[derive(Serialize, Deserialize)] pub struct Author1 { name: Option, email: Option, link: Option, } #[derive(Serialize, Deserialize)] pub struct FileInfo1 { name: String, desc: Option, author: Option, links: Vec, time: Option, } impl From<&FileInfo> for FileInfo1 { fn from(info: &FileInfo) -> Self { Self { name: info.name.clone(), desc: info.desc.clone(), author: info.author.as_ref().map(|author| Author1 { name: author.name.clone(), email: author.email.clone(), link: author.link.as_ref().map(Into::into), }), links: info.links.iter().map(Into::into).collect(), time: info.time, } } } impl From for FileInfo { fn from(info: FileInfo1) -> Self { Self { name: info.name, desc: info.desc, author: info.author.map(|author| Author { name: author.name, email: author.email, link: author.link.map(Into::into), }), links: info.links.into_iter().map(Into::into).collect(), time: info.time, } } } #[derive(Serialize, Deserialize)] pub struct TrackInfo1 { name: Option, cmt: Option, desc: Option, src: Option, links: Vec, type_: Option, color: Option, opacity: Option, width: Option, } impl From<&TrackInfo> for TrackInfo1 { fn from(info: &TrackInfo) -> Self { Self { name: info.name.clone(), cmt: info.cmt.clone(), desc: info.desc.clone(), src: info.src.clone(), links: info.links.iter().map(Into::into).collect(), type_: info.type_.clone(), color: info.color.clone(), opacity: info.opacity, width: info.width, } } } impl From for TrackInfo { fn from(info: TrackInfo1) -> Self { Self { name: info.name, cmt: info.cmt, desc: info.desc, src: info.src, links: info.links.into_iter().map(Into::into).collect(), type_: info.type_, color: info.color, opacity: info.opacity, width: info.width, } } } /// A file without its trackpoints and waypoints, which are in chunks. #[derive(Serialize, Deserialize)] pub struct FileRecord1 { pub info: FileInfo1, pub tracks: Vec, pub waypoints: Vec, } #[derive(Serialize, Deserialize)] pub struct TrackRecord1 { pub id: Uuid, pub info: TrackInfo1, pub segments: Vec, } #[derive(Serialize, Deserialize)] pub struct SegmentRecord1 { pub id: Uuid, pub chunks: Vec, } impl FileRecord1 { pub fn new(file: &File) -> Self { Self { info: (&file.info).into(), tracks: file .trk .iter() .map(|track| TrackRecord1 { id: track.id.0, info: (&track.info).into(), segments: track .trkseg .iter() .map(|segment| SegmentRecord1 { id: segment.id.0, chunks: segment.chunks().iter().map(|chunk| chunk.id.0).collect(), }) .collect(), }) .collect(), waypoints: file.wpt.chunks().iter().map(|chunk| chunk.id.0).collect(), } } } #[derive(Serialize, Deserialize)] pub struct Trackpoint1 { lng: f64, lat: f64, ele: f64, time: Option, atemp: Option, hr: Option, cad: Option, power: Option, /// The position of the name in the list of the chunk (see [`TrackpointChunk1`]). surface: Option, highway: Option, sac_scale: Option, mtb_scale: Option, anchor: Option, } impl From<&Trackpoint> for Trackpoint1 { fn from(point: &Trackpoint) -> Self { Self { lng: point.coordinates.lng, lat: point.coordinates.lat, ele: point.ele, time: point.time, atemp: point.atemp, hr: point.hr, cad: point.cad, power: point.power, surface: point.surface, highway: point.highway, sac_scale: point.sac_scale, mtb_scale: point.mtb_scale, anchor: point.anchor, } } } impl From for Trackpoint { fn from(point: Trackpoint1) -> Self { Self { coordinates: LngLat { lng: point.lng, lat: point.lat, }, ele: point.ele, time: point.time, atemp: point.atemp, hr: point.hr, cad: point.cad, power: point.power, surface: point.surface, highway: point.highway, sac_scale: point.sac_scale, mtb_scale: point.mtb_scale, anchor: point.anchor, } } } #[derive(Serialize, Deserialize)] pub struct Waypoint1 { id: Uuid, lng: f64, lat: f64, ele: f64, time: Option, name: Option, cmt: Option, desc: Option, links: Vec, sym: Option, type_: Option, } impl From<&Waypoint> for Waypoint1 { fn from(waypoint: &Waypoint) -> Self { Self { id: waypoint.id.0, lng: waypoint.coordinates.lng, lat: waypoint.coordinates.lat, ele: waypoint.ele, time: waypoint.time, name: waypoint.name.clone(), cmt: waypoint.cmt.clone(), desc: waypoint.desc.clone(), links: waypoint.links.iter().map(Into::into).collect(), sym: waypoint.sym.clone(), type_: waypoint.type_.clone(), } } } impl From for Waypoint { fn from(waypoint: Waypoint1) -> Self { Self { id: WaypointId(waypoint.id), coordinates: LngLat { lng: waypoint.lng, lat: waypoint.lat, }, ele: waypoint.ele, time: waypoint.time, name: waypoint.name, cmt: waypoint.cmt, desc: waypoint.desc, links: waypoint.links.into_iter().map(Into::into).collect(), sym: waypoint.sym, type_: waypoint.type_, } } } /// A chunk of trackpoints. It carries the names its categories refer to: the surface, highway, /// SAC scale and MTB scale of its trackpoints are the positions of the names in its own lists, /// which only hold the names it uses. So it means the same whatever the table of categories of /// the engine that reads it is, and it is not changed by what other chunks learn. #[derive(Serialize, Deserialize)] pub struct TrackpointChunk1 { surface: Vec, highway: Vec, sac_scale: Vec, mtb_scale: Vec, points: Vec, } /// The names of a category used by a chunk, with their position in the list of the chunk. struct LocalNames<'a> { table: &'a Categories, names: Vec, local: HashMap, } impl<'a> LocalNames<'a> { fn new(table: &'a Categories) -> Self { Self { table, names: vec![], local: HashMap::new(), } } /// The position in the list of the chunk of the name of a code of the table. A code that /// has no name has no position. fn position(&mut self, code: Option) -> Option { let code = code?; if let Some(position) = self.local.get(&code) { return Some(*position); } let name = self.table.name(code)?; let position = u8::try_from(self.names.len()).ok()?; self.names.push(name.to_owned()); self.local.insert(code, position); Some(position) } } /// The codes of the table for the names of a chunk, which are added to it if it does not know /// them. A name that does not fit in a full table has no code. fn codes_of(names: &[String], table: &mut Categories) -> Vec> { names.iter().map(|name| table.code(name)).collect() } impl TrackpointChunk1 { pub fn new(points: &[Trackpoint], categories: &TrackpointCategories) -> Self { let mut surface = LocalNames::new(&categories.surface); let mut highway = LocalNames::new(&categories.highway); let mut sac_scale = LocalNames::new(&categories.sac_scale); let mut mtb_scale = LocalNames::new(&categories.mtb_scale); let points = points .iter() .map(|point| { let mut stored = Trackpoint1::from(point); stored.surface = surface.position(point.surface); stored.highway = highway.position(point.highway); stored.sac_scale = sac_scale.position(point.sac_scale); stored.mtb_scale = mtb_scale.position(point.mtb_scale); stored }) .collect(); Self { surface: surface.names, highway: highway.names, sac_scale: sac_scale.names, mtb_scale: mtb_scale.names, points, } } /// The trackpoints, with the codes of `categories` for the names of the chunk. pub fn into_points(self, categories: &mut TrackpointCategories) -> Vec { let surface = codes_of(&self.surface, &mut categories.surface); let highway = codes_of(&self.highway, &mut categories.highway); let sac_scale = codes_of(&self.sac_scale, &mut categories.sac_scale); let mtb_scale = codes_of(&self.mtb_scale, &mut categories.mtb_scale); let global = |codes: &[Option], local: Option| { local.and_then(|local| codes.get(usize::from(local)).copied().flatten()) }; self.points .into_iter() .map(|stored| { let (s, h, sac, mtb) = ( global(&surface, stored.surface), global(&highway, stored.highway), global(&sac_scale, stored.sac_scale), global(&mtb_scale, stored.mtb_scale), ); let mut point = Trackpoint::from(stored); point.surface = s; point.highway = h; point.sac_scale = sac; point.mtb_scale = mtb; point }) .collect() } }