Skip to content

Commit

Permalink
Merge pull request #10 from JeromeSchmied/babkelme
Browse files Browse the repository at this point in the history
babkelme
  • Loading branch information
JeromeSchmied authored Jan 20, 2025
2 parents bb3d754 + 2af315a commit 5221beb
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 60 deletions.
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fit2gpx"
version = "0.4.0"
version = "0.5.0"
edition = "2021"

authors = ["Jeromos Kovács <[email protected]>"]
Expand All @@ -18,7 +18,7 @@ fit_file = "0.6.0"
geo-types = "0.7.14"
gpx = "0.10.0"
rayon = "1.10.0"
srtm_reader = { version = "0.3.1", optional = true }
srtm_reader = { version = "0.4.0", optional = true }
time = { version = "0.3.37", default-features = false }

[patch.crates-io]
Expand Down
26 changes: 16 additions & 10 deletions src/elevation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ pub fn read_needed_tiles(
if needs.is_empty() {
return vec![];
}
let elev_data_dir = elev_data_dir.as_ref();

let elev_data_dir = elev_data_dir.as_ref();
needs
.par_iter()
.map(|c| srtm_reader::get_filename(*c))
Expand All @@ -44,7 +44,11 @@ pub fn get_all_elev_data<'a>(
needs: &'a [(i32, i32)],
tiles: &'a [srtm_reader::Tile],
) -> HashMap<&'a (i32, i32), &'a srtm_reader::Tile> {
assert_eq!(needs.len(), tiles.len());
assert_eq!(
needs.len(),
tiles.len(),
"number of needed tiles and loaded tiles not equal"
);
needs
.par_iter()
.enumerate()
Expand All @@ -68,32 +72,34 @@ pub fn get_all_elev_data<'a>(
/// using the following order, it should be safe
///
/// ```no_run
/// use fit2gpx::elevation::*;
/// use fit2gpx::elevation;
///
/// let mut fit = fit2gpx::Fit::from_file("evening walk.gpx").unwrap();
/// let elev_data_dir = "/home/me/Downloads/srtm_data";
/// let needed_tile_coords = needed_tile_coords(&fit.track_segment.points);
/// let needed_tiles = read_needed_tiles(&needed_tile_coords, elev_data_dir);
/// let all_elev_data = get_all_elev_data(&needed_tile_coords, &needed_tiles);
/// let elev_data_dir = "~/Downloads/srtm_data";
/// let needed_tile_coords = elevation::needed_tile_coords(&fit.track_segment.points);
/// let needed_tiles = elevation::read_needed_tiles(&needed_tile_coords, elev_data_dir);
/// let all_elev_data = elevation::get_all_elev_data(&needed_tile_coords, &needed_tiles);
///
/// add_elev_unchecked(&mut fit.track_segment.points, &all_elev_data);
/// elevation::add_elev_unchecked(&mut fit.track_segment.points, &all_elev_data, false);
/// ```
pub fn add_elev_unchecked(
wps: &mut [Waypoint],
elev_data: &HashMap<&(i32, i32), &srtm_reader::Tile>,
overwrite: bool,
) {
// coord is x,y but we need y,x
let xy_yx = |wp: &Waypoint| -> srtm_reader::Coord {
let (x, y) = wp.point().x_y();
(y, x).into()
};
wps.into_par_iter()
.filter(|wp| wp.elevation.is_none() && !is_00(wp))
.filter(|wp| (wp.elevation.is_none() || overwrite) && !is_00(wp))
.for_each(|wp| {
let coord = xy_yx(wp);
let elev_data = elev_data
.get(&coord.trunc())
.expect("elevation data must be loaded");
wp.elevation = Some(elev_data.get(coord) as f64);
let elev = elev_data.get(coord);
wp.elevation = elev.map(|x| *x as f64);
});
}
54 changes: 20 additions & 34 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@
// TODO: proper docs

use crate::utils::*;
use fit_file::{fit_file, FitFieldValue, FitRecordMsg, FitSessionMsg};
use fit_file::{fit_file, FitFieldValue, FitRecordMsg};
use gpx::{Gpx, GpxVersion, Track, TrackSegment, Waypoint};
use std::{fs::File, io::BufWriter, path::Path};
use std::{
fs::File,
io::BufWriter,
path::{Path, PathBuf},
};

/// universal Result, but not sendable
pub type Res<T> = Result<T, Box<dyn std::error::Error>>;
Expand All @@ -31,14 +35,13 @@ pub fn convert_file(fit_path: impl AsRef<Path>) -> Res<()> {
fit.save_to_gpx()
}
pub fn convert_fit(read: impl std::io::Read, fname: impl AsRef<Path>) -> Res<()> {
let fit = Fit::from_fit(read)?.with_filename(fname.as_ref().to_str().unwrap());
let fit = Fit::from_fit(read)?.with_filename(fname.as_ref());
fit.save_to_gpx()
}

pub fn write_gpx_to_file(gpx: Gpx, fname: impl AsRef<Path>) -> Res<()> {
let fpath = Path::new(fname.as_ref());
// Create file at path
let gpx_file = File::create(fpath)?;
// Create file at `fname`
let gpx_file = File::create(fname.as_ref())?;
let buf = BufWriter::new(gpx_file);

// Write to file
Expand All @@ -49,18 +52,13 @@ pub fn write_gpx_to_file(gpx: Gpx, fname: impl AsRef<Path>) -> Res<()> {
/// Fit Context structure. An instance of this will be passed to the parser and ultimately to the callback function so we can use it for whatever.
#[derive(Default, Clone)]
pub struct Fit {
file_name: String,
sum00: u32,
pub file_name: PathBuf,
num_records_processed: u16,
pub track_segment: TrackSegment,
}
impl Fit {
/// no need to clone the whole [`Fit`], only the `file_name`: a [`String`]
pub fn file_name(&self) -> String {
self.file_name.to_owned()
}
/// add a filename to `self`, create new instance
pub fn with_filename(self, fname: impl Into<String>) -> Self {
pub fn with_filename(self, fname: impl Into<PathBuf>) -> Self {
Fit {
file_name: fname.into(),
..self
Expand All @@ -72,7 +70,7 @@ impl Fit {
let file = std::fs::File::open(&fit_path)?;
let mut bufread = std::io::BufReader::new(file);

Ok(Self::from_fit(&mut bufread)?.with_filename(fit_path.as_ref().to_str().unwrap()))
Ok(Self::from_fit(&mut bufread)?.with_filename(fit_path.as_ref()))
}

/// Called for each record message as it is being processed.
Expand All @@ -86,11 +84,11 @@ impl Fit {
data: &mut Fit,
) {
if global_message_num == fit_file::GLOBAL_MSG_NUM_SESSION {
let msg = FitSessionMsg::new(fields);
let sport_names = fit_file::init_sport_name_map();
let sport_id = msg.sport.unwrap();
// let msg = FitSessionMsg::new(fields);
// let sport_names = fit_file::init_sport_name_map();
// let sport_id = msg.sport.unwrap();

println!("Sport: {}", sport_names.get(&sport_id).unwrap());
// println!("Sport: {}", sport_names.get(&sport_id).unwrap());
} else if global_message_num == fit_file::GLOBAL_MSG_NUM_RECORD {
let mut msg = FitRecordMsg::new(fields);

Expand All @@ -102,10 +100,6 @@ impl Fit {
msg.timestamp = Some(timestamp);
}

if utils::no_lat_lon(&msg) {
data.sum00 += 1;
}

let wp = frm_to_gwp(msg);
data.track_segment.points.push(wp);
}
Expand All @@ -118,35 +112,27 @@ impl Fit {
let mut bufread = std::io::BufReader::new(reader);
fit_file::read(&mut bufread, Self::callback, &mut fit)?;

let percent_00 = fit.sum00 as f32 / fit.track_segment.points.len() as f32;
let no_00_remains = fit.sum00 > 0 && percent_00 < 0.9;
if no_00_remains {
eprintln!("less than 90% ({} out of {} = {percent_00}) doesn't contain latitude and longitude => deleting these points",
fit.sum00, fit.track_segment.points.len());
}
fit.track_segment.points.retain(|wp| {
let (x, y) = wp.point().x_y();
(!no_00_remains || !is_00(wp))
&& (-90. ..90.).contains(&y)
&& (-180. ..180.).contains(&x)
!is_00(wp) && (-90. ..90.).contains(&y) && (-180. ..180.).contains(&x)
});
Ok(fit)
}
pub fn save_to_gpx(self) -> Res<()> {
let fname = self.file_name();
let fname = self.file_name.with_extension("gpx");
let gpx: Gpx = self.into();
write_gpx_to_file(gpx, &fname)
}

#[cfg(feature = "elevation")]
/// add elevation data to the `fit` file, using srtm data from `elev_data_dir`
pub fn add_elev(fit: &mut Fit, elev_data_dir: impl AsRef<Path>) {
pub fn add_elev(fit: &mut Fit, elev_data_dir: impl AsRef<Path>, overwrite: bool) {
use elevation::*;
let needed_tile_coords = needed_tile_coords(&fit.track_segment.points);
let needed_tiles = read_needed_tiles(&needed_tile_coords, elev_data_dir);
let all_elev_data = get_all_elev_data(&needed_tile_coords, &needed_tiles);

add_elev_unchecked(&mut fit.track_segment.points, &all_elev_data);
add_elev_unchecked(&mut fit.track_segment.points, &all_elev_data, overwrite);
}
}
impl From<Fit> for Gpx {
Expand Down
28 changes: 21 additions & 7 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ use fit2gpx::{elevation::*, Fit, Res};
use rayon::prelude::*;
use std::path::PathBuf;

#[derive(Parser, Clone)]
#[derive(Parser, Clone, Debug, PartialEq, Eq)]
#[command(version, about, long_about = None)]
struct Args {
pub files: Vec<PathBuf>,
#[cfg(feature = "elevation")]
#[arg(short = 'd', long, env)]
#[arg(short = 'd', long, env, default_value = "/tmp")]
pub elev_data_dir: PathBuf,
#[cfg(feature = "elevation")]
#[arg(short, long, default_value_t = false, requires = "elev_data_dir")]
Expand All @@ -20,14 +20,18 @@ struct Args {
fn main() -> Res<()> {
// collecting cli args
let conf = Args::parse();
// TODO: appropriate logging
dbg!(&conf.elev_data_dir);
dbg!(&conf.add_elevation);
dbg!(&conf.overwrite);

// reading all .fit files into memory, considering whether it should be overwritten
let all_fit = conf
.files
.par_iter()
.filter(|f| {
f.extension().is_some_and(|x| x == "fit")
&& (conf.overwrite || !f.with_extension(".gpx").exists())
&& (conf.overwrite || !f.with_extension("gpx").exists())
})
.flat_map(|f| Fit::from_file(f).inspect_err(|e| eprintln!("read error: {e:?}")))
.collect::<Vec<_>>();
Expand Down Expand Up @@ -61,11 +65,21 @@ fn main() -> Res<()> {
.try_for_each(|mut fit: Fit| -> Result<(), &'static str> {
#[cfg(feature = "elevation")]
if conf.add_elevation {
add_elev_unchecked(&mut fit.track_segment.points, &all_elev_data);
dbg!(&fit.file_name);
add_elev_unchecked(
&mut fit.track_segment.points,
&all_elev_data,
conf.overwrite,
);
}
if !fit.track_segment.points.is_empty() {
fit.save_to_gpx()
.inspect_err(|e| eprintln!("conversion error: {e:?}"))
.map_err(|_| "conversion error")
} else {
eprintln!("{:?}: empty trkseg, ignoring...", fit.file_name);
Ok(())
}
fit.save_to_gpx()
.inspect_err(|e| eprintln!("conversion error: {e:?}"))
.map_err(|_| "conversion error")
})?;

Ok(())
Expand Down
4 changes: 0 additions & 4 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,6 @@ pub(crate) fn frm_to_gwp(frm: FitRecordMsg) -> Waypoint {
wp
}

// TODO: docs
pub(crate) fn no_lat_lon(frm: &FitRecordMsg) -> bool {
frm.position_long.is_none() && frm.position_lat.is_none()
}
// TODO: docs
pub(crate) fn is_00(wp: &Waypoint) -> bool {
wp.point().x_y() == (0., 0.)
Expand Down

0 comments on commit 5221beb

Please sign in to comment.