mirror of
https://github.com/gpxstudio/gpx.studio.git
synced 2026-09-24 08:27:37 +00:00
progress
This commit is contained in:
Generated
+208
@@ -2,38 +2,175 @@
|
|||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
version = 4
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "android_system_properties"
|
||||||
|
version = "0.1.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "autocfg"
|
||||||
|
version = "1.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bumpalo"
|
name = "bumpalo"
|
||||||
version = "3.20.3"
|
version = "3.20.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cc"
|
||||||
|
version = "1.4.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4"
|
||||||
|
dependencies = [
|
||||||
|
"find-msvc-tools",
|
||||||
|
"shlex",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.4"
|
version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "chrono"
|
||||||
|
version = "0.4.45"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||||
|
dependencies = [
|
||||||
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
|
"num-traits",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "core-foundation-sys"
|
||||||
|
version = "0.8.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "find-msvc-tools"
|
||||||
|
version = "0.1.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-core"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-task"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "futures-util"
|
||||||
|
version = "0.3.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"futures-task",
|
||||||
|
"pin-project-lite",
|
||||||
|
"slab",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "gpx-rs"
|
name = "gpx-rs"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"chrono",
|
||||||
"quick-xml",
|
"quick-xml",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone"
|
||||||
|
version = "0.1.65"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||||
|
dependencies = [
|
||||||
|
"android_system_properties",
|
||||||
|
"core-foundation-sys",
|
||||||
|
"iana-time-zone-haiku",
|
||||||
|
"js-sys",
|
||||||
|
"log",
|
||||||
|
"wasm-bindgen",
|
||||||
|
"windows-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "iana-time-zone-haiku"
|
||||||
|
version = "0.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "js-sys"
|
||||||
|
version = "0.3.103"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102"
|
||||||
|
dependencies = [
|
||||||
|
"cfg-if",
|
||||||
|
"futures-util",
|
||||||
|
"wasm-bindgen",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "libc"
|
||||||
|
version = "0.2.189"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.34"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.8.3"
|
version = "2.8.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "num-traits"
|
||||||
|
version = "0.2.19"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||||
|
dependencies = [
|
||||||
|
"autocfg",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.21.4"
|
version = "1.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pin-project-lite"
|
||||||
|
version = "0.2.17"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.106"
|
version = "1.0.106"
|
||||||
@@ -67,6 +204,18 @@ version = "1.0.23"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "shlex"
|
||||||
|
version = "2.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "slab"
|
||||||
|
version = "0.4.12"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "2.0.118"
|
version = "2.0.118"
|
||||||
@@ -128,3 +277,62 @@ checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-core"
|
||||||
|
version = "0.62.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb"
|
||||||
|
dependencies = [
|
||||||
|
"windows-implement",
|
||||||
|
"windows-interface",
|
||||||
|
"windows-link",
|
||||||
|
"windows-result",
|
||||||
|
"windows-strings",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-implement"
|
||||||
|
version = "0.60.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-interface"
|
||||||
|
version = "0.59.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-link"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-result"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "windows-strings"
|
||||||
|
version = "0.5.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091"
|
||||||
|
dependencies = [
|
||||||
|
"windows-link",
|
||||||
|
]
|
||||||
|
|||||||
@@ -7,5 +7,6 @@ edition = "2024"
|
|||||||
crate-type = ["cdylib"]
|
crate-type = ["cdylib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = "0.4.45"
|
||||||
quick-xml = "0.42.0"
|
quick-xml = "0.42.0"
|
||||||
wasm-bindgen = "0.2"
|
wasm-bindgen = "0.2"
|
||||||
|
|||||||
@@ -1,9 +1,14 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
<gpx xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
xmlns="http://www.topografix.com/GPX/1/1" xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd"
|
xmlns="http://www.topografix.com/GPX/1/1"
|
||||||
|
xsi:schemaLocation="http://www.topografix.com/GPX/1/1 http://www.topografix.com/GPX/1/1/gpx.xsd
|
||||||
|
http://www.garmin.com/xmlschemas/GpxExtensions/v3 http://www.garmin.com/xmlschemas/GpxExtensionsv3.xsd
|
||||||
|
http://www.garmin.com/xmlschemas/TrackPointExtension/v1 http://www.garmin.com/xmlschemas/TrackPointExtensionv1.xsd
|
||||||
|
http://www.topografix.com/GPX/gpx_style/0/2 http://www.topografix.com/GPX/gpx_style/0/2/gpx_style.xsd"
|
||||||
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
xmlns:gpxtpx="http://www.garmin.com/xmlschemas/TrackPointExtension/v1"
|
||||||
xmlns:gpxx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
|
xmlns:gpxpx="http://www.garmin.com/xmlschemas/GpxExtensions/v3"
|
||||||
xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2" version="1.1" creator="https://gpx.studio">
|
xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2" version="1.1"
|
||||||
|
creator="https://gpx.studio">
|
||||||
<metadata>
|
<metadata>
|
||||||
<name>with_power</name>
|
<name>with_power</name>
|
||||||
<author>
|
<author>
|
||||||
|
|||||||
+327
-7
@@ -4,6 +4,7 @@ use crate::gpx::{
|
|||||||
Author, GPXFile, Link, LngLat, Track, TrackPoint, TrackPointChunk, TrackSegment, Waypoint,
|
Author, GPXFile, Link, LngLat, Track, TrackPoint, TrackPointChunk, TrackSegment, Waypoint,
|
||||||
WaypointChunk,
|
WaypointChunk,
|
||||||
};
|
};
|
||||||
|
use chrono::DateTime;
|
||||||
use quick_xml::Error;
|
use quick_xml::Error;
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
use quick_xml::events::attributes::Attributes;
|
use quick_xml::events::attributes::Attributes;
|
||||||
@@ -22,10 +23,19 @@ enum GPXElement {
|
|||||||
SEGMENT(TrackSegment),
|
SEGMENT(TrackSegment),
|
||||||
TRACKPOINT(TrackPoint),
|
TRACKPOINT(TrackPoint),
|
||||||
WAYPOINT(Waypoint),
|
WAYPOINT(Waypoint),
|
||||||
|
ELEVATION,
|
||||||
|
TIME,
|
||||||
|
TEMPERATURE,
|
||||||
|
HEARTRATE,
|
||||||
|
CADENCE,
|
||||||
|
POWER,
|
||||||
SYMBOL,
|
SYMBOL,
|
||||||
TYPE,
|
TYPE,
|
||||||
ELEVATION,
|
COLOR,
|
||||||
|
OPACITY,
|
||||||
|
WIDTH,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
|
fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
|
||||||
let mut coordinates = LngLat::default();
|
let mut coordinates = LngLat::default();
|
||||||
for attr in attributes {
|
for attr in attributes {
|
||||||
@@ -83,8 +93,17 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
stack.push(GPXElement::WAYPOINT(wpt));
|
stack.push(GPXElement::WAYPOINT(wpt));
|
||||||
}
|
}
|
||||||
"ele" => stack.push(GPXElement::ELEVATION),
|
"ele" => stack.push(GPXElement::ELEVATION),
|
||||||
|
"time" => stack.push(GPXElement::TIME),
|
||||||
|
e if e.ends_with("atemp") => stack.push(GPXElement::TEMPERATURE),
|
||||||
|
e if e.ends_with("hr") => stack.push(GPXElement::HEARTRATE),
|
||||||
|
e if e.ends_with("cad") => stack.push(GPXElement::CADENCE),
|
||||||
|
"power" => stack.push(GPXElement::POWER),
|
||||||
|
e if e.ends_with("PowerInWatts") => stack.push(GPXElement::POWER),
|
||||||
"sym" => stack.push(GPXElement::SYMBOL),
|
"sym" => stack.push(GPXElement::SYMBOL),
|
||||||
"type" => stack.push(GPXElement::TYPE),
|
"type" => stack.push(GPXElement::TYPE),
|
||||||
|
e if e.ends_with("color") => stack.push(GPXElement::COLOR),
|
||||||
|
e if e.ends_with("opacity") => stack.push(GPXElement::OPACITY),
|
||||||
|
e if e.ends_with("width") => stack.push(GPXElement::WIDTH),
|
||||||
_ => println!("{:?}", e),
|
_ => println!("{:?}", e),
|
||||||
},
|
},
|
||||||
Ok(Event::End(e)) => match e.name().as_ref() {
|
Ok(Event::End(e)) => match e.name().as_ref() {
|
||||||
@@ -229,6 +248,47 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Some(GPXElement::TIME) => {
|
||||||
|
stack.pop();
|
||||||
|
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
|
||||||
|
if let Ok(time) = DateTime::parse_from_rfc3339(e.as_ref()) {
|
||||||
|
trkpt.time = Some(time.timestamp_millis());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::TEMPERATURE) => {
|
||||||
|
stack.pop();
|
||||||
|
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
|
||||||
|
trkpt.atemp = e.parse().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::HEARTRATE) => {
|
||||||
|
stack.pop();
|
||||||
|
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
|
||||||
|
trkpt.hr = e.parse().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::CADENCE) => {
|
||||||
|
stack.pop();
|
||||||
|
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
|
||||||
|
trkpt.cad = e.parse().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::POWER) => {
|
||||||
|
stack.pop();
|
||||||
|
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
|
||||||
|
trkpt.power = e.parse().ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::SYMBOL) => {
|
||||||
|
stack.pop();
|
||||||
|
match stack.last_mut() {
|
||||||
|
Some(GPXElement::WAYPOINT(wpt)) => {
|
||||||
|
wpt.sym = Some(e.to_string());
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
Some(GPXElement::TYPE) => {
|
Some(GPXElement::TYPE) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
@@ -241,11 +301,31 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
|
|||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(GPXElement::SYMBOL) => {
|
Some(GPXElement::COLOR) => {
|
||||||
stack.pop();
|
stack.pop();
|
||||||
match stack.last_mut() {
|
match stack.last_mut() {
|
||||||
Some(GPXElement::WAYPOINT(wpt)) => {
|
Some(GPXElement::TRACK(trk)) => {
|
||||||
wpt.sym = Some(e.to_string());
|
let mut color = "#".to_string();
|
||||||
|
color.push_str(&e);
|
||||||
|
trk.info.color = Some(color);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::OPACITY) => {
|
||||||
|
stack.pop();
|
||||||
|
match stack.last_mut() {
|
||||||
|
Some(GPXElement::TRACK(trk)) => {
|
||||||
|
trk.info.opacity = e.parse().ok();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(GPXElement::WIDTH) => {
|
||||||
|
stack.pop();
|
||||||
|
match stack.last_mut() {
|
||||||
|
Some(GPXElement::TRACK(trk)) => {
|
||||||
|
trk.info.width = e.parse().ok();
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
@@ -301,8 +381,8 @@ mod tests {
|
|||||||
assert!(link.text.as_ref().is_some_and(|t| t == "track link text"));
|
assert!(link.text.as_ref().is_some_and(|t| t == "track link text"));
|
||||||
assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling"));
|
assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling"));
|
||||||
|
|
||||||
assert_eq!(gpx.trk[0].trkseg.len(), 1);
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
let trkseg = &gpx.trk[0].trkseg[0];
|
let trkseg = &trk.trkseg[0];
|
||||||
assert_eq!(trkseg.chunks.len(), 1);
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
let chunk = &trkseg.chunks[0];
|
let chunk = &trkseg.chunks[0];
|
||||||
assert_eq!(chunk.trkpt.len(), 80);
|
assert_eq!(chunk.trkpt.len(), 80);
|
||||||
@@ -312,13 +392,130 @@ mod tests {
|
|||||||
assert_eq!(trkpt.ele, 109.0);
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_tracks() {
|
||||||
|
let mut f = File::open("data/with_tracks.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 2);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert!(trk.info.name.as_ref().is_some_and(|n| n == "track 1"));
|
||||||
|
assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling"));
|
||||||
|
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 49);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
|
|
||||||
|
let trk = &gpx.trk[1];
|
||||||
|
assert!(trk.info.name.as_ref().is_some_and(|n| n == "track 2"));
|
||||||
|
assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling"));
|
||||||
|
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 28);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_segments() {
|
||||||
|
let mut f = File::open("data/with_segments.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
|
||||||
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 49);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
|
|
||||||
|
let trkseg = &trk.trkseg[1];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 28);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_tracks_and_segments() {
|
||||||
|
let mut f = File::open("data/with_tracks_and_segments.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 2);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
|
||||||
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 16);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.790867);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.404968);
|
||||||
|
assert_eq!(trkpt.ele, 109.0);
|
||||||
|
|
||||||
|
let trkseg = &trk.trkseg[1];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 34);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.78727108169855);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.406133681127736);
|
||||||
|
assert_eq!(trkpt.ele, 115.0);
|
||||||
|
|
||||||
|
let trk = &gpx.trk[1];
|
||||||
|
|
||||||
|
assert_eq!(trk.trkseg.len(), 2);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 19);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.782212);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.406377);
|
||||||
|
assert_eq!(trkpt.ele, 115.5);
|
||||||
|
|
||||||
|
let trkseg = &trk.trkseg[1];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert_eq!(chunk.trkpt.len(), 10);
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert_eq!(trkpt.coordinates.lat, 50.77906316558724);
|
||||||
|
assert_eq!(trkpt.coordinates.lng, 4.412547477922485);
|
||||||
|
assert_eq!(trkpt.ele, 133.3);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_waypoint() {
|
fn test_parse_waypoint() {
|
||||||
let mut f = File::open("data/with_waypoint.gpx").unwrap();
|
let mut f = File::open("data/with_waypoint.gpx").unwrap();
|
||||||
let mut data = String::new();
|
let mut data = String::new();
|
||||||
let _ = f.read_to_string(&mut data);
|
let _ = f.read_to_string(&mut data);
|
||||||
let gpx = parse(data.as_bytes()).unwrap();
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
println!("{:?}", gpx);
|
|
||||||
|
|
||||||
assert_eq!(gpx.wpt.len(), 1);
|
assert_eq!(gpx.wpt.len(), 1);
|
||||||
let chunk = &gpx.wpt[0];
|
let chunk = &gpx.wpt[0];
|
||||||
@@ -344,4 +541,127 @@ mod tests {
|
|||||||
assert!(wpt.sym.as_ref().is_some_and(|s| s == "Bike Trail"));
|
assert!(wpt.sym.as_ref().is_some_and(|s| s == "Bike Trail"));
|
||||||
assert!(wpt.type_.as_ref().is_some_and(|t| t == "Bike Trail"));
|
assert!(wpt.type_.as_ref().is_some_and(|t| t == "Bike Trail"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_time() {
|
||||||
|
let mut f = File::open("data/with_time.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.time.is_some_and(|t| t == 1704063600000));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_hr() {
|
||||||
|
let mut f = File::open("data/with_hr.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.hr.is_some_and(|h| h == 150));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_cad() {
|
||||||
|
let mut f = File::open("data/with_cad.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.cad.is_some_and(|c| c == 80));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_power_1() {
|
||||||
|
let mut f = File::open("data/with_power_1.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.power.is_some_and(|p| p == 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_power_2() {
|
||||||
|
let mut f = File::open("data/with_power_2.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.power.is_some_and(|p| p == 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_trackpoint_atemp() {
|
||||||
|
let mut f = File::open("data/with_temp.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
let trkseg = &trk.trkseg[0];
|
||||||
|
assert_eq!(trkseg.chunks.len(), 1);
|
||||||
|
let chunk = &trkseg.chunks[0];
|
||||||
|
assert!(!chunk.trkpt.is_empty());
|
||||||
|
let trkpt = &chunk.trkpt[0];
|
||||||
|
assert!(trkpt.atemp.is_some_and(|t| t == 21));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_track_style() {
|
||||||
|
let mut f = File::open("data/with_style.gpx").unwrap();
|
||||||
|
let mut data = String::new();
|
||||||
|
let _ = f.read_to_string(&mut data);
|
||||||
|
let gpx = parse(data.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(gpx.trk.len(), 1);
|
||||||
|
let trk = &gpx.trk[0];
|
||||||
|
assert_eq!(trk.trkseg.len(), 1);
|
||||||
|
assert!(trk.info.color.as_ref().is_some_and(|c| c == "#2d3ee9"));
|
||||||
|
assert!(trk.info.opacity.is_some_and(|o| o == 0.5));
|
||||||
|
assert!(trk.info.width.is_some_and(|w| w == 6.0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,3 +9,9 @@ pub struct LngLat {
|
|||||||
pub lng: f64,
|
pub lng: f64,
|
||||||
pub lat: f64,
|
pub lat: f64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct LngLatBounds {
|
||||||
|
pub sw: LngLat,
|
||||||
|
pub ne: LngLat,
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,9 +5,9 @@ pub struct TrackPoint {
|
|||||||
pub coordinates: LngLat,
|
pub coordinates: LngLat,
|
||||||
pub ele: f64,
|
pub ele: f64,
|
||||||
pub time: Option<i64>,
|
pub time: Option<i64>,
|
||||||
|
pub atemp: Option<i16>,
|
||||||
pub hr: Option<u16>,
|
pub hr: Option<u16>,
|
||||||
pub cad: Option<u16>,
|
pub cad: Option<u16>,
|
||||||
pub power: Option<u16>,
|
pub power: Option<u16>,
|
||||||
pub atemp: Option<i16>,
|
|
||||||
// TODO OSM data? or store intervals at a higher level?
|
// TODO OSM data? or store intervals at a higher level?
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,5 @@ mod actions;
|
|||||||
mod controller;
|
mod controller;
|
||||||
mod gpx;
|
mod gpx;
|
||||||
mod stack;
|
mod stack;
|
||||||
|
mod statistics;
|
||||||
|
mod utils;
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
use crate::{
|
||||||
|
gpx::{LngLat, LngLatBounds, TrackPoint, TrackPointChunk},
|
||||||
|
utils::{distance, speed},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct GPXStatistics {
|
||||||
|
pub total_distance: f64,
|
||||||
|
pub moving_distance: f64,
|
||||||
|
pub moving_time: i64,
|
||||||
|
pub elevation_gain: f64,
|
||||||
|
pub elevation_loss: f64,
|
||||||
|
pub start_time: Option<i64>,
|
||||||
|
pub end_time: Option<i64>,
|
||||||
|
pub bounds: LngLatBounds,
|
||||||
|
pub local: Vec<TrackpointStatistics>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GPXStatistics {
|
||||||
|
fn accumulate(&mut self, prev: &TrackPoint, cur: &TrackPoint) {
|
||||||
|
self.accumulate_distance_and_time(prev, cur);
|
||||||
|
self.update_time_bounds(cur.time);
|
||||||
|
self.update_bounds(&cur.coordinates);
|
||||||
|
self.local
|
||||||
|
.push(TrackpointStatistics::from_partial_stats(&self));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accumulate_distance_and_time(&mut self, prev: &TrackPoint, cur: &TrackPoint) {
|
||||||
|
let dist = distance(&prev.coordinates, &cur.coordinates);
|
||||||
|
let time = prev.time.zip(cur.time).map(|(t1, t2)| t2 - t1);
|
||||||
|
|
||||||
|
self.total_distance += dist;
|
||||||
|
|
||||||
|
if let Some(time) = time {
|
||||||
|
let speed = speed(dist, time);
|
||||||
|
if speed >= 0.5 && speed <= 1500.0 {
|
||||||
|
self.moving_distance += dist;
|
||||||
|
self.moving_time += time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_time_bounds(&mut self, time: Option<i64>) {
|
||||||
|
if let Some(time) = time {
|
||||||
|
if self.start_time.is_none() {
|
||||||
|
self.start_time = Some(time);
|
||||||
|
}
|
||||||
|
self.end_time = Some(time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_bounds(&mut self, coordinates: &LngLat) {
|
||||||
|
self.bounds.sw.lng = self.bounds.sw.lng.min(coordinates.lng);
|
||||||
|
self.bounds.sw.lat = self.bounds.sw.lat.min(coordinates.lat);
|
||||||
|
self.bounds.ne.lng = self.bounds.ne.lng.min(coordinates.lng);
|
||||||
|
self.bounds.ne.lat = self.bounds.ne.lat.min(coordinates.lat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct SlopeSegment {
|
||||||
|
pub slope: f64,
|
||||||
|
pub distance: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct TrackpointStatistics {
|
||||||
|
pub total_distance: f64,
|
||||||
|
pub moving_distance: f64,
|
||||||
|
pub total_time: i64,
|
||||||
|
pub moving_time: i64,
|
||||||
|
pub speed: f64,
|
||||||
|
pub elevation_gain: f64,
|
||||||
|
pub elevation_loss: f64,
|
||||||
|
pub slope: f64,
|
||||||
|
pub slope_segment: SlopeSegment,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TrackpointStatistics {
|
||||||
|
fn from_partial_stats(stats: &GPXStatistics) -> Self {
|
||||||
|
Self {
|
||||||
|
total_distance: stats.total_distance,
|
||||||
|
moving_distance: stats.moving_distance,
|
||||||
|
total_time: stats
|
||||||
|
.start_time
|
||||||
|
.zip(stats.end_time)
|
||||||
|
.map_or(0, |(t1, t2)| t2 - t1),
|
||||||
|
moving_time: stats.moving_time,
|
||||||
|
speed: todo!(),
|
||||||
|
elevation_gain: stats.elevation_gain,
|
||||||
|
elevation_loss: stats.elevation_loss,
|
||||||
|
slope: Default::default(),
|
||||||
|
slope_segment: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GPXStatistics {
|
||||||
|
pub fn compute(chunk: &TrackPointChunk) -> Self {
|
||||||
|
let mut stats = Self::default();
|
||||||
|
if chunk.trkpt.is_empty() {
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut prev = &chunk.trkpt[0];
|
||||||
|
for i in 0..chunk.trkpt.len() {
|
||||||
|
let cur = &chunk.trkpt[i];
|
||||||
|
stats.accumulate(prev, cur);
|
||||||
|
prev = cur;
|
||||||
|
}
|
||||||
|
stats
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
use std::f64::consts::PI;
|
||||||
|
|
||||||
|
use crate::gpx::LngLat;
|
||||||
|
|
||||||
|
static TO_RADIANS: f64 = PI / 180.0;
|
||||||
|
static EARTH_RADIUS: f64 = 6371.0088;
|
||||||
|
|
||||||
|
/// Computes the distance in kilometers between two coordinates using the Haversine formula
|
||||||
|
pub fn distance(coord1: &LngLat, coord2: &LngLat) -> f64 {
|
||||||
|
let lat1 = coord1.lat * TO_RADIANS;
|
||||||
|
let lat2 = coord2.lat * TO_RADIANS;
|
||||||
|
let delta_lat = lat2 - lat1;
|
||||||
|
let delta_lng = (coord2.lng - coord1.lng) * TO_RADIANS;
|
||||||
|
|
||||||
|
let a =
|
||||||
|
(delta_lat / 2.0).sin().powi(2) + lat1.cos() * lat2.cos() * (delta_lng / 2.0).sin().powi(2);
|
||||||
|
let c = 2.0 * a.min(1.0).sqrt().asin();
|
||||||
|
EARTH_RADIUS * c
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Computes the speed for a given distance in kilometers and a time in milliseconds
|
||||||
|
pub fn speed(distance: f64, time: i64) -> f64 {
|
||||||
|
distance / (time as f64 / 3600_000.0)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user