diff options
Diffstat (limited to 'src/gpx.rs')
-rw-r--r-- | src/gpx.rs | 138 |
1 files changed, 0 insertions, 138 deletions
diff --git a/src/gpx.rs b/src/gpx.rs deleted file mode 100644 index fb9e00e..0000000 --- a/src/gpx.rs +++ /dev/null @@ -1,138 +0,0 @@ -//! 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)). -//! -//! Note that we throw away all information that we don't care about. Since we need only the -//! coordinates of a track, we simply use a `Vec<Coordinates>` to represent a track. -use std::{ - f64::consts::PI, - ffi::OsStr, - fs::{self, File}, - io::{BufReader, Read}, - path::Path, -}; - -use color_eyre::eyre::{eyre, Result}; -use flate2::bufread::GzDecoder; -use roxmltree::{Document, Node, NodeType}; - -/// World coordinates. -#[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)` projection, where both are in the range `[0, 256 * 2^zoom)`. - pub fn web_mercator(self, zoom: u32) -> (u64, u64) { - const WIDTH: f64 = super::layer::TILE_WIDTH as f64; - const HEIGHT: f64 = super::layer::TILE_HEIGHT as f64; - - let lambda = self.longitude.to_radians(); - let phi = self.latitude.to_radians(); - let x = 2u64.pow(zoom) as f64 / (2.0 * PI) * WIDTH * (lambda + PI); - let y = - 2u64.pow(zoom) as f64 / (2.0 * PI) * HEIGHT * (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" -} - -/// Extracts a track from the given string. -pub fn extract_from_str(input: &str) -> Result<Vec<Coordinates>> { - 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::<f64>().ok()) - .ok_or_else(|| eyre!("Invalid latitude"))?; - let longitude = point - .attribute("lon") - .and_then(|l| l.parse::<f64>().ok()) - .ok_or_else(|| eyre!("Invalid longitude"))?; - result.push(Coordinates { - latitude, - longitude, - }); - } - } - } - Ok(result) -} - -/// Compression format of the data. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum Compression { - /// Indicates that no compression is applied, and the file is plain GPX. - None, - /// Indicates that the file is gzip compressed. - Gzip, - /// Indicates that the file is brotli compressed. - Brotli, -} - -impl Compression { - /// Suggests a [`Compression`] from the given path name. - /// - /// This will suggest [`Compression::Brotli`] for files ending in `.br`, [`Compression::Gzip`] - /// for files ending with `.gz` or `.gzip`, and [`Compression::None`] for files ending with - /// `.gpx`. - /// - /// If the file does not end with any of the aforementioned extensions, an error is returned - /// instead. - pub fn suggest_from_path<P: AsRef<Path>>(path: P) -> Option<Compression> { - let Some(ext) = path.as_ref().extension() else { return None }; - if OsStr::new("br") == ext { - Some(Compression::Brotli) - } else if [OsStr::new("gz"), OsStr::new("gzip")].contains(&ext) { - Some(Compression::Gzip) - } else if OsStr::new("gpx") == ext { - Some(Compression::None) - } else { - None - } - } -} - -/// Extracts the relevant GPX data from the given file. -/// -/// Note that the content must be valid UTF-8, as that is what our parser expects. -pub fn extract_from_file<P: AsRef<Path>>( - path: P, - compression: Compression, -) -> Result<Vec<Coordinates>> { - let content = match compression { - Compression::None => fs::read_to_string(path)?, - Compression::Gzip => { - let mut result = String::new(); - GzDecoder::new(BufReader::new(File::open(path)?)).read_to_string(&mut result)?; - result - } - Compression::Brotli => { - let mut result = Vec::new(); - brotli::BrotliDecompress(&mut BufReader::new(File::open(path)?), &mut result)?; - String::from_utf8(result)? - } - }; - extract_from_str(&content) -} |