diff --git a/gpx-rs/data/with_waypoint.gpx b/gpx-rs/data/with_waypoint.gpx index c908b0515..8fb47aa8b 100644 --- a/gpx-rs/data/with_waypoint.gpx +++ b/gpx-rs/data/with_waypoint.gpx @@ -1,9 +1,14 @@ + xmlns:gpx_style="http://www.topografix.com/GPX/gpx_style/0/2" version="1.1" + creator="https://gpx.studio"> with_waypoint @@ -13,10 +18,14 @@ 122.0 - Waypoint - Comment - Description + waypoint name + waypoint comment + waypoint description + + waypoint link text + Bike Trail + Bike Trail with_waypoint diff --git a/gpx-rs/src/actions/parse.rs b/gpx-rs/src/actions/parse.rs index 143b5d0c0..d8a9a614b 100644 --- a/gpx-rs/src/actions/parse.rs +++ b/gpx-rs/src/actions/parse.rs @@ -1,6 +1,12 @@ -use crate::gpx::{Author, GPXFile, Link, Track, TrackPoint, TrackSegment}; +use std::rc::Rc; + +use crate::gpx::{ + Author, GPXFile, Link, LngLat, Track, TrackPoint, TrackPointChunk, TrackSegment, Waypoint, + WaypointChunk, +}; use quick_xml::Error; use quick_xml::events::Event; +use quick_xml::events::attributes::Attributes; use quick_xml::reader::Reader; enum GPXElement { @@ -15,14 +21,32 @@ enum GPXElement { TRACK(Track), SEGMENT(TrackSegment), TRACKPOINT(TrackPoint), + WAYPOINT(Waypoint), + SYMBOL, + TYPE, ELEVATION, } +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 +} pub fn parse(data: &[u8]) -> Result { let mut reader = Reader::from_reader(data); let mut buf = vec![]; let mut gpx = GPXFile::default(); let mut stack: Vec = vec![]; + let mut trkpt_chunk = TrackPointChunk::default(); + let mut wpt_chunk = WaypointChunk::default(); loop { match reader.read_event_into(&mut buf) { Ok(Event::Start(e)) => match e.name().as_ref() { @@ -45,28 +69,31 @@ pub fn parse(data: &[u8]) -> Result { } "text" => stack.push(GPXElement::TEXT), "trk" => stack.push(GPXElement::TRACK(Track::default())), - "trkseg" => stack.push(GPXElement::SEGMENT(TrackSegment::default())), + "trkseg" => { + stack.push(GPXElement::SEGMENT(TrackSegment::default())); + } "trkpt" => { let mut trkpt = TrackPoint::default(); - for attr in e.attributes() { - if let Ok(attr) = attr { - match attr.key.as_ref() { - "lat" => { - trkpt.coordinates.lat = attr.value.parse().unwrap_or_default() - } - "lon" => { - trkpt.coordinates.lng = attr.value.parse().unwrap_or_default() - } - _ => (), - } - } - } + trkpt.coordinates = parse_coordinates(e.attributes()); stack.push(GPXElement::TRACKPOINT(trkpt)); } + "wpt" => { + let mut wpt = Waypoint::default(); + wpt.coordinates = parse_coordinates(e.attributes()); + stack.push(GPXElement::WAYPOINT(wpt)); + } "ele" => stack.push(GPXElement::ELEVATION), + "sym" => stack.push(GPXElement::SYMBOL), + "type" => stack.push(GPXElement::TYPE), _ => println!("{:?}", e), }, Ok(Event::End(e)) => match e.name().as_ref() { + "gpx" => { + if !wpt_chunk.wpt.is_empty() { + gpx.wpt.push(Rc::new(wpt_chunk)); + wpt_chunk = WaypointChunk::default(); + } + } "metadata" => { stack.pop(); } @@ -84,6 +111,9 @@ pub fn parse(data: &[u8]) -> Result { Some(GPXElement::TRACK(trk)) => { trk.info.link = Some(link); } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.link = Some(link); + } _ => (), } } @@ -94,8 +124,12 @@ pub fn parse(data: &[u8]) -> Result { } } "trkseg" => { - if let Some(GPXElement::SEGMENT(trkseg)) = stack.pop() { + if let Some(GPXElement::SEGMENT(mut trkseg)) = stack.pop() { if let Some(GPXElement::TRACK(trk)) = stack.last_mut() { + if !trkpt_chunk.trkpt.is_empty() { + trkseg.chunks.push(Rc::new(trkpt_chunk)); + trkpt_chunk = TrackPointChunk::default(); + } trk.trkseg.push(trkseg); } } @@ -103,7 +137,20 @@ pub fn parse(data: &[u8]) -> Result { "trkpt" => { if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() { if let Some(GPXElement::SEGMENT(trkseg)) = stack.last_mut() { - trkseg.append(trkpt); + trkpt_chunk.trkpt.push(trkpt); + if trkpt_chunk.is_full() { + trkseg.chunks.push(Rc::new(trkpt_chunk)); + trkpt_chunk = TrackPointChunk::default(); + } + } + } + } + "wpt" => { + if let Some(GPXElement::WAYPOINT(wpt)) = stack.pop() { + wpt_chunk.wpt.push(wpt); + if wpt_chunk.is_full() { + gpx.wpt.push(Rc::new(wpt_chunk)); + wpt_chunk = WaypointChunk::default(); } } } @@ -122,6 +169,9 @@ pub fn parse(data: &[u8]) -> Result { Some(GPXElement::TRACK(trk)) => { trk.info.name = Some(e.to_string()); } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.name = Some(e.to_string()); + } _ => (), } } @@ -131,6 +181,9 @@ pub fn parse(data: &[u8]) -> Result { Some(GPXElement::TRACK(trk)) => { trk.info.cmt = Some(e.to_string()); } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.cmt = Some(e.to_string()); + } _ => (), } } @@ -143,6 +196,9 @@ pub fn parse(data: &[u8]) -> Result { Some(GPXElement::TRACK(trk)) => { trk.info.desc = Some(e.to_string()); } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.desc = Some(e.to_string()); + } _ => (), } } @@ -163,8 +219,35 @@ pub fn parse(data: &[u8]) -> Result { } Some(GPXElement::ELEVATION) => { stack.pop(); - if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { - trkpt.ele = e.parse().unwrap_or_default(); + match stack.last_mut() { + Some(GPXElement::TRACKPOINT(trkpt)) => { + trkpt.ele = e.parse().unwrap_or_default(); + } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.ele = e.parse().unwrap_or_default(); + } + _ => (), + } + } + Some(GPXElement::TYPE) => { + stack.pop(); + match stack.last_mut() { + Some(GPXElement::TRACK(trk)) => { + trk.info.type_ = Some(e.to_string()); + } + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.type_ = Some(e.to_string()); + } + _ => (), + } + } + Some(GPXElement::SYMBOL) => { + stack.pop(); + match stack.last_mut() { + Some(GPXElement::WAYPOINT(wpt)) => { + wpt.sym = Some(e.to_string()); + } + _ => (), } } _ => (), @@ -185,12 +268,11 @@ mod tests { use super::*; #[test] - fn test_parse() { + fn test_parse_simple() { 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(); - println!("{:?}", gpx); assert_eq!(gpx.info.name, "simple"); assert!(gpx.info.desc.is_some_and(|d| d == "description")); @@ -210,22 +292,56 @@ mod tests { trk.info .desc .as_ref() - .is_some_and(|n| n == "track description") + .is_some_and(|d| d == "track description") ); - assert!(trk.info.src.as_ref().is_some_and(|n| n == "track source")); + assert!(trk.info.src.as_ref().is_some_and(|s| s == "track source")); 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")); + assert!(trk.info.type_.as_ref().is_some_and(|c| c == "Cycling")); assert_eq!(gpx.trk[0].trkseg.len(), 1); let trkseg = &gpx.trk[0].trkseg[0]; assert_eq!(trkseg.chunks.len(), 1); - let chunk = trkseg.chunks[0].borrow(); + let chunk = &trkseg.chunks[0]; assert_eq!(chunk.trkpt.len(), 80); 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); } + + #[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(); + println!("{:?}", gpx); + + 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")); + } } diff --git a/gpx-rs/src/gpx/segment.rs b/gpx-rs/src/gpx/segment.rs index a832d489d..770ee4c99 100644 --- a/gpx-rs/src/gpx/segment.rs +++ b/gpx-rs/src/gpx/segment.rs @@ -1,34 +1,10 @@ -use std::{cell::RefCell, rc::Rc}; +use std::rc::Rc; use crate::gpx::TrackPoint; #[derive(Debug, Default)] pub struct TrackSegment { - pub chunks: Vec>>, -} - -impl TrackSegment { - pub fn append(&mut self, trkpt: TrackPoint) { - if self - .chunks - .last() - .is_none_or(|c| c.borrow().trkpt.len() == MAX_CHUNK_SIZE) - { - self.add_chunk(); - } - - self.chunks - .last_mut() - .unwrap() - .borrow_mut() - .trkpt - .push(trkpt); - } - - fn add_chunk(&mut self) { - self.chunks - .push(Rc::new(RefCell::new(TrackPointChunk::default()))); - } + pub chunks: Vec>, } static MAX_CHUNK_SIZE: usize = 4096; @@ -37,3 +13,9 @@ static MAX_CHUNK_SIZE: usize = 4096; pub struct TrackPointChunk { pub trkpt: Vec, } + +impl TrackPointChunk { + pub fn is_full(&self) -> bool { + self.trkpt.len() == MAX_CHUNK_SIZE + } +} diff --git a/gpx-rs/src/gpx/waypoint.rs b/gpx-rs/src/gpx/waypoint.rs index 62fd4e38d..5531507dd 100644 --- a/gpx-rs/src/gpx/waypoint.rs +++ b/gpx-rs/src/gpx/waypoint.rs @@ -1,7 +1,5 @@ use crate::gpx::{Link, LngLat}; -pub type WaypointChunk = Vec; - #[derive(Debug, Default)] pub struct Waypoint { pub coordinates: LngLat, @@ -14,3 +12,16 @@ pub struct Waypoint { pub sym: Option, pub type_: Option, } + +static MAX_CHUNK_SIZE: usize = 128; + +#[derive(Debug, Default)] +pub struct WaypointChunk { + pub wpt: Vec, +} + +impl WaypointChunk { + pub fn is_full(&self) -> bool { + self.wpt.len() == MAX_CHUNK_SIZE + } +}