mod algorithm;
pub mod course;
mod fit;
mod geographic;
mod gpx;
#[doc(hidden)]
pub mod internal;
mod measure;
mod point_type;
mod types;
use std::convert::Infallible;
use std::io::{BufRead, Write};
use dimensioned::si::Meter;
pub use fit::{FitCourseOptions, FitEncodeError};
use thiserror::Error;
use tracing::{Level, debug, span};
use crate::algorithm::AlgorithmError;
use crate::course::{
Course, CourseError, CoursePoint, CourseSet, CourseSetBuilder, CourseSetOptions,
};
pub use crate::fit::{CourseFile, CoursePointType, Sport};
use crate::geographic::GeographicError;
use crate::gpx::{GpxItem, GpxReader};
pub use crate::measure::{DEG, Degree};
use crate::point_type::{GpxCreator, get_course_point_type, get_gpx_creator};
pub use crate::types::{GeoPoint, TypeError};
#[derive(Error, Debug)]
#[non_exhaustive]
pub enum CoursePointerError {
#[error("I/O error")]
Io(#[from] std::io::Error),
#[error("GPX processing error")]
Gpx(#[from] gpx::GpxError),
#[error("GPX items received in unexpected order")]
GpxOrder,
#[error("Course error")]
Course(#[from] CourseError),
#[error("Unexpected number of courses (tracks or routes) in input: {0}")]
CourseCount(usize),
#[error("Course does not contain any records")]
EmptyRecords,
#[error("FIT encoding error")]
FitEncode(#[from] FitEncodeError),
#[error("Core type error")]
Type(#[from] TypeError),
#[error("Geographic calculation error")]
Geographic(#[from] GeographicError),
#[error("Infallible")]
Infallible(#[from] Infallible),
#[error("Algorithm")]
Algorithm(#[from] AlgorithmError),
}
pub type Result<T> = std::result::Result<T, CoursePointerError>;
#[derive(Clone, Debug)]
pub struct ConversionInfo {
pub course_name: Option<String>,
pub total_distance: Meter<f64>,
pub num_waypoints: usize,
pub course_points: Vec<CoursePoint>,
}
pub fn convert_gpx_to_fit<R: BufRead, W: Write>(
gpx_input: R,
fit_output: W,
course_options: CourseSetOptions,
fit_options: FitCourseOptions,
) -> Result<ConversionInfo> {
let mut course_set = read_gpx(course_options, gpx_input)?;
let course = course_set.courses.remove(0);
write_fit_course(&course, fit_output, fit_options)?;
Ok(ConversionInfo {
course_name: course.name.clone(),
total_distance: course.records.last().unwrap().cumulative_distance,
num_waypoints: course_set.num_waypoints,
course_points: course.course_points,
})
}
pub fn read_gpx<R: BufRead>(options: CourseSetOptions, gpx_input: R) -> Result<CourseSet> {
let mut builder = CourseSetBuilder::new(options);
{
let span = span!(Level::DEBUG, "read_input");
let _guard = span.enter();
let mut num_items = 0usize;
let mut skipped_items = 0usize;
let mut creator = GpxCreator::Unknown;
let gpx_reader = GpxReader::from_reader(gpx_input);
for item in gpx_reader {
let item = item?;
num_items += 1;
match item {
GpxItem::Creator(s) => {
creator = get_gpx_creator(s.as_str());
}
GpxItem::TrackOrRoute => {
builder.add_route();
}
GpxItem::TrackOrRouteName(name) => {
builder
.last_route_mut()
.ok_or(CoursePointerError::GpxOrder)?
.with_name(name);
}
GpxItem::TrackOrRoutePoint(p) => {
builder
.last_route_mut()
.ok_or(CoursePointerError::GpxOrder)?
.with_route_point(p);
}
GpxItem::Waypoint(wpt) => {
builder.add_waypoint(wpt.point, get_course_point_type(creator, &wpt), wpt.name);
}
_ => {
skipped_items += 1;
}
}
}
debug!(
"Read {} GpxItem(s), matching {}",
num_items,
num_items - skipped_items
);
}
if builder.num_routes() != 1usize {
return Err(CoursePointerError::CourseCount(builder.num_routes()));
}
Ok(builder.build()?)
}
pub fn write_fit_course<W: Write>(
course: &Course,
fit_output: W,
options: FitCourseOptions,
) -> Result<()> {
let course_file = CourseFile::new(course, options);
course_file.encode(fit_output)?;
Ok(())
}