//! GPX data extraction functions. //! //! We *could* use the [gpx](https://github.com/georust/gpx) crate, but we don't care about much //! other than the coordinates of the tracks. By implementing the little functionality ourselves, //! we can use a fast XML parser ([roxmltree](https://github.com/RazrFalcon/roxmltree)). use std::{f64::consts::PI, fs, path::Path}; use color_eyre::eyre::{eyre, Result}; use roxmltree::{Document, Node, NodeType}; #[derive(Debug, Copy, Clone, PartialEq)] pub struct Coordinates { longitude: f64, latitude: f64, } impl Coordinates { /// Calculates the [Web Mercator /// projection](https://en.wikipedia.org/wiki/Web_Mercator_projection) of the coordinates. /// Returns the `(x, y)` coordinates. pub fn web_mercator(self, zoom: u32) -> (u64, u64) { let lambda = self.longitude.to_radians(); let phi = self.latitude.to_radians(); let x = 2u64.pow(zoom) as f64 / (2.0 * PI) * 256.0 * (lambda + PI); let y = 2u64.pow(zoom) as f64 / (2.0 * PI) * 256.0 * (PI - (PI / 4.0 + phi / 2.0).tan().ln()); (x.floor() as u64, y.floor() as u64) } } fn is_track_node(node: &Node) -> bool { node.node_type() == NodeType::Element && node.tag_name().name() == "trk" } fn is_track_segment(node: &Node) -> bool { node.node_type() == NodeType::Element && node.tag_name().name() == "trkseg" } fn is_track_point(node: &Node) -> bool { node.node_type() == NodeType::Element && node.tag_name().name() == "trkpt" } pub fn extract_from_str(input: &str) -> Result> { let mut result = Vec::new(); let document = Document::parse(input)?; for node in document.root_element().children().filter(is_track_node) { for segment in node.children().filter(is_track_segment) { for point in segment.children().filter(is_track_point) { let latitude = point .attribute("lat") .and_then(|l| l.parse::().ok()) .ok_or_else(|| eyre!("Invalid latitude"))?; let longitude = point .attribute("lon") .and_then(|l| l.parse::().ok()) .ok_or_else(|| eyre!("Invalid longitude"))?; result.push(Coordinates { latitude, longitude, }); } } } Ok(result) } #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum Compression { None, } pub fn extract_from_file>( path: P, _compression: Compression, ) -> Result> { let content = fs::read_to_string(path)?; extract_from_str(&content) }