From b5c93017a4980314ac496a9ea172772439e7e23b Mon Sep 17 00:00:00 2001 From: Teascade Date: Mon, 24 Aug 2020 23:24:26 +0300 Subject: [PATCH] Add gpx file generation --- .gitignore | 3 +- Cargo.lock | 101 ++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 +- src/api.rs | 4 +- src/errors.rs | 8 ++++ src/gpx.rs | 86 ++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 12 ++++-- 7 files changed, 210 insertions(+), 8 deletions(-) create mode 100644 src/gpx.rs diff --git a/.gitignore b/.gitignore index b6a5d22..c4a2f2f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -secret.toml \ No newline at end of file +secret.toml +*.gpx \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 5005905..bc39136 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,5 +1,20 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. +[[package]] +name = "addr2line" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6a2d3371669ab3ca9797670853d61402b03d0b4b9ebf33d677dfa720203072" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" + [[package]] name = "argh" version = "0.1.3" @@ -29,12 +44,32 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1ba68f4276a778591e36a0c348a269888f3a177c8d2054969389e3b59611ff5" +[[package]] +name = "assert_approx_eq" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c07dab4369547dbe5114677b33fbbf724971019f3818172d59a97a61c774ffd" + [[package]] name = "autocfg" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +[[package]] +name = "backtrace" +version = "0.3.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46254cf2fdcdf1badb5934448c1bcbe046a56537b3987d96c51a7afc5d03f293" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + [[package]] name = "base64" version = "0.10.1" @@ -79,6 +114,43 @@ dependencies = [ "time", ] +[[package]] +name = "error-chain" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff511d5dc435d703f4971bc399647c9bc38e20cb41452e3b9feb4765419ed3f3" +dependencies = [ + "backtrace", +] + +[[package]] +name = "geo-types" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "866e8f6dbd2218b05ea8a25daa1bfac32b0515fe7e0a37cb6a7b9ed0ed82a07e" +dependencies = [ + "num-traits", +] + +[[package]] +name = "gimli" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" + +[[package]] +name = "gpx" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d96f48f1635cee5d19d368d8142fb38b3ca5a6ae8b805f0cf42590751f3b33be" +dependencies = [ + "assert_approx_eq", + "chrono", + "error-chain", + "geo-types", + "xml-rs", +] + [[package]] name = "heck" version = "0.3.1" @@ -124,6 +196,15 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "miniz_oxide" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" +dependencies = [ + "adler", +] + [[package]] name = "minreq" version = "2.2.1" @@ -157,6 +238,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "object" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" + [[package]] name = "once_cell" version = "1.4.1" @@ -196,6 +283,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "rustc-demangle" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" + [[package]] name = "rustls" version = "0.16.0" @@ -279,6 +372,8 @@ version = "0.1.0" dependencies = [ "argh", "chrono", + "geo-types", + "gpx", "minreq", "serde", "toml", @@ -425,3 +520,9 @@ name = "winapi-x86_64-pc-windows-gnu" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "xml-rs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b07db065a5cf61a7e4ba64f29e67db906fb1787316516c4e6e5ff0fea1efcd8a" diff --git a/Cargo.toml b/Cargo.toml index 18a8ae0..9a0baaa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,6 @@ serde = { version = "1.0", features = ["derive"] } toml = "0.5" minreq = { version = "2.2.0", features = ["https", "json-using-serde"] } chrono = "0.4" -argh = "0.1" \ No newline at end of file +argh = "0.1" +gpx = "0.8" +geo-types = "*" \ No newline at end of file diff --git a/src/api.rs b/src/api.rs index a73d6ef..80df376 100644 --- a/src/api.rs +++ b/src/api.rs @@ -109,9 +109,9 @@ pub struct LocationModel { #[serde(rename(deserialize = "type"))] pub loc_type: Option, #[serde(rename(deserialize = "coordinateLat"))] - pub lat: Option, + pub lat: Option, #[serde(rename(deserialize = "coordinateLng"))] - pub lon: Option, + pub lon: Option, pub accuracy: Option, } diff --git a/src/errors.rs b/src/errors.rs index 8329066..c1d6120 100644 --- a/src/errors.rs +++ b/src/errors.rs @@ -10,6 +10,7 @@ pub enum GenericError { ChronoParseError(chrono::ParseError), IOError(io::Error), FromUTF8Error(std::string::FromUtf8Error), + GPXError(gpx::errors::Error), MessagedError(String, Option>), } @@ -49,6 +50,12 @@ impl From for GenericError { } } +impl From for GenericError { + fn from(error: gpx::errors::Error) -> Self { + GenericError::GPXError(error) + } +} + impl Display for GenericError { fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> { let err = match self { @@ -66,6 +73,7 @@ impl Display for GenericError { msg, err.as_ref().map(|e| e.to_string()).unwrap_or(String::new()) ), + GenericError::GPXError(e) => format!("GPX error: {}", e), }; write!(f, "{}", err) } diff --git a/src/gpx.rs b/src/gpx.rs new file mode 100644 index 0000000..c4b0186 --- /dev/null +++ b/src/gpx.rs @@ -0,0 +1,86 @@ +use super::{Config, GenericError, LocationModel, TagModel}; +use chrono::offset::{LocalResult, TimeZone, Utc as UTC_ZONE}; +use chrono::{DateTime, NaiveDateTime, Utc}; +use geo_types::Point; +use gpx::{Gpx, GpxVersion, Metadata, Person, Route, Track, TrackSegment, Waypoint}; +use std::fs::File; + +pub fn generate_gpx( + tag: TagModel, + locations: Vec<(NaiveDateTime, LocationModel)>, + config: &Config, +) -> Result { + let route = Route::default(); + + let mut points = Vec::new(); + + for location in locations { + points.push(get_waypoint(location)?); + } + + let segment = TrackSegment { points }; + + let track = Track { + name: None, + comment: None, + description: None, + source: None, + links: Vec::new(), + _type: None, + segments: vec![segment], + }; + + let gpx = Gpx { + version: GpxVersion::Gpx11, + metadata: Some(get_metadata(&tag, config)?), + waypoints: Vec::new(), + tracks: vec![track], + route: route, + }; + + Ok(gpx) +} + +pub fn write_gpx(gpx: &Gpx) -> Result<(), GenericError> { + let file = File::create("test.gpx").unwrap(); + gpx::write(&gpx, file)?; + Ok(()) +} + +fn get_waypoint(point: (NaiveDateTime, LocationModel)) -> Result { + let (naive_time, location) = point; + let now = Utc::now(); + let time: DateTime = DateTime::from_utc(naive_time, now.offset().clone()); + if let (Some(lat), Some(lon)) = (location.lat, location.lon) { + let mut waypoint = Waypoint::new(Point::new(lon, lat)); + waypoint.time = Some(time); + waypoint.source = location.loc_type; + Ok(waypoint) + } else { + Err(GenericError::MessagedError( + "No coordinates for a given point".to_owned(), + None, + )) + } +} + +fn get_metadata(tag: &TagModel, config: &Config) -> Result { + let nick_text = match tag.get_nick(config) { + Some(nick) => format!(" - {}", nick), + None => String::new(), + }; + + Ok(Metadata { + name: Some(format!("{}{}", tag.get_id()?, nick_text)), + description: None, + author: Some(Person { + name: Some("Sofia".to_owned()), + email: Some("sofia.talarmo@gmail.com".to_owned()), + link: None, + }), + links: Vec::new(), + time: Some(Utc::now()), + keywords: None, + bounds: None, + }) +} diff --git a/src/main.rs b/src/main.rs index f7bb202..d9d4a67 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ mod api; mod cmd; mod config; mod errors; +mod gpx; use api::{LocationModel, TagModel, API}; use chrono::offset::Local; @@ -36,11 +37,13 @@ fn from_env(env: EnvOpt) -> Result<(), GenericError> { None => None, }; - let locs = run(&config, opt.device, since, until)?; + let (tag, locs) = run(&config, opt.device, since, until)?; dbg!( &locs.iter().map(|loc| loc.0).collect::>(), locs.len(), ); + let gpx = gpx::generate_gpx(tag, locs, &config)?; + gpx::write_gpx(&gpx)?; Ok(()) } Subcommand::Init(opt) => { @@ -100,11 +103,12 @@ fn run( tag_str: String, from: Option, to: Option, -) -> Result, GenericError> { +) -> Result<(TagModel, Vec<(NaiveDateTime, LocationModel)>), GenericError> { let mut api = API::new(config.clone()); let tags = api.get_tags()?; - let tag_id = find_tag(tag_str, &tags, config)?.get_id()?; + let tag = find_tag(tag_str, &tags, config)?; + let tag_id = tag.get_id()?; print!("Preparing..\r"); std::io::stdout().lock().flush().ok(); @@ -152,7 +156,7 @@ fn run( locations.sort_by(|loc1, loc2| loc2.timestamp.cmp(&loc1.timestamp)); let locs = API::get_between(&locations, from, to, false, &config); - Ok(locs) + Ok((tag, locs)) } fn exp_time(api: &API, reqs_left: u64) -> Duration {