parsing start

This commit is contained in:
vcoppe committed 2026-09-18 09:03:51 +02:00
1 parent ed6264444f
commit 69049e1345
29 files changed
+6622 -119

No files matched your search

+3
View File
@@ -0,0 +1,3 @@
mod parse;
pub use parse::*;
+231
View File
@@ -0,0 +1,231 @@
use crate::gpx::{Author, GPXFile, Link, Track, TrackPoint, TrackSegment};
use quick_xml::Error;
use quick_xml::events::Event;
use quick_xml::reader::Reader;
enum GPXElement {
METADATA,
NAME,
COMMENT,
DESCRIPTION,
SOURCE,
AUTHOR(Author),
LINK(Link),
TEXT,
TRACK(Track),
SEGMENT(TrackSegment),
TRACKPOINT(TrackPoint),
ELEVATION,
}
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![];
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => match e.name().as_ref() {
"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())),
"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();
}
}
}
stack.push(GPXElement::LINK(link));
}
"text" => stack.push(GPXElement::TEXT),
"trk" => stack.push(GPXElement::TRACK(Track::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()
}
_ => (),
}
}
}
stack.push(GPXElement::TRACKPOINT(trkpt));
}
"ele" => stack.push(GPXElement::ELEVATION),
_ => println!("{:?}", e),
},
Ok(Event::End(e)) => match e.name().as_ref() {
"metadata" => {
stack.pop();
}
"author" => {
if let Some(GPXElement::AUTHOR(author)) = stack.pop() {
gpx.info.author = Some(author);
}
}
"link" => {
if let Some(GPXElement::LINK(link)) = stack.pop() {
match stack.last_mut() {
Some(GPXElement::AUTHOR(author)) => {
author.link = Some(link);
}
Some(GPXElement::TRACK(trk)) => {
trk.info.link = Some(link);
}
_ => (),
}
}
}
"trk" => {
if let Some(GPXElement::TRACK(trk)) = stack.pop() {
gpx.trk.push(trk);
}
}
"trkseg" => {
if let Some(GPXElement::SEGMENT(trkseg)) = stack.pop() {
if let Some(GPXElement::TRACK(trk)) = stack.last_mut() {
trk.trkseg.push(trkseg);
}
}
}
"trkpt" => {
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.pop() {
if let Some(GPXElement::SEGMENT(trkseg)) = stack.last_mut() {
trkseg.append(trkpt);
}
}
}
_ => (),
},
Ok(Event::Text(e)) => match stack.last_mut() {
Some(GPXElement::NAME) => {
stack.pop();
match stack.last_mut() {
Some(GPXElement::METADATA) => {
gpx.info.name = e.to_string();
}
Some(GPXElement::AUTHOR(author)) => {
author.name = Some(e.to_string());
}
Some(GPXElement::TRACK(trk)) => {
trk.info.name = Some(e.to_string());
}
_ => (),
}
}
Some(GPXElement::COMMENT) => {
stack.pop();
match stack.last_mut() {
Some(GPXElement::TRACK(trk)) => {
trk.info.cmt = Some(e.to_string());
}
_ => (),
}
}
Some(GPXElement::DESCRIPTION) => {
stack.pop();
match stack.last_mut() {
Some(GPXElement::METADATA) => {
gpx.info.desc = Some(e.to_string());
}
Some(GPXElement::TRACK(trk)) => {
trk.info.desc = Some(e.to_string());
}
_ => (),
}
}
Some(GPXElement::SOURCE) => {
stack.pop();
match stack.last_mut() {
Some(GPXElement::TRACK(trk)) => {
trk.info.src = Some(e.to_string());
}
_ => (),
}
}
Some(GPXElement::TEXT) => {
stack.pop();
if let Some(GPXElement::LINK(link)) = stack.last_mut() {
link.text = Some(e.to_string());
}
}
Some(GPXElement::ELEVATION) => {
stack.pop();
if let Some(GPXElement::TRACKPOINT(trkpt)) = stack.last_mut() {
trkpt.ele = e.parse().unwrap_or_default();
}
}
_ => (),
},
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]
fn test_parse() {
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"));
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()
.is_some_and(|n| n == "track description")
);
assert!(trk.info.src.as_ref().is_some_and(|n| n == "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_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();
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);
}
}
+7 -7
View File
@@ -2,7 +2,7 @@ use std::rc::Rc;
use wasm_bindgen::prelude::*;
use crate::{stack::Stack, types::GPXFile};
use crate::stack::Stack;
#[wasm_bindgen]
pub struct Controller {
@@ -18,10 +18,10 @@ impl Controller {
}
}
#[wasm_bindgen]
pub fn create_file(&mut self, name: &str) {
let id = self.stack.get_new_file_id();
let file = Rc::new(GPXFile::new(id, name));
self.stack.update(&[file]);
}
// #[wasm_bindgen]
// pub fn create_file(&mut self, name: &str) {
// let id = self.stack.get_new_file_id();
// let file = Rc::new(GPXFile::new(id, name));
// self.stack.update(&[file]);
// }
}
+11
View File
@@ -0,0 +1,11 @@
#[derive(Debug, Default)]
pub struct Link {
pub href: String,
pub text: Option<String>,
}
#[derive(Debug, Default)]
pub struct LngLat {
pub lng: f64,
pub lat: f64,
}
+26
View File
@@ -0,0 +1,26 @@
use std::rc::Rc;
use crate::gpx::{Link, Track, WaypointChunk};
#[derive(Debug, Default)]
pub struct GPXFile {
pub info: GPXFileInfo,
pub trk: Vec<Track>,
pub wpt: Vec<Rc<WaypointChunk>>,
}
#[derive(Debug, Default)]
pub struct GPXFileInfo {
pub name: String,
pub desc: Option<String>,
pub author: Option<Author>,
pub link: Option<Link>,
pub time: Option<i64>,
}
#[derive(Debug, Default)]
pub struct Author {
pub name: Option<String>,
pub email: Option<String>,
pub link: Option<Link>,
}
+13
View File
@@ -0,0 +1,13 @@
mod common;
mod file;
mod segment;
mod track;
mod trackpoint;
mod waypoint;
pub use common::*;
pub use file::*;
pub use segment::*;
pub use track::*;
pub use trackpoint::*;
pub use waypoint::*;
+39
View File
@@ -0,0 +1,39 @@
use std::{cell::RefCell, rc::Rc};
use crate::gpx::TrackPoint;
#[derive(Debug, Default)]
pub struct TrackSegment {
pub chunks: Vec<Rc<RefCell<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;
#[derive(Debug, Default)]
pub struct TrackPointChunk {
pub trkpt: Vec<TrackPoint>,
}
+20
View File
@@ -0,0 +1,20 @@
use crate::gpx::{Link, TrackSegment};
#[derive(Debug, Default)]
pub struct Track {
pub info: TrackInfo,
pub trkseg: Vec<TrackSegment>,
}
#[derive(Debug, Default)]
pub struct TrackInfo {
pub name: Option<String>,
pub cmt: Option<String>,
pub desc: Option<String>,
pub src: Option<String>,
pub link: Option<Link>,
pub type_: Option<String>,
pub color: Option<String>,
pub opacity: Option<f64>,
pub width: Option<f64>,
}
+13
View File
@@ -0,0 +1,13 @@
use crate::gpx::LngLat;
#[derive(Debug, Default)]
pub struct TrackPoint {
pub coordinates: LngLat,
pub ele: f64,
pub time: Option<i64>,
pub hr: Option<u16>,
pub cad: Option<u16>,
pub power: Option<u16>,
pub atemp: Option<i16>,
// TODO OSM data? or store intervals at a higher level?
}
+16
View File
@@ -0,0 +1,16 @@
use crate::gpx::{Link, LngLat};
pub type WaypointChunk = Vec<Waypoint>;
#[derive(Debug, Default)]
pub struct Waypoint {
pub coordinates: LngLat,
pub ele: f64,
pub time: Option<i64>,
pub name: Option<String>,
pub cmt: Option<String>,
pub desc: Option<String>,
pub link: Option<Link>,
pub sym: Option<String>,
pub type_: Option<String>,
}
+2 -1
View File
@@ -1,3 +1,4 @@
mod actions;
mod controller;
mod gpx;
mod stack;
mod types;
+6 -4
View File
@@ -1,6 +1,8 @@
use std::{collections::HashMap, rc::Rc};
use crate::types::{GPXFile, GPXFileId};
use crate::gpx::GPXFile;
pub type GPXFileId = usize;
#[derive(Default)]
pub struct Stack {
@@ -29,13 +31,13 @@ impl Stack {
}
}
pub fn update(&mut self, files: &[Rc<GPXFile>]) {
pub fn update(&mut self, files: &[Rc<GPXFile>], ids: &[GPXFileId]) {
let mut next = match self.current() {
Some(current) => current.clone(),
None => StackEntry::default(),
};
for file in files {
next.insert(file.id, file.clone());
for (file, id) in files.iter().zip(ids) {
next.insert(*id, file.clone());
}
self.push(next);
}
-106
View File
@@ -1,106 +0,0 @@
use std::rc::Rc;
pub type GPXFileId = usize;
pub struct GPXFile {
pub id: GPXFileId,
pub info: Rc<GPXFileInfo>,
pub trk: Vec<Track>,
pub wpt: Vec<Rc<WaypointChunk>>,
}
impl GPXFile {
pub fn new(id: GPXFileId, name: &str) -> Self {
Self {
id,
info: Rc::new(GPXFileInfo::new(name)),
trk: Vec::new(),
wpt: Vec::new(),
}
}
}
pub struct GPXFileInfo {
pub name: String,
pub desc: Option<String>,
pub author: Option<Author>,
pub link: Option<Link>,
pub time: Option<i64>,
}
impl GPXFileInfo {
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
desc: None,
author: None,
link: None,
time: None,
}
}
}
pub struct Author {
pub name: Option<String>,
pub email: Option<String>,
pub link: Option<Link>,
}
pub struct Link {
pub href: String,
pub text: Option<String>,
pub type_: Option<String>,
}
pub struct Track {
pub info: Rc<TrackInfo>,
pub trkseg: Vec<TrackSegment>,
}
pub struct TrackInfo {
name: Option<String>,
cmt: Option<String>,
desc: Option<String>,
src: Option<String>,
link: Option<Link>,
type_: Option<String>,
color: Option<String>,
opacity: Option<f64>,
width: Option<f64>,
}
pub struct TrackSegment {
pub trkpt: Vec<Rc<TrackPointChunk>>,
}
pub type TrackPointChunk = Vec<TrackPoint>;
pub struct TrackPoint {
pub coordinates: LngLat,
pub ele: f64,
pub time: Option<i64>,
pub hr: Option<u16>,
pub cad: Option<u16>,
pub power: Option<u16>,
pub atemp: Option<i16>,
// TODO OSM data? or store intervals at a higher level?
}
pub type WaypointChunk = Vec<Waypoint>;
pub struct Waypoint {
pub coordinates: LngLat,
pub ele: f64,
pub time: Option<i64>,
pub name: Option<String>,
pub cmt: Option<String>,
pub desc: Option<String>,
pub link: Option<Link>,
pub sym: Option<String>,
pub type_: Option<String>,
}
pub struct LngLat {
pub lng: f64,
pub lat: f64,
}