parsing continued

This commit is contained in:
vcoppe
2026-09-18 09:50:04 +02:00
parent 69049e1345
commit 20222c450d
4 changed files with 175 additions and 57 deletions
+14 -5
View File
@@ -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:gpxx="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_waypoint</name> <name>with_waypoint</name>
<author> <author>
@@ -13,10 +18,14 @@
</metadata> </metadata>
<wpt lat="50.7836710064975" lon="4.410764082658738"> <wpt lat="50.7836710064975" lon="4.410764082658738">
<ele>122.0</ele> <ele>122.0</ele>
<name>Waypoint</name> <name>waypoint name</name>
<cmt>Comment</cmt> <cmt>waypoint comment</cmt>
<desc>Description</desc> <desc>waypoint description</desc>
<link href="https://gpx.studio">
<text>waypoint link text</text>
</link>
<sym>Bike Trail</sym> <sym>Bike Trail</sym>
<type>Bike Trail</type>
</wpt> </wpt>
<trk> <trk>
<name>with_waypoint</name> <name>with_waypoint</name>
+140 -24
View File
@@ -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::Error;
use quick_xml::events::Event; use quick_xml::events::Event;
use quick_xml::events::attributes::Attributes;
use quick_xml::reader::Reader; use quick_xml::reader::Reader;
enum GPXElement { enum GPXElement {
@@ -15,14 +21,32 @@ enum GPXElement {
TRACK(Track), TRACK(Track),
SEGMENT(TrackSegment), SEGMENT(TrackSegment),
TRACKPOINT(TrackPoint), TRACKPOINT(TrackPoint),
WAYPOINT(Waypoint),
SYMBOL,
TYPE,
ELEVATION, 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<GPXFile, Error> { pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
let mut reader = Reader::from_reader(data); let mut reader = Reader::from_reader(data);
let mut buf = vec![]; let mut buf = vec![];
let mut gpx = GPXFile::default(); let mut gpx = GPXFile::default();
let mut stack: Vec<GPXElement> = vec![]; let mut stack: Vec<GPXElement> = vec![];
let mut trkpt_chunk = TrackPointChunk::default();
let mut wpt_chunk = WaypointChunk::default();
loop { loop {
match reader.read_event_into(&mut buf) { match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => match e.name().as_ref() { Ok(Event::Start(e)) => match e.name().as_ref() {
@@ -45,28 +69,31 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
} }
"text" => stack.push(GPXElement::TEXT), "text" => stack.push(GPXElement::TEXT),
"trk" => stack.push(GPXElement::TRACK(Track::default())), "trk" => stack.push(GPXElement::TRACK(Track::default())),
"trkseg" => stack.push(GPXElement::SEGMENT(TrackSegment::default())), "trkseg" => {
stack.push(GPXElement::SEGMENT(TrackSegment::default()));
}
"trkpt" => { "trkpt" => {
let mut trkpt = TrackPoint::default(); let mut trkpt = TrackPoint::default();
for attr in e.attributes() { trkpt.coordinates = parse_coordinates(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()
}
_ => (),
}
}
}
stack.push(GPXElement::TRACKPOINT(trkpt)); 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), "ele" => stack.push(GPXElement::ELEVATION),
"sym" => stack.push(GPXElement::SYMBOL),
"type" => stack.push(GPXElement::TYPE),
_ => println!("{:?}", e), _ => println!("{:?}", e),
}, },
Ok(Event::End(e)) => match e.name().as_ref() { 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" => { "metadata" => {
stack.pop(); stack.pop();
} }
@@ -84,6 +111,9 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
Some(GPXElement::TRACK(trk)) => { Some(GPXElement::TRACK(trk)) => {
trk.info.link = Some(link); trk.info.link = Some(link);
} }
Some(GPXElement::WAYPOINT(wpt)) => {
wpt.link = Some(link);
}
_ => (), _ => (),
} }
} }
@@ -94,8 +124,12 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
} }
} }
"trkseg" => { "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 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); trk.trkseg.push(trkseg);
} }
} }
@@ -103,7 +137,20 @@ pub fn parse(data: &[u8]) -> Result<GPXFile, Error> {
"trkpt" => { "trkpt" => {
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() { if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() {
if let Some(GPXElement::SEGMENT(trkseg)) = stack.last_mut() { 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<GPXFile, Error> {
Some(GPXElement::TRACK(trk)) => { Some(GPXElement::TRACK(trk)) => {
trk.info.name = Some(e.to_string()); 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<GPXFile, Error> {
Some(GPXElement::TRACK(trk)) => { Some(GPXElement::TRACK(trk)) => {
trk.info.cmt = Some(e.to_string()); 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<GPXFile, Error> {
Some(GPXElement::TRACK(trk)) => { Some(GPXElement::TRACK(trk)) => {
trk.info.desc = Some(e.to_string()); 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<GPXFile, Error> {
} }
Some(GPXElement::ELEVATION) => { Some(GPXElement::ELEVATION) => {
stack.pop(); stack.pop();
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() { match stack.last_mut() {
trkpt.ele = e.parse().unwrap_or_default(); 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::*; use super::*;
#[test] #[test]
fn test_parse() { fn test_parse_simple() {
let mut f = File::open("data/simple.gpx").unwrap(); let mut f = File::open("data/simple.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.info.name, "simple"); assert_eq!(gpx.info.name, "simple");
assert!(gpx.info.desc.is_some_and(|d| d == "description")); assert!(gpx.info.desc.is_some_and(|d| d == "description"));
@@ -210,22 +292,56 @@ mod tests {
trk.info trk.info
.desc .desc
.as_ref() .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()); assert!(trk.info.link.is_some());
let link = trk.info.link.as_ref().unwrap(); let link = trk.info.link.as_ref().unwrap();
assert_eq!(link.href, "https://gpx.studio"); assert_eq!(link.href, "https://gpx.studio");
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_eq!(gpx.trk[0].trkseg.len(), 1); assert_eq!(gpx.trk[0].trkseg.len(), 1);
let trkseg = &gpx.trk[0].trkseg[0]; let trkseg = &gpx.trk[0].trkseg[0];
assert_eq!(trkseg.chunks.len(), 1); assert_eq!(trkseg.chunks.len(), 1);
let chunk = trkseg.chunks[0].borrow(); let chunk = &trkseg.chunks[0];
assert_eq!(chunk.trkpt.len(), 80); assert_eq!(chunk.trkpt.len(), 80);
let trkpt = &chunk.trkpt[0]; let trkpt = &chunk.trkpt[0];
assert_eq!(trkpt.coordinates.lat, 50.790867); assert_eq!(trkpt.coordinates.lat, 50.790867);
assert_eq!(trkpt.coordinates.lng, 4.404968); assert_eq!(trkpt.coordinates.lng, 4.404968);
assert_eq!(trkpt.ele, 109.0); 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"));
}
} }
+8 -26
View File
@@ -1,34 +1,10 @@
use std::{cell::RefCell, rc::Rc}; use std::rc::Rc;
use crate::gpx::TrackPoint; use crate::gpx::TrackPoint;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TrackSegment { pub struct TrackSegment {
pub chunks: Vec<Rc<RefCell<TrackPointChunk>>>, pub chunks: Vec<Rc<TrackPointChunk>>,
}
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())));
}
} }
static MAX_CHUNK_SIZE: usize = 4096; static MAX_CHUNK_SIZE: usize = 4096;
@@ -37,3 +13,9 @@ static MAX_CHUNK_SIZE: usize = 4096;
pub struct TrackPointChunk { pub struct TrackPointChunk {
pub trkpt: Vec<TrackPoint>, pub trkpt: Vec<TrackPoint>,
} }
impl TrackPointChunk {
pub fn is_full(&self) -> bool {
self.trkpt.len() == MAX_CHUNK_SIZE
}
}
+13 -2
View File
@@ -1,7 +1,5 @@
use crate::gpx::{Link, LngLat}; use crate::gpx::{Link, LngLat};
pub type WaypointChunk = Vec<Waypoint>;
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Waypoint { pub struct Waypoint {
pub coordinates: LngLat, pub coordinates: LngLat,
@@ -14,3 +12,16 @@ pub struct Waypoint {
pub sym: Option<String>, pub sym: Option<String>,
pub type_: Option<String>, pub type_: Option<String>,
} }
static MAX_CHUNK_SIZE: usize = 128;
#[derive(Debug, Default)]
pub struct WaypointChunk {
pub wpt: Vec<Waypoint>,
}
impl WaypointChunk {
pub fn is_full(&self) -> bool {
self.wpt.len() == MAX_CHUNK_SIZE
}
}