use crate::{Res, utils};
use fit_file::{FitFieldValue, FitRecordMsg, fit_file};
use geo_types::{Point, coord};
use gpx::{Gpx, GpxVersion, Track, TrackSegment, Waypoint};
use std::path::{Path, PathBuf};
use std::{fs, io};
use time::OffsetDateTime;
#[derive(Default, Clone)]
pub struct Fit {
pub file_name: PathBuf,
pub track_segment: TrackSegment,
}
impl Fit {
#[must_use]
pub fn with_filename(self, fname: impl Into<PathBuf>) -> Self {
Fit {
file_name: fname.into(),
..self
}
}
pub fn from_file(fit_path: impl AsRef<Path>) -> Res<Self> {
let mut file = fs::File::open(&fit_path)?;
Ok(Self::from_reader(&mut file)?.with_filename(fit_path.as_ref()))
}
pub fn from_reader(reader: impl io::Read) -> Res<Self> {
let mut fit = Fit::default();
let mut bufread = io::BufReader::new(reader);
fit_file::read(&mut bufread, Self::callback, &mut fit)?;
fit.track_segment.points.retain(|wp| {
let (x, y) = wp.point().x_y();
!utils::is_00(wp) && (-90. ..90.).contains(&y) && (-180. ..180.).contains(&x)
});
Ok(fit)
}
pub fn file_to_gpx(fit_path: impl AsRef<Path>, fname: impl AsRef<Path>) -> Res<()> {
let fit = Fit::from_file(fit_path)?;
fit.save_to_gpx(fname)
}
pub fn reader_to_gpx(read: impl io::Read, fname: impl AsRef<Path>) -> Res<()> {
let fit = Fit::from_reader(read)?;
fit.save_to_gpx(fname)
}
pub fn save_to_gpx(self, fname: impl AsRef<Path>) -> Res<()> {
let gpx: Gpx = self.into();
utils::write_gpx_to_file(gpx, fname)
}
}
impl Fit {
fn frm_to_gwp(frm: FitRecordMsg) -> Waypoint {
let time = frm.timestamp.unwrap_or(0);
let time = OffsetDateTime::from_unix_timestamp(time.into()).ok();
let lat = fit_file::semicircles_to_degrees(frm.position_lat.unwrap_or(0));
let lon = fit_file::semicircles_to_degrees(frm.position_long.unwrap_or(0));
let alt = if let Some(enh_alt) = frm.enhanced_altitude {
Some(enh_alt)
} else {
frm.altitude.map(Into::into)
}
.map(|alt| alt as f32 / 5. - 500.);
let speed = if let Some(enh_spd) = frm.enhanced_speed {
Some(enh_spd)
} else {
frm.speed.map(Into::into)
}
.map(f64::from);
let geo_point = Point(coord! {x: lon, y: lat});
let mut wp = Waypoint::new(geo_point);
wp.elevation = alt.map(Into::into);
wp.time = time.map(Into::into);
wp.speed = speed;
wp
}
fn callback(
timestamp: u32,
global_message_num: u16,
_local_msg_type: u8,
_message_index: u16,
fields: Vec<FitFieldValue>,
data: &mut Fit,
) {
if global_message_num == fit_file::GLOBAL_MSG_NUM_RECORD {
let mut msg = FitRecordMsg::new(fields);
msg.timestamp = Some(timestamp);
let wp = Self::frm_to_gwp(msg);
data.track_segment.points.push(wp);
}
}
}
impl From<Fit> for Gpx {
fn from(fit: Fit) -> Self {
let track = Track {
segments: vec![fit.track_segment],
..Track::default()
};
Self {
version: GpxVersion::Gpx11,
tracks: vec![track],
..Self::default()
}
}
}