Files
gpx.studio/gpx-rs/src/io/parse.rs
T

638 lines
24 KiB
Rust
Raw Normal View History

2026-09-18 09:50:04 +02:00
use std::rc::Rc;
use crate::gpx::{
2026-09-23 19:53:39 +02:00
Author, GPXFile, Link, LngLat, Track, TrackSegment, Trackpoint, TrackpointChunk, Waypoint,
2026-09-18 09:50:04 +02:00
WaypointChunk,
};
2026-09-20 13:58:27 +02:00
use chrono::DateTime;
2026-09-18 09:03:51 +02:00
use quick_xml::Error;
use quick_xml::events::Event;
2026-09-18 09:50:04 +02:00
use quick_xml::events::attributes::Attributes;
2026-09-18 09:03:51 +02:00
use quick_xml::reader::Reader;
enum GPXElement {
2026-09-24 20:55:30 +02:00
Metadata,
Name,
Comment,
Description,
Source,
Author(Author),
Link(Link),
Text,
Track(Track),
Segment(TrackSegment),
Trackpoint(Trackpoint),
Waypoint(Waypoint),
Elevation,
Time,
Temperature,
Heartrate,
Cadence,
Power,
Symbol,
Type,
Color,
Opacity,
Width,
2026-09-18 09:03:51 +02:00
}
2026-09-20 13:58:27 +02:00
2026-09-18 09:50:04 +02:00
fn parse_coordinates(attributes: Attributes<'_>) -> LngLat {
let mut coordinates = LngLat::default();
for attr in attributes {
if let Ok(attr) = attr {
match attr.key.as_ref() {
"lat" => coordinates.lat = attr.value.parse().unwrap_or_default(),
"lon" => coordinates.lng = attr.value.parse().unwrap_or_default(),
_ => (),
}
}
}
coordinates
}
2026-09-18 09:03:51 +02:00
pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
let mut reader = Reader::from_reader(data);
let mut buf = vec![];
let mut gpx = GPXFile::default();
let mut stack: Vec<GPXElement> = vec![];
2026-09-23 19:53:39 +02:00
let mut trkpt_chunk = TrackpointChunk::default();
2026-09-18 09:50:04 +02:00
let mut wpt_chunk = WaypointChunk::default();
2026-09-18 09:03:51 +02:00
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => match e.name().as_ref() {
2026-09-24 20:55:30 +02:00
"metadata" => stack.push(GPXElement::Metadata),
"name" => stack.push(GPXElement::Name),
"cmt" => stack.push(GPXElement::Comment),
"desc" => stack.push(GPXElement::Description),
"src" => stack.push(GPXElement::Source),
"author" => stack.push(GPXElement::Author(Author::default())),
2026-09-18 09:03:51 +02:00
"link" => {
let mut link = Link::default();
for attr in e.attributes() {
if let Ok(attr) = attr {
if attr.key.as_ref() == "href" {
link.href = attr.value.to_string();
}
}
}
2026-09-24 20:55:30 +02:00
stack.push(GPXElement::Link(link));
2026-09-18 09:03:51 +02:00
}
2026-09-24 20:55:30 +02:00
"text" => stack.push(GPXElement::Text),
"trk" => stack.push(GPXElement::Track(Track::default())),
2026-09-18 09:50:04 +02:00
"trkseg" => {
2026-09-24 20:55:30 +02:00
stack.push(GPXElement::Segment(TrackSegment::default()));
2026-09-18 09:50:04 +02:00
}
2026-09-18 09:03:51 +02:00
"trkpt" => {
2026-09-23 19:53:39 +02:00
let mut trkpt = Trackpoint::default();
2026-09-18 09:50:04 +02:00
trkpt.coordinates = parse_coordinates(e.attributes());
2026-09-24 20:55:30 +02:00
stack.push(GPXElement::Trackpoint(trkpt));
2026-09-18 09:03:51 +02:00
}
2026-09-18 09:50:04 +02:00
"wpt" => {
let mut wpt = Waypoint::default();
wpt.coordinates = parse_coordinates(e.attributes());
2026-09-24 20:55:30 +02:00
stack.push(GPXElement::Waypoint(wpt));
2026-09-18 09:50:04 +02:00
}
2026-09-24 20:55:30 +02:00
"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),
"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),
2026-09-23 19:53:39 +02:00
_ => (),
2026-09-18 09:03:51 +02:00
},
Ok(Event::End(e)) => match e.name().as_ref() {
2026-09-18 09:50:04 +02:00
"gpx" => {
if !wpt_chunk.wpt.is_empty() {
gpx.wpt.push(Rc::new(wpt_chunk));
wpt_chunk = WaypointChunk::default();
}
}
2026-09-18 09:03:51 +02:00
"metadata" => {
stack.pop();
}
"author" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Author(author)) = stack.pop() {
2026-09-18 09:03:51 +02:00
gpx.info.author = Some(author);
}
}
"link" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Link(link)) = stack.pop() {
2026-09-18 09:03:51 +02:00
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Author(author)) => {
2026-09-18 09:03:51 +02:00
author.link = Some(link);
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:03:51 +02:00
trk.info.link = Some(link);
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.link = Some(link);
}
2026-09-18 09:03:51 +02:00
_ => (),
}
}
}
"trk" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Track(trk)) = stack.pop() {
2026-09-18 09:03:51 +02:00
gpx.trk.push(trk);
}
}
"trkseg" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Segment(mut trkseg)) = stack.pop() {
if let Some(GPXElement::Track(trk)) = stack.last_mut() {
2026-09-18 09:50:04 +02:00
if !trkpt_chunk.trkpt.is_empty() {
2026-09-23 19:53:39 +02:00
trkseg.push(trkpt_chunk);
trkpt_chunk = TrackpointChunk::default();
2026-09-18 09:50:04 +02:00
}
2026-09-18 09:03:51 +02:00
trk.trkseg.push(trkseg);
}
}
}
"trkpt" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.pop() {
if let Some(GPXElement::Segment(trkseg)) = stack.last_mut() {
2026-09-18 09:50:04 +02:00
trkpt_chunk.trkpt.push(trkpt);
if trkpt_chunk.is_full() {
2026-09-23 19:53:39 +02:00
trkseg.push(trkpt_chunk);
trkpt_chunk = TrackpointChunk::default();
2026-09-18 09:50:04 +02:00
}
}
}
}
"wpt" => {
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Waypoint(wpt)) = stack.pop() {
2026-09-18 09:50:04 +02:00
wpt_chunk.wpt.push(wpt);
if wpt_chunk.is_full() {
gpx.wpt.push(Rc::new(wpt_chunk));
wpt_chunk = WaypointChunk::default();
2026-09-18 09:03:51 +02:00
}
}
}
_ => (),
},
Ok(Event::Text(e)) => match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Name) => {
2026-09-18 09:03:51 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Metadata) => {
2026-09-18 09:03:51 +02:00
gpx.info.name = e.to_string();
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Author(author)) => {
2026-09-18 09:03:51 +02:00
author.name = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:03:51 +02:00
trk.info.name = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.name = Some(e.to_string());
}
2026-09-18 09:03:51 +02:00
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Comment) => {
2026-09-18 09:03:51 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:03:51 +02:00
trk.info.cmt = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.cmt = Some(e.to_string());
}
2026-09-18 09:03:51 +02:00
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Description) => {
2026-09-18 09:03:51 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Metadata) => {
2026-09-18 09:03:51 +02:00
gpx.info.desc = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:03:51 +02:00
trk.info.desc = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.desc = Some(e.to_string());
}
2026-09-18 09:03:51 +02:00
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Source) => {
2026-09-18 09:03:51 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:03:51 +02:00
trk.info.src = Some(e.to_string());
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Text) => {
2026-09-18 09:03:51 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Link(link)) = stack.last_mut() {
2026-09-18 09:03:51 +02:00
link.text = Some(e.to_string());
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Elevation) => {
2026-09-18 09:03:51 +02:00
stack.pop();
2026-09-18 09:50:04 +02:00
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Trackpoint(trkpt)) => {
2026-09-18 09:50:04 +02:00
trkpt.ele = e.parse().unwrap_or_default();
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.ele = e.parse().unwrap_or_default();
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Time) => {
2026-09-20 13:58:27 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() {
2026-09-20 13:58:27 +02:00
if let Ok(time) = DateTime::parse_from_rfc3339(e.as_ref()) {
trkpt.time = Some(time.timestamp_millis());
}
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Temperature) => {
2026-09-20 13:58:27 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() {
2026-09-20 13:58:27 +02:00
trkpt.atemp = e.parse().ok();
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Heartrate) => {
2026-09-20 13:58:27 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() {
2026-09-20 13:58:27 +02:00
trkpt.hr = e.parse().ok();
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Cadence) => {
2026-09-20 13:58:27 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() {
2026-09-20 13:58:27 +02:00
trkpt.cad = e.parse().ok();
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Power) => {
2026-09-20 13:58:27 +02:00
stack.pop();
2026-09-24 20:55:30 +02:00
if let Some(GPXElement::Trackpoint(trkpt)) = stack.last_mut() {
2026-09-20 13:58:27 +02:00
trkpt.power = e.parse().ok();
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Symbol) => {
2026-09-20 13:58:27 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-20 13:58:27 +02:00
wpt.sym = Some(e.to_string());
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Type) => {
2026-09-18 09:50:04 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-18 09:50:04 +02:00
trk.info.type_ = Some(e.to_string());
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Waypoint(wpt)) => {
2026-09-18 09:50:04 +02:00
wpt.type_ = Some(e.to_string());
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Color) => {
2026-09-18 09:50:04 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-20 13:58:27 +02:00
let mut color = "#".to_string();
color.push_str(&e);
trk.info.color = Some(color);
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Opacity) => {
2026-09-20 13:58:27 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-20 13:58:27 +02:00
trk.info.opacity = e.parse().ok();
}
_ => (),
}
}
2026-09-24 20:55:30 +02:00
Some(GPXElement::Width) => {
2026-09-20 13:58:27 +02:00
stack.pop();
match stack.last_mut() {
2026-09-24 20:55:30 +02:00
Some(GPXElement::Track(trk)) => {
2026-09-20 13:58:27 +02:00
trk.info.width = e.parse().ok();
2026-09-18 09:50:04 +02:00
}
_ => (),
2026-09-18 09:03:51 +02:00
}
}
_ => (),
},
Ok(Event::Eof) => break,
Err(e) => return Err(e),
_ => (),
}
buf.clear();
}
Ok(gpx)
}
#[cfg(test)]
mod tests {
use std::{fs::File, io::Read};
use super::*;
#[test]
2026-09-18 09:50:04 +02:00
fn test_parse_simple() {
2026-09-18 09:03:51 +02:00
let mut f = File::open("data/simple.gpx").unwrap();
let mut data = String::new();
let _ = f.read_to_string(&mut data);
let gpx = parse(data.as_bytes()).unwrap();
assert_eq!(gpx.info.name, "simple");
assert!(gpx.info.desc.is_some_and(|d| d == "description"));
assert!(gpx.info.author.is_some());
let author = gpx.info.author.as_ref().unwrap();
assert!(author.name.as_ref().is_some_and(|n| n == "gpx.studio"));
assert!(author.link.is_some());
let link = author.link.as_ref().unwrap();
assert_eq!(link.href, "https://gpx.studio");
assert!(link.text.as_ref().is_some_and(|t| t == "link text"));
assert_eq!(gpx.trk.len(), 1);
let trk = &gpx.trk[0];
assert!(trk.info.name.as_ref().is_some_and(|n| n == "track name"));
assert!(trk.info.cmt.as_ref().is_some_and(|c| c == "track comment"));
assert!(
trk.info
.desc
.as_ref()
2026-09-18 09:50:04 +02:00
.is_some_and(|d| d == "track description")
2026-09-18 09:03:51 +02:00
);
2026-09-18 09:50:04 +02:00
assert!(trk.info.src.as_ref().is_some_and(|s| s == "track source"));
2026-09-18 09:03:51 +02:00
assert!(trk.info.link.is_some());
let link = trk.info.link.as_ref().unwrap();
assert_eq!(link.href, "https://gpx.studio");
assert!(link.text.as_ref().is_some_and(|t| t == "track link text"));
2026-09-18 09:50:04 +02:00
assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling"));
2026-09-18 09:03:51 +02:00
2026-09-20 13:58:27 +02:00
assert_eq!(trk.trkseg.len(), 1);
let trkseg = &trk.trkseg[0];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 80);
let trkpt = &trkseg[0];
2026-09-18 09:03:51 +02:00
assert_eq!(trkpt.coordinates.lat, 50.790867);
assert_eq!(trkpt.coordinates.lng, 4.404968);
assert_eq!(trkpt.ele, 109.0);
}
2026-09-18 09:50:04 +02:00
2026-09-20 13:58:27 +02:00
#[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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 49);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 28);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 49);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 28);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 16);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 34);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 19);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert_eq!(trkseg.len(), 10);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
assert_eq!(trkpt.coordinates.lat, 50.77906316558724);
assert_eq!(trkpt.coordinates.lng, 4.412547477922485);
assert_eq!(trkpt.ele, 133.3);
}
2026-09-18 09:50:04 +02:00
#[test]
fn test_parse_waypoint() {
let mut f = File::open("data/with_waypoint.gpx").unwrap();
let mut data = String::new();
let _ = f.read_to_string(&mut data);
let gpx = parse(data.as_bytes()).unwrap();
assert_eq!(gpx.wpt.len(), 1);
let chunk = &gpx.wpt[0];
assert_eq!(chunk.wpt.len(), 1);
let wpt = &chunk.wpt[0];
assert_eq!(wpt.coordinates.lat, 50.7836710064975);
assert_eq!(wpt.coordinates.lng, 4.410764082658738);
assert!(wpt.name.as_ref().is_some_and(|n| n == "waypoint name"));
assert!(wpt.cmt.as_ref().is_some_and(|c| c == "waypoint comment"));
assert!(
wpt.desc
.as_ref()
.is_some_and(|d| d == "waypoint description")
);
assert!(wpt.link.is_some());
let link = wpt.link.as_ref().unwrap();
assert_eq!(link.href, "https://gpx.studio");
assert!(
link.text
.as_ref()
.is_some_and(|t| t == "waypoint link text")
);
assert!(wpt.sym.as_ref().is_some_and(|s| s == "Bike Trail"));
assert!(wpt.type_.as_ref().is_some_and(|t| t == "Bike Trail"));
}
2026-09-20 13:58:27 +02:00
#[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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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];
2026-09-23 19:53:39 +02:00
assert!(trkseg.len() > 0);
let trkpt = &trkseg[0];
2026-09-20 13:58:27 +02:00
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));
}
2026-09-18 09:03:51 +02:00
}